589 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
		
		
			
		
	
	
			589 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
|  | //------------------------------------------------------------------------------
 | ||
|  | // 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; | ||
|  | 	} | ||
|  |     } | ||
|  | } |