Microsoft DirectX 8.0 |
このトピックでは、キャプチャ アプリケーションでの Microsoft® DirectShow® 使用にかかわるいくつかの高度な側面について説明する。しかしほとんどのキャプチャ アプリケーションにはこの情報が不要である。このトピックは「キャプチャ アプリケーションの書き方」を読んだうえで参照すること。
大半のキャプチャ アプリケーションでは、ICaptureGraphBuilder2 インターフェイスを使ってキャプチャ グラフを構築できる。ほとんどのキャプチャ シナリオには、このインターフェイスで適切に対応できる。ただし、必要なグラフが極端に複雑な場合は、1 フィルタずつ手動で構築しなければならない。この場合には、ICaptureGraphBuilder2 で自動的に行われる処理を細部まで理解しておく必要がある。また、グラフを手動で構築する場合でも、ICaptureGraphBuilder2 の FindInterface メソッドや FindPin メソッドは利用できる。
このトピックは、以下のセクションで構成される :
ユーザーがプレビュー ウィンドウに重ねて何かを表示し、それを消去すると、ビデオ レンダラは WM_PAINT メッセージを受け取る。デフォルトでは、ウィンドウを再表示するため、ビデオ レンダラが新しいフレームを要求する。ファイル キャプチャ中にこの状況が発生すると、キャプチャ ファイルが破損して使用できなくなる。したがって、キャプチャ グラフがファイルにデータを書き込む場合には、再描画イベントのデフォルト処理を取り消す必要がある。これには、フィルタ グラフ マネージャに IMediaEvent インターフェイスを照会し、パラメータに EC_REPAINT イベント コードを指定して IMediaEvent::CancelDefaultHandling メソッドを呼び出す。
詳細については、「DirectShow でのイベント通知」を参照すること。
キャプチャ フィルタには、目的の異なる複数の出力ピンを持たせることができる。たとえば、キャプチャ用とプレビュー用にそれぞれ独立したピンを持つキャプチャ フィルタがある。また、プライマリ ビデオ ストリームとは別に、クローズド キャプション データを渡すキャプチャ フィルタもある。ピンの機能は、カーネル ストリーミング プロパティ セットで識別する。ここでは、以下のピン カテゴリについて説明する。
カテゴリ GUID | 説明 |
---|---|
PIN_CATEGORY_CAPTURE | キャプチャ ピン |
PIN_CATEGORY_PREVIEW | プレビュー ピン |
PIN_CATEGORY_VIDEOPORT | ビデオ ポート (VP) ピン |
PIN_CATEGORY_CC | クローズド キャプション ピン |
PIN_CATEGORY_VBI | 垂直同期間隔 (VBI) ピン |
PIN_CATEGORY_VIDEOPORT_VBI | VBI 用のビデオ ポート ピン |
キャプチャ フィルタには、キャプチャ ピンが少なくとも 1 つある。また、プレビュー ピンとビデオ ポート ピンのどちらかが付いていることもある、ただし同時に両方が付くことはない。キャプチャ ピンとプレビュー ピンは、それぞれ異なるメディア タイプを渡すために複数使用できる。このように、1 つのフィルタが、ビデオ用とオーディオ用それぞれのキャプチャ ピンと、ビデオ用とオーディオ用それぞれのプレビュー ピンを持つことがある。クローズド キャプションと VBI は、TV と DVD で使用する。
ピンのカテゴリを判断するには、IKsPropertySet::Get メソッドを呼び出す。このプロパティ セットの GUID は AMPROPSETID_Pin であり、プロパティ識別子は AMPROPERTY_PIN_CATEGORY である。以下のサンプル コードは、ピンの照会方法を示す :
IPin *pPin; // キャプチャ フィルタの出力ピンへのポインタ。 IKsPropertySet *pKs; HRESULT hr; hr = pPin->QueryInterface(IID_IKsPropertySet, (void **)&pKs); if (SUCCEEDED(hr)) { GUID PinCategory; DWORD cbReturned; hr = pKs->Get(AMPROPSETID_Pin, AMPROPERTY_PIN_CATEGORY, NULL, 0, &PinCategory, sizeof(GUID), &cbReturned); if (SUCCEEDED(hr)) { // PinCategory にカテゴリ GUID が格納されている。 } pKs->Release(); }
プロパティ セットの詳細については、Microsoft® Windows® Driver Development Kit (DDK) のドキュメントを参照すること。
それぞれ独立したキャプチャ ピンとプレビュー ピンがあるフィルタでは、一方でプレビューしながら、もう一方でキャプチャすることができる。ただし、フィルタにプレビュー ピンがない場合でも、スマート ティー フィルタを使って同じことを実行できる。このフィルタは、キャプチャ ピンからのデータを、キャプチャ用とプレビュー用の同じ 2 つのストリームに分割する。
次の図は、スマート ティー フィルタがあるグラフを示す。
プレビューとキャプチャを 1 つのピンでカバーするため、スマート ティー フィルタは以下の最適化を行う。
2 番目の最適化については少し説明が必要である。グラフ内での遅延時間が原因となり、ライブ データはレンダリング フィルタに少し遅れて届く。プレビュー データにタイム スタンプがあると、遅れを解消するためにレンダラでフレームがドロップされることがあるが、フレームをいくらドロップしてもフレームの遅れは解消されない。タイム スタンプを削除することによって、フレームの不要なドロップを避けることができる。詳細については、「DirectShow のタイムとクロック」を参照すること。
注 : ビデオ ポート ピンは一種のプレビュー ピンとして取り扱うことができるので、VP ピンがあるフィルタにスマート ティー フィルタは必要ない。ただし、VP ピンには特殊な要件がいくつかある。次のセクションでは、これらの要件について説明する。
ハードウェア ビデオ ポートがあるキャプチャ デバイスは、Microsoft® DirectX® のビデオ ポート エクステンション (VPE) を使用できる。この場合、キャプチャ フィルタはビデオ ポート (VP) ピンを持つ。VP ピンはオーバーレイ ミキサー フィルタに接続し、オーバーレイ ミキサーはビデオ レンダリング フィルタに接続する必要がある。
VP ピンからフィルタ グラフを通じてビデオ データが渡されることはない。代わりに、ハードウェア内でビデオ フレームが生成され、ビデオ メモリに直接送られる。VP ピンを使用すると、ハードウェアにコントロール メッセージを送信できる。キャプチャ機能のみに興味がある場合でも、VP ピンは必ず接続すること。なぜなら VP ピンを接続しないと、キャプチャ グラフは動作しない。この点で、未接続でもよいプレビュー ピンとは異なる。VP ピンは必ずオーバーレイ ミキサーの入力ピン 0 に接続すること。
ビデオ レンダリング フィルタは、オーバーレイ ミキサーからビデオ データを取得しない。ただし、ビデオ ウィンドウはビデオ レンダリング フィルタによって制御されるので、フィルタ グラフにはこのフィルタを含める必要がある。フィルタ グラフ マネージャに IVideoWindow インターフェイスを照会すると、ビデオ ウィンドウをアプリケーション ウィンドウの子にすることができる。詳細については、「ビデオ ウィンドウの設定」を参照すること。
次の図は、ビデオ ポート ピンがあるキャプチャ グラフを示す。
次のサンプル コードは、オーバーレイ ミキサーにビデオ ポート ピンを接続するものである。ここでは、キャプチャ フィルタのビデオ ポート ピンとオーバーレイ ミキサーの入力ピン 0 を検索するために、ICaptureGraphBuilder2::FindPin メソッドを使用するが、代わりに IBaseFilter::EnumPins メソッドを使用することもできる。簡略化のため、このコードではエラー チェックが省略されている。
IPin *pPinOut, // キャプチャ フィルタのビデオ ポート ピン。 *pPinIn; // オーバーレイ ミキサーの入力ピン。 IBaseFilter *pOvMix = NULL; // ビデオ ポート ピンを検索する。 pCGB->FindPin( pCaptureFilter, // キャプチャ フィルタへのポインタ。 PINDIR_OUTPUT, // 出力ピンを検索する。 &PIN_CATEGORY_VIDEOPORT, // ビデオ ポート ピンを検索する。 NULL, // 任意のメディア タイプ。 TRUE, // ピンは非接続でなければならない。 0, // 一致する最初のピンを取得する。 &pPinOut // ピンへのポインタのアドレス。 ); // オーバーレイ ミキサーを作成する。 CoCreateInstance(CLSID_OverlayMixer, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void **)&pOvMix); // それをフィルタ グラフに追加する。 pGraph->AddFilter(pOvMix, L"Overlay Mixer"); // オーバーレイ ミキサーの入力ピン 0 を取得する。 pCGB->FindPin(pOvMix, PINDIR_INPUT, NULL, NULL, TRUE, 0, &pPinIn); // 2 つのピンを接続する。 pGraph->Connect(pPinOut, pPinIn);
オーバーレイ ミキサーをビデオ レンダラに接続するには、このようなコードを使用するか、またはオーバーレイ ミキサーの出力ピンを指定して IGraphBuilder::Render を呼び出す。
クローズド キャプション ピンには、いくつかの種類がある。
接続の要件は、ピンの種類によって異なる。
クローズド キャプション ピン : プレビューでは、このピンをLine 21 デコーダ フィルタに接続し、そのフィルタをオーバーレイ ミキサーのピン 1 に接続する (ピン 0 はプライマリ ビデオ ストリーム用に予約されている)。IGraphBuilder::Connect を呼び出すと、フィルタ グラフ マネージャによって自動的にLine 21 デコーダが追加される。ファイル キャプチャでは、オーバーレイ ミキサーではなく、Mux フィルタに接続する。
VBI ピン : このピンは、Tee/Sink-to-Sink コンバータ フィルタ (VBI インフィニット ティーとも呼ぶ) に接続する。このフィルタによって VBI データが複数のストリームに分割される。このフィルタを CC デコーダ フィルタに接続する。プレビューでは、前に説明したように CC デコーダ フィルタをオーバーレイ ミキサーに接続する。ファイル キャプチャでは、Mux フィルタに接続する。
VBI ビデオ ポート ピン : このピンは VBI サーフェス アロケータ フィルタ (CLSID_VBISurfaces) に接続する。このフィルタは、VBI データ用の Microsoft® DirectDraw® サーフェスを処理する。フィルタに VBI ピンと VBI ビデオ ポート ピンの両方がある場合は、その両方を接続する。
プレビューでは、キャプチャ フィルタのプレビュー ピンを、オーバーレイ ミキサーのピン 0 に接続する。これは、PIN_CATEGORY_VIDEOPORT ピンだけでなく、PIN_CATEGORY_PREVIEW ピンにも適用される。オーバーレイ ミキサーによって、クローズド キャプションがプライマリ ビデオにオーバーレイされる。
次の図は、VBI ピンと VBI ビデオ ポート ピンがあるプレビュー グラフを示す。
Tee/Sink-to-Sink コンバータ フィルタと CC デコーダ フィルタは、実際は KSProxy フィルタにラップされた WDM ミニドライバである。したがって、CoCreateInstance の呼び出しではこれらのフィルタを作成できない。代わりに、システム デバイス 列挙子を使って各フィルタのデバイス カテゴリを列挙する。
フィルタ | カテゴリ |
---|---|
Tee/Sink-to-Sink コンバータ | AM_KSCATEGORY_SPLITTER (WDM ストリーミング ティー/スプリッタ) |
CC デコーダ | AM_KSCATEGORY_VBICODEC (WDM ストリーミング VBI CODEC) |
カテゴリに含まれるそれぞれのフィルタのフレンドリ名をテストして、一致する文字列を見つける。次に、IMoniker::BindToObject を呼び出してフィルタのインスタンスを作成する。
次のサンプル コードは、Tee/Sink-to-Sink コンバータ フィルタを作成する。簡略化のため、エラー チェックは省略されている。
IBaseFilter *pFilter = NULL; // フィルタのインスタンスを受け取るポインタ。 // システム デバイス列挙子を作成する。 ICreateDevEnum *pDevEnum; CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC, IID_ICreateDevEnum, (void**)&pDevEnum); // ティー/スプリッタ カテゴリを列挙するクラス列挙子を作成する。 IEnumMoniker *pEnum; pDevEnum->CreateClassEnumerator(AM_KSCATEGORY_SPLITTER, &pEnum, 0); // このカテゴリに含まれるデバイスを列挙する。 IMoniker *pMoniker; ULONG cFetched; while(pEnum->Next(1, &pMoniker, &cFetched) == S_OK) { IPropertyBag *pBag; pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag); // フレンドリ名を確認する。 VARIANT var; var.vt = VT_BSTR; pBag->Read(L"FriendlyName", &var, NULL); if (lstrcmpiW(var.bstrVal, L"Tee/Sink-to-Sink Converter") == 0) { // 正しいフィルタが見つかった。 pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pFilter); SysFreeString(var.bstrVal); pBag->Release(); pMoniker->Release(); break; } SysFreeString(var.bstrVal); pBag->Release(); pMoniker->Release(); } pEnum->Release(); pDevEnum->Release();
プレビュー ピンには、VIDEOINFOHEADER2 フォーマットのメディア タイプが適している場合がある。このフォーマットは、正方形以外のピクセルやインターレース ビデオ (フレーム ベースではなくフィールド ベースのビデオ) などの特殊な機能をサポートする。オーバーレイ ミキサー フィルタでは、このフォーマットがサポートされる。
これらの機能を利用するには、以下を実行する。
これらの機能に興味がなければ、オーバーレイ ミキサーを使用する必要はない。
キャプチャ デバイスで WDM (Windows Driver Model) ドライバを使用する場合には、グラフ内のキャプチャ フィルタのアップストリームに一定のフィルタが必要になることがある。これらのフィルタは、"ストリーム クラス ドライバ" フィルタまたは WDM フィルタと呼ばれる。これらのフィルタでは、ハードウェアが提供する追加機能がサポートされる。たとえば、TV チューナー カードにはチャンネルを設定するための機能がある。これに対応するフィルタは、IAMTVTuner インターフェイスを公開する TV チューナー フィルタである。アプリケーションからこの機能を利用できるようにするには、キャプチャ フィルタに TV チューナー フィルタを接続する必要がある。
ICaptureGraphBuilder2 インターフェイスは、グラフに WDM フィルタを追加するための最も簡単な手段である。グラフ構築のどこかで FindInterface または RenderStream を呼び出す。これらのメソッドは、どちらも必要な WDM フィルタを自動的に見つけてキャプチャ グラフに接続する。WDM フィルタを手動で追加する方法は、このセクションの後半で説明する。ただし、可能であれば、手動ではなく ICaptureGraphBuilder2 のメソッドを呼び出すこと。
WDM フィルタのピンは、1 つ以上の "メディア" をサポートする。メディアとは、通信の手段 (バスなど) を定義するものである。接続するピンでは、同じメディアがサポートされている必要がある。DirectShow では、REGPINMEDIUM 構造体でメディアが定義される。REGPINMEDIUM 構造体は、カーネル ストリーミング ドライバに使用される KSPIN_MEDIUM 構造体と同等である。ピンのメディアを取得するには、IKsPin::KsQueryMediums メソッドを呼び出す。このメソッドは、KSMULTIPLE_ITEM 構造体と 0 個以上の REGPINMEDIUM 構造体が連続して格納されたメモリ ブロックへのポインタを返す。REGPINMEDIUM 構造体の clsMedium メンバは、メディアのクラス識別子 (CLSID) を指定するものである。
次のヘルパー関数は、ピンのメディアを取得する。
// 呼び出し元で pMedium を割り当てる。 HRESULT GetMedium(IPin *pPin, REGPINMEDIUM *pMedium) { IKsPin *pKsPin = NULL; PKSMULTIPLE_ITEM pmi; HRESULT hr = pPin->QueryInterface(IID_IKsPin, (void **)&pKsPin); if (FAILED(hr)) return hr; // ピンが IKsPin をサポートしていない。 hr = pKsPin->KsQueryMediums(&pmi); pKsPin->Release(); if (FAILED(hr)) return hr; // ピンがメディアをサポートしていない。 if (pmi->Count == 0) { CoTaskMemFree(pmi); return E_FAIL; // メディア数が 0。 } else { // ポインタ演算を使ってメディア構造体を参照する。 REGPINMEDIUM *pTemp = (REGPINMEDIUM*)(pmi + 1); memcpy(pMedium, pTemp, sizeof(REGPINMEDIUM)); CoTaskMemFree(pmi); return S_OK; } }
メディアの CLSID が GUID_NULL または KSMEDIUMSETID_Standard の場合には、ピンを接続しないこと。これらの CLSID は、ピンがメディアをサポートしていないことを示すデフォルト値である。
また、フィルタにそのピンが接続されたインスタンスが 1 つだけ必要な場合にのみ、ピンを接続すること。そうしないと、接続すべきではない各種のピンをアプリケーションが接続しようとし、その結果、プログラムが応答しなくなる危険性がある。必要なインスタンスの数を調べるには、次のサンプル コードのように KSPROPERTY_PIN_NECESSARYINSTANCES プロパティ セットを取得する。簡略化のため、このコードではリターン コードのテストとインターフェイスの解放が省略されているが、アプリケーションではこれらを両方とも実行しなければならない。
// ピン ファクトリ識別子を取得する。 IKsPinFactory *pPinFactory; hr = pPin->QueryInterface(IID_IKsPinFactory, (void **)&pPinFactory); ULONG ulFactoryId; hr = pPinFactory->KsPinFactory(&ulFactoryId); // フィルタから "インスタンス" プロパティを取得する。 IKsControl *pKsControl; hr = pFilter->QueryInterface(IID_IKsControl, (void **)&pKsControl); KSP_PIN ksPin; ksPin.Property.Set = KSPROPSETID_Pin; ksPin.Property.Id = KSPROPERTY_PIN_NECESSARYINSTANCES; ksPin.Property.Flags = KSPROPERTY_TYPE_GET; ksPin.PinId = ulFactoryId; ksPin.Reserved = 0; KSPROPERTY ksProp; ULONG ulInstances, bytes; pKsControl->KsProperty((PKSPROPERTY)&ksPin, sizeof(ksPin), &ulInstances, sizeof(ULONG), &bytes); if (hr == S_OK && bytes == sizeof(ULONG)) { if (ulInstances == 1) { // フィルタにはこのピンのインスタンスが 1 つ必要。 // このピンは OK。 } }
次の擬似コードは、WDM フィルタを検索および接続する方法について、非常に簡単な概要のみを示したものである。このコードでは、細かい処理をすべて省略し、アプリケーションで実行すべき一般的なステップだけを示す。
サポートされるフィルタを追加する : { foreach 入力ピン : skip if (ピンが接続されている) ピン メディアを取得する skip if (メディアが GUID_NULL または KSMEDIUMSETID_Standard) フィルタに KSPROPERTY_PIN_NECESSARYINSTANCES プロパティを照会する skip if (必要なインスタンス数 != 1) 既存のピンと比較する || 一致するフィルタを検索する } 既存のピンと比較する : { foreach グラフ内のフィルタ 接続されていない foreach ピン ピン メディアを取得する if (メディアが一致する) そのピンを接続する } 一致するフィルタを検索する : { フィルタ グラフ マネージャに IFilterMapper2 を照会する。 メディアが一致する出力ピンを持つフィルタを検索する。 フィルタをグラフに追加する。 ピンを接続する。 サポートされるフィルタを追加する (再帰呼び出し)。 }