Microsoft DirectX 8.0

代替ビデオレンダラ

ここでは、レンダラのやや複雑な実装要件のいくつかについて説明する。ビデオに固有の要素 (たとえば EC_REPAINT やその他の通知) もあるが、これらの要件はほとんどのレンダラに当てはまる。ここでは特に、各種の通知、状態の変更、およびフォーマットの変更について説明する。また、レンダラがフィルタ グラフ マネージャに送信する通知もおおまかに説明する。

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

代替レンダラを書く

Microsoft® DirectShow® は、ウィンドウ ベースのビデオ レンダラを提供するほか、ランタイム インストールではフルスクリーン レンダラも提供する。DirectShow の基底クラスを使用すると、代替ビデオ レンダラを書くことができる。代替レンダラが DirectShow ベースのアプリケーションと正常にやり取りを行うためには、レンダラはこのトピックで説明するガイドラインに従わなければならない。CBaseRenderer および CBaseVideoRenderer クラスは、代替ビデオ レンダラを実装するときにこのガイドラインを守るうえで役に立つ。DirectShow の開発は現在も進行中なので、実装を定期的に見直して、レンダラが最新バージョンの DirectShow との互換性を確保しているかどうかを確認することが重要である。

ここでは、レンダラが扱うべき多くの通知について説明する。その前に、DirectShow の通知を簡単に復習しておく。基本的に、DirectShow で発生する通知には次の 3 種類の通知がある。

ここでは、受け取ったストリーム通知を処理して適切なフィルタ グラフ マネージャ通知を送信するレンダリング フィルタの役割について説明する。

エンドオブストリーム通知およびフラッシング通知の処理

エンドオブストリーム通知は、送信すべきデータがもはやそれ以上ないことを検出したアップストリーム フィルタ (たとえばソース フィルタ) が生成する。この通知はグラフ内のすべてのフィルタに渡され、最終的にレンダラに渡される。これに対して、レンダラは EC_COMPLETE 通知をフィルタ グラフ マネージャに渡す責任を持つ。これらの通知の処理に関して、レンダラには特別な役割が与えられている。

入力ピンの IPin::EndOfStream メソッドがアップストリーム フィルタに呼び出されると、レンダラはエンドオブストリーム通知を受け取る。レンダラはこの通知を認識し、引き続き既に受け取っているすべてのデータをレンダリングしなければならない。残っているデータがすべて受け取られたら、レンダラは EC_COMPLETE 通知をフィルタ グラフ マネージャに送信しなければならない。EC_COMPLETE 通知は、エンドオブストリームに到達するごとにレンダラによって 1 回だけ送信されなければならない。さらに EC_COMPLETE 通知は、フィルタ グラフが実行中でない限り送信されてはならない。したがって、ソース フィルタがエンドオブストリーム通知を送信したときにフィルタ グラフがポーズ状態であった場合は、フィルタ グラフが実行されるまでは EC_COMPLETE が送信されてはならない。

エンドオブストリーム通知が通知済になると、すべての IMemInputPin::Receive または IMemInputPin::ReceiveMultiple メソッド呼び出しは拒否されなければならない。この場合、返されるエラー メッセージとしては E_UNEXPECTED が最も適している。

フィルタ グラフが停止状態にあるときは、キャッシュされたエンドオブストリーム通知はすべてクリアされなければならず、次に開始されたときに再送信されてはならない。これは、適切なフラッシングが発生するように、フィルタ グラフ マネージャがフィルタを実行する直前に必ずすべてのフィルタをポーズさせるからである。したがって、たとえばフィルタ グラフがポーズ状態でエンドオブストリーム通知が受け取られ、フィルタ グラフが停止した場合、レンダラは、後で実行されるときに EC_COMPLETE 通知を送信してはならない。シークが発生しなかった場合、ソース フィルタは、実行状態の前のポーズ状態にある間に自動的に別のエンドオブストリーム通知を送信する。一方、フィルタ グラフが停止している間にシークが発生した場合、ソース フィルタは送信すべきデータを持っている可能性がある。したがって、ソース フィルタははエンドオブストリーム通知を送信しない。

