代替ビデオ レンダラー

[このページに関連付けられている機能 DirectShow は、従来の機能です。 MediaPlayer、IMFMediaEngine、Media Foundation のオーディオ/ビデオ キャプチャに置き換わりました。 これらの機能は、Windows 10とWindows 11用に最適化されています。 新しいコードでは、可能であれば、DirectShow ではなく Media Foundation で MediaPlayerIMFMediaEngineAudio/Video Capture を使用することを強くお勧めします。 Microsoft は、レガシ API を使用する既存のコードを、可能であれば新しい API を使用するように書き換えるよう提案しています。]

このトピックでは、DirectShow 用のカスタム ビデオ レンダラーを作成する方法について説明します。

注意

カスタム ビデオ レンダラーを作成する代わりに、ビデオ ミキシング レンダラー (VMR) または 拡張ビデオ レンダラー (EVR) のプラグイン アロケーター 発表者を作成することをお勧めします。 このアプローチにより、DirectX ビデオ アクセラレーション (DXVA)、ハードウェアのインターレース解除、フレーム ステップ実行のサポートなど、VMR/EVR のすべての利点が得られ、カスタム ビデオ レンダラーよりも堅牢になる可能性が高くなります。 詳細については、次のトピックを参照してください。

 

代替レンダラーの作成

Microsoft DirectShow には、ウィンドウベースのビデオ レンダラーが用意されています。また、実行時のインストールで全画面表示レンダラーも提供されます。 DirectShow 基本クラスを使用して、代替ビデオ レンダラーを作成できます。 代替レンダラーが DirectShow ベースのアプリケーションと正しく対話するには、レンダラーは、この記事で概説されているガイドラインに従う必要があります。 CBaseRenderer クラスと CBaseVideoRenderer クラスを使用すると、代替ビデオ レンダリングを実装するときに、これらのガイドラインに従うことができます。 DirectShow の継続的な開発のため、実装を定期的に確認して、レンダラーが最新バージョンの DirectShow と互換性があることを確認します。

このトピックでは、レンダラーが処理を担当する多くの通知について説明します。 DirectShow 通知の簡単なレビューは、ステージの設定に役立つ場合があります。 DirectShow では、基本的に次の 3 種類の通知が発生します。

  • ストリーム通知。メディア ストリームで発生し、あるフィルターから次のフィルターに渡されるイベントです。 これらは、begin-flushing、end-flushing、または end-of-stream の通知であり、ダウンストリーム フィルターの入力ピン ( IPin::BeginFlush など) で適切なメソッドを呼び出すことによって送信されます。
  • フィルター グラフ通知。フィルターからフィルター グラフ マネージャーに送信されるイベント ( EC_COMPLETEなど)。 これを行うには、フィルター グラフ マネージャーで IMediaEventSink::Notify メソッドを呼び出します。
  • アプリケーション通知。制御アプリケーションによってフィルター グラフ マネージャーから取得されます。 アプリケーションは、フィルター グラフ マネージャーで IMediaEvent::GetEvent メソッドを呼び出して、これらのイベントを取得します。 多くの場合、フィルター グラフ マネージャーは、受信したイベントをアプリケーションに渡します。

このトピックでは、受信したストリーム通知を処理し、適切なフィルター グラフ通知を送信する場合のレンダラー フィルターの役割について説明します。

ストリームの終了通知とフラッシュ通知の処理

ストリームの終了通知は、そのフィルターがそれ以上データを送信できないと検出されると、アップストリーム フィルター (ソース フィルターなど) から開始されます。 これはグラフ内のすべてのフィルターを通過し、最終的にはレンダラーで終了します。これは、その後、フィルター グラフ マネージャーに 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 メッセージを受信したときに現在のフレームを再描画しないようにすることです (描画される別のフレームが受信されることを前提とします)。 ただし、ストリームの終了通知が送信されると、レンダラーは待機状態になります。まだ実行中ですが、追加のデータを受信しないことに注意してください。 このような状況では、レンダラーは通常、再生領域を黒に描画します。

フラッシュ処理は、レンダラーにとってさらに複雑です。 フラッシュは、BeginFlush と EndFlush と呼ばれる IPin メソッドのペアを介して実行されます。 フラッシュは基本的に、レンダラーが処理する必要がある追加の状態です。 ソース フィルターが EndFlush を呼び出さずに BeginFlush を呼び出すことは無効であるため、状態が短く不連続である場合は有効です。ただし、レンダラーはフラッシュ遷移中に受信したデータまたは通知を正しく処理する必要があります。

