Microsoft DirectShow でのグラフ作成
Mike Wasson
Microsoft Windows Digital Media Division
December 2001
**要約:**この記事では、 DirectShow でフィルタ グラフをプログラム的に作成する方法について説明します。アプリケーションからフィルタ グラフを作成する 4 つのシナリオ例を紹介します。個々のシナリオにはサンプル コードが含まれており、そのコードが実行するステップの説明があります。
はじめに
フィルタ グラフは Microsoft® DirectShow® の中心となるコンセプトです。フィルタは、データの作成、変換、レンダリングを行う COM コンポーネントです。フィルタ グラフは、ピンによって接続され、同じ参照クロックに同期されているフィルタのセットのことです。フィルタ グラフを視覚的に理解するには、ボックス ダイアグラムを描くのが一番簡単です。個々のボックスはフィルタを表し、線はピン接続を表し、矢印はデータ フローの向きを示します。
図 1. フィルタ グラフの例
DirectShow はフィルタ グラフ組み立てのための、いくつかのアプリケーション プログラミング インターフェイス (API) を用意しています。これには上位水準の API( 「グラフ全体を作成する」 ) から下位水準の API( 「このピンをあのピンに接続する」 ) までが含まれます。この記事では 4 つのグラフ作成シナリオを紹介し、それぞれのサンプル コードを示します。読者は DirectShow SDK の「 DirectShow の概要」を読んでおり、 DirectShow プログラミング モデルについての基本的な知識を持っているものとします。
この記事には以下のトピックがあります。
DirectShow のグラフ作成コンポーネント
フィルタ グラフの作成に使用する DirectShow オブジェクトについて簡単に説明します。Filter Graph Manager の使用
Filter Graph Manager オブジェクトがサポートしているグラフ作成手法について説明します。例 1: DV のデコーディングの解像度の設定
この例では、 DV ファイルをレンダリングし、デコーディングの解像度を設定します。例 2: カスタム ソースのレンダリング
この例では、カスタム ソース フィルタからのビデオ ストリームをレンダリングします。例 3: エフェクト フィルタを通してのオーディオのレンダリング
この例では、オーディオ エフェクト フィルタを通してオーディオ ストリームをレンダリングします。例 4: タイプ 1 DV からタイプ 2 DV への変換
この例では、タイプ 1 のインターリーブド DV ファイルをタイプ 2 の DV ファイルに変換します。Capture Graph Builder の使用
フィルタ グラフの作成に便利なもう 1 つのコンポーネントについて説明します。トラブルシューティング ヒント
DirectShow フィルタ グラフを作成しているときに遭遇する可能性のある一般的な問題についてのヒントを示します。
DirectShow のグラフ作成コンポーネント
DirectShow は、フィルタ グラフ作成に使用可能ないくつかのコンポーネントを備えています。これには以下のものが含まれます。
Filter Graph Manager
このオブジェクトがフィルタ グラフを制御します。サポートしているインターフェイスには、IGraphBuilder、IMediaControl、IMediaEventEx などがあります。すべての DirectShow アプリケーションはこのオブジェクトをいずれかの時点で使用しますが、場合によっては別のオブジェクトがアプリケーションのために Filter Graph Manager を作成することもあります。Capture Graph Builder
このオブジェクトも、フィルタ グラフ作成のためのメソッドを持っています。もともとは、その名前が示すようにビデオ キャプチャを実行するグラフの作成のために設計されたものですが、その他のさまざまなタイプのカスタム フィルタ グラフにも利用できます。このオブジェクトは ICaptureGraphBuilder2 インターフェイスをサポートしています。Filter Mapper と System Device Enumerator
これらのオブジェクトは、ユーザーのシステム上に登録されている、またはハードウェア デバイスを表すフィルタを探します。DVD Graph Builder
このオブジェクトは、 DVD の再生とナビゲーションのためのフィルタ グラフを作成します。IDvdGraphBuilder インターフェイスをサポートしています。スクリプト ベースのアプリケーションは、 MSWebDVD Microsoft Active X® コントロールを使って DVD を再生できます ( この文章では DVD の再生は扱いません ) 。Video Control
この ActiveX コントロールは Microsoft Windows® XP Home Edition と Windows XP Professional に用意されています。このコントロールは、デジタルおよびアナログ TV を DirectShow で処理します ( この文章では TV のチューニングは扱いません ) 。
Filter Graph Manager の使用
Filter Graph Manager は以下のグラフ作成メソッドをサポートしています。
IFilterGraph::ConnectDirect メソッドは、 2 つのピンの間に直接の接続を作成しようと試みます。ピンを接続できない場合には、メソッドは失敗します。
IGraphBuilder::Connect メソッドも 2 つのピン を接続します。可能ならば直接の接続を作成します。そうでなければ、中間フィルタを使って接続を完成させます。
IGraphBuilder::Render メソッドは、出力ピンを出発点として、グラフの残りの部分を作成します。このメソッドは、レンダラ フィルタに達するまで、必要に応じてフィルタを追加して下流へと進みます。
IGraphBuilder::RenderFile メソッドは、完全なファイル再生グラフを作成します。IGraphBuilder::AddSourceFilter メソッドを使ってファイルのタイプに応じたソース フィルタを追加し、IGraphBuilder::Render を使ってグラフの残りの部分を作成します。
IFilterGraph::AddFilter メソッドは、グラフにフィルタを追加します。フィルタを他のフィルタに接続することはしません。アプリケーションは、CoCreateInstance を呼び出すか、 Filter Mapper または System Device Enumerator オブジェクトを使うことで、フィルタのインスタンスを事前に作成しておかなくてはなりません。
Intelligent Connect
「 Intelligent Connect 」という用語は、 Filter Graph Manager がフィルタ グラフの全体または一部を作成するために使うアルゴリズムのセットを指します。 Filter Graph Manager は、グラフを完成させるために追加のフィルタが必要になったときに次のような処理を行います。
- グラフ中に、少なくとも 1 つの未接続入力ピンを持つフィルタがすでに存在している場合、 Filter Graph Manager はそのフィルタを使用しようと試みます。
- そ のようなフィルタが存在しない場合、 Filter Graph Manager はレジストリの中で、その接続に応じた正しいメディア タイプを受け付けるフィルタを探します。個々のフィルタは、そのフィルタがグラフを完成させるためにどれほど有用かをおおまかに示す "Merit" という名前のレジストリ値を持っています。 Filter Graph Manager は、メリットの値の順にフィルタを試していきます。個々のストリーム タイプ ( オーディオ、ビデオ、 MIDI など ) について、デフォルト レンダラは高いメリット値を持っています。デコーダも高いメリット値を持ちます。特殊目的のフィルタは、低いメリット値を持ちます。
Filter Graph Manager は、行き詰まった場合には、別のフィルタの組み合わせを試します。詳細については、 DirectShow SDK 8.1 ドキュメントのトピック「 Intelligent Connect 」を参照してください。
コード例
この文章のすべての例は同じフレームワークを使用しています。
#include <dshow.h>
void __cdecl main(void)
{
// COMを初期化する
CoInitialize(0);
// Filter Graph Managerを作成する
IGraphBuilder *pGraph = 0;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, 0,
CLSCTX_INPROC_SERVER, IID_IGraphBuilder,
reinterpret_cast(&pGraph));
/* 例の残りの部分 */
// クリーン アップ
pGraph->Release();
/* 他のインターフェイスを解放する */
CoUninitialize();
}
コードを単純にするために、サンプルでは堅牢なエラー チェックは行っていません。
例 1: DV のデコーディングの解像度の設定
最初の例では、 DV Video Decoder フィルタの IIPDVDec インターフェイスを使って、 DV ファイルをレンダリングし、デコーディングの解像度を設定します。
この例の内容
- グラフ内のフィルタを列挙する方法。
- フィルタのインターフェイスを照会する方法。
説明
DV Video Decoder フィルタは、 DV エンコードされたビデオを非圧縮ビデオに変換します。出力解像度としては、フル、 1/2 、 1/4 、および 1/8 の 4 つをサポートしています ( 具体的なピクセル数は、その形式が NTSC と PAL のどちらなのかに依存します ) 。出力解像度を設定するには、アプリケーションはフィルタの IIPDVDec インターフェイスを使用する必要があります。フィルタへのポインタがあれば、 **QueryInterface ** を通して IIPDVDec インターフェイスを取得することができますが、まず最初にそのフィルタを発見する必要があります。この例は、 DV Video Decoder を探すために、グラフ内のすべてのフィルタを列挙する方法を示しています。
以下のステップを実行します :
- RenderFile メソッドを使って、ファイル再生のためのグラフを作成します。指定されたファイルが DV ビデオを含んでいる場合、フィルタ グラフには DV Video Decoder フィルタが含まれることになります。
- フィルタを探すには、IGraphBuilder::EnumFilters メソッドを使用します。このメソッドは、現在フィルタ グラフに含まれているフィルタのコレクションを列挙するオブジェクトを返します。列挙子オブジェクトは IEnumFilters インターフェイスを公開しています。
- ループの中で IEnumFilters::Next メソッドを呼び出して、コレクションの中の各フィルタへのポインタを取得します。Next メソッドは IBaseFilter ポインタを返すので、これに対して IIPDVDec を照会できます。ループ内では IBaseFilter ポインタを繰り返しのたびに解放し、最後には IEnumFilters インターフェイスを解放することを忘れないようにしてください。
一般に、フィルタを探すときには、フィルタが公開しているインターフェイスを通して行う方が、名前によって検索するよりも信頼性があります。イン ターフェイスは関連する関数のセットをカプセル化しているので、フィルタが同じインターフェイスを公開する別のフィルタに置き換えられた場合でも、コード は正常に動作を続けます。
コード例
// ファイルをレンダリングする
hr = pGraph->RenderFile(L"C:\\My_dv_file.avi", 0);
// フィルタを列挙する
IEnumFilters *pEnum = 0;
pGraph->EnumFilters(&pEnum);
IBaseFilter *pF = 0;
while (S_OK == pEnum->Next(1, &pF, 0))
{
IIPDVDec *pDvDec = 0;
hr = pF->QueryInterface(IID_IIPDVDec,
reinterpret_cast(&pDvDec));
pF->Release();
if (SUCCEEDED(hr))
{
// 出力解像度を 1/4 に設定する
pDvDec->put_IPDisplay(DVRESOLUTION_QUARTER);
pDvDec->Release();
break;
}
}
pEnum->Release();
例 2: カスタム ソースのレンダリング
次の例では、カスタム ソース フィルタからのビデオ ストリームをレンダリングします。
この例の内容
- フィルタ グラフにフィルタを追加する方法。
- フィルタ上のピンを列挙し、ピンの向きを調べる方法。
- 出力ピンからのストリームをレンダリングする方法。
説明
RenderFile メソッドは、ファイル拡張子か、ファイル内の「シグニチャ」バイトのセットをもとに、ファイルのデフォルト ソース フィルタを自動的に探します。ただし、カスタム ソース フィルタを使いたい場合は、プログラマが自分でグラフに追加する必要があります。この例では、 DirectShow SDK の Ball Filter サンプルを使用しています。
Ball Filter は、ビデオ ストリームを配信する 1 つの出力ピンを持っています。IGraphBuilder::Render メソッドを呼び出すことで、このストリームをビデオ レンダラに送信できます。Render メソッドは、グラフへのビデオ レンダラの追加やピンの接続などの残りの作業を行います。 Ball Filter が圧縮済みビデオを配信していた場合、Render メソッドは適切なデコーダ フィルタも追加します。
以下のステップを実行します。
- サンプルを実行する前に、プロジェクトをビルドし、システム上に DLL を登録します。
- アプリケーションの中で Ball Filter のクラス識別子 (CLSID) を宣言します。 CLSID がないと、 COM はフィルタを含んでいる DLL を見つけることができません。 CLSID は Ball.h ヘッダー ファイルに記されています。
- CoCreateInstance 関数を呼び出して、 Ball Filter のインスタンスを作成します。
- フィルタをフィルタ グラフに追加する IFilterGraph::AddFilter メソッドを呼び出します。
- IBaseFilter::EnumPins メソッドを呼び出して、フィルタ上のピンを列挙します。このメソッドは EnumFilters メソッドに似ています。このメソッドは IEnumPins インターフェイス ポインタを返します。
- IEnumPins::Next メソッドを使って、個々のピンをループで処理します。
- **IPin::QueryDirection ** を呼び出してピンの向きをチェックします。ピンが出力ピンならば、 IGraphBuilder::Render を呼び出して、出力ピンへのポインタを渡します ( 我々はすでに、 Ball Filter が出力ピンを 1 つ持ち、入力ピンを持っていないことを知っていますが、次に示すコードは一般的な解決法です ) 。
コード例
// Ball CLSIDをインクルードする
#include
DEFINE_GUID(CLSID_BouncingBall, // ball.hから貼り付け
0xfd501041, 0x8ebe, 0x11ce, 0x81, 0x83, 0x00, 0xaa, 0x00, 0x57, 0x7d,
0xa1);
// Ballフィルタの新しいインスタンスを作成し、グラフに追加する
IBaseFilter *pBall = 0;
hr = CoCreateInstance(CLSID_BouncingBall, 0, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, reinterpret_cast(&pBall));
hr = pGraph->AddFilter(pBall, L"Bouncing Ball");
// 個々の出力ピンをレンダリングする
IEnumPins *pEnum = 0;
IPin *pPin = 0;
pBall->EnumPins(&pEnum);
while (hr = pEnum->Next(1, &pPin, NULL), hr == S_OK)
{
// ピンの向きをチェックする
PIN_DIRECTION PinDir;
pPin->QueryDirection(&PinDir);
if (PinDir == PINDIR_OUTPUT)
{
hr = pGraph->Render(pPin);
}
pPin->Release();
}
pEnum->Release();
pBall->Release();
例 3: エフェクト フィルタを通してのオーディオのレンダリング
第 3 の例は、オーディオ エフェクト フィルタを通してオーディオ ストリームをレンダリングします。
この例の内容
この例は、グラフにフィルタを選択的に追加して、 Intelligent Connect を活用する方法を示します。
説明
この例では、 DirectShow SDK の Gargle Filter サンプルを使用します。 Gargle Filter は、 PCM オーディオを入力として受け取り、ちょっとしたスクランブル処理を行い、スクランブルしたオーディオを出力として配信する単純なオーディオ エフェクトです。RenderFile を呼び出す前にこのフィルタをグラフに追加すると、 Filter Graph Manager は自動的に最初のオーディオ ストリームをフィルタに送り、さらにオーディオ レンダラに送ります。
以下のステップを実行します。
- Gargle Filter プロジェクトをビルドし、システム上に DLL を登録します。
- プロジェクト内で Gargle Filter の CLSID を宣言します。
- Gargle Filter のインスタンスを作成し、グラフに追加します。
- オーディオの入っているファイル名を指定して RenderFile を呼び出します。
RenderFile メソッドは、ソース フィルタを追加した後に、下流に向けてグラフの残りの部分を作成していきます。次のフィルタは、通常は AVI Splitter などの、ファイルをビデオおよびオーディオ ストリームにパースするパーサー フィルタです。前に述べたように、 Filter Graph Manager はすでにグラフに含まれているフィルタを使ってピンを接続しようと試みます。このため、パーサー フィルタのオーディオ出力を Gargle フィルタの入力ピンに接続します ( オーディオが圧縮されている場合には、両者の間にオーディオ展開フィルタを挿入します ) 。次に、 Gargle フィルタをオーディオ レンダラに接続します。ビデオ出力はビデオ レンダラに送られます。
コード例
次に、フィルタを作成し、これをグラフに追加するヘルパー関数を示します。
HRESULT AddFilter(
IGraphBuilder *pGraph, // Filter Graph Manager へのポインタ
const GUID& clsid, // 作成するフィルタの CLSID
LPCWSTR wszName, // フィルタの名前
IBaseFilter **ppF) // フィルタへのポインタが格納される
{
*ppF = 0;
IBaseFilter *pF = 0;
HRESULT hr = CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER,
IID_IBaseFilter, reinterpret_cast(&pF));
if (SUCCEEDED(hr))
{
hr = pGraph->AddFilter(pF, wszName);
if (SUCCEEDED(hr))
*ppF = pF;
else
pF->Release();
}
return hr;
}
次に、コード例の本体を示します。
// Gargle フィルタの CLSID を定義する
#include
#include "..\..\Filters\Gargle\garguids.h"
// これは筆者のコンピュータ上の Gargle Filter サンプルへの相対パスである
// 読者のコンピュータでは異なる場合がある
// グラフに Gargle フィルタを追加する
IBaseFilter *pGargle = 0;
hr = AddFilter(pGraph, CLSID_Gargle, L"Gargle", &pGargle);
// ファイルをレンダリングする
hr = pGraph->RenderFile(L"C:\\FileWithAudio.avi", 0);
注 ソース ファイルにオーディオが含まれていない場合、 Gargle フィルタは他のどのフィルタにも接続されません。これは、ピンを列挙して、IPin::ConnectedTo メソッドを呼び出せばチェックできます。ただしこのケースでは、グラフの実行時にフィルタが未接続の状態であってもかまいません。
例 4: タイプ 1 DV からタイプ 2 DV への変換
最後の例では、タイプ 1 のインターリーブド DV ファイルをタイプ 2 の DV ファイルに変換する方法を示します。
この例の内容
この例は、最も小さい制御レベルである、 2 つのピンを接続する方法を示します。
説明
DV データを AVI ファイルに格納する方法は 2 つあります。タイプ 1 のファイルでは、インターリーブ データがファイル内にそのままの形で格納され、個々の DV フレームは AVI ファイルの中で自己完結的な単位となります。再生時には、インターリーブされたストリームをそのビデオ コンポーネントとオーディオ コンポーネントに分割する必要があります。タイプ 2 のファイルでは、オーディオ データとビデオ データは AVI ファイルの中で別々の単位として格納されます。
DirectShow は、タイプ 1 のファイルをサポートするフィルタとして、 DV Splitter と DV Mux の 2 つを用意しています。 DV Splitter はインターリーブ DV ストリームをそのビデオおよびオーディオ コンポーネントに分割します。その逆に、 DV Mux はオーディオ ストリームと DV エンコードされたビデオ ストリームを、 1 つのインターリーブ ストリームに多重化します。
この例では、タイプ 1 のファイルをタイプ 2 のファイルに変換します。このためには、次のようなグラフを作成する必要があります。
図 2. DV ファイル変換グラフ
Filter Graph Manager は、我々がデータを Avi Mux フィルタに送信しようとしていることを知らないので、あまり役には立ちません。また、 AVI Splitter は DV Splitter なしに Avi Mux に直接に接続できるので、 Intelligent Connect は DV Splitter を追加してくれません。このため、このグラフはフィルタを 1 つずつ追加して作成する必要があります。
コード例
まず、ヘルパー関数をいくつか示します。次に示すのは、フィルタ上で特定の向きを持つ最初の未接続のピンを探すためのヘルパー関数です。
HRESULT GetUnconnectedPin(
IBaseFilter *pFilter, // フィルタへのポインタ
PIN_DIRECTION PinDir, // 探すピンの向き
IPin **ppPin) // ピンへのポインタが格納される
{
*ppPin = 0;
IEnumPins *pEnum = 0;
IPin *pPin = 0;
HRESULT hr = pFilter->EnumPins(&pEnum);
if (FAILED(hr))
{
return hr;
}
while (pEnum->Next(1, &pPin, NULL) == S_OK)
{
PIN_DIRECTION ThisPinDir;
pPin->QueryDirection(&ThisPinDir);
if (ThisPinDir == PinDir)
{
IPin *pTmp = 0;
hr = pPin->ConnectedTo(&pTmp);
if (SUCCEEDED(hr)) // 接続済み。われわれの探しているピンではない
{
pTmp->Release();
}
else // 適合するピンは見つからなかった
{
pEnum->Release();
*ppPin = pPin;
return S_OK;
}
}
pPin->Release();
}
pEnum->Release();
// Didn't find a matching pin.
return E_FAIL;
}
次に示すのは、各フィルタ上の最初の未接続のピンを使って、 2 つのフィルタを接続するヘルパー関数です。
HRESULT ConnectFilters(IGraphBuilder *pGraph, IBaseFilter *pSrc,
IBaseFilter *pDest)
{
IPin *pOut = 0, *pIn = 0;
HRESULT hr = GetUnconnectedPin(pSrc, PINDIR_OUTPUT, &pOut);
if (FAILED(hr))
{
return hr;
}
hr = GetUnconnectedPin(pDest, PINDIR_INPUT, &pIn);
if (FAILED(hr))
{
pIn->Release();
return hr;
}
hr = pGraph->Connect(pOut, pIn);
pOut->Release();
pIn->Release();
return hr;
}
これらのヘルパー関数を定義すれば、グラフを作成するためのコードは比較的簡単です。
// 必要なフィルタを追加する
IBaseFilter *pSrc = 0, *pMux = 0, *pWriter = 0, *pDVSplit = 0;
hr = pGraph->AddSourceFilter(L"C:\\DV_Interleaved.avi", L"Source", &pSrc);
hr = AddFilter(pGraph, CLSID_DVSplitter, L"DV Split", &pDVSplit);
hr = AddFilter(pGraph, CLSID_AviDest , L"AVI Mux", &pMux);
hr = AddFilter(pGraph, CLSID_FileWriter, L"Writer", &pWriter);
// 注: AddFilter 関数は例 3 で定義されている。
// File Writer フィルタを初期化する
IFileSinkFilter2 *pSink = 0;
hr = pWriter->QueryInterface(IID_IFileSinkFilter2,
reinterpret_cast(&pSink));
hr = pSink->SetFileName(L"C:\\Test.avi", NULL);
hr = pSink->SetMode(AM_FILE_OVERWRITE);
pSink->Release();
// Async File Source を DV Splitter に接続する
// これにより、両者の間に AVI Splitter が自動的に挿入される
// (この呼び出しが失敗した場合には、タイプ 1 の DV ファイルではない!)
hr = ConnectFilters(pGraph, pSrc, pDVSplit);
// DV Splitter を AVI Mux に接続する
// 両方のストリームを AVI Mux に入れたいので、この関数を 2 回呼び出す
hr = ConnectFilters(pGraph, pDVSplit, pMux);
hr = ConnectFilters(pGraph, pDVSplit, pMux);
// mux を File Writer に接続する
hr = ConnectFilters(pGraph, pMux, pWriter);
pDVSplit->Release();
pWriter->Release();
pSrc->Release();
pMux->Release();
Capture Graph Builder の使用
DirectShow を使って開発を行っていると、これまでに示したようなヘルパー関数のライブラリができあがってくるでしょう。しかし、ICaptureGraphBuilder2 インターフェイスにはいくつかのきわめて便利なヘルパー関数がすでに存在しています。その例として、ICaptureGraphBuilder2 を使って、前の例の DV ファイル タイプの変換を単純化する方法を示します。
ICaptureGraphBuilder2 *pCGB = 0;
hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, 0,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2,
reinterpret_cast<void**>(&pCGB));
pCGB->SetFiltergraph(pGraph);
IBaseFilter *pSrc = 0, *pMux = 0, *pDVSplit = 0;
hr = pGraph->AddSourceFilter(L"C:\\DV_Interleaved.avi",
L"Source", &pSrc);
// AVI Mux / File Writer の組み合わせを作成する
hr = pCGB->SetOutputFileName(&MEDIASUBTYPE_Avi, L"C:\\test.avi",
&pMux, 0);
// ヘルパー関数を使って DV Splitter を追加する
hr = AddFilter(pGraph, CLSID_DVSplitter, L"DV Split", &pDVSplit);
// 両方のストリームをレンダリングする
hr = pCGB->RenderStream(0, 0, pSrc, pDVSplit, pMux);
hr = pCGB->RenderStream(0, 0, pDVSplit, 0, pMux);
pSrc->Release();
pMux->Release();
pDVSplit->Release();
pCGB->Release();
まず CoCreateInstance を呼び出して、 Capture Graph Builder オブジェクトを作成します。 Filter Graph Manager へのポインタを指定して ICaptureGraphBuilder2::SetFiltergraph を呼び出すことによって初期化を行います。
RenderStream メソッドは、 1 つのフィルタ上のピンを別のフィルタ上のピンに、オプションとして中間フィルタを通して接続します。すべての接続は Intelligent Connect メカニズムを使用します。この例では最初の 2 つのパラメータは不要です。これらは、どの出力ピンがレンダリングされるかを指定するために使います。
もう 1 つの便利なメソッドが、グラフの中で指定されたインターフェイスを公開しているフィルタまたはピンを検索する ICaptureGraphBuilder2::FindInterface です。このメソッドを使うと、グラフ内を移動するための大量のコードを書かずに済ませることができます。
ICaptureGraphBuilder2IGraphBuilder だけを使用したのかと疑問に思う方もいるかもしれません。その理由の 1 つは、グラフ作成の背後にある原理を理解していないと、ICaptureGraphBuilder2 は混乱の種になるということです。ピン接続と Intelligent Connect を理解している人は、ICaptureGraphBuilder2 を使うことで最善の結果が得られます。また特定のケースで ICaptureGraphBuilder2 に問題が生じた場合には、IGraphBuilder の比較的下位水準のメソッドが代替案として利用できます。
トラブルシューティング ヒント
フィルタの作成
すべてのフィルタが CoCreateInstance で作成できるわけではありません。一部のフィルタは、 codec やデバイス ドライバなどの他のオブジェクトのラッパーの役割を果たします。デコンプレッサの場合、 Filter Graph Manager はほとんどのケースで正しいフィルタを自動的に追加します。コンプレッサまたはキャプチャ デバイスの場合には ICreateDevEnum インターフェイスを使用します。 SDK ドキュメントのトピック「 システムデバイス列挙子の使用 」と、各フィルタのリファレンス ページを参照してください。
また、フィルタを作成したのに AddFilter を呼び出すのを忘れると、そのフィルタはグラフに追加されません ! このときにピンを接続しようと試みると、戻り値 VFW_E_NOT_IN_GRAPH が返されます ( 筆者自身が何度もこの間違いを犯しました ) 。
WDM フィルタ
WDM キャプチャ デバイスでは、キャプチャ フィルタの上流にいくつかのフィルタを追加しなくてはならないことがあります。一般にこれらのフィルタは、 TV チューニングなどのデバイス上の他の機能を制御します。基本的に、 WDM キャプチャ グラフを作成する正しい方法は Capture Graph Builder です。詳細については、 DirectShow SDK ドキュメントの「キャプチャ グラフの作成」を参照してください。
RenderFile
DirectShow を初めて使う人の多くは、**RenderFile ** を呼び出すと、グラフから既存のフィルタが削除されると思い込みます。しかしそうはならないので、RenderFile を 2 回呼び出すと、グラフは 2 つのソース フィルタと、 2 つのレンダラのセットを持つことになります。フィルタ グラフは並行した未接続ストリームを複数持つことができるので、これは必ずしも悪いことではないのですが、開発者の意図からは外れているかもしれません。
最初のフィルタのセットを削除するには、IFilterGraph::RemoveFilter
GraphEdit
GraphEdit ユーティリティを使って、作成したいグラフのプロトタイプを作成し、すべてのピンが接続されることを確認します。また、コードがグラフの作成に使用するステップのシーケエンスもシミュレートできます。RenderFile、Render、Connect、ConnectDirect、AddFilter メソッドは、いずれも GraphEdit に等価なメソッドを持っています。
テストの際には、アプリケーションから GraphEdit ファイルを保存し、 GraphEdit で開いて、グラフが正しく作成されたかどうかを確認すると便利です。詳細については、 DirectShow SDK 8.1 ドキュメントの「 Saving a Filter Graph to a GraphEdit File 」を参照してください。また、 GraphEdit は外部プロセスによって作成されたフィルタ グラフをロードできます。「 Loading a Graph from an External Process 」を参照してください。
まとめ
DirectShow は強力で柔軟性がありますが、カスタム フィルタ グラフを作成する場合には学習期間がある程度必要となります。ただし、知っておかなくてはならない API の数はそれほど多くありません。この記事の例と SDK に含まれているサンプルを研究することで、必要な基礎知識が得られるでしょう。
関連情報
DirectShow の詳細については、 MSDN の DirectShow SDK ドキュメント 8.0 と 8.1 を参照してください。