在异步模式下使用源读取器

本主题介绍如何在异步模式下使用 源读取器 。 在异步模式下,应用程序提供回调接口,用于通知应用程序数据可用。

本主题假定你已阅读主题 使用源读取器处理媒体数据

使用异步模式

源读取器在同步模式或异步模式下运行。 上一部分中显示的代码示例假定源读取器使用的是同步模式,这是默认模式。 在同步模式下, 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。 当媒体源中发生某些事件(例如缓冲或网络连接事件)时,通知应用程序。
  • OnFlush 刷新方法完成时调用。

实现回调接口

回调接口必须是线程安全的,因为 OnReadSample 和其他回调方法是从工作线程调用的。

实现回调时,可以采用几种不同的方法。 例如,可以执行回调中的所有工作,也可以使用回调通知应用程序 (,例如,通过向事件句柄发出) 信号,然后从应用程序线程执行工作。

对于对 IMFSourceReader::ReadSample 方法进行的每个调用,将调用 OnReadSample 方法一次。 若要获取下一个示例,请再次调用 ReadSample 。 如果发生错误,将调用 OnReadSample ,其中包含 hrStatus 参数的错误代码。

以下示例演示回调接口的最小实现。 首先,下面是实现 接口的类的声明。

#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;
}

源读取器