使用來源讀取器來處理媒體資料
本主題描述如何使用 來源讀取器 來處理媒體資料。
若要使用來源讀取器,請遵循下列基本步驟:
- 建立來源讀取器的實例。
- 列舉可能的輸出格式。
- 設定每個資料流程的實際輸出格式。
- 處理來源的資料。
本主題的其餘部分會詳細說明這些步驟。
建立來源讀取器
若要建立來源讀取器的實例,請呼叫下列其中一個函式:
函式 | 描述 |
---|---|
MFCreateSourceReaderFromURL |
接受 URL 作為輸入。 此函式會使用 來源解析程式 從 URL 建立媒體來源。 |
MFCreateSourceReaderFromByteStream |
取得位元組資料流程的指標。 此函式也會使用來源解析程式來建立媒體來源。 |
MFCreateSourceReaderFromMediaSource |
取得已建立之媒體來源的指標。 此函式適用于來源解析程式無法建立的媒體來源,例如擷取裝置或自訂媒體來源。 |
一般而言,針對媒體檔案,請使用 MFCreateSourceReaderFromURL。 針對網路攝影機之類的裝置,請使用 MFCreateSourceReaderFromMediaSource。 (如需有關在 Microsoft Media Foundation 中擷取裝置的詳細資訊,請參閱 音訊/視訊擷取.)
每個函式都會採用選擇性 的 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 傳回給應用程式。
如果您想要解碼器中目前緩衝的所有樣本解碼,而不要將任何新的範例傳遞至解碼器,請在ReadSample的dwControlFlags參數中設定MF_SOURCE_READER_CONTROLF_DRAIN旗標。 繼續在迴圈中執行此動作,直到 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 列舉中。 兩個旗標與搜尋有關:
旗標 | 描述 |
---|---|
MFMEDIASOURCE_CAN_SEEK |
來源可以搜尋。 |
MFMEDIASOURCE_HAS_SLOW_SEEK |
搜尋可能需要很長的時間才能完成。 例如,來源可能需要下載整個檔案,才能進行搜尋。 (來源沒有嚴格的準則可傳回此 flag.) |
下列程式碼會測試 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。
相關主題