Microsoft DirectX 8.0

変換フィルタの作成

変換フィルタは、入力ピンに入力されたメディア データを変換し、変換後のデータを出力ピンに送信する。変換フィルタは、データの圧縮や圧縮解除、オーディオ データやビジュアル データの分割、コントラストやワーブリングなどのエフェクトのメディア データへの適用といった用途に使用することができる。Microsoft® DirectShow® には、いろいろな変換を行ういくつかのサンプル 変換フィルタが用意されている。

ここでは、DirectShow C++ クラス ライブラリを使用する変換を作成するステップを説明する。5 つの基本ステップを説明する中で、最後のステップとして、派生クラスの必要なメンバ関数をオーバーライドして変換フィルタを実装する方法を説明する。また、変換フィルタの作成に伴う 2 つのよくある質問 (どの基底クラスを使用すべきか?、どのようにメンバ関数をオーバーライドするか?) についての答えも示す。

このトピックは、以下のセクションを含んでいる。

詳細については、以下のトピックを参照すること。

変換フィルタを作成する

変換フィルタを作成する作業は、以下のステップに分けられる。

  1. フィルタがメディア サンプルをコピーする必要があるか、またはその場でメディア サンプルを処理できるかを判断する。

    メディア ストリーム内のコピーの数は少ないほど良い。しかし、コピー操作が必要なフィルタもある。この点が基底クラスの選択に影響する。

  2. 使用する基底クラスを決め、その基底クラスからフィルタ クラス (および必要であればピン クラス) を派生させる。

    このステップでは、フィルタのヘッダーを作成する。多くの場合は、変換基底クラスを使用して適切な変換フィルタ クラスからクラスを派生させ、いくつかのメンバ関数をオーバーライドする。また、汎用的な基底クラスを使うこともできる。接続およびネゴシエーション メカニズムのほとんどはこれらのクラスによって実装されるが、さらにメンバ関数をオーバーライドすることによって柔軟性を提供することができる。

  3. フィルタをインスタンス化するのに必要なコードを追加する。

    このステップでは、スタティックな CreateInstance メンバ関数を派生クラスに追加し、さらにフィルタの名前、CLSID、およびそのメンバ関数へのポインタを格納するグローバル配列を追加する。

  4. フィルタの一意なインターフェイスを渡すための NonDelegatingQueryInterface メンバ関数を追加する。

    このステップは、Component Object Model (COM) の、基底クラス以外のインターフェイスの実装に関する側面を示している。

  5. 基底クラス メンバ関数をオーバーライドする。

    この作業には、フィルタに固有の変換関数を書いて、アロケータ サイズの設定やメディア タイプの提供などの、接続プロセスに必要ないくつかのメンバ関数をオーバーライドすることが含まれる。

フィルタがメディア サンプルをコピーする必要性を判断する

すべてのコピー処理は貴重な CPU サイクルを使用するので、フィルタの開発者は可能であればメディア サンプルのコピー処理を行わないようにすべきである。最も望ましいのは、別のフィルタから取得したアロケータ上でその場でメディア サンプルを変更するようなフィルタを書くことである。場合によってはこのような処理が不可能で、コピー処理を実行しなければならないことがある。

コピーの必要がない変換インプレイス フィルタの実行時のオーバーヘッドは、関数のそれに比べて大きくなるということはない。しかし、変換をフィルタとしてパッケージ化することにより、フィルタ グラフ アーキテクチャの完全な柔軟性を手に入れることができる。

以下のような場合は、フィルタを変換インプレイス フィルタではなくコピー変換フィルタとして作成する。

使用する基底クラスを決める

変換フィルタの基底クラスを選ぶ前に、フィルタに複数の入力ピンおよび出力ピンが必要かどうかを決めなければならない。複数のピンが必要な場合は、CBaseFilter からフィルタ クラスを派生しなければならない。

フィルタでビデオ変換を行う必要がある場合は、CVideoTransformFilter からフィルタ クラスを派生しなければならない。

それ以外の場合は、CTransformFilter または CTransInPlaceFilter からフィルタ クラスを派生する。どちらを使用するかを決めるためには、フィルタがメディア サンプルをコピーする必要があるか、またはメディア サンプルをその場で変換することができるかを決めなければならない。すべてのコピー処理は貴重な CPU サイクルを使用するので、フィルタの開発者は可能であればメディア サンプルのコピー処理を行わないようにすべきである。最も望ましいのは、別のフィルタから取得したアロケータ上でその場でメディア サンプルを変更するようなフィルタを書くことである。場合によってはこのような処理が不可能で、コピー処理を実行しなければならないことがある。

