pcsx2/3rdparty/baseclasses/pullpin.cpp
Jonathan Li ab9bdb009b baseclasses: Move from unfree to 3rdparty
Update it to the version found at
https://github.com/Microsoft/Windows-classic-samples , which is in an
MIT licensed repo, and add the LICENSE file (edited to remove the SIL
Open Font LICENSE part since that doesn't apply).

Some modifications have been made to reduce the diff/stop git
complaining (not including any file that wasn't in the previous version
and removing the related header includes in streams.h, and fixing some
but not all of the whitespace issues).
2018-04-29 02:19:17 +01:00

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