/* Copyright (c) 1998-1999 Microsoft Corporation */ #ifndef __MEDIA_TERMINAL_FILTER__ #define __MEDIA_TERMINAL_FILTER__ // include header files for the amovie types #include "Stream.h" #include "Sample.h" // number of internal buffers allocated by default // (for write terminal) const DWORD DEFAULT_AM_MST_NUM_BUFFERS = 5; // while this is a LONG, it should actually be a positive value that'll // fit in a LONG (the buffer size and data size variables of the sample) // are LONG, so this is long as well const LONG DEFAULT_AM_MST_SAMPLE_SIZE = 640; // alignment of buffers allocated const LONG DEFAULT_AM_MST_BUFFER_ALIGNMENT = 1; // number of prefix bytes in buffers allocated const LONG DEFAULT_AM_MST_BUFFER_PREFIX = 0; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // // CNBQueue // // Non blocking version of active movie queue class. Very basic Q built // entirely on Win32. // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// template class CNBQueue { private: HANDLE hSemPut; // Semaphore controlling queue "putting" HANDLE hSemGet; // Semaphore controlling queue "getting" CRITICAL_SECTION CritSect; // Thread seriallization int nMax; // Max objects allowed in queue int iNextPut; // Array index of next "PutMsg" int iNextGet; // Array index of next "GetMsg" T **QueueObjects; // Array of objects (ptr's to void) public: BOOL InitializeQ(int n) { LOG((MSP_TRACE, "CNBQueue::InitializeQ[%p] - enter", this)); // // the argument had better be valid // if (0 > n) { TM_ASSERT(FALSE); return FALSE; } if (QueueObjects != NULL) { // // already initialized. this is a bug. // TM_ASSERT(FALSE); return FALSE; } iNextPut = 0; iNextGet = 0; // // attempt to create critical section // try { InitializeCriticalSection(&CritSect); } catch(...) { // // failed to create critical section // LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to initialize critical section")); return FALSE; } // // attempt to create a semaphore // TCHAR *ptczSemaphoreName = NULL; #if DBG // // in debug build, use named semaphores. // TCHAR tszPutSemaphoreName[MAX_PATH]; _stprintf(tszPutSemaphoreName, _T("CNBQueuePutSemaphore_pid[0x%lx]_CNBQueue[%p]_"), GetCurrentProcessId(), this); LOG((MSP_TRACE, "CNBQueue::InitializeQ - creating put semaphore [%S]", tszPutSemaphoreName)); ptczSemaphoreName = &tszPutSemaphoreName[0]; #endif hSemPut = CreateSemaphore(NULL, n, n, ptczSemaphoreName); if (NULL == hSemPut) { // // cleanup and exit // DeleteCriticalSection(&CritSect); LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to create put semaphore")); return FALSE; } #if DBG // // in debug build, use named semaphores. // TCHAR tszGetSemaphoreName[MAX_PATH]; _stprintf(tszGetSemaphoreName, _T("CNBQueueGetSemaphore_pid[0x%lx]_CNBQueue[%p]_"), GetCurrentProcessId(), this); LOG((MSP_TRACE, "CNBQueue::InitializeQ - creating get semaphore [%S]", tszGetSemaphoreName)); ptczSemaphoreName = &tszGetSemaphoreName[0]; #endif hSemGet = CreateSemaphore(NULL, 0, n, ptczSemaphoreName); if (NULL == hSemGet) { // // cleanup and exit // CloseHandle(hSemPut); hSemPut = NULL; DeleteCriticalSection(&CritSect); LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to create get semaphore")); return FALSE; } // // attempt to allocate queue // QueueObjects = new T*[n]; if (NULL == QueueObjects) { // // cleanup and exit // CloseHandle(hSemPut); hSemPut = NULL; CloseHandle(hSemGet); hSemGet = NULL; DeleteCriticalSection(&CritSect); LOG((MSP_ERROR, "CNBQueue::InitializeQ - failed to allocate queue objects")); return FALSE; } nMax = n; LOG((MSP_TRACE, "CNBQueue::InitializeQ - exit")); return TRUE; } void ShutdownQ() { // // QueueObjects also doubles as "Object Initialized" flag // // if object is initialized, _all_ its resource data members must // be released // if (NULL != QueueObjects) { delete [] QueueObjects; QueueObjects = NULL; DeleteCriticalSection(&CritSect); CloseHandle(hSemPut); hSemPut = NULL; CloseHandle(hSemGet); hSemGet = NULL; } } public: CNBQueue() : QueueObjects(NULL), hSemPut(NULL), hSemGet(NULL), iNextPut(0), iNextGet(0), nMax(0) {} ~CNBQueue() { // // deallocate resources if needed // ShutdownQ(); } T *DeQueue(BOOL fBlock = TRUE) { if (NULL == QueueObjects) { // // the queue is not initialized // return NULL; } // // block as needed // if (fBlock) { DWORD dwr = WaitForSingleObject(hSemGet, INFINITE); if ( WAIT_OBJECT_0 != dwr) { // // something's wrong // return NULL; } } else { // // Check for something on the queue but don't wait. If there // is nothing in the queue then we'll let the caller deal with // it. // DWORD dwr = WaitForSingleObject(hSemGet, 0); if (dwr == WAIT_TIMEOUT) { return NULL; } } // // get an object from the queue // EnterCriticalSection(&CritSect); int iSlot = iNextGet++ % nMax; T *pObject = QueueObjects[iSlot]; LeaveCriticalSection(&CritSect); // Release anyone waiting to put an object onto our queue as there // is now space available in the queue. // ReleaseSemaphore(hSemPut, 1L, NULL); return pObject; } BOOL EnQueue(T *pObject) { if (NULL == QueueObjects) { // // the queue is not initialized // return FALSE; } // Wait for someone to get something from our queue, returns straight // away is there is already an empty slot on the queue. // DWORD dwr = WaitForSingleObject(hSemPut, INFINITE); if ( WAIT_OBJECT_0 != dwr) { // // something's wrong // return FALSE; } EnterCriticalSection(&CritSect); int iSlot = iNextPut++ % nMax; QueueObjects[iSlot] = pObject; LeaveCriticalSection(&CritSect); // Release anyone waiting to remove an object from our queue as there // is now an object available to be removed. // ReleaseSemaphore(hSemGet, 1L, NULL); return TRUE; } }; /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // // define class CTMStreamSample - this is used by CMediaTerminalFilter // currently, the actual buffer used by the sample is created dynamically on // the heap and when the sample is destroyed the buffer is also destroyed // this may be changed to using a fixed size buffer pool in future // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// class CTMStreamSample : public CSample { friend class CMediaTerminalFilter; public: inline CTMStreamSample(); // needs to be virtual, or the derived classes' destructor may not // be called when a CTMStreamSample * is deleted virtual ~CTMStreamSample() {} // calls CSample::InitSample(pStream, bIsInternalSample) // sets member variables HRESULT Init( CStream &Stream, bool bIsInternalSample, PBYTE pBuffer, LONG BufferSize ); inline void SetBufferInfo( DWORD BufferSize, BYTE *pBuffer, DWORD DataSize ); inline void GetBufferInfo( DWORD &BufferSize, BYTE *&pBuffer, DWORD &DataSize ); // copy the contents of the src media sample into this instance // CSample::CopyFrom doesn't set time (start/stop) valid flags // this fixes the problem. void CopyFrom( IN IMediaSample *pSrcMediaSample ); protected: PBYTE m_pBuffer; LONG m_BufferSize; LONG m_DataSize; private: // Methods forwarded from MediaSample object. HRESULT MSCallback_GetPointer(BYTE ** ppBuffer) { *ppBuffer = m_pBuffer; return NOERROR; } LONG MSCallback_GetSize(void) { return m_BufferSize; } LONG MSCallback_GetActualDataLength(void) { return m_DataSize; } HRESULT MSCallback_SetActualDataLength(LONG lActual) { if (lActual <= m_BufferSize) { m_DataSize = lActual; return NOERROR; } return E_INVALIDARG; }; }; inline CTMStreamSample::CTMStreamSample( ) : m_pBuffer(NULL), m_BufferSize(0), m_DataSize(0) { } inline void CTMStreamSample::SetBufferInfo( DWORD BufferSize, BYTE *pBuffer, DWORD DataSize ) { m_BufferSize = BufferSize; m_pBuffer = pBuffer; m_DataSize = DataSize; } inline void CTMStreamSample::GetBufferInfo( DWORD &BufferSize, BYTE *&pBuffer, DWORD &DataSize ) { BufferSize = m_BufferSize; pBuffer = m_pBuffer; DataSize = m_DataSize; } class CQueueMediaSample : public CTMStreamSample { public: inline CQueueMediaSample(); #if DBG virtual ~CQueueMediaSample(); #endif // DBG // calls CTMStreamSample::Init, sets members HRESULT Init( IN CStream &pStream, IN CNBQueue &pQueue ); void HoldFragment( IN DWORD FragSize, IN BYTE *pbData, IN IMediaSample &FragMediaSample ); inline DWORD GetDataSize() { return m_DataSize; } protected: // pointer to a queue that contains us! CNBQueue *m_pSampleQueue; // ptr to the sample being fragmented CComPtr m_pFragMediaSample; // Overridden to provide different behavior void FinalMediaSampleRelease(); }; inline CQueueMediaSample::CQueueMediaSample( ) : m_pSampleQueue(NULL) { } class CUserMediaSample : protected CTMStreamSample, public IMemoryData, public ITAMMediaFormat { public: BEGIN_COM_MAP(CUserMediaSample) COM_INTERFACE_ENTRY2(IUnknown, IStreamSample) COM_INTERFACE_ENTRY(IStreamSample) COM_INTERFACE_ENTRY(IMemoryData) COM_INTERFACE_ENTRY(ITAMMediaFormat) COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pFTM) END_COM_MAP() inline CUserMediaSample(); virtual ~CUserMediaSample(); // if asked to allocate buffers, verify allocator properties static BOOL VerifyAllocatorProperties( IN BOOL bAllocateBuffers, IN const ALLOCATOR_PROPERTIES &AllocProps ); // calls CTMStreamSample::Init, sets members HRESULT Init( IN CStream &Stream, IN BOOL bAllocateBuffer, IN DWORD ReqdBufferSize, IN const ALLOCATOR_PROPERTIES &AllocProps ); void BeginFragment( IN BOOL bNoteCurrentTime ); // assign fragment to CQueueMediaSample void Fragment( IN BOOL bFragment, IN LONG AllocBufferSize, IN OUT CQueueMediaSample &QueueMediaSample, OUT BOOL &bDone ); // copy fragment to downstream allocator's IMediaSample HRESULT CopyFragment( IN BOOL bFragment, IN LONG AllocBufferSize, IN OUT IMediaSample * pDestMediaSample, OUT BOOL & bDone ); // computes the time to wait. it checks the time at which the last // fragmented byte would be due and determines the time to wait using // the time delay since the beginning of fragmentation DWORD GetTimeToWait( IN DOUBLE DelayPerByte ); // when we are decommitted/aborted while being fragmented, we // need to get rid of our refcnt on internal IMediaSample and set // the error code to E_ABORT. this will be signaled to the user // only when the last refcnt on IMediaSample is released // (possibly by an outstanding queue sample) void AbortDuringFragmentation(); // copy the contents of the src media sample into this instance HRESULT CopyFrom( IN IMediaSample *pSrcMediaSample ); HRESULT CopyFrom( IN IMediaSample *pSrcMediaSample, IN OUT BYTE *&pBuffer, IN OUT LONG &DataLength ); // over-ridden to check if the instance is committed before // adding the sample to the CStream buffer pool virtual HRESULT SetCompletionStatus(HRESULT hrCompletionStatus); // IStreamSample // this method is over-ridden from the base class so that we can // decrement the refcnt on a sample if stealing it from the CStream // free buffer pool is successful STDMETHODIMP CompletionStatus( IN DWORD dwFlags, IN /* [optional] */ DWORD dwMilliseconds ); // IMemoryData STDMETHOD(SetBuffer)( IN DWORD cbSize, IN BYTE * pbData, IN DWORD dwFlags ); STDMETHOD(GetInfo)( OUT DWORD *pdwLength, OUT BYTE **ppbData, OUT DWORD *pcbActualData ); STDMETHOD(SetActual)( IN DWORD cbDataValid ); // ITAMMediaFormat // redirect this call to ((CMediaTerminalFilter *)m_pStream) STDMETHOD(get_MediaFormat)( OUT /* [optional] */ AM_MEDIA_TYPE **ppFormat ); // this is not allowed STDMETHOD(put_MediaFormat)( IN const AM_MEDIA_TYPE *pFormat ); protected: // marshaller IUnknown *m_pFTM; // TRUE if we allocated the buffer (then, we need to destroy it too) BOOL m_bWeAllocatedBuffer; // time at which BeginFragment was called (value returned // by timeGetTime) DWORD m_BeginFragmentTime; // these many bytes of the buffer have already been fragmented LONG m_NumBytesFragmented; // TRUE if being fragmented BOOL m_bBeingFragmented; // size of the buffer that the application will have to provide, if app // does its own memory allocation DWORD m_dwRequiredBufferSize; // this calls the base class FinalMediaSampleRelease and // then releases reference to self obtained in BeginFragment virtual void FinalMediaSampleRelease(); private: virtual HRESULT InternalUpdate( DWORD dwFlags, HANDLE hEvent, PAPCFUNC pfnAPC, DWORD_PTR dwptrAPCData ); }; inline CUserMediaSample::CUserMediaSample( ) : m_bWeAllocatedBuffer(FALSE), m_NumBytesFragmented(0), m_bBeingFragmented(FALSE), m_BeginFragmentTime(0), m_dwRequiredBufferSize(0) { // can fail CoCreateFreeThreadedMarshaler( GetControllingUnknown(), &m_pFTM ); } inline CUserMediaSample::~CUserMediaSample( ) { if (m_bWeAllocatedBuffer) { if (NULL != m_pBuffer) { delete m_pBuffer; } } // if there is an outstanding APC call and the user handle // (the targe thread handle) has not been closed, close it if ((NULL != m_UserAPC) && (NULL != m_hUserHandle)) { CloseHandle(m_hUserHandle); } if (NULL != m_pFTM) { m_pFTM->Release(); m_pFTM = NULL; } } /* The media stream terminal filter */ // uses class CMediaPumpPool class CMediaPumpPool; // friend class CMediaTerminal; class CMediaTerminalFilter : public CStream, public ITAllocatorProperties { friend CMediaTerminal; public: DECLARE_AGGREGATABLE(CMediaTerminalFilter) DECLARE_GET_CONTROLLING_UNKNOWN() BEGIN_COM_MAP(CMediaTerminalFilter) COM_INTERFACE_ENTRY(ITAllocatorProperties) COM_INTERFACE_ENTRY_CHAIN(CStream) END_COM_MAP() // set the member variables inline CMediaTerminalFilter(); virtual ~CMediaTerminalFilter(); // calls the IAMMediaStream::Initialize(NULL, 0, PurposeId, StreamType), // sets certain member variables // ex. m_pAmovieMajorType virtual HRESULT Init( IN REFMSPID PurposeId, IN const STREAM_TYPE StreamType, IN const GUID &AmovieMajorType ); // the thread pump calls the filter back during the registration // to tell it that registration succeeded and that the pump will be // waiting on the m_hWaitFreeSem handle HRESULT SignalRegisteredAtPump(); // this method only makes sense for a write terminal and is used by CMediaPump // to obtain a filled buffer for passing downstream virtual HRESULT GetFilledBuffer( OUT IMediaSample *&pMediaSample, OUT DWORD &WaitTime ); // the caller is supposed to call DeleteMediaType(*ppmt) (on success) HRESULT GetFormat( OUT AM_MEDIA_TYPE **ppmt ); // This method can only be called after initialization when the stream // is not connected. It can only be called if the stream is writeable. // it is used in writeable filters to set the media format to negotiate // when connected to the filter graph. HRESULT SetFormat( IN AM_MEDIA_TYPE *pmt ); // checks if the filter is committed before adding the sample // to the CStream buffer pool HRESULT AddToPoolIfCommitted( IN CSample *pSample ); // first check if this sample is the one being fragmented currently, // then check the free pool BOOL StealSample( IN CSample *pSample ); // ITAllocatorProperties - // exposes the allocator properties of the Media Streaming Terminal // (MST) to a user. A user only needs to use this interface when he // needs to use his own buffers or needs to operate with a fixed set // of samples // this method may only be called before connection and will // force the MST to use these values during filter negotiation // if the connecting filter doesn't accept these, the connection // shall not be established STDMETHOD(SetAllocatorProperties)( IN ALLOCATOR_PROPERTIES *pAllocProperties ); // gets current values for the allocator properties // after connection, this provides the negotiated values // it is invalid before connection. The MST will accept // any values suggested by the filters it connects to STDMETHOD(GetAllocatorProperties)( OUT ALLOCATOR_PROPERTIES *pAllocProperties ); // TRUE by default. when set to FALSE, the sample allocated // by the MST don't have any buffers and they must be supplied // before Update is called on the samples STDMETHOD(SetAllocateBuffers)( IN BOOL bAllocBuffers ); // returns the current value of this boolean configuration parameter STDMETHOD(GetAllocateBuffers)( OUT BOOL *pbAllocBuffers ); // this size is used for allocating buffers when AllocateSample is // called. this is only valid when we have been told to allocate buffers STDMETHOD(SetBufferSize)( IN DWORD BufferSize ); // returns the value used to allocate buffers when AllocateSample is // called. this is only valid when we have been told to allocate buffers STDMETHOD(GetBufferSize)( OUT DWORD *pBufferSize ); // over-ridden base class methods // CStream // IAMMediaStream // over-ride this to return failure. we don't allow it to join a multi-media // stream because the multi-media stream thinks it owns the stream STDMETHOD(JoinAMMultiMediaStream)( IN IAMMultiMediaStream *pAMMultiMediaStream ); // over-ride this to return failure if the filter is anything other than the internally // created filter. The internally created media stream filter has only one IAMMediaStream // (this one) in it STDMETHOD(JoinFilter)( IN IMediaStreamFilter *pMediaStreamFilter ); STDMETHOD(AllocateSample)( IN DWORD dwFlags, OUT IStreamSample **ppSample ); STDMETHOD(CreateSharedSample)( IN IStreamSample *pExistingSample, IN DWORD dwFlags, OUT IStreamSample **ppNewSample ); STDMETHOD(SetSameFormat)( IN IMediaStream *pStream, IN DWORD dwFlags ); // CStream over-ride - this method had to be replaced because // of references to CPump which itself is being replaced by CMediaPump STDMETHODIMP SetState( IN FILTER_STATE State ); // CStream - end // IMemInputPin STDMETHOD(GetAllocatorRequirements)( IN ALLOCATOR_PROPERTIES*pProps ); STDMETHOD(Receive)( IN IMediaSample *pSample ); // supports IAMBufferNegotiation interface on TERMINAL // this is necessary because ITAllocatorProperties also // has an identical GetAllocatorProperties method! STDMETHOD(SuggestAllocatorProperties)( IN const ALLOCATOR_PROPERTIES *pProperties ); // IMemAllocator STDMETHOD(GetBuffer)(IMediaSample **ppBuffer, REFERENCE_TIME * pStartTime, REFERENCE_TIME * pEndTime, DWORD dwFlags); // ** figure out what needs to be done for this allocator interface // since the number of buffers that can be created is unbounded STDMETHOD(SetProperties)(ALLOCATOR_PROPERTIES* pRequest, ALLOCATOR_PROPERTIES* pActual); STDMETHOD(GetProperties)(ALLOCATOR_PROPERTIES* pProps); STDMETHOD(Commit)(); STDMETHOD(Decommit)(); // IPin STDMETHOD(Connect)(IPin * pReceivePin, const AM_MEDIA_TYPE *pmt); STDMETHOD(ReceiveConnection)(IPin * pConnector, const AM_MEDIA_TYPE *pmt); // the base class implementation doesn't validate the parameter STDMETHOD(ConnectionMediaType)(AM_MEDIA_TYPE *pmt); // should accept all media types which match the major type corresponding to the purpose id STDMETHOD(QueryAccept)(const AM_MEDIA_TYPE *pmt); // over-ridden from CStream to set the end of stream flag to false // this is done instead of setting it in Connect and ReceiveConnection STDMETHODIMP Disconnect(); // // this is called by media pump when it has a sample for us to process // STDMETHODIMP ProcessSample(IMediaSample *pSample); protected: // last sample ended at this (calculated) time REFERENCE_TIME m_rtLastSampleEndedAt; // // calculated duration of the sample that was last submitted // REFERENCE_TIME m_rtLastSampleDuration; // // real (measured) time of submission of the last sample // REFERENCE_TIME m_rtRealTimeOfLastSample; // flag to check if this is an audio filter, the CStream member // m_PurposeId is a guiid and this just provides a less expensive // way of checking the same thing BOOL m_bIsAudio; // contains the samples that will be passed to downstream filters. CNBQueue m_SampleQueue; // These datamembers provide some fragmentation support // for buffers going downstream CUserMediaSample *m_pSampleBeingFragmented; // flag for allocating buffers for samples when AllocateSample is // called. Its TRUE by default, but the user can set it before // connection BOOL m_bAllocateBuffers; // size of buffers to allocate in AllocateSample if m_bAllocateBuffers // is TRUE. if this isn't set (i.e. set to 0), the negotiated // allocator properties buffer size is used in its place DWORD m_AllocateSampleBufferSize; // FALSE by default. This is set to TRUE if the user specifies // allocator properties for them to see. // (we used to insist on our own allocator properties when this // was TRUE, but now this just means that we need to translate // between disjoint buffer sizes if needed) BOOL m_bUserAllocProps; ALLOCATOR_PROPERTIES m_UserAllocProps; // allocator properties negotiated -- if none suggested (by msp) and // none requested by user, we use whatever the other filter has BOOL m_bSuggestedAllocProps; ALLOCATOR_PROPERTIES m_AllocProps; // per byte delay for audio samples - only valid for write filter DOUBLE m_AudioDelayPerByte; // per frame delay for video samples - only valid for write filter DWORD m_VideoDelayPerFrame; // the filter restricts the acceptable media types to those that match the major type // this corresponds to the purpose id of the IAMMediaStream (set in Init) const GUID *m_pAmovieMajorType; // this is the media type suggested by a user of the terminal // it is only valid for writeable streams if put_MediaType was called // (i.e. not valid for readable streams) // this needs to be freed in the destructor // cstream - cbaseterm gets\sets it through methods AM_MEDIA_TYPE *m_pSuggestedMediaType; // this pump replaces the CStream related implementation of CPump // CPump uses a separate thread for each write terminal, // it uses IMemAllocator::GetBuffer to get a user written media // sample (for passing on downstream). This method should only be // used to get the next free buffer to write into. // CStream methods // this is only used during Connect and ReceiveConnect to supply the optional media type // since we over-ride Connect and ReceiveConnection methods, this should never get called virtual HRESULT GetMediaType(ULONG Index, AM_MEDIA_TYPE **ppMediaType); // others // sets the time to delay - per byte for audio, per frame for video void GetTimingInfo( IN const AM_MEDIA_TYPE &MediaType ); // timestamps the sample HRESULT SetTime( IN IMediaSample *pMediaSample ); // set discontinuity flag on the sample it the sample came too late -- we // assume that if the application stopped feeding mst with data, this is // because there was a gap in the actual data flow HRESULT SetDiscontinuityIfNeeded( IN IMediaSample *pMediaSample ); // set the default allocator properties void SetDefaultAllocatorProperties(); // // Helper methods for GetFilledBuffer. // virtual HRESULT FillDownstreamAllocatorBuffer( OUT IMediaSample *& pMediaSample, OUT DWORD & WaitTime, OUT BOOL * pfDone ); virtual HRESULT FillMyBuffer( OUT IMediaSample *& pMediaSample, OUT DWORD & WaitTime, OUT BOOL * pfDone ); private : // this is a weak reference and should not be a CComPtr // this tells us that we should only accept this media stream filter // when a non-null value is proposed in JoinFilter IMediaStreamFilter *m_pMediaStreamFilterToAccept; // sets the media stream filter that may be acceptable inline void SetMediaStreamFilter( IN IMediaStreamFilter *pMediaStreamFilter ) { m_pMediaStreamFilterToAccept = pMediaStreamFilter; } public: // implements single thread pump for all write terminal filters // it uses GetFilledBuffer to obtain filled samples to write downstream // and to detect when to remove this filter from its list of filters to // service // ZoltanS: must be public so we can access it in DllMain // ZoltanS: no longer single thread pump; it is a wrapper which delegated // to one or more single thread pumps static CMediaPumpPool ms_MediaPumpPool; }; // set the member variables inline CMediaTerminalFilter::CMediaTerminalFilter( ) : m_bIsAudio(TRUE), m_bAllocateBuffers(TRUE), m_AllocateSampleBufferSize(0), m_bUserAllocProps(FALSE), m_bSuggestedAllocProps(FALSE), m_AudioDelayPerByte(0), m_VideoDelayPerFrame(0), m_pAmovieMajorType(NULL), m_pSuggestedMediaType(NULL), m_pSampleBeingFragmented(NULL), m_pMediaStreamFilterToAccept(NULL), m_rtLastSampleEndedAt(0), m_rtLastSampleDuration(0), m_rtRealTimeOfLastSample(0) { } #endif // __MEDIA_TERMINAL_FILTER__