ビデオ レンダラは、EC_COMPLETE 通知を送信するよりも、エンドオブストリーム通知を使用することが多い。たとえばストリームが再生を完了し (すなわち、エンドオブストリーム通知が送信され)、別のウィンドウがビデオ レンダラ ウィンドウ上にドラッグされた場合、いくつかの WM_PAINT ウィンドウ メッセージが生成される。ビデオ レンダラを実行する一般的な方法は、WM_PAINT メッセージを受信したときに、(描画されるべき別のフレームが受信されると想定して) 現在のフレームを再描画しないようにすることである。ただし、エンドオブストリーム通知が送信されると、レンダラは待機状態になる。すなわち、レンダラは実行中ではあるが追加のデータを受信しない。このような状況で、レンダラは通常、再生領域を黒で塗りつぶす。

フラッシングの処理は、レンダラにとってのもう 1 つの問題である。フラッシングは、BeginFlushEndFlush の 1 組の IPin メソッドを介して実行される。基本的に、フラッシングはレンダラが処理しなければならない追加の状態である。ソース フィルタが EndFlush を呼び出さずに BeginFlush を呼び出すことは不正であるので、この状態はおそらく短くて連続していない。ただしレンダラは、フラッシュ移行の間に受け取ったデータまたは通知を正常に処理しなければならない。

BeginFlush 呼び出し後に受け取ったデータは、E_UNEXPECTED を返すことによって即座に拒否しなければならない。さらに、キャッシュされたエンドオブストリーム通知も、レンダラがフラッシュされるときにクリアされなければならない。通常、レンダラはシークに対してフラッシュされる。フラッシュを行うことにより、新しいサンプルが送信される前に古いデータがフィルタ グラフから確実にクリアされる。(通常、ストリームの 2 つのセクションを続けて再生する場合、1 つのセクションが完了するのを待ってからシーク コマンドを発行するよりも、遅延コマンドを使う方が処理的に優れている。)

状態の変更とポーズの完了の処理

レンダリング フィルタは、その状態が変化するときにフィルタ グラフ内のほかのフィルタと同じように動作するが、次のような例外がある。ポーズされた後、レンダラはデータをキューに入れ、以降の実行でレンダリングを行う準備をする。ビデオ レンダラを停止した場合、レンダラはこのキューに入っているデータを保持する。これは、フィルタ グラフが停止状態のときはフィルタはリソースを保持してはならないという DirectShow の規則に対する例外である。

この例外の理由は、リソースを保持することでレンダラは WM_PAINT メッセージを受け取った場合にウィンドウを再描画するイメージを常に持つということにある。またレンダラは、現在のイメージのコピーを要求する CBaseControlVideo::GetStaticImage などのメソッドを満足するイメージを持つ。リソースを保持するもう 1 つの効果として、イメージを保持することでアロケータがデコミットされることがなくなり、イメージ バッファが既に割り当てられているので次の状態変更がより迅速に行われるようになる。

ビデオ レンダラは、実行中にのみ、サンプルをレンダリングおよび解放しなければならない。ポーズ状態のときは、フィルタがサンプルをレンダリングすることはある (たとえばウィンドウ内でスタティック ポスター イメージを描画するとき) が、サンプルを解放してはならない。オーディオ レンダラは、ポーズ状態のときはレンダリングを行わない (ただし、たとえば wave デバイスの準備などのほかの処理は行える)。サンプルをレンダリングするタイムは、サンプルのストリーム タイムと、IMediaControl::Run メソッドへのパラメータとして渡された基準タイムとを組み合わせることによって求められる。レンダラは、終了タイム以下の開始タイムを持つサンプルを拒否しなければならない。

アプリケーションがフィルタ グラフをポーズすると、フィルタ グラフはレンダラでキューに入れられたデータが存在するまで IMediaControl::Pause メソッドから復帰しない。これを確実にするために、レンダラがポーズされたときにレンダリングするデータがない場合は S_FALSE を返さなければならない。キューに入れられているデータがある場合は、S_OK を返すことができる。

フィルタ グラフ マネージャは、フィルタ グラフをポーズするときにすべての戻り値をチェックして、レンダラがデータをキューに入れたこと確認する。1 つまたは複数のフィルタの準備が整っていない場合、フィルタ グラフ マネージャは GetState 呼び出しによってグラフ内のそのフィルタをポーリングする。GetState メソッドは、タイムアウト パラメータを取る。状態の変更を完了する前にデータが到着するのを待っているフィルタ (通常はレンダラ) は、GetState メソッドがタイムアウトすると VFW_S_STATE_INTERMEDIATE を返す。データがレンダラに到着した場合は、GetState は S_OK を即座に返さなければならない。

