非同期モードでのソース リーダーの使用

このトピックでは、非同期モードで ソース リーダー を使用する方法について説明します。 非同期モードでは、アプリケーションはコールバック インターフェイスを提供します。これは、データが使用可能であることをアプリケーションに通知するために使用されます。

このトピックでは、 ソース リーダーを使用したメディア データの処理に関するトピックを既に読んでいるものとします。

非同期モードの使用

ソース リーダーは、同期モードまたは非同期モードで動作します。 前のセクションで示したコード例では、ソース リーダーが同期モード (既定値) を使用していることを前提としています。 同期モードでは、メディア ソースが次のサンプルを生成する間、 IMFSourceReader::ReadSample メソッドはブロックします。 メディア ソースは通常、一部の外部ソース (ローカル ファイルやネットワーク接続など) からデータを取得するため、 メソッドは呼び出し元のスレッドを顕著な時間ブロックできます。

非同期モードでは、 ReadSample は直ちにを返し、作業は別のスレッドで実行されます。 操作が完了すると、ソース リーダーは IMFSourceReaderCallback コールバック インターフェイスを介してアプリケーションを呼び出します。 非同期モードを使用するには、次のように、最初にソース リーダーを作成するときにコールバック ポインターを指定する必要があります。

  1. MFCreateAttributes 関数を呼び出して属性ストアを作成します。
  2. 属性ストアに MF_SOURCE_READER_ASYNC_CALLBACK 属性を設定します。 属性値は、コールバック オブジェクトへのポインターです。
  3. ソース リーダーを作成するときに、 pAttributes パラメーターの作成関数に属性ストアを渡します。 ソース リーダーを作成するすべての関数には、このパラメーターがあります。

次の例は、これらの手順を示しています。

HRESULT CreateSourceReaderAsync(
    PCWSTR pszURL, 
    IMFSourceReaderCallback *pCallback, 
    IMFSourceReader **ppReader)
{
    HRESULT hr = S_OK;
    IMFAttributes *pAttributes = NULL;

    hr = MFCreateAttributes(&pAttributes, 1);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = MFCreateSourceReaderFromURL(pszURL, pAttributes, ppReader);

done:
    SafeRelease(&pAttributes);
    return hr;
}

ソース リーダーを作成した後は、同期モードと非同期モードを切り替えることはできません。

非同期モードでデータを取得するには、 ReadSample メソッドを呼び出しますが、次の例に示すように、最後の 4 つのパラメーターを NULL に設定します。

    // Request the first sample.
    hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
        0, NULL, NULL, NULL, NULL);

ReadSample メソッドが非同期的に完了すると、ソース リーダーは IMFSourceReaderCallback::OnReadSample メソッドを呼び出します。 このメソッドには、次の 5 つのパラメーターがあります。

  • hrStatus: HRESULT 値が含まれています。 これは、 ReadSample が同期モードで返すのと同じ値です。 hrStatus にエラー コードが含まれている場合は、残りのパラメーターを無視できます。
  • dwStreamIndexdwStreamFlags、llTimestamp、 および pSample: これら 3 つのパラメーターは 、ReadSample の最後の 3 つのパラメーターと同じです。 ストリーム番号、状態フラグ、 および IMFSample ポインターがそれぞれ含まれています。
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

さらに、コールバック インターフェイスは、他の 2 つのメソッドを定義します。

  • OnEvent。 バッファーやネットワーク接続イベントなど、メディア ソースで特定のイベントが発生したときにアプリケーションに通知します。
  • OnFlushFlush メソッドが完了したときに呼び出されます。

コールバック インターフェイスの実装

OnReadSample とその他のコールバック メソッドはワーカー スレッドから呼び出されるため、コールバック インターフェイスはスレッド セーフである必要があります。

コールバックを実装するときに実行できる方法はいくつかあります。 たとえば、コールバック内のすべての作業を行ったり、コールバックを使用してアプリケーションに通知したり (イベント ハンドルの通知など)、アプリケーション スレッドから作業を実行したりできます。

ONReadSample メソッドは、IMFSourceReader::ReadSample メソッドを呼び出すたびに 1 回呼び出されます。 次のサンプルを取得するには、 ReadSample をもう一度呼び出します。 エラーが発生した場合は、hrStatus パラメーターのエラー コードを使用して OnReadSample が呼び出されます。

次の例は、コールバック インターフェイスの最小限の実装を示しています。 まず、インターフェイスを実装するクラスの宣言を次に示します。

#include <shlwapi.h>

class SourceReaderCB : public IMFSourceReaderCallback
{
public:
    SourceReaderCB(HANDLE hEvent) : 
      m_nRefCount(1), m_hEvent(hEvent), m_bEOS(FALSE), m_hrStatus(S_OK)
    {
        InitializeCriticalSection(&m_critsec);
    }