コピーの必要がない変換インプレイス フィルタの実行時のオーバーヘッドは、関数のそれに比べて大きくなるということはない。しかし、変換をフィルタとしてパッケージ化することにより、フィルタ グラフ アーキテクチャの完全な柔軟性を手に入れることができる。

以下のような場合は、フィルタを変換インプレイス フィルタではなくコピー変換フィルタとして作成する。

変換フィルタがメディア サンプルをコピーするか、またはメディア サンプルをその場で変換するかを決定した後は、使用する基底クラスと、オーバーライドおよび実装するメンバ関数を決める必要がある。その後、派生クラスを定義する。

基底クラスの一部のメンバ関数は、基底クラスで純粋仮想関数として宣言されている (実装を持たない) か、またはエラー値を返す以外の動作を行わないデフォルト実装を持っているので、派生クラスでオーバーライドしなければならない。

フィルタ クラスは、変換基底クラス CTransformFilterCTransInPlaceFilter、または CVideoTransformFilter から派生させるか、または汎用的な CBaseFilter フィルタ クラスから派生させる。接続、メディア タイプ、およびアロケータ ネゴシエーション コードのほとんどは基底クラスで扱われ、変換クラスによって継承される。変換クラスを使用すると、1 つのフィルタ クラス (ピン クラスなし) を派生させるだけでフィルタを作成することができる。変換クラスは、変換フィルタを作成するプロセスを簡単にするために、変換フィルタの動作についていくつかの仮定を行っている。

CTransformFilter および CTransInPlaceFilter について、および派生クラスによって通常オーバーライドされるメンバ関数については、「変換基底クラス CTransformFilter および CTransInPlaceFilter の使用」を参照すること。

フィルタをインスタンス化する

すべてのフィルタは、基底クラスがフィルタをインスタンス化するためのコードを追加しなければならない。フィルタをインスタンス化するには、2 種類のコードをフィルタに追加する必要がある。1 つは派生フィルタ クラスに追加するスタティックな CreateInstance メンバ関数で、もう 1 つは、この関数へのアクセス法を基底クラスのクラス ファクトリに通知するためのコードである。

一般に、CreateInstance メンバ関数は派生フィルタ クラスに対してコンストラクタを呼び出す。以下に、CreateInstance メンバ関数の実装例を示す。

CUnknown *CGargle::CreateInstance(LPUNKNOWN punk, HRESULT *phr) {

    CGargle *pNewObject = new CGargle(NAME("Gargle Filter"), punk, phr);
    if (pNewObject == NULL) {
    *phr = E_OUTOFMEMORY;
    }

return pNewObject;
} // CreateInstance

クラス ファクトリと通信を行うためには、CFactoryTemplate オブジェクトのグローバル配列を g_Templates として宣言し、フィルタの名前、フィルタのクラス識別子 (CLSID)、およびフィルタ オブジェクトを作成するスタティック CreateInstance メンバ関数へのポインタを指定する。

// CreateInstance メカニズムに必要
CFactoryTemplate g_Templates[2]=
    { { L"Gargle filter"              , &CLSID_Gargle , CGargle::CreateInstance          }
    , { L"Gargle filter Property Page", &CLSID_GargProp, CGargleProperties::CreateInstance}
    };

int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);

フィルタが自己登録を行うようにするには、CFactoryTemplate テンプレートにパラメータを追加する。この処理については、「DirectShow オブジェクトの登録方法」を参照すること。

最後に、フィルタを Strmbase.lib にリンクし、.def ファイルを使用して DllGetClassObject および DllCanUnloadNow をエクスポートする。

追加したインターフェイスを NonDelegatingQueryInterface で利用できるようにする

IUnknown メンバ関数 (基底クラスでは INonDelegatingUnknown と呼ばれる) を実装する必要があるのは、基底クラスに含まれていないインターフェイス (たとえばプロパティ ページの作成に必要なインターフェイス) を追加するフィルタだけである。基底クラスは、IUnknown メソッドのデフォルトの実装を提供する。COM ベースのコード内の IUnknown メソッドは、オブジェクトからインターフェイスを取得し、そのインターフェイスの参照カウントをインクリメントおよびデクリメントする。たとえば、IUnknown::QueryInterface メソッドはインターフェイスをオブジェクトから取得する。

