//------------------------------------------------------------------------------ | |
// File: AMFilter.cpp | |
// | |
// Desc: DirectShow base classes - implements class hierarchy for streams | |
// architecture. | |
// | |
// Copyright (c) 1992-2001 Microsoft Corporation. All rights reserved. | |
//------------------------------------------------------------------------------ | |
//===================================================================== | |
//===================================================================== | |
// The following classes are declared in this header: | |
// | |
// | |
// CBaseMediaFilter Basic IMediaFilter support (abstract class) | |
// CBaseFilter Support for IBaseFilter (incl. IMediaFilter) | |
// CEnumPins Enumerate input and output pins | |
// CEnumMediaTypes Enumerate the preferred pin formats | |
// CBasePin Abstract base class for IPin interface | |
// CBaseOutputPin Adds data provider member functions | |
// CBaseInputPin Implements IMemInputPin interface | |
// CMediaSample Basic transport unit for IMemInputPin | |
// CBaseAllocator General list guff for most allocators | |
// CMemAllocator Implements memory buffer allocation | |
// | |
//===================================================================== | |
//===================================================================== | |
#include <pjmedia-videodev/config.h> | |
#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 | |
#include <streams.h> | |
#include <strsafe.h> | |
#ifdef DXMPERF | |
#include "dxmperf.h" | |
#endif // DXMPERF | |
//===================================================================== | |
// Helpers | |
//===================================================================== | |
STDAPI CreateMemoryAllocator(__deref_out IMemAllocator **ppAllocator) | |
{ | |
return CoCreateInstance(CLSID_MemoryAllocator, | |
0, | |
CLSCTX_INPROC_SERVER, | |
IID_IMemAllocator, | |
(void **)ppAllocator); | |
} | |
// Put this one here rather than in ctlutil.cpp to avoid linking | |
// anything brought in by ctlutil.cpp | |
STDAPI CreatePosPassThru( | |
__in_opt LPUNKNOWN pAgg, | |
BOOL bRenderer, | |
IPin *pPin, | |
__deref_out IUnknown **ppPassThru | |
) | |
{ | |
*ppPassThru = NULL; | |
IUnknown *pUnkSeek; | |
HRESULT hr = CoCreateInstance(CLSID_SeekingPassThru, | |
pAgg, | |
CLSCTX_INPROC_SERVER, | |
IID_IUnknown, | |
(void **)&pUnkSeek | |
); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
ISeekingPassThru *pPassThru; | |
hr = pUnkSeek->QueryInterface(IID_ISeekingPassThru, (void**)&pPassThru); | |
if (FAILED(hr)) { | |
pUnkSeek->Release(); | |
return hr; | |
} | |
hr = pPassThru->Init(bRenderer, pPin); | |
pPassThru->Release(); | |
if (FAILED(hr)) { | |
pUnkSeek->Release(); | |
return hr; | |
} | |
*ppPassThru = pUnkSeek; | |
return S_OK; | |
} | |
#define CONNECT_TRACE_LEVEL 3 | |
//===================================================================== | |
//===================================================================== | |
// Implements CBaseMediaFilter | |
//===================================================================== | |
//===================================================================== | |
/* Constructor */ | |
CBaseMediaFilter::CBaseMediaFilter(__in_opt LPCTSTR pName, | |
__inout_opt LPUNKNOWN pUnk, | |
__in CCritSec *pLock, | |
REFCLSID clsid) : | |
CUnknown(pName, pUnk), | |
m_pLock(pLock), | |
m_clsid(clsid), | |
m_State(State_Stopped), | |
m_pClock(NULL) | |
{ | |
} | |
/* Destructor */ | |
CBaseMediaFilter::~CBaseMediaFilter() | |
{ | |
// must be stopped, but can't call Stop here since | |
// our critsec has been destroyed. | |
/* Release any clock we were using */ | |
if (m_pClock) { | |
m_pClock->Release(); | |
m_pClock = NULL; | |
} | |
} | |
/* Override this to say what interfaces we support and where */ | |
STDMETHODIMP | |
CBaseMediaFilter::NonDelegatingQueryInterface( | |
REFIID riid, | |
__deref_out void ** ppv) | |
{ | |
if (riid == IID_IMediaFilter) { | |
return GetInterface((IMediaFilter *) this, ppv); | |
} else if (riid == IID_IPersist) { | |
return GetInterface((IPersist *) this, ppv); | |
} else { | |
return CUnknown::NonDelegatingQueryInterface(riid, ppv); | |
} | |
} | |
/* Return the filter's clsid */ | |
STDMETHODIMP | |
CBaseMediaFilter::GetClassID(__out CLSID *pClsID) | |
{ | |
CheckPointer(pClsID,E_POINTER); | |
ValidateReadWritePtr(pClsID,sizeof(CLSID)); | |
*pClsID = m_clsid; | |
return NOERROR; | |
} | |
/* Override this if your state changes are not done synchronously */ | |
STDMETHODIMP | |
CBaseMediaFilter::GetState(DWORD dwMSecs, __out FILTER_STATE *State) | |
{ | |
UNREFERENCED_PARAMETER(dwMSecs); | |
CheckPointer(State,E_POINTER); | |
ValidateReadWritePtr(State,sizeof(FILTER_STATE)); | |
*State = m_State; | |
return S_OK; | |
} | |
/* Set the clock we will use for synchronisation */ | |
STDMETHODIMP | |
CBaseMediaFilter::SetSyncSource(__inout_opt IReferenceClock *pClock) | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
// Ensure the new one does not go away - even if the same as the old | |
if (pClock) { | |
pClock->AddRef(); | |
} | |
// if we have a clock, release it | |
if (m_pClock) { | |
m_pClock->Release(); | |
} | |
// Set the new reference clock (might be NULL) | |
// Should we query it to ensure it is a clock? Consider for a debug build. | |
m_pClock = pClock; | |
return NOERROR; | |
} | |
/* Return the clock we are using for synchronisation */ | |
STDMETHODIMP | |
CBaseMediaFilter::GetSyncSource(__deref_out_opt IReferenceClock **pClock) | |
{ | |
CheckPointer(pClock,E_POINTER); | |
ValidateReadWritePtr(pClock,sizeof(IReferenceClock *)); | |
CAutoLock cObjectLock(m_pLock); | |
if (m_pClock) { | |
// returning an interface... addref it... | |
m_pClock->AddRef(); | |
} | |
*pClock = (IReferenceClock*)m_pClock; | |
return NOERROR; | |
} | |
/* Put the filter into a stopped state */ | |
STDMETHODIMP | |
CBaseMediaFilter::Stop() | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
m_State = State_Stopped; | |
return S_OK; | |
} | |
/* Put the filter into a paused state */ | |
STDMETHODIMP | |
CBaseMediaFilter::Pause() | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
m_State = State_Paused; | |
return S_OK; | |
} | |
// Put the filter into a running state. | |
// The time parameter is the offset to be added to the samples' | |
// stream time to get the reference time at which they should be presented. | |
// | |
// you can either add these two and compare it against the reference clock, | |
// or you can call CBaseMediaFilter::StreamTime and compare that against | |
// the sample timestamp. | |
STDMETHODIMP | |
CBaseMediaFilter::Run(REFERENCE_TIME tStart) | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
// remember the stream time offset | |
m_tStart = tStart; | |
if (m_State == State_Stopped){ | |
HRESULT hr = Pause(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
} | |
m_State = State_Running; | |
return S_OK; | |
} | |
// | |
// return the current stream time - samples with start timestamps of this | |
// time or before should be rendered by now | |
HRESULT | |
CBaseMediaFilter::StreamTime(CRefTime& rtStream) | |
{ | |
// Caller must lock for synchronization | |
// We can't grab the filter lock because we want to be able to call | |
// this from worker threads without deadlocking | |
if (m_pClock == NULL) { | |
return VFW_E_NO_CLOCK; | |
} | |
// get the current reference time | |
HRESULT hr = m_pClock->GetTime((REFERENCE_TIME*)&rtStream); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
// subtract the stream offset to get stream time | |
rtStream -= m_tStart; | |
return S_OK; | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CBaseFilter | |
//===================================================================== | |
//===================================================================== | |
/* Override this to say what interfaces we support and where */ | |
STDMETHODIMP CBaseFilter::NonDelegatingQueryInterface(REFIID riid, | |
__deref_out void **ppv) | |
{ | |
/* Do we have this interface */ | |
if (riid == IID_IBaseFilter) { | |
return GetInterface((IBaseFilter *) this, ppv); | |
} else if (riid == IID_IMediaFilter) { | |
return GetInterface((IMediaFilter *) this, ppv); | |
} else if (riid == IID_IPersist) { | |
return GetInterface((IPersist *) this, ppv); | |
} else if (riid == IID_IAMovieSetup) { | |
return GetInterface((IAMovieSetup *) this, ppv); | |
} else { | |
return CUnknown::NonDelegatingQueryInterface(riid, ppv); | |
} | |
} | |
#ifdef DEBUG | |
STDMETHODIMP_(ULONG) CBaseFilter::NonDelegatingRelease() | |
{ | |
if (m_cRef == 1) { | |
KASSERT(m_pGraph == NULL); | |
} | |
return CUnknown::NonDelegatingRelease(); | |
} | |
#endif | |
/* Constructor */ | |
CBaseFilter::CBaseFilter(__in_opt LPCTSTR pName, | |
__inout_opt LPUNKNOWN pUnk, | |
__in CCritSec *pLock, | |
REFCLSID clsid) : | |
CUnknown( pName, pUnk ), | |
m_pLock(pLock), | |
m_clsid(clsid), | |
m_State(State_Stopped), | |
m_pClock(NULL), | |
m_pGraph(NULL), | |
m_pSink(NULL), | |
m_pName(NULL), | |
m_PinVersion(1) | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( pName ? pName : L"CBaseFilter", (IBaseFilter *) this ); | |
#endif // DXMPERF | |
ASSERT(pLock != NULL); | |
} | |
/* Passes in a redundant HRESULT argument */ | |
CBaseFilter::CBaseFilter(__in_opt LPCTSTR pName, | |
__in_opt LPUNKNOWN pUnk, | |
__in CCritSec *pLock, | |
REFCLSID clsid, | |
__inout HRESULT *phr) : | |
CUnknown( pName, pUnk ), | |
m_pLock(pLock), | |
m_clsid(clsid), | |
m_State(State_Stopped), | |
m_pClock(NULL), | |
m_pGraph(NULL), | |
m_pSink(NULL), | |
m_pName(NULL), | |
m_PinVersion(1) | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( pName ? pName : L"CBaseFilter", (IBaseFilter *) this ); | |
#endif // DXMPERF | |
ASSERT(pLock != NULL); | |
UNREFERENCED_PARAMETER(phr); | |
} | |
#ifdef UNICODE | |
CBaseFilter::CBaseFilter(__in_opt LPCSTR pName, | |
__in_opt LPUNKNOWN pUnk, | |
__in CCritSec *pLock, | |
REFCLSID clsid) : | |
CUnknown( pName, pUnk ), | |
m_pLock(pLock), | |
m_clsid(clsid), | |
m_State(State_Stopped), | |
m_pClock(NULL), | |
m_pGraph(NULL), | |
m_pSink(NULL), | |
m_pName(NULL), | |
m_PinVersion(1) | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( L"CBaseFilter", (IBaseFilter *) this ); | |
#endif // DXMPERF | |
ASSERT(pLock != NULL); | |
} | |
CBaseFilter::CBaseFilter(__in_opt LPCSTR pName, | |
__in_opt LPUNKNOWN pUnk, | |
__in CCritSec *pLock, | |
REFCLSID clsid, | |
__inout HRESULT *phr) : | |
CUnknown( pName, pUnk ), | |
m_pLock(pLock), | |
m_clsid(clsid), | |
m_State(State_Stopped), | |
m_pClock(NULL), | |
m_pGraph(NULL), | |
m_pSink(NULL), | |
m_pName(NULL), | |
m_PinVersion(1) | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( L"CBaseFilter", (IBaseFilter *) this ); | |
#endif // DXMPERF | |
ASSERT(pLock != NULL); | |
UNREFERENCED_PARAMETER(phr); | |
} | |
#endif | |
/* Destructor */ | |
CBaseFilter::~CBaseFilter() | |
{ | |
#ifdef DXMPERF | |
PERFLOG_DTOR( L"CBaseFilter", (IBaseFilter *) this ); | |
#endif // DXMPERF | |
// NOTE we do NOT hold references on the filtergraph for m_pGraph or m_pSink | |
// When we did we had the circular reference problem. Nothing would go away. | |
delete[] m_pName; | |
// must be stopped, but can't call Stop here since | |
// our critsec has been destroyed. | |
/* Release any clock we were using */ | |
if (m_pClock) { | |
m_pClock->Release(); | |
m_pClock = NULL; | |
} | |
} | |
/* Return the filter's clsid */ | |
STDMETHODIMP | |
CBaseFilter::GetClassID(__out CLSID *pClsID) | |
{ | |
CheckPointer(pClsID,E_POINTER); | |
ValidateReadWritePtr(pClsID,sizeof(CLSID)); | |
*pClsID = m_clsid; | |
return NOERROR; | |
} | |
/* Override this if your state changes are not done synchronously */ | |
STDMETHODIMP | |
CBaseFilter::GetState(DWORD dwMSecs, __out FILTER_STATE *State) | |
{ | |
UNREFERENCED_PARAMETER(dwMSecs); | |
CheckPointer(State,E_POINTER); | |
ValidateReadWritePtr(State,sizeof(FILTER_STATE)); | |
*State = m_State; | |
return S_OK; | |
} | |
/* Set the clock we will use for synchronisation */ | |
STDMETHODIMP | |
CBaseFilter::SetSyncSource(__in_opt IReferenceClock *pClock) | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
// Ensure the new one does not go away - even if the same as the old | |
if (pClock) { | |
pClock->AddRef(); | |
} | |
// if we have a clock, release it | |
if (m_pClock) { | |
m_pClock->Release(); | |
} | |
// Set the new reference clock (might be NULL) | |
// Should we query it to ensure it is a clock? Consider for a debug build. | |
m_pClock = pClock; | |
return NOERROR; | |
} | |
/* Return the clock we are using for synchronisation */ | |
STDMETHODIMP | |
CBaseFilter::GetSyncSource(__deref_out_opt IReferenceClock **pClock) | |
{ | |
CheckPointer(pClock,E_POINTER); | |
ValidateReadWritePtr(pClock,sizeof(IReferenceClock *)); | |
CAutoLock cObjectLock(m_pLock); | |
if (m_pClock) { | |
// returning an interface... addref it... | |
m_pClock->AddRef(); | |
} | |
*pClock = (IReferenceClock*)m_pClock; | |
return NOERROR; | |
} | |
// override CBaseMediaFilter Stop method, to deactivate any pins this | |
// filter has. | |
STDMETHODIMP | |
CBaseFilter::Stop() | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
HRESULT hr = NOERROR; | |
// notify all pins of the state change | |
if (m_State != State_Stopped) { | |
int cPins = GetPinCount(); | |
for (int c = 0; c < cPins; c++) { | |
CBasePin *pPin = GetPin(c); | |
if (NULL == pPin) { | |
break; | |
} | |
// Disconnected pins are not activated - this saves pins worrying | |
// about this state themselves. We ignore the return code to make | |
// sure everyone is inactivated regardless. The base input pin | |
// class can return an error if it has no allocator but Stop can | |
// be used to resync the graph state after something has gone bad | |
if (pPin->IsConnected()) { | |
HRESULT hrTmp = pPin->Inactive(); | |
if (FAILED(hrTmp) && SUCCEEDED(hr)) { | |
hr = hrTmp; | |
} | |
} | |
} | |
} | |
#ifdef DXMPERF | |
PERFLOG_STOP( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, m_State ); | |
#endif // DXMPERF | |
m_State = State_Stopped; | |
return hr; | |
} | |
// override CBaseMediaFilter Pause method to activate any pins | |
// this filter has (also called from Run) | |
STDMETHODIMP | |
CBaseFilter::Pause() | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
// notify all pins of the change to active state | |
if (m_State == State_Stopped) { | |
int cPins = GetPinCount(); | |
for (int c = 0; c < cPins; c++) { | |
CBasePin *pPin = GetPin(c); | |
if (NULL == pPin) { | |
break; | |
} | |
// Disconnected pins are not activated - this saves pins | |
// worrying about this state themselves | |
if (pPin->IsConnected()) { | |
HRESULT hr = pPin->Active(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
} | |
} | |
} | |
#ifdef DXMPERF | |
PERFLOG_PAUSE( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, m_State ); | |
#endif // DXMPERF | |
m_State = State_Paused; | |
return S_OK; | |
} | |
// Put the filter into a running state. | |
// The time parameter is the offset to be added to the samples' | |
// stream time to get the reference time at which they should be presented. | |
// | |
// you can either add these two and compare it against the reference clock, | |
// or you can call CBaseFilter::StreamTime and compare that against | |
// the sample timestamp. | |
STDMETHODIMP | |
CBaseFilter::Run(REFERENCE_TIME tStart) | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
// remember the stream time offset | |
m_tStart = tStart; | |
if (m_State == State_Stopped){ | |
HRESULT hr = Pause(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
} | |
// notify all pins of the change to active state | |
if (m_State != State_Running) { | |
int cPins = GetPinCount(); | |
for (int c = 0; c < cPins; c++) { | |
CBasePin *pPin = GetPin(c); | |
if (NULL == pPin) { | |
break; | |
} | |
// Disconnected pins are not activated - this saves pins | |
// worrying about this state themselves | |
if (pPin->IsConnected()) { | |
HRESULT hr = pPin->Run(tStart); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
} | |
} | |
} | |
#ifdef DXMPERF | |
PERFLOG_RUN( m_pName ? m_pName : L"CBaseFilter", (IBaseFilter *) this, tStart, m_State ); | |
#endif // DXMPERF | |
m_State = State_Running; | |
return S_OK; | |
} | |
// | |
// return the current stream time - samples with start timestamps of this | |
// time or before should be rendered by now | |
HRESULT | |
CBaseFilter::StreamTime(CRefTime& rtStream) | |
{ | |
// Caller must lock for synchronization | |
// We can't grab the filter lock because we want to be able to call | |
// this from worker threads without deadlocking | |
if (m_pClock == NULL) { | |
return VFW_E_NO_CLOCK; | |
} | |
// get the current reference time | |
HRESULT hr = m_pClock->GetTime((REFERENCE_TIME*)&rtStream); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
// subtract the stream offset to get stream time | |
rtStream -= m_tStart; | |
return S_OK; | |
} | |
/* Create an enumerator for the pins attached to this filter */ | |
STDMETHODIMP | |
CBaseFilter::EnumPins(__deref_out IEnumPins **ppEnum) | |
{ | |
CheckPointer(ppEnum,E_POINTER); | |
ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *)); | |
/* Create a new ref counted enumerator */ | |
*ppEnum = new CEnumPins(this, | |
NULL); | |
return *ppEnum == NULL ? E_OUTOFMEMORY : NOERROR; | |
} | |
// default behaviour of FindPin is to assume pins are named | |
// by their pin names | |
STDMETHODIMP | |
CBaseFilter::FindPin( | |
LPCWSTR Id, | |
__deref_out IPin ** ppPin | |
) | |
{ | |
CheckPointer(ppPin,E_POINTER); | |
ValidateReadWritePtr(ppPin,sizeof(IPin *)); | |
// We're going to search the pin list so maintain integrity | |
CAutoLock lck(m_pLock); | |
int iCount = GetPinCount(); | |
for (int i = 0; i < iCount; i++) { | |
CBasePin *pPin = GetPin(i); | |
if (NULL == pPin) { | |
break; | |
} | |
if (0 == lstrcmpW(pPin->Name(), Id)) { | |
// Found one that matches | |
// | |
// AddRef() and return it | |
*ppPin = pPin; | |
pPin->AddRef(); | |
return S_OK; | |
} | |
} | |
*ppPin = NULL; | |
return VFW_E_NOT_FOUND; | |
} | |
/* Return information about this filter */ | |
STDMETHODIMP | |
CBaseFilter::QueryFilterInfo(__out FILTER_INFO * pInfo) | |
{ | |
CheckPointer(pInfo,E_POINTER); | |
ValidateReadWritePtr(pInfo,sizeof(FILTER_INFO)); | |
if (m_pName) { | |
(void)StringCchCopyW(pInfo->achName, NUMELMS(pInfo->achName), m_pName); | |
} else { | |
pInfo->achName[0] = L'\0'; | |
} | |
pInfo->pGraph = m_pGraph; | |
if (m_pGraph) | |
m_pGraph->AddRef(); | |
return NOERROR; | |
} | |
/* Provide the filter with a filter graph */ | |
STDMETHODIMP | |
CBaseFilter::JoinFilterGraph( | |
__inout_opt IFilterGraph * pGraph, | |
__in_opt LPCWSTR pName) | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
// NOTE: we no longer hold references on the graph (m_pGraph, m_pSink) | |
m_pGraph = pGraph; | |
if (m_pGraph) { | |
HRESULT hr = m_pGraph->QueryInterface(IID_IMediaEventSink, | |
(void**) &m_pSink); | |
if (FAILED(hr)) { | |
ASSERT(m_pSink == NULL); | |
} | |
else m_pSink->Release(); // we do NOT keep a reference on it. | |
} else { | |
// if graph pointer is null, then we should | |
// also release the IMediaEventSink on the same object - we don't | |
// refcount it, so just set it to null | |
m_pSink = NULL; | |
} | |
if (m_pName) { | |
delete[] m_pName; | |
m_pName = NULL; | |
} | |
if (pName) { | |
size_t namelen; | |
HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &namelen); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
m_pName = new WCHAR[namelen + 1]; | |
if (m_pName) { | |
(void)StringCchCopyW(m_pName, namelen + 1, pName); | |
} else { | |
return E_OUTOFMEMORY; | |
} | |
} | |
#ifdef DXMPERF | |
PERFLOG_JOINGRAPH( m_pName ? m_pName : L"CBaseFilter",(IBaseFilter *) this, pGraph ); | |
#endif // DXMPERF | |
return NOERROR; | |
} | |
// return a Vendor information string. Optional - may return E_NOTIMPL. | |
// memory returned should be freed using CoTaskMemFree | |
// default implementation returns E_NOTIMPL | |
STDMETHODIMP | |
CBaseFilter::QueryVendorInfo( | |
__deref_out LPWSTR* pVendorInfo) | |
{ | |
UNREFERENCED_PARAMETER(pVendorInfo); | |
return E_NOTIMPL; | |
} | |
// send an event notification to the filter graph if we know about it. | |
// returns S_OK if delivered, S_FALSE if the filter graph does not sink | |
// events, or an error otherwise. | |
HRESULT | |
CBaseFilter::NotifyEvent( | |
long EventCode, | |
LONG_PTR EventParam1, | |
LONG_PTR EventParam2) | |
{ | |
// Snapshot so we don't have to lock up | |
IMediaEventSink *pSink = m_pSink; | |
if (pSink) { | |
if (EC_COMPLETE == EventCode) { | |
EventParam2 = (LONG_PTR)(IBaseFilter*)this; | |
} | |
return pSink->Notify(EventCode, EventParam1, EventParam2); | |
} else { | |
return E_NOTIMPL; | |
} | |
} | |
// Request reconnect | |
// pPin is the pin to reconnect | |
// pmt is the type to reconnect with - can be NULL | |
// Calls ReconnectEx on the filter graph | |
HRESULT | |
CBaseFilter::ReconnectPin( | |
IPin *pPin, | |
__in_opt AM_MEDIA_TYPE const *pmt | |
) | |
{ | |
IFilterGraph2 *pGraph2; | |
if (m_pGraph != NULL) { | |
HRESULT hr = m_pGraph->QueryInterface(IID_IFilterGraph2, (void **)&pGraph2); | |
if (SUCCEEDED(hr)) { | |
hr = pGraph2->ReconnectEx(pPin, pmt); | |
pGraph2->Release(); | |
return hr; | |
} else { | |
return m_pGraph->Reconnect(pPin); | |
} | |
} else { | |
return E_NOINTERFACE; | |
} | |
} | |
/* This is the same idea as the media type version does for type enumeration | |
on pins but for the list of pins available. So if the list of pins you | |
provide changes dynamically then either override this virtual function | |
to provide the version number, or more simply call IncrementPinVersion */ | |
LONG CBaseFilter::GetPinVersion() | |
{ | |
return m_PinVersion; | |
} | |
/* Increment the current pin version cookie */ | |
void CBaseFilter::IncrementPinVersion() | |
{ | |
InterlockedIncrement(&m_PinVersion); | |
} | |
/* register filter */ | |
STDMETHODIMP CBaseFilter::Register() | |
{ | |
// get setup data, if it exists | |
// | |
LPAMOVIESETUP_FILTER psetupdata = GetSetupData(); | |
// check we've got data | |
// | |
if( NULL == psetupdata ) return S_FALSE; | |
// init is ref counted so call just in case | |
// we're being called cold. | |
// | |
HRESULT hr = CoInitialize( (LPVOID)NULL ); | |
ASSERT( SUCCEEDED(hr) ); | |
// get hold of IFilterMapper | |
// | |
IFilterMapper *pIFM; | |
hr = CoCreateInstance( CLSID_FilterMapper | |
, NULL | |
, CLSCTX_INPROC_SERVER | |
, IID_IFilterMapper | |
, (void **)&pIFM ); | |
if( SUCCEEDED(hr) ) | |
{ | |
hr = AMovieSetupRegisterFilter( psetupdata, pIFM, TRUE ); | |
pIFM->Release(); | |
} | |
// and clear up | |
// | |
CoFreeUnusedLibraries(); | |
CoUninitialize(); | |
return NOERROR; | |
} | |
/* unregister filter */ | |
STDMETHODIMP CBaseFilter::Unregister() | |
{ | |
// get setup data, if it exists | |
// | |
LPAMOVIESETUP_FILTER psetupdata = GetSetupData(); | |
// check we've got data | |
// | |
if( NULL == psetupdata ) return S_FALSE; | |
// OLE init is ref counted so call | |
// just in case we're being called cold. | |
// | |
HRESULT hr = CoInitialize( (LPVOID)NULL ); | |
ASSERT( SUCCEEDED(hr) ); | |
// get hold of IFilterMapper | |
// | |
IFilterMapper *pIFM; | |
hr = CoCreateInstance( CLSID_FilterMapper | |
, NULL | |
, CLSCTX_INPROC_SERVER | |
, IID_IFilterMapper | |
, (void **)&pIFM ); | |
if( SUCCEEDED(hr) ) | |
{ | |
hr = AMovieSetupRegisterFilter( psetupdata, pIFM, FALSE ); | |
// release interface | |
// | |
pIFM->Release(); | |
} | |
// clear up | |
// | |
CoFreeUnusedLibraries(); | |
CoUninitialize(); | |
// handle one acceptable "error" - that | |
// of filter not being registered! | |
// (couldn't find a suitable #define'd | |
// name for the error!) | |
// | |
if( 0x80070002 == hr) | |
return NOERROR; | |
else | |
return hr; | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CEnumPins | |
//===================================================================== | |
//===================================================================== | |
CEnumPins::CEnumPins(__in CBaseFilter *pFilter, | |
__in_opt CEnumPins *pEnumPins) : | |
m_Position(0), | |
m_PinCount(0), | |
m_pFilter(pFilter), | |
m_cRef(1), // Already ref counted | |
m_PinCache(NAME("Pin Cache")) | |
{ | |
#ifdef DEBUG | |
m_dwCookie = DbgRegisterObjectCreation("CEnumPins", 0); | |
#endif | |
/* We must be owned by a filter derived from CBaseFilter */ | |
ASSERT(pFilter != NULL); | |
/* Hold a reference count on our filter */ | |
m_pFilter->AddRef(); | |
/* Are we creating a new enumerator */ | |
if (pEnumPins == NULL) { | |
m_Version = m_pFilter->GetPinVersion(); | |
m_PinCount = m_pFilter->GetPinCount(); | |
} else { | |
ASSERT(m_Position <= m_PinCount); | |
m_Position = pEnumPins->m_Position; | |
m_PinCount = pEnumPins->m_PinCount; | |
m_Version = pEnumPins->m_Version; | |
m_PinCache.AddTail(&(pEnumPins->m_PinCache)); | |
} | |
} | |
/* Destructor releases the reference count on our filter NOTE since we hold | |
a reference count on the filter who created us we know it is safe to | |
release it, no access can be made to it afterwards though as we have just | |
caused the last reference count to go and the object to be deleted */ | |
CEnumPins::~CEnumPins() | |
{ | |
m_pFilter->Release(); | |
#ifdef DEBUG | |
DbgRegisterObjectDestruction(m_dwCookie); | |
#endif | |
} | |
/* Override this to say what interfaces we support where */ | |
STDMETHODIMP | |
CEnumPins::QueryInterface(REFIID riid, __deref_out void **ppv) | |
{ | |
CheckPointer(ppv, E_POINTER); | |
/* Do we have this interface */ | |
if (riid == IID_IEnumPins || riid == IID_IUnknown) { | |
return GetInterface((IEnumPins *) this, ppv); | |
} else { | |
*ppv = NULL; | |
return E_NOINTERFACE; | |
} | |
} | |
STDMETHODIMP_(ULONG) | |
CEnumPins::AddRef() | |
{ | |
return InterlockedIncrement(&m_cRef); | |
} | |
STDMETHODIMP_(ULONG) | |
CEnumPins::Release() | |
{ | |
ULONG cRef = InterlockedDecrement(&m_cRef); | |
if (cRef == 0) { | |
delete this; | |
} | |
return cRef; | |
} | |
/* One of an enumerator's basic member functions allows us to create a cloned | |
interface that initially has the same state. Since we are taking a snapshot | |
of an object (current position and all) we must lock access at the start */ | |
STDMETHODIMP | |
CEnumPins::Clone(__deref_out IEnumPins **ppEnum) | |
{ | |
CheckPointer(ppEnum,E_POINTER); | |
ValidateReadWritePtr(ppEnum,sizeof(IEnumPins *)); | |
HRESULT hr = NOERROR; | |
/* Check we are still in sync with the filter */ | |
if (AreWeOutOfSync() == TRUE) { | |
*ppEnum = NULL; | |
hr = VFW_E_ENUM_OUT_OF_SYNC; | |
} else { | |
*ppEnum = new CEnumPins(m_pFilter, | |
this); | |
if (*ppEnum == NULL) { | |
hr = E_OUTOFMEMORY; | |
} | |
} | |
return hr; | |
} | |
/* Return the next pin after the current position */ | |
STDMETHODIMP | |
CEnumPins::Next(ULONG cPins, // place this many pins... | |
__out_ecount(cPins) IPin **ppPins, // ...in this array | |
__out_opt ULONG *pcFetched) // actual count passed returned here | |
{ | |
CheckPointer(ppPins,E_POINTER); | |
ValidateReadWritePtr(ppPins,cPins * sizeof(IPin *)); | |
ASSERT(ppPins); | |
if (pcFetched!=NULL) { | |
ValidateWritePtr(pcFetched, sizeof(ULONG)); | |
*pcFetched = 0; // default unless we succeed | |
} | |
// now check that the parameter is valid | |
else if (cPins>1) { // pcFetched == NULL | |
return E_INVALIDARG; | |
} | |
ULONG cFetched = 0; // increment as we get each one. | |
/* Check we are still in sync with the filter */ | |
if (AreWeOutOfSync() == TRUE) { | |
// If we are out of sync, we should refresh the enumerator. | |
// This will reset the position and update the other members, but | |
// will not clear cache of pins we have already returned. | |
Refresh(); | |
} | |
/* Return each pin interface NOTE GetPin returns CBasePin * not addrefed | |
so we must QI for the IPin (which increments its reference count) | |
If while we are retrieving a pin from the filter an error occurs we | |
assume that our internal state is stale with respect to the filter | |
(for example someone has deleted a pin) so we | |
return VFW_E_ENUM_OUT_OF_SYNC */ | |
while (cFetched < cPins && m_PinCount > m_Position) { | |
/* Get the next pin object from the filter */ | |
CBasePin *pPin = m_pFilter->GetPin(m_Position++); | |
if (pPin == NULL) { | |
// If this happend, and it's not the first time through, then we've got a problem, | |
// since we should really go back and release the iPins, which we have previously | |
// AddRef'ed. | |
ASSERT( cFetched==0 ); | |
return VFW_E_ENUM_OUT_OF_SYNC; | |
} | |
/* We only want to return this pin, if it is not in our cache */ | |
if (0 == m_PinCache.Find(pPin)) | |
{ | |
/* From the object get an IPin interface */ | |
*ppPins = pPin; | |
pPin->AddRef(); | |
cFetched++; | |
ppPins++; | |
m_PinCache.AddTail(pPin); | |
} | |
} | |
if (pcFetched!=NULL) { | |
*pcFetched = cFetched; | |
} | |
return (cPins==cFetched ? NOERROR : S_FALSE); | |
} | |
/* Skip over one or more entries in the enumerator */ | |
STDMETHODIMP | |
CEnumPins::Skip(ULONG cPins) | |
{ | |
/* Check we are still in sync with the filter */ | |
if (AreWeOutOfSync() == TRUE) { | |
return VFW_E_ENUM_OUT_OF_SYNC; | |
} | |
/* Work out how many pins are left to skip over */ | |
/* We could position at the end if we are asked to skip too many... */ | |
/* ..which would match the base implementation for CEnumMediaTypes::Skip */ | |
ULONG PinsLeft = m_PinCount - m_Position; | |
if (cPins > PinsLeft) { | |
return S_FALSE; | |
} | |
m_Position += cPins; | |
return NOERROR; | |
} | |
/* Set the current position back to the start */ | |
/* Reset has 4 simple steps: | |
* | |
* Set position to head of list | |
* Sync enumerator with object being enumerated | |
* Clear the cache of pins already returned | |
* return S_OK | |
*/ | |
STDMETHODIMP | |
CEnumPins::Reset() | |
{ | |
m_Version = m_pFilter->GetPinVersion(); | |
m_PinCount = m_pFilter->GetPinCount(); | |
m_Position = 0; | |
// Clear the cache | |
m_PinCache.RemoveAll(); | |
return S_OK; | |
} | |
/* Set the current position back to the start */ | |
/* Refresh has 3 simple steps: | |
* | |
* Set position to head of list | |
* Sync enumerator with object being enumerated | |
* return S_OK | |
*/ | |
STDMETHODIMP | |
CEnumPins::Refresh() | |
{ | |
m_Version = m_pFilter->GetPinVersion(); | |
m_PinCount = m_pFilter->GetPinCount(); | |
m_Position = 0; | |
return S_OK; | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CEnumMediaTypes | |
//===================================================================== | |
//===================================================================== | |
CEnumMediaTypes::CEnumMediaTypes(__in CBasePin *pPin, | |
__in_opt CEnumMediaTypes *pEnumMediaTypes) : | |
m_Position(0), | |
m_pPin(pPin), | |
m_cRef(1) | |
{ | |
#ifdef DEBUG | |
m_dwCookie = DbgRegisterObjectCreation("CEnumMediaTypes", 0); | |
#endif | |
/* We must be owned by a pin derived from CBasePin */ | |
ASSERT(pPin != NULL); | |
/* Hold a reference count on our pin */ | |
m_pPin->AddRef(); | |
/* Are we creating a new enumerator */ | |
if (pEnumMediaTypes == NULL) { | |
m_Version = m_pPin->GetMediaTypeVersion(); | |
return; | |
} | |
m_Position = pEnumMediaTypes->m_Position; | |
m_Version = pEnumMediaTypes->m_Version; | |
} | |
/* Destructor releases the reference count on our base pin. NOTE since we hold | |
a reference count on the pin who created us we know it is safe to release | |
it, no access can be made to it afterwards though as we might have just | |
caused the last reference count to go and the object to be deleted */ | |
CEnumMediaTypes::~CEnumMediaTypes() | |
{ | |
#ifdef DEBUG | |
DbgRegisterObjectDestruction(m_dwCookie); | |
#endif | |
m_pPin->Release(); | |
} | |
/* Override this to say what interfaces we support where */ | |
STDMETHODIMP | |
CEnumMediaTypes::QueryInterface(REFIID riid, __deref_out void **ppv) | |
{ | |
CheckPointer(ppv, E_POINTER); | |
/* Do we have this interface */ | |
if (riid == IID_IEnumMediaTypes || riid == IID_IUnknown) { | |
return GetInterface((IEnumMediaTypes *) this, ppv); | |
} else { | |
*ppv = NULL; | |
return E_NOINTERFACE; | |
} | |
} | |
STDMETHODIMP_(ULONG) | |
CEnumMediaTypes::AddRef() | |
{ | |
return InterlockedIncrement(&m_cRef); | |
} | |
STDMETHODIMP_(ULONG) | |
CEnumMediaTypes::Release() | |
{ | |
ULONG cRef = InterlockedDecrement(&m_cRef); | |
if (cRef == 0) { | |
delete this; | |
} | |
return cRef; | |
} | |
/* One of an enumerator's basic member functions allows us to create a cloned | |
interface that initially has the same state. Since we are taking a snapshot | |
of an object (current position and all) we must lock access at the start */ | |
STDMETHODIMP | |
CEnumMediaTypes::Clone(__deref_out IEnumMediaTypes **ppEnum) | |
{ | |
CheckPointer(ppEnum,E_POINTER); | |
ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *)); | |
HRESULT hr = NOERROR; | |
/* Check we are still in sync with the pin */ | |
if (AreWeOutOfSync() == TRUE) { | |
*ppEnum = NULL; | |
hr = VFW_E_ENUM_OUT_OF_SYNC; | |
} else { | |
*ppEnum = new CEnumMediaTypes(m_pPin, | |
this); | |
if (*ppEnum == NULL) { | |
hr = E_OUTOFMEMORY; | |
} | |
} | |
return hr; | |
} | |
/* Enumerate the next pin(s) after the current position. The client using this | |
interface passes in a pointer to an array of pointers each of which will | |
be filled in with a pointer to a fully initialised media type format | |
Return NOERROR if it all works, | |
S_FALSE if fewer than cMediaTypes were enumerated. | |
VFW_E_ENUM_OUT_OF_SYNC if the enumerator has been broken by | |
state changes in the filter | |
The actual count always correctly reflects the number of types in the array. | |
*/ | |
STDMETHODIMP | |
CEnumMediaTypes::Next(ULONG cMediaTypes, // place this many types... | |
__out_ecount(cMediaTypes) AM_MEDIA_TYPE **ppMediaTypes, // ...in this array | |
__out ULONG *pcFetched) // actual count passed | |
{ | |
CheckPointer(ppMediaTypes,E_POINTER); | |
ValidateReadWritePtr(ppMediaTypes,cMediaTypes * sizeof(AM_MEDIA_TYPE *)); | |
/* Check we are still in sync with the pin */ | |
if (AreWeOutOfSync() == TRUE) { | |
return VFW_E_ENUM_OUT_OF_SYNC; | |
} | |
if (pcFetched!=NULL) { | |
ValidateWritePtr(pcFetched, sizeof(ULONG)); | |
*pcFetched = 0; // default unless we succeed | |
} | |
// now check that the parameter is valid | |
else if (cMediaTypes>1) { // pcFetched == NULL | |
return E_INVALIDARG; | |
} | |
ULONG cFetched = 0; // increment as we get each one. | |
/* Return each media type by asking the filter for them in turn - If we | |
have an error code retured to us while we are retrieving a media type | |
we assume that our internal state is stale with respect to the filter | |
(for example the window size changing) so we return | |
VFW_E_ENUM_OUT_OF_SYNC */ | |
while (cMediaTypes) { | |
CMediaType cmt; | |
HRESULT hr = m_pPin->GetMediaType(m_Position++, &cmt); | |
if (S_OK != hr) { | |
break; | |
} | |
/* We now have a CMediaType object that contains the next media type | |
but when we assign it to the array position we CANNOT just assign | |
the AM_MEDIA_TYPE structure because as soon as the object goes out of | |
scope it will delete the memory we have just copied. The function | |
we use is CreateMediaType which allocates a task memory block */ | |
/* Transfer across the format block manually to save an allocate | |
and free on the format block and generally go faster */ | |
*ppMediaTypes = (AM_MEDIA_TYPE *)CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE)); | |
if (*ppMediaTypes == NULL) { | |
break; | |
} | |
/* Do a regular copy */ | |
**ppMediaTypes = cmt; | |
/* Make sure the destructor doesn't free these */ | |
cmt.pbFormat = NULL; | |
cmt.cbFormat = NULL; | |
cmt.pUnk = NULL; | |
ppMediaTypes++; | |
cFetched++; | |
cMediaTypes--; | |
} | |
if (pcFetched!=NULL) { | |
*pcFetched = cFetched; | |
} | |
return ( cMediaTypes==0 ? NOERROR : S_FALSE ); | |
} | |
/* Skip over one or more entries in the enumerator */ | |
STDMETHODIMP | |
CEnumMediaTypes::Skip(ULONG cMediaTypes) | |
{ | |
// If we're skipping 0 elements we're guaranteed to skip the | |
// correct number of elements | |
if (cMediaTypes == 0) { | |
return S_OK; | |
} | |
/* Check we are still in sync with the pin */ | |
if (AreWeOutOfSync() == TRUE) { | |
return VFW_E_ENUM_OUT_OF_SYNC; | |
} | |
m_Position += cMediaTypes; | |
/* See if we're over the end */ | |
CMediaType cmt; | |
return S_OK == m_pPin->GetMediaType(m_Position - 1, &cmt) ? S_OK : S_FALSE; | |
} | |
/* Set the current position back to the start */ | |
/* Reset has 3 simple steps: | |
* | |
* set position to head of list | |
* sync enumerator with object being enumerated | |
* return S_OK | |
*/ | |
STDMETHODIMP | |
CEnumMediaTypes::Reset() | |
{ | |
m_Position = 0; | |
// Bring the enumerator back into step with the current state. This | |
// may be a noop but ensures that the enumerator will be valid on the | |
// next call. | |
m_Version = m_pPin->GetMediaTypeVersion(); | |
return NOERROR; | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CBasePin | |
//===================================================================== | |
//===================================================================== | |
/* NOTE The implementation of this class calls the CUnknown constructor with | |
a NULL outer unknown pointer. This has the effect of making us a self | |
contained class, ie any QueryInterface, AddRef or Release calls will be | |
routed to the class's NonDelegatingUnknown methods. You will typically | |
find that the classes that do this then override one or more of these | |
virtual functions to provide more specialised behaviour. A good example | |
of this is where a class wants to keep the QueryInterface internal but | |
still wants its lifetime controlled by the external object */ | |
/* Constructor */ | |
CBasePin::CBasePin(__in_opt LPCTSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pName, | |
PIN_DIRECTION dir) : | |
CUnknown( pObjectName, NULL ), | |
m_pFilter(pFilter), | |
m_pLock(pLock), | |
m_pName(NULL), | |
m_Connected(NULL), | |
m_dir(dir), | |
m_bRunTimeError(FALSE), | |
m_pQSink(NULL), | |
m_TypeVersion(1), | |
m_tStart(), | |
m_tStop(MAX_TIME), | |
m_bCanReconnectWhenActive(false), | |
m_bTryMyTypesFirst(false), | |
m_dRate(1.0) | |
{ | |
/* WARNING - pFilter is often not a properly constituted object at | |
this state (in particular QueryInterface may not work) - this | |
is because its owner is often its containing object and we | |
have been called from the containing object's constructor so | |
the filter's owner has not yet had its CUnknown constructor | |
called | |
*/ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( pName ? pName : L"CBasePin", (IPin *) this ); | |
#endif // DXMPERF | |
ASSERT(pFilter != NULL); | |
ASSERT(pLock != NULL); | |
if (pName) { | |
size_t cchName; | |
HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &cchName); | |
if (SUCCEEDED(hr)) { | |
m_pName = new WCHAR[cchName + 1]; | |
if (m_pName) { | |
(void)StringCchCopyW(m_pName, cchName + 1, pName); | |
} | |
} | |
} | |
#ifdef DEBUG | |
m_cRef = 0; | |
#endif | |
} | |
#ifdef UNICODE | |
CBasePin::CBasePin(__in_opt LPCSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pName, | |
PIN_DIRECTION dir) : | |
CUnknown( pObjectName, NULL ), | |
m_pFilter(pFilter), | |
m_pLock(pLock), | |
m_pName(NULL), | |
m_Connected(NULL), | |
m_dir(dir), | |
m_bRunTimeError(FALSE), | |
m_pQSink(NULL), | |
m_TypeVersion(1), | |
m_tStart(), | |
m_tStop(MAX_TIME), | |
m_bCanReconnectWhenActive(false), | |
m_bTryMyTypesFirst(false), | |
m_dRate(1.0) | |
{ | |
/* WARNING - pFilter is often not a properly constituted object at | |
this state (in particular QueryInterface may not work) - this | |
is because its owner is often its containing object and we | |
have been called from the containing object's constructor so | |
the filter's owner has not yet had its CUnknown constructor | |
called | |
*/ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( pName ? pName : L"CBasePin", (IPin *) this ); | |
#endif // DXMPERF | |
ASSERT(pFilter != NULL); | |
ASSERT(pLock != NULL); | |
if (pName) { | |
size_t cchName; | |
HRESULT hr = StringCchLengthW(pName, STRSAFE_MAX_CCH, &cchName); | |
if (SUCCEEDED(hr)) { | |
m_pName = new WCHAR[cchName + 1]; | |
if (m_pName) { | |
(void)StringCchCopyW(m_pName, cchName + 1, pName); | |
} | |
} | |
} | |
#ifdef DEBUG | |
m_cRef = 0; | |
#endif | |
} | |
#endif | |
/* Destructor since a connected pin holds a reference count on us there is | |
no way that we can be deleted unless we are not currently connected */ | |
CBasePin::~CBasePin() | |
{ | |
#ifdef DXMPERF | |
PERFLOG_DTOR( m_pName ? m_pName : L"CBasePin", (IPin *) this ); | |
#endif // DXMPERF | |
// We don't call disconnect because if the filter is going away | |
// all the pins must have a reference count of zero so they must | |
// have been disconnected anyway - (but check the assumption) | |
ASSERT(m_Connected == FALSE); | |
delete[] m_pName; | |
// check the internal reference count is consistent | |
ASSERT(m_cRef == 0); | |
} | |
/* Override this to say what interfaces we support and where */ | |
STDMETHODIMP | |
CBasePin::NonDelegatingQueryInterface(REFIID riid, __deref_out void ** ppv) | |
{ | |
/* Do we have this interface */ | |
if (riid == IID_IPin) { | |
return GetInterface((IPin *) this, ppv); | |
} else if (riid == IID_IQualityControl) { | |
return GetInterface((IQualityControl *) this, ppv); | |
} else { | |
return CUnknown::NonDelegatingQueryInterface(riid, ppv); | |
} | |
} | |
/* Override to increment the owning filter's reference count */ | |
STDMETHODIMP_(ULONG) | |
CBasePin::NonDelegatingAddRef() | |
{ | |
ASSERT(InterlockedIncrement(&m_cRef) > 0); | |
return m_pFilter->AddRef(); | |
} | |
/* Override to decrement the owning filter's reference count */ | |
STDMETHODIMP_(ULONG) | |
CBasePin::NonDelegatingRelease() | |
{ | |
ASSERT(InterlockedDecrement(&m_cRef) >= 0); | |
return m_pFilter->Release(); | |
} | |
/* Displays pin connection information */ | |
#ifdef DEBUG | |
void | |
CBasePin::DisplayPinInfo(IPin *pReceivePin) | |
{ | |
if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) { | |
PIN_INFO ConnectPinInfo; | |
PIN_INFO ReceivePinInfo; | |
if (FAILED(QueryPinInfo(&ConnectPinInfo))) { | |
StringCchCopyW(ConnectPinInfo.achName, sizeof(ConnectPinInfo.achName)/sizeof(WCHAR), L"Bad Pin"); | |
} else { | |
QueryPinInfoReleaseFilter(ConnectPinInfo); | |
} | |
if (FAILED(pReceivePin->QueryPinInfo(&ReceivePinInfo))) { | |
StringCchCopyW(ReceivePinInfo.achName, sizeof(ReceivePinInfo.achName)/sizeof(WCHAR), L"Bad Pin"); | |
} else { | |
QueryPinInfoReleaseFilter(ReceivePinInfo); | |
} | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying to connect Pins :"))); | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" <%ls>"), ConnectPinInfo.achName)); | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" <%ls>"), ReceivePinInfo.achName)); | |
} | |
} | |
#endif | |
/* Displays general information on the pin media type */ | |
#ifdef DEBUG | |
void CBasePin::DisplayTypeInfo(IPin *pPin, const CMediaType *pmt) | |
{ | |
UNREFERENCED_PARAMETER(pPin); | |
if (DbgCheckModuleLevel(LOG_TRACE, CONNECT_TRACE_LEVEL)) { | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Trying media type:"))); | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" major type: %hs"), | |
GuidNames[*pmt->Type()])); | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT(" sub type : %hs"), | |
GuidNames[*pmt->Subtype()])); | |
} | |
} | |
#endif | |
/* Asked to connect to a pin. A pin is always attached to an owning filter | |
object so we always delegate our locking to that object. We first of all | |
retrieve a media type enumerator for the input pin and see if we accept | |
any of the formats that it would ideally like, failing that we retrieve | |
our enumerator and see if it will accept any of our preferred types */ | |
STDMETHODIMP | |
CBasePin::Connect( | |
IPin * pReceivePin, | |
__in_opt const AM_MEDIA_TYPE *pmt // optional media type | |
) | |
{ | |
CheckPointer(pReceivePin,E_POINTER); | |
ValidateReadPtr(pReceivePin,sizeof(IPin)); | |
CAutoLock cObjectLock(m_pLock); | |
DisplayPinInfo(pReceivePin); | |
/* See if we are already connected */ | |
if (m_Connected) { | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Already connected"))); | |
return VFW_E_ALREADY_CONNECTED; | |
} | |
/* See if the filter is active */ | |
if (!IsStopped() && !m_bCanReconnectWhenActive) { | |
return VFW_E_NOT_STOPPED; | |
} | |
// Find a mutually agreeable media type - | |
// Pass in the template media type. If this is partially specified, | |
// each of the enumerated media types will need to be checked against | |
// it. If it is non-null and fully specified, we will just try to connect | |
// with this. | |
const CMediaType * ptype = (CMediaType*)pmt; | |
HRESULT hr = AgreeMediaType(pReceivePin, ptype); | |
if (FAILED(hr)) { | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to agree type"))); | |
// Since the procedure is already returning an error code, there | |
// is nothing else this function can do to report the error. | |
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); | |
#ifdef DXMPERF | |
PERFLOG_CONNECT( (IPin *) this, pReceivePin, hr, pmt ); | |
#endif // DXMPERF | |
return hr; | |
} | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded"))); | |
#ifdef DXMPERF | |
PERFLOG_CONNECT( (IPin *) this, pReceivePin, NOERROR, pmt ); | |
#endif // DXMPERF | |
return NOERROR; | |
} | |
// given a specific media type, attempt a connection (includes | |
// checking that the type is acceptable to this pin) | |
HRESULT | |
CBasePin::AttemptConnection( | |
IPin* pReceivePin, // connect to this pin | |
const CMediaType* pmt // using this type | |
) | |
{ | |
// The caller should hold the filter lock becasue this function | |
// uses m_Connected. The caller should also hold the filter lock | |
// because this function calls SetMediaType(), IsStopped() and | |
// CompleteConnect(). | |
ASSERT(CritCheckIn(m_pLock)); | |
// Check that the connection is valid -- need to do this for every | |
// connect attempt since BreakConnect will undo it. | |
HRESULT hr = CheckConnect(pReceivePin); | |
if (FAILED(hr)) { | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("CheckConnect failed"))); | |
// Since the procedure is already returning an error code, there | |
// is nothing else this function can do to report the error. | |
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); | |
return hr; | |
} | |
DisplayTypeInfo(pReceivePin, pmt); | |
/* Check we will accept this media type */ | |
hr = CheckMediaType(pmt); | |
if (hr == NOERROR) { | |
/* Make ourselves look connected otherwise ReceiveConnection | |
may not be able to complete the connection | |
*/ | |
m_Connected = pReceivePin; | |
m_Connected->AddRef(); | |
hr = SetMediaType(pmt); | |
if (SUCCEEDED(hr)) { | |
/* See if the other pin will accept this type */ | |
hr = pReceivePin->ReceiveConnection((IPin *)this, pmt); | |
if (SUCCEEDED(hr)) { | |
/* Complete the connection */ | |
hr = CompleteConnect(pReceivePin); | |
if (SUCCEEDED(hr)) { | |
return hr; | |
} else { | |
DbgLog((LOG_TRACE, | |
CONNECT_TRACE_LEVEL, | |
TEXT("Failed to complete connection"))); | |
pReceivePin->Disconnect(); | |
} | |
} | |
} | |
} else { | |
// we cannot use this media type | |
// return a specific media type error if there is one | |
// or map a general failure code to something more helpful | |
// (in particular S_FALSE gets changed to an error code) | |
if (SUCCEEDED(hr) || | |
(hr == E_FAIL) || | |
(hr == E_INVALIDARG)) { | |
hr = VFW_E_TYPE_NOT_ACCEPTED; | |
} | |
} | |
// BreakConnect and release any connection here in case CheckMediaType | |
// failed, or if we set anything up during a call back during | |
// ReceiveConnection. | |
// Since the procedure is already returning an error code, there | |
// is nothing else this function can do to report the error. | |
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); | |
/* If failed then undo our state */ | |
if (m_Connected) { | |
m_Connected->Release(); | |
m_Connected = NULL; | |
} | |
return hr; | |
} | |
/* Given an enumerator we cycle through all the media types it proposes and | |
firstly suggest them to our derived pin class and if that succeeds try | |
them with the pin in a ReceiveConnection call. This means that if our pin | |
proposes a media type we still check in here that we can support it. This | |
is deliberate so that in simple cases the enumerator can hold all of the | |
media types even if some of them are not really currently available */ | |
HRESULT CBasePin::TryMediaTypes( | |
IPin *pReceivePin, | |
__in_opt const CMediaType *pmt, | |
IEnumMediaTypes *pEnum) | |
{ | |
/* Reset the current enumerator position */ | |
HRESULT hr = pEnum->Reset(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
CMediaType *pMediaType = NULL; | |
ULONG ulMediaCount = 0; | |
// attempt to remember a specific error code if there is one | |
HRESULT hrFailure = S_OK; | |
for (;;) { | |
/* Retrieve the next media type NOTE each time round the loop the | |
enumerator interface will allocate another AM_MEDIA_TYPE structure | |
If we are successful then we copy it into our output object, if | |
not then we must delete the memory allocated before returning */ | |
hr = pEnum->Next(1, (AM_MEDIA_TYPE**)&pMediaType,&ulMediaCount); | |
if (hr != S_OK) { | |
if (S_OK == hrFailure) { | |
hrFailure = VFW_E_NO_ACCEPTABLE_TYPES; | |
} | |
return hrFailure; | |
} | |
ASSERT(ulMediaCount == 1); | |
ASSERT(pMediaType); | |
// check that this matches the partial type (if any) | |
if (pMediaType && | |
((pmt == NULL) || | |
pMediaType->MatchesPartial(pmt))) { | |
hr = AttemptConnection(pReceivePin, pMediaType); | |
// attempt to remember a specific error code | |
if (FAILED(hr) && | |
SUCCEEDED(hrFailure) && | |
(hr != E_FAIL) && | |
(hr != E_INVALIDARG) && | |
(hr != VFW_E_TYPE_NOT_ACCEPTED)) { | |
hrFailure = hr; | |
} | |
} else { | |
hr = VFW_E_NO_ACCEPTABLE_TYPES; | |
} | |
if(pMediaType) { | |
DeleteMediaType(pMediaType); | |
pMediaType = NULL; | |
} | |
if (S_OK == hr) { | |
return hr; | |
} | |
} | |
} | |
/* This is called to make the connection, including the taask of finding | |
a media type for the pin connection. pmt is the proposed media type | |
from the Connect call: if this is fully specified, we will try that. | |
Otherwise we enumerate and try all the input pin's types first and | |
if that fails we then enumerate and try all our preferred media types. | |
For each media type we check it against pmt (if non-null and partially | |
specified) as well as checking that both pins will accept it. | |
*/ | |
HRESULT CBasePin::AgreeMediaType( | |
IPin *pReceivePin, | |
const CMediaType *pmt) | |
{ | |
ASSERT(pReceivePin); | |
IEnumMediaTypes *pEnumMediaTypes = NULL; | |
// if the media type is fully specified then use that | |
if ( (pmt != NULL) && (!pmt->IsPartiallySpecified())) { | |
// if this media type fails, then we must fail the connection | |
// since if pmt is nonnull we are only allowed to connect | |
// using a type that matches it. | |
return AttemptConnection(pReceivePin, pmt); | |
} | |
/* Try the other pin's enumerator */ | |
HRESULT hrFailure = VFW_E_NO_ACCEPTABLE_TYPES; | |
for (int i = 0; i < 2; i++) { | |
HRESULT hr; | |
if (i == (int)m_bTryMyTypesFirst) { | |
hr = pReceivePin->EnumMediaTypes(&pEnumMediaTypes); | |
} else { | |
hr = EnumMediaTypes(&pEnumMediaTypes); | |
} | |
if (SUCCEEDED(hr)) { | |
ASSERT(pEnumMediaTypes); | |
hr = TryMediaTypes(pReceivePin,pmt,pEnumMediaTypes); | |
pEnumMediaTypes->Release(); | |
if (SUCCEEDED(hr)) { | |
return NOERROR; | |
} else { | |
// try to remember specific error codes if there are any | |
if ((hr != E_FAIL) && | |
(hr != E_INVALIDARG) && | |
(hr != VFW_E_TYPE_NOT_ACCEPTED)) { | |
hrFailure = hr; | |
} | |
} | |
} | |
} | |
return hrFailure; | |
} | |
/* Called when we want to complete a connection to another filter. Failing | |
this will also fail the connection and disconnect the other pin as well */ | |
HRESULT | |
CBasePin::CompleteConnect(IPin *pReceivePin) | |
{ | |
UNREFERENCED_PARAMETER(pReceivePin); | |
return NOERROR; | |
} | |
/* This is called to set the format for a pin connection - CheckMediaType | |
will have been called to check the connection format and if it didn't | |
return an error code then this (virtual) function will be invoked */ | |
HRESULT | |
CBasePin::SetMediaType(const CMediaType *pmt) | |
{ | |
HRESULT hr = m_mt.Set(*pmt); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
return NOERROR; | |
} | |
/* This is called during Connect() to provide a virtual method that can do | |
any specific check needed for connection such as QueryInterface. This | |
base class method just checks that the pin directions don't match */ | |
HRESULT | |
CBasePin::CheckConnect(IPin * pPin) | |
{ | |
/* Check that pin directions DONT match */ | |
PIN_DIRECTION pd; | |
pPin->QueryDirection(&pd); | |
ASSERT((pd == PINDIR_OUTPUT) || (pd == PINDIR_INPUT)); | |
ASSERT((m_dir == PINDIR_OUTPUT) || (m_dir == PINDIR_INPUT)); | |
// we should allow for non-input and non-output connections? | |
if (pd == m_dir) { | |
return VFW_E_INVALID_DIRECTION; | |
} | |
return NOERROR; | |
} | |
/* This is called when we realise we can't make a connection to the pin and | |
must undo anything we did in CheckConnect - override to release QIs done */ | |
HRESULT | |
CBasePin::BreakConnect() | |
{ | |
return NOERROR; | |
} | |
/* Called normally by an output pin on an input pin to try and establish a | |
connection. | |
*/ | |
STDMETHODIMP | |
CBasePin::ReceiveConnection( | |
IPin * pConnector, // this is the pin who we will connect to | |
const AM_MEDIA_TYPE *pmt // this is the media type we will exchange | |
) | |
{ | |
CheckPointer(pConnector,E_POINTER); | |
CheckPointer(pmt,E_POINTER); | |
ValidateReadPtr(pConnector,sizeof(IPin)); | |
ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE)); | |
CAutoLock cObjectLock(m_pLock); | |
/* Are we already connected */ | |
if (m_Connected) { | |
return VFW_E_ALREADY_CONNECTED; | |
} | |
/* See if the filter is active */ | |
if (!IsStopped() && !m_bCanReconnectWhenActive) { | |
return VFW_E_NOT_STOPPED; | |
} | |
HRESULT hr = CheckConnect(pConnector); | |
if (FAILED(hr)) { | |
// Since the procedure is already returning an error code, there | |
// is nothing else this function can do to report the error. | |
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); | |
#ifdef DXMPERF | |
PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); | |
#endif // DXMPERF | |
return hr; | |
} | |
/* Ask derived class if this media type is ok */ | |
CMediaType * pcmt = (CMediaType*) pmt; | |
hr = CheckMediaType(pcmt); | |
if (hr != NOERROR) { | |
// no -we don't support this media type | |
// Since the procedure is already returning an error code, there | |
// is nothing else this function can do to report the error. | |
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); | |
// return a specific media type error if there is one | |
// or map a general failure code to something more helpful | |
// (in particular S_FALSE gets changed to an error code) | |
if (SUCCEEDED(hr) || | |
(hr == E_FAIL) || | |
(hr == E_INVALIDARG)) { | |
hr = VFW_E_TYPE_NOT_ACCEPTED; | |
} | |
#ifdef DXMPERF | |
PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); | |
#endif // DXMPERF | |
return hr; | |
} | |
/* Complete the connection */ | |
m_Connected = pConnector; | |
m_Connected->AddRef(); | |
hr = SetMediaType(pcmt); | |
if (SUCCEEDED(hr)) { | |
hr = CompleteConnect(pConnector); | |
if (SUCCEEDED(hr)) { | |
#ifdef DXMPERF | |
PERFLOG_RXCONNECT( pConnector, (IPin *) this, NOERROR, pmt ); | |
#endif // DXMPERF | |
return NOERROR; | |
} | |
} | |
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Failed to set the media type or failed to complete the connection."))); | |
m_Connected->Release(); | |
m_Connected = NULL; | |
// Since the procedure is already returning an error code, there | |
// is nothing else this function can do to report the error. | |
EXECUTE_ASSERT( SUCCEEDED( BreakConnect() ) ); | |
#ifdef DXMPERF | |
PERFLOG_RXCONNECT( pConnector, (IPin *) this, hr, pmt ); | |
#endif // DXMPERF | |
return hr; | |
} | |
/* Called when we want to terminate a pin connection */ | |
STDMETHODIMP | |
CBasePin::Disconnect() | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
/* See if the filter is active */ | |
if (!IsStopped()) { | |
return VFW_E_NOT_STOPPED; | |
} | |
return DisconnectInternal(); | |
} | |
STDMETHODIMP | |
CBasePin::DisconnectInternal() | |
{ | |
ASSERT(CritCheckIn(m_pLock)); | |
if (m_Connected) { | |
HRESULT hr = BreakConnect(); | |
if( FAILED( hr ) ) { | |
#ifdef DXMPERF | |
PERFLOG_DISCONNECT( (IPin *) this, m_Connected, hr ); | |
#endif // DXMPERF | |
// There is usually a bug in the program if BreakConnect() fails. | |
DbgBreak( "WARNING: BreakConnect() failed in CBasePin::Disconnect()." ); | |
return hr; | |
} | |
m_Connected->Release(); | |
m_Connected = NULL; | |
#ifdef DXMPERF | |
PERFLOG_DISCONNECT( (IPin *) this, m_Connected, S_OK ); | |
#endif // DXMPERF | |
return S_OK; | |
} else { | |
// no connection - not an error | |
#ifdef DXMPERF | |
PERFLOG_DISCONNECT( (IPin *) this, m_Connected, S_FALSE ); | |
#endif // DXMPERF | |
return S_FALSE; | |
} | |
} | |
/* Return an AddRef()'d pointer to the connected pin if there is one */ | |
STDMETHODIMP | |
CBasePin::ConnectedTo( | |
__deref_out IPin **ppPin | |
) | |
{ | |
CheckPointer(ppPin,E_POINTER); | |
ValidateReadWritePtr(ppPin,sizeof(IPin *)); | |
// | |
// It's pointless to lock here. | |
// The caller should ensure integrity. | |
// | |
IPin *pPin = m_Connected; | |
*ppPin = pPin; | |
if (pPin != NULL) { | |
pPin->AddRef(); | |
return S_OK; | |
} else { | |
ASSERT(*ppPin == NULL); | |
return VFW_E_NOT_CONNECTED; | |
} | |
} | |
/* Return the media type of the connection */ | |
STDMETHODIMP | |
CBasePin::ConnectionMediaType( | |
__out AM_MEDIA_TYPE *pmt | |
) | |
{ | |
CheckPointer(pmt,E_POINTER); | |
ValidateReadWritePtr(pmt,sizeof(AM_MEDIA_TYPE)); | |
CAutoLock cObjectLock(m_pLock); | |
/* Copy constructor of m_mt allocates the memory */ | |
if (IsConnected()) { | |
CopyMediaType( pmt, &m_mt ); | |
return S_OK; | |
} else { | |
((CMediaType *)pmt)->InitMediaType(); | |
return VFW_E_NOT_CONNECTED; | |
} | |
} | |
/* Return information about the filter we are connect to */ | |
STDMETHODIMP | |
CBasePin::QueryPinInfo( | |
__out PIN_INFO * pInfo | |
) | |
{ | |
CheckPointer(pInfo,E_POINTER); | |
ValidateReadWritePtr(pInfo,sizeof(PIN_INFO)); | |
pInfo->pFilter = m_pFilter; | |
if (m_pFilter) { | |
m_pFilter->AddRef(); | |
} | |
if (m_pName) { | |
(void)StringCchCopyW(pInfo->achName, NUMELMS(pInfo->achName), m_pName); | |
} else { | |
pInfo->achName[0] = L'\0'; | |
} | |
pInfo->dir = m_dir; | |
return NOERROR; | |
} | |
STDMETHODIMP | |
CBasePin::QueryDirection( | |
__out PIN_DIRECTION * pPinDir | |
) | |
{ | |
CheckPointer(pPinDir,E_POINTER); | |
ValidateReadWritePtr(pPinDir,sizeof(PIN_DIRECTION)); | |
*pPinDir = m_dir; | |
return NOERROR; | |
} | |
// Default QueryId to return the pin's name | |
STDMETHODIMP | |
CBasePin::QueryId( | |
__deref_out LPWSTR * Id | |
) | |
{ | |
// We're not going away because someone's got a pointer to us | |
// so there's no need to lock | |
return AMGetWideString(Name(), Id); | |
} | |
/* Does this pin support this media type WARNING this interface function does | |
not lock the main object as it is meant to be asynchronous by nature - if | |
the media types you support depend on some internal state that is updated | |
dynamically then you will need to implement locking in a derived class */ | |
STDMETHODIMP | |
CBasePin::QueryAccept( | |
const AM_MEDIA_TYPE *pmt | |
) | |
{ | |
CheckPointer(pmt,E_POINTER); | |
ValidateReadPtr(pmt,sizeof(AM_MEDIA_TYPE)); | |
/* The CheckMediaType method is valid to return error codes if the media | |
type is horrible, an example might be E_INVALIDARG. What we do here | |
is map all the error codes into either S_OK or S_FALSE regardless */ | |
HRESULT hr = CheckMediaType((CMediaType*)pmt); | |
if (FAILED(hr)) { | |
return S_FALSE; | |
} | |
// note that the only defined success codes should be S_OK and S_FALSE... | |
return hr; | |
} | |
/* This can be called to return an enumerator for the pin's list of preferred | |
media types. An input pin is not obliged to have any preferred formats | |
although it can do. For example, the window renderer has a preferred type | |
which describes a video image that matches the current window size. All | |
output pins should expose at least one preferred format otherwise it is | |
possible that neither pin has any types and so no connection is possible */ | |
STDMETHODIMP | |
CBasePin::EnumMediaTypes( | |
__deref_out IEnumMediaTypes **ppEnum | |
) | |
{ | |
CheckPointer(ppEnum,E_POINTER); | |
ValidateReadWritePtr(ppEnum,sizeof(IEnumMediaTypes *)); | |
/* Create a new ref counted enumerator */ | |
*ppEnum = new CEnumMediaTypes(this, | |
NULL); | |
if (*ppEnum == NULL) { | |
return E_OUTOFMEMORY; | |
} | |
return NOERROR; | |
} | |
/* This is a virtual function that returns a media type corresponding with | |
place iPosition in the list. This base class simply returns an error as | |
we support no media types by default but derived classes should override */ | |
HRESULT CBasePin::GetMediaType(int iPosition, __inout CMediaType *pMediaType) | |
{ | |
UNREFERENCED_PARAMETER(iPosition); | |
UNREFERENCED_PARAMETER(pMediaType); | |
return E_UNEXPECTED; | |
} | |
/* This is a virtual function that returns the current media type version. | |
The base class initialises the media type enumerators with the value 1 | |
By default we always returns that same value. A Derived class may change | |
the list of media types available and after doing so it should increment | |
the version either in a method derived from this, or more simply by just | |
incrementing the m_TypeVersion base pin variable. The type enumerators | |
call this when they want to see if their enumerations are out of date */ | |
LONG CBasePin::GetMediaTypeVersion() | |
{ | |
return m_TypeVersion; | |
} | |
/* Increment the cookie representing the current media type version */ | |
void CBasePin::IncrementTypeVersion() | |
{ | |
InterlockedIncrement(&m_TypeVersion); | |
} | |
/* Called by IMediaFilter implementation when the state changes from Stopped | |
to either paused or running and in derived classes could do things like | |
commit memory and grab hardware resource (the default is to do nothing) */ | |
HRESULT | |
CBasePin::Active(void) | |
{ | |
return NOERROR; | |
} | |
/* Called by IMediaFilter implementation when the state changes from | |
to either paused to running and in derived classes could do things like | |
commit memory and grab hardware resource (the default is to do nothing) */ | |
HRESULT | |
CBasePin::Run(REFERENCE_TIME tStart) | |
{ | |
UNREFERENCED_PARAMETER(tStart); | |
return NOERROR; | |
} | |
/* Also called by the IMediaFilter implementation when the state changes to | |
Stopped at which point you should decommit allocators and free hardware | |
resources you grabbed in the Active call (default is also to do nothing) */ | |
HRESULT | |
CBasePin::Inactive(void) | |
{ | |
m_bRunTimeError = FALSE; | |
return NOERROR; | |
} | |
// Called when no more data will arrive | |
STDMETHODIMP | |
CBasePin::EndOfStream(void) | |
{ | |
return S_OK; | |
} | |
STDMETHODIMP | |
CBasePin::SetSink(IQualityControl * piqc) | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
if (piqc) ValidateReadPtr(piqc,sizeof(IQualityControl)); | |
m_pQSink = piqc; | |
return NOERROR; | |
} // SetSink | |
STDMETHODIMP | |
CBasePin::Notify(IBaseFilter * pSender, Quality q) | |
{ | |
UNREFERENCED_PARAMETER(q); | |
UNREFERENCED_PARAMETER(pSender); | |
DbgBreak("IQualityControl::Notify not over-ridden from CBasePin. (IGNORE is OK)"); | |
return E_NOTIMPL; | |
} //Notify | |
// NewSegment notifies of the start/stop/rate applying to the data | |
// about to be received. Default implementation records data and | |
// returns S_OK. | |
// Override this to pass downstream. | |
STDMETHODIMP | |
CBasePin::NewSegment( | |
REFERENCE_TIME tStart, | |
REFERENCE_TIME tStop, | |
double dRate) | |
{ | |
m_tStart = tStart; | |
m_tStop = tStop; | |
m_dRate = dRate; | |
return S_OK; | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CBaseOutputPin | |
//===================================================================== | |
//===================================================================== | |
CBaseOutputPin::CBaseOutputPin(__in_opt LPCTSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pName) : | |
CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT), | |
m_pAllocator(NULL), | |
m_pInputPin(NULL) | |
{ | |
ASSERT(pFilter); | |
} | |
#ifdef UNICODE | |
CBaseOutputPin::CBaseOutputPin(__in_opt LPCSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pName) : | |
CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT), | |
m_pAllocator(NULL), | |
m_pInputPin(NULL) | |
{ | |
ASSERT(pFilter); | |
} | |
#endif | |
/* This is called after a media type has been proposed | |
Try to complete the connection by agreeing the allocator | |
*/ | |
HRESULT | |
CBaseOutputPin::CompleteConnect(IPin *pReceivePin) | |
{ | |
UNREFERENCED_PARAMETER(pReceivePin); | |
return DecideAllocator(m_pInputPin, &m_pAllocator); | |
} | |
/* This method is called when the output pin is about to try and connect to | |
an input pin. It is at this point that you should try and grab any extra | |
interfaces that you need, in this case IMemInputPin. Because this is | |
only called if we are not currently connected we do NOT need to call | |
BreakConnect. This also makes it easier to derive classes from us as | |
BreakConnect is only called when we actually have to break a connection | |
(or a partly made connection) and not when we are checking a connection */ | |
/* Overriden from CBasePin */ | |
HRESULT | |
CBaseOutputPin::CheckConnect(IPin * pPin) | |
{ | |
HRESULT hr = CBasePin::CheckConnect(pPin); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
// get an input pin and an allocator interface | |
hr = pPin->QueryInterface(IID_IMemInputPin, (void **) &m_pInputPin); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
return NOERROR; | |
} | |
/* Overriden from CBasePin */ | |
HRESULT | |
CBaseOutputPin::BreakConnect() | |
{ | |
/* Release any allocator we hold */ | |
if (m_pAllocator) { | |
// Always decommit the allocator because a downstream filter may or | |
// may not decommit the connection's allocator. A memory leak could | |
// occur if the allocator is not decommited when a connection is broken. | |
HRESULT hr = m_pAllocator->Decommit(); | |
if( FAILED( hr ) ) { | |
return hr; | |
} | |
m_pAllocator->Release(); | |
m_pAllocator = NULL; | |
} | |
/* Release any input pin interface we hold */ | |
if (m_pInputPin) { | |
m_pInputPin->Release(); | |
m_pInputPin = NULL; | |
} | |
return NOERROR; | |
} | |
/* This is called when the input pin didn't give us a valid allocator */ | |
HRESULT | |
CBaseOutputPin::InitAllocator(__deref_out IMemAllocator **ppAlloc) | |
{ | |
return CreateMemoryAllocator(ppAlloc); | |
} | |
/* Decide on an allocator, override this if you want to use your own allocator | |
Override DecideBufferSize to call SetProperties. If the input pin fails | |
the GetAllocator call then this will construct a CMemAllocator and call | |
DecideBufferSize on that, and if that fails then we are completely hosed. | |
If the you succeed the DecideBufferSize call, we will notify the input | |
pin of the selected allocator. NOTE this is called during Connect() which | |
therefore looks after grabbing and locking the object's critical section */ | |
// We query the input pin for its requested properties and pass this to | |
// DecideBufferSize to allow it to fulfill requests that it is happy | |
// with (eg most people don't care about alignment and are thus happy to | |
// use the downstream pin's alignment request). | |
HRESULT | |
CBaseOutputPin::DecideAllocator(IMemInputPin *pPin, __deref_out IMemAllocator **ppAlloc) | |
{ | |
HRESULT hr = NOERROR; | |
*ppAlloc = NULL; | |
// get downstream prop request | |
// the derived class may modify this in DecideBufferSize, but | |
// we assume that he will consistently modify it the same way, | |
// so we only get it once | |
ALLOCATOR_PROPERTIES prop; | |
ZeroMemory(&prop, sizeof(prop)); | |
// whatever he returns, we assume prop is either all zeros | |
// or he has filled it out. | |
pPin->GetAllocatorRequirements(&prop); | |
// if he doesn't care about alignment, then set it to 1 | |
if (prop.cbAlign == 0) { | |
prop.cbAlign = 1; | |
} | |
/* Try the allocator provided by the input pin */ | |
hr = pPin->GetAllocator(ppAlloc); | |
if (SUCCEEDED(hr)) { | |
hr = DecideBufferSize(*ppAlloc, &prop); | |
if (SUCCEEDED(hr)) { | |
hr = pPin->NotifyAllocator(*ppAlloc, FALSE); | |
if (SUCCEEDED(hr)) { | |
return NOERROR; | |
} | |
} | |
} | |
/* If the GetAllocator failed we may not have an interface */ | |
if (*ppAlloc) { | |
(*ppAlloc)->Release(); | |
*ppAlloc = NULL; | |
} | |
/* Try the output pin's allocator by the same method */ | |
hr = InitAllocator(ppAlloc); | |
if (SUCCEEDED(hr)) { | |
// note - the properties passed here are in the same | |
// structure as above and may have been modified by | |
// the previous call to DecideBufferSize | |
hr = DecideBufferSize(*ppAlloc, &prop); | |
if (SUCCEEDED(hr)) { | |
hr = pPin->NotifyAllocator(*ppAlloc, FALSE); | |
if (SUCCEEDED(hr)) { | |
return NOERROR; | |
} | |
} | |
} | |
/* Likewise we may not have an interface to release */ | |
if (*ppAlloc) { | |
(*ppAlloc)->Release(); | |
*ppAlloc = NULL; | |
} | |
return hr; | |
} | |
/* This returns an empty sample buffer from the allocator WARNING the same | |
dangers and restrictions apply here as described below for Deliver() */ | |
HRESULT | |
CBaseOutputPin::GetDeliveryBuffer(__deref_out IMediaSample ** ppSample, | |
__in_opt REFERENCE_TIME * pStartTime, | |
__in_opt REFERENCE_TIME * pEndTime, | |
DWORD dwFlags) | |
{ | |
if (m_pAllocator != NULL) { | |
return m_pAllocator->GetBuffer(ppSample,pStartTime,pEndTime,dwFlags); | |
} else { | |
return E_NOINTERFACE; | |
} | |
} | |
/* Deliver a filled-in sample to the connected input pin. NOTE the object must | |
have locked itself before calling us otherwise we may get halfway through | |
executing this method only to find the filter graph has got in and | |
disconnected us from the input pin. If the filter has no worker threads | |
then the lock is best applied on Receive(), otherwise it should be done | |
when the worker thread is ready to deliver. There is a wee snag to worker | |
threads that this shows up. The worker thread must lock the object when | |
it is ready to deliver a sample, but it may have to wait until a state | |
change has completed, but that may never complete because the state change | |
is waiting for the worker thread to complete. The way to handle this is for | |
the state change code to grab the critical section, then set an abort event | |
for the worker thread, then release the critical section and wait for the | |
worker thread to see the event we set and then signal that it has finished | |
(with another event). At which point the state change code can complete */ | |
// note (if you've still got any breath left after reading that) that you | |
// need to release the sample yourself after this call. if the connected | |
// input pin needs to hold onto the sample beyond the call, it will addref | |
// the sample itself. | |
// of course you must release this one and call GetDeliveryBuffer for the | |
// next. You cannot reuse it directly. | |
HRESULT | |
CBaseOutputPin::Deliver(IMediaSample * pSample) | |
{ | |
if (m_pInputPin == NULL) { | |
return VFW_E_NOT_CONNECTED; | |
} | |
#ifdef DXMPERF | |
PERFLOG_DELIVER( m_pName ? m_pName : L"CBaseOutputPin", (IPin *) this, (IPin *) m_pInputPin, pSample, &m_mt ); | |
#endif // DXMPERF | |
return m_pInputPin->Receive(pSample); | |
} | |
// called from elsewhere in our filter to pass EOS downstream to | |
// our connected input pin | |
HRESULT | |
CBaseOutputPin::DeliverEndOfStream(void) | |
{ | |
// remember this is on IPin not IMemInputPin | |
if (m_Connected == NULL) { | |
return VFW_E_NOT_CONNECTED; | |
} | |
return m_Connected->EndOfStream(); | |
} | |
/* Commit the allocator's memory, this is called through IMediaFilter | |
which is responsible for locking the object before calling us */ | |
HRESULT | |
CBaseOutputPin::Active(void) | |
{ | |
if (m_pAllocator == NULL) { | |
return VFW_E_NO_ALLOCATOR; | |
} | |
return m_pAllocator->Commit(); | |
} | |
/* Free up or unprepare allocator's memory, this is called through | |
IMediaFilter which is responsible for locking the object first */ | |
HRESULT | |
CBaseOutputPin::Inactive(void) | |
{ | |
m_bRunTimeError = FALSE; | |
if (m_pAllocator == NULL) { | |
return VFW_E_NO_ALLOCATOR; | |
} | |
return m_pAllocator->Decommit(); | |
} | |
// we have a default handling of EndOfStream which is to return | |
// an error, since this should be called on input pins only | |
STDMETHODIMP | |
CBaseOutputPin::EndOfStream(void) | |
{ | |
return E_UNEXPECTED; | |
} | |
// BeginFlush should be called on input pins only | |
STDMETHODIMP | |
CBaseOutputPin::BeginFlush(void) | |
{ | |
return E_UNEXPECTED; | |
} | |
// EndFlush should be called on input pins only | |
STDMETHODIMP | |
CBaseOutputPin::EndFlush(void) | |
{ | |
return E_UNEXPECTED; | |
} | |
// call BeginFlush on the connected input pin | |
HRESULT | |
CBaseOutputPin::DeliverBeginFlush(void) | |
{ | |
// remember this is on IPin not IMemInputPin | |
if (m_Connected == NULL) { | |
return VFW_E_NOT_CONNECTED; | |
} | |
return m_Connected->BeginFlush(); | |
} | |
// call EndFlush on the connected input pin | |
HRESULT | |
CBaseOutputPin::DeliverEndFlush(void) | |
{ | |
// remember this is on IPin not IMemInputPin | |
if (m_Connected == NULL) { | |
return VFW_E_NOT_CONNECTED; | |
} | |
return m_Connected->EndFlush(); | |
} | |
// deliver NewSegment to connected pin | |
HRESULT | |
CBaseOutputPin::DeliverNewSegment( | |
REFERENCE_TIME tStart, | |
REFERENCE_TIME tStop, | |
double dRate) | |
{ | |
if (m_Connected == NULL) { | |
return VFW_E_NOT_CONNECTED; | |
} | |
return m_Connected->NewSegment(tStart, tStop, dRate); | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CBaseInputPin | |
//===================================================================== | |
//===================================================================== | |
/* Constructor creates a default allocator object */ | |
CBaseInputPin::CBaseInputPin(__in_opt LPCTSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pPinName) : | |
CBasePin(pObjectName, pFilter, pLock, phr, pPinName, PINDIR_INPUT), | |
m_pAllocator(NULL), | |
m_bReadOnly(FALSE), | |
m_bFlushing(FALSE) | |
{ | |
ZeroMemory(&m_SampleProps, sizeof(m_SampleProps)); | |
} | |
#ifdef UNICODE | |
CBaseInputPin::CBaseInputPin(__in LPCSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pPinName) : | |
CBasePin(pObjectName, pFilter, pLock, phr, pPinName, PINDIR_INPUT), | |
m_pAllocator(NULL), | |
m_bReadOnly(FALSE), | |
m_bFlushing(FALSE) | |
{ | |
ZeroMemory(&m_SampleProps, sizeof(m_SampleProps)); | |
} | |
#endif | |
/* Destructor releases it's reference count on the default allocator */ | |
CBaseInputPin::~CBaseInputPin() | |
{ | |
if (m_pAllocator != NULL) { | |
m_pAllocator->Release(); | |
m_pAllocator = NULL; | |
} | |
} | |
// override this to publicise our interfaces | |
STDMETHODIMP | |
CBaseInputPin::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) | |
{ | |
/* Do we know about this interface */ | |
if (riid == IID_IMemInputPin) { | |
return GetInterface((IMemInputPin *) this, ppv); | |
} else { | |
return CBasePin::NonDelegatingQueryInterface(riid, ppv); | |
} | |
} | |
/* Return the allocator interface that this input pin would like the output | |
pin to use. NOTE subsequent calls to GetAllocator should all return an | |
interface onto the SAME object so we create one object at the start | |
Note: | |
The allocator is Release()'d on disconnect and replaced on | |
NotifyAllocator(). | |
Override this to provide your own allocator. | |
*/ | |
STDMETHODIMP | |
CBaseInputPin::GetAllocator( | |
__deref_out IMemAllocator **ppAllocator) | |
{ | |
CheckPointer(ppAllocator,E_POINTER); | |
ValidateReadWritePtr(ppAllocator,sizeof(IMemAllocator *)); | |
CAutoLock cObjectLock(m_pLock); | |
if (m_pAllocator == NULL) { | |
HRESULT hr = CreateMemoryAllocator(&m_pAllocator); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
} | |
ASSERT(m_pAllocator != NULL); | |
*ppAllocator = m_pAllocator; | |
m_pAllocator->AddRef(); | |
return NOERROR; | |
} | |
/* Tell the input pin which allocator the output pin is actually going to use | |
Override this if you care - NOTE the locking we do both here and also in | |
GetAllocator is unnecessary but derived classes that do something useful | |
will undoubtedly have to lock the object so this might help remind people */ | |
STDMETHODIMP | |
CBaseInputPin::NotifyAllocator( | |
IMemAllocator * pAllocator, | |
BOOL bReadOnly) | |
{ | |
CheckPointer(pAllocator,E_POINTER); | |
ValidateReadPtr(pAllocator,sizeof(IMemAllocator)); | |
CAutoLock cObjectLock(m_pLock); | |
IMemAllocator *pOldAllocator = m_pAllocator; | |
pAllocator->AddRef(); | |
m_pAllocator = pAllocator; | |
if (pOldAllocator != NULL) { | |
pOldAllocator->Release(); | |
} | |
// the readonly flag indicates whether samples from this allocator should | |
// be regarded as readonly - if true, then inplace transforms will not be | |
// allowed. | |
m_bReadOnly = (BYTE)bReadOnly; | |
return NOERROR; | |
} | |
HRESULT | |
CBaseInputPin::BreakConnect() | |
{ | |
/* We don't need our allocator any more */ | |
if (m_pAllocator) { | |
// Always decommit the allocator because a downstream filter may or | |
// may not decommit the connection's allocator. A memory leak could | |
// occur if the allocator is not decommited when a pin is disconnected. | |
HRESULT hr = m_pAllocator->Decommit(); | |
if( FAILED( hr ) ) { | |
return hr; | |
} | |
m_pAllocator->Release(); | |
m_pAllocator = NULL; | |
} | |
return S_OK; | |
} | |
/* Do something with this media sample - this base class checks to see if the | |
format has changed with this media sample and if so checks that the filter | |
will accept it, generating a run time error if not. Once we have raised a | |
run time error we set a flag so that no more samples will be accepted | |
It is important that any filter should override this method and implement | |
synchronization so that samples are not processed when the pin is | |
disconnected etc | |
*/ | |
STDMETHODIMP | |
CBaseInputPin::Receive(IMediaSample *pSample) | |
{ | |
CheckPointer(pSample,E_POINTER); | |
ValidateReadPtr(pSample,sizeof(IMediaSample)); | |
ASSERT(pSample); | |
HRESULT hr = CheckStreaming(); | |
if (S_OK != hr) { | |
return hr; | |
} | |
#ifdef DXMPERF | |
PERFLOG_RECEIVE( m_pName ? m_pName : L"CBaseInputPin", (IPin *) m_Connected, (IPin *) this, pSample, &m_mt ); | |
#endif // DXMPERF | |
/* Check for IMediaSample2 */ | |
IMediaSample2 *pSample2; | |
if (SUCCEEDED(pSample->QueryInterface(IID_IMediaSample2, (void **)&pSample2))) { | |
hr = pSample2->GetProperties(sizeof(m_SampleProps), (PBYTE)&m_SampleProps); | |
pSample2->Release(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
} else { | |
/* Get the properties the hard way */ | |
m_SampleProps.cbData = sizeof(m_SampleProps); | |
m_SampleProps.dwTypeSpecificFlags = 0; | |
m_SampleProps.dwStreamId = AM_STREAM_MEDIA; | |
m_SampleProps.dwSampleFlags = 0; | |
if (S_OK == pSample->IsDiscontinuity()) { | |
m_SampleProps.dwSampleFlags |= AM_SAMPLE_DATADISCONTINUITY; | |
} | |
if (S_OK == pSample->IsPreroll()) { | |
m_SampleProps.dwSampleFlags |= AM_SAMPLE_PREROLL; | |
} | |
if (S_OK == pSample->IsSyncPoint()) { | |
m_SampleProps.dwSampleFlags |= AM_SAMPLE_SPLICEPOINT; | |
} | |
if (SUCCEEDED(pSample->GetTime(&m_SampleProps.tStart, | |
&m_SampleProps.tStop))) { | |
m_SampleProps.dwSampleFlags |= AM_SAMPLE_TIMEVALID | | |
AM_SAMPLE_STOPVALID; | |
} | |
if (S_OK == pSample->GetMediaType(&m_SampleProps.pMediaType)) { | |
m_SampleProps.dwSampleFlags |= AM_SAMPLE_TYPECHANGED; | |
} | |
pSample->GetPointer(&m_SampleProps.pbBuffer); | |
m_SampleProps.lActual = pSample->GetActualDataLength(); | |
m_SampleProps.cbBuffer = pSample->GetSize(); | |
} | |
/* Has the format changed in this sample */ | |
if (!(m_SampleProps.dwSampleFlags & AM_SAMPLE_TYPECHANGED)) { | |
return NOERROR; | |
} | |
/* Check the derived class accepts this format */ | |
/* This shouldn't fail as the source must call QueryAccept first */ | |
hr = CheckMediaType((CMediaType *)m_SampleProps.pMediaType); | |
if (hr == NOERROR) { | |
return NOERROR; | |
} | |
/* Raise a runtime error if we fail the media type */ | |
m_bRunTimeError = TRUE; | |
EndOfStream(); | |
m_pFilter->NotifyEvent(EC_ERRORABORT,VFW_E_TYPE_NOT_ACCEPTED,0); | |
return VFW_E_INVALIDMEDIATYPE; | |
} | |
/* Receive multiple samples */ | |
STDMETHODIMP | |
CBaseInputPin::ReceiveMultiple ( | |
__in_ecount(nSamples) IMediaSample **pSamples, | |
long nSamples, | |
__out long *nSamplesProcessed) | |
{ | |
CheckPointer(pSamples,E_POINTER); | |
ValidateReadPtr(pSamples,nSamples * sizeof(IMediaSample *)); | |
HRESULT hr = S_OK; | |
*nSamplesProcessed = 0; | |
while (nSamples-- > 0) { | |
hr = Receive(pSamples[*nSamplesProcessed]); | |
/* S_FALSE means don't send any more */ | |
if (hr != S_OK) { | |
break; | |
} | |
(*nSamplesProcessed)++; | |
} | |
return hr; | |
} | |
/* See if Receive() might block */ | |
STDMETHODIMP | |
CBaseInputPin::ReceiveCanBlock() | |
{ | |
/* Ask all the output pins if they block | |
If there are no output pin assume we do block | |
*/ | |
int cPins = m_pFilter->GetPinCount(); | |
int cOutputPins = 0; | |
for (int c = 0; c < cPins; c++) { | |
CBasePin *pPin = m_pFilter->GetPin(c); | |
if (NULL == pPin) { | |
break; | |
} | |
PIN_DIRECTION pd; | |
HRESULT hr = pPin->QueryDirection(&pd); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
if (pd == PINDIR_OUTPUT) { | |
IPin *pConnected; | |
hr = pPin->ConnectedTo(&pConnected); | |
if (SUCCEEDED(hr)) { | |
ASSERT(pConnected != NULL); | |
cOutputPins++; | |
IMemInputPin *pInputPin; | |
hr = pConnected->QueryInterface( | |
IID_IMemInputPin, | |
(void **)&pInputPin); | |
pConnected->Release(); | |
if (SUCCEEDED(hr)) { | |
hr = pInputPin->ReceiveCanBlock(); | |
pInputPin->Release(); | |
if (hr != S_FALSE) { | |
return S_OK; | |
} | |
} else { | |
/* There's a transport we don't understand here */ | |
return S_OK; | |
} | |
} | |
} | |
} | |
return cOutputPins == 0 ? S_OK : S_FALSE; | |
} | |
// Default handling for BeginFlush - call at the beginning | |
// of your implementation (makes sure that all Receive calls | |
// fail). After calling this, you need to free any queued data | |
// and then call downstream. | |
STDMETHODIMP | |
CBaseInputPin::BeginFlush(void) | |
{ | |
// BeginFlush is NOT synchronized with streaming but is part of | |
// a control action - hence we synchronize with the filter | |
CAutoLock lck(m_pLock); | |
// if we are already in mid-flush, this is probably a mistake | |
// though not harmful - try to pick it up for now so I can think about it | |
ASSERT(!m_bFlushing); | |
// first thing to do is ensure that no further Receive calls succeed | |
m_bFlushing = TRUE; | |
// now discard any data and call downstream - must do that | |
// in derived classes | |
return S_OK; | |
} | |
// default handling for EndFlush - call at end of your implementation | |
// - before calling this, ensure that there is no queued data and no thread | |
// pushing any more without a further receive, then call downstream, | |
// then call this method to clear the m_bFlushing flag and re-enable | |
// receives | |
STDMETHODIMP | |
CBaseInputPin::EndFlush(void) | |
{ | |
// Endlush is NOT synchronized with streaming but is part of | |
// a control action - hence we synchronize with the filter | |
CAutoLock lck(m_pLock); | |
// almost certainly a mistake if we are not in mid-flush | |
ASSERT(m_bFlushing); | |
// before calling, sync with pushing thread and ensure | |
// no more data is going downstream, then call EndFlush on | |
// downstream pins. | |
// now re-enable Receives | |
m_bFlushing = FALSE; | |
// No more errors | |
m_bRunTimeError = FALSE; | |
return S_OK; | |
} | |
STDMETHODIMP | |
CBaseInputPin::Notify(IBaseFilter * pSender, Quality q) | |
{ | |
UNREFERENCED_PARAMETER(q); | |
CheckPointer(pSender,E_POINTER); | |
ValidateReadPtr(pSender,sizeof(IBaseFilter)); | |
DbgBreak("IQuality::Notify called on an input pin"); | |
return NOERROR; | |
} // Notify | |
/* Free up or unprepare allocator's memory, this is called through | |
IMediaFilter which is responsible for locking the object first */ | |
HRESULT | |
CBaseInputPin::Inactive(void) | |
{ | |
m_bRunTimeError = FALSE; | |
if (m_pAllocator == NULL) { | |
return VFW_E_NO_ALLOCATOR; | |
} | |
m_bFlushing = FALSE; | |
return m_pAllocator->Decommit(); | |
} | |
// what requirements do we have of the allocator - override if you want | |
// to support other people's allocators but need a specific alignment | |
// or prefix. | |
STDMETHODIMP | |
CBaseInputPin::GetAllocatorRequirements(__out ALLOCATOR_PROPERTIES*pProps) | |
{ | |
UNREFERENCED_PARAMETER(pProps); | |
return E_NOTIMPL; | |
} | |
// Check if it's OK to process data | |
// | |
HRESULT | |
CBaseInputPin::CheckStreaming() | |
{ | |
// Shouldn't be able to get any data if we're not connected! | |
ASSERT(IsConnected()); | |
// Don't process stuff in Stopped state | |
if (IsStopped()) { | |
return VFW_E_WRONG_STATE; | |
} | |
if (m_bFlushing) { | |
return S_FALSE; | |
} | |
if (m_bRunTimeError) { | |
return VFW_E_RUNTIME_ERROR; | |
} | |
return S_OK; | |
} | |
// Pass on the Quality notification q to | |
// a. Our QualityControl sink (if we have one) or else | |
// b. to our upstream filter | |
// and if that doesn't work, throw it away with a bad return code | |
HRESULT | |
CBaseInputPin::PassNotify(Quality& q) | |
{ | |
// We pass the message on, which means that we find the quality sink | |
// for our input pin and send it there | |
DbgLog((LOG_TRACE,3,TEXT("Passing Quality notification through transform"))); | |
if (m_pQSink!=NULL) { | |
return m_pQSink->Notify(m_pFilter, q); | |
} else { | |
// no sink set, so pass it upstream | |
HRESULT hr; | |
IQualityControl * pIQC; | |
hr = VFW_E_NOT_FOUND; // default | |
if (m_Connected) { | |
m_Connected->QueryInterface(IID_IQualityControl, (void**)&pIQC); | |
if (pIQC!=NULL) { | |
hr = pIQC->Notify(m_pFilter, q); | |
pIQC->Release(); | |
} | |
} | |
return hr; | |
} | |
} // PassNotify | |
//===================================================================== | |
//===================================================================== | |
// Memory allocation class, implements CMediaSample | |
//===================================================================== | |
//===================================================================== | |
/* NOTE The implementation of this class calls the CUnknown constructor with | |
a NULL outer unknown pointer. This has the effect of making us a self | |
contained class, ie any QueryInterface, AddRef or Release calls will be | |
routed to the class's NonDelegatingUnknown methods. You will typically | |
find that the classes that do this then override one or more of these | |
virtual functions to provide more specialised behaviour. A good example | |
of this is where a class wants to keep the QueryInterface internal but | |
still wants it's lifetime controlled by the external object */ | |
/* The last two parameters have default values of NULL and zero */ | |
CMediaSample::CMediaSample(__in_opt LPCTSTR pName, | |
__in_opt CBaseAllocator *pAllocator, | |
__inout_opt HRESULT *phr, | |
__in_bcount_opt(length) LPBYTE pBuffer, | |
LONG length) : | |
m_pBuffer(pBuffer), // Initialise the buffer | |
m_cbBuffer(length), // And it's length | |
m_lActual(length), // By default, actual = length | |
m_pMediaType(NULL), // No media type change | |
m_dwFlags(0), // Nothing set | |
m_cRef(0), // 0 ref count | |
m_dwTypeSpecificFlags(0), // Type specific flags | |
m_dwStreamId(AM_STREAM_MEDIA), // Stream id | |
m_pAllocator(pAllocator) // Allocator | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( pName ? pName : L"CMediaSample", (IMediaSample *) this ); | |
#endif // DXMPERF | |
/* We must have an owner and it must also be derived from class | |
CBaseAllocator BUT we do not hold a reference count on it */ | |
ASSERT(pAllocator); | |
if (length < 0) { | |
*phr = VFW_E_BUFFER_OVERFLOW; | |
m_cbBuffer = 0; | |
} | |
} | |
#ifdef UNICODE | |
CMediaSample::CMediaSample(__in_opt LPCSTR pName, | |
__in_opt CBaseAllocator *pAllocator, | |
__inout_opt HRESULT *phr, | |
__in_bcount_opt(length) LPBYTE pBuffer, | |
LONG length) : | |
m_pBuffer(pBuffer), // Initialise the buffer | |
m_cbBuffer(length), // And it's length | |
m_lActual(length), // By default, actual = length | |
m_pMediaType(NULL), // No media type change | |
m_dwFlags(0), // Nothing set | |
m_cRef(0), // 0 ref count | |
m_dwTypeSpecificFlags(0), // Type specific flags | |
m_dwStreamId(AM_STREAM_MEDIA), // Stream id | |
m_pAllocator(pAllocator) // Allocator | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( L"CMediaSample", (IMediaSample *) this ); | |
#endif // DXMPERF | |
/* We must have an owner and it must also be derived from class | |
CBaseAllocator BUT we do not hold a reference count on it */ | |
ASSERT(pAllocator); | |
} | |
#endif | |
/* Destructor deletes the media type memory */ | |
CMediaSample::~CMediaSample() | |
{ | |
#ifdef DXMPERF | |
PERFLOG_DTOR( L"CMediaSample", (IMediaSample *) this ); | |
#endif // DXMPERF | |
if (m_pMediaType) { | |
DeleteMediaType(m_pMediaType); | |
} | |
} | |
/* Override this to publicise our interfaces */ | |
STDMETHODIMP | |
CMediaSample::QueryInterface(REFIID riid, __deref_out void **ppv) | |
{ | |
if (riid == IID_IMediaSample || | |
riid == IID_IMediaSample2 || | |
riid == IID_IUnknown) { | |
return GetInterface((IMediaSample *) this, ppv); | |
} else { | |
*ppv = NULL; | |
return E_NOINTERFACE; | |
} | |
} | |
STDMETHODIMP_(ULONG) | |
CMediaSample::AddRef() | |
{ | |
return InterlockedIncrement(&m_cRef); | |
} | |
// -- CMediaSample lifetimes -- | |
// | |
// On final release of this sample buffer it is not deleted but | |
// returned to the freelist of the owning memory allocator | |
// | |
// The allocator may be waiting for the last buffer to be placed on the free | |
// list in order to decommit all the memory, so the ReleaseBuffer() call may | |
// result in this sample being deleted. We also need to hold a refcount on | |
// the allocator to stop that going away until we have finished with this. | |
// However, we cannot release the allocator before the ReleaseBuffer, as the | |
// release may cause us to be deleted. Similarly we can't do it afterwards. | |
// | |
// Thus we must leave it to the allocator to hold an addref on our behalf. | |
// When he issues us in GetBuffer, he addref's himself. When ReleaseBuffer | |
// is called, he releases himself, possibly causing us and him to be deleted. | |
STDMETHODIMP_(ULONG) | |
CMediaSample::Release() | |
{ | |
/* Decrement our own private reference count */ | |
LONG lRef; | |
if (m_cRef == 1) { | |
lRef = 0; | |
m_cRef = 0; | |
} else { | |
lRef = InterlockedDecrement(&m_cRef); | |
} | |
ASSERT(lRef >= 0); | |
DbgLog((LOG_MEMORY,3,TEXT(" Unknown %X ref-- = %d"), | |
this, m_cRef)); | |
/* Did we release our final reference count */ | |
if (lRef == 0) { | |
/* Free all resources */ | |
if (m_dwFlags & Sample_TypeChanged) { | |
SetMediaType(NULL); | |
} | |
ASSERT(m_pMediaType == NULL); | |
m_dwFlags = 0; | |
m_dwTypeSpecificFlags = 0; | |
m_dwStreamId = AM_STREAM_MEDIA; | |
/* This may cause us to be deleted */ | |
// Our refcount is reliably 0 thus no-one will mess with us | |
m_pAllocator->ReleaseBuffer(this); | |
} | |
return (ULONG)lRef; | |
} | |
// set the buffer pointer and length. Used by allocators that | |
// want variable sized pointers or pointers into already-read data. | |
// This is only available through a CMediaSample* not an IMediaSample* | |
// and so cannot be changed by clients. | |
HRESULT | |
CMediaSample::SetPointer(__in_bcount(cBytes) BYTE * ptr, LONG cBytes) | |
{ | |
if (cBytes < 0) { | |
return VFW_E_BUFFER_OVERFLOW; | |
} | |
m_pBuffer = ptr; // new buffer area (could be null) | |
m_cbBuffer = cBytes; // length of buffer | |
m_lActual = cBytes; // length of data in buffer (assume full) | |
return S_OK; | |
} | |
// get me a read/write pointer to this buffer's memory. I will actually | |
// want to use sizeUsed bytes. | |
STDMETHODIMP | |
CMediaSample::GetPointer(__deref_out BYTE ** ppBuffer) | |
{ | |
ValidateReadWritePtr(ppBuffer,sizeof(BYTE *)); | |
// creator must have set pointer either during | |
// constructor or by SetPointer | |
ASSERT(m_pBuffer); | |
*ppBuffer = m_pBuffer; | |
return NOERROR; | |
} | |
// return the size in bytes of this buffer | |
STDMETHODIMP_(LONG) | |
CMediaSample::GetSize(void) | |
{ | |
return m_cbBuffer; | |
} | |
// get the stream time at which this sample should start and finish. | |
STDMETHODIMP | |
CMediaSample::GetTime( | |
__out REFERENCE_TIME * pTimeStart, // put time here | |
__out REFERENCE_TIME * pTimeEnd | |
) | |
{ | |
ValidateReadWritePtr(pTimeStart,sizeof(REFERENCE_TIME)); | |
ValidateReadWritePtr(pTimeEnd,sizeof(REFERENCE_TIME)); | |
if (!(m_dwFlags & Sample_StopValid)) { | |
if (!(m_dwFlags & Sample_TimeValid)) { | |
return VFW_E_SAMPLE_TIME_NOT_SET; | |
} else { | |
*pTimeStart = m_Start; | |
// Make sure old stuff works | |
*pTimeEnd = m_Start + 1; | |
return VFW_S_NO_STOP_TIME; | |
} | |
} | |
*pTimeStart = m_Start; | |
*pTimeEnd = m_End; | |
return NOERROR; | |
} | |
// Set the stream time at which this sample should start and finish. | |
// NULL pointers means the time is reset | |
STDMETHODIMP | |
CMediaSample::SetTime( | |
__in_opt REFERENCE_TIME * pTimeStart, | |
__in_opt REFERENCE_TIME * pTimeEnd | |
) | |
{ | |
if (pTimeStart == NULL) { | |
ASSERT(pTimeEnd == NULL); | |
m_dwFlags &= ~(Sample_TimeValid | Sample_StopValid); | |
} else { | |
if (pTimeEnd == NULL) { | |
m_Start = *pTimeStart; | |
m_dwFlags |= Sample_TimeValid; | |
m_dwFlags &= ~Sample_StopValid; | |
} else { | |
ValidateReadPtr(pTimeStart,sizeof(REFERENCE_TIME)); | |
ValidateReadPtr(pTimeEnd,sizeof(REFERENCE_TIME)); | |
ASSERT(*pTimeEnd >= *pTimeStart); | |
m_Start = *pTimeStart; | |
m_End = *pTimeEnd; | |
m_dwFlags |= Sample_TimeValid | Sample_StopValid; | |
} | |
} | |
return NOERROR; | |
} | |
// get the media times (eg bytes) for this sample | |
STDMETHODIMP | |
CMediaSample::GetMediaTime( | |
__out LONGLONG * pTimeStart, | |
__out LONGLONG * pTimeEnd | |
) | |
{ | |
ValidateReadWritePtr(pTimeStart,sizeof(LONGLONG)); | |
ValidateReadWritePtr(pTimeEnd,sizeof(LONGLONG)); | |
if (!(m_dwFlags & Sample_MediaTimeValid)) { | |
return VFW_E_MEDIA_TIME_NOT_SET; | |
} | |
*pTimeStart = m_MediaStart; | |
*pTimeEnd = (m_MediaStart + m_MediaEnd); | |
return NOERROR; | |
} | |
// Set the media times for this sample | |
STDMETHODIMP | |
CMediaSample::SetMediaTime( | |
__in_opt LONGLONG * pTimeStart, | |
__in_opt LONGLONG * pTimeEnd | |
) | |
{ | |
if (pTimeStart == NULL) { | |
ASSERT(pTimeEnd == NULL); | |
m_dwFlags &= ~Sample_MediaTimeValid; | |
} else { | |
if (NULL == pTimeEnd) { | |
return E_POINTER; | |
} | |
ValidateReadPtr(pTimeStart,sizeof(LONGLONG)); | |
ValidateReadPtr(pTimeEnd,sizeof(LONGLONG)); | |
ASSERT(*pTimeEnd >= *pTimeStart); | |
m_MediaStart = *pTimeStart; | |
m_MediaEnd = (LONG)(*pTimeEnd - *pTimeStart); | |
m_dwFlags |= Sample_MediaTimeValid; | |
} | |
return NOERROR; | |
} | |
STDMETHODIMP | |
CMediaSample::IsSyncPoint(void) | |
{ | |
if (m_dwFlags & Sample_SyncPoint) { | |
return S_OK; | |
} else { | |
return S_FALSE; | |
} | |
} | |
STDMETHODIMP | |
CMediaSample::SetSyncPoint(BOOL bIsSyncPoint) | |
{ | |
if (bIsSyncPoint) { | |
m_dwFlags |= Sample_SyncPoint; | |
} else { | |
m_dwFlags &= ~Sample_SyncPoint; | |
} | |
return NOERROR; | |
} | |
// returns S_OK if there is a discontinuity in the data (this same is | |
// not a continuation of the previous stream of data | |
// - there has been a seek). | |
STDMETHODIMP | |
CMediaSample::IsDiscontinuity(void) | |
{ | |
if (m_dwFlags & Sample_Discontinuity) { | |
return S_OK; | |
} else { | |
return S_FALSE; | |
} | |
} | |
// set the discontinuity property - TRUE if this sample is not a | |
// continuation, but a new sample after a seek. | |
STDMETHODIMP | |
CMediaSample::SetDiscontinuity(BOOL bDiscont) | |
{ | |
// should be TRUE or FALSE | |
if (bDiscont) { | |
m_dwFlags |= Sample_Discontinuity; | |
} else { | |
m_dwFlags &= ~Sample_Discontinuity; | |
} | |
return S_OK; | |
} | |
STDMETHODIMP | |
CMediaSample::IsPreroll(void) | |
{ | |
if (m_dwFlags & Sample_Preroll) { | |
return S_OK; | |
} else { | |
return S_FALSE; | |
} | |
} | |
STDMETHODIMP | |
CMediaSample::SetPreroll(BOOL bIsPreroll) | |
{ | |
if (bIsPreroll) { | |
m_dwFlags |= Sample_Preroll; | |
} else { | |
m_dwFlags &= ~Sample_Preroll; | |
} | |
return NOERROR; | |
} | |
STDMETHODIMP_(LONG) | |
CMediaSample::GetActualDataLength(void) | |
{ | |
return m_lActual; | |
} | |
STDMETHODIMP | |
CMediaSample::SetActualDataLength(LONG lActual) | |
{ | |
if (lActual > m_cbBuffer || lActual < 0) { | |
ASSERT(lActual <= GetSize()); | |
return VFW_E_BUFFER_OVERFLOW; | |
} | |
m_lActual = lActual; | |
return NOERROR; | |
} | |
/* These allow for limited format changes in band */ | |
STDMETHODIMP | |
CMediaSample::GetMediaType(__deref_out AM_MEDIA_TYPE **ppMediaType) | |
{ | |
ValidateReadWritePtr(ppMediaType,sizeof(AM_MEDIA_TYPE *)); | |
ASSERT(ppMediaType); | |
/* Do we have a new media type for them */ | |
if (!(m_dwFlags & Sample_TypeChanged)) { | |
ASSERT(m_pMediaType == NULL); | |
*ppMediaType = NULL; | |
return S_FALSE; | |
} | |
ASSERT(m_pMediaType); | |
/* Create a copy of our media type */ | |
*ppMediaType = CreateMediaType(m_pMediaType); | |
if (*ppMediaType == NULL) { | |
return E_OUTOFMEMORY; | |
} | |
return NOERROR; | |
} | |
/* Mark this sample as having a different format type */ | |
STDMETHODIMP | |
CMediaSample::SetMediaType(__in_opt AM_MEDIA_TYPE *pMediaType) | |
{ | |
/* Delete the current media type */ | |
if (m_pMediaType) { | |
DeleteMediaType(m_pMediaType); | |
m_pMediaType = NULL; | |
} | |
/* Mechanism for resetting the format type */ | |
if (pMediaType == NULL) { | |
m_dwFlags &= ~Sample_TypeChanged; | |
return NOERROR; | |
} | |
ASSERT(pMediaType); | |
ValidateReadPtr(pMediaType,sizeof(AM_MEDIA_TYPE)); | |
/* Take a copy of the media type */ | |
m_pMediaType = CreateMediaType(pMediaType); | |
if (m_pMediaType == NULL) { | |
m_dwFlags &= ~Sample_TypeChanged; | |
return E_OUTOFMEMORY; | |
} | |
m_dwFlags |= Sample_TypeChanged; | |
return NOERROR; | |
} | |
// Set and get properties (IMediaSample2) | |
STDMETHODIMP CMediaSample::GetProperties( | |
DWORD cbProperties, | |
__out_bcount(cbProperties) BYTE * pbProperties | |
) | |
{ | |
if (0 != cbProperties) { | |
CheckPointer(pbProperties, E_POINTER); | |
// Return generic stuff up to the length | |
AM_SAMPLE2_PROPERTIES Props; | |
Props.cbData = min(cbProperties, sizeof(Props)); | |
Props.dwSampleFlags = m_dwFlags & ~Sample_MediaTimeValid; | |
Props.dwTypeSpecificFlags = m_dwTypeSpecificFlags; | |
Props.pbBuffer = m_pBuffer; | |
Props.cbBuffer = m_cbBuffer; | |
Props.lActual = m_lActual; | |
Props.tStart = m_Start; | |
Props.tStop = m_End; | |
Props.dwStreamId = m_dwStreamId; | |
if (m_dwFlags & AM_SAMPLE_TYPECHANGED) { | |
Props.pMediaType = m_pMediaType; | |
} else { | |
Props.pMediaType = NULL; | |
} | |
CopyMemory(pbProperties, &Props, Props.cbData); | |
} | |
return S_OK; | |
} | |
#define CONTAINS_FIELD(type, field, offset) \ | |
((FIELD_OFFSET(type, field) + sizeof(((type *)0)->field)) <= offset) | |
HRESULT CMediaSample::SetProperties( | |
DWORD cbProperties, | |
__in_bcount(cbProperties) const BYTE * pbProperties | |
) | |
{ | |
/* Generic properties */ | |
AM_MEDIA_TYPE *pMediaType = NULL; | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbData, cbProperties)) { | |
CheckPointer(pbProperties, E_POINTER); | |
AM_SAMPLE2_PROPERTIES *pProps = | |
(AM_SAMPLE2_PROPERTIES *)pbProperties; | |
/* Don't use more data than is actually there */ | |
if (pProps->cbData < cbProperties) { | |
cbProperties = pProps->cbData; | |
} | |
/* We only handle IMediaSample2 */ | |
if (cbProperties > sizeof(*pProps) || | |
pProps->cbData > sizeof(*pProps)) { | |
return E_INVALIDARG; | |
} | |
/* Do checks first, the assignments (for backout) */ | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) { | |
/* Check the flags */ | |
if (pProps->dwSampleFlags & | |
(~Sample_ValidFlags | Sample_MediaTimeValid)) { | |
return E_INVALIDARG; | |
} | |
/* Check a flag isn't being set for a property | |
not being provided | |
*/ | |
if ((pProps->dwSampleFlags & AM_SAMPLE_TIMEVALID) && | |
!(m_dwFlags & AM_SAMPLE_TIMEVALID) && | |
!CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) { | |
return E_INVALIDARG; | |
} | |
} | |
/* NB - can't SET the pointer or size */ | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pbBuffer, cbProperties)) { | |
/* Check pbBuffer */ | |
if (pProps->pbBuffer != 0 && pProps->pbBuffer != m_pBuffer) { | |
return E_INVALIDARG; | |
} | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties)) { | |
/* Check cbBuffer */ | |
if (pProps->cbBuffer != 0 && pProps->cbBuffer != m_cbBuffer) { | |
return E_INVALIDARG; | |
} | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, cbBuffer, cbProperties) && | |
CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) { | |
/* Check lActual */ | |
if (pProps->cbBuffer < pProps->lActual) { | |
return E_INVALIDARG; | |
} | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) { | |
/* Check pMediaType */ | |
if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) { | |
CheckPointer(pProps->pMediaType, E_POINTER); | |
pMediaType = CreateMediaType(pProps->pMediaType); | |
if (pMediaType == NULL) { | |
return E_OUTOFMEMORY; | |
} | |
} | |
} | |
/* Now do the assignments */ | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwStreamId, cbProperties)) { | |
m_dwStreamId = pProps->dwStreamId; | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwSampleFlags, cbProperties)) { | |
/* Set the flags */ | |
m_dwFlags = pProps->dwSampleFlags | | |
(m_dwFlags & Sample_MediaTimeValid); | |
m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags; | |
} else { | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, dwTypeSpecificFlags, cbProperties)) { | |
m_dwTypeSpecificFlags = pProps->dwTypeSpecificFlags; | |
} | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, lActual, cbProperties)) { | |
/* Set lActual */ | |
m_lActual = pProps->lActual; | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStop, cbProperties)) { | |
/* Set the times */ | |
m_End = pProps->tStop; | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, tStart, cbProperties)) { | |
/* Set the times */ | |
m_Start = pProps->tStart; | |
} | |
if (CONTAINS_FIELD(AM_SAMPLE2_PROPERTIES, pMediaType, cbProperties)) { | |
/* Set pMediaType */ | |
if (pProps->dwSampleFlags & AM_SAMPLE_TYPECHANGED) { | |
if (m_pMediaType != NULL) { | |
DeleteMediaType(m_pMediaType); | |
} | |
m_pMediaType = pMediaType; | |
} | |
} | |
/* Fix up the type changed flag to correctly reflect the current state | |
If, for instance the input contained no type change but the | |
output does then if we don't do this we'd lose the | |
output media type. | |
*/ | |
if (m_pMediaType) { | |
m_dwFlags |= Sample_TypeChanged; | |
} else { | |
m_dwFlags &= ~Sample_TypeChanged; | |
} | |
} | |
return S_OK; | |
} | |
// | |
// The streaming thread calls IPin::NewSegment(), IPin::EndOfStream(), | |
// IMemInputPin::Receive() and IMemInputPin::ReceiveMultiple() on the | |
// connected input pin. The application thread calls Block(). The | |
// following class members can only be called by the streaming thread. | |
// | |
// Deliver() | |
// DeliverNewSegment() | |
// StartUsingOutputPin() | |
// StopUsingOutputPin() | |
// ChangeOutputFormat() | |
// ChangeMediaType() | |
// DynamicReconnect() | |
// | |
// The following class members can only be called by the application thread. | |
// | |
// Block() | |
// SynchronousBlockOutputPin() | |
// AsynchronousBlockOutputPin() | |
// | |
CDynamicOutputPin::CDynamicOutputPin( | |
__in_opt LPCTSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pName) : | |
CBaseOutputPin(pObjectName, pFilter, pLock, phr, pName), | |
m_hStopEvent(NULL), | |
m_pGraphConfig(NULL), | |
m_bPinUsesReadOnlyAllocator(FALSE), | |
m_BlockState(NOT_BLOCKED), | |
m_hUnblockOutputPinEvent(NULL), | |
m_hNotifyCallerPinBlockedEvent(NULL), | |
m_dwBlockCallerThreadID(0), | |
m_dwNumOutstandingOutputPinUsers(0) | |
{ | |
HRESULT hr = Initialize(); | |
if( FAILED( hr ) ) { | |
*phr = hr; | |
return; | |
} | |
} | |
#ifdef UNICODE | |
CDynamicOutputPin::CDynamicOutputPin( | |
__in_opt LPCSTR pObjectName, | |
__in CBaseFilter *pFilter, | |
__in CCritSec *pLock, | |
__inout HRESULT *phr, | |
__in_opt LPCWSTR pName) : | |
CBaseOutputPin(pObjectName, pFilter, pLock, phr, pName), | |
m_hStopEvent(NULL), | |
m_pGraphConfig(NULL), | |
m_bPinUsesReadOnlyAllocator(FALSE), | |
m_BlockState(NOT_BLOCKED), | |
m_hUnblockOutputPinEvent(NULL), | |
m_hNotifyCallerPinBlockedEvent(NULL), | |
m_dwBlockCallerThreadID(0), | |
m_dwNumOutstandingOutputPinUsers(0) | |
{ | |
HRESULT hr = Initialize(); | |
if( FAILED( hr ) ) { | |
*phr = hr; | |
return; | |
} | |
} | |
#endif | |
CDynamicOutputPin::~CDynamicOutputPin() | |
{ | |
if(NULL != m_hUnblockOutputPinEvent) { | |
// This call should not fail because we have access to m_hUnblockOutputPinEvent | |
// and m_hUnblockOutputPinEvent is a valid event. | |
EXECUTE_ASSERT(::CloseHandle(m_hUnblockOutputPinEvent)); | |
} | |
if(NULL != m_hNotifyCallerPinBlockedEvent) { | |
// This call should not fail because we have access to m_hNotifyCallerPinBlockedEvent | |
// and m_hNotifyCallerPinBlockedEvent is a valid event. | |
EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); | |
} | |
} | |
HRESULT CDynamicOutputPin::Initialize(void) | |
{ | |
m_hUnblockOutputPinEvent = ::CreateEvent( NULL, // The event will have the default security descriptor. | |
TRUE, // This is a manual reset event. | |
TRUE, // The event is initially signaled. | |
NULL ); // The event is not named. | |
// CreateEvent() returns NULL if an error occurs. | |
if(NULL == m_hUnblockOutputPinEvent) { | |
return AmGetLastErrorToHResult(); | |
} | |
// Set flag to say we can reconnect while streaming. | |
SetReconnectWhenActive(true); | |
return S_OK; | |
} | |
STDMETHODIMP CDynamicOutputPin::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) | |
{ | |
if(riid == IID_IPinFlowControl) { | |
return GetInterface(static_cast<IPinFlowControl*>(this), ppv); | |
} else { | |
return CBaseOutputPin::NonDelegatingQueryInterface(riid, ppv); | |
} | |
} | |
STDMETHODIMP CDynamicOutputPin::Disconnect(void) | |
{ | |
CAutoLock cObjectLock(m_pLock); | |
return DisconnectInternal(); | |
} | |
STDMETHODIMP CDynamicOutputPin::Block(DWORD dwBlockFlags, HANDLE hEvent) | |
{ | |
const DWORD VALID_FLAGS = AM_PIN_FLOW_CONTROL_BLOCK; | |
// Check for illegal flags. | |
if(dwBlockFlags & ~VALID_FLAGS) { | |
return E_INVALIDARG; | |
} | |
// Make sure the event is unsignaled. | |
if((dwBlockFlags & AM_PIN_FLOW_CONTROL_BLOCK) && (NULL != hEvent)) { | |
if( !::ResetEvent( hEvent ) ) { | |
return AmGetLastErrorToHResult(); | |
} | |
} | |
// No flags are set if we are unblocking the output pin. | |
if(0 == dwBlockFlags) { | |
// This parameter should be NULL because unblock operations are always synchronous. | |
// There is no need to notify the caller when the event is done. | |
if(NULL != hEvent) { | |
return E_INVALIDARG; | |
} | |
} | |
#ifdef DEBUG | |
AssertValid(); | |
#endif // DEBUG | |
HRESULT hr; | |
if(dwBlockFlags & AM_PIN_FLOW_CONTROL_BLOCK) { | |
// IPinFlowControl::Block()'s hEvent parameter is NULL if the block is synchronous. | |
// If hEvent is not NULL, the block is asynchronous. | |
if(NULL == hEvent) { | |
hr = SynchronousBlockOutputPin(); | |
} else { | |
hr = AsynchronousBlockOutputPin(hEvent); | |
} | |
} else { | |
hr = UnblockOutputPin(); | |
} | |
#ifdef DEBUG | |
AssertValid(); | |
#endif // DEBUG | |
if(FAILED(hr)) { | |
return hr; | |
} | |
return S_OK; | |
} | |
HRESULT CDynamicOutputPin::SynchronousBlockOutputPin(void) | |
{ | |
HANDLE hNotifyCallerPinBlockedEvent = :: CreateEvent( NULL, // The event will have the default security attributes. | |
FALSE, // This is an automatic reset event. | |
FALSE, // The event is initially unsignaled. | |
NULL ); // The event is not named. | |
// CreateEvent() returns NULL if an error occurs. | |
if(NULL == hNotifyCallerPinBlockedEvent) { | |
return AmGetLastErrorToHResult(); | |
} | |
HRESULT hr = AsynchronousBlockOutputPin(hNotifyCallerPinBlockedEvent); | |
if(FAILED(hr)) { | |
// This call should not fail because we have access to hNotifyCallerPinBlockedEvent | |
// and hNotifyCallerPinBlockedEvent is a valid event. | |
EXECUTE_ASSERT(::CloseHandle(hNotifyCallerPinBlockedEvent)); | |
return hr; | |
} | |
hr = WaitEvent(hNotifyCallerPinBlockedEvent); | |
// This call should not fail because we have access to hNotifyCallerPinBlockedEvent | |
// and hNotifyCallerPinBlockedEvent is a valid event. | |
EXECUTE_ASSERT(::CloseHandle(hNotifyCallerPinBlockedEvent)); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
return S_OK; | |
} | |
HRESULT CDynamicOutputPin::AsynchronousBlockOutputPin(HANDLE hNotifyCallerPinBlockedEvent) | |
{ | |
// This function holds the m_BlockStateLock because it uses | |
// m_dwBlockCallerThreadID, m_BlockState and | |
// m_hNotifyCallerPinBlockedEvent. | |
CAutoLock alBlockStateLock(&m_BlockStateLock); | |
if(NOT_BLOCKED != m_BlockState) { | |
if(m_dwBlockCallerThreadID == ::GetCurrentThreadId()) { | |
return VFW_E_PIN_ALREADY_BLOCKED_ON_THIS_THREAD; | |
} else { | |
return VFW_E_PIN_ALREADY_BLOCKED; | |
} | |
} | |
BOOL fSuccess = ::DuplicateHandle( ::GetCurrentProcess(), | |
hNotifyCallerPinBlockedEvent, | |
::GetCurrentProcess(), | |
&m_hNotifyCallerPinBlockedEvent, | |
EVENT_MODIFY_STATE, | |
FALSE, | |
0 ); | |
if( !fSuccess ) { | |
return AmGetLastErrorToHResult(); | |
} | |
m_BlockState = PENDING; | |
m_dwBlockCallerThreadID = ::GetCurrentThreadId(); | |
// The output pin cannot be blocked if the streaming thread is | |
// calling IPin::NewSegment(), IPin::EndOfStream(), IMemInputPin::Receive() | |
// or IMemInputPin::ReceiveMultiple() on the connected input pin. Also, it | |
// cannot be blocked if the streaming thread is calling DynamicReconnect(), | |
// ChangeMediaType() or ChangeOutputFormat(). | |
if(!StreamingThreadUsingOutputPin()) { | |
// The output pin can be immediately blocked. | |
BlockOutputPin(); | |
} | |
return S_OK; | |
} | |
void CDynamicOutputPin::BlockOutputPin(void) | |
{ | |
// The caller should always hold the m_BlockStateLock because this function | |
// uses m_BlockState and m_hNotifyCallerPinBlockedEvent. | |
ASSERT(CritCheckIn(&m_BlockStateLock)); | |
// This function should not be called if the streaming thread is modifying | |
// the connection state or it's passing data downstream. | |
ASSERT(!StreamingThreadUsingOutputPin()); | |
// This should not fail because we successfully created the event | |
// and we have the security permissions to change it's state. | |
EXECUTE_ASSERT(::ResetEvent(m_hUnblockOutputPinEvent)); | |
// This event should not fail because AsynchronousBlockOutputPin() successfully | |
// duplicated this handle and we have the appropriate security permissions. | |
EXECUTE_ASSERT(::SetEvent(m_hNotifyCallerPinBlockedEvent)); | |
EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); | |
m_BlockState = BLOCKED; | |
m_hNotifyCallerPinBlockedEvent = NULL; | |
} | |
HRESULT CDynamicOutputPin::UnblockOutputPin(void) | |
{ | |
// UnblockOutputPin() holds the m_BlockStateLock because it | |
// uses m_BlockState, m_dwBlockCallerThreadID and | |
// m_hNotifyCallerPinBlockedEvent. | |
CAutoLock alBlockStateLock(&m_BlockStateLock); | |
if(NOT_BLOCKED == m_BlockState) { | |
return S_FALSE; | |
} | |
// This should not fail because we successfully created the event | |
// and we have the security permissions to change it's state. | |
EXECUTE_ASSERT(::SetEvent(m_hUnblockOutputPinEvent)); | |
// Cancel the block operation if it's still pending. | |
if(NULL != m_hNotifyCallerPinBlockedEvent) { | |
// This event should not fail because AsynchronousBlockOutputPin() successfully | |
// duplicated this handle and we have the appropriate security permissions. | |
EXECUTE_ASSERT(::SetEvent(m_hNotifyCallerPinBlockedEvent)); | |
EXECUTE_ASSERT(::CloseHandle(m_hNotifyCallerPinBlockedEvent)); | |
} | |
m_BlockState = NOT_BLOCKED; | |
m_dwBlockCallerThreadID = 0; | |
m_hNotifyCallerPinBlockedEvent = NULL; | |
return S_OK; | |
} | |
HRESULT CDynamicOutputPin::StartUsingOutputPin(void) | |
{ | |
// The caller should not hold m_BlockStateLock. If the caller does, | |
// a deadlock could occur. | |
ASSERT(CritCheckOut(&m_BlockStateLock)); | |
CAutoLock alBlockStateLock(&m_BlockStateLock); | |
#ifdef DEBUG | |
AssertValid(); | |
#endif // DEBUG | |
// Are we in the middle of a block operation? | |
while(BLOCKED == m_BlockState) { | |
m_BlockStateLock.Unlock(); | |
// If this ASSERT fires, a deadlock could occur. The caller should make sure | |
// that this thread never acquires the Block State lock more than once. | |
ASSERT(CritCheckOut( &m_BlockStateLock )); | |
// WaitForMultipleObjects() returns WAIT_OBJECT_0 if the unblock event | |
// is fired. It returns WAIT_OBJECT_0 + 1 if the stop event if fired. | |
// See the Windows SDK documentation for more information on | |
// WaitForMultipleObjects(). | |
const DWORD UNBLOCK = WAIT_OBJECT_0; | |
const DWORD STOP = WAIT_OBJECT_0 + 1; | |
HANDLE ahWaitEvents[] = { m_hUnblockOutputPinEvent, m_hStopEvent }; | |
DWORD dwNumWaitEvents = sizeof(ahWaitEvents)/sizeof(HANDLE); | |
DWORD dwReturnValue = ::WaitForMultipleObjects( dwNumWaitEvents, ahWaitEvents, FALSE, INFINITE ); | |
m_BlockStateLock.Lock(); | |
#ifdef DEBUG | |
AssertValid(); | |
#endif // DEBUG | |
switch( dwReturnValue ) { | |
case UNBLOCK: | |
break; | |
case STOP: | |
return VFW_E_STATE_CHANGED; | |
case WAIT_FAILED: | |
return AmGetLastErrorToHResult(); | |
default: | |
DbgBreak( "An Unexpected case occured in CDynamicOutputPin::StartUsingOutputPin()." ); | |
return E_UNEXPECTED; | |
} | |
} | |
m_dwNumOutstandingOutputPinUsers++; | |
#ifdef DEBUG | |
AssertValid(); | |
#endif // DEBUG | |
return S_OK; | |
} | |
void CDynamicOutputPin::StopUsingOutputPin(void) | |
{ | |
CAutoLock alBlockStateLock(&m_BlockStateLock); | |
#ifdef DEBUG | |
AssertValid(); | |
#endif // DEBUG | |
m_dwNumOutstandingOutputPinUsers--; | |
if((m_dwNumOutstandingOutputPinUsers == 0) && (NOT_BLOCKED != m_BlockState)) { | |
BlockOutputPin(); | |
} | |
#ifdef DEBUG | |
AssertValid(); | |
#endif // DEBUG | |
} | |
bool CDynamicOutputPin::StreamingThreadUsingOutputPin(void) | |
{ | |
CAutoLock alBlockStateLock(&m_BlockStateLock); | |
return (m_dwNumOutstandingOutputPinUsers > 0); | |
} | |
void CDynamicOutputPin::SetConfigInfo(IGraphConfig *pGraphConfig, HANDLE hStopEvent) | |
{ | |
// This pointer is not addrefed because filters are not allowed to | |
// hold references to the filter graph manager. See the documentation for | |
// IBaseFilter::JoinFilterGraph() in the Direct Show SDK for more information. | |
m_pGraphConfig = pGraphConfig; | |
m_hStopEvent = hStopEvent; | |
} | |
HRESULT CDynamicOutputPin::Active(void) | |
{ | |
// Make sure the user initialized the object by calling SetConfigInfo(). | |
if((NULL == m_hStopEvent) || (NULL == m_pGraphConfig)) { | |
DbgBreak( ERROR: CDynamicOutputPin::Active() failed because m_pGraphConfig and m_hStopEvent were not initialized. Call SetConfigInfo() to initialize them. ); | |
return E_FAIL; | |
} | |
// If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). | |
// The ASSERT can also fire if the event if destroyed and then Active() is called. An event | |
// handle is invalid if 1) the event does not exist or the user does not have the security | |
// permissions to use the event. | |
EXECUTE_ASSERT(ResetEvent(m_hStopEvent)); | |
return CBaseOutputPin::Active(); | |
} | |
HRESULT CDynamicOutputPin::Inactive(void) | |
{ | |
// If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). | |
// The ASSERT can also fire if the event if destroyed and then Active() is called. An event | |
// handle is invalid if 1) the event does not exist or the user does not have the security | |
// permissions to use the event. | |
EXECUTE_ASSERT(SetEvent(m_hStopEvent)); | |
return CBaseOutputPin::Inactive(); | |
} | |
HRESULT CDynamicOutputPin::DeliverBeginFlush(void) | |
{ | |
// If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). | |
// The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called. | |
// An event handle is invalid if 1) the event does not exist or the user does not have the security | |
// permissions to use the event. | |
EXECUTE_ASSERT(SetEvent(m_hStopEvent)); | |
return CBaseOutputPin::DeliverBeginFlush(); | |
} | |
HRESULT CDynamicOutputPin::DeliverEndFlush(void) | |
{ | |
// If this ASSERT fires, the user may have passed an invalid event handle to SetConfigInfo(). | |
// The ASSERT can also fire if the event if destroyed and then DeliverBeginFlush() is called. | |
// An event handle is invalid if 1) the event does not exist or the user does not have the security | |
// permissions to use the event. | |
EXECUTE_ASSERT(ResetEvent(m_hStopEvent)); | |
return CBaseOutputPin::DeliverEndFlush(); | |
} | |
// ChangeOutputFormat() either dynamicly changes the connection's format type or it dynamicly | |
// reconnects the output pin. | |
HRESULT CDynamicOutputPin::ChangeOutputFormat | |
( | |
const AM_MEDIA_TYPE *pmt, | |
REFERENCE_TIME tSegmentStart, | |
REFERENCE_TIME tSegmentStop, | |
double dSegmentRate | |
) | |
{ | |
// The caller should call StartUsingOutputPin() before calling this | |
// method. | |
ASSERT(StreamingThreadUsingOutputPin()); | |
// Callers should always pass a valid media type to ChangeOutputFormat() . | |
ASSERT(NULL != pmt); | |
CMediaType cmt(*pmt); | |
HRESULT hr = ChangeMediaType(&cmt); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
hr = DeliverNewSegment(tSegmentStart, tSegmentStop, dSegmentRate); | |
if( FAILED( hr ) ) { | |
return hr; | |
} | |
return S_OK; | |
} | |
HRESULT CDynamicOutputPin::ChangeMediaType(const CMediaType *pmt) | |
{ | |
// The caller should call StartUsingOutputPin() before calling this | |
// method. | |
ASSERT(StreamingThreadUsingOutputPin()); | |
// This function assumes the filter graph is running. | |
ASSERT(!IsStopped()); | |
if(!IsConnected()) { | |
return VFW_E_NOT_CONNECTED; | |
} | |
/* First check if the downstream pin will accept a dynamic | |
format change | |
*/ | |
QzCComPtr<IPinConnection> pConnection; | |
m_Connected->QueryInterface(IID_IPinConnection, (void **)&pConnection); | |
if(pConnection != NULL) { | |
if(S_OK == pConnection->DynamicQueryAccept(pmt)) { | |
HRESULT hr = ChangeMediaTypeHelper(pmt); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
return S_OK; | |
} | |
} | |
/* Can't do the dynamic connection */ | |
return DynamicReconnect(pmt); | |
} | |
HRESULT CDynamicOutputPin::ChangeMediaTypeHelper(const CMediaType *pmt) | |
{ | |
// The caller should call StartUsingOutputPin() before calling this | |
// method. | |
ASSERT(StreamingThreadUsingOutputPin()); | |
HRESULT hr = m_Connected->ReceiveConnection(this, pmt); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
hr = SetMediaType(pmt); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
// Does this pin use the local memory transport? | |
if(NULL != m_pInputPin) { | |
// This function assumes that m_pInputPin and m_Connected are | |
// two different interfaces to the same object. | |
ASSERT(::IsEqualObject(m_Connected, m_pInputPin)); | |
ALLOCATOR_PROPERTIES apInputPinRequirements; | |
apInputPinRequirements.cbAlign = 0; | |
apInputPinRequirements.cbBuffer = 0; | |
apInputPinRequirements.cbPrefix = 0; | |
apInputPinRequirements.cBuffers = 0; | |
m_pInputPin->GetAllocatorRequirements(&apInputPinRequirements); | |
// A zero allignment does not make any sense. | |
if(0 == apInputPinRequirements.cbAlign) { | |
apInputPinRequirements.cbAlign = 1; | |
} | |
hr = m_pAllocator->Decommit(); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
hr = DecideBufferSize(m_pAllocator, &apInputPinRequirements); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
hr = m_pAllocator->Commit(); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
hr = m_pInputPin->NotifyAllocator(m_pAllocator, m_bPinUsesReadOnlyAllocator); | |
if(FAILED(hr)) { | |
return hr; | |
} | |
} | |
return S_OK; | |
} | |
// this method has to be called from the thread that is pushing data, | |
// and it's the caller's responsibility to make sure that the thread | |
// has no outstand samples because they cannot be delivered after a | |
// reconnect | |
// | |
HRESULT CDynamicOutputPin::DynamicReconnect( const CMediaType* pmt ) | |
{ | |
// The caller should call StartUsingOutputPin() before calling this | |
// method. | |
ASSERT(StreamingThreadUsingOutputPin()); | |
if((m_pGraphConfig == NULL) || (NULL == m_hStopEvent)) { | |
return E_FAIL; | |
} | |
HRESULT hr = m_pGraphConfig->Reconnect( | |
this, | |
NULL, | |
pmt, | |
NULL, | |
m_hStopEvent, | |
AM_GRAPH_CONFIG_RECONNECT_CACHE_REMOVED_FILTERS ); | |
return hr; | |
} | |
HRESULT CDynamicOutputPin::CompleteConnect(IPin *pReceivePin) | |
{ | |
HRESULT hr = CBaseOutputPin::CompleteConnect(pReceivePin); | |
if(SUCCEEDED(hr)) { | |
if(!IsStopped() && m_pAllocator) { | |
hr = m_pAllocator->Commit(); | |
ASSERT(hr != VFW_E_ALREADY_COMMITTED); | |
} | |
} | |
return hr; | |
} | |
#ifdef DEBUG | |
void CDynamicOutputPin::AssertValid(void) | |
{ | |
// Make sure the object was correctly initialized. | |
// This ASSERT only fires if the object failed to initialize | |
// and the user ignored the constructor's return code (phr). | |
ASSERT(NULL != m_hUnblockOutputPinEvent); | |
// If either of these ASSERTs fire, the user did not correctly call | |
// SetConfigInfo(). | |
ASSERT(NULL != m_hStopEvent); | |
ASSERT(NULL != m_pGraphConfig); | |
// Make sure the block state is consistent. | |
CAutoLock alBlockStateLock(&m_BlockStateLock); | |
// BLOCK_STATE variables only have three legal values: PENDING, BLOCKED and NOT_BLOCKED. | |
ASSERT((NOT_BLOCKED == m_BlockState) || (PENDING == m_BlockState) || (BLOCKED == m_BlockState)); | |
// m_hNotifyCallerPinBlockedEvent is only needed when a block operation cannot complete | |
// immediately. | |
ASSERT(((NULL == m_hNotifyCallerPinBlockedEvent) && (PENDING != m_BlockState)) || | |
((NULL != m_hNotifyCallerPinBlockedEvent) && (PENDING == m_BlockState)) ); | |
// m_dwBlockCallerThreadID should always be 0 if the pin is not blocked and | |
// the user is not trying to block the pin. | |
ASSERT((0 == m_dwBlockCallerThreadID) || (NOT_BLOCKED != m_BlockState)); | |
// If this ASSERT fires, the streaming thread is using the output pin and the | |
// output pin is blocked. | |
ASSERT(((0 != m_dwNumOutstandingOutputPinUsers) && (BLOCKED != m_BlockState)) || | |
((0 == m_dwNumOutstandingOutputPinUsers) && (NOT_BLOCKED != m_BlockState)) || | |
((0 == m_dwNumOutstandingOutputPinUsers) && (NOT_BLOCKED == m_BlockState)) ); | |
} | |
#endif // DEBUG | |
HRESULT CDynamicOutputPin::WaitEvent(HANDLE hEvent) | |
{ | |
const DWORD EVENT_SIGNALED = WAIT_OBJECT_0; | |
DWORD dwReturnValue = ::WaitForSingleObject(hEvent, INFINITE); | |
switch( dwReturnValue ) { | |
case EVENT_SIGNALED: | |
return S_OK; | |
case WAIT_FAILED: | |
return AmGetLastErrorToHResult(); | |
default: | |
DbgBreak( "An Unexpected case occured in CDynamicOutputPin::WaitEvent()." ); | |
return E_UNEXPECTED; | |
} | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CBaseAllocator | |
//===================================================================== | |
//===================================================================== | |
/* Constructor overrides the default settings for the free list to request | |
that it be alertable (ie the list can be cast to a handle which can be | |
passed to WaitForSingleObject). Both of the allocator lists also ask for | |
object locking, the all list matches the object default settings but I | |
have included them here just so it is obvious what kind of list it is */ | |
CBaseAllocator::CBaseAllocator(__in_opt LPCTSTR pName, | |
__inout_opt LPUNKNOWN pUnk, | |
__inout HRESULT *phr, | |
BOOL bEvent, | |
BOOL fEnableReleaseCallback | |
) : | |
CUnknown(pName, pUnk), | |
m_lAllocated(0), | |
m_bChanged(FALSE), | |
m_bCommitted(FALSE), | |
m_bDecommitInProgress(FALSE), | |
m_lSize(0), | |
m_lCount(0), | |
m_lAlignment(0), | |
m_lPrefix(0), | |
m_hSem(NULL), | |
m_lWaiting(0), | |
m_fEnableReleaseCallback(fEnableReleaseCallback), | |
m_pNotify(NULL) | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( pName ? pName : L"CBaseAllocator", (IMemAllocator *) this ); | |
#endif // DXMPERF | |
if (bEvent) { | |
m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); | |
if (m_hSem == NULL) { | |
*phr = E_OUTOFMEMORY; | |
return; | |
} | |
} | |
} | |
#ifdef UNICODE | |
CBaseAllocator::CBaseAllocator(__in_opt LPCSTR pName, | |
__inout_opt LPUNKNOWN pUnk, | |
__inout HRESULT *phr, | |
BOOL bEvent, | |
BOOL fEnableReleaseCallback) : | |
CUnknown(pName, pUnk), | |
m_lAllocated(0), | |
m_bChanged(FALSE), | |
m_bCommitted(FALSE), | |
m_bDecommitInProgress(FALSE), | |
m_lSize(0), | |
m_lCount(0), | |
m_lAlignment(0), | |
m_lPrefix(0), | |
m_hSem(NULL), | |
m_lWaiting(0), | |
m_fEnableReleaseCallback(fEnableReleaseCallback), | |
m_pNotify(NULL) | |
{ | |
#ifdef DXMPERF | |
PERFLOG_CTOR( L"CBaseAllocator", (IMemAllocator *) this ); | |
#endif // DXMPERF | |
if (bEvent) { | |
m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL); | |
if (m_hSem == NULL) { | |
*phr = E_OUTOFMEMORY; | |
return; | |
} | |
} | |
} | |
#endif | |
/* Destructor */ | |
CBaseAllocator::~CBaseAllocator() | |
{ | |
// we can't call Decommit here since that would mean a call to a | |
// pure virtual in destructor. | |
// We must assume that the derived class has gone into decommit state in | |
// its destructor. | |
#ifdef DXMPERF | |
PERFLOG_DTOR( L"CBaseAllocator", (IMemAllocator *) this ); | |
#endif // DXMPERF | |
ASSERT(!m_bCommitted); | |
if (m_hSem != NULL) { | |
EXECUTE_ASSERT(CloseHandle(m_hSem)); | |
} | |
if (m_pNotify) { | |
m_pNotify->Release(); | |
} | |
} | |
/* Override this to publicise our interfaces */ | |
STDMETHODIMP | |
CBaseAllocator::NonDelegatingQueryInterface(REFIID riid, __deref_out void **ppv) | |
{ | |
/* Do we know about this interface */ | |
if (riid == IID_IMemAllocator || | |
riid == IID_IMemAllocatorCallbackTemp && m_fEnableReleaseCallback) { | |
return GetInterface((IMemAllocatorCallbackTemp *) this, ppv); | |
} else { | |
return CUnknown::NonDelegatingQueryInterface(riid, ppv); | |
} | |
} | |
/* This sets the size and count of the required samples. The memory isn't | |
actually allocated until Commit() is called, if memory has already been | |
allocated then assuming no samples are outstanding the user may call us | |
to change the buffering, the memory will be released in Commit() */ | |
STDMETHODIMP | |
CBaseAllocator::SetProperties( | |
__in ALLOCATOR_PROPERTIES* pRequest, | |
__out ALLOCATOR_PROPERTIES* pActual) | |
{ | |
CheckPointer(pRequest, E_POINTER); | |
CheckPointer(pActual, E_POINTER); | |
ValidateReadWritePtr(pActual, sizeof(ALLOCATOR_PROPERTIES)); | |
CAutoLock cObjectLock(this); | |
ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES)); | |
ASSERT(pRequest->cbBuffer > 0); | |
/* Check the alignment requested */ | |
if (pRequest->cbAlign != 1) { | |
DbgLog((LOG_ERROR, 2, TEXT("Alignment requested was 0x%x, not 1"), | |
pRequest->cbAlign)); | |
return VFW_E_BADALIGN; | |
} | |
/* Can't do this if already committed, there is an argument that says we | |
should not reject the SetProperties call if there are buffers still | |
active. However this is called by the source filter, which is the same | |
person who is holding the samples. Therefore it is not unreasonable | |
for them to free all their samples before changing the requirements */ | |
if (m_bCommitted) { | |
return VFW_E_ALREADY_COMMITTED; | |
} | |
/* Must be no outstanding buffers */ | |
if (m_lAllocated != m_lFree.GetCount()) { | |
return VFW_E_BUFFERS_OUTSTANDING; | |
} | |
/* There isn't any real need to check the parameters as they | |
will just be rejected when the user finally calls Commit */ | |
pActual->cbBuffer = m_lSize = pRequest->cbBuffer; | |
pActual->cBuffers = m_lCount = pRequest->cBuffers; | |
pActual->cbAlign = m_lAlignment = pRequest->cbAlign; | |
pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix; | |
m_bChanged = TRUE; | |
return NOERROR; | |
} | |
STDMETHODIMP | |
CBaseAllocator::GetProperties( | |
__out ALLOCATOR_PROPERTIES * pActual) | |
{ | |
CheckPointer(pActual,E_POINTER); | |
ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES)); | |
CAutoLock cObjectLock(this); | |
pActual->cbBuffer = m_lSize; | |
pActual->cBuffers = m_lCount; | |
pActual->cbAlign = m_lAlignment; | |
pActual->cbPrefix = m_lPrefix; | |
return NOERROR; | |
} | |
// get container for a sample. Blocking, synchronous call to get the | |
// next free buffer (as represented by an IMediaSample interface). | |
// on return, the time etc properties will be invalid, but the buffer | |
// pointer and size will be correct. | |
HRESULT CBaseAllocator::GetBuffer(__deref_out IMediaSample **ppBuffer, | |
__in_opt REFERENCE_TIME *pStartTime, | |
__in_opt REFERENCE_TIME *pEndTime, | |
DWORD dwFlags | |
) | |
{ | |
UNREFERENCED_PARAMETER(pStartTime); | |
UNREFERENCED_PARAMETER(pEndTime); | |
UNREFERENCED_PARAMETER(dwFlags); | |
CMediaSample *pSample; | |
*ppBuffer = NULL; | |
for (;;) | |
{ | |
{ // scope for lock | |
CAutoLock cObjectLock(this); | |
/* Check we are committed */ | |
if (!m_bCommitted) { | |
return VFW_E_NOT_COMMITTED; | |
} | |
pSample = (CMediaSample *) m_lFree.RemoveHead(); | |
if (pSample == NULL) { | |
SetWaiting(); | |
} | |
} | |
/* If we didn't get a sample then wait for the list to signal */ | |
if (pSample) { | |
break; | |
} | |
if (dwFlags & AM_GBF_NOWAIT) { | |
return VFW_E_TIMEOUT; | |
} | |
ASSERT(m_hSem != NULL); | |
WaitForSingleObject(m_hSem, INFINITE); | |
} | |
/* Addref the buffer up to one. On release | |
back to zero instead of being deleted, it will requeue itself by | |
calling the ReleaseBuffer member function. NOTE the owner of a | |
media sample must always be derived from CBaseAllocator */ | |
ASSERT(pSample->m_cRef == 0); | |
pSample->m_cRef = 1; | |
*ppBuffer = pSample; | |
#ifdef DXMPERF | |
PERFLOG_GETBUFFER( (IMemAllocator *) this, pSample ); | |
#endif // DXMPERF | |
return NOERROR; | |
} | |
/* Final release of a CMediaSample will call this */ | |
STDMETHODIMP | |
CBaseAllocator::ReleaseBuffer(IMediaSample * pSample) | |
{ | |
CheckPointer(pSample,E_POINTER); | |
ValidateReadPtr(pSample,sizeof(IMediaSample)); | |
#ifdef DXMPERF | |
PERFLOG_RELBUFFER( (IMemAllocator *) this, pSample ); | |
#endif // DXMPERF | |
BOOL bRelease = FALSE; | |
{ | |
CAutoLock cal(this); | |
/* Put back on the free list */ | |
m_lFree.Add((CMediaSample *)pSample); | |
if (m_lWaiting != 0) { | |
NotifySample(); | |
} | |
// if there is a pending Decommit, then we need to complete it by | |
// calling Free() when the last buffer is placed on the free list | |
LONG l1 = m_lFree.GetCount(); | |
if (m_bDecommitInProgress && (l1 == m_lAllocated)) { | |
Free(); | |
m_bDecommitInProgress = FALSE; | |
bRelease = TRUE; | |
} | |
} | |
if (m_pNotify) { | |
ASSERT(m_fEnableReleaseCallback); | |
// | |
// Note that this is not synchronized with setting up a notification | |
// method. | |
// | |
m_pNotify->NotifyRelease(); | |
} | |
/* For each buffer there is one AddRef, made in GetBuffer and released | |
here. This may cause the allocator and all samples to be deleted */ | |
if (bRelease) { | |
Release(); | |
} | |
return NOERROR; | |
} | |
STDMETHODIMP | |
CBaseAllocator::SetNotify( | |
IMemAllocatorNotifyCallbackTemp* pNotify | |
) | |
{ | |
ASSERT(m_fEnableReleaseCallback); | |
CAutoLock lck(this); | |
if (pNotify) { | |
pNotify->AddRef(); | |
} | |
if (m_pNotify) { | |
m_pNotify->Release(); | |
} | |
m_pNotify = pNotify; | |
return S_OK; | |
} | |
STDMETHODIMP | |
CBaseAllocator::GetFreeCount( | |
__out LONG* plBuffersFree | |
) | |
{ | |
ASSERT(m_fEnableReleaseCallback); | |
CAutoLock cObjectLock(this); | |
*plBuffersFree = m_lCount - m_lAllocated + m_lFree.GetCount(); | |
return NOERROR; | |
} | |
void | |
CBaseAllocator::NotifySample() | |
{ | |
if (m_lWaiting != 0) { | |
ASSERT(m_hSem != NULL); | |
ReleaseSemaphore(m_hSem, m_lWaiting, 0); | |
m_lWaiting = 0; | |
} | |
} | |
STDMETHODIMP | |
CBaseAllocator::Commit() | |
{ | |
/* Check we are not decommitted */ | |
CAutoLock cObjectLock(this); | |
// cannot need to alloc or re-alloc if we are committed | |
if (m_bCommitted) { | |
return NOERROR; | |
} | |
/* Allow GetBuffer calls */ | |
m_bCommitted = TRUE; | |
// is there a pending decommit ? if so, just cancel it | |
if (m_bDecommitInProgress) { | |
m_bDecommitInProgress = FALSE; | |
// don't call Alloc at this point. He cannot allow SetProperties | |
// between Decommit and the last free, so the buffer size cannot have | |
// changed. And because some of the buffers are not free yet, he | |
// cannot re-alloc anyway. | |
return NOERROR; | |
} | |
DbgLog((LOG_MEMORY, 1, TEXT("Allocating: %ldx%ld"), m_lCount, m_lSize)); | |
// actually need to allocate the samples | |
HRESULT hr = Alloc(); | |
if (FAILED(hr)) { | |
m_bCommitted = FALSE; | |
return hr; | |
} | |
AddRef(); | |
return NOERROR; | |
} | |
STDMETHODIMP | |
CBaseAllocator::Decommit() | |
{ | |
BOOL bRelease = FALSE; | |
{ | |
/* Check we are not already decommitted */ | |
CAutoLock cObjectLock(this); | |
if (m_bCommitted == FALSE) { | |
if (m_bDecommitInProgress == FALSE) { | |
return NOERROR; | |
} | |
} | |
/* No more GetBuffer calls will succeed */ | |
m_bCommitted = FALSE; | |
// are any buffers outstanding? | |
if (m_lFree.GetCount() < m_lAllocated) { | |
// please complete the decommit when last buffer is freed | |
m_bDecommitInProgress = TRUE; | |
} else { | |
m_bDecommitInProgress = FALSE; | |
// need to complete the decommit here as there are no | |
// outstanding buffers | |
Free(); | |
bRelease = TRUE; | |
} | |
// Tell anyone waiting that they can go now so we can | |
// reject their call | |
#pragma warning(push) | |
#ifndef _PREFAST_ | |
#pragma warning(disable:4068) | |
#endif | |
#pragma prefast(suppress:__WARNING_DEREF_NULL_PTR, "Suppress warning related to Free() invalidating 'this' which is no applicable to CBaseAllocator::Free()") | |
NotifySample(); | |
#pragma warning(pop) | |
} | |
if (bRelease) { | |
Release(); | |
} | |
return NOERROR; | |
} | |
/* Base definition of allocation which checks we are ok to go ahead and do | |
the full allocation. We return S_FALSE if the requirements are the same */ | |
HRESULT | |
CBaseAllocator::Alloc(void) | |
{ | |
/* Error if he hasn't set the size yet */ | |
if (m_lCount <= 0 || m_lSize <= 0 || m_lAlignment <= 0) { | |
return VFW_E_SIZENOTSET; | |
} | |
/* should never get here while buffers outstanding */ | |
ASSERT(m_lFree.GetCount() == m_lAllocated); | |
/* If the requirements haven't changed then don't reallocate */ | |
if (m_bChanged == FALSE) { | |
return S_FALSE; | |
} | |
return NOERROR; | |
} | |
/* Implement CBaseAllocator::CSampleList::Remove(pSample) | |
Removes pSample from the list | |
*/ | |
void | |
CBaseAllocator::CSampleList::Remove(__inout CMediaSample * pSample) | |
{ | |
CMediaSample **pSearch; | |
for (pSearch = &m_List; | |
*pSearch != NULL; | |
pSearch = &(CBaseAllocator::NextSample(*pSearch))) { | |
if (*pSearch == pSample) { | |
*pSearch = CBaseAllocator::NextSample(pSample); | |
CBaseAllocator::NextSample(pSample) = NULL; | |
m_nOnList--; | |
return; | |
} | |
} | |
DbgBreak("Couldn't find sample in list"); | |
} | |
//===================================================================== | |
//===================================================================== | |
// Implements CMemAllocator | |
//===================================================================== | |
//===================================================================== | |
/* This goes in the factory template table to create new instances */ | |
CUnknown *CMemAllocator::CreateInstance(__inout_opt LPUNKNOWN pUnk, __inout HRESULT *phr) | |
{ | |
CUnknown *pUnkRet = new CMemAllocator(NAME("CMemAllocator"), pUnk, phr); | |
return pUnkRet; | |
} | |
CMemAllocator::CMemAllocator( | |
__in_opt LPCTSTR pName, | |
__inout_opt LPUNKNOWN pUnk, | |
__inout HRESULT *phr) | |
: CBaseAllocator(pName, pUnk, phr, TRUE, TRUE), | |
m_pBuffer(NULL) | |
{ | |
} | |
#ifdef UNICODE | |
CMemAllocator::CMemAllocator( | |
__in_opt LPCSTR pName, | |
__inout_opt LPUNKNOWN pUnk, | |
__inout HRESULT *phr) | |
: CBaseAllocator(pName, pUnk, phr, TRUE, TRUE), | |
m_pBuffer(NULL) | |
{ | |
} | |
#endif | |
/* This sets the size and count of the required samples. The memory isn't | |
actually allocated until Commit() is called, if memory has already been | |
allocated then assuming no samples are outstanding the user may call us | |
to change the buffering, the memory will be released in Commit() */ | |
STDMETHODIMP | |
CMemAllocator::SetProperties( | |
__in ALLOCATOR_PROPERTIES* pRequest, | |
__out ALLOCATOR_PROPERTIES* pActual) | |
{ | |
CheckPointer(pActual,E_POINTER); | |
ValidateReadWritePtr(pActual,sizeof(ALLOCATOR_PROPERTIES)); | |
CAutoLock cObjectLock(this); | |
ZeroMemory(pActual, sizeof(ALLOCATOR_PROPERTIES)); | |
ASSERT(pRequest->cbBuffer > 0); | |
SYSTEM_INFO SysInfo; | |
GetSystemInfo(&SysInfo); | |
/* Check the alignment request is a power of 2 */ | |
if ((-pRequest->cbAlign & pRequest->cbAlign) != pRequest->cbAlign) { | |
DbgLog((LOG_ERROR, 1, TEXT("Alignment requested 0x%x not a power of 2!"), | |
pRequest->cbAlign)); | |
} | |
/* Check the alignment requested */ | |
if (pRequest->cbAlign == 0 || | |
(SysInfo.dwAllocationGranularity & (pRequest->cbAlign - 1)) != 0) { | |
DbgLog((LOG_ERROR, 1, TEXT("Invalid alignment 0x%x requested - granularity = 0x%x"), | |
pRequest->cbAlign, SysInfo.dwAllocationGranularity)); | |
return VFW_E_BADALIGN; | |
} | |
/* Can't do this if already committed, there is an argument that says we | |
should not reject the SetProperties call if there are buffers still | |
active. However this is called by the source filter, which is the same | |
person who is holding the samples. Therefore it is not unreasonable | |
for them to free all their samples before changing the requirements */ | |
if (m_bCommitted == TRUE) { | |
return VFW_E_ALREADY_COMMITTED; | |
} | |
/* Must be no outstanding buffers */ | |
if (m_lFree.GetCount() < m_lAllocated) { | |
return VFW_E_BUFFERS_OUTSTANDING; | |
} | |
/* There isn't any real need to check the parameters as they | |
will just be rejected when the user finally calls Commit */ | |
// round length up to alignment - remember that prefix is included in | |
// the alignment | |
LONG lSize = pRequest->cbBuffer + pRequest->cbPrefix; | |
LONG lRemainder = lSize % pRequest->cbAlign; | |
if (lRemainder != 0) { | |
lSize = lSize - lRemainder + pRequest->cbAlign; | |
} | |
pActual->cbBuffer = m_lSize = (lSize - pRequest->cbPrefix); | |
pActual->cBuffers = m_lCount = pRequest->cBuffers; | |
pActual->cbAlign = m_lAlignment = pRequest->cbAlign; | |
pActual->cbPrefix = m_lPrefix = pRequest->cbPrefix; | |
m_bChanged = TRUE; | |
return NOERROR; | |
} | |
// override this to allocate our resources when Commit is called. | |
// | |
// note that our resources may be already allocated when this is called, | |
// since we don't free them on Decommit. We will only be called when in | |
// decommit state with all buffers free. | |
// | |
// object locked by caller | |
HRESULT | |
CMemAllocator::Alloc(void) | |
{ | |
CAutoLock lck(this); | |
/* Check he has called SetProperties */ | |
HRESULT hr = CBaseAllocator::Alloc(); | |
if (FAILED(hr)) { | |
return hr; | |
} | |
/* If the requirements haven't changed then don't reallocate */ | |
if (hr == S_FALSE) { | |
ASSERT(m_pBuffer); | |
return NOERROR; | |
} | |
ASSERT(hr == S_OK); // we use this fact in the loop below | |
/* Free the old resources */ | |
if (m_pBuffer) { | |
ReallyFree(); | |
} | |
/* Make sure we've got reasonable values */ | |
if ( m_lSize < 0 || m_lPrefix < 0 || m_lCount < 0 ) { | |
return E_OUTOFMEMORY; | |
} | |
/* Compute the aligned size */ | |
LONG lAlignedSize = m_lSize + m_lPrefix; | |
/* Check overflow */ | |
if (lAlignedSize < m_lSize) { | |
return E_OUTOFMEMORY; | |
} | |
if (m_lAlignment > 1) { | |
LONG lRemainder = lAlignedSize % m_lAlignment; | |
if (lRemainder != 0) { | |
LONG lNewSize = lAlignedSize + m_lAlignment - lRemainder; | |
if (lNewSize < lAlignedSize) { | |
return E_OUTOFMEMORY; | |
} | |
lAlignedSize = lNewSize; | |
} | |
} | |
/* Create the contiguous memory block for the samples | |
making sure it's properly aligned (64K should be enough!) | |
*/ | |
ASSERT(lAlignedSize % m_lAlignment == 0); | |
LONGLONG lToAllocate = m_lCount * (LONGLONG)lAlignedSize; | |
/* Check overflow */ | |
if (lToAllocate > MAXLONG) { | |
return E_OUTOFMEMORY; | |
} | |
m_pBuffer = (PBYTE)VirtualAlloc(NULL, | |
(LONG)lToAllocate, | |
MEM_COMMIT, | |
PAGE_READWRITE); | |
if (m_pBuffer == NULL) { | |
return E_OUTOFMEMORY; | |
} | |
LPBYTE pNext = m_pBuffer; | |
CMediaSample *pSample; | |
ASSERT(m_lAllocated == 0); | |
// Create the new samples - we have allocated m_lSize bytes for each sample | |
// plus m_lPrefix bytes per sample as a prefix. We set the pointer to | |
// the memory after the prefix - so that GetPointer() will return a pointer | |
// to m_lSize bytes. | |
for (; m_lAllocated < m_lCount; m_lAllocated++, pNext += lAlignedSize) { | |
pSample = new CMediaSample( | |
NAME("Default memory media sample"), | |
this, | |
&hr, | |
pNext + m_lPrefix, // GetPointer() value | |
m_lSize); // not including prefix | |
ASSERT(SUCCEEDED(hr)); | |
if (pSample == NULL) { | |
return E_OUTOFMEMORY; | |
} | |
// This CANNOT fail | |
m_lFree.Add(pSample); | |
} | |
m_bChanged = FALSE; | |
return NOERROR; | |
} | |
// override this to free up any resources we have allocated. | |
// called from the base class on Decommit when all buffers have been | |
// returned to the free list. | |
// | |
// caller has already locked the object. | |
// in our case, we keep the memory until we are deleted, so | |
// we do nothing here. The memory is deleted in the destructor by | |
// calling ReallyFree() | |
void | |
CMemAllocator::Free(void) | |
{ | |
return; | |
} | |
// called from the destructor (and from Alloc if changing size/count) to | |
// actually free up the memory | |
void | |
CMemAllocator::ReallyFree(void) | |
{ | |
/* Should never be deleting this unless all buffers are freed */ | |
ASSERT(m_lAllocated == m_lFree.GetCount()); | |
/* Free up all the CMediaSamples */ | |
CMediaSample *pSample; | |
for (;;) { | |
pSample = m_lFree.RemoveHead(); | |
if (pSample != NULL) { | |
delete pSample; | |
} else { | |
break; | |
} | |
} | |
m_lAllocated = 0; | |
// free the block of buffer memory | |
if (m_pBuffer) { | |
EXECUTE_ASSERT(VirtualFree(m_pBuffer, 0, MEM_RELEASE)); | |
m_pBuffer = NULL; | |
} | |
} | |
/* Destructor frees our memory resources */ | |
CMemAllocator::~CMemAllocator() | |
{ | |
Decommit(); | |
ReallyFree(); | |
} | |
// ------------------------------------------------------------------------ | |
// filter registration through IFilterMapper. used if IFilterMapper is | |
// not found (Quartz 1.0 install) | |
STDAPI | |
AMovieSetupRegisterFilter( const AMOVIESETUP_FILTER * const psetupdata | |
, IFilterMapper * pIFM | |
, BOOL bRegister ) | |
{ | |
DbgLog((LOG_TRACE, 3, TEXT("= AMovieSetupRegisterFilter"))); | |
// check we've got data | |
// | |
if( NULL == psetupdata ) return S_FALSE; | |
// unregister filter | |
// (as pins are subkeys of filter's CLSID key | |
// they do not need to be removed separately). | |
// | |
DbgLog((LOG_TRACE, 3, TEXT("= = unregister filter"))); | |
HRESULT hr = pIFM->UnregisterFilter( *(psetupdata->clsID) ); | |
if( bRegister ) | |
{ | |
// register filter | |
// | |
DbgLog((LOG_TRACE, 3, TEXT("= = register filter"))); | |
hr = pIFM->RegisterFilter( *(psetupdata->clsID) | |
, psetupdata->strName | |
, psetupdata->dwMerit ); | |
if( SUCCEEDED(hr) ) | |
{ | |
// all its pins | |
// | |
DbgLog((LOG_TRACE, 3, TEXT("= = register filter pins"))); | |
for( UINT m1=0; m1 < psetupdata->nPins; m1++ ) | |
{ | |
hr = pIFM->RegisterPin( *(psetupdata->clsID) | |
, psetupdata->lpPin[m1].strName | |
, psetupdata->lpPin[m1].bRendered | |
, psetupdata->lpPin[m1].bOutput | |
, psetupdata->lpPin[m1].bZero | |
, psetupdata->lpPin[m1].bMany | |
, *(psetupdata->lpPin[m1].clsConnectsToFilter) | |
, psetupdata->lpPin[m1].strConnectsToPin ); | |
if( SUCCEEDED(hr) ) | |
{ | |
// and each pin's media types | |
// | |
DbgLog((LOG_TRACE, 3, TEXT("= = register filter pin types"))); | |
for( UINT m2=0; m2 < psetupdata->lpPin[m1].nMediaTypes; m2++ ) | |
{ | |
hr = pIFM->RegisterPinType( *(psetupdata->clsID) | |
, psetupdata->lpPin[m1].strName | |
, *(psetupdata->lpPin[m1].lpMediaType[m2].clsMajorType) | |
, *(psetupdata->lpPin[m1].lpMediaType[m2].clsMinorType) ); | |
if( FAILED(hr) ) break; | |
} | |
if( FAILED(hr) ) break; | |
} | |
if( FAILED(hr) ) break; | |
} | |
} | |
} | |
// handle one acceptable "error" - that | |
// of filter not being registered! | |
// (couldn't find a suitable #define'd | |
// name for the error!) | |
// | |
if( 0x80070002 == hr) | |
return NOERROR; | |
else | |
return hr; | |
} | |
// Remove warnings about unreferenced inline functions | |
#pragma warning(disable:4514) | |
#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */ |