EVR プレゼンターを作成する方法

[このページで説明されているコンポーネントである拡張ビデオ レンダラーは、レガシ機能です。 これは、MediaPlayer および IMFMediaEngine コンポーネントを介して公開される単純なビデオ レンダラー (SVR) に置き換えられました。 ビデオ コンテンツを再生するには、これらのコンポーネントのいずれかにデータを送信し、新しいビデオ レンダラーをインスタンス化できるようにする必要があります。 これらのコンポーネントは、Windows 10 および Windows 11 用に最適化されています。 Windows でビデオ メディアを再生する新しいコードでは、可能な場合は、EVR ではなく、MediaPlayer または下位レベルの IMFMediaEngine API を使うことを強くお勧めします。 Microsoft は、レガシ API を使用する既存コードを、新しい API を使用するように可能であれば書き直すことを提案しています。]

この記事では、拡張ビデオ レンダラー (EVR) のカスタム プレゼンターを作成する方法について説明します。 カスタム プレゼンターは、DirectShow とメディア ファンデーションの両方で使用できます。インターフェイスとオブジェクト モデルは両方のテクノロジで同じですが、操作の正確なシーケンスは異なる場合があります。

このトピックのコード例は、Windows SDK で提供されている EVRPresenter サンプルを作成し直したものです。

このトピックは、次のセクションで構成されています。

前提条件

カスタム プレゼンターを作成する前に、次のテクノロジについて理解しておく必要があります。

  • 拡張ビデオ レンダラー。 「拡張ビデオ レンダラー」を参照してください。
  • Direct3D グラフィックス プレゼンターを作成するために 3-D グラフィックスを理解する必要はありませんが、Direct3D デバイスを作成して Direct3D サーフェスを管理する方法を知っている必要があります。 Direct3D に慣れていない場合は、DirectX Graphics SDK ドキュメントの「Direct3D デバイス」と「Direct3D リソース」のセクションを参照してください。
  • DirectShow フィルター グラフまたはメディア ファンデーション パイプライン。アプリケーションがビデオのレンダリングに使用するテクノロジに応じて異なります。
  • メディア ファンデーション変換 EVR ミキサーはメディア ファンデーション変換であり、プレゼンターはミキサーでメソッドを直接呼び出します。
  • COM オブジェクトの実装。 プレゼンターは、インプロセスのフリースレッド COM オブジェクトです。

プレゼンター オブジェクト モデル

このセクションでは、プレゼンター オブジェクト モデルとインターフェイスの概要について説明します。

EVR 内のデータ フロー

EVR は、ミキサープレゼンターの 2 つのプラグイン コンポーネントを使用してビデオをレンダリングします。 ミキサーはビデオ ストリームをブレンドし、必要に応じてビデオのインターレースを解除します。 プレゼンターは、ビデオをディスプレイに描画 (またはプレゼンテーション) し、各フレームの描画時にスケジュールします。 アプリケーションは、これらのオブジェクトのいずれかをカスタム実装に置き換えることができます。

EVR には 1 つ以上の入力ストリームがあり、ミキサーには対応する数の入力ストリームがあります。 ストリーム 0 は常に参照ストリームです。 他のストリームはサブストリームであり、ミキサーはこれを参照ストリームにアルファ ブレンドします。 参照ストリームは、複合ビデオのマスター フレーム レートを決定します。 参照フレームごとに、ミキサーは各サブストリームから最新のフレームを取得し、それらを参照フレームにアルファ ブレンドし、1 つの複合フレームを出力します。 ミキサーは、必要に応じて、インターレース解除と YUV から RGB への色変換も実行します。 EVR は、入力ストリームの数やビデオ形式に関係なく、常にミキサーをビデオ パイプラインに挿入します。 次の図は、このプロセスを示しています。

diagram showing the reference stream and substream pointing to the mixer, which points to the presenter, which points to the display

プレゼンターは次のタスクを実行します。

  • ミキサーの出力形式を設定します。 ストリーミングが開始される前に、プレゼンターはミキサーの出力ストリームにメディアの種類を設定します。 このメディアの種類により、複合イメージの形式が定義されます。
  • Direct3D デバイスを作成します。
  • Direct3D サーフェスを割り当てます。 ミキサーは、複合フレームをこれらのサーフェスに blit します。
  • ミキサーから出力を取得します。
  • フレームが提示されるタイミングをスケジュールします。 EVR はプレゼンテーション クロックを提供し、プレゼンターはこのクロックに従ってフレームをスケジュールします。
  • Direct3D を使用して各フレームを提示します。
  • フレームのステップ実行とスクラブを実行します。

プレゼンターの状態

発表者は、いつでも次のいずれかの状態になります。

  • 開始。 EVR のプレゼンテーション クロックが実行されています。 プレゼンターは、ビデオ フレームが到着するとプレゼンテーションのスケジュールを設定します。
  • 一時停止。 プレゼンテーション クロックが一時停止されています。 プレゼンターは新しいサンプルを提示しませんが、スケジュールされたサンプルのキューを維持します。 新しいサンプルを受け取った場合、プレゼンターはそれらをキューに追加します。
  • 停止済み。 プレゼンテーション クロックが停止されています。 プレゼンターは、スケジュールされたすべてのサンプルを破棄します。
  • シャットダウン。 プレゼンターは、Direct3D サーフェスなど、ストリーミングに関連するすべてのリソースを解放します。 これはプレゼンターの初期状態であり、プレゼンターが破棄される前の最終状態です。

このトピックのコード例では、プレゼンターの状態は列挙によって表されます。

enum RENDER_STATE
{
    RENDER_STATE_STARTED = 1,
    RENDER_STATE_STOPPED,
    RENDER_STATE_PAUSED,
    RENDER_STATE_SHUTDOWN,  // Initial state.
};

プレゼンターがシャットダウン状態にある間は、一部の操作が無効です。 このコード例では、ヘルパー メソッドを呼び出すことによって、この状態をチェックします。

    HRESULT CheckShutdown() const
    {
        if (m_RenderState == RENDER_STATE_SHUTDOWN)
        {
            return MF_E_SHUTDOWN;
        }
        else
        {
            return S_OK;
        }
    }

プレゼンターのインターフェイス

プレゼンターは、次のインターフェイスを実装する必要があります。

Interface 説明
IMFClockStateSink EVR のクロックの状態が変化したときにプレゼンターに通知します。 「IMFClockStateSink の実装」を参照してください。
IMFGetService アプリケーションとパイプライン内の他のコンポーネントがプレゼンターからインターフェイスを取得する方法を提供します。
IMFTopologyServiceLookupClient プレゼンターが EVR またはミキサーからインターフェイスを取得できるようにします。 「IMFTopologyServiceLookupClient の実装」を参照してください。
IMFVideoDeviceID プレゼンターとミキサーが互換性のあるテクノロジを使用するようにします。 「IMFVideoDeviceID の実装」を参照してください。
IMFVideoPresenter EVR からのメッセージを処理します。 「IMFVideoPresenter の実装」を参照してください。

 

次のインターフェイスはオプションです。

Interface 説明
IEVRTrustedVideoPlugin プレゼンターが保護されたメディアを操作できるようにします。 プレゼンターが保護されたメディア パス (PMP) で動作するように設計された信頼できるコンポーネントである場合は、このインターフェイスを実装します。
IMFRateSupport プレゼンターがサポートする再生レートの範囲を報告します。 「IMFRateSupport の実装」を参照してください。
IMFVideoPositionMapper 出力ビデオ フレームの座標を入力ビデオ フレームの座標にマップします。
IQualProp パフォーマンス情報を報告します。 EVR は、この情報を品質管理に使用します。 このインターフェイスは、DirectShow SDK に記載されています。

 

また、アプリケーションがプレゼンターと通信するためのインターフェイスを提供することもできます。 標準プレゼンターは、この目的のために IMFVideoDisplayControl インターフェイスを実装します。 このインターフェイスを実装することも、独自のインターフェイスを定義することもできます。 アプリケーションは、EVR で IMFGetService::GetService を呼び出すことで、プレゼンターからインターフェイスを取得します。 サービス GUID が MR_VIDEO_RENDER_SERVICE である場合、EVR は GetService 要求をプレゼンターに渡します。

IMFVideoDeviceID の実装

IMFVideoDeviceID インターフェイスには、デバイス GUID を返す 1 つのメソッド GetDeviceID が含まれています。 デバイスGUID は、プレゼンターとミキサーが互換性のあるテクノロジを使用するようにします。 デバイス GUID が一致しない場合、EVR は初期化に失敗します。

標準ミキサーとプレゼンターはどちらも Direct3D 9 を使用し、デバイス GUID は IID_IDirect3DDevice9 と等しくなります。 標準ミキサーでカスタム プレゼンターを使用する場合は、プレゼンターのデバイス GUID を IID_IDirect3DDevice9 にする必要があります。 両方のコンポーネントを置き換える場合は、新しいデバイス GUID を定義できます。 この記事の残りの部分では、プレゼンターが Direct3D 9 を使用することを前提としています。 GetDeviceID の標準的な実装を次に示します。

HRESULT EVRCustomPresenter::GetDeviceID(IID* pDeviceID)
{
    if (pDeviceID == NULL)
    {
        return E_POINTER;
    }

    *pDeviceID = __uuidof(IDirect3DDevice9);
    return S_OK;
}

このメソッドは、プレゼンターがシャットダウンされた場合でも成功します。

IMFTopologyServiceLookupClient の実装

