在異步模式中使用來源讀取器
本主題描述如何在異步模式中使用 來源讀取器。 在異步模式中,應用程式會提供回呼介面,用來通知應用程式有可用的數據。
本主題假設您已經閱讀本主題 使用來源讀取器處理媒體資料。
使用異步模式
來源讀取器會以同步模式或異步模式運作。 上一節中顯示的程式代碼範例假設來源讀取器使用同步模式,這是預設值。 在同步模式中,當媒體來源產生下一個範例時,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);
此外,回呼介面會定義另外兩種方法:
實作回呼介面
回呼介面必須是執行緒安全的,因為 OnReadSample 和其他回呼方法是從工作執行緒呼叫的。
實作回呼時,您可以採取數種不同的方法。 例如,您可以在回呼內執行所有工作,或使用回呼來通知應用程式(例如,透過發出事件句柄的訊號),然後從應用程式線程執行工作。
每次呼叫 IMFSourceReader::ReadSample 方法時,都會呼叫 OnReadSample 方法一次。 若要取得下一個範例,請再次呼叫 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;
}
相關主題