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:
- Crie um repositório de atributos chamando a função MFCreateAttributes .
- 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.
- 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;
}
Tópicos relacionados