中間状態および完了状態のどちらにおいても、報告されるフィルタ状態は State_Paused となる。フィルタの準備が整っているかどうかは、戻り値によって示される。レンダラがデータの到着を待っている場合、ソース フィルタはエンドオブストリーム通知を送信し、状態変更を完了しなければならない。

すべてのフィルタにおいてレンダリングするデータの準備が整うと、フィルタ グラフは、ポーズ状態の変更を完了する。

終了処理

ビデオ レンダラは、ユーザーからの終了イベントを正常に処理しなければならない。これは、ウィンドウを正常に非表示にするとともに、ウィンドウの表示が後で強制されたときにどうすべきかを知ることを意味する。ビデオ レンダラは、ウィンドウをいつ破棄 (正確には、レンダラがいつフィルタ グラフから削除されるか) してリソースを解放するかをフィルタ グラフ マネージャに通知しなければならない。

ユーザーがビデオ ウィンドウを閉じた場合 (たとえば Alt + F4 キーを押した場合)、慣習ではウィンドウを即座に非表示にし、EC_USERABORT 通知をフィルタ グラフ マネージャに送信する。この通知はアプリケーションに渡され、アプリケーションはグラフの再生を中止する。EC_USERABORT を送信した後は、ビデオ レンダラは配送されるすべてのサンプルを拒否しなければならない。

レンダラはアボート フラグをオンに保たなければならず、中止されたときに、アプリケーションがユーザー アクションをオーバーライドして (必要であれば) グラフの再生を続行するできるようにフラグをリセットしなければならない。ビデオの実行中に Alt + F4 キーが押された場合、ウィンドウは非表示になり、それ以降に配送されたサンプルはすべて拒否される。ウィンドウがそれ以降も表示される場合 (IVideoWindow::put_Visible)、EC_REPAINT 通知が生成されてはならない。

また、ビデオ レンダラは、終了するときに EC_WINDOW_DESTROYED 通知をフィルタ グラフに送信しなければならない。この処理は、実際のビデオ ウィンドウが破棄されるまで待つよりも、レンダラの IBaseFilter::JoinFilterGraph メソッドが (レンダラがフィルタ グラフから削除されようとしていることを示す) Null パラメータを取って呼び出されたときに行うのが最適である。この通知を送信すると、フィルタ グラフ マネージャの PID が、ウィンドウのフォーカスに依存したリソースを別のフィルタ (たとえばオーディオ デバイス) に渡せるようになる。

動的なフォーマットの変更の処理

DirectShow のビデオ レンダラは、効率的に描画できるビデオ フォーマットしか受け付けない。たとえばウィンドウ ベースのランタイム レンダラは、現在のディスプレイ デバイス モードに一致する RGB フォーマット (たとえばディスプレイが 65,536 色に設定されている場合は RGB565) だけを受け付ける。また、最後の手段として、ビデオ レンダラは、ほとんどのディスプレイ カードで効率的な描画が可能である 8 ビットのパレット化されたフォーマットを受け付ける。Microsoft® DirectDraw® をロードした場合、レンダラは DirectDraw サーフェスに書き込んだりディスプレイ ハードウェアから直接描画できるものに切り替えるように後でソース フィルタに要求することがある。場合によっては、ビデオの再生中にレンダラのアップストリーム フィルタがビデオ フォーマットを変更しようとすることもある。このような操作は、ビデオ ストリームがパレットの変更を行ったときによく発生する。動的なフォーマット変更を開始するのは、ほとんどの場合ビデオ デコンプレッサである。

フォーマットを動的に変更しようとするアップストリーム フィルタは、レンダラの入力ピンで必ず IPin::QueryAccept メソッドを呼び出さなければならない (CTransformFilter に基づくフィルタの場合、これは CTransformFilter::CheckInputType で実装される)。アップストリーム フィルタが動的に変更する対象としてレンダラがどのフォーマットを許可するかは不定である。ただし、最低限でもレンダラはアップストリーム フィルタがパレットを変更することを許可しなければならない。メディア タイプを変更する場合、アップストリーム フィルタは新しいタイプで配送された最初のサンプルにフォーマットをアタッチする。レンダリングされるのを待っているキューにたくさんのサンプルがある場合、レンダラはタイプが変更されたサンプルが実際にレンダリングされるときまで、フォーマットの変更を遅らせなければならない。