IMFTopologyServiceLookupClient インターフェイスを使用すると、プレゼンターは次のように EVR とミキサーからインターフェイス ポインターを取得できます。

  1. EVR は、プレゼンターを初期化するの、プレゼンターの IMFTopologyServiceLookupClient::InitServicePointers メソッドを呼び出します。 引数は、EVR の IMFTopologyServiceLookup インターフェイスへのポインターです。
  2. プレゼンターは IMFTopologyServiceLookup::LookupService を呼び出して、EVR またはミキサーからインターフェイス ポインターを取得します。

LookupService メソッドは、IMFGetService::GetService メソッドに似ています。 どちらのメソッドもサービス GUID とインターフェイス識別子 (IID) を入力として受け取りますが、LookupService はインターフェイス ポインターの配列を返し、GetService は単一のポインターを返します。 ただし、実際には、配列のサイズを常に 1 に設定できます。 クエリが実行されるオブジェクトは、サービス GUID によって異なります。

  • サービス GUID が MR_VIDEO_RENDER_Standard Edition RVICE である場合、EVR のクエリが実行されます。
  • サービス GUID が MR_VIDEO_MIXER_SERVICE である場合、ミキサーのクエリが実行されます。

InitServicePointers の実装で、EVR から次のインターフェイスを取得します。

EVR インターフェイス 説明
IMediaEventSink プレゼンターが EVR にメッセージを送信する方法を提供します。 このインターフェイスは DirectShow SDK で定義されているため、メッセージは、メディア ファンデーション イベントではなく DirectShow イベントのパターンに従います。
IMFClock EVR のクロックを提示します。 プレゼンターはこのインターフェイスを使用して、プレゼンテーションのサンプルをスケジュールします。 EVR はクロックなしで実行できるため、このインターフェイスは使用できない可能性があります。 使用できない場合は、LookupService のエラー コードを無視します。
このクロックは IMFTimer インターフェイスも実装します。 メディア ファンデーション パイプラインでは、クロックは IMFPresentationClock インターフェイスを実装します。 DirectShow ではこのインターフェイスを実装しません。

 

ミキサーから次のインターフェイスを取得します。

ミキサー インターフェイス 説明
IMFTransform プレゼンターがミキサーと通信できるようにします。
IMFVideoDeviceID プレゼンターがミキサーのデバイス GUID を検証できるようにします。

 

次のコードは、InitServicePointers メソッドを実装します。

HRESULT EVRCustomPresenter::InitServicePointers(
    IMFTopologyServiceLookup *pLookup
    )
{
    if (pLookup == NULL)
    {
        return E_POINTER;
    }

    HRESULT             hr = S_OK;
    DWORD               dwObjectCount = 0;

    EnterCriticalSection(&m_ObjectLock);

    // Do not allow initializing when playing or paused.
    if (IsActive())
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    // Ask for the clock. Optional, because the EVR might not have a clock.
    dwObjectCount = 1;

    (void)pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL,   // Not used.
        0,                          // Reserved.
        MR_VIDEO_RENDER_SERVICE,    // Service to look up.
        IID_PPV_ARGS(&m_pClock),    // Interface to retrieve.
        &dwObjectCount              // Number of elements retrieved.
        );

    // Ask for the mixer. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_pMixer), &dwObjectCount
        );

    if (FAILED(hr))
    {
        goto done;
    }

    // Make sure that we can work with this mixer.
    hr = ConfigureMixer(m_pMixer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Ask for the EVR's event-sink interface. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_pMediaEventSink),
        &dwObjectCount
        );

    if (FAILED(hr))
    {
        goto done;
    }

    // Successfully initialized. Set the state to "stopped."
    m_RenderState = RENDER_STATE_STOPPED;

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

LookupService から取得したインターフェイス ポインターが無効になると、EVR は IMFTopologyServiceLookupClient::ReleaseServicePointers を呼び出します。 このメソッド内で、すべてのインターフェイス ポインターを解放し、プレゼンターの状態をシャットダウンに設定します。

HRESULT EVRCustomPresenter::ReleaseServicePointers()
{
    // Enter the shut-down state.
    EnterCriticalSection(&m_ObjectLock);

    m_RenderState = RENDER_STATE_SHUTDOWN;

    LeaveCriticalSection(&m_ObjectLock);

    // Flush any samples that were scheduled.
    Flush();

    // Clear the media type and release related resources.
    SetMediaType(NULL);

    // Release all services that were acquired from InitServicePointers.
    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    return S_OK;
}

EVR は、次のようなさまざまな理由で ReleaseServicePointers を呼び出します。

  • ピンの切断または再接続 (DirectShow)、またはストリーム シンクの追加または削除 (Media Foundation)。
  • 形式の変更。
  • 新しいクロックの設定。
  • EVR の最終シャットダウン。

プレゼンターの有効期間中、EVR は InitServicePointersReleaseServicePointers を複数回呼び出す場合があります。

IMFVideoPresenter の実装

IMFVideoPresenter インターフェイスは IMFClockStateSink を継承し、次の 2 つのメソッドを追加します。

メソッド 説明
GetCurrentMediaType 複合ビデオ フレームのメディアの種類を返します。
ProcessMessage プレゼンターにさまざまなアクションを実行するよう通知します。

 

GetCurrentMediaType メソッドは、プレゼンターのメディアの種類を返します。 (メディアの種類の設定の詳細については、「形式のネゴシエート」を参照してください)。メディアの種類は、IMFVideoMediaType インターフェイス ポインターとして返されます。 次の例では、プレゼンターがメディアの種類を IMFMediaType ポインターとして格納することを前提としています。 メディアの種類から IMFVideoMediaType インターフェイスを取得するには、QueryInterface を呼び出します。

