在非同步模式中使用來源讀取器

本主題描述如何在非同步模式中使用 來源讀取器 。 在非同步模式中,應用程式會提供回呼介面,用來通知應用程式是否有可用的資料。

本主題假設您已經閱讀 使用來源讀取器來處理媒體資料的主題。

使用非同步模式

來源讀取器會以同步模式或非同步模式運作。 上一節所示的程式碼範例假設來源讀取器使用同步模式,這是預設值。 在同步模式中,當媒體來源產生下一個範例時, 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 方法,但將最後四個參數設定為 Null,如下列範例所示。

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

ReadSample 方法以非同步方式完成時,來源讀取器會呼叫 您的 IMFSourceReaderCallback::OnReadSample 方法。 這個方法有五個參數:

  • hrStatus:包含 HRESULT 值。 這是 ReadSample 在同步模式中傳回的相同值。 如果 hrStatus 包含錯誤碼,您可以忽略其餘參數。
  • dwStreamIndexdwStreamFlags、llTimestamp 和 pSample:這三個參數相當於 ReadSample中的最後三個參數。 它們分別包含資料流程編號、狀態旗標和 IMFSample 指標。
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

此外,回呼介面會定義兩個其他方法:

  • OnEvent。 在媒體來源中發生特定事件時通知應用程式,例如緩衝或網路線上活動。
  • OnFlushFlush方法完成時呼叫。

實作回呼介面

回呼介面必須是安全線程,因為 OnReadSample 和其他回呼方法是從背景工作執行緒呼叫。

實作回呼時,您可以採用數種不同的方法。 例如,您可以在回呼內執行所有工作,或使用回呼來通知應用程式 (,例如,藉由向事件控制碼發出訊號) ,然後從應用程式執行緒執行工作。

針對您對IMFSourceReader::ReadSample方法進行的每個呼叫,都會呼叫OnReadSample方法一次。 若要取得下一個範例,請再次呼叫 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;

};

在此範例中,我們對 OnEventOnFlush 方法不感興趣,因此它們只會傳回 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;
}

來源讀取器