BeginFlush を呼び出した後に受信したデータは、S_FALSEを返すことによってすぐに拒否する必要があります。 さらに、レンダラーがフラッシュされると、キャッシュされたストリームの終了通知もクリアする必要があります。 レンダラーは通常、シークに応答してフラッシュされます。 フラッシュにより、新しいサンプルが送信される前に、古いデータがフィルター グラフからクリアされます。 (通常、ストリームの 2 つのセクションの再生は、1 つのセクションが終了してから seek コマンドを発行するのを待つのではなく、遅延コマンドを使用して処理するのが最適です)。

状態変更の処理と一時停止の完了

レンダラー フィルターは、状態が変更されたときにフィルター グラフ内の他のフィルターと同じように動作しますが、次の例外があります。 一時停止した後、レンダラーは一部のデータをキューに入れ、その後の実行時にレンダリングする準備が整います。 ビデオ レンダラーが停止すると、このキューに登録されたデータが保持されます。 これは、フィルター グラフの停止中にフィルターによってリソースを保持する必要がない DirectShow ルールの例外です。

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

ビデオ レンダラーは、実行中にのみサンプルをレンダリングしてリリースする必要があります。 一時停止中は、フィルターによってレンダリングされる場合がありますが (たとえば、ウィンドウに静的ポスター イメージを描画する場合)、解放しないでください。 オーディオ レンダラーは、一時停止中はレンダリングを行いません (ただし、ウェーブ デバイスの準備など、他のアクティビティを実行することはできます)。 サンプルをレンダリングする時刻は、サンプル内のストリーム時間と 、IMediaControl::Run メソッドにパラメーターとして渡される参照時間を組み合わせることによって取得されます。 レンダラーは、開始時刻が終了時刻以下のサンプルを拒否する必要があります。

アプリケーションがフィルター グラフを一時停止すると、フィルター グラフは、レンダラーでデータがキューに格納されるまで 、IMediaControl::P ause メソッドから戻りません。 これを確認するために、レンダラーが一時停止すると、レンダリングを待機しているデータがない場合はS_FALSEが返されます。 データがキューに入っている場合は、 S_OKを返すことができます。

フィルター グラフ マネージャーは、フィルター グラフを一時停止するときにすべての戻り値をチェックして、レンダラーがデータをキューに入れるようにします。 1 つ以上のフィルターの準備ができていない場合、フィルター グラフ マネージャーは IMediaFilter::GetState を呼び出してグラフ内のフィルターをポーリングします。 GetState メソッドは、タイムアウト パラメーターを受け取ります。 状態の変更を完了する前にデータの到着を待機しているフィルター (通常はレンダラー) は、GetState メソッドの有効期限が切れた場合にVFW_S_STATE_INTERMEDIATEを返します。 レンダラーにデータが到着すると、 GetStateS_OKを使用してすぐに返されます。

中間状態と完了状態の両方で、報告されたフィルター状態がState_Pausedされます。 戻り値のみが、フィルターが実際に準備ができているかどうかを示します。 レンダラーがデータの到着を待機している間に、ソース フィルターがストリームの終了通知を送信する場合は、状態の変更も完了する必要があります。

すべてのフィルターが実際にデータのレンダリングを待機すると、フィルター グラフは一時停止状態の変更を完了します。

終了の処理

ビデオ レンダラーは、ユーザーからの終了イベントを正しく処理する必要があります。 これは、ウィンドウを正しく非表示にし、その後ウィンドウが強制的に表示された場合の処理を把握することを意味します。 また、ビデオ レンダラーは、リソースを解放するために、ウィンドウが破棄されたときにフィルター グラフ マネージャーに通知する必要があります (または、レンダラーがフィルター グラフから削除された場合、より正確に)。

ユーザーが (たとえば Alt + F4 キーを押して) ビデオ ウィンドウを閉じる場合、規則はウィンドウをすぐに非表示にし、 フィルター グラフ マネージャーにEC_USERABORT通知を送信することです。 この通知はアプリケーションに渡され、グラフの再生が停止されます。 EC_USERABORT送信した後、ビデオ レンダラーは、配信された追加のサンプルを拒否する必要があります。

グラフ停止フラグは、その後停止するまでレンダラーによってオンのままにする必要があります。その時点で、アプリケーションがユーザー アクションをオーバーライドし、必要に応じてグラフを再生し続けることができるようにリセットする必要があります。 ビデオの実行中に Alt + F4 キーを押すと、ウィンドウは非表示になり、それ以降に配信されるすべてのサンプルは拒否されます。 その後ウィンドウが表示される場合 ( IVideoWindow::p ut_Visible を使用する場合)、 EC_REPAINT 通知は生成されません。