HRESULT EVRCustomPresenter::GetCurrentMediaType(
    IMFVideoMediaType** ppMediaType
    )
{
    HRESULT hr = S_OK;

    if (ppMediaType == NULL)
    {
        return E_POINTER;
    }

    *ppMediaType = NULL;

    EnterCriticalSection(&m_ObjectLock);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    if (m_pMediaType == NULL)
    {
        hr = MF_E_NOT_INITIALIZED;
        goto done;
    }

    hr = m_pMediaType->QueryInterface(IID_PPV_ARGS(ppMediaType));

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

ProcessMessage メソッドは、EVR がプレゼンターと通信するための主要なメカニズムです。 次のメッセージが定義されています。 各メッセージの実装の詳細については、このトピックの残りの部分を参照してください。

メッセージ 説明
MFVP_MESSAGE_INVALIDATEMEDIATYPE ミキサーの出力メディアの種類が無効です。 プレゼンターは、ミキサーと新しいメディアの種類をネゴシエートする必要があります。 「形式のネゴシエート」を参照してください。
MFVP_MESSAGE_BEGINSTREAMING ストリーミングが開始されました。 このメッセージには特定のアクションは必要ありませんが、リソースの割り当てに使用できます。
MFVP_MESSAGE_ENDSTREAMING ストリーミングは終了しました。 MFVP_MESSAGE_BEGINSTREAMING メッセージに応答して割り当てたリソースをすべて解放します。
MFVP_MESSAGE_PROCESSINPUTNOTIFY ミキサーは新しい入力サンプルを受け取りましたが、新しい出力フレームを生成できる可能性があります。 プレゼンターは、ミキサーで IMFTransform::P rocessOutput を呼び出す必要があります。 「出力の処理」を参照してください。
MFVP_MESSAGE_ENDOFSTREAM プレゼンテーションは終了しました。 「ストリームの終了」を参照してください。
MFVP_MESSAGE_FLUSH EVR は、レンダリング パイプライン内のデータをフラッシュしています。 プレゼンターは、プレゼンテーション用としてスケジュールされているビデオ フレームをすべて削除する必要があります。
MFVP_MESSAGE_STEP プレゼンターに N フレームのステップ フォワードを要求します。 プレゼンターは、次の N-1 フレームを破棄し、N 番目のフレームを表示する必要があります。 「フレームのステップ実行」を参照してください。
MFVP_MESSAGE_CANCELSTEP フレームのステップ実行を取り消します。

 

IMFClockStateSink の実装

プレゼンターは、IMFClockStateSink を継承する IMFVideoPresenter の実装の一環として、IMFClockStateSink インターフェイスを実装する必要があります。 EVR はこのインターフェイスを使用して、EVR のクロックの状態が変化するたびにプレゼンターに通知します。 クロックの状態の詳細については、「プレゼンテーション クロック」を参照してください。

このインターフェイスでのメソッドの実装に関するガイドラインをいくつか次に示します。 プレゼンターがシャットダウンされると、すべてのメソッドが失敗します。

メソッド 説明
OnClockStart
  1. プレゼンターの状態を開始に設定します。
  2. llClockStartOffsetPRESENTATION_CURRENT_POSITION でない場合は、プレゼンターのサンプルのキューをフラッシュします。 (これは、MFVP_MESSAGE_FLUSH メッセージの受信に相当します。)
  3. 前のフレーム ステップ要求がまだ保留中の場合は、要求を処理します (「フレームのステップ実行」を参照)。 それ以外の場合は、ミキサーからの出力を処理してみてください (「出力の処理」を参照)。
OnClockStop
  1. プレゼンターの状態を停止に設定します。
  2. プレゼンターのサンプルのキューをフラッシュします。
  3. 保留中のフレーム ステップ操作を取り消します。
OnClockPause プレゼンターの状態を一時停止に設定します。
OnClockRestart OnClockStart と同じように扱いますが、サンプルのキューはフラッシュしません。
OnClockSetRate
  1. レートがゼロからゼロ以外に変化する場合は、フレームのステップ実行を取り消します。
  2. 新しいクロック レートを格納します。 クロック レートは、サンプルが提示されるタイミングに影響します。 詳細については、「サンプルのスケジュール」を参照してください。

 

IMFRateSupport の実装

1×速度以外の再生レートをサポートするには、プレゼンターが IMFRateSupport インターフェイスを実装する必要があります。 このインターフェイスでのメソッドの実装に関するガイドラインをいくつか次に示します。 プレゼンターがシャットダウンされると、すべてのメソッドが失敗します。 このインターフェイスの詳細については、「レート コントロール」を参照してください。

説明
GetSlowestRate 最小再生レートがないことを示す場合は、ゼロを返します。
GetFastestRate 非シン再生の場合、再生レートはモニターのリフレッシュ レートを超えないようにする必要があります: 最大レート = リフレッシュ レート (Hz) / ビデオ フレーム レート (fps)。 ビデオ フレーム レートは、プレゼンターのメディアの種類で指定されます。
シン再生の場合、再生レートは無制限です。値 FLT_MAX を返します。 実際には、ソースとデコーダーは、シン再生中の制限要因になります。
逆再生の場合は、最大レートの負の値を返します。
IsRateSupported flRate の絶対値がプレゼンターの最大再生レートを超えた場合は、MF_E_UNSUPPORTED_RATE を返します。 GetFastestRate の説明に従って、最大再生レートを計算します。

 

次の例は、GetFastestRate メソッドを実装する方法を示しています。

float EVRCustomPresenter::GetMaxRate(BOOL bThin)
{
    // Non-thinned:
    // If we have a valid frame rate and a monitor refresh rate, the maximum
    // playback rate is equal to the refresh rate. Otherwise, the maximum rate
    // is unbounded (FLT_MAX).

    // Thinned: The maximum rate is unbounded.

    float   fMaxRate = FLT_MAX;
    MFRatio fps = { 0, 0 };
    UINT    MonitorRateHz = 0;

    if (!bThin && (m_pMediaType != NULL))
    {
        GetFrameRate(m_pMediaType, &fps);
        MonitorRateHz = m_pD3DPresentEngine->RefreshRate();

        if (fps.Denominator && fps.Numerator && MonitorRateHz)
        {
            // Max Rate = Refresh Rate / Frame Rate
            fMaxRate = (float)MulDiv(
                MonitorRateHz, fps.Denominator, fps.Numerator);
        }
    }

    return fMaxRate;
}

前の例では、ヘルパー メソッド GetMaxRate を呼び出して、最大前方再生レートを計算しています。

次の例は、IsRateSupported メソッドを実装する方法を示しています。

HRESULT EVRCustomPresenter::IsRateSupported(
    BOOL bThin,
    float fRate,
    float *pfNearestSupportedRate
    )
{
    EnterCriticalSection(&m_ObjectLock);

    float   fMaxRate = 0.0f;
    float   fNearestRate = fRate;  // If we support fRate, that is the nearest.

    HRESULT hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    // Find the maximum forward rate.
    // Note: We have no minimum rate (that is, we support anything down to 0).
    fMaxRate = GetMaxRate(bThin);

    if (fabsf(fRate) > fMaxRate)
    {
        // The (absolute) requested rate exceeds the maximum rate.
        hr = MF_E_UNSUPPORTED_RATE;

        // The nearest supported rate is fMaxRate.
        fNearestRate = fMaxRate;
        if (fRate < 0)
        {
            // Negative for reverse playback.
            fNearestRate = -fNearestRate;
        }
    }

    // Return the nearest supported rate.
    if (pfNearestSupportedRate != NULL)
    {
        *pfNearestSupportedRate = fNearestRate;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

EVR へのイベントの送信

プレゼンターは、さまざまなイベントを EVR に通知する必要があります。 そのためには、EVR がプレゼンターの IMFTopologyServiceLookupClient::InitServicePointers メソッドを呼び出すときに取得される、EVR の IMediaEventSink インターフェイスを使用します。 (IMediaEventSink インターフェイスは、元は DirectShow インターフェイスですが、DirectShow EVR とメディア ファンデーションの両方で使用されます)。次のコードは、EVR にイベントを送信する方法を示しています。

    // NotifyEvent: Send an event to the EVR through its IMediaEventSink interface.
    void NotifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
    {
        if (m_pMediaEventSink)
        {
            m_pMediaEventSink->Notify(EventCode, Param1, Param2);
        }
    }

次の表に、プレゼンターが送信するイベントとイベント パラメーターを示します。

イベント 説明
EC_COMPLETE プレゼンターは、MFVP_MESSAGE_ENDOFSTREAM メッセージの後にすべてのフレームのレンダリングを完了しました。
  • Param1: 操作の状態を示す HRESULT。
  • Param2: 使用されません。
詳細については、「ストリームの終了」を参照してください。
EC_DISPLAY_CHANGED Direct3D デバイスが変更されました。
  • Param1: 使用されません。
  • Param2: 使用されません。
詳細については、「Direct3D デバイスの管理」を参照してください。
EC_ERRORABORT ストリーミングを停止する必要があるエラーが発生しました。
  • Param1: 発生したエラーを示す HRESULT
  • Param2: 使用されません。
EC_PROCESSING_LATENCY プレゼンターによる各フレームのレンダリングにかかる時間を指定します。 (省略可能)
  • Param1: フレームを処理する時間 (100 ナノ秒単位) が含まれる定数 LONGLONG 値へのポインター。
  • Param2: 使用されません。
詳細については、「出力の処理」を参照してください。
EC_SAMPLE_LATENCY レンダリング サンプル内の現在のラグ タイムを指定します。 値が正の場合、サンプルはスケジュールより遅れています。 値が負の場合、サンプルはスケジュールより進んでいます。 (省略可能)
  • Param1: ラグ タイム (100 ナノ秒単位) が含まれる定数 LONGLONG 値へのポインター。
  • Param2: 使用されません。
EC_SCRUB_TIME 再生レートがゼロである場合、EC_STEP_COMPLETE の直後に送信されます。 このイベントには、表示されたフレームのタイム スタンプが含まれます。
  • Param1: タイム スタンプの下位 32 ビット。
  • Param2: タイム スタンプの上位 32 ビット。
詳細については、「フレームのステップ実行」を参照してください。
EC_STEP_COMPLETE プレゼンターがフレーム ステップを完了したか取り消しました。
- Param1: 使用されません。
- Param2: 使用されません。
詳細については、「フレームのステップ実行」を参照してください。
注: 以前のバージョンのドキュメントでは、Param1 が間違って記述されていました。 このパラメータは、このイベントには使用されません。

 

形式のネゴシエート

プレゼンターは、EVR から MFVP_MESSAGE_INVALIDATEMEDIATYPE メッセージを受信するたびに、次のようにミキサーに出力形式を設定する必要があります。

  1. ミキサーで IMFTransform::GetOutputAvailableType を呼び出し、可能な出力の種類を取得します。 この種類は、入力ストリームとグラフィックス デバイスのビデオ処理機能が与えられた状況でミキサーが生成できる形式を記述します。

  2. プレゼンターがこのメディアの種類をレンダリング形式として使用できるかどうかを確認します。 確認する必要がある点を次に示します。ただし、実装には独自の要件がある場合があります。

    • ビデオは圧縮が解除されている必要があります。
    • ビデオにはプログレッシブ フレームのみが必要です。 MF_MT_INTERLACE_MODE 属性が MFVideoInterlace_Progressive と等しいことを確認します。
    • 形式は Direct3D デバイスと互換性がある必要があります。

    この種類が許容できない場合は、手順 1 に戻り、ミキサーの次の提案の種類を取得します。

  3. 元の種類の複製である新しいメディアの種類を作成してから、次の属性を変更します。

    • 割り当てる Direct3D サーフェスに必要な幅と高さと等しい MF_MT_FRAME_SIZE 属性を設定します。
    • MF_MT_PAN_SCAN_ENABLED 属性を FALSE に設定します。
    • ディスプレイの PAR と等しい MF_MT_PIXEL_ASPECT_RATIO 属性を設定します (通常は 1:1)。
    • Direct3D サーフェス内の四角形と等しい幾何学的な開口部 (MF_MT_GEOMETRIC_APERTURE 属性)を設定します。 ミキサーは、出力フレームを生成する場合、ソース イメージをこの四角形に blit します。 幾何学的な開口部は、サーフェスと同じ大きさにすることも、サーフェス内のサブ四角形にすることもできます。 詳細については、「ソースとコピー先の四角形」を参照してください。
  4. ミキサーが変更された出力の種類を受け入れるかどうかをテストするには、MFT_SET_TYPE_TEST_ONLY フラグを指定して IMFTransform::SetOutputType を呼び出します。 ミキサーがこの種類を拒否した場合は、手順 1 に戻り、次の種類を取得します。

  5. Direct3D サーフェスの割り当て」の説明に従って、Direct3D サーフェスのプールを割り当てます。 ミキサーは、複合ビデオ フレームを描画するときに、これらのサーフェスを使用します。

  6. フラグなしで SetOutputType を呼び出して、ミキサーの出力の種類を設定します。 SetOutputType の最初の呼び出しが手順 4 で成功した場合、メソッドは再び成功します。

ミキサーの種類が不足している場合、GetOutputAvailableType メソッドは MF_E_NO_MORE_TYPES を返します。 プレゼンターがミキサーに適した出力の種類を見つけられない場合は、ストリームをレンダリングできません。 その場合、DirectShow またはメディア ファンデーションが別のストリーム形式を試す可能性があります。 したがって、プレゼンターは、有効な種類が見つかるまで、複数の MFVP_MESSAGE_INVALIDATEMEDIATYPE メッセージを連続して受信する可能性があります。

ミキサーは、ソースとコピー先のピクセル縦横比 (PAR) を考慮して、ビデオを自動的にレターボックス化します。 最適な結果を得るには、サーフェスの幅と高さおよび幾何学的な開口部が、ビデオをディスプレイに表示する実際のサイズと同じである必要があります。 次の図は、このプロセスを示しています。

diagram showing a composited fram leading to a direct3d surface, which leads to a window

次のコードは、プロセスの概要を示しています。 一部の手順はヘルパー関数内にありますが、どの手順かに関する正確な詳細はプレゼンターの要件によって異なります。

HRESULT EVRCustomPresenter::RenegotiateMediaType()
{
    HRESULT hr = S_OK;
    BOOL bFoundMediaType = FALSE;

    IMFMediaType *pMixerType = NULL;
    IMFMediaType *pOptimalType = NULL;
    IMFVideoMediaType *pVideoType = NULL;

    if (!m_pMixer)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Loop through all of the mixer's proposed output types.
    DWORD iTypeIndex = 0;
    while (!bFoundMediaType && (hr != MF_E_NO_MORE_TYPES))
    {
        SafeRelease(&pMixerType);
        SafeRelease(&pOptimalType);

        // Step 1. Get the next media type supported by mixer.
        hr = m_pMixer->GetOutputAvailableType(0, iTypeIndex++, &pMixerType);
        if (FAILED(hr))
        {
            break;
        }

        // From now on, if anything in this loop fails, try the next type,
        // until we succeed or the mixer runs out of types.

        // Step 2. Check if we support this media type.
        if (SUCCEEDED(hr))
        {
            // Note: None of the modifications that we make later in CreateOptimalVideoType
            // will affect the suitability of the type, at least for us. (Possibly for the mixer.)
            hr = IsMediaTypeSupported(pMixerType);
        }

        // Step 3. Adjust the mixer's type to match our requirements.
        if (SUCCEEDED(hr))
        {
            hr = CreateOptimalVideoType(pMixerType, &pOptimalType);
        }

        // Step 4. Check if the mixer will accept this media type.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, MFT_SET_TYPE_TEST_ONLY);
        }

        // Step 5. Try to set the media type on ourselves.
        if (SUCCEEDED(hr))
        {
            hr = SetMediaType(pOptimalType);
        }

        // Step 6. Set output media type on mixer.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, 0);

            assert(SUCCEEDED(hr)); // This should succeed unless the MFT lied in the previous call.

            // If something went wrong, clear the media type.
            if (FAILED(hr))
            {
                SetMediaType(NULL);
            }
        }

        if (SUCCEEDED(hr))
        {
            bFoundMediaType = TRUE;
        }
    }

    SafeRelease(&pMixerType);
    SafeRelease(&pOptimalType);
    SafeRelease(&pVideoType);

    return hr;
}

ビデオ メディアの種類の詳細については、「ビデオ メディアの種類」を参照してください。

Direct3D デバイスの管理

プレゼンターは Direct3D デバイスを作成し、ストリーミング中のデバイス損失を処理します。 プレゼンターは Direct3D デバイス マネージャーもホストします。このマネージャーは、他のコンポーネントが同じデバイスを使用する方法を提供します。 たとえば、ミキサーは Direct3D デバイスを使用して、サブストリームのミックス、インターレース解除、色の調整の実行を行います。 デコーダーは、ビデオ アクセラレータ デコードに Direct3D デバイスを使用できます。 (ビデオ アクセラレーションの詳細については、「DirectX ビデオ アクセラレータ 2.0」を参照してください。)

Direct3D デバイスを設定するには、次の手順に従います。

  1. Direct3DCreate9 または Direct3DCreate9Ex を呼び出して Direct3D オブジェクトを作成します。
  2. IDirect3D9::CreateDevice または IDirect3D9Ex::CreateDevice を呼び出してデバイスを作成します。
  3. DXVA2CreateDirect3DDeviceManager9 を呼び出 して、デバイス マネージャーを作成します。
  4. IDirect3DDeviceManager9::ResetDevice を呼び出して、デバイス マネージャーにデバイスを設定します。

別のパイプライン コンポーネントでデバイス マネージャーが必要な場合は、サービス GUID に MR_VIDEO_ACCELERATION_SERVICE を指定して、EVR で IMFGetService::GetService を呼び出します。 EVR は、プレゼンターに要求を渡します。 オブジェクトは、IDirect3DDeviceManager9 ポインターを取得した後、IDirect3DDeviceManager9::OpenDeviceHandle を呼び出すことで、デバイスへのハンドルを取得できます。 オブジェクトは、デバイスを使用する必要がある場合、IDirect3DDeviceManager9::LockDevice メソッドにデバイス ハンドルを渡し、このメソッドが IDirect3DDevice9 ポインターを返します。

デバイスが作成された後、プレゼンターがデバイスを破棄して新しいデバイスを作成した場合、プレゼンターは ResetDevice をもう一度呼び出す必要があります。 ResetDevice メソッドは、既存のデバイス ハンドルをすべて無効にします。この結果、LockDeviceDXVA2_E_NEW_VIDEO_DEVICE を返します。 このエラー コードは、デバイスを使用している他のオブジェクトに、新しいデバイス ハンドルを開く必要があることを通知します。 デバイス マネージャーの使用方法の詳細については、「Direct3D デバイス マネージャー」を参照してください。

プレゼンターは、ウィンドウ モードまたは全画面表示排他モードでデバイスを作成できます。 ウィンドウ モードの場合は、アプリケーションでビデオ ウィンドウを指定する方法を指定する必要があります。 標準プレゼンターは、この目的のために IMFVideoDisplayControl::SetVideoWindow インターフェイスを実装します。 プレゼンターが最初に作成されるときに、デバイスを作成する必要があります。 通常、現時点では、ウィンドウやバック バッファー形式など、すべてのデバイス パラメータを把握しているわけではありません。 一時デバイスを作成し、後で置き換えることができます。デバイス マネージャーで ResetDevice を呼び出すことを覚えておいてください。

新しいデバイスを作成する場合、または既存のデバイスで IDirect3DDevice9::Reset または IDirect3DDevice9Ex::ResetEx を呼び出す場合は、EC_DISPLAY_CHANGED イベントを EVR に送信します。 このイベントは、メディアの種類を再ネゴシエートするように EVR に通知します。 EVR は、このイベントのイベント パラメータを無視します。

Direct3D サーフェスの割り当て

プレゼンターは、メディアの種類を設定した後、ミキサーがビデオ フレームの書き込みに使用する Direct3D サーフェスを割り当てることができます。 サーフェスは、プレゼンターのメディアの種類と一致する必要があります。

  • サーフェス形式はメディア サブタイプと一致する必要があります。 たとえば、サブタイプが MFVideoFormat_RGB24 である場合、サーフェス形式は D3DFMT_X8R8G8B8 である必要があります。 サブタイプと Direct3D 形式の詳細については、「ビデオ サブタイプの GUID」を参照してください。
  • サーフェスの幅と高さは、メディアの種類の MF_MT_FRAME_SIZE 属性で指定された寸法と一致する必要があります。

サーフェスを割り当てる方法として推奨される方法は、プレゼンターがウィンドウ表示または全画面表示のどちらを実行するかによって異なります。

Direct3D デバイスがウィンドウ表示されている場合、それぞれ 1 つのバック バッファーを持つ複数のスワップ チェーンを作成できます。 このアプローチを使用する場合、1 つのスワップ チェーンを提示しても他のスワップ チェーンに干渉しないため、各サーフェスを個別に提示できます。 ミキサーは、別のサーフェスがプレゼンテーション用にスケジュールされている間に、サーフェスにデータを書き込むことができます。

まず、作成するスワップ チェーンの数を決定します。 少なくとも 3 つを作成することをお勧めします。 スワップ チェーンごとに、次の操作を行います。

  1. IDirect3DDevice9::CreateAdditionalSwapChain を呼び出してスワップ チェーンを作成します。
  2. IDirect3DSwapChain9::GetBackBuffer を呼び出して、スワップ チェーンのバック バッファー サーフェイスへのポインターを取得します。
  3. MFCreateVideoSampleFromSurface を呼び出し、サーフェスへのポインターを渡します。 この関数は、ビデオ サンプル オブジェクトへのポインターを返します。 ビデオ サンプル オブジェクトは IMFSample インターフェイスを実装し、プレゼンターは、プレゼンターがミキサーの IMFTransform::ProcessOutput メソッドを呼び出すときに、このインターフェイスを使用してサーフェスをミキサーに配信します。 ビデオ サンプル オブジェクトの詳細については、「ビデオ サンプル」を参照してください。
  4. IMFSample ポインターをキューに格納します。 プレゼンターは、「出力の処理」の説明に従って、処理中にこのキューからサンプルをプルします。
  5. スワップ チェーンが解放されないように、IDirect3DSwapChain9 ポインターへの参照を保持します。

全画面表示排他モードでは、デバイスは複数のスワップ チェーンを持つことはできません。 このスワップ チェーンは、全画面表示デバイスを作成するときに暗黙的に作成されます。 スワップ チェーンは、複数のバック バッファーを持つことができます。 ただし、残念ながら、同じスワップ チェーン内の別のバック バッファーに書き込むときに 1 つのバック バッファーを提示する場合、2 つの操作を調整する簡単な方法はありません。 これは、Direct3D がサーフェスの反転を実装する方法が原因です。 Present を呼び出すと、グラフィックス ドライバーはグラフィックス メモリ内のサーフェス ポインターを更新します。 Present を呼び出すときに IDirect3DSurface9 ポインターを保持している場合、これらのポインターは、Present 呼び出しが返された後に異なるバッファーを指します。

最も簡単なオプションは、スワップ チェーン用に 1 つのビデオ サンプルを作成することです。 このオプションを選択する場合は、ウィンドウ モードと同じ手順に従います。 唯一の違いは、サンプル キューに 1 つのビデオ サンプルが含まれていることです。 もう 1 つのオプションは、オフスクリーン サーフェスを作成し、バック バッファーに blit することです。 作成するサーフェスは、ミキサーが出力フレームの合成に使用する IDirectXVideoProcessor::VideoProcessBlt メソッドをサポートする必要があります。

サンプルの追跡

プレゼンターは、最初にビデオ サンプルを割り当てるときに、それらをキューに配置します。 プレゼンターは、ミキサーから新しいフレームを取得する必要があるときは常に、このキューから描画します。 ミキサーがフレームを出力した後、プレゼンターはサンプルを 2 番目のキューに移動します。 2 番目のキューは、スケジュールされたプレゼンテーション時間を待機しているサンプル用です。

各サンプルの状態を追跡しやすくするために、ビデオ サンプル オブジェクトは IMFTrackedSample インターフェイスを実装します。 このインターフェイスは次のように使用できます。

  1. プレゼンターに IMFAsyncCallback インターフェイスを実装します。

  2. スケジュールされたキューにサンプルを配置する前に、IMFTrackedSample インターフェイスのビデオ サンプル オブジェクトに対してクエリを実行します。

  3. コールバック インターフェイスへのポインターを使用して IMFTrackedSample::SetAllocator を呼び出します。

  4. サンプルのプレゼンテーションの準備ができたら、スケジュールされたキューからサンプルを削除し、それを提示して、サンプルへのすべての参照を解放します。

  5. このサンプルでは、コールバックを呼び出します。 (この場合、コールバックが呼び出されるまでサンプル オブジェクト自体に参照カウントが保持されるため、サンプル オブジェクトは削除されません)。

  6. コールバック内で、使用可能なキューにサンプルを返します。

プレゼンターは、サンプルを追跡するために IMFTrackedSample を使用する必要はありません。デザインに最適な手法を実装できます。 IMFTrackedSample の利点の 1 つは、プレゼンターのスケジューリング関数とレンダリング関数をヘルパー オブジェクトに移動できることです。これらのオブジェクトには、ビデオ サンプルをリリースするときにプレゼンターにコールバックするための特別なメカニズムは必要ありません。なぜなら、サンプル オブジェクトにそのメカニズムが用意されているからです。

次のコードは、コールバックの設定方法を示しています。

HRESULT EVRCustomPresenter::TrackSample(IMFSample *pSample)
{
    IMFTrackedSample *pTracked = NULL;

    HRESULT hr = pSample->QueryInterface(IID_PPV_ARGS(&pTracked));

    if (SUCCEEDED(hr))
    {
        hr = pTracked->SetAllocator(&m_SampleFreeCB, NULL);
    }

    SafeRelease(&pTracked);
    return hr;
}

コールバックで、非同期結果オブジェクトで IMFAsyncResult::GetObject を呼び出して、サンプルへのポインターを取得します。

HRESULT EVRCustomPresenter::OnSampleFree(IMFAsyncResult *pResult)
{
    IUnknown *pObject = NULL;
    IMFSample *pSample = NULL;
    IUnknown *pUnk = NULL;

    // Get the sample from the async result object.
    HRESULT hr = pResult->GetObject(&pObject);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pObject->QueryInterface(IID_PPV_ARGS(&pSample));
    if (FAILED(hr))
    {
        goto done;
    }

    // If this sample was submitted for a frame-step, the frame step operation
    // is complete.

    if (m_FrameStep.state == FRAMESTEP_SCHEDULED)
    {
        // Query the sample for IUnknown and compare it to our cached value.
        hr = pSample->QueryInterface(IID_PPV_ARGS(&pUnk));
        if (FAILED(hr))
        {
            goto done;
        }

        if (m_FrameStep.pSampleNoRef == (DWORD_PTR)pUnk)
        {
            // Notify the EVR.
            hr = CompleteFrameStep(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Note: Although pObject is also an IUnknown pointer, it is not
        // guaranteed to be the exact pointer value returned through
        // QueryInterface. Therefore, the second QueryInterface call is
        // required.
    }

    /*** Begin lock ***/

    EnterCriticalSection(&m_ObjectLock);

    UINT32 token = MFGetAttributeUINT32(
        pSample, MFSamplePresenter_SampleCounter, (UINT32)-1);

    if (token == m_TokenCounter)
    {
        // Return the sample to the sample pool.
        hr = m_SamplePool.ReturnSample(pSample);
        if (SUCCEEDED(hr))
        {
            // A free sample is available. Process more data if possible.
            (void)ProcessOutputLoop();
        }
    }

    LeaveCriticalSection(&m_ObjectLock);

    /*** End lock ***/

done:
    if (FAILED(hr))
    {
        NotifyEvent(EC_ERRORABORT, hr, 0);
    }
    SafeRelease(&pObject);
    SafeRelease(&pSample);
    SafeRelease(&pUnk);
    return hr;
}

出力の処理

ミキサーが新しい入力サンプルを受信するたびに、EVR はプレゼンターに MFVP_MESSAGE_PROCESSINPUTNOTIFY メッセージを送信します。 このメッセージは、配信対象の新しいビデオ フレームがミキサーにある可能性があることを示します。 これに対して、プレゼンターはミキサーで IMFTransform::ProcessOutput を呼び出します。 メソッドが成功した場合、プレゼンターはプレゼンテーションのサンプルをスケジュールします。

ミキサーから出力を取得するには、次の手順を実行します。

  1. クロックの状態を確認します。 クロックが一時停止している場合は、これが最初のビデオ フレームでない限り、MFVP_MESSAGE_PROCESSINPUTNOTIFY メッセージを無視します。 クロックが実行されている場合、またはこれが最初のビデオ フレームの場合は、続行します。

  2. 使用可能なサンプルのキューからサンプルを取得します。 キューが空である場合、割り当てられているすべてのサンプルが現在、プレゼンテーション用にスケジュールされていることを意味します。 その場合は、現時点で MFVP_MESSAGE_PROCESSINPUTNOTIFY メッセージを無視します。 次のサンプルが使用可能になったら、ここに記載されている手順を繰り返します。

  3. (省略可能)クロックが使用可能な場合は、IMFClock::GetCorrelatedTime を呼び出して現在のクロック時間 (T1) を取得します。

  4. ミキサーで IMFTransform::ProcessOutput を呼び出します。 ProcessOutput が成功した場合、サンプルにはビデオ フレームが含まれます。 メソッドが失敗した場合は、リターン コードをチェックします。 ProcessOutput の次のエラー コードは、重大なエラーではありません。

    エラー コード 説明
    MF_E_TRANSFORM_NEED_MORE_INPUT ミキサーは、新しい出力フレームを生成する前に、より多くの入力を必要とします。
    このエラー コードが表示された場合は、「ストリームの終了」で説明されているように、EVR がストリームの末尾に達したかどうかをチェックし、それに応じて応答します。 それ以外の場合は、この MF_E_TRANSFORM_NEED_MORE_INPUT メッセージを無視します。 ミキサーがより多くの入力を取得すると、EVR は別のメッセージを送信します。
    MF_E_TRANSFORM_STREAM_CHANGE おそらくはアップストリームの形式変更が原因で、ミキサーの出力の種類が無効になりました。
    このエラー コードが表示される場合は、プレゼンターのメディアの種類を NULL に設定します。 EVR は新しい形式を要求します。
    MF_E_TRANSFORM_TYPE_NOT_SET ミキサーには新しいメディアの種類が必要です。
    このエラー コードが表示される場合は、「ネゴシエート形式」の説明に従って、ミキサーの出力の種類を再ネゴシエートします。

     

    ProcessOutput が成功した場合は、続行します。

  5. (省略可能)クロックが使用可能な場合は、現在のクロック時間 (T2) を取得します。 ミキサーによって発生する待機時間の長さは (T2 - T1) です。 この値を持つ EC_PROCESSING_LATENCY イベントを EVR に送信します。 EVR はこの値を品質管理に使用します。 使用可能なクロックがない場合、EC_PROCESSING_LATENCY イベントを送信する理由はありません。

  6. (省略可能)IMFTrackedSample のサンプルのクエリを実行し、「サンプルの追跡」の説明に従って IMFTrackedSample::SetAllocator を呼び出します。

  7. プレゼンテーションのサンプルをスケジュールします。

この一連の手順は、プレゼンターがミキサーから出力を取得する前に終了できます。 要求が削除されないようにするには、次の場合に同じ手順を繰り返す必要があります。

  • プレゼンターの IMFClockStateSink::OnClockStart メソッドまたは IMFClockStateSink::OnClockStart メソッドが呼び出されます。 これにより、クロックが一時停止しているためにミキサーが入力を無視するケースが処理されます (手順 1)。
  • IMFTrackedSample コールバックが呼び出されます。 これにより、ミキサーが入力を受け取るが、プレゼンターのすべてのビデオ サンプルが使用されているケースが処理されます (手順 2)。

次のいくつかのコード例では、これらの手順について詳しく説明します。 プレゼンターは、MFVP_MESSAGE_PROCESSINPUTNOTIFY メッセージを取得するときに ProcessInputNotify メソッド (次の例を参照) を呼び出します。

//-----------------------------------------------------------------------------
// ProcessInputNotify
//
// Attempts to get a new output sample from the mixer.
//
// This method is called when the EVR sends an MFVP_MESSAGE_PROCESSINPUTNOTIFY
// message, which indicates that the mixer has a new input sample.
//
// Note: If there are multiple input streams, the mixer might not deliver an
// output sample for every input sample.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessInputNotify()
{
    HRESULT hr = S_OK;

    // Set the flag that says the mixer has a new sample.
    m_bSampleNotify = TRUE;

    if (m_pMediaType == NULL)
    {
        // We don't have a valid media type yet.
        hr = MF_E_TRANSFORM_TYPE_NOT_SET;
    }
    else
    {
        // Try to process an output sample.
        ProcessOutputLoop();
    }
    return hr;
}

このProcessInputNotify メソッドは、ミキサーに新しい入力があるという事実を記録するブール型のフラグを設定します。 次に、次の例に示すように、ProcessOutputLoop メソッドを呼び出します。 このメソッドは、ミキサーからできるだけ多くのサンプルをプルしようとします。

void EVRCustomPresenter::ProcessOutputLoop()
{
    HRESULT hr = S_OK;

    // Process as many samples as possible.
    while (hr == S_OK)
    {
        // If the mixer doesn't have a new input sample, break from the loop.
        if (!m_bSampleNotify)
        {
            hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
            break;
        }

        // Try to process a sample.
        hr = ProcessOutput();

        // NOTE: ProcessOutput can return S_FALSE to indicate it did not
        // process a sample. If so, break out of the loop.
    }

    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
    {
        // The mixer has run out of input data. Check for end-of-stream.
        CheckEndOfStream();
    }
}

次の例に示す ProcessOutput メソッドは、ミキサーから 1 つのビデオ フレームを取得しようとします。 使用可能なビデオ フレームがない場合は、ProcessSample は S_FALSE またはエラー コードを返します。これらのどちらかが ProcessOutputLoop 内のループを中断します。 ほとんどの処理は ProcessOutput メソッド内で行われます。

//-----------------------------------------------------------------------------
// ProcessOutput
//
// Attempts to get a new output sample from the mixer.
//
// Called in two situations:
// (1) ProcessOutputLoop, if the mixer has a new input sample.
// (2) Repainting the last frame.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessOutput()
{
    assert(m_bSampleNotify || m_bRepaint);  // See note above.

    HRESULT     hr = S_OK;
    DWORD       dwStatus = 0;
    LONGLONG    mixerStartTime = 0, mixerEndTime = 0;
    MFTIME      systemTime = 0;
    BOOL        bRepaint = m_bRepaint; // Temporarily store this state flag.

    MFT_OUTPUT_DATA_BUFFER dataBuffer;
    ZeroMemory(&dataBuffer, sizeof(dataBuffer));

    IMFSample *pSample = NULL;

    // If the clock is not running, we present the first sample,
    // and then don't present any more until the clock starts.

    if ((m_RenderState != RENDER_STATE_STARTED) &&  // Not running.
         !m_bRepaint &&             // Not a repaint request.
         m_bPrerolled               // At least one sample has been presented.
         )
    {
        return S_FALSE;
    }

    // Make sure we have a pointer to the mixer.
    if (m_pMixer == NULL)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Try to get a free sample from the video sample pool.
    hr = m_SamplePool.GetSample(&pSample);
    if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
    {
        // No free samples. Try again when a sample is released.
        return S_FALSE;
    }
    else if (FAILED(hr))
    {
        return hr;
    }

    // From now on, we have a valid video sample pointer, where the mixer will
    // write the video data.
    assert(pSample != NULL);

    // (If the following assertion fires, it means we are not managing the sample pool correctly.)
    assert(MFGetAttributeUINT32(pSample, MFSamplePresenter_SampleCounter, (UINT32)-1) == m_TokenCounter);

    if (m_bRepaint)
    {
        // Repaint request. Ask the mixer for the most recent sample.
        SetDesiredSampleTime(
            pSample,
            m_scheduler.LastSampleTime(),
            m_scheduler.FrameDuration()
            );

        m_bRepaint = FALSE; // OK to clear this flag now.
    }
    else
    {
        // Not a repaint request. Clear the desired sample time; the mixer will
        // give us the next frame in the stream.
        ClearDesiredSampleTime(pSample);

        if (m_pClock)
        {
            // Latency: Record the starting time for ProcessOutput.
            (void)m_pClock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
        }
    }

    // Now we are ready to get an output sample from the mixer.
    dataBuffer.dwStreamID = 0;
    dataBuffer.pSample = pSample;
    dataBuffer.dwStatus = 0;

    hr = m_pMixer->ProcessOutput(0, 1, &dataBuffer, &dwStatus);

    if (FAILED(hr))
    {
        // Return the sample to the pool.
        HRESULT hr2 = m_SamplePool.ReturnSample(pSample);
        if (FAILED(hr2))
        {
            hr = hr2;
            goto done;
        }
        // Handle some known error codes from ProcessOutput.
        if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
        {
            // The mixer's format is not set. Negotiate a new format.
            hr = RenegotiateMediaType();
        }
        else if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
        {
            // There was a dynamic media type change. Clear our media type.
            SetMediaType(NULL);
        }
        else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
            // The mixer needs more input.
            // We have to wait for the mixer to get more input.
            m_bSampleNotify = FALSE;
        }
    }
    else
    {
        // We got an output sample from the mixer.

        if (m_pClock && !bRepaint)
        {
            // Latency: Record the ending time for the ProcessOutput operation,
            // and notify the EVR of the latency.

            (void)m_pClock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);

            LONGLONG latencyTime = mixerEndTime - mixerStartTime;
            NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
        }

        // Set up notification for when the sample is released.
        hr = TrackSample(pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Schedule the sample.
        if ((m_FrameStep.state == FRAMESTEP_NONE) || bRepaint)
        {
            hr = DeliverSample(pSample, bRepaint);
            if (FAILED(hr))
            {
                goto done;
            }
        }
        else
        {
            // We are frame-stepping (and this is not a repaint request).
            hr = DeliverFrameStepSample(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        m_bPrerolled = TRUE; // We have presented at least one sample now.
    }

done:
    SafeRelease(&pSample);

    // Important: Release any events returned from the ProcessOutput method.
    SafeRelease(&dataBuffer.pEvents);
    return hr;
}

この例に関するいくつかの解説:

  • m_SamplePool 変数は、使用可能なビデオ サンプルのキューを保持するコレクション オブジェクトであると見なされます。 キューが空である場合、オブジェクトの GetSample メソッドは MF_E_SAMPLEALLOCATOR_EMPTY を返します。
  • ミキサーの ProcessOutput メソッドがMF_E_TRANSFORM_NEED_MORE_INPUT を返す場合、ミキサーがそれ以上出力を生成できないことを意味するため、プレゼンターは m_fSampleNotify フラグをクリアします。
  • IMFTrackedSample コールバックを設定する TrackSample メソッドは、「サンプルの追跡」セクションに示されています。

フレームの再描画

プレゼンターが最新のビデオ フレームを再描画する必要がある場合があります。 たとえば、標準プレゼンターは、次の状況でフレームを再描画します。

ミキサーに最新のフレームを再作成するように要求するには、次の手順に従います。

  1. キューからビデオ サンプルを取得します。
  2. IMFDesiredSample インターフェイスのサンプルのクエリを実行します。
  3. IMFDesiredSample::SetDesiredSampleTimeAndDuration を呼び出します。 最新のビデオ フレームのタイムスタンプを指定します。 (この値をキャッシュし、フレームごとに更新する必要があります)。
  4. ミキサーで ProcessOutput を呼び出します。

フレームを再描画する場合は、プレゼンテーション クロックを無視して、フレームをすぐに表示できます。

サンプルのスケジュール

ビデオ フレームはいつでも EVR に到達できます。 プレゼンターは、フレームのタイムスタンプに基づいて、各フレームを正しい時間で提示する役割を担います。 プレゼンターは、ミキサーから新しいサンプルを取得すると、スケジュールされたキューにサンプルを配置します。 別のスレッドで、プレゼンターはキューの先頭から最初のサンプルを継続的に取得し、次の処理を行うかどうかを判断します。

  • サンプルを提示します。
  • サンプルは早いため、キューに保持します。
  • サンプルは遅いため、破棄します。 可能であればフレームの破棄は避ける必要がありますが、プレゼンターが継続的に遅れているときに必要になる場合があります。

ビデオ フレームのタイム スタンプを取得するには、ビデオ サンプルで IMFSample::GetSampleTime を呼び出します。 タイムスタンプは、EVR のプレゼンテーション クロックを基準にしています。 現在のクロック時間を取得するには、IMFClock::GetCorrelatedTime を呼び出します。 EVR にプレゼンテーション クロックがない場合、またはサンプルにタイム スタンプがない場合、サンプルを取得した直後に提示できます。

各サンプルの継続時間を取得するには、IMFSample::GetSampleDuration を呼び出します。 サンプルに期間がない場合は、MFFrameRateToAverageTimePerFrame 関数を使用してフレーム レートから継続時間を計算できます。

サンプルをスケジュールする場合、次の点に注意してください。

  • 再生レートが通常の速度よりも速いか遅い場合、クロックはより高速または低速で実行されます。 つまり、サンプルのタイムスタンプは、プレゼンテーション クロックを基準にして常に正しいターゲット時間を示します。 ただし、プレゼンテーション時間を他のクロック時間 (高解像度パフォーマンス カウンターなど) に変換する場合、クロック速度に基づいて時間をスケーリングする必要があります。 クロック速度が変化すると、EVR はプレゼンターの IMFClockStateSink::OnClockSetRate メソッドを呼び出します。
  • 逆再生の場合、再生レートが負になる場合があります。 再生レートが負である場合、プレゼンテーション クロックは逆進します。 つまり、時間 N + 1 が時間 N の前に発生します。

次の例では、プレゼンテーション クロックを基準にして、サンプルがどれほど早いか遅いかを計算します。

    LONGLONG hnsPresentationTime = 0;
    LONGLONG hnsTimeNow = 0;
    MFTIME   hnsSystemTime = 0;

    BOOL bPresentNow = TRUE;
    LONG lNextSleep = 0;

    if (m_pClock)
    {
        // Get the sample's time stamp. It is valid for a sample to
        // have no time stamp.
        hr = pSample->GetSampleTime(&hnsPresentationTime);

        // Get the clock time. (But if the sample does not have a time stamp,
        // we don't need the clock time.)
        if (SUCCEEDED(hr))
        {
            hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
        }

        // Calculate the time until the sample's presentation time.
        // A negative value means the sample is late.
        LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
        if (m_fRate < 0)
        {
            // For reverse playback, the clock runs backward. Therefore, the
            // delta is reversed.
            hnsDelta = - hnsDelta;
        }

プレゼンテーション クロックは通常、システム クロックまたはオーディオ レンダラーによって駆動されます。 (オーディオ レンダラーは、サウンド カードがオーディオを消費するレートから時間を導き出します)。一般に、プレゼンテーション クロックはモニターのリフレッシュ レートと同期されません。

Direct3D プレゼンテーション パラメーターでプレゼンテーション間隔に D3DPRESENT_INTERVAL_DEFAULT または D3DPRESENT_INTERVAL_ONE を指定した場合、Present 操作はモニターの垂直帰線を待機します。 これは、テアリングを防ぐ簡単な方法ですが、スケジュール アルゴリズムの精度が低下します。 逆に、プレゼンテーション間隔が D3DPRESENT_INTERVAL_IMMEDIATE である場合、Present メソッドは直ちに実行されます。これにより、スケジュール アルゴリズムが正確であるために垂直帰線期間中にのみ Present を呼び出す場合を除き、テアリングが発生します。

次の関数は、正確なタイミング情報を取得するのに役立ちます。

  • IDirect3DDevice9::GetRasterStatus は、現在のスキャン ラインや、ラスターが垂直空白期間にあるかどうかなど、ラスターに関する情報を返します。
  • DwmGetCompositionTimingInfo は、デスクトップ ウィンドウ マネージャーのタイミング情報を返します。 この情報は、デスクトップ コンポジションが有効になっている場合に役立ちます。

サンプルのプレゼンテーション

このセクションでは、「Direct3D サーフェスの割り当て」の説明に従って、サーフェスごとに個別のスワップ チェーンを作成したことを前提としています。 サンプルを提示するには、次のようにビデオ サンプルからスワップ チェーンを取得します。

  1. ビデオ サンプルで IMFSample::GetBufferByIndex を呼び出してバッファーを取得します。
  2. IMFGetService インターフェイスのバッファーのクエリを実行します。
  3. IMFGetService::GetService を呼び出して、Direct3D サーフェスの IDirect3DSurface9 インターフェイスを取得します。 (MFGetService を呼び出すことで、この手順と前の手順を 1 つに 組み合わせることができます。)
  4. サーフェスで IDirect3DSurface9::GetContainer を呼び出してスワップ チェーンへのポインターを取得します。
  5. スワップ チェーンで IDirect3DSwapChain9::Present を呼び出します。

これらの手順を示すコードは次のようになります。

HRESULT D3DPresentEngine::PresentSample(IMFSample* pSample, LONGLONG llTarget)
{
    HRESULT hr = S_OK;

    IMFMediaBuffer* pBuffer = NULL;
    IDirect3DSurface9* pSurface = NULL;
    IDirect3DSwapChain9* pSwapChain = NULL;

    if (pSample)
    {
        // Get the buffer from the sample.
        hr = pSample->GetBufferByIndex(0, &pBuffer);
        if (FAILED(hr))
        {
            goto done;
        }

        // Get the surface from the buffer.
        hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pSurface));
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (m_pSurfaceRepaint)
    {
        // Redraw from the last surface.
        pSurface = m_pSurfaceRepaint;
        pSurface->AddRef();
    }

    if (pSurface)
    {
        // Get the swap chain from the surface.
        hr = pSurface->GetContainer(IID_PPV_ARGS(&pSwapChain));
        if (FAILED(hr))
        {
            goto done;
        }

        // Present the swap chain.
        hr = PresentSwapChain(pSwapChain, pSurface);
        if (FAILED(hr))
        {
            goto done;
        }

        // Store this pointer in case we need to repaint the surface.
        CopyComPointer(m_pSurfaceRepaint, pSurface);
    }
    else
    {
        // No surface. All we can do is paint a black rectangle.
        PaintFrameWithGDI();
    }

done:
    SafeRelease(&pSwapChain);
    SafeRelease(&pSurface);
    SafeRelease(&pBuffer);

    if (FAILED(hr))
    {
        if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG)
        {
            // We failed because the device was lost. Fill the destination rectangle.
            PaintFrameWithGDI();

            // Ignore. We need to reset or re-create the device, but this method
            // is probably being called from the scheduler thread, which is not the
            // same thread that created the device. The Reset(Ex) method must be
            // called from the thread that created the device.

            // The presenter will detect the state when it calls CheckDeviceState()
            // on the next sample.
            hr = S_OK;
        }
    }
    return hr;
}

ソースとコピー先の四角形

ソースの四角形は、表示するビデオ フレームの部分です。 これは正規化された座標系を基準にして定義されます。この座標系では、ビデオ フレーム全体が座標 {0, 0, 1, 1} の四角形を占有します。 コピー先の四角形は、ビデオ フレームが描画されるコピー先のサーフェス内の領域です。 標準プレゼンターは、アプリケーションで IMFVideoDisplayControl::SetVideoPosition を呼び出すことでこれらの四角形を設定できるようにします。

ソース四角形とコピー先四角形を適用するには、いくつかのオプションがあります。 最初のオプションは、ミキサーがそれらを適用できるようにすることです。

  • VIDEO_ZOOM_RECT 属性を使用してソース四角形を設定します。 ミキサーは、ビデオをターゲット サーフェスに blit するときにソースの四角形を適用します。 ミキサーの既定のソース四角形はフレーム全体です。
  • ミキサーの出力の種類で、コピー先四角形を幾何学的な開口部として設定します。 詳細については、「形式のネゴシエート」を参照してください。

2 番目のオプションは、Present メソッドで pSourceRect パラメーターと pDestRect パラメーターを指定することで IDirect3DSwapChain9::Present するときに四角形を適用することです。 これらのオプションを組み合わせることができます。 たとえば、ミキサーにソース四角形を設定しますが、Present メソッドでコピー先四角形を適用できます。

アプリケーションでコピー先四角形を変更したり、ウィンドウのサイズを変更したりする場合は、新しいサーフェスを割り当てる必要がある場合があります。 その場合は、この操作をスケジュール スレッドと同期するように注意する必要があります。 新しいサーフェスを割り当てる前に、スケジュール キューをフラッシュし、古いサンプルを破棄します。

ストリームの終了

EVR のすべての入力ストリームが終了すると、EVR はプレゼンターに MFVP_MESSAGE_ENDOFSTREAM メッセージを送信します。 ただし、メッセージを受信した時点で、いくつかのビデオ フレームが処理されていないまま残っている可能性があります。 ストリームの終了メッセージに応答する前に、ミキサーからすべての出力をドレインし、残りのフレームをすべて提示する必要があります。 最後のフレームが提示されたら、EC_COMPLETE イベントを EVR に送信します。

次の例は、さまざまな条件が満たされた場合に EC_COMPLETE イベントを送信するメソッドを示しています。 それ以外の場合は、イベントを送信せずに S_OK を返します。

HRESULT EVRCustomPresenter::CheckEndOfStream()
{
    if (!m_bEndStreaming)
    {
        // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
        return S_OK;
    }

    if (m_bSampleNotify)
    {
        // The mixer still has input.
        return S_OK;
    }

    if (m_SamplePool.AreSamplesPending())
    {
        // Samples are still scheduled for rendering.
        return S_OK;
    }

    // Everything is complete. Now we can tell the EVR that we are done.
    NotifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
    m_bEndStreaming = FALSE;
    return S_OK;
}

このメソッドは、次の状態をチェックします。

  • m_fSampleNotify 変数が TRUE である場合、まだ処理されていない 1 つ以上のフレームがミキサーにあることを意味します。 (詳細については、「出力の処理」を参照してください。)
  • m_fEndStreaming 変数は、初期値 FALSE を持つブール型のフラグです。 プレゼンターは、EVR が MFVP_MESSAGE_ENDOFSTREAM メッセージを送信するときに、フラグを TRUE に設定します。
  • AreSamplesPending メソッドは、スケジュールされたキューで 1 つ以上のフレームが待機している限り、TRUE を返すと見なされています。

IMFVideoPresenter::ProcessMessage メソッドで、m_fEndStreamingTRUE に設定し、EVR が MFVP_MESSAGE_ENDOFSTREAM メッセージを送信するときに CheckEndOfStream を呼び出します。

HRESULT EVRCustomPresenter::ProcessMessage(
    MFVP_MESSAGE_TYPE eMessage,
    ULONG_PTR ulParam
    )
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_ObjectLock);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    switch (eMessage)
    {
    // Flush all pending samples.
    case MFVP_MESSAGE_FLUSH:
        hr = Flush();
        break;

    // Renegotiate the media type with the mixer.
    case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
        hr = RenegotiateMediaType();
        break;

    // The mixer received a new input sample.
    case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
        hr = ProcessInputNotify();
        break;

    // Streaming is about to start.
    case MFVP_MESSAGE_BEGINSTREAMING:
        hr = BeginStreaming();
        break;

    // Streaming has ended. (The EVR has stopped.)
    case MFVP_MESSAGE_ENDSTREAMING:
        hr = EndStreaming();
        break;

    // All input streams have ended.
    case MFVP_MESSAGE_ENDOFSTREAM:
        // Set the EOS flag.
        m_bEndStreaming = TRUE;
        // Check if it's time to send the EC_COMPLETE event to the EVR.
        hr = CheckEndOfStream();
        break;

    // Frame-stepping is starting.
    case MFVP_MESSAGE_STEP:
        hr = PrepareFrameStep(LODWORD(ulParam));
        break;

    // Cancels frame-stepping.
    case MFVP_MESSAGE_CANCELSTEP:
        hr = CancelFrameStep();
        break;

    default:
        hr = E_INVALIDARG; // Unknown message. This case should never occur.
        break;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

さらに、ミキサーの IMFTransform::ProcessOutput メソッドが MF_E_TRANSFORM_NEED_MORE_INPUT を返す場合も CheckEndOfStream を呼び出します。 このエラー コードは、ミキサーにこれ以上入力サンプルがないことを示します (「出力の処理」を参照)。

フレームのステップ実行

EVR は、DirectShow でのフレームのステップ実行とメディア ファンデーションでのスクラブをサポートするように設計されています。 フレームのステップ実行とスクラブは概念的に似ています。 どちらの場合も、アプリケーションは一度に 1 つのビデオ フレームを要求します。 内部的には、プレゼンターは同じメカニズムを使用して両方の機能を実装します。

DirectShow でのフレームのステップ実行は次のように機能します。

  • アプリケーションは IVideoFrameStep::Step を呼び出します。 ステップの数は dwSteps パラメーターで指定します。 EVR は、MFVP_MESSAGE_STEP メッセージをプレゼンターに送信します。ここで、メッセージ パラメーター (ulParam) はステップ数です。
  • アプリケーションが IVideoFrameStep::CancelStep を呼び出すか、グラフの状態 (実行中、一時停止、または停止) を変更した場合、EVR は MFVP_MESSAGE_CANCELSTEP メッセージを送信します。

メディア ファンデーションでのスクラブは次のように機能します。

  • アプリケーションは、IMFRateControl::SetRate を呼び出して再生レートをゼロに設定します。
  • 新しいフレームをレンダリングするために、アプリケーションは IMFMediaSession::Start を目的の位置で呼び出します。 EVR は、ulParam を 1 として MFVP_MESSAGE_STEP メッセージを送信します。
  • スクラブを停止するために、アプリケーションは再生レートを 0 以外の値に設定します。 EVR は、MFVP_MESSAGE_CANCELSTEP メッセージを送信します。

MFVP_MESSAGE_STEP メッセージを受信した後、プレゼンターはターゲット フレームが到着するまで待機します。 ステップ数が N である場合、プレゼンターは次の (N - 1) サンプルを破棄し、N 番目のサンプルを提示します。 プレゼンターは、フレーム ステップを完了すると、lParam1FALSE に設定された EC_STEP_COMPLETE イベントを EVR に送信します。 さらに、再生レートがゼロである場合、プレゼンターは EC_SCRUB_TIME イベントを送信します。 フレームのステップ実行がまだ保留中の間に EVR がフレームのステップ実行をキャンセルした場合、プレゼンターは lParam1TRUE に設定された EC_STEP_COMPLETE イベントを送信します。

アプリケーションはフレームのステップ実行とスクラブを複数回行うことができるため、プレゼンターは MFVP_MESSAGE_CANCELSTEP メッセージを取得する前に複数の MFVP_MESSAGE_STEP メッセージを受信する可能性があります。 また、プレゼンターは、クロックの開始前またはクロックの実行中に、MFVP_MESSAGE_STEP メッセージを受信できます。

フレームのステップ実行の実装

このセクションでは、フレームのステップ実行を実装するアルゴリズムについて説明します。 フレームのステップ実行アルゴリズムでは、次の変数を使用します。

  • step_count。 現在のフレームのステップ実行操作のステップ数を指定する符号なし整数。

  • step_queueIMFSample ポインターのキュー。

  • step_state。 プレゼンターは、フレームのステップ実行に関して、いつでも次のいずれかの状態になることができます。

    State 説明
    NOT_STEPPING フレームのステップ実行を行っていません。
    WAITING プレゼンターは MFVP_MESSAGE_STEP メッセージを受信しましたが、クロックは開始されていません。
    PENDING プレゼンターは MFVP_MESSAGE_STEP メッセージを受信し、クロックが開始されましたが、プレゼンターはターゲット フレームの受信を待機しています。
    SCHEDULED プレゼンターはターゲット フレームを受け取り、プレゼンテーションのスケジュールを設定しましたが、フレームは提示されていません。
    完了 プレゼンターはターゲット フレームを提示し、EC_STEP_COMPLETE イベントを送信し、次の MFVP_MESSAGE_STEP または MFVP_MESSAGE_CANCELSTEP メッセージを待機しています。

     

    これらの状態は、「プレゼンターの状態」セクションに記載されているプレゼンターの状態とは無関係です。

フレームのステップ実行アルゴリズムには、次の手順が定義されています。

PrepareFrameStep 手順

  1. step_count を増分します。
  2. step_state を WAITING に設定します。
  3. クロックが実行されている場合、StartFrameStep を呼び出します。

StartFrameStep 手順

  1. step_state が WAITING と等しい場合、step_state を PENDING に設定します。 step_queue のサンプルごとに、DeliverFrameStepSample を呼び出します。
  2. step_state が NOT_STEPPINGと等しい場合、step_queue からサンプルを削除し、プレゼンテーションのスケジュールを設定します。

CompleteFrameStep 手順

  1. step_state を COMPLETE に設定します。
  2. lParam1 = FALSE として EC_STEP_COMPLETE イベントを送信します。
  3. クロック レートがゼロである場合、サンプル時間で EC_SCRUB_TIME イベントを送信します。

DeliverFrameStepSample 手順

  1. クロック レートがゼロでサンプル時間 + サンプル継続時間<クロック時間である場合、サンプルを破棄します。 終了します。
  2. step_state が SCHEDULED または COMPLETE と等しい場合、サンプルを step_queue に追加します。 終了します。
  3. step_count を減分します。
  4. step_count> 0 である場合、サンプルを破棄します。 終了します。
  5. step_state が WAITING と等しい場合、サンプルを step_queue に追加します。 終了します。
  6. プレゼンテーションのサンプルをスケジュールします。
  7. step_state を SCHEDULED に設定します。

CancelFrameStep 手順

  1. step_state を NOT_STEPPING に設定します。
  2. step_count をゼロにリセットします。
  3. step_state の前の値が WAITING、PENDING、または SCHEDULED であった場合、lParam1 = TRUE で EC_STEP_COMPLETE を送信します。

これらの手順を次のように呼び出します。

プレゼンターのメッセージまたはメソッド プロシージャ
MFVP_MESSAGE_STEP メッセージ PrepareFrameStep
MFVP_MESSAGE_STEP メッセージ CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
IMFTrackedSample コールバック CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

次のフロー チャートは、フレームのステップ実行の手順を示しています。

flow chart showing paths that start with mfvp-message-step and mfvp-message-processinputnotify and end at

EVR でのプレゼンターの設定

プレゼンターを実装した後、次の手順は、それを使用するように EVR を構成することです。

DirectShow でのプレゼンターの設定

DirectShow アプリケーションで、EVR のプレゼンターを次のように設定します。

  1. CoCreateInstance を呼び出して EVR フィルターを作成します。 CLSID は CLSID_EnhancedVideoRenderer です。
  2. フィルター グラフに EVR を追加します。
  3. プレゼンターのインスタンスを作成します。 プレゼンターは IClassFactory を使用した標準の COM オブジェクトの作成をサポートできますが、これは必須ではありません。
  4. IMFVideoRenderer インターフェイスの EVR フィルターのクエリを実行します。
  5. IMFVideoRenderer::InitializeRenderer を呼び出します。

メディア ファンデーションでのプレゼンターの設定

メディア ファンデーションでは、EVR メディア シンクと EVR ライセンス認証オブジェクトのどちらを作成するかに応じて、いくつかのオプションがあります。 ライセンス認証オブジェクトの詳細については、「ライセンス認証オブジェクト」を参照してください。

EVR メディア シンクの場合は、次の操作を行います。

  1. MFCreateVideoRenderer を呼び出して、メディア シンクを作成します。
  2. プレゼンターのインスタンスを作成します。
  3. IMFVideoRenderer インターフェイスの EVR メディア シンクのクエリを実行します。
  4. IMFVideoRenderer::InitializeRenderer を呼び出します。

EVR ライセンス認証オブジェクトの場合は、次の操作を行います。

  1. MFCreateVideoRendererActivate を呼び出して、ライセンス認証オブジェクトを作成します。

  2. ライセンス認証オブジェクトに次のいずれかの属性を設定します。

    属性 説明
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE プレゼンターのライセンス認証オブジェクトへのポインター。
    このフラグを使用する場合、プレゼンターにライセンス認証オブジェクトを提供する必要があります。 ライセンス認証オブジェクトは、IMFActivate インターフェイスを実装する必要があります。
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID プレゼンターの CLSID。
    このフラグを使用する場合、プレゼンターは IClassFactory を使用した標準の COM オブジェクトの作成をサポートする必要があります。

     

  3. 必要に応じて、ライセンス認証オブジェクトに MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS 属性を設定します。

強化されたビデオ レンダラー

EVRPresenter サンプル