mirror of https://github.com/PCSX2/pcsx2.git
5204 lines
144 KiB
C++
5204 lines
144 KiB
C++
//------------------------------------------------------------------------------
|
|
// File: AMFilter.cpp
|
|
//
|
|
// Desc: DirectShow base classes - implements class hierarchy for streams
|
|
// architecture.
|
|
//
|
|
// Copyright (c) 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 "streams.h"
|
|
|
|
|
|
|
|
//=====================================================================
|
|
// Helpers
|
|
//=====================================================================
|
|
STDAPI CreateMemoryAllocator(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(
|
|
LPUNKNOWN pAgg,
|
|
BOOL bRenderer,
|
|
IPin *pPin,
|
|
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(const TCHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
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,
|
|
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(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, 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(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(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,
|
|
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(const TCHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
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)
|
|
{
|
|
|
|
ASSERT(pLock != NULL);
|
|
}
|
|
|
|
/* Passes in a redundant HRESULT argument */
|
|
|
|
CBaseFilter::CBaseFilter(TCHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
CCritSec *pLock,
|
|
REFCLSID clsid,
|
|
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)
|
|
{
|
|
|
|
ASSERT(pLock != NULL);
|
|
UNREFERENCED_PARAMETER(phr);
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
CBaseFilter::CBaseFilter(const CHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
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)
|
|
{
|
|
|
|
ASSERT(pLock != NULL);
|
|
}
|
|
CBaseFilter::CBaseFilter(CHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
CCritSec *pLock,
|
|
REFCLSID clsid,
|
|
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)
|
|
{
|
|
|
|
ASSERT(pLock != NULL);
|
|
UNREFERENCED_PARAMETER(phr);
|
|
}
|
|
#endif
|
|
|
|
/* Destructor */
|
|
|
|
CBaseFilter::~CBaseFilter()
|
|
{
|
|
|
|
// 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(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, 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(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(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);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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(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,
|
|
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);
|
|
ASSERT(pPin != NULL);
|
|
|
|
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(FILTER_INFO * pInfo)
|
|
{
|
|
CheckPointer(pInfo,E_POINTER);
|
|
ValidateReadWritePtr(pInfo,sizeof(FILTER_INFO));
|
|
|
|
if (m_pName) {
|
|
lstrcpynW(pInfo->achName, m_pName, sizeof(pInfo->achName)/sizeof(WCHAR));
|
|
} 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(
|
|
IFilterGraph * pGraph,
|
|
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) {
|
|
DWORD nameLen = lstrlenW(pName)+1;
|
|
m_pName = new WCHAR[nameLen];
|
|
if (m_pName) {
|
|
CopyMemory(m_pName, pName, nameLen*sizeof(WCHAR));
|
|
} else {
|
|
// !!! error here?
|
|
ASSERT(FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
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(
|
|
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,
|
|
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(CBaseFilter *pFilter,
|
|
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,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(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...
|
|
IPin **ppPins, // ...in this array
|
|
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();
|
|
}
|
|
|
|
/* Calculate the number of available pins */
|
|
|
|
int cRealPins = min(m_PinCount - m_Position, (int) cPins);
|
|
if (cRealPins == 0) {
|
|
return S_FALSE;
|
|
}
|
|
|
|
/* 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 (cRealPins && (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);
|
|
|
|
cRealPins--;
|
|
|
|
}
|
|
}
|
|
|
|
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(CBasePin *pPin,
|
|
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,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(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...
|
|
AM_MEDIA_TYPE **ppMediaTypes, // ...in this array
|
|
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 = (AM_MEDIA_TYPE)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(TCHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
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
|
|
*/
|
|
|
|
ASSERT(pFilter != NULL);
|
|
ASSERT(pLock != NULL);
|
|
|
|
if (pName) {
|
|
DWORD nameLen = lstrlenW(pName)+1;
|
|
m_pName = new WCHAR[nameLen];
|
|
if (m_pName) {
|
|
CopyMemory(m_pName, pName, nameLen*sizeof(WCHAR));
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
m_cRef = 0;
|
|
#endif
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
CBasePin::CBasePin(CHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
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
|
|
*/
|
|
|
|
ASSERT(pFilter != NULL);
|
|
ASSERT(pLock != NULL);
|
|
|
|
if (pName) {
|
|
DWORD nameLen = lstrlenW(pName)+1;
|
|
m_pName = new WCHAR[nameLen];
|
|
if (m_pName) {
|
|
CopyMemory(m_pName, pName, nameLen*sizeof(WCHAR));
|
|
}
|
|
}
|
|
|
|
#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()
|
|
{
|
|
|
|
// 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, 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))) {
|
|
(void)StringCchCopyW(ConnectPinInfo.achName, NUMELMS(ConnectPinInfo.achName),L"Bad Pin");
|
|
} else {
|
|
QueryPinInfoReleaseFilter(ConnectPinInfo);
|
|
}
|
|
|
|
if (FAILED(pReceivePin->QueryPinInfo(&ReceivePinInfo))) {
|
|
(void)StringCchCopyW(ReceivePinInfo.achName, NUMELMS(ReceivePinInfo.achName),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,
|
|
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() ) );
|
|
|
|
|
|
return hr;
|
|
}
|
|
|
|
DbgLog((LOG_TRACE, CONNECT_TRACE_LEVEL, TEXT("Connection succeeded")));
|
|
|
|
|
|
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,
|
|
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 ((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;
|
|
}
|
|
|
|
DeleteMediaType(pMediaType);
|
|
|
|
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() ) );
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
return hr;
|
|
}
|
|
|
|
/* Complete the connection */
|
|
|
|
m_Connected = pConnector;
|
|
m_Connected->AddRef();
|
|
hr = SetMediaType(pcmt);
|
|
if (SUCCEEDED(hr)) {
|
|
hr = CompleteConnect(pConnector);
|
|
if (SUCCEEDED(hr)) {
|
|
|
|
|
|
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() ) );
|
|
|
|
|
|
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 ) ) {
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
return S_OK;
|
|
} else {
|
|
// no connection - not an error
|
|
|
|
|
|
return S_FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/* Return an AddRef()'d pointer to the connected pin if there is one */
|
|
STDMETHODIMP
|
|
CBasePin::ConnectedTo(
|
|
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(
|
|
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(
|
|
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) {
|
|
lstrcpynW(pInfo->achName, m_pName, sizeof(pInfo->achName)/sizeof(WCHAR));
|
|
} else {
|
|
pInfo->achName[0] = L'\0';
|
|
}
|
|
|
|
pInfo->dir = m_dir;
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP
|
|
CBasePin::QueryDirection(
|
|
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(
|
|
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(
|
|
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, 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(TCHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
LPCWSTR pName) :
|
|
CBasePin(pObjectName, pFilter, pLock, phr, pName, PINDIR_OUTPUT),
|
|
m_pAllocator(NULL),
|
|
m_pInputPin(NULL)
|
|
{
|
|
ASSERT(pFilter);
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
CBaseOutputPin::CBaseOutputPin(CHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
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(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, 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(IMediaSample ** ppSample,
|
|
REFERENCE_TIME * pStartTime,
|
|
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;
|
|
}
|
|
|
|
|
|
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(TCHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
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(CHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
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, 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(
|
|
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;
|
|
}
|
|
|
|
|
|
|
|
/* 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 (
|
|
IMediaSample **pSamples,
|
|
long nSamples,
|
|
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);
|
|
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(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(TCHAR *pName,
|
|
CBaseAllocator *pAllocator,
|
|
HRESULT *phr,
|
|
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
|
|
{
|
|
|
|
/* 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);
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
CMediaSample::CMediaSample(CHAR *pName,
|
|
CBaseAllocator *pAllocator,
|
|
HRESULT *phr,
|
|
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
|
|
{
|
|
|
|
/* 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()
|
|
{
|
|
|
|
if (m_pMediaType) {
|
|
DeleteMediaType(m_pMediaType);
|
|
}
|
|
}
|
|
|
|
/* Override this to publicise our interfaces */
|
|
|
|
STDMETHODIMP
|
|
CMediaSample::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
if (riid == IID_IMediaSample ||
|
|
riid == IID_IMediaSample2 ||
|
|
riid == IID_IUnknown) {
|
|
return GetInterface((IMediaSample *) this, ppv);
|
|
} else {
|
|
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(BYTE * ptr, LONG cBytes)
|
|
{
|
|
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(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(
|
|
REFERENCE_TIME * pTimeStart, // put time here
|
|
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(
|
|
REFERENCE_TIME * pTimeStart,
|
|
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(
|
|
LONGLONG * pTimeStart,
|
|
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(
|
|
LONGLONG * pTimeStart,
|
|
LONGLONG * pTimeEnd
|
|
)
|
|
{
|
|
if (pTimeStart == NULL) {
|
|
ASSERT(pTimeEnd == NULL);
|
|
m_dwFlags &= ~Sample_MediaTimeValid;
|
|
} else {
|
|
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) {
|
|
ASSERT(lActual <= GetSize());
|
|
return VFW_E_BUFFER_OVERFLOW;
|
|
}
|
|
m_lActual = lActual;
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
/* These allow for limited format changes in band */
|
|
|
|
STDMETHODIMP
|
|
CMediaSample::GetMediaType(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(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,
|
|
BYTE * pbProperties
|
|
)
|
|
{
|
|
if (0 != cbProperties) {
|
|
CheckPointer(pbProperties, E_POINTER);
|
|
// Return generic stuff up to the length
|
|
AM_SAMPLE2_PROPERTIES Props;
|
|
Props.cbData = (DWORD) (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,
|
|
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(
|
|
TCHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
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(
|
|
CHAR *pObjectName,
|
|
CBaseFilter *pFilter,
|
|
CCritSec *pLock,
|
|
HRESULT *phr,
|
|
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, 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(TCHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
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)
|
|
{
|
|
|
|
if (bEvent) {
|
|
m_hSem = CreateSemaphore(NULL, 0, 0x7FFFFFFF, NULL);
|
|
if (m_hSem == NULL) {
|
|
*phr = E_OUTOFMEMORY;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
CBaseAllocator::CBaseAllocator(CHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
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)
|
|
{
|
|
|
|
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.
|
|
|
|
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, 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(
|
|
ALLOCATOR_PROPERTIES* pRequest,
|
|
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(
|
|
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(IMediaSample **ppBuffer,
|
|
REFERENCE_TIME *pStartTime,
|
|
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;
|
|
|
|
|
|
return NOERROR;
|
|
}
|
|
|
|
|
|
/* Final release of a CMediaSample will call this */
|
|
|
|
STDMETHODIMP
|
|
CBaseAllocator::ReleaseBuffer(IMediaSample * pSample)
|
|
{
|
|
CheckPointer(pSample,E_POINTER);
|
|
ValidateReadPtr(pSample,sizeof(IMediaSample));
|
|
|
|
|
|
|
|
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(
|
|
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
|
|
NotifySample();
|
|
}
|
|
|
|
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(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(LPUNKNOWN pUnk, HRESULT *phr)
|
|
{
|
|
CUnknown *pUnkRet = new CMemAllocator(NAME("CMemAllocator"), pUnk, phr);
|
|
return pUnkRet;
|
|
}
|
|
|
|
CMemAllocator::CMemAllocator(
|
|
TCHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
HRESULT *phr)
|
|
: CBaseAllocator(pName, pUnk, phr, TRUE, TRUE),
|
|
m_pBuffer(NULL)
|
|
{
|
|
}
|
|
|
|
#ifdef UNICODE
|
|
CMemAllocator::CMemAllocator(
|
|
CHAR *pName,
|
|
LPUNKNOWN pUnk,
|
|
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(
|
|
ALLOCATOR_PROPERTIES* pRequest,
|
|
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();
|
|
}
|
|
|
|
/* Compute the aligned size */
|
|
LONG lAlignedSize = m_lSize + m_lPrefix;
|
|
if (m_lAlignment > 1) {
|
|
LONG lRemainder = lAlignedSize % m_lAlignment;
|
|
if (lRemainder != 0) {
|
|
lAlignedSize += (m_lAlignment - lRemainder);
|
|
}
|
|
}
|
|
|
|
/* Create the contiguous memory block for the samples
|
|
making sure it's properly aligned (64K should be enough!)
|
|
*/
|
|
ASSERT(lAlignedSize % m_lAlignment == 0);
|
|
|
|
m_pBuffer = (PBYTE)VirtualAlloc(NULL,
|
|
m_lCount * lAlignedSize,
|
|
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)
|
|
|