ビデオ レンダラーは、ビデオ レンダラーの終了時に 、EC_WINDOW_DESTROYED 通知をフィルター グラフに送信する必要もあります。 実際、実際のビデオ ウィンドウが破棄されるまで待つのではなく、レンダラーの IBaseFilter::JoinFilterGraph メソッドが null パラメーター (レンダラーがフィルター グラフから削除されようとしていることを示す) で呼び出されたときに、これを処理することをお勧めします。 この通知を送信すると、Filter Graph Manager のプラグイン ディストリビューターは、ウィンドウフォーカスに依存するリソースをオーディオ デバイスなどの他のフィルターに渡すことができます。

動的書式変更の処理

場合によっては、レンダラーのアップストリーム フィルターがビデオの再生中にビデオ形式の変更を試みる場合があります。 ほとんどの場合、動的な形式の変更を開始するビデオ圧縮解除器です。

動的に形式を変更しようとするアップストリーム フィルターでは、常にレンダラー入力ピンで IPin::QueryAccept メソッドを呼び出す必要があります。 ビデオ レンダラーには、サポートする必要がある動的な形式の変更の種類に関する余裕があります。 少なくとも、アップストリーム フィルターでパレットを変更できるようにする必要があります。 アップストリーム フィルターは、メディアの種類を変更すると、新しい形式で配信された最初のサンプルにメディアの種類をアタッチします。 レンダラーがレンダリング用のキューにサンプルを保持している場合は、型が変更された状態でサンプルをレンダリングするまで、形式を変更しないでください。

ビデオ レンダラーは、デコーダーからフォーマットの変更を要求することもできます。 たとえば、デコーダーに対して、負の biHeight を使用して DirectDraw 互換の形式を提供するように求める場合があります。 レンダラーを一時停止すると、アップストリーム ピンで QueryAccept を 呼び出して、デコーダーが提供できる形式を確認する必要があります。 ただし、デコーダーは受け入れ可能なすべての型を列挙しない可能性があるため、デコーダーがそれらをアドバタイズしない場合でも、レンダラーは一部の型を提供する必要があります。

デコーダーが要求された形式に切り替えることができる場合は、QueryAccept からS_OKが返されます。 次に、レンダラーは、アップストリーム アロケーターの次のメディア サンプルに新しいメディアの種類をアタッチします。 これを機能させるには、レンダラーは、メディアの種類を次のサンプルにアタッチするためのプライベート メソッドを実装するカスタム アロケーターを提供する必要があります。 (このプライベート メソッド内で 、IMediaSample::SetMediaType を呼び出して型を設定します)。

レンダラーの入力ピンは、 IMemInputPin::GetAllocator メソッドでレンダラーのカスタム アロケーターを返す必要があります。 アップストリーム フィルターでレンダラーのアロケーターが使用されていない場合に失敗するように 、IMemInputPin::NotifyAllocator をオーバーライドします。

一部のデコーダーでは、 biHeight を YUV 型で正の数に設定すると、デコーダーはイメージを逆さまに描画します。 (これは間違っており、デコーダーのバグと見なす必要があります)。

ビデオ レンダラーによってフォーマットの変更が検出されるたびに、 EC_DISPLAY_CHANGED 通知を送信する必要があります。 ほとんどのビデオ レンダラーは、接続中に形式を選択して、GDI を介して効率的に形式を描画できるようにします。 ユーザーがコンピューターを再起動せずに現在の表示モードを変更した場合、レンダラーはイメージ形式の接続が正しくなく、この通知を送信する必要があります。 最初のパラメーターは、再接続が必要なピンである必要があります。 フィルター グラフ マネージャーは、フィルター グラフが停止され、ピンが再接続されるように配置されます。 その後の再接続中に、レンダラーはより適切な形式を受け入れる可能性があります。

ビデオ レンダラーは、ストリーム内のパレットの変更を検出するたびに、 EC_PALETTE_CHANGED 通知をフィルター グラフ マネージャーに送信する必要があります。 DirectShow ビデオ レンダラーは、パレットが動的な形式で実際に変更されたかどうかを検出します。 ビデオ レンダラーは、送信された EC_PALETTE_CHANGED 通知の数を除外するだけでなく、必要なパレットの作成、インストール、削除の量を減らすためにもこれを行います。

最後に、ビデオ レンダラーはビデオのサイズが変更されたことを検出する場合もあります。その場合は、 EC_VIDEO_SIZE_CHANGED 通知を送信する必要があります。 アプリケーションはこの通知を使用して、複合ドキュメント内の領域をネゴシエートする場合があります。 実際のビデオ サイズは、 IBasicVideo コントロール インターフェイスを介して使用できます。 DirectShow レンダラーは、これらのイベントを送信する前に、ビデオが実際にサイズを変更したかどうかを検出します。

