DirectShow関係
4.オリジナルフィルタの作成(その2)
前回は形だけだったのでフィルタとしては動作しなかったので、
実際に動くフィルタを紹介します。
といっても、ここで紹介するのは単純な変換フィルタです。(^^;
入力ピン:1つ
出力ピン:1つ
効果:一つ前の映像と新しい画像とブレンドする。
これだけのフィルタです。
効果の仕組みとしては、
接続時に確保したメモリに入力ピンから来た映像を保存しておき、
新しく映像が来たときに前回の映像と新しい映像を95:5の割合で混ぜます。
じわ〜と映像が変わっていくような効果があります。
カメラの入力とかにつかうと映画マトリックスのような感じがちょっとだけします(笑
実際の全コードは以下のようになります。
色付き部分が前回から追加された部分です。
// MoFilter.cpp: CMoFilter クラスのインプリメンテーション // ////////////////////////////////////////////////////////////////////// #include <stdio.h> #include <streams.h> #include <initguid.h> #include "MoFilter.h" ////////////////////////////////////////////////////////////////////// // 入力ピンのメディアタイプ const AMOVIESETUP_MEDIATYPE mPinMediaType_In= { &MEDIATYPE_NULL,&MEDIASUBTYPE_NULL }; // 出力ピンのメディアタイプ const AMOVIESETUP_MEDIATYPE mPinMediaType_Out= { &MEDIATYPE_NULL,&MEDIASUBTYPE_NULL }; // ピン情報 const AMOVIESETUP_PIN mPinInfo[]= { { L"Input", // ピンの名前(未使用) FALSE, // このピンをレンダリングするか? FALSE, // これは出力ピンか? FALSE, // このフィルタはゼロインスタンスを生成できるか? FALSE, // このフィルタは複数のインスタンスを生成するか? &CLSID_NULL, // 未使用 NULL, // 未使用 1, // メディアタイプの数 &mPinMediaType_In // メディアタイプ }, { L"Output", // ピンの名前(未使用) FALSE, // このピンをレンダリングするか? TRUE, // これは出力ピンか? FALSE, // このフィルタはゼロインスタンスを生成できるか? FALSE, // このフィルタは複数のインスタンスを生成するか? &CLSID_NULL, // 未使用 NULL, // 未使用 1, // メディアタイプの数 &mPinMediaType_Out // メディアタイプ } }; // フィルタ設定 const AMOVIESETUP_FILTER mFilterInfo= { &CLSID_MoFilter, // クラスID L"MoFilter", // フィルタ名 MERIT_DO_NOT_USE, // メリット 2, // ピンの数 mPinInfo // ピン情報 }; // ファクトリテンプレートの宣言 CFactoryTemplate g_Templates[]= { { L"MoFilter", &CLSID_MoFilter, CMoFilter::CreateInstance, NULL, &mFilterInfo } }; int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]); ////////////////////////////////////////////////////////////////////// // レジストリへの登録 STDAPI DllRegisterServer() { return AMovieDllRegisterServer2( TRUE ); } // レジストリからの解除 STDAPI DllUnregisterServer() { return AMovieDllRegisterServer2( FALSE ); } ////////////////////////////////////////////////////////////////////// // コンストラクタ CMoFilter::CMoFilter(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) : CTransformFilter(tszName, pUnk, CLSID_MoFilter) { mImageBuffer=NULL; } // デストラクタ CMoFilter::~CMoFilter() { if( mImageBuffer!=NULL ) delete[] mImageBuffer; } ////////////////////////////////////////////////////////////////////// // インスタンス作成 CUnknown *CMoFilter::CreateInstance(LPUNKNOWN punk, HRESULT *phr) { CMoFilter *pNewObject=new CMoFilter( NAME("MoFilter"),punk,phr ); if( pNewObject==NULL ) { *phr=E_OUTOFMEMORY; } return pNewObject; } ////////////////////////////////////////////////////////////////////// // 入力ピンの接続チェック HRESULT CMoFilter::CheckInputType(const CMediaType *mtIn) { // ビデオストリームであること if( *mtIn->FormatType() != FORMAT_VideoInfo ) return VFW_E_TYPE_NOT_ACCEPTED; // メディアタイプのチェック if( MediaCheck( mtIn )!=S_OK ) return VFW_E_TYPE_NOT_ACCEPTED; // OK return S_OK; } // 出力ピンの接続チェック HRESULT CMoFilter::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) { // メディアタイプのチェック if( MediaCheck( mtIn )!=S_OK ) return VFW_E_TYPE_NOT_ACCEPTED; // 入力形式と出力形式が同じであること if( *mtIn!=*mtOut ) return VFW_E_TYPE_NOT_ACCEPTED; // OK return S_OK; } // メディアタイプのチェック HRESULT CMoFilter::MediaCheck(const CMediaType *MediaType) { if( IsEqualGUID( *MediaType->Type(),MEDIATYPE_Video ) ) // Videoか? { if( IsEqualGUID( *MediaType->Subtype(),MEDIASUBTYPE_RGB32) ) // RGB32ビットタイプか? { VIDEOINFOHEADER *pvi=(VIDEOINFOHEADER *)MediaType->Format(); if( pvi->bmiHeader.biBitCount == 32 ) // ビットカウントが32ビットであるか? { // OK return S_OK; } } } // NG return VFW_E_TYPE_NOT_ACCEPTED; } ////////////////////////////////////////////////////////////////////// // バッファの準備 HRESULT CMoFilter::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties) { // 入力ピンが接続されていること if( m_pInput->IsConnected()==FALSE ) return E_UNEXPECTED; // バッファサイズを取得 pProperties->cBuffers=1; pProperties->cbBuffer=m_pInput->CurrentMediaType().GetSampleSize(); ALLOCATOR_PROPERTIES Actual; HRESULT rc; if( FAILED(rc=pAlloc->SetProperties( pProperties,&Actual )) ) return rc; // 確保されたかのチェック if( (pProperties->cBuffers > Actual.cBuffers) || (pProperties->cbBuffer > Actual.cbBuffer) ) return E_FAIL; // OK return S_OK; } // 出力ピンが接続できるメディアタイプを返す HRESULT CMoFilter::GetMediaType(int iPosition, CMediaType *pMediaType) { // 入力ピンが接続されていない場合はエラー if( m_pInput->IsConnected()==FALSE ) return E_UNEXPECTED; // 番号のチェック(iPosition==0のみOK) if( iPosition<0 ) return E_INVALIDARG; // 0未満 if( 0<iPosition ) return VFW_S_NO_MORE_ITEMS; // 1以上 // 入力メディアタイプをそのまま返す *pMediaType=m_pInput->CurrentMediaType(); return S_OK; } ////////////////////////////////////////////////////////////////////// // 接続完了 HRESULT CMoFilter::CompleteConnect( PIN_DIRECTION direction,IPin *pReceivePin ) { // サイズを取得 AM_MEDIA_TYPE *MType=NULL; // メディアタイプ VIDEOINFOHEADER *VInfo=NULL; // ビデオ情報 long ImgX=0,ImgY=0; // ビデオサイズ MType=&m_pInput->CurrentMediaType(); VInfo=(VIDEOINFOHEADER *)(MType->pbFormat); ImgX=VInfo->bmiHeader.biWidth; ImgY=VInfo->bmiHeader.biHeight; // 既に確保していたら解放する if( mImageBuffer!=NULL ) { delete[] mImageBuffer; mImageBuffer=NULL; } // イメージ保存用バッファの確保 mImageBuffer=new long[ImgX*ImgY]; memset( mImageBuffer,0,ImgX*ImgY*sizeof(long) ); return S_OK; } ////////////////////////////////////////////////////////////////////// // 転送 HRESULT CMoFilter::Transform(IMediaSample *pIn, IMediaSample *pOut) { HRESULT rc; // 転送 if( FAILED(rc=Copy( pIn,pOut )) ) return rc; // 更新 if( FAILED(rc=UpdateBitmap( pOut )) ) return rc; // OK return S_OK; } // 入力側のデータを出力側にコピーする HRESULT CMoFilter::Copy(IMediaSample *Src, IMediaSample *Dst) const { // ビットマップ転送 long srcsize=Src->GetActualDataLength(); BYTE *srcbuff,*dstbuff; Src->GetPointer( &srcbuff ); Dst->GetPointer( &dstbuff ); CopyMemory( (void *)dstbuff,(void *)srcbuff,srcsize ); // タイムスタンプを複写 REFERENCE_TIME ts,te; if( SUCCEEDED(Src->GetTime( &ts,&te )) ) Dst->SetTime( &ts,&te ); LONGLONG ms,me; if( SUCCEEDED(Src->GetMediaTime( &ms,&me )) ) Dst->SetMediaTime( &ms,&me ); // 同期ポイントを複写 HRESULT hr=Src->IsSyncPoint(); if( hr==S_OK ) Dst->SetSyncPoint( TRUE ); else if( hr==S_FALSE ) Dst->SetSyncPoint( FALSE ); else return E_UNEXPECTED; // メディアタイプを複製 AM_MEDIA_TYPE *mt; Src->GetMediaType( &mt ); Dst->SetMediaType( mt ); DeleteMediaType( mt ); // 連続性情報を複写 hr=Src->IsDiscontinuity(); if( hr==S_OK ) Dst->SetDiscontinuity( TRUE ); else if( hr==S_FALSE ) Dst->SetDiscontinuity( FALSE ); else return E_UNEXPECTED; // バッファ内の有効なデータ長を複写 Dst->SetActualDataLength( Src->GetActualDataLength() ); return S_OK; } // 出力イメージを更新する HRESULT CMoFilter::UpdateBitmap(IMediaSample *MediaSample) { // 情報取得 BYTE *BmpBuff=NULL; // ビットマップ領域の先頭 long BmpBuffSize=0; // ビットマップ領域のサイズ AM_MEDIA_TYPE *MType=NULL; // メディアタイプ VIDEOINFOHEADER *VInfo=NULL; // ビデオ情報 long ImgX=0,ImgY=0; // ビデオサイズ MediaSample->GetPointer( &BmpBuff ); BmpBuffSize=MediaSample->GetSize(); MType=&m_pInput->CurrentMediaType(); VInfo=(VIDEOINFOHEADER *)(MType->pbFormat); ImgX=VInfo->bmiHeader.biWidth; ImgY=VInfo->bmiHeader.biHeight; // イメージの変更 BYTE *b0=(BYTE *)mImageBuffer; BYTE *b1=BmpBuff; long d0,d1,dd; long z; for( z=0;z<BmpBuffSize;z++ ) { d0=*b0; d1=*b1; dd=(d0*95+d1*5)/100; *b1=(BYTE)dd; b0++; b1++; } // 現在のフレームを保存しておく CopyMemory( (void *)mImageBuffer,(void *)BmpBuff,BmpBuffSize ); return S_OK; } |
// MoFilter.h: CMoFilter クラスのインターフェイス // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_MOFILTER_H__49A24DB0_FC0D_4D9F_BC69_08D8D24D93B0__INCLUDED_) #define AFX_MOFILTER_H__49A24DB0_FC0D_4D9F_BC69_08D8D24D93B0__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // CmoFilterのクラスID // {A6A4E64B-6AAA-4838-AF6C-048CCE72924C} static const GUID CLSID_MoFilter = { 0xa6a4e64b, 0x6aaa, 0x4838, { 0xaf, 0x6c, 0x4, 0x8c, 0xce, 0x72, 0x92, 0x4c } }; class CMoFilter : public CTransformFilter { public: DECLARE_IUNKNOWN; // CUnknownのインターフェース定義 CMoFilter(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr); virtual ~CMoFilter(); static CUnknown *CreateInstance(LPUNKNOWN punk, HRESULT *phr); HRESULT CheckInputType(const CMediaType *mtIn); HRESULT CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut); HRESULT Transform(IMediaSample *pIn, IMediaSample *pOut); HRESULT DecideBufferSize( IMemAllocator *pAlloc,ALLOCATOR_PROPERTIES *pProperties); HRESULT GetMediaType(int iPosition, CMediaType *pMediaType); HRESULT CompleteConnect( PIN_DIRECTION direction,IPin *pReceivePin ); private: HRESULT MediaCheck(const CMediaType *MediaType); HRESULT Copy(IMediaSample *Src, IMediaSample *Dst) const; HRESULT UpdateBitmap(IMediaSample *MediaSample); long *mImageBuffer; }; #endif // !defined(AFX_MOFILTER_H__49A24DB0_FC0D_4D9F_BC69_08D8D24D93B0__INCLUDED_) |
MoFilter.defは変更がありません。
順に説明しますが、コードの多くはDirectX
SDKにあるフィルタのサンプルから持ってきた通りですので
そちらも見ながらの方がいいかもしれません(^^;
あとSDKヘルプの「変換フィルタの作成」の所も参考になると思います。
(そこ見ながら作ったんで)
◎初期化/開放
////////////////////////////////////////////////////////////////////// // コンストラクタ CMoFilter::CMoFilter(TCHAR *tszName, LPUNKNOWN pUnk, HRESULT *phr) : CTransformFilter(tszName, pUnk, CLSID_MoFilter) { mImageBuffer=NULL; } // デストラクタ CMoFilter::~CMoFilter() { if( mImageBuffer!=NULL ) delete[] mImageBuffer; } |
mImageBufferは映像を保存しておくバッファへのポインタで
コンストラクタでは初期化しているだけです。
またデストラクタでは確保されていたら開放しています。
◎ピンのチェック
////////////////////////////////////////////////////////////////////// // 入力ピンの接続チェック HRESULT CMoFilter::CheckInputType(const CMediaType *mtIn) { // ビデオストリームであること if( *mtIn->FormatType() != FORMAT_VideoInfo ) return VFW_E_TYPE_NOT_ACCEPTED; // メディアタイプのチェック if( MediaCheck( mtIn )!=S_OK ) return VFW_E_TYPE_NOT_ACCEPTED; // OK return S_OK; } // 出力ピンの接続チェック HRESULT CMoFilter::CheckTransform(const CMediaType *mtIn, const CMediaType *mtOut) { // メディアタイプのチェック if( MediaCheck( mtIn )!=S_OK ) return VFW_E_TYPE_NOT_ACCEPTED; // 入力形式と出力形式が同じであること if( *mtIn!=*mtOut ) return VFW_E_TYPE_NOT_ACCEPTED; // OK return S_OK; } // メディアタイプのチェック HRESULT CMoFilter::MediaCheck(const CMediaType *MediaType) { if( IsEqualGUID( *MediaType->Type(),MEDIATYPE_Video ) ) // Videoか? { if( IsEqualGUID( *MediaType->Subtype(),MEDIASUBTYPE_RGB32) ) // RGB32ビットタイプか? { VIDEOINFOHEADER *pvi=(VIDEOINFOHEADER *)MediaType->Format(); if( pvi->bmiHeader.biBitCount == 32 ) // ビットカウントが32ビットであるか? { // OK return S_OK; } } } // NG return VFW_E_TYPE_NOT_ACCEPTED; } |
CheckInputTypeとCheckTransformでは
このフィルタで有効なメディアタイプであるかをチェックし、
有効ならS_OK、ダメならエラーを返します。
CheckInputTypeでは入力ピンの接続をチェックします。
入力ピンに接続できるのはRGBがそれぞれ8ビット毎で
1ピクセルが4バイトで構成されたストリームだけにしています。
色々なフォーマットに対応するのは困難なんで(^^;
RGB32タイプでない場合では前後にColorSpaceConverterフィルタが入るので
それを利用させて頂きます。
出力ピンでも同様です。
◎バッファサイズとメディアタイプ
////////////////////////////////////////////////////////////////////// // バッファの準備 HRESULT CMoFilter::DecideBufferSize(IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *pProperties) { // 入力ピンが接続されていること if( m_pInput->IsConnected()==FALSE ) return E_UNEXPECTED; // バッファサイズを取得 pProperties->cBuffers=1; pProperties->cbBuffer=m_pInput->CurrentMediaType().GetSampleSize(); ALLOCATOR_PROPERTIES Actual; HRESULT rc; if( FAILED(rc=pAlloc->SetProperties( pProperties,&Actual )) ) return rc; // 確保されたかのチェック if( (pProperties->cBuffers > Actual.cBuffers) || (pProperties->cbBuffer > Actual.cbBuffer) ) return E_FAIL; // OK return S_OK; } // 出力ピンが接続できるメディアタイプを返す HRESULT CMoFilter::GetMediaType(int iPosition, CMediaType *pMediaType) { // 入力ピンが接続されていない場合はエラー if( m_pInput->IsConnected()==FALSE ) return E_UNEXPECTED; // 番号のチェック(iPosition==0のみOK) if( iPosition<0 ) return E_INVALIDARG; // 0未満 if( 0<iPosition ) return VFW_S_NO_MORE_ITEMS; // 1以上 // 入力メディアタイプをそのまま返す *pMediaType=m_pInput->CurrentMediaType(); return S_OK; } |
出力ピンに接続する下流のフィルタからの呼び出しに答えるメソッドです。
DecideBufferSizeはバッファのサイズを返します。
入力ピンのメディアタイプからサイズを取得してます。
GetMediaTypeは出力できるメディアタイプを返します。
これも入力と同じメディアタイプを返します。
◎接続時
////////////////////////////////////////////////////////////////////// // 接続完了 HRESULT CMoFilter::CompleteConnect( PIN_DIRECTION direction,IPin *pReceivePin ) { // サイズを取得 AM_MEDIA_TYPE *MType=NULL; // メディアタイプ VIDEOINFOHEADER *VInfo=NULL; // ビデオ情報 long ImgX=0,ImgY=0; // ビデオサイズ MType=&m_pInput->CurrentMediaType(); VInfo=(VIDEOINFOHEADER *)(MType->pbFormat); ImgX=VInfo->bmiHeader.biWidth; ImgY=VInfo->bmiHeader.biHeight; // 既に確保していたら解放する if( mImageBuffer!=NULL ) { delete[] mImageBuffer; mImageBuffer=NULL; } // イメージ保存用バッファの確保 mImageBuffer=new long[ImgX*ImgY]; memset( mImageBuffer,0,ImgX*ImgY*sizeof(long) ); return S_OK; } |
フィルタの接続が完了するとCompleteConnectが呼び出されます。
ここでは映像を保存するための領域を確保しています。
サイズは親クラスのm_pInputから取得できますので
ここからサイズ等を取得します。
◎転送
////////////////////////////////////////////////////////////////////// // 転送 HRESULT CMoFilter::Transform(IMediaSample *pIn, IMediaSample *pOut) { HRESULT rc; // 転送 if( FAILED(rc=Copy( pIn,pOut )) ) return rc; // 更新 if( FAILED(rc=UpdateBitmap( pOut )) ) return rc; // OK return S_OK; } // 入力側のデータを出力側にコピーする HRESULT CMoFilter::Copy(IMediaSample *Src, IMediaSample *Dst) const { // ビットマップ転送 long srcsize=Src->GetActualDataLength(); BYTE *srcbuff,*dstbuff; Src->GetPointer( &srcbuff ); Dst->GetPointer( &dstbuff ); CopyMemory( (void *)dstbuff,(void *)srcbuff,srcsize ); // タイムスタンプを複写 REFERENCE_TIME ts,te; if( SUCCEEDED(Src->GetTime( &ts,&te )) ) Dst->SetTime( &ts,&te ); LONGLONG ms,me; if( SUCCEEDED(Src->GetMediaTime( &ms,&me )) ) Dst->SetMediaTime( &ms,&me ); // 同期ポイントを複写 HRESULT hr=Src->IsSyncPoint(); if( hr==S_OK ) Dst->SetSyncPoint( TRUE ); else if( hr==S_FALSE ) Dst->SetSyncPoint( FALSE ); else return E_UNEXPECTED; // メディアタイプを複製 AM_MEDIA_TYPE *mt; Src->GetMediaType( &mt ); Dst->SetMediaType( mt ); DeleteMediaType( mt ); // 連続性情報を複写 hr=Src->IsDiscontinuity(); if( hr==S_OK ) Dst->SetDiscontinuity( TRUE ); else if( hr==S_FALSE ) Dst->SetDiscontinuity( FALSE ); else return E_UNEXPECTED; // バッファ内の有効なデータ長を複写 Dst->SetActualDataLength( Src->GetActualDataLength() ); return S_OK; } // 出力イメージを更新する HRESULT CMoFilter::UpdateBitmap(IMediaSample *MediaSample) { // 情報取得 BYTE *BmpBuff=NULL; // ビットマップ領域の先頭 long BmpBuffSize=0; // ビットマップ領域のサイズ AM_MEDIA_TYPE *MType=NULL; // メディアタイプ VIDEOINFOHEADER *VInfo=NULL; // ビデオ情報 long ImgX=0,ImgY=0; // ビデオサイズ MediaSample->GetPointer( &BmpBuff ); BmpBuffSize=MediaSample->GetSize(); MType=&m_pInput->CurrentMediaType(); VInfo=(VIDEOINFOHEADER *)(MType->pbFormat); ImgX=VInfo->bmiHeader.biWidth; ImgY=VInfo->bmiHeader.biHeight; // イメージの変更 BYTE *b0=(BYTE *)mImageBuffer; BYTE *b1=BmpBuff; long d0,d1,dd; long z; for( z=0;z<BmpBuffSize;z++ ) { d0=*b0; d1=*b1; dd=(d0*95+d1*5)/100; *b1=(BYTE)dd; b0++; b1++; } // 現在のフレームを保存しておく CopyMemory( (void *)mImageBuffer,(void *)BmpBuff,BmpBuffSize ); return S_OK; } |
Transformは1フレーム毎に呼び出されて、入力から出力に映像を転送してやります。
このとき映像に手を加えることになります。
Copyメソッドでは入力のデータをそっくり出力にコピーしています。
映像だけでなく、タイムスタンプとかメディアタイプとかもコピーしてやります。
UpdateBitmapでは出力側に転送したイメージと
メモリに確保しておいた前回のイメージをブレンドしています。
ブレンドの処理は見ての通りいいかげんなやつです(^^;
最後に次回の為にイメージを保存用メモリにコピーしておきます。
以上のソースをコンパイルして出力されたMoFilter.axをRegsvr32でレジストリに登録すれば
GraphEditなどで使うことが出来ます。
登録したフィルタはDirectShow
Filtersカテゴリに入っていると思いますので、
それをグラフに入れて、Render Media Fileなどを行えば
あとは上手いことグラフを作ってくれると思います。
全ソースはこちらになります。
SDKサンプルにはコントラストを変更したり、エンボス加工したりできるフィルタのサンプルがありますので
それを改造するだけで結構簡単にオリジナルの変換フィルタは作れると思います。
ちゃんとしたフィルタを作るならフィルタの構造よりも画像処理などの技術の方が難しいですが。
サンプルで面白と思ったのは、scopeってやつです。
オーディオ用のレンダリングフィルタで登録するとOscilloscopeという名前で登録されます。
こんな感じでInfinitePinTeeFilterを使って
通常のオーディオレンダリングフィルタと並列に配置します。
Oscilloscopeフィルタをグラフに追加したときにこのようなウィンドウが開いて、
メディアを再生するとリアルタイムに再生される音声の波形を見ることが出来ます。
何に使えるのかはさておき、見てるだけで楽しいですよ(^^;