ケース スタディ: MPEG-1 メディア ソース

Microsoft Media Foundation では、データ パイプラインにメディア データを導入するオブジェクトを メディア ソースと呼びます。 このトピックでは、 MPEG-1 Media Source SDK サンプルについて詳しく説明します。

前提条件

このトピックを読む前に、次の Media Foundation の概念を理解しておく必要があります。

また、Media Foundation アーキテクチャ、特にパイプラインにおけるメディア ソースの役割に関する基本的な理解も必要です。 (詳細については、「 メディア ソース」を参照してください)。

さらに、トピック「 カスタム メディア ソースの作成」を参照してください。このトピックでは、ここで説明する手順の一般的な概要を説明します。

このトピックでは、サンプルがかなり大きいため、SDK サンプルのすべてのコードを再現するわけではありません。

MPEG-1 ソースで使用される C++ クラス

サンプル MPEG-1 ソースは、次の C++ クラスで実装されています。

  • MPEG1ByteStreamHandler. メディア ソースのバイト ストリーム ハンドラーを実装します。 バイト ストリームを指定すると、バイト ストリーム ハンドラーはソースのインスタンスを作成します。
  • MPEG1Source. メディア ソースを実装します。
  • MPEG1Stream. メディア ストリーム オブジェクトを実装します。 メディア ソースは、MPEG-1 ビットストリーム内のすべてのオーディオまたはビデオ ストリームに対して 1 つの MPEG1Stream オブジェクトを作成します。
  • Parser. MPEG-1 ビットストリームを解析します。 ほとんどの場合、このクラスの詳細は Media Foundation API には関係ありません。
  • SourceOp、OpQueue: これら 2 つのクラスは、メディア ソースでの非同期操作を管理します。 ( 「非同期操作」を参照)。

その他のヘルパー クラスについては、このトピックで後述します。

Byte-Stream ハンドラー

バイト ストリーム ハンドラーは、メディア ソースを作成するオブジェクトです。 バイト ストリーム ハンドラーは、ソース リゾルバーによって作成されます。アプリケーションはバイト ストリーム ハンドラーと直接対話しません。 ソース リゾルバーは、レジストリを調べることでバイト ストリーム ハンドラーを検出します。 ハンドラーは、ファイル名拡張子または MIME の種類によって登録されます。 MPEG-1 ソースの場合、バイト ストリーム ハンドラーは ".mpg" ファイル名拡張子に登録されます。

注意

カスタム URL スキームをサポートする場合は、 スキーム ハンドラーを記述することもできます。 MPEG-1 ソースはローカル ファイル用に設計されており、Media Foundation には既に "file://" URL のスキーム ハンドラーが用意されています。

 

バイト ストリーム ハンドラーは、 IMFByteStreamHandler インターフェイスを 実装します。 このインターフェイスには、実装する必要がある 2 つの最も重要なメソッドがあります。

  • BeginCreateObject。 メディア ソースを作成するための非同期操作を開始します。
  • EndCreateObject。 非同期呼び出しを完了します。

他の 2 つのメソッドは省略可能であり、SDK サンプルでは実装されていません。

  • CancelObjectCreationBeginCreateObject メソッドを取り消します。 この方法は、起動時に待機時間が長くなる可能性があるネットワーク ソースに役立ちます。
  • GetMaxNumberOfBytesRequiredForResolution。 ハンドラーがソース ストリームから読み取る最大バイト数を取得します。 メディア ソースを作成する前にバイト ストリーム ハンドラーのデータ量がわかっている場合は、このメソッドを実装します。 それ以外の場合は、 単にE_NOTIMPLを返します。

BeginCreateObject メソッドの実装を次に示します。

HRESULT MPEG1ByteStreamHandler::BeginCreateObject(
        /* [in] */ IMFByteStream *pByteStream,
        /* [in] */ LPCWSTR pwszURL,
        /* [in] */ DWORD dwFlags,
        /* [in] */ IPropertyStore *pProps,
        /* [out] */ IUnknown **ppIUnknownCancelCookie,  // Can be NULL
        /* [in] */ IMFAsyncCallback *pCallback,
        /* [in] */ IUnknown *punkState                  // Can be NULL
        )
{
    if (pByteStream == NULL)
    {
        return E_POINTER;
    }

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

    if ((dwFlags & MF_RESOLUTION_MEDIASOURCE) == 0)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;

    IMFAsyncResult *pResult = NULL;
    MPEG1Source    *pSource = NULL;

    // Create an instance of the media source.
    hr = MPEG1Source::CreateInstance(&pSource);

    // Create a result object for the caller's async callback.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateAsyncResult(NULL, pCallback, punkState, &pResult);
    }

    // Start opening the source. This is an async operation.
    // When it completes, the source will invoke our callback
    // and then we will invoke the caller's callback.
    if (SUCCEEDED(hr))
    {
        hr = pSource->BeginOpen(pByteStream, this, NULL);
    }

    if (SUCCEEDED(hr))
    {
        if (ppIUnknownCancelCookie)
        {
            *ppIUnknownCancelCookie = NULL;
        }

        m_pSource = pSource;
        m_pSource->AddRef();

        m_pResult = pResult;
        m_pResult->AddRef();
    }

