//------------------------------------------------------------------------------ | |
// File: PullPin.cpp | |
// | |
// Desc: DirectShow base classes - implements CPullPin class that pulls data | |
// from IAsyncReader. | |
// | |
// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. | |
//------------------------------------------------------------------------------ | |
#include <streams.h> | |
#include "pullpin.h" | |
#ifdef DXMPERF | |
#include "dxmperf.h" | |
#endif // DXMPERF | |
CPullPin::CPullPin() | |
: m_pReader(NULL), | |
m_pAlloc(NULL), | |
m_State(TM_Exit) | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( L"CPullPin", this ); | |
#endif // DXMPERF | |
} | |
CPullPin::~CPullPin() | |
{ | |
Disconnect(); | |
#ifdef DXMPERF | |
PERFLOG_DTOR( L"CPullPin", this ); | |
#endif // DXMPERF | |
} | |
// returns S_OK if successfully connected to an IAsyncReader interface | |
// from this object | |
// Optional allocator should be proposed as a preferred allocator if | |
// necessary | |
HRESULT | |
CPullPin::Connect(IUnknown* pUnk, IMemAllocator* pAlloc, BOOL bSync) | |
{ | |
CAutoLock lock(&m_AccessLock); | |
if (m_pReader) { | |
return VFW_E_ALREADY_CONNECTED; | |
} | |
HRESULT hr = pUnk->QueryInterface(IID_IAsyncReader, (void**)&m_pReader); | |
if (FAILED(hr)) { | |
#ifdef DXMPERF | |
{ | |
AM_MEDIA_TYPE * pmt = NULL; | |
PERFLOG_CONNECT( this, pUnk, hr, pmt ); | |
} | |
#endif // DXMPERF | |
return(hr); | |
} | |
hr = DecideAllocator(pAlloc, NULL); | |
if (FAILED(hr)) { | |
Disconnect(); | |
#ifdef DXMPERF | |
{ | |
AM_MEDIA_TYPE * pmt = NULL; | |
PERFLOG_CONNECT( this, pUnk, hr, pmt ); | |
} | |
#endif // DXMPERF | |
return hr; | |
} | |
LONGLONG llTotal, llAvail; | |
hr = m_pReader->Length(&llTotal, &llAvail); | |
if (FAILED(hr)) { | |
Disconnect(); | |
#ifdef DXMPERF | |
{ | |
AM_MEDIA_TYPE * pmt = NULL; | |
PERFLOG_CONNECT( this, pUnk, hr, pmt ); | |
} | |
#endif | |
return hr; | |
} | |
// convert from file position to reference time | |
m_tDuration = llTotal * UNITS; | |
m_tStop = m_tDuration; | |
m_tStart = 0; | |
m_bSync = bSync; | |
#ifdef DXMPERF | |
{ | |
AM_MEDIA_TYPE * pmt = NULL; | |
PERFLOG_CONNECT( this, pUnk, S_OK, pmt ); | |
} | |
#endif // DXMPERF | |
return S_OK; | |
} | |
// disconnect any connection made in Connect | |
HRESULT | |
CPullPin::Disconnect() | |
{ | |
CAutoLock lock(&m_AccessLock); | |
StopThread(); | |
#ifdef DXMPERF | |
PERFLOG_DISCONNECT( this, m_pReader, S_OK ); | |
#endif // DXMPERF | |
if (m_pReader) { | |
m_pReader->Release(); | |
m_pReader = NULL; | |
} | |
if (m_pAlloc) { | |
m_pAlloc->Release(); | |
m_pAlloc = NULL; | |
} | |
return S_OK; | |
} | |
// agree an allocator using RequestAllocator - optional | |
// props param specifies your requirements (non-zero fields). | |
// returns an error code if fail to match requirements. | |
// optional IMemAllocator interface is offered as a preferred allocator | |
// but no error occurs if it can't be met. | |
HRESULT | |
CPullPin::DecideAllocator( | |
IMemAllocator * pAlloc, | |
__inout_opt ALLOCATOR_PROPERTIES * pProps) | |
{ | |
ALLOCATOR_PROPERTIES *pRequest; | |
ALLOCATOR_PROPERTIES Request; | |
if (pProps == NULL) { | |
Request.cBuffers = 3; | |
Request.cbBuffer = 64*1024; | |
Request.cbAlign = 0; | |
Request.cbPrefix = 0; | |
pRequest = &Request; | |
} else { | |
pRequest = pProps; | |
} | |
HRESULT hr = m_pReader->RequestAllocator( | |
pAlloc, | |
pRequest, | |
&m_pAlloc); | |
return hr; | |
} | |
// start pulling data | |
HRESULT | |
CPullPin::Active(void) | |
{ | |
ASSERT(!ThreadExists()); | |
return StartThread(); | |
} | |
// stop pulling data | |
HRESULT | |
CPullPin::Inactive(void) | |
{ | |
StopThread(); | |
return S_OK; | |
} | |
HRESULT | |
CPullPin::Seek(REFERENCE_TIME tStart, REFERENCE_TIME tStop) | |
{ | |
CAutoLock lock(&m_AccessLock); | |
ThreadMsg AtStart = m_State; | |
if (AtStart == TM_Start) { | |
BeginFlush(); | |
PauseThread(); | |
EndFlush(); | |
} | |
m_tStart = tStart; | |
m_tStop = tStop; | |
HRESULT hr = S_OK; | |
if (AtStart == TM_Start) { | |
hr = StartThread(); | |
} | |
return hr; | |
} | |
HRESULT | |
CPullPin::Duration(__out REFERENCE_TIME* ptDuration) | |
{ | |
*ptDuration = m_tDuration; | |
return S_OK; | |
} | |
HRESULT | |
CPullPin::StartThread() | |
{ | |
CAutoLock lock(&m_AccessLock); | |
if (!m_pAlloc || !m_pReader) { | |
return E_UNEXPECTED; | |
} | |
HRESULT hr; | |
if (!ThreadExists()) { | |
// commit allocator | |
hr = m_pAlloc->Commit(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
// start thread | |
if (!Create()) { | |
return E_FAIL; | |
} | |
} | |
m_State = TM_Start; | |
hr = (HRESULT) CallWorker(m_State); | |
return hr; | |
} | |
HRESULT | |
CPullPin::PauseThread() | |
{ | |
CAutoLock lock(&m_AccessLock); | |
if (!ThreadExists()) { | |
return E_UNEXPECTED; | |
} | |
// need to flush to ensure the thread is not blocked | |
// in WaitForNext | |
HRESULT hr = m_pReader->BeginFlush(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
m_State = TM_Pause; | |
hr = CallWorker(TM_Pause); | |
m_pReader->EndFlush(); | |
return hr; | |
} | |
HRESULT | |
CPullPin::StopThread() | |
{ | |
CAutoLock lock(&m_AccessLock); | |
if (!ThreadExists()) { | |
return S_FALSE; | |
} | |
// need to flush to ensure the thread is not blocked | |
// in WaitForNext | |
HRESULT hr = m_pReader->BeginFlush(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
m_State = TM_Exit; | |
hr = CallWorker(TM_Exit); | |
m_pReader->EndFlush(); | |
// wait for thread to completely exit | |
Close(); | |
// decommit allocator | |
if (m_pAlloc) { | |
m_pAlloc->Decommit(); | |
} | |
return S_OK; | |
} | |
DWORD | |
CPullPin::ThreadProc(void) | |
{ | |
while(1) { | |
DWORD cmd = GetRequest(); | |
switch(cmd) { | |
case TM_Exit: | |
Reply(S_OK); | |
return 0; | |
case TM_Pause: | |
// we are paused already | |
Reply(S_OK); | |
break; | |
case TM_Start: | |
Reply(S_OK); | |
Process(); | |
break; | |
} | |
// at this point, there should be no outstanding requests on the | |
// upstream filter. | |
// We should force begin/endflush to ensure that this is true. | |
// !!!Note that we may currently be inside a BeginFlush/EndFlush pair | |
// on another thread, but the premature EndFlush will do no harm now | |
// that we are idle. | |
m_pReader->BeginFlush(); | |
CleanupCancelled(); | |
m_pReader->EndFlush(); | |
} | |
} | |
HRESULT | |
CPullPin::QueueSample( | |
__inout REFERENCE_TIME& tCurrent, | |
REFERENCE_TIME tAlignStop, | |
BOOL bDiscontinuity | |
) | |
{ | |
IMediaSample* pSample; | |
HRESULT hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS); | |
if (tStopThis > tAlignStop) { | |
tStopThis = tAlignStop; | |
} | |
pSample->SetTime(&tCurrent, &tStopThis); | |
tCurrent = tStopThis; | |
pSample->SetDiscontinuity(bDiscontinuity); | |
hr = m_pReader->Request( | |
pSample, | |
0); | |
if (FAILED(hr)) { | |
pSample->Release(); | |
CleanupCancelled(); | |
OnError(hr); | |
} | |
return hr; | |
} | |
HRESULT | |
CPullPin::CollectAndDeliver( | |
REFERENCE_TIME tStart, | |
REFERENCE_TIME tStop) | |
{ | |
IMediaSample* pSample = NULL; // better be sure pSample is set | |
DWORD_PTR dwUnused; | |
HRESULT hr = m_pReader->WaitForNext( | |
INFINITE, | |
&pSample, | |
&dwUnused); | |
if (FAILED(hr)) { | |
if (pSample) { | |
pSample->Release(); | |
} | |
} else { | |
hr = DeliverSample(pSample, tStart, tStop); | |
} | |
if (FAILED(hr)) { | |
CleanupCancelled(); | |
OnError(hr); | |
} | |
return hr; | |
} | |
HRESULT | |
CPullPin::DeliverSample( | |
IMediaSample* pSample, | |
REFERENCE_TIME tStart, | |
REFERENCE_TIME tStop | |
) | |
{ | |
// fix up sample if past actual stop (for sector alignment) | |
REFERENCE_TIME t1, t2; | |
if (S_OK == pSample->GetTime(&t1, &t2)) { | |
if (t2 > tStop) { | |
t2 = tStop; | |
} | |
// adjust times to be relative to (aligned) start time | |
t1 -= tStart; | |
t2 -= tStart; | |
HRESULT hr = pSample->SetTime(&t1, &t2); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
} | |
#ifdef DXMPERF | |
{ | |
AM_MEDIA_TYPE * pmt = NULL; | |
pSample->GetMediaType( &pmt ); | |
PERFLOG_RECEIVE( L"CPullPin", m_pReader, this, pSample, pmt ); | |
} | |
#endif | |
HRESULT hr = Receive(pSample); | |
pSample->Release(); | |
return hr; | |
} | |
void | |
CPullPin::Process(void) | |
{ | |
// is there anything to do? | |
if (m_tStop <= m_tStart) { | |
EndOfStream(); | |
return; | |
} | |
BOOL bDiscontinuity = TRUE; | |
// if there is more than one sample at the allocator, | |
// then try to queue 2 at once in order to overlap. | |
// -- get buffer count and required alignment | |
ALLOCATOR_PROPERTIES Actual; | |
HRESULT hr = m_pAlloc->GetProperties(&Actual); | |
// align the start position downwards | |
REFERENCE_TIME tStart = AlignDown(m_tStart / UNITS, Actual.cbAlign) * UNITS; | |
REFERENCE_TIME tCurrent = tStart; | |
REFERENCE_TIME tStop = m_tStop; | |
if (tStop > m_tDuration) { | |
tStop = m_tDuration; | |
} | |
// align the stop position - may be past stop, but that | |
// doesn't matter | |
REFERENCE_TIME tAlignStop = AlignUp(tStop / UNITS, Actual.cbAlign) * UNITS; | |
DWORD dwRequest; | |
if (!m_bSync) { | |
// Break out of the loop either if we get to the end or we're asked | |
// to do something else | |
while (tCurrent < tAlignStop) { | |
// Break out without calling EndOfStream if we're asked to | |
// do something different | |
if (CheckRequest(&dwRequest)) { | |
return; | |
} | |
// queue a first sample | |
if (Actual.cBuffers > 1) { | |
hr = QueueSample(tCurrent, tAlignStop, TRUE); | |
bDiscontinuity = FALSE; | |
if (FAILED(hr)) { | |
return; | |
} | |
} | |
// loop queueing second and waiting for first.. | |
while (tCurrent < tAlignStop) { | |
hr = QueueSample(tCurrent, tAlignStop, bDiscontinuity); | |
bDiscontinuity = FALSE; | |
if (FAILED(hr)) { | |
return; | |
} | |
hr = CollectAndDeliver(tStart, tStop); | |
if (S_OK != hr) { | |
// stop if error, or if downstream filter said | |
// to stop. | |
return; | |
} | |
} | |
if (Actual.cBuffers > 1) { | |
hr = CollectAndDeliver(tStart, tStop); | |
if (FAILED(hr)) { | |
return; | |
} | |
} | |
} | |
} else { | |
// sync version of above loop | |
while (tCurrent < tAlignStop) { | |
// Break out without calling EndOfStream if we're asked to | |
// do something different | |
if (CheckRequest(&dwRequest)) { | |
return; | |
} | |
IMediaSample* pSample; | |
hr = m_pAlloc->GetBuffer(&pSample, NULL, NULL, 0); | |
if (FAILED(hr)) { | |
OnError(hr); | |
return; | |
} | |
LONGLONG tStopThis = tCurrent + (pSample->GetSize() * UNITS); | |
if (tStopThis > tAlignStop) { | |
tStopThis = tAlignStop; | |
} | |
pSample->SetTime(&tCurrent, &tStopThis); | |
tCurrent = tStopThis; | |
if (bDiscontinuity) { | |
pSample->SetDiscontinuity(TRUE); | |
bDiscontinuity = FALSE; | |
} | |
hr = m_pReader->SyncReadAligned(pSample); | |
if (FAILED(hr)) { | |
pSample->Release(); | |
OnError(hr); | |
return; | |
} | |
hr = DeliverSample(pSample, tStart, tStop); | |
if (hr != S_OK) { | |
if (FAILED(hr)) { | |
OnError(hr); | |
} | |
return; | |
} | |
} | |
} | |
EndOfStream(); | |
} | |
// after a flush, cancelled i/o will be waiting for collection | |
// and release | |
void | |
CPullPin::CleanupCancelled(void) | |
{ | |
while (1) { | |
IMediaSample * pSample; | |
DWORD_PTR dwUnused; | |
HRESULT hr = m_pReader->WaitForNext( | |
0, // no wait | |
&pSample, | |
&dwUnused); | |
if(pSample) { | |
pSample->Release(); | |
} else { | |
// no more samples | |
return; | |
} | |
} | |
} |