Использование средства чтения источника в асинхронном режиме
В этом разделе описывается, как использовать ридер источников в асинхронном режиме. В асинхронном режиме приложение предоставляет интерфейс обратного вызова, который используется для уведомления приложения о доступности данных.
В этом разделе предполагается, что вы уже прочитали раздел «Использование средства чтения источника для обработки данных мультимедиа».
Использование асинхронного режима
Средство чтения источника работает либо в синхронном режиме, либо в асинхронном режиме. В примере кода, показанном в предыдущем разделе, предполагается, что средство чтения исходного кода использует синхронный режим, который является значением по умолчанию. В синхронном режиме метод IMFSourceReader::ReadSample блокируется, пока источник мультимедиа создает следующий образец. Источник мультимедиа обычно получает данные из какого-либо внешнего источника (например, локального файла или сетевого подключения), поэтому метод может блокировать вызывающий поток за заметное время.
В асинхронном режиме ReadSample возвращается немедленно, и работа выполняется в другом потоке. После завершения операции источник-ридер вызывает приложение через интерфейс обратного вызова IMFSourceReaderCallback. Чтобы использовать асинхронный режим, необходимо указать указатель обратного вызова при первом создании средства чтения источника, как показано ниже.
- Создайте хранилище атрибутов, вызвав функциюMFCreateAttributes.
- Задайте атрибут MF_SOURCE_READER_ASYNC_CALLBACK в хранилище атрибутов. Значение атрибута — это указатель на объект обратного вызова.
- При создании средства чтения источника передайте хранилище атрибутов функции создания в параметре 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;
}
Связанные разделы