DirectShow では、INonDelegatingUnknown という特殊な IUnknown クラスが定義されていて、このクラスのメソッドは IUnknown と同じ働きを持つ。(名前が変更されているのは、オブジェクトを集成できるようにするためである。) NonDelegatingQueryInterface メソッドは、いずれかのオブジェクトまたはアプリケーションが、実装するインターフェイスについてピンまたはフィルタに照会する場合に呼び出される。基底クラス実装にリストされている以外のインターフェイスをフィルタが実装する場合は、NonDelegatingQueryInterface メソッドをオーバーライドして、実装しているインターフェイスへのポインタを返す必要がある。たとえば、以下のコード例は、メンバ関数をオーバーライドして、ISpecifyPropertyPages および IPersistStream インターフェイスへの参照を配布する。

// 永続ストリーム、プロパティ ページ、および IGargle インターフェイスを公開する。
STDMETHODIMP CGargle::NonDelegatingQueryInterface(REFIID riid, void **ppv) {

if (riid == IID_IGargle) {
return GetInterface((IGargle *) this, ppv);
} else if (riid == IID_ISpecifyPropertyPages) {
return GetInterface((ISpecifyPropertyPages *) this, ppv);
} else if (riid == IID_IPersistStream) {
        AddRef();     // 参照カウントを追加する
*ppv = (void *)(IPersistStream *)this;
return NOERROR;

} else {
return CTransInPlaceFilter::NonDelegatingQueryInterface(riid, ppv);
    }
} // NonDelegatingQueryInterface

注 :  このサンプルは、メンバ関数の CTransInPlaceFilter 実装を呼び出して終了処理を行う。

基底クラス メンバ関数をオーバーライドする

使用する基底クラスを決めたら (「使用する基底クラスを決める」を参照)、ヘッダーを書いて、実装するメンバ関数を定義する。このとき、変換基底クラス (CTransformFilter または CTransInPlaceFilter) または汎用の CBaseFilter フィルタ クラスのいずれからフィルタ クラスを派生させるか決める必要がある。ここでは、以下のメンバ関数の実装法について説明する。

Transform メンバ関数をオーバーライドする

派生クラスの Transform メンバ関数は、別のサンプルを転送するために、フィルタの入力ピンの IMemInputPin::Receive メソッドが呼び出されるごとに呼び出される。フィルタの実際の目的の処理を実行するコードを、このメンバ関数またはそこから呼び出される関数に挿入する。一般に、コピー変換フィルタは、変換コードをプライベートな Copy メンバ関数に関連付けるが、変換インプレイス関数は 1 つのバッファ内のコードを変更する。

CheckInputType メンバ関数をオーバーライドする

ピン接続の間、提示されたメディア タイプが受け付け可能かどうかを調べるために入力ピンの CheckMediaType メンバ関数が呼び出される。CTransformInputPin::CheckMediaType メンバ関数は、派生フィルタ クラスの CheckInputType メンバ関数をそのメディア タイプで呼び出すように実装される。フィルタが対応しているメディア タイプを使用するためには、このメンバ関数を実装しなければならない。以下のコード例に、MEDIATYPE_Audio 以外のメディア タイプを拒否する CGargle::CheckInputType メンバ関数の一部を示す。

