Microsoft DirectX 8.0 |
このトピックでは、Microsoft® DirectShow® アプリケーションで発生するイベントに応答する方法を解説する。ビデオ ウィンドウの設定で示したサンプルに基づくサンプル プログラムを示すが、サンプルのすべてのコードは示さず、追加が必要な約 25 行のコードについてのみ説明する。
このトピックは、以下のセクションで構成される。
DirectShow アプリケーションの実行中は、さまざまな時点でフィルタの状態が変化する。たとえば、フィルタはポーズ状態から実行状態になったり、ストリーム内でエラーに出くわしたり、ビデオ ウィンドウの再描画を要求したりする。フィルタ グラフ マネージャに変化を知らせるために、フィルタは"イベント通知"を送信する。イベント通知は、イベントのタイプを示すイベント コードと、追加情報を与える 2 つのパラメータからなる。パラメータの意味は、イベント コードによって異なる。イベント コードとそのパラメータの完全なリストについては、「イベント通知コード」を参照すること。
フィルタ グラフ マネージャは、ビデオ ウィンドウの再描画の要求など、一部のイベントについてはアプリケーションに通知せずに処理を行い、その他のイベントはキューに入れる。アプリケーションは、キュー内のイベントを順番に取り出して処理する。DirectShow のイベント通知は、この点で Microsoft® Windows® のメッセージ キューイングと似ている。実際、新しいイベントが発生したとき、フィルタ グラフ マネージャに、指定したウィンドウに Windows メッセージを送信させることができる。アプリケーションは、この方法でウィンドウのメッセージ ループの中からイベントを処理することができる。
アプリケーションは、その目的に応じていくつかのイベント コードを扱う必要がある。ここでは、EC_COMPLETE と EC_USERABORT の 2 つに重点を置く。
サンプル アプリケーションでは、ウィンドウのメイン メッセージ ループの中からイベントを処理する。そのために、新しいイベントが発生したとき、フィルタ グラフ マネージャにメッセージを送信させる。アプリケーションはそれに応えて、イベントを受け取り適切な処理を行う。
ウィンドウのメイン メッセージ ループの中からイベントを処理するために、新しいイベントが発生したときにアプリケーションに送信されるメッセージを定義する。以下の例に示すように、アプリケーションはプライベート メッセージとして、WM_APP 〜 0xBFFF の範囲のメッセージ番号を使用できる。
#define WM_GRAPHNOTIFY WM_APP + 1
次に、このメッセージをアプリケーションのメイン ウィンドウに配信するように、フィルタ グラフ マネージャを設定する。
pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);
IMediaEventEx::SetNotifyWindow メソッドは、指定されたウィンドウ (g_hwnd) をメッセージの受信者として指定する。フィルタ グラフを作成し、所有者ウィンドウを指定したら、グラフを実行する前にこのメソッドを呼び出す。詳細については、「サンプル コード」を参照すること。IMediaEventEx インターフェイスは、フィルタ グラフ マネージャが公開する。
アプリケーションが WM_GRAPHNOTIFY メッセージを受け取るとき、メッセージの lParam パラメータは、SetNotifyWindow に渡した 3 つめのパラメータと等しい。このパラメータを使用すれば、メッセージと共にインスタンス データを送ることができる。サンプル コードはこのデータを使用しないので、値 0 を渡す。メッセージの wParam パラメータは常に 0 である。
ウィンドウの WindowProc 関数に、WM_GRAPHNOTIFY メッセージ用の case ステートメントを追加する :
case WM_GRAPHNOTIFY: HandleEvent(); break;
WM_GRAPHNOTIFY は、通常の Windows メッセージであり、DirectShow のイベント通知キューとは別に送信される。イベントのシーケンスは以下のとおりである。
イベント ハンドラ関数の中で IMediaEvent::GetEvent メソッドを呼び出して、キューからイベントを取り出す。
long evCode, param1, param2; HRESULT hr; while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr)) { hr = pEvent->FreeEventParams(evCode, param1, param2); if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode)) { CleanUp(); break; } }
GetEvent メソッドは、イベント コードと 2 つのパラメータを取り出す。GetEvent への最後のパラメータは、このメソッドがイベントを待つ時間を指定する。アプリケーションは、WM_GRAPHNOTIFY メッセージに応えてこのメソッドを呼び出す。そのときイベントは既にキューに入っているので、タイムアウト値に 0 を指定する。
イベント通知とメッセージ ループはどちらも非同期で行われるため、アプリケーションがメッセージに応答するときまでに、キューには複数のイベントが入っている可能性がある。また、フィルタ グラフ マネージャは、イベントが無効になった場合にキューからイベントを消去することができる。そのため、キューが空であることを示す失敗コードが返されるまで GetEvent を呼び出す。
サンプル プログラムは、EC_COMPLETE または EC_USERABORT イベントを受け取ると、アプリケーションで定義された CleanUp 関数を呼び出す。この関数によって、アプリケーションは速やかに終了する。2 つのイベント パラメータ (param1 と param2) は無視される。イベントを取り出したら、IMediaEvent::FreeEventParams を呼び出して、イベント パラメータに関連するリソースを解放する。たとえば、パラメータが BSTR の場合、そのメモリはフィルタ グラフ マネージャによって割り当てられている。
EC_COMPLETE イベントが発生したとき、フィルタ グラフは自動的には停止状態に切り替わらないので、アプリケーションがグラフを停止またはポーズしなければならない。グラフが停止すると、フィルタは保持しているリソースを解放する。グラフがポーズした場合、フィルタはリソースを保持し続ける。なお、ビデオ レンダラはポーズしたとき、最新のフレームの静止画像を表示する。サンプル プログラムはグラフを停止する。
#include <windows.h> #include <dshow.h> #define WM_GRAPHNOTIFY WM_APP + 1 #define CLASSNAME "EventNotify" IGraphBuilder *pGraph = NULL; IMediaControl *pMediaControl = NULL; IMediaEventEx *pEvent = NULL; IVideoWindow *pVidWin = NULL; HWND g_hwnd; void PlayFile(void) { // フィルタ グラフ マネージャを作成し、ファイルをレンダリングする。 CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGraph); pGraph->RenderFile(L"C:\\Media\\Boys.avi", NULL); // 所有者ウィンドウを指定する。 pGraph->QueryInterface(IID_IVideoWindow, (void **)&pVidWin); pVidWin->put_Owner((OAHWND)g_hwnd); pVidWin->put_WindowStyle( WS_CHILD | WS_CLIPSIBLINGS); // イベント通知を受け取るように所有者ウィンドウを設定する。 pGraph->QueryInterface(IID_IMediaEventEx, (void **)&pEvent); pEvent->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0); // グラフを実行する。 pGraph->QueryInterface(IID_IMediaControl, (void **)&pMediaControl); pMediaControl->Run(); } void CleanUp(void) { pVidWin->put_Visible(OAFALSE); pVidWin->put_Owner(NULL); // グラフを停止する。 pMediaControl->Stop(); pMediaControl->Release(); pVidWin->Release(); pEvent->Release(); pGraph->Release(); PostQuitMessage(0); } void HandleEvent() { long evCode, param1, param2; HRESULT hr; while (hr = pEvent->GetEvent(&evCode, ¶m1, ¶m2, 0), SUCCEEDED(hr)) { hr = pEvent->FreeEventParams(evCode, param1, param2); if ((EC_COMPLETE == evCode) || (EC_USERABORT == evCode)) { CleanUp(); break; } } } /* ここに WindowProc が入る。以下の case ステートメントを追加する。 case WM_GRAPHNOTIFY: HandleEvent(); break; */ // ここに WinMain が入る。