| //------------------------------------------------------------------------------ | |
| // 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; | |
| } | |
| } | |
| } |