永続的なプロパティの処理

IBasicVideo インターフェイスと IVideoWindow インターフェイスを介して設定されるすべてのプロパティは、接続間で永続的であることを意図しています。 そのため、レンダラーを切断して再接続しても、ウィンドウのサイズ、位置、またはスタイルに影響を及ぼす必要はありません。 ただし、接続間でビデオのサイズが変更された場合、レンダラーはソースとターゲットの四角形を既定値にリセットする必要があります。 ソースと宛先の位置は、 IBasicVideo インターフェイスを介して設定されます。

IBasicVideoIVideoWindow はどちらも、アプリケーションがインターフェイス内のすべてのデータを永続的な形式で保存および復元できるように、プロパティに十分なアクセスを提供します。 これは、編集セッション中にフィルター グラフの正確な構成とプロパティを保存し、後で復元する必要があるアプリケーションに役立ちます。

EC_REPAINT通知の処理

EC_REPAINT通知は、レンダラーが一時停止または停止されている場合にのみ送信されます。 この通知は、レンダラーにデータが必要であることを Filter Graph Manager に通知します。 これらの通知の 1 つを受信したときにフィルター グラフが停止した場合、フィルター グラフは一時停止し、すべてのフィルターが ( GetState を呼び出して) データを受信するのを待ってから、もう一度停止します。 停止すると、ビデオ レンダラーは、後続の WM_PAINT メッセージを処理できるように、イメージを保持する必要があります。

したがって、ビデオ レンダラーが停止または一時停止したときに WM_PAINT メッセージを受信し、ウィンドウを描画するものが何もない場合は、 EC_REPAINT をフィルター グラフ マネージャーに送信する必要があります。 一時停止中 にEC_REPAINT 通知が受信された場合、Filter Graph Manager は IMediaPosition::p ut_CurrentPosition を現在の位置で呼び出します (つまり、現在の位置をシークします)。 これにより、ソース フィルターがフィルター グラフをフラッシュし、フィルター グラフを介して新しいデータが送信されます。

レンダラーは、これらの通知の 1 つだけを一度に送信する必要があります。 したがって、レンダラーが通知を送信すると、一部のサンプルが配信されるまで送信されないようにする必要があります。 これを行う従来の方法は、再描画を送信できることを示すフラグを持つことです。これは、 EC_REPAINT 通知が送信された後にオフになります。 このフラグは、データが配信された後、または入力ピンがフラッシュされたときにリセットする必要がありますが、入力ピンでストリームの終わりが通知される場合はリセットされません。

レンダラーが EC_REPAINT 通知を監視しない場合は、フィルター グラフ マネージャーに EC_REPAINT 要求が殺到します (処理には比較的コストがかかります)。 たとえば、レンダラーに描画するイメージがなく、フル ドラッグ操作でレンダラーのウィンドウ全体に別のウィンドウがドラッグされた場合、レンダラーは複数の WM_PAINT メッセージを受信します。 レンダラーから Filter Graph Manager への EC_REPAINT イベント通知を生成する必要があるのは、これらのうちの 1 つのみです。

レンダラーは、最初のパラメーターとして入力ピンを EC_REPAINT 通知に送信する必要があります。 これにより、接続された出力ピンに IMediaEventSink のクエリが実行され、サポートされている場合は、 最初にEC_REPAINT 通知が送信されます。 これにより、フィルター グラフをタッチする前に出力ピンで再描画を処理できます。 これは、フィルター グラフが停止した場合は行われません。これは、コミットされていないレンダラー アロケーターから使用できるバッファーがないためです。

出力ピンが要求を処理できず、フィルター グラフが実行されている場合、 EC_REPAINT 通知は無視されます。 出力ピンは IMediaEventSink::Notify からS_OKを返して、再描画要求が正常に処理されたことを通知する必要があります。 出力ピンは Filter Graph Manager ワーカー スレッドで呼び出されます。これにより、レンダラーが出力ピンを直接呼び出すのを回避し、デッドロックの問題を回避できます。 フィルター グラフが停止または一時停止され、出力で要求が処理されない場合は、既定の処理が実行されます。

Full-Screen モードでの通知の処理

フィルター グラフの IVideoWindow プラグイン ディストリビューター (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 ウィンドウフォーカスに依存するリソースを他のフィルターに渡すことができるように、フィルターが削除または破棄されたときにビデオ レンダラーによって送信されます。

 

ビデオ レンダラーの作成