// cleanup
    SafeRelease(&pSource);
    SafeRelease(&pResult);
    return hr;
}

メソッドは、次の手順を実行します。

  1. MPEG1Source オブジェクトの新しいインスタンスを作成します。
  2. 非同期の結果オブジェクトを作成します。 このオブジェクトは、後でソース リゾルバーのコールバック メソッドを呼び出すために使用されます。
  3. クラスで定義されている非同期メソッド をMPEG1Source呼び出MPEG1Source::BeginOpenします。
  4. 呼び出し元に CancelObjectCreation がサポートされていないことを通知する ppIUnknownCancelCookieを NULL に設定します。

メソッドは MPEG1Source::BeginOpen 、バイト ストリームを読み取り、オブジェクトを初期化する実際の作業を行 MPEG1Source います。 このメソッドはパブリック API の一部ではありません。 ハンドラーとメディア ソースの間には、ニーズに合った任意のメカニズムを定義できます。 ほとんどのロジックをメディア ソースに配置すると、バイト ストリーム ハンドラーは比較的単純になります。

簡単に言うと、 BeginOpen 次の処理を行います。

  1. IMFByteStream::GetCapabilities を呼び出して、ソース バイト ストリームが読み取り可能でシーク可能であることを確認します。
  2. IMFByteStream::BeginRead を呼び出して、非同期 I/O 要求を開始します。

初期化の残りの部分は非同期的に行われます。 メディア ソースは、ストリームから MPEG-1 シーケンス ヘッダーを解析するのに十分なデータを読み取ります。 次に、ファイル内のオーディオストリームとビデオストリームを記述するために使用されるオブジェクトである プレゼンテーション記述子を作成します。 (詳細については、「 プレゼンテーション記述子」を参照してください)。操作が BeginOpen 完了すると、バイト ストリーム ハンドラーによってソース リゾルバーのコールバック メソッドが呼び出されます。 その時点で、ソース リゾルバーは IMFByteStreamHandler::EndCreateObject を呼び出します。 EndCreateObject メソッドは、操作の状態を返します。

HRESULT MPEG1ByteStreamHandler::EndCreateObject(
        /* [in] */ IMFAsyncResult *pResult,
        /* [out] */ MF_OBJECT_TYPE *pObjectType,
        /* [out] */ IUnknown **ppObject)
{
    if (pResult == NULL || pObjectType == NULL || ppObject == NULL)
    {
        return E_POINTER;
    }

    HRESULT hr = S_OK;

    *pObjectType = MF_OBJECT_INVALID;
    *ppObject = NULL;

    hr = pResult->GetStatus();

    if (SUCCEEDED(hr))
    {
        *pObjectType = MF_OBJECT_MEDIASOURCE;

        assert(m_pSource != NULL);

        hr = m_pSource->QueryInterface(IID_PPV_ARGS(ppObject));
    }

    SafeRelease(&m_pSource);
    SafeRelease(&m_pResult);
    return hr;
}

このプロセス中にいつでもエラーが発生した場合、コールバックはエラー状態コードで呼び出されます。

プレゼンテーション記述子

プレゼンテーション記述子は、次の情報を含む MPEG-1 ファイルの内容を記述します。

  • ストリームの数。
  • 各ストリームの形式。
  • ストリーム識別子。
  • 各ストリームの選択状態 (選択または選択解除)。

Media Foundation アーキテクチャの観点では、プレゼンテーション記述子には 1 つ以上の ストリーム記述子が含まれています。 各ストリーム記述子には 、ストリームのメディアの種類を取得または設定するために使用されるメディア型ハンドラーが含まれています。 Media Foundation は、プレゼンテーション記述子とストリーム記述子のストック実装を提供します。これらは、ほとんどのメディア ソースに適しています。

プレゼンテーション記述子を作成するには、次の手順を実行します。

  1. ストリームごとに次の手順を実行します。
    1. ストリーム ID と使用可能なメディアの種類の配列を指定します。 ストリームで複数のメディアの種類がサポートされている場合は、メディアの種類の一覧を優先順位で並べ替える必要があります (存在する場合)。 (最適な型を最初に、最も最適な型を最後に配置します)。
    2. MFCreateStreamDescriptor を呼び出して、ストリーム記述子を作成します。
    3. 新しく作成されたストリーム記述子で IMFStreamDescriptor::GetMediaTypeHandler を呼び出します。
    4. IMFMediaTypeHandler::SetCurrentMediaType を呼び出して、ストリームの既定の形式を設定します。 メディアの種類が複数ある場合は、通常、リストの最初の種類を設定する必要があります。
  2. MFCreatePresentationDescriptor を呼び出し、ストリーム記述子ポインターの配列を渡します。
  3. ストリームごとに IMFPresentationDescriptor::SelectStream または DeselectStream を呼び出して、既定の選択状態を設定します。 同じ種類 (オーディオまたはビデオ) のストリームが複数ある場合は、既定では 1 つだけ選択する必要があります。

