Microsoft DirectX 8.0

フィルタ開発者が使用するデータ フロー

ここでは、データがフィルタ グラフ内をどのように移動するかについて説明する。独自のカスタム フィルタを作成する開発者を対象にしている。Microsoft® DirectShow® でのデータ フロー処理の概要については、「フィルタ グラフのデータ フロー」を参照すること。

ここでは、IMemInputPin および IMemAllocator インターフェイスを使用したローカル メモリ転送について説明する。内容は次のとおりである。

プッシュとプル

ソース フィルタの役割は、データをグラフに読み込むことである。ソース フィルタは、プッシュ モデルまたはプル モデルのいずれかのモデルに従う。

ビデオ カメラなどのライブ ソースではプッシュ モデルが一般的である。プル モデルはファイル リーダーで一般的であり、その場合のダウンストリーム フィルタは AVI スプリッタ フィルタなどのファイル パーサーである。

新しいサンプルの生成

ここでは、ソース フィルタがメディア サンプルを生成する方法について説明する。

プッシュ モデル

プッシュ モデルでは、ソース フィルタは次のように処理を開始する。

  1. ソース フィルタは IMemAllocator::GetBuffer を呼び出し、空のメディア サンプルを取得する。
  2. ソース フィルタは、取得した空のメディア サンプルにデータを挿入する。挿入の方法は、ソースの種類によって完全に異なる。
  3. ソース フィルタは、ダウンストリームの入力ピンに対して IMemInputPin::Receive を呼び出し、サンプルの IMediaSample インターフェイスへのポインタを渡す。
  4. ダウンストリーム フィルタは、Receive から戻る前にサンプルを処理するか、あるいはサンプルを保留して後で処理することができる。フィルタを保留する場合、ダウンストリーム フィルタはサンプルに対して IUnknown::AddRef を呼び出す。
  5. ソース フィルタは、サンプルに対して IUnknown::Release を呼び出す。

この時点で、ダウンストリーム フィルタはサンプルの参照カウントを保留し、ソース フィルタが簡単にサンプルを再利用できないようにする。次のサンプルを送信するには、ソース フィルタは、手順 1 に示したように IMemAlloctor::GetBuffer をもう一度呼び出す必要がある。

注 :  複数のサンプルを送信するには、ソース フィルタは手順 3 で代わりに IMemInputPin::ReceiveMultiple を呼び出すことができる。

プル モデル

プル モデルでは、パーサー フィルタがソース フィルタにデータを要求する。パーサー フィルタはソース フィルタの出力ピンに対して、次のように IAsyncReader インターフェイスを使用する。

  1. パーサー フィルタは IMemAllocator::GetBuffer を呼び出し、空のメディア サンプルを取得する。
  2. パーサー フィルタは IAsyncReader::Request を呼び出し、ソース フィルタにデータを要求する。
  3. ソース フィルタがデータを取得する間、パーサーは IAsyncReader::WaitForNext を呼び出す。このメソッドは、手順 2 の要求が完了するまで待機する。
  4. パーサー フィルタはデータを処理し、ダウンストリームに送信する。

手順 2 〜 3 で実行されるのは非同期読み取り操作であるが、パーサーは、IAsyncReader::SyncRead または IAsyncReader::SyncReadAligned メソッドを使用して、代わりに同期読み取り操作を要求することもできる。

サンプルの処理

フィルタの入力ピンがサンプルを受け取ると、フィルタはサンプルに含まれるデータを処理し、次のダウンストリーム フィルタにサンプルを送信する。前の「新しいサンプルの生成」で説明したように、フィルタは Receive メソッドの中ですべての処理を実行するか、サンプルを保留して後で処理することができる。

Receive の呼び出しの中で、入力ピンはアップストリーム フィルタの呼び出し側スレッドをブロックすることが可能である。入力ピンの動作は、IMemInputPin::ReceiveCanBlock メソッドを呼び出すことによって確認できる。戻り値が S_OK である場合、入力ピンは Receive の呼び出しをブロックする。戻り値が S_FALSE である場合、入力ピンが Receive の呼び出しをブロックすることはない。戻り値に基づいて、アップストリーム フィルタは別個のワーカー スレッドを使用してサンプルを送信できる。

データをコピーせずサンプルを元の場所で処理するフィルタもある。データをコピーしてから処理するフィルタもある。当然のこととして、不要なコピー操作によるオーバーヘッドを避けるために、可能な限りデータを元の場所で処理する方が望ましい。

