Filter组件开发中的SDK基类分析
转载请标明是引用于 http://blog.****.net/chenyujing1234
参考书<<DirectShow开发指南>>
DirectShow SDK提供了一套开发Filter的基类源代码。基于这些基类开发Filter将大大简化开发过程。
1、CBaseObject
大部分SDK类都从CBaseObject类(参见combase.h)中继承而来的。
class CBaseObject { private: // Disable the copy constructor and assignment by default so you will get // compiler errors instead of unexpected behaviour if you pass objects // by value or assign objects. CBaseObject(const CBaseObject& objectSrc); // no implementation void operator=(const CBaseObject& objectSrc); // no implementation private: static LONG m_cObjects; /* Total number of objects active */ protected: #ifdef DEBUG DWORD m_dwCookie; /* Cookie identifying this object */ #endif public: /* These increment and decrement the number of active objects */ CBaseObject(const TCHAR *pName); #ifdef UNICODE CBaseObject(const char *pName); #endif ~CBaseObject(); /* Call this to find if there are any CUnknown derived objects active */ static LONG ObjectsActive() { return m_cObjects; }; };
2、 CUnknown
作为COM组件(参见combase.cpp文件),最基本的当然是IUnknown接口的实现。SDK提供了CUnknown类,SDK实现了COM接口类都是直接或间接从这个类继承来的。
class AM_NOVTABLE CUnknown : public INonDelegatingUnknown, public CBaseObject { private: const LPUNKNOWN m_pUnknown; /* Owner of this object */ protected: /* So we can override NonDelegatingRelease() */ volatile LONG m_cRef; /* Number of reference counts */ public: CUnknown(const TCHAR *pName, LPUNKNOWN pUnk); virtual ~CUnknown() {}; // This is redundant, just use the other constructor // as we never touch the HRESULT in this anyway CUnknown(TCHAR *pName, LPUNKNOWN pUnk,HRESULT *phr); #ifdef UNICODE CUnknown(const char *pName, LPUNKNOWN pUnk); CUnknown(char *pName, LPUNKNOWN pUnk,HRESULT *phr); #endif /* Return the owner of this object */ LPUNKNOWN GetOwner() const { return m_pUnknown; }; /* Called from the class factory to create a new instance, it is pure virtual so it must be overriden in your derived class */ /* static CUnknown *CreateInstance(LPUNKNOWN, HRESULT *) */ /* Non delegating unknown implementation */ STDMETHODIMP NonDelegatingQueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) NonDelegatingAddRef(); STDMETHODIMP_(ULONG) NonDelegatingRelease(); };CUnknown类从CBaseObject中继承而来,另外CUnknown类还实现了INonDelegatingUnknown接口,用于支持引用计数、接口查询、COM组件“聚合”等。
CUnknown类的使用方法如下:
(1) 从CUnknown派生一个子类,并在子类的public区加入DECLARE_IUNKNOWN宏;
(2) 重写NonDelegatingQueryInterface函数,用以支持IUnknown外的其他接口;
(3) 在子类的构造函数中调用CUnknown的构造函数。
eg:
class CSeekingPassThru : public ISeekingPassThru, public CUnknown { public: static CUnknown *CreateInstance(LPUNKNOWN pUnk, HRESULT *phr); CSeekingPassThru(TCHAR *pName, LPUNKNOWN pUnk, HRESULT *phr); ~CSeekingPassThru(); DECLARE_IUNKNOWN; STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv); STDMETHODIMP Init(BOOL bSupportRendering, IPin *pPin); private: CPosPassThru *m_pPosPassThru; };
3、 CBaseFilter
最基本的Filter由CBaseFilter 类(参见amfilter.cpp)实现。
作为Filter的基本特征,CBaseFilter实现了IBaseFilter接口(IbaseFilter从IMediaFilter继承而来)。
同时CBaseFilter还实现了Filter框架(描述了各个Pin组件的情况)。
CBaseFilter类的使用方法如下:
(1) 声明一个新类是从CBaseFilter中继承而来;
(2) 在新类中定义Filter上的Pin的实例(Pin从CBasePin类继承而来);
(3) 实现纯虚函数CBaseFilter::GetPin,用于返回Filter上各个Pin的对象指针;
(4) 实现纯虚函数CBaseFilter::GetPinCount,用于返回Filter上Pin 的数量;
(5) 考虑如何处理从输入Pin进来的Sample数据。
eg:
// // The filter object itself. Supports IBaseFilter through // CBaseFilter and also IFileSourceFilter directly in this object // CAsyncReader类实现了一个Filter,它从CBaseFilter派生,实现了仅含一个输出 // Pin(CAsyncOutputPin类的实例)的Source filter框架。 class CAsyncReader : public CBaseFilter { protected: // filter-wide lock CCritSec m_csFilter; // all i/o done here CAsyncIo m_Io; // (2)在新类中定义Filter上的Pin的实例(Pin从CBasePin类继承而来); // our output pin CAsyncOutputPin m_OutputPin; // Type we think our data is CMediaType m_mt; public: // construction / destruction CAsyncReader( TCHAR *pName, LPUNKNOWN pUnk, CAsyncStream *pStream, // 它是Filter获取数据的源 HRESULT *phr); ~CAsyncReader(); //(3) 实现纯虚函数CBaseFilter::GetPin,用于返回Filter上各个Pin的对象指针; //(4) 实现纯虚函数CBaseFilter::GetPinCount,用于返回Filter上Pin 的数量; int GetPinCount(); CBasePin *GetPin(int n); // --- Access our media type const CMediaType *LoadType() const { return &m_mt; } virtual HRESULT Connect( IPin * pReceivePin, const AM_MEDIA_TYPE *pmt // optional media type ) { return m_OutputPin.CBasePin::Connect(pReceivePin, pmt); } };
还有SDK类的CSource、CBaseRenderer、 CTracsformFilter都是从CBaseFilter继承来的,实现开发Filter时,
使用这些子类作为Filter类。
4、CBasePin
Filter 上最基本的Pin由CBasePin类(参见 amfilter.h)实现。
作为Pin的基本特征,CBasePin实现了IPin接口。CBasePin设计了Pin 的整个连接过程。
另外,这个类还实现了IQualityControl接口,该接口用于质量控制。
class AM_NOVTABLE CBasePin : public CUnknown, public IPin, public IQualityControl { protected: WCHAR * m_pName; // This pin's name IPin *m_Connected; // Pin we have connected to PIN_DIRECTION m_dir; // Direction of this pin CCritSec *m_pLock; // Object we use for locking bool m_bRunTimeError; // Run time error generated bool m_bCanReconnectWhenActive; // OK to reconnect when active bool m_bTryMyTypesFirst; // When connecting enumerate // this pin's types first CBaseFilter *m_pFilter; // Filter we were created by IQualityControl *m_pQSink; // Target for Quality messages LONG m_TypeVersion; // Holds current type version CMediaType m_mt; // Media type of connection CRefTime m_tStart; // time from NewSegment call CRefTime m_tStop; // time from NewSegment double m_dRate; // rate from NewSegment #ifdef DEBUG LONG m_cRef; // Ref count tracing #endif // displays pin connection information #ifdef DEBUG void DisplayPinInfo(IPin *pReceivePin); void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt); #else void DisplayPinInfo(IPin *pReceivePin) {}; void DisplayTypeInfo(IPin *pPin, const CMediaType *pmt) {}; #endif // used to agree a media type for a pin connection // given a specific media type, attempt a connection (includes // checking that the type is acceptable to this pin) HRESULT AttemptConnection( IPin* pReceivePin, // connect to this pin const CMediaType* pmt // using this type ); // try all the media types in this enumerator - for each that // we accept, try to connect using ReceiveConnection. HRESULT TryMediaTypes( IPin *pReceivePin, // connect to this pin const CMediaType *pmt, // proposed type from Connect IEnumMediaTypes *pEnum); // try this enumerator // establish a connection with a suitable mediatype. Needs to // propose a media type if the pmt pointer is null or partially // specified - use TryMediaTypes on both our and then the other pin's // enumerator until we find one that works. HRESULT AgreeMediaType( IPin *pReceivePin, // connect to this pin const CMediaType *pmt); // proposed type from Connect public: CBasePin( TCHAR *pObjectName, // Object description CBaseFilter *pFilter, // Owning filter who knows about pins CCritSec *pLock, // Object who implements the lock HRESULT *phr, // General OLE return code LPCWSTR pName, // Pin name for us PIN_DIRECTION dir); // Either PINDIR_INPUT or PINDIR_OUTPUT #ifdef UNICODE CBasePin( CHAR *pObjectName, // Object description CBaseFilter *pFilter, // Owning filter who knows about pins CCritSec *pLock, // Object who implements the lock HRESULT *phr, // General OLE return code LPCWSTR pName, // Pin name for us PIN_DIRECTION dir); // Either PINDIR_INPUT or PINDIR_OUTPUT #endif virtual ~CBasePin(); DECLARE_IUNKNOWN STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void ** ppv); STDMETHODIMP_(ULONG) NonDelegatingRelease(); STDMETHODIMP_(ULONG) NonDelegatingAddRef(); // --- IPin methods --- // take lead role in establishing a connection. Media type pointer // may be null, or may point to partially-specified mediatype // (subtype or format type may be GUID_NULL). STDMETHODIMP Connect( IPin * pReceivePin, const AM_MEDIA_TYPE *pmt // optional media type ); // (passive) accept a connection from another pin STDMETHODIMP ReceiveConnection( IPin * pConnector, // this is the initiating connecting pin const AM_MEDIA_TYPE *pmt // this is the media type we will exchange ); STDMETHODIMP Disconnect(); STDMETHODIMP ConnectedTo(IPin **pPin); STDMETHODIMP ConnectionMediaType(AM_MEDIA_TYPE *pmt); STDMETHODIMP QueryPinInfo( PIN_INFO * pInfo ); STDMETHODIMP QueryDirection( PIN_DIRECTION * pPinDir ); STDMETHODIMP QueryId( LPWSTR * Id ); // does the pin support this media type STDMETHODIMP QueryAccept( const AM_MEDIA_TYPE *pmt ); // return an enumerator for this pins preferred media types STDMETHODIMP EnumMediaTypes( IEnumMediaTypes **ppEnum ); // return an array of IPin* - the pins that this pin internally connects to // All pins put in the array must be AddReffed (but no others) // Errors: "Can't say" - FAIL, not enough slots - return S_FALSE // Default: return E_NOTIMPL // The filter graph will interpret NOT_IMPL as any input pin connects to // all visible output pins and vice versa. // apPin can be NULL if nPin==0 (not otherwise). STDMETHODIMP QueryInternalConnections( IPin* *apPin, // array of IPin* ULONG *nPin // on input, the number of slots // on output the number of pins ) { return E_NOTIMPL; } // Called when no more data will be sent STDMETHODIMP EndOfStream(void); // Begin/EndFlush still PURE // 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 NewSegment( REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate); //================================================================================ // IQualityControl methods //================================================================================ STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); STDMETHODIMP SetSink(IQualityControl * piqc); // --- helper methods --- // Returns true if the pin is connected. false otherwise. BOOL IsConnected(void) {return (m_Connected != NULL); }; // Return the pin this is connected to (if any) IPin * GetConnected() { return m_Connected; }; // Check if our filter is currently stopped BOOL IsStopped() { return (m_pFilter->m_State == State_Stopped); }; // find out the current type version (used by enumerators) virtual LONG GetMediaTypeVersion(); void IncrementTypeVersion(); // switch the pin to active (paused or running) mode // not an error to call this if already active virtual HRESULT Active(void); // switch the pin to inactive state - may already be inactive virtual HRESULT Inactive(void); // Notify of Run() from filter virtual HRESULT Run(REFERENCE_TIME tStart); // check if the pin can support this specific proposed type and format virtual HRESULT CheckMediaType(const CMediaType *) PURE; // set the connection to use this format (previously agreed) virtual HRESULT SetMediaType(const CMediaType *); // check that the connection is ok before verifying it // can be overridden eg to check what interfaces will be supported. virtual HRESULT CheckConnect(IPin *); // Set and release resources required for a connection virtual HRESULT BreakConnect(); virtual HRESULT CompleteConnect(IPin *pReceivePin); // returns the preferred formats for a pin virtual HRESULT GetMediaType(int iPosition,CMediaType *pMediaType); // access to NewSegment values REFERENCE_TIME CurrentStopTime() { return m_tStop; } REFERENCE_TIME CurrentStartTime() { return m_tStart; } double CurrentRate() { return m_dRate; } // Access name LPWSTR Name() { return m_pName; }; // Can reconnectwhen active? void SetReconnectWhenActive(bool bCanReconnect) { m_bCanReconnectWhenActive = bCanReconnect; } bool CanReconnectWhenActive() { return m_bCanReconnectWhenActive; } protected: STDMETHODIMP DisconnectInternal(); };
在CBasePin实现的成员函数中,有3个与Filter的状态转换相对应。
我们来看一下Filter的Stop的实现,实际上就是调用Filter的所有pin的Inactive函数
STDMETHODIMP CBaseFilter::Stop() { CAutoLock cObjectLock(m_pLock); HRESULT hr = NOERROR; // 通知所有pin改变状态 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 // 仅在完成连接的pin上调用Inactive函数 // 如果Inactive函数返回一个错误值,则暂时忽略, // 以便所有Pin都有机会被调用Inactive if (pPin->IsConnected()) { HRESULT hrTmp = pPin->Inactive(); if (FAILED(hrTmp) && SUCCEEDED(hr)) { hr = hrTmp; } } } } m_State = State_Stopped; return hr; }在实际开发Filter的过程中,很有可能重写CBasePin::Inactive、 CBasePin::Active和CBasePin::Run这3个函数,以进行必要的初始化、释放资源等。
CBasePin类的使用方法如下:
(1) 从CBasePin派生一个子类;
(2) 实现纯虚函数CBasePIn::CheckMediaType,进行Pin连接时的媒体类型检查;
(3) 实现纯虚函数CBasePin::GetMediaType,提供Pin上的首选媒体类型。
(4) 实现IPin::BeginFlush和IPin::EndFlush两个函数。
(5) 可能需要重写的函数包括
CBasePin::Active() 实现资源分配
CBasePin::Inactive 实现资源释放
CBasePin::Run 在Filter运行前进行一些初始化
CBasePin::CheckConnect 连接时检查,如查询对方Pin上是否支持某个特殊接口
CBasePin::BreakConnect 断开连接,并进行必要的资源释放
CBasePin::CompleteConnect 完成连接时被调用,可以在这个函数中获得当前连接的媒体类型参数
CBasePin::EndOfStream 当上流数据全部传送完毕后被调用。
如果这个是Transform Filter,则将EndOfStream继续入下传送;
如果是Renderer Filter,需要向Filter Graph Manager发送一个EC_COMPLETE事件
CBasePin::Noftify 直接响应质量控制。
eg:
// CAsyncOutputPin实现了一个输出Pin // 继承自IAsyncReader、CBasePin,这是对拉模式的Source Filter的基本要求 /* IAsyncReader接口方法及描述如下: BeginFlush 放弃所有正在进行的数据读取 EndFlush 与BeginFlush配对,标示Flush过程结束 Length 得到数据总长度和当前可以读取的长度 RequestAlloctor 要求一个输入Pin上的Sample管理器 Request 发出一个数据请求 SyncReadAligned 同步读取数据(边界对齐) SyncRead 同步读取数据 WaitForNext 等待一个请求的完成 ====================================================================== 可以看出CAsyOutputPin类上实现的IAsyncReader的各个接口方法,都“委托” 给了CAsyncIo类对象的同名成员函数 */ class CAsyncOutputPin : public IAsyncReader, public CBasePin { protected: CAsyncReader* m_pReader; CAsyncIo * m_pIo; // This is set every time we're asked to return an IAsyncReader // interface // This allows us to know if the downstream pin can use // this transport, otherwise we can hook up to thinks like the // dump filter and nothing happens BOOL m_bQueriedForAsyncReader; HRESULT InitAllocator(IMemAllocator **ppAlloc); public: // constructor and destructor CAsyncOutputPin( HRESULT * phr, CAsyncReader *pReader, CAsyncIo *pIo, CCritSec * pLock); ~CAsyncOutputPin(); // --- CUnknown --- // need to expose IAsyncReader DECLARE_IUNKNOWN STDMETHODIMP NonDelegatingQueryInterface(REFIID, void**); // --- IPin methods --- STDMETHODIMP Connect( IPin * pReceivePin, const AM_MEDIA_TYPE *pmt // optional media type ); // --- CBasePin methods --- // return the types we prefer - this will return the known // file type HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); // can we support this type? HRESULT CheckMediaType(const CMediaType* pType); // Clear the flag so we see if IAsyncReader is queried for HRESULT CheckConnect(IPin *pPin) { m_bQueriedForAsyncReader = FALSE; return CBasePin::CheckConnect(pPin); } // See if it was asked for HRESULT CompleteConnect(IPin *pReceivePin) { if (m_bQueriedForAsyncReader) { return CBasePin::CompleteConnect(pReceivePin); } else { #ifdef VFW_E_NO_TRANSPORT return VFW_E_NO_TRANSPORT; #else return E_FAIL; #endif } } // Remove our connection status HRESULT BreakConnect() { m_bQueriedForAsyncReader = FALSE; return CBasePin::BreakConnect(); } // --- IAsyncReader methods --- // pass in your preferred allocator and your preferred properties. // method returns the actual allocator to be used. Call GetProperties // on returned allocator to learn alignment and prefix etc chosen. // this allocator will be not be committed and decommitted by // the async reader, only by the consumer. STDMETHODIMP RequestAllocator( IMemAllocator* pPreferred, ALLOCATOR_PROPERTIES* pProps, IMemAllocator ** ppActual); // queue a request for data. // media sample start and stop times contain the requested absolute // byte position (start inclusive, stop exclusive). // may fail if sample not obtained from agreed allocator. // may fail if start/stop position does not match agreed alignment. // samples allocated from source pin's allocator may fail // GetPointer until after returning from WaitForNext. STDMETHODIMP Request( IMediaSample* pSample, DWORD_PTR dwUser); // user context // block until the next sample is completed or the timeout occurs. // timeout (millisecs) may be 0 or INFINITE. Samples may not // be delivered in order. If there is a read error of any sort, a // notification will already have been sent by the source filter, // and STDMETHODIMP will be an error. STDMETHODIMP WaitForNext( DWORD dwTimeout, IMediaSample** ppSample, // completed sample DWORD_PTR * pdwUser); // user context // sync read of data. Sample passed in must have been acquired from // the agreed allocator. Start and stop position must be aligned. // equivalent to a Request/WaitForNext pair, but may avoid the // need for a thread on the source filter. STDMETHODIMP SyncReadAligned( IMediaSample* pSample); // sync read. works in stopped state as well as run state. // need not be aligned. Will fail if read is beyond actual total // length. STDMETHODIMP SyncRead( LONGLONG llPosition, // absolute file position LONG lLength, // nr bytes required BYTE* pBuffer); // write data here // return total length of stream, and currently available length. // reads for beyond the available length but within the total length will // normally succeed but may block for a long period. STDMETHODIMP Length( LONGLONG* pTotal, LONGLONG* pAvailable); // cause all outstanding reads to return, possibly with a failure code // (VFW_E_TIMEOUT) indicating they were cancelled. // these are defined on IAsyncReader and IPin STDMETHODIMP BeginFlush(void); STDMETHODIMP EndFlush(void); };
5、 CBaseInputPin和CBaseOutputPin
从CBasePin类派生的,也是很基本的输入或输出pin。
它们的实现可参见 amfilter.cpp
CBaseInputPin实现了IMemInputPin(用于推模式的数据传送),
而CBaseOutputPin主要完成了传送数据所使用的Sample管理器(Allocator)的协商,并重写了CBasePin::Active(用于实际的Sample内存分配)
以及CBasePin::Inactive(用于Sample内存的释放)
CBaseInputPin类的使用方法(派生一个子类,并且至少需要重写以下函数)如下:
(1) CBaseInputPin::BeginFlush
(2) CBaseInputPin::EndFlush
(3) CBaseInputPin::Receive
(4) CBaseInputPin::CheckMediaType
(5) CBaseInputPin::GetMediaType
eg:
class CRendererInputPin : public CBaseInputPin { protected: CBaseRenderer *m_pRenderer; public: CRendererInputPin(CBaseRenderer *pRenderer, HRESULT *phr, LPCWSTR Name); // Overriden from the base pin classes HRESULT BreakConnect(); HRESULT CompleteConnect(IPin *pReceivePin); HRESULT SetMediaType(const CMediaType *pmt); HRESULT CheckMediaType(const CMediaType *pmt); HRESULT Active(); HRESULT Inactive(); // Add rendering behaviour to interface functions STDMETHODIMP QueryId(LPWSTR *Id); STDMETHODIMP EndOfStream(); STDMETHODIMP BeginFlush(); STDMETHODIMP EndFlush(); STDMETHODIMP Receive(IMediaSample *pMediaSample); // Helper IMemAllocator inline *Allocator() const { return m_pAllocator; } };CBaseOutputPin类的使用方法(派生一个子类,并且最少需要重写以下函数)如下:
(1) 重写CBasePin::CheckMediaType进行连接时媒体类型的检查;
(2) 实现纯虚函数CBaseOutputPin::DecideBufferSize,决定Sample内存的大小;
(3) 重写CBasePin::GetMediaType, 提供Pin 上的首选媒体类型。
class CTransformOutputPin : public CBaseOutputPin { friend class CTransformFilter; protected: CTransformFilter *m_pTransformFilter; public: // implement IMediaPosition by passing upstream IUnknown * m_pPosition; CTransformOutputPin( TCHAR *pObjectName, CTransformFilter *pTransformFilter, HRESULT * phr, LPCWSTR pName); #ifdef UNICODE CTransformOutputPin( CHAR *pObjectName, CTransformFilter *pTransformFilter, HRESULT * phr, LPCWSTR pName); #endif ~CTransformOutputPin(); // override to expose IMediaPosition STDMETHODIMP NonDelegatingQueryInterface(REFIID riid, void **ppv); // --- CBaseOutputPin ------------ STDMETHODIMP QueryId(LPWSTR * Id) { return AMGetWideString(L"Out", Id); } // Grab and release extra interfaces if required HRESULT CheckConnect(IPin *pPin); HRESULT BreakConnect(); HRESULT CompleteConnect(IPin *pReceivePin); // check that we can support this output type HRESULT CheckMediaType(const CMediaType* mtOut); // set the connection media type HRESULT SetMediaType(const CMediaType *pmt); // called from CBaseOutputPin during connection to ask for // the count and size of buffers we need. HRESULT DecideBufferSize( IMemAllocator * pAlloc, ALLOCATOR_PROPERTIES *pProp); // returns the preferred formats for a pin HRESULT GetMediaType(int iPosition,CMediaType *pMediaType); // inherited from IQualityControl via CBasePin STDMETHODIMP Notify(IBaseFilter * pSender, Quality q); // Media type public: CMediaType& CurrentMediaType() { return m_mt; }; };===================================================================
如果开发的是一个Transform Filter,Filter的父类很多时候都是选择CTransformFilter或CTransInPlaceFilter,这种Filter的开发相对简单。
但有时,Filter框架不得不选择CBaseFilter、 CBaseInputPin、CBaseOutputFilter等类来实现,这就有点麻烦了。
这时候可以参考CTransformFilter、CTransformInputPin、CTransformOutputPin对上述3上基类的使用,以此来指导Filter的开发。
===================================================================
6、 CSource
DirectShow SDK还提供了其他更加实用的Filter类,如:
CSource、CTransformFilter、CTransInPlaceFilter、CVideoTransformFilter、 CBaseRender、CBase Video Render等。
它们的继承关系如图:
如上图所示,CSource类(参见source.cpp的实现)直接从CaseFilter中继承而来,一般作为推模式Source Filter的父类。
CSource类的使用方法如下:
(1)从CSource类中派生一个新的Filter类;
(2)在新的Filter类的构造函数中创建各个CSourceStream类实例(CSourceStream类的构造函数会自动将该Pin加入Filter中,并在析构函数中自动删除);
(3)使用CSource::pStateLock函数返回的同步对象进行Filter对象上的多线程同步。
注意: 使用CSource作为Filter父类的Filter未必就是Source Filter。在有些开发Transform Filter的应用中,输出Pin需要使用独立的线程。(即与输入Pin上传送数据
不同的线程)传关,也可以考虑使用CSource。
eg: 参照我的另一篇文章:
class CPushSourceBitmap : public CSource { private: // Constructor is private because you have to use CreateInstance CPushSourceBitmap(IUnknown *pUnk, HRESULT *phr); ~CPushSourceBitmap(); CPushPinBitmap *m_pPin; public: static CUnknown * WINAPI CreateInstance(IUnknown *pUnk, HRESULT *phr); }; CPushSourceBitmap::CPushSourceBitmap(IUnknown *pUnk, HRESULT *phr) : CSource(NAME("PushSourceBitmap"), pUnk, CLSID_PushSourceBitmap) { // The pin magically adds itself to our pin array. m_pPin = new CPushPinBitmap(phr, this); if (phr) { if (m_pPin == NULL) *phr = E_OUTOFMEMORY; else *phr = S_OK; } }
7、 CSourceStream
CSource实际上继承自CBaseFilter,提供了一个“推”数据的能力,这种Filter至少有一个输出
Pin采用了CSourecStream类(或CSourceStream的子类)。如下图所示:
CSourceStream上实现了一个线程(CSourceStream从CAMThread类继承而来),Sample数据就是靠这个线程向一线Filter发送的。
CSourceStream类的使用方法如下:
(1)从CSourceStream派生一个输出Pin类;
(2)重写CSourceStream::GetMediaType,提供输出Pin上的首选媒体类型;
(3)重写CSourceStream::CheckMediaType,进行连续时媒体类型的检查;(可选)
(4)实现CBaseOutPin::DecideBufferSize,决定Sample内存的大小;
(5)实现CSourceStream::FillBuffer,为即将传送出去的Sample 填充数据;
(6)可选地实现CSourceStream::OnThreadCreate、CSourceSream::OnThreadDestroy、CSourceStream::OnThreadStartPlay等函数,
进行适当时节的初始化、资源管理等操作。
eg: 参照我的另一篇文章:
class CPushPinBitmap : public CSourceStream { protected: int m_FramesWritten; // To track where we are in the file BOOL m_bZeroMemory; // Do we need to clear the buffer? CRefTime m_rtSampleTime; // The time stamp for each sample BITMAPINFO *m_pBmi; // Pointer to the bitmap header DWORD m_cbBitmapInfo; // Size of the bitmap header // File opening variables HANDLE m_hFile; // Handle returned from CreateFile BYTE * m_pFile; // Points to beginning of file buffer BYTE * m_pImage; // Points to pixel bits int m_iFrameNumber; const REFERENCE_TIME m_rtFrameLength; CCritSec m_cSharedState; // Protects our internal state CImageDisplay m_Display; // Figures out our media type for us public: CPushPinBitmap(HRESULT *phr, CSource *pFilter); ~CPushPinBitmap(); // Override the version that offers exactly one media type HRESULT GetMediaType(CMediaType *pMediaType); HRESULT DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pRequest); HRESULT FillBuffer(IMediaSample *pSample); // Quality control // Not implemented because we aren't going in real time. // If the file-writing filter slows the graph down, we just do nothing, which means // wait until we're unblocked. No frames are ever dropped. STDMETHODIMP Notify(IBaseFilter *pSelf, Quality q) { return E_FAIL; } };
8、 CTransformFilter
CTransformFilter类是开发Transform Filter最基本的类,也是最常用到的类。结构如下:
它有一个输入Pin和一个输出Pin,分别使用CTransformInputPin类和CTransformOutputPin类。
从图4.8和图4.9可以看出,
CTransformFilter从CBaseFilter继承而来,
CTransformInputPin从CBaseInputPin继承而来,
CTransformOutputPin从CBaseOutputPin继承而来。另个,在CTransformOutputPin上还实现了IMdiaSeeking和 IMediaPosition接口。
(其实,CTransformOutputPin并没有真正实现各个Seek操作。在实际的Seek操作请发生时,CTransformOutpPin会将这些操作请求转发给上一级Filter的输出Pin)。
CTransformFilter实现的最大特征是,它将Pin上必须实现的函数都“委托”到了Filter上(Pin上必须实现的函数在Filter上有类似的函数定义)。
一般无须重写输入和输出Pin类,而只须在Filter上实现相应的函数就行了)。
提示:CTransformFilter默认在GetPin函数实现中创建输入和输出Pin。因此,如果重写了自己的输入或输出Pin类,需要重写GetPin函数。
CTransformFilter类的使用方法(派生一个Filter子类,且最少需要重写以下函数):
(1)CTransformFilter::CheckInputType
(2)CTransformFilter::CheckTransform
(3)CTransformFilter::DecideBufferSize
(4)CTransformFilter::GetMeiaType
(5)CTransformFilter::Transform
eg:CVideoTransformFilter虽然没有实现上面五个函数,但CVideoTransformFilter 的继承类去实现它们。
class CVideoTransformFilter : public CTransformFilter { public: CVideoTransformFilter(TCHAR *, LPUNKNOWN, REFCLSID clsid); ~CVideoTransformFilter(); HRESULT EndFlush(); // ================================================================= // ----- override these bits --------------------------------------- // ================================================================= // The following methods are in CTransformFilter which is inherited. // They are mentioned here for completeness // // These MUST be supplied in a derived class // // NOTE: // virtual HRESULT Transform(IMediaSample * pIn, IMediaSample *pOut); // virtual HRESULT CheckInputType(const CMediaType* mtIn) PURE; // virtual HRESULT CheckTransform // (const CMediaType* mtIn, const CMediaType* mtOut) PURE; // static CCOMObject * CreateInstance(LPUNKNOWN, HRESULT *); // virtual HRESULT DecideBufferSize // (IMemAllocator * pAllocator, ALLOCATOR_PROPERTIES *pprop) PURE; // virtual HRESULT GetMediaType(int iPosition, CMediaType *pMediaType) PURE; // // These MAY also be overridden // // virtual HRESULT StopStreaming(); // virtual HRESULT SetMediaType(PIN_DIRECTION direction,const CMediaType *pmt); // virtual HRESULT CheckConnect(PIN_DIRECTION dir,IPin *pPin); // virtual HRESULT BreakConnect(PIN_DIRECTION dir); // virtual HRESULT CompleteConnect(PIN_DIRECTION direction,IPin *pReceivePin); // virtual HRESULT EndOfStream(void); // virtual HRESULT BeginFlush(void); // virtual HRESULT EndFlush(void); // virtual HRESULT NewSegment // (REFERENCE_TIME tStart,REFERENCE_TIME tStop,double dRate); #ifdef PERF // If you override this - ensure that you register all these ids // as well as any of your own, virtual void RegisterPerfId() { m_idSkip = MSR_REGISTER(TEXT("Video Transform Skip frame")); m_idFrameType = MSR_REGISTER(TEXT("Video transform frame type")); m_idLate = MSR_REGISTER(TEXT("Video Transform Lateness")); m_idTimeTillKey = MSR_REGISTER(TEXT("Video Transform Estd. time to next key")); CTransformFilter::RegisterPerfId(); } #endif protected: // =========== QUALITY MANAGEMENT IMPLEMENTATION ======================== // Frames are assumed to come in three types: // Type 1: an AVI key frame or an MPEG I frame. // This frame can be decoded with no history. // Dropping this frame means that no further frame can be decoded // until the next type 1 frame. // Type 1 frames are sync points. // Type 2: an AVI non-key frame or an MPEG P frame. // This frame cannot be decoded unless the previous type 1 frame was // decoded and all type 2 frames since have been decoded. // Dropping this frame means that no further frame can be decoded // until the next type 1 frame. // Type 3: An MPEG B frame. // This frame cannot be decoded unless the previous type 1 or 2 frame // has been decoded AND the subsequent type 1 or 2 frame has also // been decoded. (This requires decoding the frames out of sequence). // Dropping this frame affects no other frames. This implementation // does not allow for these. All non-sync-point frames are treated // as being type 2. // // The spacing of frames of type 1 in a file is not guaranteed. There MUST // be a type 1 frame at (well, near) the start of the file in order to start // decoding at all. After that there could be one every half second or so, // there could be one at the start of each scene (aka "cut", "shot") or // there could be no more at all. // If there is only a single type 1 frame then NO FRAMES CAN BE DROPPED // without losing all the rest of the movie. There is no way to tell whether // this is the case, so we find that we are in the gambling business. // To try to improve the odds, we record the greatest interval between type 1s // that we have seen and we bet on things being no worse than this in the // future. // You can tell if it's a type 1 frame by calling IsSyncPoint(). // there is no architected way to test for a type 3, so you should override // the quality management here if you have B-frames. int m_nKeyFramePeriod; // the largest observed interval between type 1 frames // 1 means every frame is type 1, 2 means every other. int m_nFramesSinceKeyFrame; // Used to count frames since the last type 1. // becomes the new m_nKeyFramePeriod if greater. BOOL m_bSkipping; // we are skipping to the next type 1 frame #ifdef PERF int m_idFrameType; // MSR id Frame type. 1=Key, 2="non-key" int m_idSkip; // MSR id skipping int m_idLate; // MSR id lateness int m_idTimeTillKey; // MSR id for guessed time till next key frame. #endif virtual HRESULT StartStreaming(); HRESULT AbortPlayback(HRESULT hr); // if something bad happens HRESULT Receive(IMediaSample *pSample); HRESULT AlterQuality(Quality q); BOOL ShouldSkipFrame(IMediaSample * pIn); int m_itrLate; // lateness from last Quality message // (this overflows at 214 secs late). int m_tDecodeStart; // timeGetTime when decode started. int m_itrAvgDecode; // Average decode time in reference units. BOOL m_bNoSkip; // debug - no skipping. // We send an EC_QUALITY_CHANGE notification to the app if we have to degrade. // We send one when we start degrading, not one for every frame, this means // we track whether we've sent one yet. BOOL m_bQualityChanged; // When non-zero, don't pass anything to renderer until next keyframe // If there are few keys, give up and eventually draw something int m_nWaitForKey; };9、 CTransInPlaceFilter
CTransInPlaceFilter是一个“就地”处理的Transform Filter类。结构如下:
与CTransformFilter,CTransInPlaceFilter也有一个输入Pin和一个输出Pin,但使用CTransInPlaceOutputPin类。
CTransInPlaceFilter的输入和输出Pin上一般使用相同的媒体类型进行连接,并且使用同一个Sample管理器(如果Filter实现时要修改Sample数据,
而协商达成一致的Sample管理器只读的,那么CTransInPlaceFilter的输入和输出Pin将不得不使用各自的Sample管理器)。
CTransInPlaceFilter类要实现上述的目标,主要依赖于CTransInPlaceFilter::CompleteConnect、CTransInPlaceInputPin::GetAllocator和
CTransInPlaceInputPin::NotifyAlocator的函数实现。代码如下:
// 当输入或输出Pin完成连接时被调用, // 经过一个反复重连的过程,来达到输入和输出Pin使用相同的媒体类型的目的 HRESULT CTransInPlaceFilter::CompleteConnect(PIN_DIRECTION dir,IPin *pReceivePin) { UNREFERENCED_PARAMETER(pReceivePin); ASSERT(m_pInput); ASSERT(m_pOutput); // if we are not part of a graph, then don't indirect the pointer // this probably prevents use of the filter without a filtergraph if (!m_pGraph) { return VFW_E_NOT_IN_GRAPH; } // Always reconnect the input to account for buffering changes // // Because we don't get to suggest a type on ReceiveConnection // we need another way of making sure the right type gets used. // // One way would be to have our EnumMediaTypes return our output // connection type first but more deterministic and simple is to // call ReconnectEx passing the type we want to reconnect with // via the base class ReconeectPin method. // 当输出Pin调用该函数(并且此时输入Pin已连上)时,使用输出Pin上的媒体类型对 // 输入Pin进行重连接 if (dir == PINDIR_OUTPUT) { if( m_pInput->IsConnected() ) { return ReconnectPin( m_pInput, &m_pOutput->CurrentMediaType() ); } return NOERROR; } ASSERT(dir == PINDIR_INPUT); // Reconnect output if necessary // 当输入Pin调用该函数(并且此时输出Pin已连上)时,如果输入和输出Pin上使用的 // 媒体类型不一致,则使用输入Pin上的媒体类型对输出Pin进行重新连接 if( m_pOutput->IsConnected() ) { if ( m_pInput->CurrentMediaType() != m_pOutput->CurrentMediaType() ) { return ReconnectPin( m_pOutput, &m_pInput->CurrentMediaType() ); } } return NOERROR; } // ComnpleteConnectCTransInPlaceFilter类定义了一个成员变量m_bModifiesData ,用于指示我们在Filter中是否会修改Saple数据。
这个变量在CTransInPlaceFilter构造函数调用时被默认初始化为true。如果我们确定不会在Filter中修改Sample数据,
那么,将m_bModifiesData设置为false, 可以保证输入和输出Pin连接完成后使用同一个Sample管理器。
10、 CVideoTransformFilter
CVieoTransformFilter是一个实现了视频的质量控制的Transform Filter类。其结构如下:
CVieoTransformFilter通过输入Pin上的Receive 函数接收Sample时,能够根据质量消息决定是否丢帧。这个类主要是为开发AVI解码Filter而设计的。
CVieoTransformFilter类的使用基本上与CTransformFilter相同。
11、 CBaseRenderer
CBaseRender是最基本的实现Renderer Filter的类。它默认实现了一个使用CRendererInputPin类的输入Pin(Renderer Filter没有输出Pin)。
这两个类的结构如下:
从图中可以看出,CBaseRenderer从CBaseFilter继承而来。另外,CBaseRenderer上还实现了IMediaSeekin和IMediaPosition接口。
CRendererInputPin从CBaseInputPin继承而来,它把各个主要函数调用都“委托”到Filter上。值得注意的是,当输入Pin接收到EndOfStream调用时,
Renderer Filter 有责任向Filter Graph Manager发送一个EC_COMPLETE事件。
CBaseRenderer类的使用方法(派生一个子类,并至少实现如下函数)如下:
(1)CBaseRenderer::CheckMediaType,用于检查输入Pin连接用的媒体类型;
(2)CBaseRenderer::DoRenderSample,处理当前的Sample。
提示:CBaseRenderer实际上是为了用于播放的Render Filter设计的,对于Sample的安排比较复杂。
如果我们要开发Renderer Filter不播放Sample(比如写文件的Filter、或者负责网络的Filter),Fitler的基类可以选择CBaseFilter,而此时输入Pin最
好选择CRenderedInputPin类派生。
12、CBaseVideoRenderer
CBaseVideoRenderer是一个实现Video Renderer类的基类,结构如下:
CBaseVideoRenderer在CBaseRenderer的基础上增加了IQualityControl和IQualProp接口的实现。
其中IQualityControl用于视频质量控制,IQualProp用于在Filter属性页显示一些实时性能参数。CBaseVideoRenderer类的使用方法基本上与CBaseRenderer相同。
在DirectShow SDK基类库中,除了上述Filter和Pin类外,还有很多工具类,有了这些类的支持,我们开发Fitler或DirectShow应用程序会更加轻松。
这些类包括: CPullPin、 COutputQueue、 CSourceSeeking 、CEnumPins、 CEnumMediaTypes 、CMemAllocator、 CMediaSample 、
CBaseReferenceClock 、CMediaType、 CBasePropertyPage 等。