HRESULT CGargle::CheckInputType(const CMediaType *pmt) {    
    ...
    // Audio 以外のタイプを拒否する
    if (pmt->majortype != MEDIATYPE_Audio) {
    return E_INVALIDARG;
    }

CheckTransform メンバ関数をオーバーライドする

コピー変換フィルタは、入力ピンから出力ピンにメディア タイプを変換することができる。したがって、出力ピンが接続される (すなわちそのメディア タイプが既知である) 場合、接続時に CTransformInputPin::CheckMediaType メンバ関数が呼び出されると、入力タイプから出力タイプへの変換が有効かどうかを調べるために、派生クラスの CheckTransform メンバ関数が呼び出される。このメンバ関数は、CTransformOutputPin::CheckMediaType が呼び出されたときも呼び出される。

CTransInPlaceFilter クラスでは、このメンバ関数は単に S_OK を返すために基底クラスのヘッダー ファイル内で実装される。なぜなら、このメンバ関数を呼び出す CTransformFilter の関数は、CheckInputType を呼び出すために CTransInPlaceFilter でオーバーライドされるからである。この操作は、コピー変換フィルタの場合と同様に、変換インプレイス フィルタ内でメディア タイプが変化しないことを想定している。

DecideBufferSize メンバ関数をオーバーライドする

コピー変換フィルタは、コピー先のアロケータのプロパティを設定しなければならないことがある。このような状況は、ダウンストリーム フィルタが新しく作成されたアロケータを提供した場合 (すなわち、さらに先のダウンストリームからのアロケータを渡しているのではない場合) や、出力ピンが独自のアロケータを作成しなければならない場合に起こりやすい。この場合、純粋仮想 CBaseOutputPin::DecideBufferSize メンバ関数が CBaseOutputPin::DecideAllocator メンバ関数から呼び出され、派生クラスが参照先のアロケータ オブジェクトの IMemAllocator::SetProperties メソッドを呼び出すことによってバッファ要求を満たす。

別のフィルタのアロケータは常に使用されているので、CTransInPlaceFilter::DecideBufferSize メソッドが呼び出されることはない。このメソッドは基底クラスのヘッダー ファイルで実装されて E_UNEXPECTED を返す。

GetMediaType メンバ関数をオーバーライドする

ピンは、列挙子を提供して、ほかのオブジェクトがピンのメディア タイプを判定できるようにする。ピンは、メディア タイプ列挙子 (IEnumMediaTypes インターフェイス) を提供する (メディア タイプ列挙子は、ピン クラスの GetMediaType メンバ関数を呼び出すためにピン基底クラスが実装する)。コピー変換フィルタ クラスでは、それぞれのピンの CTransformOutputPin::GetMediaType メンバ関数は、フィルタ クラスの仮想 CTransformFilter::GetMediaType メンバ関数を呼び出すだけである。このメンバ関数を派生クラスで実装して、メディア タイプのリスト内のサポートされている各メディア タイプを提供しなければならない。

変換インプレイス クラスでは、列挙子は、変換フィルタのアップストリームとダウンストリームのフィルタの間に透過チャンネルを形成する。変換フィルタの入力ピンが列挙を行わなければならない場合、入力ピンはダウンストリーム フィルタの入力ピンから列挙子を取得する。出力ピンが列挙を行わなければならない場合、出力ピンはアップストリーム フィルタの出力ピンから列挙子を取得する。したがって、2 つの変換インプレイス フィルタを互いに接続するためには、そのうちの少なくとも 1 つがそれ以外のものに接続していなければならない。なぜなら、どちらの変換インプレイス フィルタも接続のためのメディア タイプを提示できないからである。

ピン メンバ関数をオーバーライドする

フィルタ クラスを変換クラスから派生し、複数の入力ピンまたは出力ピンを使用する場合は、ピン クラス (たとえば CTransformInputPin または CTransformOutputPin) をオーバーライドする必要がある。ピン クラスをオーバーライドする場合は、CTransformFilter または CTransInPlaceFilterGetPin メンバ関数をオーバーライドして、派生クラスからピン オブジェクトを作成できるようにしなければならない。一方のピン クラス (たとえば CTransformInputPin) をオーバーライドし、GetPin をオーバーライドしてピン オブジェクトを作成する場合は、さらに同じ基底クラスのもう一方のピン オブジェクト (たとえば CTransformOutputPin) も作成するように GetPin をオーバーライドしなければならない。

複数の入力または出力ピンを使用する場合は、変換クラスよりも CBaseFilter からフィルタを派生させる方が簡単である。

CBaseOutput::DecideAllocator メンバ関数をオーバーライドする

基底クラスは CBaseOutputPin::DecideAllocator を実装して、出力ピンが自動的にダウンストリーム ピンのアロケータを使うようにする。派生クラスにおける最も一般的な変更は、オブジェクト独自のアロケータ (またはアップストリーム フィルタからのアロケータ) の使用を強制することである。たとえば DirectShow のモデルでは、ソース フィルタはメディア サンプルを次のフィルタにプッシュし、独自のアロケータを必要とする。たとえば、変換インプレイス フィルタを書いてそれをソース フィルタとデコンプレッサ フィルタの間に配置した場合、変換フィルタはソース フィルタのアロケータをデコンプレッサに提示しなければならない。したがって、CBaseOutputPin::DecideAllocator メンバ関数をオーバーライドしなければならない。