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

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

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

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

Средство чтения источника работает либо в синхронном режиме, либо в асинхронном режиме. В примере кода, показанном в предыдущем разделе, предполагается, что средство чтения исходного кода использует синхронный режим, который является значением по умолчанию. В синхронном режиме метод 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. Уведомляет приложение о возникновении определенных событий в источнике мультимедиа, таких как буферизация или события сетевого подключения.
  • НаФлуш. Вызывается после завершения метода 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;
}

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