    // IUnknown methods
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv)
    {
        static const QITAB qit[] =
        {
            QITABENT(SourceReaderCB, IMFSourceReaderCallback),
            { 0 },
        };
        return QISearch(this, qit, iid, ppv);
    }
    STDMETHODIMP_(ULONG) AddRef()
    {
        return InterlockedIncrement(&m_nRefCount);
    }
    STDMETHODIMP_(ULONG) Release()
    {
        ULONG uCount = InterlockedDecrement(&m_nRefCount);
        if (uCount == 0)
        {
            delete this;
        }
        return uCount;
    }

    // IMFSourceReaderCallback methods
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

    STDMETHODIMP OnEvent(DWORD, IMFMediaEvent *)
    {
        return S_OK;
    }

    STDMETHODIMP OnFlush(DWORD)
    {
        return S_OK;
    }

public:
    HRESULT Wait(DWORD dwMilliseconds, BOOL *pbEOS)
    {
        *pbEOS = FALSE;

        DWORD dwResult = WaitForSingleObject(m_hEvent, dwMilliseconds);
        if (dwResult == WAIT_TIMEOUT)
        {
            return E_PENDING;
        }
        else if (dwResult != WAIT_OBJECT_0)
        {
            return HRESULT_FROM_WIN32(GetLastError());
        }

        *pbEOS = m_bEOS;
        return m_hrStatus;
    }
    
private:
    
    // Destructor is private. Caller should call Release.
    virtual ~SourceReaderCB() 
    {
    }

    void NotifyError(HRESULT hr)
    {
        wprintf(L"Source Reader error: 0x%X\n", hr);
    }

private:
    long                m_nRefCount;        // Reference count.
    CRITICAL_SECTION    m_critsec;
    HANDLE              m_hEvent;
    BOOL                m_bEOS;
    HRESULT             m_hrStatus;

};

この例では、 OnEvent メソッドと OnFlush メソッドには関心がないため、 単に S_OK を返します。 クラスは、イベント ハンドルを使用してアプリケーションに通知します。このハンドルは、コンストラクターを介して提供されます。

この最小の例では、 OnReadSample メソッドはタイム スタンプをコンソール ウィンドウに出力するだけです。 次に、状態コードとストリームの終了フラグを格納し、イベント ハンドルに通知します。

HRESULT SourceReaderCB::OnReadSample(
    HRESULT hrStatus,
    DWORD /* dwStreamIndex */,
    DWORD dwStreamFlags,
    LONGLONG llTimestamp,
    IMFSample *pSample      // Can be NULL
    )
{
    EnterCriticalSection(&m_critsec);

    if (SUCCEEDED(hrStatus))
    {
        if (pSample)
        {
            // Do something with the sample.
            wprintf(L"Frame @ %I64d\n", llTimestamp);
        }
    }
    else
    {
        // Streaming error.
        NotifyError(hrStatus);
    }

    if (MF_SOURCE_READERF_ENDOFSTREAM & dwStreamFlags)
    {
        // Reached the end of the stream.
        m_bEOS = TRUE;
    }
    m_hrStatus = hrStatus;

    LeaveCriticalSection(&m_critsec);
    SetEvent(m_hEvent);
    return S_OK;
}

次のコードは、アプリケーションでこのコールバック クラスを使用して、メディア ファイルからすべてのビデオ フレームを読み取ることを示しています。

HRESULT ReadMediaFile(PCWSTR pszURL)
{
    HRESULT hr = S_OK;

    IMFSourceReader *pReader = NULL;
    SourceReaderCB *pCallback = NULL;

    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (hEvent == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto done;
    }

    // Create an instance of the callback object.
    pCallback = new (std::nothrow) SourceReaderCB(hEvent);
    if (pCallback == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    // Create the Source Reader.
    hr = CreateSourceReaderAsync(pszURL, pCallback, &pReader);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = ConfigureDecoder(pReader, MF_SOURCE_READER_FIRST_VIDEO_STREAM);
    if (FAILED(hr))
    {
        goto done;
    }

    // Request the first sample.
    hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM, 
        0, NULL, NULL, NULL, NULL);
    if (FAILED(hr))
    {
        goto done;
    }

    while (SUCCEEDED(hr))
    {
        BOOL bEOS;
        hr = pCallback->Wait(INFINITE, &bEOS);
        if (FAILED(hr) || bEOS)
        {
            break;
        }
        hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,
            0, NULL, NULL, NULL, NULL);
    }

done:
    SafeRelease(&pReader);
    SafeRelease(&pCallback);
    return hr;
}

ソース リーダー