Microsoft DirectX 8.0

イベントへの応答

このトピックでは、Microsoft® DirectShow® アプリケーションで発生するイベントに応答する方法を解説する。ビデオ ウィンドウの設定で示したサンプルに基づくサンプル プログラムを示すが、サンプルのすべてのコードは示さず、追加が必要な約 25 行のコードについてのみ説明する。

このトピックは、以下のセクションで構成される。

イベント通知の動作

DirectShow アプリケーションの実行中は、さまざまな時点でフィルタの状態が変化する。たとえば、フィルタはポーズ状態から実行状態になったり、ストリーム内でエラーに出くわしたり、ビデオ ウィンドウの再描画を要求したりする。フィルタ グラフ マネージャに変化を知らせるために、フィルタは"イベント通知"を送信する。イベント通知は、イベントのタイプを示すイベント コードと、追加情報を与える 2 つのパラメータからなる。パラメータの意味は、イベント コードによって異なる。イベント コードとそのパラメータの完全なリストについては、「イベント通知コード」を参照すること。

フィルタ グラフ マネージャは、ビデオ ウィンドウの再描画の要求など、一部のイベントについてはアプリケーションに通知せずに処理を行い、その他のイベントはキューに入れる。アプリケーションは、キュー内のイベントを順番に取り出して処理する。DirectShow のイベント通知は、この点で Microsoft® Windows® のメッセージ キューイングと似ている。実際、新しいイベントが発生したとき、フィルタ グラフ マネージャに、指定したウィンドウに Windows メッセージを送信させることができる。アプリケーションは、この方法でウィンドウのメッセージ ループの中からイベントを処理することができる。

アプリケーションは、その目的に応じていくつかのイベント コードを扱う必要がある。ここでは、EC_COMPLETEEC_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 のイベント通知キューとは別に送信される。イベントのシーケンスは以下のとおりである。

  1. フィルタが、フィルタ グラフ マネージャにイベント通知を送信する。
  2. フィルタ グラフ マネージャはイベントを処理しない場合、イベントをイベント キューに入れる。
  3. フィルタ グラフ マネージャが、アプリケーション ウィンドウに WM_GRAPHNOTIFY メッセージを送信する。
  4. アプリケーションが、ウィンドウのメッセージ ループの中からメッセージに応答する。
  5. アプリケーションが、キューからイベント通知を取り出す。

イベント ハンドラ関数の中で IMediaEvent::GetEvent メソッドを呼び出して、キューからイベントを取り出す。

long evCode, param1, param2;
HRESULT hr;
while (hr = pEvent->GetEvent(&evCode, &param1, &param2, 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 イベントが発生したとき、フィルタ グラフは自動的には停止状態に切り替わらないので、アプリケーションがグラフを停止またはポーズしなければならない。グラフが停止すると、フィルタは保持しているリソースを解放する。グラフがポーズした場合、フィルタはリソースを保持し続ける。なお、ビデオ レンダラはポーズしたとき、最新のフレームの静止画像を表示する。サンプル プログラムはグラフを停止する。

サンプル コード

以下のコードには、WinMain 関数や WindowProc 関数は含まれていない。これらの関数については、「ビデオ ウィンドウの設定」のサンプル コードを参照すること。
#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, &param1, &param2, 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 が入る。