フォーマットの変更を検出するごとに、ビデオ レンダラは EC_DISPLAY_CHANGED 通知を送信しなければならない。ほとんどのビデオ レンダラは接続中にフォーマットを選択して、そのフォーマットが GDI を介して効率的に描画されるようにする。ユーザーがコンピュータを再起動せずに現在のディスプレイ モードを変更した場合、レンダラはそれ自体のイメージ フォーマット接続が不良であると判断してこの通知を送信しなければならない。第 1 パラメータは、再接続が必要なピンとする。フィルタ グラフ マネージャは、フィルタ グラフを停止してピンの再接続が行われるように準備する。それに続く再接続の間、レンダラはより適切なフォーマットを受け付けることができる。

ストリーム内でパレットの変更を検出するごとに、ビデオ レンダラは EC_PALETTE_CHANGED 通知をフィルタ グラフ マネージャに送信しなければならない。DirectShow ビデオ レンダラは、パレットが動的に変更されたかどうかを検出する。ビデオ レンダラは、送信された EC_PALETTE_CHANGED 通知をフィルタで除外するほかに、必要なパレットの作成、インストール、および削除処理の量を減らす目的で、このような動作を行う。

最後に、ビデオ レンダラはビデオのサイズが変更されたことを検出することがある。この場合、ビデオ レンダラは EC_VIDEO_SIZE_CHANGED 通知を送信しなければならない。アプリケーションはこの通知を使用して複合ドキュメント内の空間を調整することができる。ビデオの実際のディメンジョンは、IBasicVideo コントロール インターフェイスを使用して取得できる。DirectShow レンダラは、これらのイベントを送信する前に、ビデオのサイズが変更されたかどうかを検出する。

持続プロパティの処理

IBasicVideo および IVideoWindow インターフェイスを介して設定されるすべてのプロパティは、接続間で持続性を持つとみなされる。したがって、レンダラの接続解除および接続処理は、ウィンドウのサイズ、位置、またはスタイルに影響を及ぼしてはならない。しかし、接続間でビデオ ディメンジョンが変更された場合、レンダラは転送元矩形および転送先矩形をそのデフォルトにリセットしなければならない。転送元および転送先の位置は、IBasicVideo インターフェイスを介して設定される。

IBasicVideo および IVideoWindow は、どちらもプロパティへの十分なアクセスを提供して、インターフェイス内のすべてのデータを持続フォーマットで保存および復元することを可能にする。これは、編集セッション中にフィルタ グラフの正確な構成およびプロパティを保存して後で復元しなければならないアプリケーションにとって役に立つ。

EC_REPAINT 通知の処理

EC_REPAINT 通知は、レンダラがポーズまたは停止状態のときだけ送信される。この通知は、レンダラがデータを必要としていることをフィルタ グラフ マネージャに通知する。これらの通知の 1 つを受け取ったときにフィルタ グラフが停止状態であった場合、フィルタ グラフ マネージャはフィルタ グラフをポーズ状態にし、(GetState 呼び出しによって) すべてのフィルタがデータを受け取るのを待ち、再度フィルタ グラフを停止させる。停止状態のとき、ビデオ レンダラは後続の WM_PAINT メッセージを処理できるようにイメージを保持しなければならない。

したがって、停止またはポーズ状態でビデオ レンダラが WM_PAINT メッセージを受け取り、ウィンドウを描画するものがない場合は、EC_REPAINT をフィルタ グラフ マネージャに送信しなければならない。EC_REPAINT 通知がポーズ状態で受け取られた場合、フィルタ グラフ マネージャは、現在位置を使って IMediaPosition::put_CurrentPosition を呼び出す (すなわち現在位置へのシーク)。これにより、ソース フィルタはフィルタ グラフをフラッシュし、新しいデータがフィルタ グラフを介して送信される。

レンダラは、一度のこれらの通知のうち 1 つだけを送信しなければならない。したがって、通知を送信した場合、レンダラはいくつかのサンプルが配送されるまでそれ以上のデータが送信されないことを保証しなければならない。これを行う従来の方法は、フラグを使用して再描画が送信可能かどうかを示す方法である。この場合、フラグは、EC_REPAINT 通知が送信された後にオフに設定される。このフラグは、データが配送されたり入力ピンがフラッシュされたときにリセットされなければならない。ただし、エンドオブストリームが入力ピンに通知済の場合はこれに当てはまらない。

