Использование средства чтения источника в асинхронном режиме

В этом разделе описывается, как использовать средство чтения источника в асинхронном режиме. В асинхронном режиме приложение предоставляет интерфейс обратного вызова, который используется для уведомления приложения о доступности данных.

В этом разделе предполагается, что вы уже ознакомились с разделом Использование средства чтения источника для обработки данных мультимедиа.

Использование асинхронного режима

Средство чтения исходного кода работает в синхронном или асинхронном режиме. В примере кода, показанном в предыдущем разделе, предполагается, что средство чтения исходного кода использует синхронный режим, который используется по умолчанию. В синхронном режиме метод 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 содержит код ошибки, остальные параметры можно игнорировать.
  • dwStreamIndex, dwStreamFlags, llTimestamp и pSample: эти три параметра эквивалентны последним трем параметрам в ReadSample. Они содержат номер потока, флаги состояния и указатель IMFSample соответственно.
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

Кроме того, интерфейс обратного вызова определяет два других метода:

  • OnEvent. Уведомляет приложение о возникновении определенных событий в источнике мультимедиа, таких как буферизация или события сетевого подключения.
  • OnFlush. Вызывается после завершения метода Flush .

Реализация интерфейса обратного вызова

Интерфейс обратного вызова должен быть потокобезопасный, так как OnReadSample и другие методы обратного вызова вызываются из рабочих потоков.

Существует несколько различных подходов, которые можно использовать при реализации обратного вызова. Например, можно выполнить всю работу внутри обратного вызова или использовать обратный вызов для уведомления приложения (например, путем передачи сигнала дескриптору события), а затем выполнить работу из потока приложения.

Метод OnReadSample будет вызываться один раз для каждого вызова метода IMFSourceReader::ReadSample . Чтобы получить следующий пример, снова вызовите 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;

};

В этом примере нас не интересуют методы 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;
}

Средство чтения исходного кода