使用來源讀取器來處理媒體資料

本主題描述如何使用 來源讀取器 來處理媒體資料。

若要使用來源讀取器,請遵循下列基本步驟:

  1. 建立來源讀取器的實例。
  2. 列舉可能的輸出格式。
  3. 設定每個資料流程的實際輸出格式。
  4. 處理來源的資料。

本主題的其餘部分會詳細說明這些步驟。

建立來源讀取器

若要建立來源讀取器的實例,請呼叫下列其中一個函式:

函式 描述
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傳回的其中一種類型。
  • 若要解碼資料流程,請建立描述所需未壓縮格式的新媒體類型。

在解碼器的情況下,建立媒體類型,如下所示:

  1. 呼叫 MFCreateMediaType 以建立新的媒體類型。
  2. 設定 MF_MT_MAJOR_TYPE 屬性以指定音訊或視訊。
  3. 設定 MF_MT_SUBTYPE 屬性以指定解碼格式的子類型。 (請參閱 音訊子類型 GUID視訊子類型 GUID.)
  4. 呼叫 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的範例。 解碼器會保存 t2t3的樣本。

顯示解碼器中緩衝處理的圖例。

在下一次呼叫 ReadSample時,來源讀取器可能會提供 t4 給解碼器,並將 t2 傳回給應用程式。

如果您想要解碼器中目前緩衝的所有樣本解碼,而不要將任何新的範例傳遞至解碼器,請在ReadSampledwControlFlags參數中設定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 方法,以從媒體來源取得 IMFRateSupportIMFRateControl 介面。

硬體加速

來源讀取器與 Microsoft DirectX 影片加速相容, (DXVA) 2.0 進行硬體加速視訊解碼。 若要搭配來源讀取器使用 DXVA,請執行下列步驟。

  1. 建立 Microsoft Direct3D 裝置。
  2. 呼叫 DXVA2CreateDirect3DDeviceManager9 函式來建立 Direct3D 裝置管理員。 此函式會取得 IDirect3DDeviceManager9 介面的指標。
  3. 使用 Direct3D 裝置的指標呼叫 IDirect3DDeviceManager9::ResetDevice 方法。
  4. 呼叫 MFCreateAttributes 函式來建立屬性存放區。
  5. 建立來源讀取器。 在建立函式的 pAttributes 參數中傳遞屬性存放區。

當您提供 Direct3D 裝置時,來源讀取器會配置與 DXVA 視訊處理器 API 相容的影片範例。 您可以使用 DXVA 視訊處理來執行硬體反交錯或視訊混合。 如需詳細資訊,請參閱 DXVA 視訊處理。 此外,如果解碼器支援 DXVA 2.0,則會使用 Direct3D 裝置來執行硬體加速解碼。

重要

從Windows 8開始,可以使用IMFDXGIDeviceManager,而不是IDirect3DDeviceManager9。 針對 Windows 市集應用程式,您必須使用 IMFDXGIDeviceManager。 如需詳細資訊,請參閱 Direct3D 11 影片 API

 

來源讀取器