非同期モードでのソース リーダーの使用
このトピックでは、非同期モードで ソース リーダー を使用する方法について説明します。 非同期モードでは、アプリケーションはコールバック インターフェイスを提供します。これは、データが使用可能であることをアプリケーションに通知するために使用されます。
このトピックでは、 ソース リーダーを使用したメディア データの処理に関するトピックを既に読んでいるものとします。
非同期モードの使用
ソース リーダーは、同期モードまたは非同期モードで動作します。 前のセクションで示したコード例では、ソース リーダーが同期モード (既定値) を使用していることを前提としています。 同期モードでは、メディア ソースが次のサンプルを生成する間、 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 メソッドを呼び出しますが、次の例に示すように、最後の 4 つのパラメーターを NULL に設定します。
// Request the first sample.
hr = pReader->ReadSample(MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0, NULL, NULL, NULL, NULL);
ReadSample メソッドが非同期的に完了すると、ソース リーダーは IMFSourceReaderCallback::OnReadSample メソッドを呼び出します。 このメソッドには、次の 5 つのパラメーターがあります。
- hrStatus: HRESULT 値が含まれています。 これは、 ReadSample が同期モードで返すのと同じ値です。 hrStatus にエラー コードが含まれている場合は、残りのパラメーターを無視できます。
- dwStreamIndex、 dwStreamFlags、llTimestamp、 および pSample: これら 3 つのパラメーターは 、ReadSample の最後の 3 つのパラメーターと同じです。 ストリーム番号、状態フラグ、 および IMFSample ポインターがそれぞれ含まれています。
STDMETHODIMP OnReadSample(HRESULT hrStatus, DWORD dwStreamIndex,
DWORD dwStreamFlags, LONGLONG llTimestamp, IMFSample *pSample);
さらに、コールバック インターフェイスは、他の 2 つのメソッドを定義します。
- OnEvent。 バッファーやネットワーク接続イベントなど、メディア ソースで特定のイベントが発生したときにアプリケーションに通知します。
- OnFlush。 Flush メソッドが完了したときに呼び出されます。
コールバック インターフェイスの実装
OnReadSample とその他のコールバック メソッドはワーカー スレッドから呼び出されるため、コールバック インターフェイスはスレッド セーフである必要があります。
コールバックを実装するときに実行できる方法はいくつかあります。 たとえば、コールバック内のすべての作業を行ったり、コールバックを使用してアプリケーションに通知したり (イベント ハンドルの通知など)、アプリケーション スレッドから作業を実行したりできます。
ONReadSample メソッドは、IMFSourceReader::ReadSample メソッドを呼び出すたびに 1 回呼び出されます。 次のサンプルを取得するには、 ReadSample をもう一度呼び出します。 エラーが発生した場合は、hrStatus パラメーターのエラー コードを使用して OnReadSample が呼び出されます。
次の例は、コールバック インターフェイスの最小限の実装を示しています。 まず、インターフェイスを実装するクラスの宣言を次に示します。
#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;
}
関連トピック