blob: 421cc697afbd7d9ec210140ff89d03b0bdbcd16c [file] [log] [blame]
// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
#include <strmif.h>
#include <uuids.h>
#include "webmsplitfilter.h"
#include "cenumpins.h"
#include "mkvparser.hpp"
#include "mkvparserstreamvideo.h"
#include "mkvparserstreamaudio.h"
#include "webmsplitoutpin.h"
#include "webmtypes.h"
#include <new>
#include <cassert>
#include <vfwmsgs.h>
#include <process.h>
#include <evcode.h>
#include <limits>
#ifdef _DEBUG
#include "iidstr.h"
#include "odbgstream.h"
using std::endl;
using std::hex;
using std::dec;
#endif
using std::wstring;
//using std::wistringstream;
namespace WebmSplit
{
const LONGLONG Filter::kNoSeek(std::numeric_limits<LONGLONG>::min());
HRESULT CreateInstance(
IClassFactory* pClassFactory,
IUnknown* pOuter,
const IID& iid,
void** ppv)
{
if (ppv == 0)
return E_POINTER;
*ppv = 0;
if ((pOuter != 0) && (iid != __uuidof(IUnknown)))
return E_INVALIDARG;
Filter* p = new (std::nothrow) Filter(pClassFactory, pOuter);
if (p == 0)
return E_OUTOFMEMORY;
assert(p->m_nondelegating.m_cRef == 0);
const HRESULT hr = p->m_nondelegating.QueryInterface(iid, ppv);
if (SUCCEEDED(hr))
{
assert(*ppv);
assert(p->m_nondelegating.m_cRef == 1);
return S_OK;
}
assert(*ppv == 0);
assert(p->m_nondelegating.m_cRef == 0);
delete p;
p = 0;
return hr;
}
#pragma warning(disable:4355) //'this' ptr in member init list
Filter::Filter(IClassFactory* pClassFactory, IUnknown* pOuter)
: m_pClassFactory(pClassFactory),
m_nondelegating(this),
m_pOuter(pOuter ? pOuter : &m_nondelegating),
m_state(State_Stopped),
m_clock(0),
m_hThread(0),
m_pSegment(0),
m_pSeekBase(0),
m_seekBase_ns(-1),
m_currTime(kNoSeek),
m_inpin(this),
m_cStarvation(-1) //means "not starving"
{
m_pClassFactory->LockServer(TRUE);
const HRESULT hr = CLockable::Init();
hr;
assert(SUCCEEDED(hr));
m_hNewCluster = CreateEvent(0, 0, 0, 0);
assert(m_hNewCluster); //TODO
m_info.pGraph = 0;
m_info.achName[0] = L'\0';
#ifdef _DEBUG
odbgstream os;
os << "WebmSplit::ctor" << endl;
#endif
}
#pragma warning(default:4355)
Filter::~Filter()
{
#ifdef _DEBUG
odbgstream os;
os << "WebmSplit::dtor" << endl;
#endif
assert(m_hThread == 0);
assert(m_outpins.empty());
assert(m_pSegment == 0);
m_pClassFactory->LockServer(FALSE);
}
void Filter::Init()
{
assert(m_hThread == 0);
if (!m_inpin.m_reader.IsOpen())
return;
if (m_pSegment->DoneParsing())
return; //nothing for thread to do
const uintptr_t h = _beginthreadex(
0, //security
0, //stack size
&Filter::ThreadProc,
this,
0, //run immediately
0); //thread id
m_hThread = reinterpret_cast<HANDLE>(h);
assert(m_hThread);
}
void Filter::Final()
{
if (m_hThread == 0)
return;
assert(m_inpin.m_reader.IsOpen());
//odbgstream os;
//os << "WebmSplit::Filter::Final(begin)" << endl;
//os << "WebmSplit::Filter::Final: calling BeginFlush" << endl;
//TODO: calling BeginFlush has the exact opposite semantics that
//I thought it did: if flush is in effect, then the SyncRead blocks
//indefinitely, until EndFlush is called. In the local file playback
//case this isn't a problem, since SyncRead will never officially block.
//The problem case occurs when this is a slow network download, and
//SyncRead blocks. I thought that BeginFlush could be used to cancel
//the SyncRead in progress, but that doesn't appear to be the case.
//(Apparently it cancels asyncronous reads, not synchronous reads.)
//This only really matters during the transition to stopped. In that
//case we could do something ugly like timeout the wait for signal
//of thread termination, then if timeout occurs then forcibly
//terminate the thread (but I don't know if that will work either).
//The only other alternative is to use proper timed reads, but
//that requires that reads be aligned.
//HRESULT hr = pReader->BeginFlush();
//assert(SUCCEEDED(hr));
//
//os << "WebmSplit::Filter::Final: called BeginFlush; "
// << "waiting for thread termination"
//<< endl;
//HRESULT hr = m_inpin.m_reader.Cancel();
//assert(SUCCEEDED(hr));
const DWORD dw = WaitForSingleObject(m_hThread, INFINITE);
dw;
assert(dw == WAIT_OBJECT_0);
//os << "WebmSplit::Filter::Final: thread terminated" << endl;
const BOOL b = CloseHandle(m_hThread);
b;
assert(b);
m_hThread = 0;
//os << "WebmSplit::Filter::Final: calling EndFlush" << endl;
//hr = pReader->EndFlush();
//assert(SUCCEEDED(hr));
//os << "WebmSplit::Filter::Final: called EndFlush" << endl;
//os << "WebmSplit::Filter::Final(end)" << endl;
}
Filter::CNondelegating::CNondelegating(Filter* p)
: m_pFilter(p),
m_cRef(0) //see CreateInstance
{
}
Filter::CNondelegating::~CNondelegating()
{
}
HRESULT Filter::CNondelegating::QueryInterface(
const IID& iid,
void** ppv)
{
if (ppv == 0)
return E_POINTER;
IUnknown*& pUnk = reinterpret_cast<IUnknown*&>(*ppv);
if (iid == __uuidof(IUnknown))
{
pUnk = this; //must be nondelegating
}
else if ((iid == __uuidof(IBaseFilter)) ||
(iid == __uuidof(IMediaFilter)) ||
(iid == __uuidof(IPersist)))
{
pUnk = static_cast<IBaseFilter*>(m_pFilter);
}
else
{
#if 0
wodbgstream os;
os << "mkvsource::filter::QI: iid=" << IIDStr(iid) << std::endl;
#endif
pUnk = 0;
return E_NOINTERFACE;
}
pUnk->AddRef();
return S_OK;
}
ULONG Filter::CNondelegating::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
ULONG Filter::CNondelegating::Release()
{
const LONG n = InterlockedDecrement(&m_cRef);
//odbgstream os;
//os << "Filter::Release: n=" << n << endl;
if (n > 0)
return n;
delete m_pFilter;
return 0;
}
HRESULT Filter::QueryInterface(const IID& iid, void** ppv)
{
return m_pOuter->QueryInterface(iid, ppv);
}
ULONG Filter::AddRef()
{
return m_pOuter->AddRef();
}
ULONG Filter::Release()
{
return m_pOuter->Release();
}
HRESULT Filter::GetClassID(CLSID* p)
{
if (p == 0)
return E_POINTER;
*p = WebmTypes::CLSID_WebmSplit;
return S_OK;
}
HRESULT Filter::Stop()
{
//Stop is a synchronous operation: when it completes,
//the filter is stopped.
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
switch (m_state)
{
case State_Paused:
case State_Running:
//Stop is synchronous. When stop completes, all threads
//should be stopped. What does "stopped" mean" In our
//case it probably means "terminated".
//It's a bit tricky here because we hold the filter
//lock. If threads need to acquire filter lock
//then we'll have to release it. Only the FGM can call
//Stop, etc, so there's no problem to release lock
//while Stop is executing, to allow threads to acquire
//filter lock temporarily.
//The streaming thread will receiving an indication
//automatically (assuming it's connected), either via
//GetBuffer or Receive, so there's nothing this filter
//needs to do to tell the streaming thread to stop.
//One implementation strategy is to have build a
//vector of thread handles, and then wait for a signal
//on one of them. When the handle is signalled
//(meaning that the thread has terminated), then
//we remove that handle from the vector, close the
//handle, and the wait again. Repeat until the
//all threads have been terminated.
//We also need to clean up any unused samples,
//and decommit the allocator. (In fact, we could
//decommit the allocator immediately, and then wait
//for the threads to terminated.)
m_state = State_Stopped;
hr = m_inpin.m_reader.BeginFlush();
assert(SUCCEEDED(hr));
lock.Release();
OnStop();
hr = lock.Seize(this);
assert(SUCCEEDED(hr)); //TODO
hr = m_inpin.m_reader.EndFlush();
assert(SUCCEEDED(hr));
break;
case State_Stopped:
default:
break;
}
return S_OK;
}
HRESULT Filter::Pause()
{
//Unlike Stop(), Pause() can be asynchronous (that's why you have
//GetState()). We could use that here to build the samples index.
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
//odbgstream os;
//os << "WebmSplit::Filter::Pause" << endl;
switch (m_state)
{
case State_Stopped:
OnStart();
break;
case State_Running:
case State_Paused:
default:
break;
}
m_state = State_Paused;
return S_OK;
}
HRESULT Filter::Run(REFERENCE_TIME start)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
//odbgstream os;
//os << "WebmSplit::Filter::Run" << endl;
switch (m_state)
{
case State_Stopped:
OnStart();
break;
case State_Paused:
case State_Running:
default:
break;
}
m_start = start;
m_state = State_Running;
return S_OK;
}
HRESULT Filter::GetState(
DWORD timeout,
FILTER_STATE* p)
{
if (p == 0)
return E_POINTER;
//What the GetState.timeout parameter refers to is not to locking
//the filter, but rather to waiting to determine the current state.
//A request to Stop is always synchronous (hence no timeout parameter),
//but a request to Pause can be asynchronous, so the caller can say
//how long he's willing to wait for the transition (to paused) to
//complete.
//TODO: implement a waiting scheme here. We'll probably have to
//use SignalObjectAndWait atomically release the mutex and then
//wait for the condition variable to change.
//if (hr == VFW_E_TIMEOUT)
// return VFW_S_STATE_INTERMEDIATE;
Lock lock;
HRESULT hrLock = lock.Seize(this);
//The lock is only used for synchronization. If Seize fails,
//it means there's a serious problem with the filter.
if (FAILED(hrLock))
return E_FAIL;
FILTER_STATE& state = *p;
if (m_cStarvation < 0) //not starving
{
state = m_state;
return S_OK;
}
assert(m_pSegment);
long count = m_pSegment->GetCount();
if (count > m_cStarvation)
{
m_cStarvation = -1;
state = m_state; //TODO: should be State_Paused?
return S_OK;
}
for (;;)
{
lock.Release();
DWORD index;
//TODO: this timeout isn't quite correct. The parameter refers
//to the total wait time. As used here in the call to WaitForHandles,
//it refers to the wait time for this pass through the loop.
const HRESULT hrWait = CoWaitForMultipleHandles(
0, //wait flags
timeout,
1,
&m_hNewCluster,
&index);
if (SUCCEEDED(hrWait))
assert(index == 0);
else if (hrWait != RPC_S_CALLPENDING) //error, despite "S" in name
return hrWait;
hrLock = lock.Seize(this);
if (FAILED(hrLock))
return E_FAIL;
count = m_pSegment->GetCount();
if (count > m_cStarvation)
{
m_cStarvation = -1;
state = m_state; //TODO: should be State_Paused?
return S_OK;
}
if (FAILED(hrWait)) //there was a timeout before receiving signal
return VFW_S_STATE_INTERMEDIATE;
}
}
HRESULT Filter::SetSyncSource(
IReferenceClock* clock)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
if (m_clock)
m_clock->Release();
m_clock = clock;
if (m_clock)
m_clock->AddRef();
return S_OK;
}
HRESULT Filter::GetSyncSource(
IReferenceClock** pclock)
{
if (pclock == 0)
return E_POINTER;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
IReferenceClock*& clock = *pclock;
clock = m_clock;
if (clock)
clock->AddRef();
return S_OK;
}
HRESULT Filter::EnumPins(IEnumPins** pp)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
const ULONG outpins_count = static_cast<ULONG>(m_outpins.size());
const ULONG n = 1 + outpins_count;
//odbgstream os;
//os << "WebmSplit::filter::enumpins: n=" << n << endl;
const size_t cb = n * sizeof(IPin*);
IPin** const pins = (IPin**)_alloca(cb);
IPin** pin = pins;
*pin++ = &m_inpin;
typedef outpins_t::iterator iter_t;
iter_t i = m_outpins.begin();
const iter_t j = m_outpins.end();
while (i != j)
*pin++ = *i++;
return CEnumPins::CreateInstance(pins, n, pp);
}
HRESULT Filter::FindPin(
LPCWSTR id1,
IPin** pp)
{
if (pp == 0)
return E_POINTER;
IPin*& p = *pp;
p = 0;
if (id1 == 0)
return E_INVALIDARG;
{
Pin* const pPin = &m_inpin;
const wstring& id2_ = pPin->m_id;
const wchar_t* const id2 = id2_.c_str();
if (wcscmp(id1, id2) == 0) //case-sensitive
{
p = pPin;
p->AddRef();
return S_OK;
}
}
typedef outpins_t::const_iterator iter_t;
iter_t i = m_outpins.begin();
const iter_t j = m_outpins.end();
while (i != j)
{
Pin* const pPin = *i++;
const wstring& id2_ = pPin->m_id;
const wchar_t* const id2 = id2_.c_str();
if (wcscmp(id1, id2) == 0) //case-sensitive
{
p = pPin;
p->AddRef();
return S_OK;
}
}
return VFW_E_NOT_FOUND;
}
HRESULT Filter::QueryFilterInfo(FILTER_INFO* p)
{
if (p == 0)
return E_POINTER;
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
enum { size = sizeof(p->achName)/sizeof(WCHAR) };
const errno_t e = wcscpy_s(p->achName, size, m_info.achName);
e;
assert(e == 0);
p->pGraph = m_info.pGraph;
if (p->pGraph)
p->pGraph->AddRef();
return S_OK;
}
HRESULT Filter::JoinFilterGraph(
IFilterGraph *pGraph,
LPCWSTR name)
{
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return hr;
//NOTE:
//No, do not adjust reference counts here!
//Read the docs for the reasons why.
//ENDNOTE.
m_info.pGraph = pGraph;
if (name == 0)
m_info.achName[0] = L'\0';
else
{
enum { size = sizeof(m_info.achName)/sizeof(WCHAR) };
const errno_t e = wcscpy_s(m_info.achName, size, name);
e;
assert(e == 0); //TODO
}
return S_OK;
}
HRESULT Filter::QueryVendorInfo(LPWSTR* pstr)
{
if (pstr == 0)
return E_POINTER;
wchar_t*& str = *pstr;
str = 0;
return E_NOTIMPL;
}
HRESULT Filter::Open()
{
if (m_pSegment)
return VFW_E_WRONG_STATE;
assert(m_outpins.empty());
MkvReader& reader = m_inpin.m_reader;
__int64 result, pos;
mkvparser::EBMLHeader h;
result = h.Parse(&reader, pos);
if (result < 0) //error
{
if (result == mkvparser::E_FILE_FORMAT_INVALID)
return VFW_E_INVALID_FILE_FORMAT;
if (result == mkvparser::E_BUFFER_NOT_FULL)
return VFW_E_BUFFER_UNDERFLOW;
return VFW_E_RUNTIME_ERROR;
}
if (result > 0)
return VFW_E_BUFFER_UNDERFLOW;
if (h.m_version > 1)
return VFW_E_INVALID_FILE_FORMAT;
if (h.m_maxIdLength > 8)
return VFW_E_INVALID_FILE_FORMAT;
if (h.m_maxSizeLength > 8)
return VFW_E_INVALID_FILE_FORMAT;
const char* const docType = h.m_docType;
if (_stricmp(docType, "webm") == 0)
__noop;
else if (_stricmp(docType, "matroska") == 0)
__noop;
else
return VFW_E_INVALID_FILE_FORMAT;
if (h.m_docTypeVersion < 1)
return VFW_E_INVALID_FILE_FORMAT;
if (h.m_docTypeReadVersion > 2)
return VFW_E_INVALID_FILE_FORMAT;
//Just the EBML header has been consumed. pos points
//to start of (first) segment.
mkvparser::Segment* p;
result = mkvparser::Segment::CreateInstance(&reader, pos, p);
if (result < 0) //error
{
if (result == mkvparser::E_FILE_FORMAT_INVALID)
return VFW_E_INVALID_FILE_FORMAT;
if (result == mkvparser::E_BUFFER_NOT_FULL)
return VFW_E_BUFFER_UNDERFLOW;
return VFW_E_RUNTIME_ERROR;
}
if (result > 0)
return VFW_E_BUFFER_UNDERFLOW; //TODO: handle this as below
assert(p);
std::auto_ptr<mkvparser::Segment> pSegment(p);
#if 0
result = pSegment->FindFirstCluster(pos);
if (result < 0)
{
if (result == mkvparser::E_FILE_FORMAT_INVALID)
return VFW_E_INVALID_FILE_FORMAT;
if (result != mkvparser::E_BUFFER_NOT_FULL)
return VFW_E_RUNTIME_ERROR;
}
//if you're going to do this, why not just a sync read...
const HRESULT hr = reader.Wait(*this, pos, 1, 5000);
if (FAILED(hr))
return hr;
#endif
result = pSegment->ParseHeaders();
if (result < 0) //error
{
if (result == mkvparser::E_FILE_FORMAT_INVALID)
return VFW_E_INVALID_FILE_FORMAT;
if (result == mkvparser::E_BUFFER_NOT_FULL)
return VFW_E_BUFFER_UNDERFLOW;
return VFW_E_RUNTIME_ERROR;
}
if (result > 0)
return VFW_E_BUFFER_UNDERFLOW;
using namespace mkvparser;
const SegmentInfo* const pInfo = pSegment->GetInfo();
if (pInfo == 0)
return VFW_E_INVALID_FILE_FORMAT; //TODO: liberalize
#ifdef _DEBUG
{
wstring muxingApp, writingApp;
if (const char* str = pInfo->GetMuxingAppAsUTF8())
muxingApp = Stream::ConvertFromUTF8(str);
if (const char* str = pInfo->GetWritingAppAsUTF8())
writingApp = Stream::ConvertFromUTF8(str);
}
#endif
const Tracks* const pTracks = pSegment->GetTracks();
if (pTracks == 0)
return VFW_E_INVALID_FILE_FORMAT;
const ULONG n = pTracks->GetTracksCount();
for (ULONG i = 0; i < n; ++i)
{
const Track* const pTrack = pTracks->GetTrackByIndex(i);
if (pTrack == 0)
continue;
const long long type = pTrack->GetType();
if (type == 1) //video
{
typedef mkvparser::VideoTrack VT;
const VT* const t = static_cast<const VT*>(pTrack);
if (VideoStream* s = VideoStream::CreateInstance(t))
CreateOutpin(s);
}
#if 1
else if (type == 2) //audio
{
typedef mkvparser::AudioTrack AT;
const AT* const t = static_cast<const AT*>(pTrack);
if (AudioStream* s = AudioStream::CreateInstance(t))
CreateOutpin(s);
}
#endif
}
if (m_outpins.empty())
return VFW_E_INVALID_FILE_FORMAT; //TODO: better return value here?
m_pSegment = pSegment.release();
m_pSeekBase = 0;
m_seekBase_ns = -1;
m_currTime = kNoSeek;
return S_OK;
}
void Filter::CreateOutpin(mkvparser::Stream* s)
{
//Outpin* const p = new (std::nothrow) Outpin(this, s);
Outpin* const p = Outpin::Create(this, s);
m_outpins.push_back(p);
}
void Filter::OnStart()
{
//m_inpin.Start();
typedef outpins_t::iterator iter_t;
iter_t i = m_outpins.begin();
const iter_t j = m_outpins.end();
int n = 0;
while (i != j)
{
Outpin* const pPin = *i++;
assert(pPin);
const HRESULT hr = pPin->Start();
assert(SUCCEEDED(hr));
if (hr == S_OK)
++n;
}
if (n)
{
assert(m_pSegment);
m_cStarvation = 0; //temporarily enter starvation mode to force check
}
Init(); //create reader thread
}
void Filter::OnStop()
{
Final(); //terminate reader thread
typedef outpins_t::iterator iter_t;
iter_t i = m_outpins.begin();
const iter_t j = m_outpins.end();
while (i != j)
{
Outpin* const pPin = *i++;
assert(pPin);
pPin->Stop();
}
//m_inpin.Stop();
}
int Filter::GetConnectionCount() const
{
//filter already locked by caller
int n = 0;
typedef outpins_t::const_iterator iter_t;
iter_t i = m_outpins.begin();
const iter_t j = m_outpins.end();
while (i != j)
{
const Outpin* const pin = *i++;
assert(pin);
if (pin->m_pPinConnection)
++n;
}
return n;
}
unsigned Filter::ThreadProc(void* pv)
{
Filter* const pFilter = static_cast<Filter*>(pv);
assert(pFilter);
return pFilter->Main();
}
unsigned Filter::Main()
{
assert(m_pSegment);
for (;;)
{
Sleep(0);
#if 0
LONGLONG cluster_pos, new_pos;
const long status = m_pSegment->ParseCluster(cluster_pos, new_pos);
if (status < 0) //TODO: how to handle outpin streaming threads?
return 1;
Lock lock;
const HRESULT hr = lock.Seize(this);
assert(SUCCEEDED(hr)); //TODO
if (FAILED(hr))
return 1;
const bool bDone = m_pSegment->AddCluster(cluster_pos, new_pos);
//odbgstream os;
//os << "webmsplit::filter::main: ParseCluster; cluster_pos="
// << cluster_pos
// << " new_pos="
// << new_pos
// << " count="
// << m_pSegment->GetCount()
// << " unparsed="
// << m_pSegment->Unparsed()
// << endl;
#else
Lock lock;
HRESULT hr = lock.Seize(this);
if (FAILED(hr))
return 1;
for (;;)
{
LONGLONG pos;
LONG size;
const long status = m_pSegment->LoadCluster(pos, size);
if (status >= 0)
break;
if (status != mkvparser::E_BUFFER_NOT_FULL)
return 1;
hr = m_inpin.m_reader.Wait(*this, pos, size, INFINITE);
if (FAILED(hr)) //wait was cancelled
return 1;
}
const bool bDone = m_pSegment->DoneParsing();
#endif
OnNewCluster();
if (bDone)
return 0;
if (m_state == State_Stopped)
return 0;
}
}
void Filter::OnNewCluster()
{
const BOOL b = SetEvent(m_hNewCluster); //see Filter::GetState
b;
assert(b);
typedef outpins_t::iterator iter_t;
iter_t i = m_outpins.begin();
const iter_t j = m_outpins.end();
while (i != j)
{
Outpin* const outpin = *i++;
assert(outpin);
outpin->OnNewCluster();
}
}
HRESULT Filter::OnDisconnectInpin()
{
assert(m_hThread == 0);
while (!m_outpins.empty())
{
Outpin* const pPin = m_outpins.back();
assert(pPin);
if (IPin* pPinConnection = pPin->m_pPinConnection)
{
assert(m_info.pGraph);
HRESULT hr = m_info.pGraph->Disconnect(pPinConnection);
assert(SUCCEEDED(hr));
hr = m_info.pGraph->Disconnect(pPin);
assert(SUCCEEDED(hr));
}
m_outpins.pop_back();
const ULONG n = pPin->Destroy();
n;
}
m_currTime = kNoSeek;
m_pSeekBase = 0;
m_seekBase_ns = -1;
delete m_pSegment;
m_pSegment = 0;
m_cStarvation = -1;
return S_OK;
}
void Filter::OnStarvation(ULONG count)
{
#ifdef _DEBUG
odbgstream os;
os << "WebmSplit::Filter::OnStarvation: count=" << count
<< " m_cStarvation=" << m_cStarvation
<< endl;
#endif
if (m_cStarvation < 0)
{
const GraphUtil::IMediaEventSinkPtr pSink(m_info.pGraph);
assert(bool(pSink));
const HRESULT hr = pSink->Notify(EC_STARVATION, 0, 0);
hr;
assert(SUCCEEDED(hr));
m_cStarvation = count;
}
}
void Filter::SetCurrPosition(
LONGLONG currTime,
DWORD dwCurr,
Outpin* pOutpin)
{
assert(pOutpin);
assert(bool(pOutpin->m_pPinConnection));
using namespace mkvparser;
Stream* const pSeekStream = pOutpin->GetStream();
assert(pSeekStream);
if (m_currTime == currTime)
{
SetCurrPositionUsingSameTime(pSeekStream);
return;
}
m_currTime = currTime;
if (InCache())
m_pSegment->LoadCluster();
if (m_pSegment->GetCount() <= 0) //no clusters loaded yet
{
m_pSeekBase = 0; //best we can do is to assume first cluster
m_seekBase_ns = -1;
m_seekTime_ns = -1;
pSeekStream->SetCurrPosition(-1, 0); //lazy init
return;
}
const LONGLONG ns = pSeekStream->GetSeekTime(currTime, dwCurr);
const Track* const pSeekTrack = pSeekStream->m_pTrack;
if (pSeekTrack->GetType() == 1) //video
SetCurrPositionVideo(ns, pSeekStream);
else
SetCurrPositionAudio(ns, pSeekStream);
}
void Filter::SetCurrPositionUsingSameTime(mkvparser::Stream* pStream)
{
const mkvparser::BlockEntry* pCurr;
const mkvparser::Track* const pTrack = pStream->m_pTrack;
if (m_pSeekBase == 0) //lazy init
pCurr = 0;
else if (m_pSeekBase->EOS())
pCurr = pTrack->GetEOS();
else
{
pCurr = m_pSeekBase->GetEntry(pTrack, m_seekTime_ns);
#ifdef _DEBUG
if (pCurr == 0)
__noop;
else if (pCurr->EOS())
__noop;
else if (pTrack->GetType() == 1) //video
{
const mkvparser::Block* const pCurrBlock = pCurr->GetBlock();
const LONGLONG ns = pCurrBlock->GetTime(pCurr->GetCluster());
assert(ns >= m_seekBase_ns);
assert(pCurrBlock->IsKey());
}
#endif
}
pStream->SetCurrPosition(m_seekBase_ns, pCurr);
}
void Filter::SetCurrPositionVideo(
LONGLONG ns,
mkvparser::Stream* pStream)
{
using namespace mkvparser;
const Track* const pTrack = pStream->m_pTrack;
const bool bInCache = InCache();
if (!bInCache)
__noop;
else if (const Cues* pCues = m_pSegment->GetCues())
{
while (!pCues->DoneParsing())
{
pCues->LoadCuePoint();
const CuePoint* const pCP = pCues->GetLast();
assert(pCP);
if (pCP->GetTime(m_pSegment) >= ns)
break;
}
const CuePoint* pCP;
const CuePoint::TrackPosition* pTP;
if (pCues->Find(ns, pTrack, pCP, pTP))
{
const BlockEntry* const pCurr = pCues->GetBlock(pCP, pTP);
if ((pCurr != 0) && !pCurr->EOS())
{
m_pSeekBase = pCurr->GetCluster();
m_seekBase_ns = pCurr->GetBlock()->GetTime(m_pSeekBase);
m_seekTime_ns = m_seekBase_ns;
pStream->SetCurrPosition(m_seekBase_ns, pCurr);
return;
}
}
}
const mkvparser::BlockEntry* pCurr = 0;
for (;;)
{
long status = pTrack->Seek(ns, pCurr);
if ((status >= 0) ||
(status != mkvparser::E_BUFFER_NOT_FULL) ||
!bInCache)
{
break;
}
status = m_pSegment->LoadCluster();
if (status < 0)
break;
}
if ((pCurr == 0) || pCurr->EOS())
{
m_pSeekBase = &m_pSegment->m_eos;
m_seekBase_ns = -1;
m_seekTime_ns = -1;
}
else
{
m_pSeekBase = pCurr->GetCluster();
m_seekBase_ns = pCurr->GetBlock()->GetTime(m_pSeekBase);
m_seekTime_ns = m_seekBase_ns;
}
pStream->SetCurrPosition(m_seekBase_ns, pCurr);
}
void Filter::SetCurrPositionAudio(
LONGLONG ns,
mkvparser::Stream* pSeekStream)
{
using namespace mkvparser;
const Track* const pSeekTrack = pSeekStream->m_pTrack;
typedef outpins_t::const_iterator iter_t;
iter_t i = m_outpins.begin();
const iter_t j = m_outpins.end();
mkvparser::Stream* pVideoStream = 0;
while (i != j)
{
const Outpin* const pin = *i++;
assert(pin);
if (!bool(pin->m_pPinConnection))
continue;
const AM_MEDIA_TYPE& mt = pin->m_connection_mtv[0];
const BOOL bVideo = (mt.majortype == MEDIATYPE_Video);
if (bVideo)
{
pVideoStream = pin->GetStream();
assert(pVideoStream);
assert(pVideoStream != pSeekStream);
break;
}
}
if (pVideoStream == 0) //no video tracks in this file
{
const mkvparser::BlockEntry* pCurr;
const long status = pSeekTrack->Seek(ns, pCurr);
if ((status < 0) || (pCurr == 0) || pCurr->EOS())
{
m_pSeekBase = &m_pSegment->m_eos;
m_seekBase_ns = -1;
m_seekTime_ns = -1;
}
else
{
m_pSeekBase = pCurr->GetCluster();
m_seekBase_ns = m_pSeekBase->GetFirstTime();
m_seekTime_ns = ns;
}
pSeekStream->SetCurrPosition(m_seekBase_ns, pCurr);
return;
}
const mkvparser::Track* const pVideoTrack = pVideoStream->m_pTrack;
assert(pVideoTrack->GetType() == 1); //video
const bool bInCache = InCache();
if (!bInCache)
__noop;
else if (const Cues* pCues = m_pSegment->GetCues())
{
while (!pCues->DoneParsing())
{
pCues->LoadCuePoint();
const CuePoint* const pCP = pCues->GetLast();
assert(pCP);
if (pCP->GetTime(m_pSegment) >= ns)
break;
}
const CuePoint* pCP;
const CuePoint::TrackPosition* pTP;
if (pCues->Find(ns, pVideoTrack, pCP, pTP))
{
const BlockEntry* pCurr = pCues->GetBlock(pCP, pTP);
if ((pCurr != 0) && !pCurr->EOS())
{
m_pSeekBase = pCurr->GetCluster();
m_seekBase_ns = pCurr->GetBlock()->GetTime(m_pSeekBase);
m_seekTime_ns = m_seekBase_ns; //to find same block later
pCurr = m_pSeekBase->GetEntry(pSeekTrack, m_seekBase_ns);
assert(pCurr);
if (!pCurr->EOS())
m_seekBase_ns = pCurr->GetBlock()->GetTime(m_pSeekBase);
pSeekStream->SetCurrPosition(m_seekBase_ns, pCurr);
return;
}
}
}
const BlockEntry* pCurr = 0;
for (;;)
{
long status = pVideoTrack->Seek(ns, pCurr);
if ((status >= 0) ||
(status != mkvparser::E_BUFFER_NOT_FULL) ||
!bInCache)
{
break;
}
status = m_pSegment->LoadCluster();
if (status < 0)
break;
}
if ((pCurr == 0) || pCurr->EOS())
{
m_pSeekBase = &m_pSegment->m_eos;
m_seekBase_ns = -1;
m_seekTime_ns = -1;
pCurr = pSeekTrack->GetEOS();
pSeekStream->SetCurrPosition(m_seekBase_ns, pCurr);
return;
}
m_pSeekBase = pCurr->GetCluster();
m_seekBase_ns = pCurr->GetBlock()->GetTime(m_pSeekBase);
m_seekTime_ns = m_seekBase_ns; //to find same block later
pCurr = m_pSeekBase->GetEntry(pSeekTrack, m_seekBase_ns);
assert(pCurr);
if (!pCurr->EOS())
m_seekBase_ns = pCurr->GetBlock()->GetTime(m_pSeekBase);
pSeekStream->SetCurrPosition(m_seekBase_ns, pCurr);
}
bool Filter::InCache()
{
LONGLONG total, avail;
const int status = m_inpin.m_reader.Length(&total, &avail);
if (status < 0)
return false;
if (total < 0)
return false;
return (avail >= total);
}
} //end namespace WebmSplit