レンダラがその EC_REPAINT 通知を監視しないと、フィルタ グラフ マネージャは EC_REPAINT 要求であふれることになる (非常に高価な処理になる)。たとえば描画するイメージをレンダラが持たず、フルドラッグ操作で別のウィンドウがレンダラのウィンドウ上をドラッグされた場合、レンダラは複数の WM_PAINT メッセージを受け取る。これらのメッセージのうちの最初のものだけがレンダラからフィルタ グラフ マネージャへの EC_REPAINT イベント通知を生成しなければならない。

レンダラは、その入力ピンを EC_REPAINT 通知の第 1 パラメータとして送信しなければならない。これにより、アタッチされた出力ピンに対して IMediaEventSink の照会が行われ、サポートされている場合は EC_REPAINT 通知が最初に送信される。これにより、フィルタ グラフ以前に出力ピンが再描画を扱うことが可能になる。フィルタ グラフが停止状態の場合、デコミットされたレンダラ アロケータからのバッファを利用することができないので、この処理は行われない。

出力ピンが要求を処理することができず、フィルタ グラフが実行中の場合、EC_REPAINT 通知は無視される。出力ピンは、IMediaEventSink::Notify から NOERROR (S_OK) を返すことによって、再描画要求を正常に処理したことを通知しなければならない。出力ピンはフィルタ グラフ マネージャのワーカー スレッドで呼び出される。すなわち、レンダラが出力ピンを直接呼び出さないようにしてデッドロックが回避されする。フィルタ グラフが停止状態またはポーズ状態で出力が要求を処理しない場合は、デフォルトの処理が行われる。

フルスクリーン モードでの通知の処理

フィルタ グラフの IVideoWindow PID は、フルスクリーン再生を管理する。この PID は、スペシャリスト フルスクリーン レンダラのためにビデオ レンダラをスワップ アウトしたり、レンダラのウィンドウをフルスクリーンに拡大したり、あるいはレンダラにフルスクリーン再生を直接実装させる。フルスクリーン プロトコルを使うためには、ビデオ レンダラはウィンドウがアクティブまたは非アクティブにされるごとに EC_ACTIVATE 通知を送信しなければならない。すなわち、レンダラが受け取るそれぞれの WM_ACTIVATEAPP メッセージに対して EC_ACTIVATE 通知を送信しなければならない。

レンダラをフルスクリーン モードで使用するとき、これらの通知はフルスクリーン モードの切り替えを管理する。通常、ウィンドウの非アクティブは、ユーザーが Alt + Tab キーを押して別のウィンドウに切り替えるときに発生する。DirectShow フルスクリーン レンダラはこれをキューとして一般的なレンダリング モードに戻る。

フルスクリーン モードの終了時に EC_ACTIVATE 通知がフィルタ グラフ マネージャに送信されると、フィルタ グラフ マネージャは EC_FULLSCREEN_LOST 通知を制御アプリケーションに送信する。アプリケーションは、たとえばこの通知を使用してフルスクリーン ボタンの状態を復元する。EC_ACTIVATE 通知は、ビデオ レンダラからキューが渡されたときのフルスクリーン切り替えを管理するために DirectShow 内部で使用される。

通知の要約

レンダラが送信可能なフィルタ グラフ通知を以下に示す。

イベント通知説明
EC_ACTIVATE フルスクリーン レンダリング モードのビデオ レンダラによって、受け取ったそれぞれの WM_ACTIVATEAPP メッセージについて送信される。
EC_COMPLETE すべてのデータがレンダリングされた後でレンダラによって送信される。
EC_DISPLAY_CHANGED ディスプレイ フォーマットが変更されたときにビデオ レンダラによって送信される。
EC_PALETTE_CHANGED ビデオ レンダラがストリーム内でパレットの変更を検出するごとに送信される。
EC_REPAINT WM_PAINT メッセージが受け取られ、表示するデータがないときに、停止またはポーズ状態のビデオ レンダラによって送信される。これにより、フィルタ グラフ マネージャは、ディスプレイに描画するフレームを生成する。
EC_USERABORT ユーザーが終了を要求したことを通知するためにビデオ レンダラによって送信される (たとえばユーザーがビデオ ウィンドウを閉じた場合)。
EC_VIDEO_SIZE_CHANGED ネイティブ ビデオ サイズの変更が検出されるごとにビデオ レンダラによって送信される。
EC_WINDOW_DESTROYED フィルタが削除または破棄されたときにビデオ レンダラによって送信され、ウィンドウのフォーカスに依存するリソースをほかのフィルタに渡せるようにする。