オブジェクトは MPEG1Source 、そのメソッドでプレゼンテーション記述子を InitPresentationDescriptor 作成します。

HRESULT MPEG1Source::InitPresentationDescriptor()
{
    HRESULT hr = S_OK;
    DWORD cStreams = 0;

    assert(m_pPresentationDescriptor == NULL);
    assert(m_state == STATE_OPENING);

    if (m_pHeader == NULL)
    {
        return E_FAIL;
    }

    // Get the number of streams, as declared in the MPEG-1 header, skipping
    // any streams with an unsupported format.
    for (DWORD i = 0; i < m_pHeader->cStreams; i++)
    {
        if (IsStreamTypeSupported(m_pHeader->streams[i].type))
        {
            cStreams++;
        }
    }

    // How many streams do we actually have?
    if (cStreams > m_streams.GetCount())
    {
        // Keep reading data until we have seen a packet for each stream.
        return S_OK;
    }

    // We should never create a stream we don't support.
    assert(cStreams == m_streams.GetCount());

    // Ready to create the presentation descriptor.

    // Create an array of IMFStreamDescriptor pointers.
    IMFStreamDescriptor **ppSD =
            new (std::nothrow) IMFStreamDescriptor*[cStreams];

    if (ppSD == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    ZeroMemory(ppSD, cStreams * sizeof(IMFStreamDescriptor*));

    // Fill the array by getting the stream descriptors from the streams.
    for (DWORD i = 0; i < cStreams; i++)
    {
        hr = m_streams[i]->GetStreamDescriptor(&ppSD[i]);
        if (FAILED(hr))
        {
            goto done;
        }
    }

    // Create the presentation descriptor.
    hr = MFCreatePresentationDescriptor(cStreams, ppSD,
        &m_pPresentationDescriptor);

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

    // Select the first video stream (if any).
    for (DWORD i = 0; i < cStreams; i++)
    {
        GUID majorType = GUID_NULL;

        hr = GetStreamMajorType(ppSD[i], &majorType);
        if (FAILED(hr))
        {
            goto done;
        }

        if (majorType == MFMediaType_Video)
        {
            hr = m_pPresentationDescriptor->SelectStream(i);
            if (FAILED(hr))
            {
                goto done;
            }
            break;
        }
    }

    // Switch state from "opening" to stopped.
    m_state = STATE_STOPPED;

    // Invoke the async callback to complete the BeginOpen operation.
    hr = CompleteOpen(S_OK);

done:
    // clean up:
    if (ppSD)
    {
        for (DWORD i = 0; i < cStreams; i++)
        {
            SafeRelease(&ppSD[i]);
        }
        delete [] ppSD;
    }
    return hr;
}

アプリケーションは、 IMFMediaSource::CreatePresentationDescriptor を呼び出してプレゼンテーション記述子を取得します。 このメソッドは、 IMFPresentationDescriptor::Clone を呼び出して、プレゼンテーション記述子の簡易コピーを作成します。 (コピーには、元のストリーム記述子へのポインターが含まれています)。アプリケーションは、プレゼンテーション記述子を使用して、メディアの種類を設定したり、ストリームを選択したり、ストリームの選択を解除したりできます。

必要に応じて、プレゼンテーション記述子とストリーム記述子には、ソースに関する追加情報を提供する属性を含めることができます。 このような属性の一覧については、次のトピックを参照してください。

1 つの属性は特別なメンションに値します。MF_PD_DURATION属性には、ソースの合計期間が含まれます。 前もって期間がわかっている場合は、この属性を設定します。たとえば、ファイル形式に応じて、ファイル ヘッダーに期間を指定できます。 アプリケーションでこの値を表示したり、進行状況バーまたはシーク バーを設定したりするために使用する場合があります。

ストリーミングの状態

メディア ソースは、次の状態を定義します。

状態 Description
Started ソースは、サンプル要求を受け入れて処理します。
一時停止 ソースはサンプル要求を受け入れますが、処理しません。 要求は、ソースが開始されるまでキューに入れられます。
停止中。 ソースはサンプル要求を拒否します。

 

[開始]

IMFMediaSource::Start メソッドは、メディア ソースを開始します。 使用できるパラメーターは次のとおりです。

  • プレゼンテーション記述子。
  • 時刻形式の GUID。
  • 開始位置。

アプリケーションは、ソースで CreatePresentationDescriptor を 呼び出して、プレゼンテーション記述子を取得する必要があります。 プレゼンテーション記述子を検証するための定義済みのメカニズムはありません。 アプリケーションで間違ったプレゼンテーション記述子が指定されている場合、結果は未定義になります。

時間形式 GUID は、開始位置を解釈する方法を指定します。 標準形式は 100 ナノ秒 (ns) 単位で、GUID_NULLで示されます。 すべてのメディア ソースで 100 ns ユニットをサポートする必要があります。 必要に応じて、ソースはフレーム番号やタイム コードなど、他の時間単位をサポートできます。 ただし、メディア ソースに対して、サポートされている時間形式の一覧を照会する標準的な方法はありません。

開始位置は PROPVARIANT として指定され、時間形式に応じて異なるデータ型を使用できます。 100 ns の場合、 PROPVARIANT 型は VT_I8 または VT_EMPTYです。 VT_I8場合、PROPVARIANT には開始位置が 100 ns 単位で格納されます。 VT_EMPTY値には、"現在位置から開始する" という特別な意味があります。

次のように Start メソッドを実装します。

  1. パラメーターと状態を検証します。
    • NULL パラメーターを確認します。
    • 時刻形式の GUID を確認します。 値が無効な場合は、 MF_E_UNSUPPORTED_TIME_FORMATを返します。
    • 開始位置を保持する PROPVARIANT のデータ型を確認します。
    • 開始位置を検証します。 無効な場合は、 MF_E_INVALIDREQUESTを返します。
    • ソースがシャットダウンされている場合は、 MF_E_SHUTDOWNを返します。
  2. 手順 1 でエラーが発生しない場合は、非同期操作をキューに登録します。 この手順の後のすべての処理は、作業キュー スレッドで行われます。
  3. ストリームごとに次の手順を実行します。
    1. ストリームが以前の 開始 要求から既にアクティブになっているかどうかを確認します。

    2. IMFPresentationDescriptor::GetStreamDescriptorByIndex を呼び出して、アプリケーションがストリームを選択したか選択解除したかをチェックします。

    3. 以前に選択したストリームの選択が解除された場合は、そのストリームの配信不能なサンプルをすべてフラッシュします。

    4. ストリームがアクティブな場合、(ストリームではなく) メディア ソースから次のいずれかのイベントが送信されます。

      どちらのイベントでも、イベント データはストリームの IMFMediaStream ポインターです。

    5. ソースが一時停止状態から再起動している場合は、保留中のサンプル要求が存在する可能性があります。 その場合は、今すぐ配信してください。

    6. ソースが新しい位置にシークしている場合、各ストリーム オブジェクトは MEStreamSeeked イベントを送信します。 それ以外の場合、各ストリームは MEStreamStarted イベントを 送信します。

  4. ソースが新しい位置を探している場合、メディア ソースは MESourceSeeked イベントを送信します。 それ以外の場合は、 MESourceStarted イベントを 送信します。

手順 2 の後にいつでもエラーが発生した場合、ソースはエラー コードを含む MESourceStarted イベントを送信します。 これにより、 Start メソッドが非同期的に失敗したことをアプリケーションに警告します。

次のコードは、手順 1 から 2 を示しています。

HRESULT MPEG1Source::Start(
        IMFPresentationDescriptor* pPresentationDescriptor,
        const GUID* pguidTimeFormat,
        const PROPVARIANT* pvarStartPos
    )
{

    HRESULT hr = S_OK;
    SourceOp *pAsyncOp = NULL;

    // Check parameters.

    // Start position and presentation descriptor cannot be NULL.
    if (pvarStartPos == NULL || pPresentationDescriptor == NULL)
    {
        return E_INVALIDARG;
    }

    // Check the time format.
    if ((pguidTimeFormat != NULL) && (*pguidTimeFormat != GUID_NULL))
    {
        // Unrecognized time format GUID.
        return MF_E_UNSUPPORTED_TIME_FORMAT;
    }

    // Check the data type of the start position.
    if ((pvarStartPos->vt != VT_I8) && (pvarStartPos->vt != VT_EMPTY))
    {
        return MF_E_UNSUPPORTED_TIME_FORMAT;
    }

    EnterCriticalSection(&m_critSec);

    // Check if this is a seek request. This sample does not support seeking.

    if (pvarStartPos->vt == VT_I8)
    {
        // If the current state is STOPPED, then position 0 is valid.
        // Otherwise, the start position must be VT_EMPTY (current position).

        if ((m_state != STATE_STOPPED) || (pvarStartPos->hVal.QuadPart != 0))
        {
            hr = MF_E_INVALIDREQUEST;
            goto done;
        }
    }

    // Fail if the source is shut down.
    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    // Fail if the source was not initialized yet.
    hr = IsInitialized();
    if (FAILED(hr))
    {
        goto done;
    }

    // Perform a basic check on the caller's presentation descriptor.
    hr = ValidatePresentationDescriptor(pPresentationDescriptor);
    if (FAILED(hr))
    {
        goto done;
    }

    // The operation looks OK. Complete the operation asynchronously.
    hr = SourceOp::CreateStartOp(pPresentationDescriptor, &pAsyncOp);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pAsyncOp->SetData(*pvarStartPos);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = QueueOperation(pAsyncOp);

done:
    SafeRelease(&pAsyncOp);
    LeaveCriticalSection(&m_critSec);
    return hr;
}

残りの手順を次の例に示します。

HRESULT MPEG1Source::DoStart(StartOp *pOp)
{
    assert(pOp->Op() == SourceOp::OP_START);

    IMFPresentationDescriptor *pPD = NULL;
    IMFMediaEvent  *pEvent = NULL;

    HRESULT     hr = S_OK;
    LONGLONG    llStartOffset = 0;
    BOOL        bRestartFromCurrentPosition = FALSE;
    BOOL        bSentEvents = FALSE;

    hr = BeginAsyncOp(pOp);

    // Get the presentation descriptor from the SourceOp object.
    // This is the PD that the caller passed into the Start() method.
    // The PD has already been validated.
    if (SUCCEEDED(hr))
    {
        hr = pOp->GetPresentationDescriptor(&pPD);
    }

    // Because this sample does not support seeking, the start
    // position must be 0 (from stopped) or "current position."

    // If the sample supported seeking, we would need to get the
    // start position from the PROPVARIANT data contained in pOp.

    if (SUCCEEDED(hr))
    {
        // Select/deselect streams, based on what the caller set in the PD.
        // This method also sends the MENewStream/MEUpdatedStream events.
        hr = SelectStreams(pPD, pOp->Data());
    }

    if (SUCCEEDED(hr))
    {
        m_state = STATE_STARTED;

        // Queue the "started" event. The event data is the start position.
        hr = m_pEventQueue->QueueEventParamVar(
            MESourceStarted,
            GUID_NULL,
            S_OK,
            &pOp->Data()
            );
    }

    if (FAILED(hr))
    {
        // Failure. Send the error code to the application.

        // Note: It's possible that QueueEvent itself failed, in which case it
        // is likely to fail again. But there is no good way to recover in
        // that case.

        (void)m_pEventQueue->QueueEventParamVar(
            MESourceStarted, GUID_NULL, hr, NULL);
    }

    CompleteAsyncOp(pOp);

    SafeRelease(&pEvent);
    SafeRelease(&pPD);
    return hr;
}

一時停止

IMFMediaSource::P ause メソッドは、メディア ソースを一時停止します。 このメソッドを次のように実装します。

  1. 非同期操作をキューに入れます。
  2. アクティブな各ストリームは 、MEStreamPaused イベントを 送信します。
  3. メディア ソースは MESourcePaused イベントを送信します。

一時停止中、ソースは要求を処理せずにサンプル要求をキューに入れます。 ( 「サンプル要求」を参照してください)。

Stop

IMFMediaSource::Stop メソッドはメディア ソースを停止します。 このメソッドを次のように実装します。

  1. 非同期操作をキューに入れます。
  2. アクティブな各ストリームは 、MEStreamStopped イベントを送信します。
  3. キューに登録されているすべてのサンプルとサンプル要求をクリアします。
  4. メディア ソースは MESourceStopped イベントを送信します。

停止中、ソースはサンプルのすべての要求を拒否します。

I/O 要求の進行中にソースが停止した場合、ソースが停止状態になった後に I/O 要求が完了する可能性があります。 その場合、ソースはその I/O 要求の結果を破棄する必要があります。

要求例

Media Foundation では、パイプラインがメディア ソースからサンプルを要求する プル モデルが使用されます。 これは、ソースがサンプルを "プッシュ" する DirectShow で使用されるモデルとは異なります。

新しいサンプルを要求するために、Media Foundation パイプラインは IMFMediaStream::RequestSample を呼び出します。 このメソッドは、トークン オブジェクトを表す IUnknown ポインターを受け取ります。 トークン オブジェクトの実装は呼び出し元次第です。呼び出し元がサンプル要求を追跡する方法を提供するだけです。 token パラメーターには NULL を指定することもできます。

ソースが非同期 I/O 要求を使用してデータを読み取ると、サンプル生成はサンプル要求と同期されません。 サンプル要求をサンプル生成と同期するために、メディア ソースは次の処理を行います。

  1. 要求トークンはキューに配置されます。
  2. サンプルが生成されると、2 番目のキューに配置されます。
  3. メディア ソースは、最初のキューから要求トークンをプルし、2 番目のキューからサンプルをプルすることで、サンプル要求を完了します。
  4. メディア ソースは MEMediaSample イベントを送信します。 イベントにはサンプルへのポインターが含まれており、サンプルにはトークンへのポインターが含まれています。

次の図は、 MEMediaSample イベント、サンプル、および要求トークンの関係を示しています。

memediasample と imfsample を指すサンプル キューを示す図。imfsample と要求キュー ポイントが iunknown

MPEG-1 ソースの例では、このプロセスを次のように実装しています。

  1. RequestSample メソッドは、FIFO キューに要求を配置します。
  2. I/O 要求が完了すると、メディア ソースによって新しいサンプルが作成され、2 番目の FIFO キューに配置されます。 (このキューの最大サイズは、ソースが先に読み取りすぎないようにするためです)。
  3. これらのキューの両方に少なくとも 1 つの項目 (1 つの要求と 1 つのサンプル) がある場合、メディア ソースは、サンプル キューから最初のサンプルを送信することによって、要求キューからの最初の要求を完了します。
  4. サンプルを配信するために、(ソース オブジェクトではなく) ストリーム オブジェクトから MEMediaSample イベントが送信されます。
    • イベント データは、サンプルの IMFSample インターフェイスへのポインターです。
    • 要求にトークンが含まれている場合は、サンプルの MFSampleExtension_Token 属性を設定して、トークンをサンプルにアタッチします。

この時点で、次の 3 つの可能性があります。

  • サンプル キューには別のサンプルがありますが、一致する要求はありません。
  • 要求はありますが、サンプルはありません。
  • どちらのキューも空です。サンプルも要求もありません。

サンプル キューが空の場合、ソースはストリームの末尾を確認します (「 ストリームの終了」を参照してください)。 それ以外の場合は、データに対する別の I/O 要求が開始されます。 このプロセス中にエラーが発生した場合、ストリームは MEError イベントを送信します。

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

HRESULT MPEG1Stream::RequestSample(IUnknown* pToken)
{
    HRESULT hr = S_OK;
    IMFMediaSource *pSource = NULL;

    // Hold the media source object's critical section.
    SourceLock lock(m_pSource);

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

    if (m_state == STATE_STOPPED)
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    if (!m_bActive)
    {
        // If the stream is not active, it should not get sample requests.
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    if (m_bEOS && m_Samples.IsEmpty())
    {
        // This stream has already reached the end of the stream, and the
        // sample queue is empty.
        hr = MF_E_END_OF_STREAM;
        goto done;
    }

    hr = m_Requests.InsertBack(pToken);
    if (FAILED(hr))
    {
        goto done;
    }

    // Dispatch the request.
    hr = DispatchSamples();
    if (FAILED(hr))
    {
        goto done;
    }

done:
    if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
    {
        // An error occurred. Send an MEError even from the source,
        // unless the source is already shut down.
        hr = m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
    }
    return hr;
}

メソッドは DispatchSamples サンプル キューからサンプルをプルし、保留中のサンプル要求と一致し、 MEMediaSample イベントをキューに入れます。

HRESULT MPEG1Stream::DispatchSamples()
{
    HRESULT hr = S_OK;
    BOOL bNeedData = FALSE;
    BOOL bEOS = FALSE;

    SourceLock lock(m_pSource);

    // An I/O request can complete after the source is paused, stopped, or
    // shut down. Do not deliver samples unless the source is running.
    if (m_state != STATE_STARTED)
    {
        return S_OK;
    }

    IMFSample *pSample = NULL;
    IUnknown  *pToken = NULL;

    // Deliver as many samples as we can.
    while (!m_Samples.IsEmpty() && !m_Requests.IsEmpty())
    {
        // Pull the next sample from the queue.
        hr = m_Samples.RemoveFront(&pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Pull the next request token from the queue. Tokens can be NULL.
        hr = m_Requests.RemoveFront(&pToken);
        if (FAILED(hr))
        {
            goto done;
        }

        if (pToken)
        {
            // Set the token on the sample.
            hr = pSample->SetUnknown(MFSampleExtension_Token, pToken);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Send an MEMediaSample event with the sample.
        hr = m_pEventQueue->QueueEventParamUnk(
            MEMediaSample, GUID_NULL, S_OK, pSample);

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

        SafeRelease(&pSample);
        SafeRelease(&pToken);
    }

    if (m_Samples.IsEmpty() && m_bEOS)
    {
        // The sample queue is empty AND we have reached the end of the source
        // stream. Notify the pipeline by sending the end-of-stream event.

        hr = m_pEventQueue->QueueEventParamVar(
            MEEndOfStream, GUID_NULL, S_OK, NULL);

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

        // Notify the source. It will send the end-of-presentation event.
        hr = m_pSource->QueueAsyncOperation(SourceOp::OP_END_OF_STREAM);
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (NeedsData())
    {
        // The sample queue is empty; the request queue is not empty; and we
        // have not reached the end of the stream. Ask for more data.
        hr = m_pSource->QueueAsyncOperation(SourceOp::OP_REQUEST_DATA);
        if (FAILED(hr))
        {
            goto done;
        }
    }

done:
    if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
    {
        // An error occurred. Send an MEError even from the source,
        // unless the source is already shut down.
        m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
    }

    SafeRelease(&pSample);
    SafeRelease(&pToken);
    return S_OK;
}

メソッドは DispatchSamples 、次の状況で呼び出されます。

  • RequestSample メソッド内。
  • メディア ソースが一時停止状態から再起動したとき。
  • I/O 要求が完了したとき。

ストリームの終了

ストリームにそれ以上のデータがなく、そのストリームのすべてのサンプルが配信されている場合、ストリーム オブジェクトは MEEndOfStream イベントを送信します。

すべてのアクティブなストリームが完了すると、メディア ソースから MEEndOfPresentation イベントが 送信されます。

非同期操作

メディア ソースを記述する最も難しい部分は、Media Foundation 非同期モデルを理解することです。

ストリーミングを制御するメディア ソース上のすべてのメソッドは非同期です。 いずれの場合も、 メソッドは、パラメーターのチェックなど、いくつかの初期検証を行います。 その後、ソースは残りの作業を作業キューにディスパッチします。 操作が完了すると、メディア ソースは、メディア ソースの IMFMediaEventGenerator インターフェイスを介して呼び出し元にイベントを送信します。 そのため、作業キューを理解することが重要です。

作業キューに項目を配置するには、 MFPutWorkItem または MFPutWorkItemEx を呼び出します。 MPEG-1 ソースは MFPutWorkItem を使用しますが、2 つの関数は同じことを行います。 MFPutWorkItem 関数は、次のパラメーターを受け取ります。

  • 作業キューを識別する DWORD 値。 プライベート作業キューを作成することも、 MFASYNC_CALLBACK_QUEUE_STANDARDを使用することもできます。
  • IMFAsyncCallback インターフェイスへのポインター。 このコールバック インターフェイスは、作業を実行するために呼び出されます。
  • IUnknown を実装する必要がある省略可能な状態オブジェクト。

作業キューは、キューから次の作業項目を継続的にプルし、コールバック インターフェイスの IMFAsyncCallback::Invoke メソッドを呼び出す 1 つ以上のワーカー スレッドによって処理されます。

作業項目は、キューに配置した順序と同じ順序で実行される保証はありません。 複数のスレッドが同じ作業キューにサービスを提供できるため、 呼び出し の呼び出しが重複したり、順序が異なったりする可能性があります。 そのため、作業キュー項目を適切な順序で送信することで、正しい内部状態を維持するのはメディア ソース次第です。 前の操作が完了した場合にのみ、ソースは次の操作を開始します。

保留中の操作を表すために、MPEG-1 ソースは という名前 SourceOpのクラスを定義します。

// Represents a request for an asynchronous operation.

class SourceOp : public IUnknown
{
public:

    enum Operation
    {
        OP_START,
        OP_PAUSE,
        OP_STOP,
        OP_REQUEST_DATA,
        OP_END_OF_STREAM
    };

    static HRESULT CreateOp(Operation op, SourceOp **ppOp);
    static HRESULT CreateStartOp(IMFPresentationDescriptor *pPD, SourceOp **ppOp);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    SourceOp(Operation op);
    virtual ~SourceOp();

    HRESULT SetData(const PROPVARIANT& var);

    Operation Op() const { return m_op; }
    const PROPVARIANT& Data() { return m_data;}

protected:
    long        m_cRef;     // Reference count.
    Operation   m_op;
    PROPVARIANT m_data;     // Data for the operation.
};

列挙体は Operation 、保留中の操作を識別します。 クラスには、操作の追加データを伝達するための PROPVARIANT も含まれています。

操作キュー

操作をシリアル化するために、メディア ソースは オブジェクトの SourceOp キューを保持します。 ヘルパー クラスを使用してキューを管理します。

template <class OP_TYPE>
class OpQueue : public IUnknown
{
public:

    typedef ComPtrList<OP_TYPE>   OpList;

    HRESULT QueueOperation(OP_TYPE *pOp);

protected:

    HRESULT ProcessQueue();
    HRESULT ProcessQueueAsync(IMFAsyncResult *pResult);

    virtual HRESULT DispatchOperation(OP_TYPE *pOp) = 0;
    virtual HRESULT ValidateOperation(OP_TYPE *pOp) = 0;

    OpQueue(CRITICAL_SECTION& critsec)
        : m_OnProcessQueue(this, &OpQueue::ProcessQueueAsync),
          m_critsec(critsec)
    {
    }

    virtual ~OpQueue()
    {
    }

protected:
    OpList                  m_OpQueue;         // Queue of operations.
    CRITICAL_SECTION&       m_critsec;         // Protects the queue state.
    AsyncCallback<OpQueue>  m_OnProcessQueue;  // ProcessQueueAsync callback.
};

クラスは OpQueue 、非同期作業項目を実行するコンポーネントによって継承されるように設計されています。 OP_TYPE テンプレート パラメーターは、キュー内の作業項目を表すために使用されるオブジェクト型です。この場合、OP_TYPE になりますSourceOp。 クラスは OpQueue 、次のメソッドを実装します。

  • QueueOperation はキューに新しい項目を配置します。
  • ProcessQueue はキューから次の操作をディスパッチします。 このメソッドは非同期です。
  • ProcessQueueAsync は非同期 ProcessQueue メソッドを完了します。

派生クラスでは、別の 2 つのメソッドを実装する必要があります。

  • ValidateOperation は、メディア ソースの現在の状態を指定して、指定した操作を実行することが有効かどうかを確認します。
  • DispatchOperation は非同期作業項目を実行します。

操作キューは次のように使用されます。

  1. Media Foundation パイプラインは、 IMFMediaSource::Start などのメディア ソースで非同期メソッドを呼び出します。
  2. 非同期メソッドは を呼び出QueueOperationします。これにより、Start 操作がキューに配置され、(オブジェクトのSourceOp形式で) が呼び出ProcessQueueされます。
  3. ProcessQueueは MFPutWorkItem を呼び出します。
  4. ワーク キュー スレッドは を呼び出します ProcessQueueAsync
  5. メソッドは ProcessQueueAsync と を呼び出しますValidateOperationDispatchOperation

次のコードは、MPEG-1 ソースに対する新しい操作をキューに入れます。

template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::QueueOperation(OP_TYPE *pOp)
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_critsec);

    hr = m_OpQueue.InsertBack(pOp);
    if (SUCCEEDED(hr))
    {
        hr = ProcessQueue();
    }

    LeaveCriticalSection(&m_critsec);
    return hr;
}

次のコードは、キューを処理します。

template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::ProcessQueue()
{
    HRESULT hr = S_OK;
    if (m_OpQueue.GetCount() > 0)
    {
        hr = MFPutWorkItem(
            MFASYNC_CALLBACK_QUEUE_STANDARD,    // Use the standard work queue.
            &m_OnProcessQueue,                  // Callback method.
            NULL                                // State object.
            );
    }
    return hr;
}

メソッドは ValidateOperation 、MPEG-1 ソースがキュー内の次の操作をディスパッチできるかどうかを確認します。 別の操作が進行中の場合は、 ValidateOperationMF_E_NOTACCEPTINGを返します。 これにより、別の操作が DispatchOperation 保留中の間に が呼び出されないことが保証されます。

HRESULT MPEG1Source::ValidateOperation(SourceOp *pOp)
{
    if (m_pCurrentOp != NULL)
    {
        return MF_E_NOTACCEPTING;
    }
    return S_OK;
}

DispatchOperation メソッドは、操作の種類に切り替わります。

//-------------------------------------------------------------------
// DispatchOperation
//
// Performs the asynchronous operation indicated by pOp.
//
// NOTE:
// This method implements the pure-virtual OpQueue::DispatchOperation
// method. It is always called from a work-queue thread.
//-------------------------------------------------------------------

HRESULT MPEG1Source::DispatchOperation(SourceOp *pOp)
{
    EnterCriticalSection(&m_critSec);

    HRESULT hr = S_OK;

    if (m_state == STATE_SHUTDOWN)
    {
        LeaveCriticalSection(&m_critSec);

        return S_OK; // Already shut down, ignore the request.
    }

    switch (pOp->Op())
    {

    // IMFMediaSource methods:

    case SourceOp::OP_START:
        hr = DoStart((StartOp*)pOp);
        break;

    case SourceOp::OP_STOP:
        hr = DoStop(pOp);
        break;

    case SourceOp::OP_PAUSE:
        hr = DoPause(pOp);
        break;

    // Operations requested by the streams:

    case SourceOp::OP_REQUEST_DATA:
        hr = OnStreamRequestSample(pOp);
        break;

    case SourceOp::OP_END_OF_STREAM:
        hr = OnEndOfStream(pOp);
        break;

    default:
        hr = E_UNEXPECTED;
    }

    if (FAILED(hr))
    {
        StreamingError(hr);
    }

    LeaveCriticalSection(&m_critSec);
    return hr;
}

まとめ

  1. パイプラインは、 IMFMediaSource::Start などの非同期メソッドを呼び出します。
  2. 非同期メソッドは を呼び出し OpQueue::QueueOperation、オブジェクトへのポインターを SourceOp 渡します。
  3. メソッドは QueueOperationm_OpQueue キューに操作を配置し、 を呼び出します OpQueue::ProcessQueue
  4. メソッドは ProcessQueueMFPutWorkItem を呼び出します。 この時点から、すべてが Media Foundation のワーク キュー スレッドで行われます。 非同期メソッドは呼び出し元に戻ります。
  5. ワーク キュー スレッドは メソッドを OpQueue::ProcessQueueAsync 呼び出します。
  6. メソッドは ProcessQueueAsync を呼び出 MPEG1Source:ValidateOperation して操作を検証します。
  7. メソッドは ProcessQueueAsync を呼び出 MPEG1Source::DispatchOperation して操作を処理します。

この設計にはいくつかの利点があります。

  • メソッドは非同期であるため、呼び出し元のアプリケーションのスレッドはブロックされません。
  • 操作は、パイプライン コンポーネント間で共有される Media Foundation 作業キュー スレッドでディスパッチされます。 そのため、メディア ソースは独自のスレッドを作成しないため、作成されるスレッドの合計数が減ります。
  • メディア ソースは、操作の完了を待機している間はブロックされません。 これにより、メディア ソースが誤ってデッドロックを引き起こす可能性が減り、コンテキストの切り替えを減らすことができます。
  • メディア ソースは、非同期 I/O を使用してソース ファイルを読み取ることができます ( IMFByteStream::BeginRead を呼び出します)。 メディア ソースは、I/O ルーチンの完了を待機している間にブロックする必要はありません。

SDK サンプルに示されているパターンに従う場合は、メディア ソースの特定の詳細に注目できます。

メディア ソース

カスタム メディア ソースの作成