今回は実装編その2です。自前でDirectShowのフィルタを実装するときの注意点などを見ていきます。
前にDirectShowのフィルタを実装したときと違い、CBasePinやらCBaseFilterやらとbaseclassesの基本オブジェクトを使って実装しているのでこちらで実装しなければならない動作が多いです。
その辺に注意しながら見ていきます。
ソースフィルタの出力ピンの実装部
まずはピンの基本的処理から。
//出力ピン //コンストラクタ CDataSourceOutputPin::CDataSourceOutputPin(HRESULT *lphr,CDataSourceReader *lpSourceReader,CDataAsyncReader *lpAsyncReader,CCritSec *lpLockCritical) : CBasePin(NAME("DataSourceOutputPin"),lpSourceReader,lpLockCritical,lphr,L"SourceOutput",PINDIR_OUTPUT), m_lpSourceReader(lpSourceReader), m_lpAsyncReader(lpAsyncReader), m_bIsQueriedForAsyncReader(FALSE) { } //デストラクタ CDataSourceOutputPin::~CDataSourceOutputPin() { } //QueryInterfaceの実装 STDMETHODIMP CDataSourceOutputPin::NonDelegatingQueryInterface(REFIID refiid,void **lplpInterface) { if(IsEqualGUID(refiid,IID_IAsyncReader)){ m_bIsQueriedForAsyncReader = TRUE; return GetInterface(static_cast<IAsyncReader *>(this),lplpInterface); } return CBasePin::NonDelegatingQueryInterface(refiid,lplpInterface); } //ピンを接続する STDMETHODIMP CDataSourceOutputPin::Connect(IPin *lpReceivePin,const AM_MEDIA_TYPE *lpMediaType) { return m_lpSourceReader->Connect(lpReceivePin,lpMediaType); } //CBasePinを使ってピンを接続する HRESULT CDataSourceOutputPin::ConnectOnBasePin(IPin *lpReceivePin,const AM_MEDIA_TYPE *lpMediaType) { return CBasePin::Connect(lpReceivePin,lpMediaType); } //このピンから出力されるメディアタイプを取得する HRESULT CDataSourceOutputPin::GetMediaType(int nPosition,CMediaType *lpMediaType) { CheckPointer(lpMediaType,E_POINTER); if(nPosition < 0){ return E_INVALIDARG; } else if(nPosition > 0){ return VFW_S_NO_MORE_ITEMS; } CheckPointer(m_lpSourceReader,E_UNEXPECTED); *lpMediaType = *m_lpSourceReader->GetMediaType(); return S_OK; } //指定されたメディアタイプがサポートされているかどうかをチェックする HRESULT CDataSourceOutputPin::CheckMediaType(const CMediaType *lpMediaType) { const CMediaType *lpSourceMediaType = m_lpSourceReader->GetMediaType(); if(!IsEqualGUID(lpSourceMediaType->majortype,lpMediaType->majortype)) return VFW_E_TYPE_NOT_ACCEPTED; if(IsEqualGUID(lpSourceMediaType->subtype,lpMediaType->subtype)) return S_OK; if(IsEqualGUID(lpSourceMediaType->subtype,MEDIASUBTYPE_NULL)) return S_OK; return VFW_E_TYPE_NOT_ACCEPTED; } //指定されたピント適切に接続されているかどうか HRESULT CDataSourceOutputPin::CheckConnect(IPin *lpPin) { m_bIsQueriedForAsyncReader = FALSE; return CBasePin::CheckConnect(lpPin); } //ピンへの接続を確立する HRESULT CDataSourceOutputPin::CompleteConnect(IPin *lpReceivePin) { if(m_bIsQueriedForAsyncReader) return CBasePin::CompleteConnect(lpReceivePin); else #ifdef VFW_E_NO_TRANSPORT return VFW_E_NO_TRANSPORT; #else return E_FAIL; #endif } //ピンへの接続を解放する HRESULT CDataSourceOutputPin::BreakConnect(void) { m_bIsQueriedForAsyncReader = FALSE; return CBasePin::BreakConnect(); }
基本的な処理はCBasePinに実装されているのでそちらに回しているものも多いです。
コンストラクタがちょっと長いですが、引数の役目にあわせて渡せば問題はありません。
こちらで任意に設定できるのは第一引数のpObjectNameおよび第五引数のpNameです。pNameはGraphEditなどでピンの名前として表示されますので設定しておくといいです。
QueryInterfaceではIAsyncReaderが追加されるのでそれを返しています。
またConnect処理が微妙な遠回りとなっています。Connect時に一度フィルタに投げているのでそうするとCBasePinがもつConnect処理が継承でつぶされてしまうのでそこを別の関数で補っています。
次にメモリアロケータを。
//メモリ確保インターフェイスを初期化する HRESULT CDataSourceOutputPin::InitializeAllocator(IMemAllocator **lplpAllocator) { CMemAllocator *lpMemoryAllocator; HRESULT hr; CheckPointer(lplpAllocator,E_POINTER); *lplpAllocator = NULL; lpMemoryAllocator = NULL; hr = S_OK; lpMemoryAllocator = new CMemAllocator(NAME("BaseMemoryAllocator"),NULL,&hr); if(lpMemoryAllocator == NULL) return E_OUTOFMEMORY; if(hr != S_OK){ delete lpMemoryAllocator; return hr; } hr = lpMemoryAllocator->QueryInterface(IID_PPV_ARGS(lplpAllocator)); if(hr != S_OK){ delete lpMemoryAllocator; return E_NOINTERFACE; } if(*lplpAllocator == NULL){ delete lpMemoryAllocator; return E_FAIL; } return S_OK; } //メモリ確保インターフェイスを取得する STDMETHODIMP CDataSourceOutputPin::RequestAllocator(IMemAllocator *lpPreferred,ALLOCATOR_PROPERTIES *lpProperties,IMemAllocator **lplpActualAllocator) { ALLOCATOR_PROPERTIES ActualProperties,RequestProperties; IMemAllocator *lpMemoryAllocator; HRESULT hr; CheckPointer(lpProperties,E_POINTER); CheckPointer(lplpActualAllocator,E_POINTER); ActualProperties = RequestProperties = *lpProperties; if(RequestProperties.cbAlign == 0 || !m_lpAsyncReader->IsAligned(RequestProperties.cbAlign)){ m_lpAsyncReader->Alignment(&(RequestProperties.cbAlign)); } if(lpPreferred != NULL){ hr = lpPreferred->SetProperties(&RequestProperties,&ActualProperties); if(hr == S_OK && m_lpAsyncReader->IsAligned(ActualProperties.cbAlign)){ lpPreferred->AddRef(); *lplpActualAllocator = lpPreferred; return S_OK; } } hr = InitializeAllocator(&lpMemoryAllocator); if(hr != S_OK) return hr; hr = lpMemoryAllocator->SetProperties(&RequestProperties,&ActualProperties); if(hr == S_OK && m_lpAsyncReader->IsAligned(ActualProperties.cbAlign)){ *lplpActualAllocator = lpMemoryAllocator; return S_OK; } lpMemoryAllocator->Release(); if(hr == S_OK) hr = VFW_E_BADALIGN; return hr; }
基本的には接続先から使ってほしいアロケータが投げられるので使用可能かどうか判定して使えるようならそちらを使います。
使える要件はメモリのアライメントが非同期読み取りのアライメント調整とあっているかどうかです。失敗すればbaseclassesが持っているCMemAllocatorが登場するというものです。
最後にIAsyncReaderの実装です。RequestAllocatorもIAsyncReaderに必要ですが、残りの方が重要です。
//リクエストキューにデータ要求を入れる STDMETHODIMP CDataSourceOutputPin::Request(IMediaSample *lpSample,DWORD_PTR dwUser) { REFERENCE_TIME tStart,tStop; LONGLONG llPos,llTotalSize,llAvailableSize; long lLength,lAlignment; BYTE *lpBuffer; HRESULT hr; CheckPointer(lpSample,E_POINTER); hr = lpSample->GetTime(&tStart,&tStop); if(hr != S_OK) return hr; llPos = tStart / UNITS; lLength = (long)((tStop - tStart) / UNITS); llTotalSize = llAvailableSize = 0; m_lpAsyncReader->Length(&llTotalSize,&llAvailableSize); if(llPos + (LONGLONG)lLength > llTotalSize){ m_lpAsyncReader->Alignment(&lAlignment); llTotalSize = (llTotalSize + (LONGLONG)lAlignment - 1) & ~((LONGLONG)(lAlignment - 1)); if(llPos + (LONGLONG)lLength > llTotalSize){ lLength = (long)(llTotalSize - llPos); tStop = llTotalSize * UNITS; lpSample->SetTime(&tStart,&tStop); } } hr = lpSample->GetPointer(&lpBuffer); if(hr != S_OK) return hr; return m_lpAsyncReader->Request(llPos,lLength,TRUE,lpBuffer,lpSample,dwUser); } //次のデータ読み取りまで待機する STDMETHODIMP CDataSourceOutputPin::WaitForNext(DWORD dwTimeOut,IMediaSample **lplpSample,DWORD_PTR *lpdwUser) { long lActualSize; HRESULT hr; CheckPointer(lplpSample,E_POINTER); CheckPointer(lpdwUser,E_POINTER); hr = m_lpAsyncReader->WaitForNext(dwTimeOut,(void **)lplpSample,lpdwUser,&lActualSize); if(SUCCEEDED(hr)) (*lplpSample)->SetActualDataLength(lActualSize); return hr; } //アライメントを合わせた同期読み取りを行う STDMETHODIMP CDataSourceOutputPin::SyncReadAligned(IMediaSample *lpSample) { REFERENCE_TIME tStart,tStop; LONGLONG llPos,llTotalSize,llAvailableSize; long lLength,lAlignment,lActualSize; BYTE *lpBuffer; HRESULT hr; CheckPointer(lpSample,E_POINTER); hr = lpSample->GetTime(&tStart,&tStop); if(hr != S_OK) return hr; llPos = tStart / UNITS; lLength = (long)((tStop - tStart) / UNITS); llTotalSize = llAvailableSize = 0; m_lpAsyncReader->Length(&llTotalSize,&llAvailableSize); if(llPos + (LONGLONG)lLength > llTotalSize){ m_lpAsyncReader->Alignment(&lAlignment); llTotalSize = (llTotalSize + (LONGLONG)lAlignment - 1) & ~((LONGLONG)(lAlignment - 1)); if(llPos + (LONGLONG)lLength > llTotalSize){ lLength = (long)(llTotalSize - llPos); tStop = llTotalSize * UNITS; lpSample->SetTime(&tStart,&tStop); lpSample->SetMediaTime(&tStart,&tStop); } } hr = lpSample->GetPointer(&lpBuffer); if(hr != S_OK) return hr; hr = m_lpAsyncReader->SyncReadAligned(llPos,lLength,lpBuffer,&lActualSize,lpSample); lpSample->SetActualDataLength(lActualSize); return hr; } //同期読み取りを行う STDMETHODIMP CDataSourceOutputPin::SyncRead(LONGLONG llPosition,long lLength,BYTE *lpBuffer) { return m_lpAsyncReader->SyncRead(llPosition,lLength,lpBuffer); } //メディアストリームの長さを取得する STDMETHODIMP CDataSourceOutputPin::Length(LONGLONG *lpllTotalLength,LONGLONG *lpllAvailableLength) { return m_lpAsyncReader->Length(lpllTotalLength,lpllAvailableLength); } //フラッシュ処理を開始する STDMETHODIMP CDataSourceOutputPin::BeginFlush(void) { return m_lpAsyncReader->BeginFlush(); } //フラッシュ処理を終了する STDMETHODIMP CDataSourceOutputPin::EndFlush(void) { return m_lpAsyncReader->EndFlush(); }
ほとんどは先に実装したIAsyncReaderの機能を受け持つクラスに動作を投げています。
しかもよく見たらRequestとSyncReadAlignedでIMediaSampleに対して行っている処理コードってほぼ同じですよね・・・さすがに古いコード・・・。
なお、IMediaSampleから取得できるタイムコードとサンプルの位置関係についてはコードの通りです。
なぜそうなるかは「仕様です」の一言でしかありません。時間とストリーム位置が何でこんな関係なのか・・・
後はフィルタに関するコード
//GUIDの定義 class __declspec(uuid("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}")) CDataSourceReader; //データリーダ //コンストラクタ CDataSourceReader::CDataSourceReader(const TCHAR *lpFilterName,IUnknown *lpOwner,HRESULT *lphr,IDataStream *lpDataStream) : CBaseFilter(lpFilterName,lpOwner,&m_vCSFilter,__uuidof(CDataSourceReader)), m_lpOutputPin(NULL), m_lpAsyncReader(NULL) { m_lpAsyncReader = new CDataAsyncReader(lpDataStream); m_lpOutputPin = new CDataSourceOutputPin(lphr,this,m_lpAsyncReader,&m_vCSFilter); } //デストラクタ CDataSourceReader::~CDataSourceReader() { delete m_lpOutputPin; delete m_lpAsyncReader; } //接続できるピンの数を取得する int CDataSourceReader::GetPinCount() { return 1; } //ピンを取得する CBasePin *CDataSourceReader::GetPin(int n) { CBasePin *lpPin; switch(n){ case 0: lpPin = m_lpOutputPin; break; default: lpPin = NULL; } return lpPin; } //保持しているメディア形式を取得する const CMediaType *CDataSourceReader::GetMediaType() const { return } //ピンを接続する HRESULT CDataSourceReader::Connect(IPin *lpReceivePin,const AM_MEDIA_TYPE *lpMediaType) { return m_lpOutputPin->ConnectOnBasePin(lpReceivePin,lpMediaType); }
GUIDについては自分で設定してください。
非同期読み取りサポートクラスはnew~deleteは当然ですが、出力ピンまでnew~deleteになっています。
これがCBasePinとCBaseFilterの関係性になるので注意です。
CBasePinの所有者としてCBaseFilterを渡してCBasePinのコンストラクタを動かしますが、この時点でピンは参照カウントを自身で保持しなくなります。
このあたりのコードはbaseclassesのCUnknownの実装を参考にしてほしいですが、ピン側にAddRefやReleaseなどの参照カウント処理を行うとそれはフィルタ側の参照カウント処理になる、という動作になります。
参照カウントがフィルタ側だけで処理されるのでピンとフィルタのオブジェクト寿命が一緒になる、というわけです。
その代わり、ピンのメモリ解放はReleaseではできず(ピンのReleaseを行った場合はフィルタのReleaseが呼び出されるのと同じになるため)deleteが必要になります。
また、CBaseFilterにはあるのにインターフェイス側のIBaseFilterにない処理としてGetPinCountおよびGetPinがあります。
これはCBaseFilterでは純粋仮想メソッドとして定義されているため実装は必須となります。
ちなみにこれら(+IncrementPinVersion)は何をするのかというとIEnumPinsというピンを列挙するインターフェイスを実装するための内部処理を担当しています。
IEnumPinsを実装するのは手間なのでこの関数群で処理を簡単に記述できるようになっている、という理屈です。
戻って言うならCBasePinのGetMediaTypeもこれの仲間(IEnumMediaTypesの内部実装)ですね。
あとは使用側ですか
とりあえず使用側のコードを書いて今回は終了ですね。