使用來源讀取器來處理媒體數據
本主題描述如何使用 來源讀取器 來處理媒體數據。
若要使用來源讀取器,請遵循下列基本步驟:
- 建立來源讀取器的實例。
- 列舉可能的輸出格式。
- 設定每個數據流的實際輸出格式。
- 處理來源的數據。
本主題的其餘部分會詳細說明這些步驟。
建立來源讀取器
若要建立來源讀取器的實例,請呼叫下列其中一個函式:
功能 | 描述 |
---|---|
MFCreateSourceReaderFromURL |
接受 URL 做為輸入。 此函式會使用 來源解析程式 從URL建立媒體來源。 |
MFCreateSourceReaderFromByteStream |
需要位元組流的指標。 此函式也會使用來源解析程式來建立媒體來源。 |
MFCreateSourceReaderFromMediaSource |
接收已建立之媒體來源的指標。 此函式適用於來源解析程式無法建立的媒體來源,例如擷取裝置或自定義媒體來源。 |
一般而言,針對媒體檔案,請使用 MFCreateSourceReaderFromURL。 針對網路攝影機等裝置,請使用 MFCreateSourceReaderFromMediaSource。 (如需Microsoft媒體基礎中擷取裝置的詳細資訊,請參閱 音訊/視訊擷取。
每個函式都會採用選擇性 IMFAttributes 指標,用來在來源讀取器上設定各種選項,如這些函式的參考主題中所述。 若要取得預設行為,請將此參數設定為 NULL。 每個函式都會傳回 IMFSourceReader 指標作為輸出參數。 您必須先呼叫 CoInitialize(Ex) 和 MFStartup 函式,才能呼叫上述任一函式。
下列程式代碼會從 URL 建立來源讀取器。
int __cdecl wmain(int argc, __in_ecount(argc) PCWSTR* argv)
{
if (argc < 2)
{
return 1;
}
const WCHAR *pszURL = argv[1];
// Initialize the COM runtime.
HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
// Initialize the Media Foundation platform.
hr = MFStartup(MF_VERSION);
if (SUCCEEDED(hr))
{
// Create the source reader.
IMFSourceReader *pReader;
hr = MFCreateSourceReaderFromURL(pszURL, NULL, &pReader);
if (SUCCEEDED(hr))
{
ReadMediaFile(pReader);
pReader->Release();
}
// Shut down Media Foundation.
MFShutdown();
}
CoUninitialize();
}
}
列舉輸出格式
每個媒體來源至少有一個數據流。 例如,視訊檔案可能包含視訊串流和音訊串流。 每個數據流的格式會使用媒體類型來描述,由IMFMediaType介面表示。 如需媒體類型的詳細資訊,請參閱 媒體類型。 您必須檢查媒體類型,以瞭解從來源讀取器取得的數據格式。
一開始,每個數據流都有預設格式,您可以藉由呼叫 IMFSourceReader::GetCurrentMediaType 方法來找到:
針對每個數據流,媒體來源會提供該數據流的可能媒體類型清單。 類型數目取決於來源。 如果來源代表媒體檔案,通常每個數據流只會有一個類型。 另一方面,網路攝影機可能能夠以數種不同的格式串流視訊。 在此情況下,應用程式可以從媒體類型清單中選取要使用的格式。
若要取得數據流的其中一種媒體類型,請呼叫 IMFSourceReader::GetNativeMediaType 方法。 此方法會採用兩個索引參數:數據流的索引,以及數據流媒體類型清單中的索引。 若要列舉數據流的所有類型,請遞增清單索引,同時保留數據流索引常數。 當清單索引超出範圍時,GetNativeMediaType 傳回 MF_E_NO_MORE_TYPES。
HRESULT EnumerateTypesForStream(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
HRESULT hr = S_OK;
DWORD dwMediaTypeIndex = 0;
while (SUCCEEDED(hr))
{
IMFMediaType *pType = NULL;
hr = pReader->GetNativeMediaType(dwStreamIndex, dwMediaTypeIndex, &pType);
if (hr == MF_E_NO_MORE_TYPES)
{
hr = S_OK;
break;
}
else if (SUCCEEDED(hr))
{
// Examine the media type. (Not shown.)
pType->Release();
}
++dwMediaTypeIndex;
}
return hr;
}
若要列舉每個數據流的媒體類型,請遞增數據流索引。 當數據流索引超出界限時,GetNativeMediaType 傳回 MF_E_INVALIDSTREAMNUMBER。
HRESULT EnumerateMediaTypes(IMFSourceReader *pReader)
{
HRESULT hr = S_OK;
DWORD dwStreamIndex = 0;
while (SUCCEEDED(hr))
{
hr = EnumerateTypesForStream(pReader, dwStreamIndex);
if (hr == MF_E_INVALIDSTREAMNUMBER)
{
hr = S_OK;
break;
}
++dwStreamIndex;
}
return hr;
}
設定輸出格式
若要變更輸出格式,請呼叫 IMFSourceReader::SetCurrentMediaType 方法。 此方法會採用資料流索引和媒體類型:
hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);
針對媒體類型,這取決於您是否要插入譯碼器。
- 若要直接從來源取得數據而不譯碼,請使用 getNativeMediaType 傳回的其中一種類型。
- 若要譯碼數據流,請建立描述所需未壓縮格式的新媒體類型。
在譯碼器的情況下,建立媒體類型,如下所示:
- 呼叫 MFCreateMediaType,以建立新的媒體類型。
- 設定 MF_MT_MAJOR_TYPE 屬性以指定音訊或視訊。
- 設定 MF_MT_SUBTYPE 屬性,以指定譯碼格式的子類型。 (請參閱 音訊子類型 GUID 和 視訊子類型 GUID。
- 呼叫 IMFSourceReader::SetCurrentMediaType。
來源讀取器會自動載入譯碼器。 若要取得解碼格式的完整詳細數據,請在呼叫 SetCurrentMediaType 之後呼叫 IMFSourceReader::GetCurrentMediaType
下列程式代碼會設定 RGB-32 的視訊串流,以及 PCM 音訊的音訊串流。
HRESULT ConfigureDecoder(IMFSourceReader *pReader, DWORD dwStreamIndex)
{
IMFMediaType *pNativeType = NULL;
IMFMediaType *pType = NULL;
// Find the native format of the stream.
HRESULT hr = pReader->GetNativeMediaType(dwStreamIndex, 0, &pNativeType);
if (FAILED(hr))
{
return hr;
}
GUID majorType, subtype;
// Find the major type.
hr = pNativeType->GetGUID(MF_MT_MAJOR_TYPE, &majorType);
if (FAILED(hr))
{
goto done;
}
// Define the output type.
hr = MFCreateMediaType(&pType);
if (FAILED(hr))
{
goto done;
}
hr = pType->SetGUID(MF_MT_MAJOR_TYPE, majorType);
if (FAILED(hr))
{
goto done;
}
// Select a subtype.
if (majorType == MFMediaType_Video)
{
subtype= MFVideoFormat_RGB32;
}
else if (majorType == MFMediaType_Audio)
{
subtype = MFAudioFormat_PCM;
}
else
{
// Unrecognized type. Skip.
goto done;
}
hr = pType->SetGUID(MF_MT_SUBTYPE, subtype);
if (FAILED(hr))
{
goto done;
}
// Set the uncompressed format.
hr = pReader->SetCurrentMediaType(dwStreamIndex, NULL, pType);
if (FAILED(hr))
{
goto done;
}
done:
SafeRelease(&pNativeType);
SafeRelease(&pType);
return hr;
}
處理媒體數據
若要從來源取得媒體數據,請呼叫 IMFSourceReader::ReadSample 方法,如下列程式碼所示。
DWORD streamIndex, flags;
LONGLONG llTimeStamp;
hr = pReader->ReadSample(
MF_SOURCE_READER_ANY_STREAM, // Stream index.
0, // Flags.
&streamIndex, // Receives the actual stream index.
&flags, // Receives status flags.
&llTimeStamp, // Receives the time stamp.
&pSample // Receives the sample or NULL.
);
第一個參數是您想要取得數據的數據流索引。 您也可以指定 MF_SOURCE_READER_ANY_STREAM,從任何數據流取得下一個可用的數據。 第二個參數包含選擇性旗標;如需這些清單,請參閱 MF_SOURCE_READER_CONTROL_FLAG。 第三個參數會接收實際產生數據的數據流索引。 如果您將第一個參數設定為 MF_SOURCE_READER_ANY_STREAM,您將需要此資訊。 第四個參數會接收狀態旗標,指出讀取數據時可能發生的各種事件,例如數據流中的格式變更。 如需狀態旗標的清單,請參閱 MF_SOURCE_READER_FLAG。
如果媒體來源能夠產生所要求數據流的數據,ReadSample 的最後一個參數 會接收媒體範例物件的 IMFSample 介面指標。 使用媒體範例來達到以下目的:
- 取得媒體數據的指標。
- 取得簡報時間和範例持續時間。
- 取得描述交錯、場優勢和其他樣本層面的屬性。
媒體數據的內容取決於數據流的格式。 針對未壓縮的視訊串流,每個媒體範例都包含單一視訊畫面。 對於未壓縮的音訊數據流,每個媒體範例都包含一連串的音訊畫面。
ReadSample 方法可以傳回 S_OK,但尚未傳回 pSample 參數 中的媒體範例。 例如,當您到達檔案結尾時,ReadSample 在 dwFlags 中設定 MF_SOURCE_READERF_ENDOFSTREAM 旗標,並將 pSample 設定為 NULL。 在此情況下,ReadSample 方法會傳回 S_OK,因為並未發生錯誤,即使 pSample 參數設定為 NULL。 因此,在取值之前,請一律檢查 pSample 的值。
下列程式代碼示範如何在迴圈中呼叫 ReadSample,並檢查 方法傳回的資訊,直到到達媒體檔案的結尾為止。
HRESULT ProcessSamples(IMFSourceReader *pReader)
{
HRESULT hr = S_OK;
IMFSample *pSample = NULL;
size_t cSamples = 0;
bool quit = false;
while (!quit)
{
DWORD streamIndex, flags;
LONGLONG llTimeStamp;
hr = pReader->ReadSample(
MF_SOURCE_READER_ANY_STREAM, // Stream index.
0, // Flags.
&streamIndex, // Receives the actual stream index.
&flags, // Receives status flags.
&llTimeStamp, // Receives the time stamp.
&pSample // Receives the sample or NULL.
);
if (FAILED(hr))
{
break;
}
wprintf(L"Stream %d (%I64d)\n", streamIndex, llTimeStamp);
if (flags & MF_SOURCE_READERF_ENDOFSTREAM)
{
wprintf(L"\tEnd of stream\n");
quit = true;
}
if (flags & MF_SOURCE_READERF_NEWSTREAM)
{
wprintf(L"\tNew stream\n");
}
if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
{
wprintf(L"\tNative type changed\n");
}
if (flags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
{
wprintf(L"\tCurrent type changed\n");
}
if (flags & MF_SOURCE_READERF_STREAMTICK)
{
wprintf(L"\tStream tick\n");
}
if (flags & MF_SOURCE_READERF_NATIVEMEDIATYPECHANGED)
{
// The format changed. Reconfigure the decoder.
hr = ConfigureDecoder(pReader, streamIndex);
if (FAILED(hr))
{
break;
}
}
if (pSample)
{
++cSamples;
}
SafeRelease(&pSample);
}
if (FAILED(hr))
{
wprintf(L"ProcessSamples FAILED, hr = 0x%x\n", hr);
}
else
{
wprintf(L"Processed %d samples\n", cSamples);
}
SafeRelease(&pSample);
return hr;
}
清空數據管線
在數據處理期間,譯碼器或其他轉換可能會緩衝輸入範例。 在下圖中,應用程式會呼叫 ReadSample,並接收顯示時間等於 t1的樣本。 譯碼器會針對 t2 和 t3存放樣本。
在下一次呼叫 ReadSample時,來源讀取器可能會為譯碼器提供 t4,並將 t2 傳回給應用程式。
如果您想要將目前在解碼器中緩衝的所有樣本解碼,而不需將任何新的樣本傳遞至解碼器,請在 dwControlFlags 參數中設定 MF_SOURCE_READER_CONTROLF_DRAIN 旗標,ReadSample。 繼續在迴圈中執行這項作,直到 ReadSample 傳回 NULL 的範例指標為止。 取決於解碼器緩衝樣本的方式,這可能會立即發生,或在多次呼叫 readSample 之後發生。
取得檔案持續時間
若要取得媒體檔案的持續時間,請呼叫 IMFSourceReader::GetPresentationAttribute 方法並要求 MF_PD_DURATION 屬性,如下列程式代碼所示。
HRESULT GetDuration(IMFSourceReader *pReader, LONGLONG *phnsDuration)
{
PROPVARIANT var;
HRESULT hr = pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE,
MF_PD_DURATION, &var);
if (SUCCEEDED(hr))
{
hr = PropVariantToInt64(var, phnsDuration);
PropVariantClear(&var);
}
return hr;
}
此處顯示的函式會取得 100 奈秒單位的持續時間。 將數值除以 10,000,000 以取得以秒為單位的持續時間。
尋求
從本機檔案取得數據的媒體來源,通常可以搜尋檔案中的任意位置。 擷取網路攝影機等裝置通常無法搜尋,因為數據是即時的。 視網路串流通訊協定而定,透過網路串流數據的來源可能能夠搜尋。
若要了解媒體來源是否可以搜尋,請呼叫 IMFSourceReader::GetPresentationAttribute 並要求 MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS 属性,如下列程式代碼所示:
HRESULT GetSourceFlags(IMFSourceReader *pReader, ULONG *pulFlags)
{
ULONG flags = 0;
PROPVARIANT var;
PropVariantInit(&var);
HRESULT hr = pReader->GetPresentationAttribute(
MF_SOURCE_READER_MEDIASOURCE,
MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS,
&var);
if (SUCCEEDED(hr))
{
hr = PropVariantToUInt32(var, &flags);
}
if (SUCCEEDED(hr))
{
*pulFlags = flags;
}
PropVariantClear(&var);
return hr;
}
此函式會從來源取得一組功能旗標。 這些旗標定義於 MFMEDIASOURCE_CHARACTERISTICS 列舉中。 兩個旗標與搜尋相關:
旗 | 描述 |
---|---|
No changes required; the translation remains as provided:
MFMEDIASOURCE_CAN_SEEK |
來源可以搜尋。 |
MFMEDIASOURCE_HAS_SLOW_SEEK |
搜尋可能需要很長的時間才能完成。 例如,來源可能需要先下載整個檔案,才能搜尋。 (來源沒有嚴格的準則可傳回此標誌。) |
下列程式代碼會測試 MFMEDIASOURCE_CAN_SEEK 旗標。
BOOL SourceCanSeek(IMFSourceReader *pReader)
{
BOOL bCanSeek = FALSE;
ULONG flags;
if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
{
bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
}
return bCanSeek;
}
若要搜尋,請呼叫 IMFSourceReader::SetCurrentPosition方法,如下列程式代碼所示。
HRESULT SetPosition(IMFSourceReader *pReader, const LONGLONG& hnsPosition)
{
PROPVARIANT var;
HRESULT hr = InitPropVariantFromInt64(hnsPosition, &var);
if (SUCCEEDED(hr))
{
hr = pReader->SetCurrentPosition(GUID_NULL, var);
PropVariantClear(&var);
}
return hr;
}
第一個參數會提供您用來指定搜尋位置的時間格式。 媒體基礎中的所有媒體來源都必須支援 100 奈秒的單位,以值 GUID_NULL表示。 第二個參數是包含搜尋位置 PROPVARIANT。 對於 100 奈秒的時間單位,資料型別是 LONGLONG。
請注意,並非所有媒體來源都提供畫面精確的搜尋。 搜尋的正確性取決於數個因素,例如主要畫面格間隔、媒體檔案是否包含索引,以及數據是否有常數或可變比特率。 因此,在您搜尋檔案中的位置之後,不保證下一個範例上的時間戳會完全符合要求的位置。 一般而言,實際位置不會晚於要求的位置,因此您可以捨棄樣本,直到到達數據流中所需的點為止。
播放速率
雖然您可以使用來源讀取器來設定播放速率,但這樣做通常並不十分有用,原因如下:
- 即使媒體來源確實如此,來源讀取器也不支援反向播放。
- 應用程式會控制簡報時間,因此應用程式可以實作快速或緩慢的播放,而不需在來源上設定速率。
- 某些媒體來源支援 精簡化 模式,在此模式下,來源會提供較少的樣本,通常只包含關鍵影格。 不過,如果您想要去除非關鍵幀,您可以檢查每個範例是否有 MFSampleExtension_CleanPoint 屬性。
若要使用來源讀取器設定播放速率,請呼叫 IMFSourceReader::GetServiceForStream 方法,從媒體來源取得 IMFRateSupport,並 IMFRateControl 介面。
硬體加速
來源讀取器與 Microsoft DirectX 視訊加速(DXVA)2.0 相容,以進行硬體加速的視訊解碼。 若要搭配來源讀取器使用 DXVA,請執行下列步驟。
- 建立 Microsoft Direct3D 裝置。
- 呼叫 DXVA2CreateDirect3DDeviceManager9 函式來建立 Direct3D 設備管理器。 此函式會取得 IDirect3DDeviceManager9 介面的指標。
- 使用指向 Direct3D 裝置的指標,呼叫 IDirect3DDeviceManager9::ResetDevice 方法。
- 呼叫 MFCreateAttributes 函式來建立屬性存放區。
- 建立來源讀取器。 將屬性存放區傳遞到建立函式的 pAttributes 參數中。
當您提供 Direct3D 裝置時,來源讀取器會配置與 DXVA 視訊處理器 API 相容的影片範例。 您可以使用 DXVA 視訊處理來進行硬體反交錯或視訊混合。 如需詳細資訊,請參閱 DXVA 視訊處理。 此外,如果譯碼器支援 DXVA 2.0,它會使用 Direct3D 裝置來執行硬體加速譯碼。
重要
從 Windows 8 開始,IMFDXGIDeviceManager 可以使用,而不是 IDirect3DDeviceManager9。 針對 Windows 市集應用程式,您必須使用 IMFDXGIDeviceManager 。 如需詳細資訊,請參閱 Direct3D 11 影片 API。
相關主題