入力ピンは、Receive メソッドでエラー コードまたは S_FALSE を返すことによって、サンプルを拒否できる。S_FALSE という値は、ピンがデータをフラッシュすることを示す (「フラッシュ」を参照すること)。エラー コードは、それとは別の問題を示す。たとえば、フィルタが停止した場合、戻り値は VFW_E_WRONG_STATE になる。ピンがサンプルを拒否した場合、フィルタは接続されている入力ピンに対して IPin::EndOfStream メソッドを呼び出す。さらに、エラーが発生した場合、フィルタはフィルタ グラフ マネージャに EC_ERRORABORT イベントを送信する。ピンがサンプルを拒否した後、アップストリーム フィルタはデータの送信を停止する。データ送信は、フラッシュが完了した後、またはグラフが停止後に再起動した時点で再開できる。

フィルタは特定の入力ピンに対するすべての Receive 呼び出しを継続して処理する必要がある。

新しいセグメント

新しい各ストリームの始めに、ソース フィルタ (またはパーサー フィルタ) は IPin::NewSegment を呼び出す必要がある。このメソッドは、ストリームの開始タイムと終了タイムを元のソースに対して相対的に指定し、再生レートを指定する。フィルタは、ストリーミング開始時、およびシーク操作後に NewSegment を呼び出す。新しいセグメントごとにストリーム時間はゼロにリセットされ、新しいセグメントの後に送信されたサンプルにはゼロから始まるタイム スタンプが付く。

フィルタがサンプルを正しく処理するためにセグメント情報が必要な場合もある。たとえば、MPEG-1 などのフォーマットでは、中間フレームを構成するためにフィルタは終了タイム以後のキー フレームを必要とする。セグメント情報はフィルタに実際の終了タイムを知らせる。フィルタがセグメント情報を使用するかどうかにかかわらず、フィルタは NewSegment 呼び出しをダウンストリームに渡す。NewSegment 呼び出しは、Receive などのほかのストリーミング呼び出しとともに継続して処理する必要がある。

ストリームの終わり

ソース フィルタは、送信するデータがなくなると、ダウンストリームの入力ピンに対して IPin::EndOfStream メソッドを呼び出す。ダウンストリーム フィルタはこの呼び出しを次のフィルタに伝達する。最後には EndOfStream 呼び出しがレンダリング フィルタに到達する。レンダリング フィルタはフィルタ グラフ マネージャに EC_COMPLETE イベントを送信する。

フィルタ グラフ マネージャは、アプリケーションに EC_COMPLETE 通知を直ちに転送するのではなく、すべてのストリームから EC_COMPLETE が送信されるまで待機する。すべてのストリームが完了すると、アプリケーションは EC_COMPLETE 通知を受け取る。グラフ内のストリームの数を確認するために、フィルタ グラフ マネージャは IMediaSeeking または IMediaPosition をサポートし、レンダリングされた入力ピンを持つフィルタの数をカウントする。レンダリングされた入力ピンは、対応する出力がない入力ピンである。IPin::QueryInternalConnections メソッドは、レンダリングされた入力ピンについては 0 を返す。ほかのオプションとして、フィルタは IAMFilterMiscFlags インターフェイスを実装し、AM_FILTER_MISC_FLAGS_IS_RENDERER フラグを返すことができる。

フィルタは、Receive などのほかのストリーミング呼び出しとともに EndOfStream 呼び出しを継続して処理する必要がある。

フラッシュ

フラッシュは、グラフ内のペンディング状態のサンプルすべてを破棄する処理である。フラッシュは、イベントによって正常なデータ フローが変更されたときのグラフの応答を改善する。たとえば、シーク操作では、新しいデータが読み込まれる前に古いデータをグラフからフラッシュする。グラフに複数のストリームが含まれている場合は、各ストリームを別々にフラッシュすることが可能である。

フラッシュ処理には 2 つの段階がある。

入力ピンの BeginFlush メソッドを呼び出すとき、フィルタは次の動作を実行する。

  1. ダウンストリームの入力ピンに対する BeginFlush を呼び出す。
  2. ReceiveEndOfStream など、データのストリーミングを実行する次の呼び出しを拒否する。
  3. フィルタのアロケータからのサンプルを待ってブロックされているアップストリーム フィルタを非ブロックにする。この目的のため、フィルタはそのアロケータをデコミットする事が多い。
  4. ストリーミングをブロックする待機状態を終了する。たとえば、レンダリング フィルタはポーズするとブロックする。さらに、正しいストリーム タイムにサンプルをレンダリングするために待機しているときも、ブロックする。フィルタは、ブロックを解除して、アップストリームのキューにあるサンプルを送信および拒否できるようにしなければならない。この手順によって、ストリーミング チェーン内のすべてのフィルタが最終的にブロック解除されることが保証される。

