共用方式為


使用來源讀取器來處理媒體數據

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

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

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

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

建立來源讀取器

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

功能 描述
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 傳回的其中一種類型
  • 若要譯碼數據流,請建立描述所需未壓縮格式的新媒體類型。

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

  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 參數 中的媒體範例。 例如,當您到達檔案結尾時,ReadSampledwFlags 中設定 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 傳回給應用程式。

如果您想要將目前在解碼器中緩衝的所有樣本解碼,而不需將任何新的樣本傳遞至解碼器,請在 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,請執行下列步驟。

  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

 

源讀取器