Share via


Usando o leitor de origem no modo assíncrono

Este tópico descreve como usar o Leitor de Origem no modo assíncrono. No modo assíncrono, o aplicativo fornece uma interface de retorno de chamada, que é usada para notificar o aplicativo de que os dados estão disponíveis.

Este tópico pressupõe que você já leu o tópico Usando o Leitor de Origem para processar dados de mídia.

Usando o modo assíncrono

O Leitor de Origem opera no modo síncrono ou no modo assíncrono. O exemplo de código mostrado na seção anterior pressupõe que o Leitor de Origem esteja usando o modo síncrono, que é o padrão. No modo síncrono, o método IMFSourceReader::ReadSample é bloqueado enquanto a fonte de mídia produz a próxima amostra. Uma fonte de mídia normalmente adquire dados de alguma fonte externa (como um arquivo local ou uma conexão de rede), para que o método possa bloquear o thread de chamada por uma quantidade notável de tempo.

No modo assíncrono, o ReadSample retorna imediatamente e o trabalho é executado em outro thread. Após a conclusão da operação, o Leitor de Origem chama o aplicativo por meio da interface de retorno de chamada IMFSourceReaderCallback . Para usar o modo assíncrono, você deve fornecer um ponteiro de retorno de chamada ao criar pela primeira vez o Leitor de Origem, da seguinte maneira:

  1. Crie um repositório de atributos chamando a função MFCreateAttributes .
  2. Defina o atributo MF_SOURCE_READER_ASYNC_CALLBACK no repositório de atributos. O valor do atributo é um ponteiro para o objeto de retorno de chamada.
  3. Ao criar o Leitor de Origem, passe o repositório de atributos para a função de criação no parâmetro pAttributes . Todas as funções para criar o Leitor de Origem têm esse parâmetro.

O exemplo a seguir mostra estas etapas.

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

Depois de criar o Leitor de Origem, você não pode alternar os modos entre síncrono e assíncrono.

Para obter dados no modo assíncrono, chame o método ReadSample , mas defina os últimos quatro parâmetros como NULL, conforme mostrado no exemplo a seguir.

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

Quando o método ReadSample é concluído de forma assíncrona, o Leitor de Origem chama o método IMFSourceReaderCallback::OnReadSample . Esse método tem cinco parâmetros:

  • hrStatus: contém um valor HRESULT . Esse é o mesmo valor que ReadSample retornaria no modo síncrono. Se hrStatus contiver um código de erro, você poderá ignorar os parâmetros restantes.
  • dwStreamIndex, dwStreamFlags, llTimestamp e pSample: esses três parâmetros são equivalentes aos três últimos parâmetros em ReadSample. Eles contêm o número do fluxo, status sinalizadores e o ponteiro IMFSample, respectivamente.
    STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
        DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);

Além disso, a interface de retorno de chamada define dois outros métodos:

  • OnEvent. Notifica o aplicativo quando determinados eventos ocorrem na fonte de mídia, como buffer ou eventos de conexão de rede.
  • OnFlush. Chamado quando o método Flush é concluído.

Implementando a interface de retorno de chamada

A interface de retorno de chamada deve ser thread-safe, pois OnReadSample e os outros métodos de retorno de chamada são chamados de threads de trabalho.

Há várias abordagens diferentes que você pode adotar ao implementar o retorno de chamada. Por exemplo, você pode fazer todo o trabalho dentro do retorno de chamada ou pode usar o retorno de chamada para notificar o aplicativo (por exemplo, sinalizando um identificador de evento) e, em seguida, trabalhar no thread do aplicativo.

O método OnReadSample será chamado uma vez para cada chamada feita ao método IMFSourceReader::ReadSample . Para obter o próximo exemplo, chame ReadSample novamente. Se ocorrer um erro, OnReadSample será chamado com um código de erro para o parâmetro hrStatus .

O exemplo a seguir mostra uma implementação mínima da interface de retorno de chamada. Primeiro, aqui está a declaração de uma classe que implementa a interface .

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

};

Neste exemplo, não estamos interessados nos métodos OnEvent e OnFlush , portanto, eles simplesmente retornam S_OK. A classe usa um identificador de evento para sinalizar o aplicativo; esse identificador é fornecido por meio do construtor.

Neste exemplo mínimo, o método OnReadSample apenas imprime o carimbo de data/hora na janela do console. Em seguida, ele armazena o código status e o sinalizador de fim do fluxo e sinaliza o identificador de evento:

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

O código a seguir mostra que o aplicativo usaria essa classe de retorno de chamada para ler todos os quadros de vídeo de um arquivo de mídia:

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

Leitor de origem