EndFlush メソッドを呼び出すとき、フィルタは次の動作を実行する。

  1. キューにあるすべてのサンプルが破棄されるまで待機する。
  2. バッファ内のデータを解放する。この手順は場合によって BeginFlush メソッドで実行することも可能である。ただし、BeginFlush はストリーミング スレッドと同期化されない。フィルタは、BeginFlush を呼び出してから EndFlush を呼び出すまでの間、データを処理したりバッファに入れることはできない。
  3. ペンディング状態の EC_COMPLETE 通知をクリアする。
  4. ダウンストリームの EndFlush を呼び出す。

この時点で、フィルタはサンプルの受け取りを再開できる。すべてのサンプルはフラッシュより新しいことが保証されている。

プル モデルでは、ソース フィルタではなくパーサー フィルタがフラッシュを開始する。イベントのシーケンスは同じだが、ソース フィルタの出力ピンに対する BeginFlush および EndFlush もパーサーが呼び出す。

スレッドとクリティカル セクション

DirectShow アプリケーションでは、最低 2 つの重要なスレッドが動作している。1 つ目はアプリケーション スレッドである。このスレッドは、フィルタ グラフの状態の変更を開始する。状態変更には、グラフの実行、ポーズ、および停止、ピンの接続と接続終了、グラフへのフィルタの追加、およびグラフからのフィルタの削除がある。2 つ目の重要なスレッドはストリーミング スレッドである。ピンはストリーミング スレッド内でサンプルを受け取って送信する。アプリケーション スレッドがデータ フローを中断することなくブロックできるように (たとえば、アプリケーションがユーザーと通信する間)、別個のストリーミング スレッドが必要である。

フィルタはスレッドを作成することもできる。たとえば、IMemInputPin::Receive メソッドでは、フィルタは直ちに戻って受信したサンプルを別のスレッドで処理できる。別個のスレッドを使用して、送信待ちのサンプルをキューに入れるフィルタもある。

フィルタ操作はマルチスレッドであるため、特定の操作では、フィルタが操作を継続して処理することが非常に重要である。そうしないと、競合状態が発生する可能性がある。たとえば、次に示すコードを考える。このコードは基準クロックの有無を確認し、現在の時刻を取得する。

if (m_pClock != NULL)  // 基準クロックがあるか?
{
    m_pClock->GetTime(&rtTimeNow);
    // 以下コード省略...
}

ストリーミング スレッドがこのコードを実行し、コードはプロテクトされていないとする。 if ステートメントと次の行の間に、アプリケーション スレッドが IMediaFilter::SetSyncSource を呼び出し、基準クロックを NULL に設定することがある。その結果、ストリーミング スレッドは NULL ポインタの参照を解除する。例外が発生し、アプリケーションはクラッシュする。

競合状態を回避するために、フィルタは 2 つのクリティカル セクションを使用する。1 つ目はストリーミング ロックである。ストリーミング ロックはストリーミング操作を継続して処理する。2 つ目はフィルタ ロックである。フィルタ ロックは状態変更を継続して処理し、ステート情報の整合性を保護する。

フィルタは次に示すメソッド呼び出しを処理するときにストリーミング ロックを保持する。

フィルタは次に示すタイプの呼び出しを処理するときにフィルタ ロックを保持する。

この一覧に示した呼び出しがすべてではない。フィルタを実装する場合は、フィルタ状態を変更するメソッドはどれか、およびストリーミング操作を実行するメソッドはどれかを考慮する必要がある。

クリティカル セクションは潜在的にデッドロックを発生させる可能性がある。デッドロックを回避するには、ストリーミング メソッドがフィルタ ロックを保持してはならない。理由は、フィルタ グラフの状態移行が終わらないと完了しないストリーミング操作が存在するからである。たとえばレンダラは、ポーズ中は Receive 呼び出しをブロックする。アップストリーム フィルタが Receive を呼び出すときにフィルタ ロックを保持すると、循環する待機状態が発生する場合がある。ストリーミング スレッドはグラフのポーズ状態が終了するのを待機する。しかし、アプリケーション スレッドはフィルタ ロックを取得するまで待機するため、その状態移行が行われることはあり得ない。

次の図は、このデッドロックを示している。

フィルタのデッドロック

Stop および EndFlush メソッドでは、ストリーミング操作をフィルタ状態と同期化する必要がある。そうしないと、フィルタは状態移行後も古いサンプルを送信し続けることがある。Stop メソッドでは、デッドロックを回避するように注意する必要がある。最初にフィルタ ロックを保持し、入力ピンのアロケータをデコミットする。次にストリーミング ロックを保持する。アロケータをデコミットすると、Receive の呼び出しが失敗し、したがってブロックを確実に回避できる (実装については、「CTransformFilter::Stop」を参照すること)。フラッシュ処理では、BeginFlush メソッドがサンプルの取り出しを開始し、EndFlush メソッドがストリーミング ロックを保持する。