// 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.hpp" | |
#include "cenumpins.hpp" | |
#include "mkvparser.hpp" | |
#include "mkvparserstreamvideo.hpp" | |
#include "mkvparserstreamaudio.hpp" | |
#include "webmsplitoutpin.hpp" | |
#include "webmtypes.hpp" | |
#include <new> | |
#include <cassert> | |
#include <vfwmsgs.h> | |
#include <process.h> | |
#include <evcode.h> | |
#include <limits> | |
#ifdef _DEBUG | |
#include "iidstr.hpp" | |
#include "odbgstream.hpp" | |
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 > 2) | |
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 | |