Médiaadatok feldolgozása a Forrásolvasó használatával

Ez a témakör azt ismerteti, hogyan használható a forrásolvasó médiaadatok feldolgozására.

A Forrásolvasó használatához kövesse az alábbi alapvető lépéseket:

  1. Hozzon létre egy példányt a Forrásolvasóból.
  2. A lehetséges kimeneti formátumok számbavétele.
  3. Adja meg az egyes streamek tényleges kimeneti formátumát.
  4. Adatok feldolgozása a forrásból.

A témakör többi része részletesen ismerteti ezeket a lépéseket.

A forrásolvasó létrehozása

A Forrásolvasó egy példányának létrehozásához hívja meg az alábbi függvények egyikét:

Funkció Leírás
MFCreateSourceReaderFromURL
Bemenetként egy URL-címet vesz fel. Ez a függvény a Source Resolver használatával hoz létre médiaforrást az URL-címből.
MFCreateSourceReaderFromByteStream
Mutatót visz egy bájtfolyamra. Ez a függvény a Forrásfeloldót is használja a médiaforrás létrehozásához.
MFCreateSourceReaderFromMediaSource
Egy már létrehozott médiaforrásra mutat. Ez a függvény olyan médiaforrások esetében hasznos, amelyeket a Forrásfeloldó nem tud létrehozni, mint például rögzítő eszközök vagy egyedi médiaforrások.

 

Médiafájlok esetében általában MFCreateSourceReaderFromURL. Az olyan eszközök esetén, mint a webkamerák, használja a(z) MFCreateSourceReaderFromMediaSource. (A Microsoft Media Foundation eszközök rögzítéséről további információt a Hang- és videó rögzítéscímű témakörben talál.)

Ezek a függvények egy választható IMFAttributes mutatót használnak, amely a forrásolvasó különböző beállításainak beállítására szolgál, a függvények referenciatémakörökben leírtak szerint. Az alapértelmezett viselkedés lekéréséhez állítsa be ezt a paramétert a NULL . Minden függvény egy IMFSourceReader mutatót ad vissza kimeneti paraméterként. A CoInitialize(Ex) és MFStartup függvényt kell meghívnia, mielőtt bármelyik függvényt meghívja.

Az alábbi kód egy URL-címből hozza létre a Forrásolvasót.

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();
    }
}

Kimeneti formátumok számbavétele

Minden médiaforrás rendelkezik legalább egy streamel. Egy videofájl tartalmazhat például egy videostreamet és egy hangstreamet. Az egyes streamek formátumát médiatípussal írják le, amelyet az IMFMediaTypefelülete jelöl. További információ a médiatípusokról: Médiatípusok. A forrásolvasótól kapott adatok formátumának megértéséhez meg kell vizsgálnia a médiatípust.

Kezdetben minden streamnek van egy alapértelmezett formátuma, amelyet a IMFSourceReader::GetCurrentMediaType metódus meghívásával talál:

Az egyes streamekhez a médiaforrás felsorolja az adott stream lehetséges médiatípusait. A típusok száma a forrástól függ. Ha a forrás egy médiafájlt jelöl, általában csak egy típus van streamenként. A webkamerák azonban több különböző formátumban is streamelhetik a videókat. Ebben az esetben az alkalmazás kiválaszthatja, hogy melyik formátumot használja a médiatípusok listájából.

A stream médiatípusainak lekéréséhez hívja meg a IMFSourceReader::GetNativeMediaType metódust. Ez a módszer két indexparamétert használ: a stream indexét és egy indexet a stream médiatípusainak listájába. A streamek összes típusának számbavételéhez növelje a listaindexet a streamindex állandójának megtartása mellett. Ha a listaindex kívül esik a határokon, GetNativeMediaTypeMF_E_NO_MORE_TYPESértéket ad vissza.

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;
}

Az egyes streamek médiatípusainak számbavételéhez növelje a streamindexet. Amikor a folyamindex túllépi a határokat, a GetNativeMediaType eljárás MF_E_INVALIDSTREAMNUMBERértéket ad vissza.

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;
}

Kimeneti formátumok beállítása

A kimeneti formátum módosításához hívja meg a IMFSourceReader::SetCurrentMediaType metódust. Ez a módszer a streamindexet és a médiatípust veszi igénybe:

hr = pReader->SetCurrentMediaType(dwStreamIndex, pMediaType);

Az adathordozó típusa attól függ, hogy be szeretne-e szúrni egy dekódert.

  • Ha közvetlenül a forrásból szeretne adatokat lekérni dekódolás nélkül, használja a GetNativeMediaTypeáltal visszaadott egyik típust.
  • A stream dekódolásához hozzon létre egy új médiatípust, amely leírja a kívánt tömörítetlen formátumot.

A dekóder esetében hozza létre a médiatípust az alábbiak szerint:

  1. Új médiatípus létrehozásához hívja meg MFCreateMediaType.
  2. Állítsa be a MF_MT_MAJOR_TYPE attribútumot hang vagy videó megadásához.
  3. Állítsa be a MF_MT_SUBTYPE attribútumot a dekódolási formátum altípusának megadásához. (Lásd: Hang altípus GUID-ok és Video altípus GUID-ok.)
  4. Hívás IMFSourceReader::SetCurrentMediaType.

A forrásolvasó automatikusan betölti a dekódert. A dekódolt formátum teljes részleteinek lekéréséhez a SetCurrentMediaType hívása után hívja meg az IMFSourceReader::GetCurrentMediaType metódust.

Az alábbi kód az RGB-32-hez készült videostreamet és a PCM hangstreamet konfigurálja.

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;
}

Médiaadatok feldolgozása

Ha médiaadatokat szeretne lekérni a forrásból, hívja meg az IMFSourceReader::ReadSample metódust az alábbi kódban látható módon.

        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.
            );

Az első paraméter annak a streamnek az indexe, amelyhez adatokat szeretne lekérni. Megadhatja akár a MF_SOURCE_READER_ANY_STREAM értéket, hogy bármely streamből lekérje a legközelebbi rendelkezésre álló adatokat. A második paraméter választható jelzőket tartalmaz; ezek listáját lásd a MF_SOURCE_READER_CONTROL_FLAG. A harmadik paraméter megkapja annak a streamnek az indexét, amely ténylegesen létrehozza az adatokat. Erre az információra akkor lesz szüksége, ha az első paramétert MF_SOURCE_READER_ANY_STREAMértékre állítja. A negyedik paraméter állapotjelzőket kap, amelyek különböző eseményeket jeleznek az adatok beolvasása során, például a stream formátumváltozásait. Az állapotjelzők listáját a MF_SOURCE_READER_FLAGcímű témakörben találja.

Ha a médiaforrás képes adatokat előállítani a kért streamhez, a ReadSample utolsó paramétere egy médiamintaobjektum IMFSample felületére mutató mutatót kap. Használja a médiamintát a következőre:

  • Mutasson a médiaadatokra.
  • Lekérheti a bemutató idejét és a minta időtartamát.
  • Lekérheti az átfűzést, a mező dominanciáját és a minta egyéb aspektusait leíró attribútumokat.

A médiaadatok tartalma a stream formátumától függ. Tömörítetlen videostreamek esetén minden médiaminta egyetlen videókeretet tartalmaz. Tömörítetlen hangstreamek esetén minden médiaminta hangkeretek sorozatát tartalmazza.

A ReadSample metódus S_OK adhat vissza, de nem ad vissza médiamintát a pSample paraméterben. Ha például eléri a fájl végét, a ReadSample beállítja a MF_SOURCE_READERF_ENDOFSTREAM jelzőt a dwFlags-ben, és a pSample a NULLértékre állítja. Ebben az esetben a ReadSample metódus S_OK ad vissza, mert nem történt hiba, annak ellenére, hogy a pSample paraméter értéke null . Ezért mindig ellenőrizze pSample értékét, mielőtt dereferálja.

Az alábbi kód bemutatja, hogyan hívhatja meg ReadSample egy hurokban, és ellenőrizheti a metódus által visszaadott információkat, amíg el nem éri a médiafájl végét.

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;
}

Az adatfolyam ürítése

Az adatfeldolgozás során a dekóder vagy más átalakítás pufferelheti a bemeneti mintákat. Az alábbi ábrán az alkalmazás meghívja ReadSample, és egy t1egyenlő bemutatóidővel rendelkező mintát fogad. A dekóder t2 és t3mintákat tartja.

egy dekóderben lévő pufferelést bemutató ábrát.

A ReadSamplekövetkező hívásában a forrásolvasó t4 adhat a dekódernek, és t2 adhat vissza az alkalmazásnak.

Ha dekódolni szeretné a dekóderben jelenleg pufferelt összes mintát anélkül, hogy új mintákat adna át a dekódernek, állítsa be a MF_SOURCE_READER_CONTROLF_DRAIN jelölőt a dwControlFlags paraméterben ReadSample. Folytassa ezt egy ciklusban, amíg ReadSample vissza nem ad egy NULL mintamutatót. Attól függően, hogy a dekóder hogyan puffereli a mintákat, ez azonnal vagy több ReadSamplehívás után fordulhat elő.

A fájl időtartamának lekérése

Egy médiafájl időtartamának lekéréséhez hívja meg az IMFSourceReader::GetPresentationAttribute metódust, és kérje le a MF_PD_DURATION attribútumot, ahogyan az az alábbi kódban látható.

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;
}

Az itt látható függvény 100 nanoszekundumos egységekben kapja meg az időtartamot. Oszd el 10 000 000-sel, hogy másodpercek alatt megkapd az időtartamot.

Keresek

A helyi fájlból adatokat lekérő médiaforrás általában tetszőleges helyeket keres a fájlban. Az olyan rögzítőeszközök, mint a webkamerák, általában nem tudnak keresni, mert az adatok valós idejűek. Előfordulhat, hogy egy olyan forrás, amely adatokat streamel egy hálózaton keresztül, képes keresni, a hálózati streamelési protokolltól függően.

Annak megállapításához, hogy egy médiaforrás kereshet-e, hívja meg IMFSourceReader::GetPresentationAttribute, és kérje le a MF_SOURCE_READER_MEDIASOURCE_CHARACTERISTICS attribútumot, ahogy az az alábbi kódban látható:

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;
}

Ez a függvény a forrásból megszerez egy készletnyi kapacitás jelzőt. Ezek a jelzők a MFMEDIASOURCE_CHARACTERISTICS számbavételben vannak definiálva. Két jelző kapcsolódik a kereséshez:

Zászló Leírás
MFMEDIASOURCE_CAN_SEEK
A forrás kereshet.
MFMEDIASOURCE_HAS_SLOW_SEEK
A keresés hosszú időt vehet igénybe. Előfordulhat például, hogy a forrásnak le kell töltenie a teljes fájlt, mielőtt rákeresne. (A forrásnak nincs szigorú feltétele a jelző visszaadásához.)

 

Az alábbi kód teszteli a MFMEDIASOURCE_CAN_SEEK jelzőt.

BOOL SourceCanSeek(IMFSourceReader *pReader)
{
    BOOL bCanSeek = FALSE;
    ULONG flags;
    if (SUCCEEDED(GetSourceFlags(pReader, &flags)))
    {
        bCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
    }
    return bCanSeek;
}

A kereséshez hívja meg a IMFSourceReader::SetCurrentPosition metódust az alábbi kódban látható módon.

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;
}

Az első paraméter megadja a keresési pozíció megadásához használt időformátumot. A Media Foundation összes médiaforrásának támogatnia kell a 100 nanoszekundumos egységeket, amelyeket a GUID_NULLérték jelez. A második paraméter egy PROPVARIANT, amely a keresési pozíciót tartalmazza. A 100 nanoszekundumos időegységek esetében az adattípus LONGLONG.

Ne feledje, hogy nem minden médiaforrás biztosít keretpontosságú keresést. A keresés pontossága számos tényezőtől függ, például a kulcskeret időközétől, attól, hogy a médiafájl tartalmaz-e indexet, és hogy az adatok állandó vagy változó bitsebességűek-e. Ezért ha egy fájlban keres pozíciót, nincs garancia arra, hogy a következő mintán szereplő időbélyeg pontosan megfelel a kért pozíciónak. A tényleges pozíció általában nem lesz későbbi, mint a kért pozíció, így elvetheti a mintákat, amíg el nem éri a kívánt pontot a streamben.

Lejátszási sebesség

Bár a forrásolvasóval beállíthatja a lejátszási sebességet, a művelet általában nem túl hasznos, az alábbi okok miatt:

  • A Forrásolvasó nem támogatja a fordított lejátszást, még akkor sem, ha a médiaforrás igen.
  • Az alkalmazás szabályozza a bemutatók idejét, így az alkalmazás gyors vagy lassú lejátszást valósíthat meg anélkül, hogy beállítanák a forrás sebességét.
  • Egyes médiaforrások támogatják ritkítási módot, ahol a forrás kevesebb mintát biztosít – jellemzően csak a kulcskockákat. Ha azonban nem kulcsos kereteket szeretne elvetni, az egyes mintákban ellenőrizheti a MFSampleExtension_CleanPoint attribútumot.

A forrásolvasóval történő lejátszási sebesség beállításához hívja meg a IMFSourceReader::GetServiceForStream metódust, hogy megkapja a médiaforrásból a IMFRateSupport és a IMFRateControl interfészeket.

Hardveres gyorsítás

A forrásolvasó kompatibilis a Microsoft DirectX Video Acceleration (DXVA) 2.0-val a hardveres gyorsított videokódoláshoz. A DXVA forrásolvasóval való használatához hajtsa végre az alábbi lépéseket.

  1. Microsoft Direct3D-eszköz létrehozása.
  2. Hívja meg a DXVA2CreateDirect3DDeviceManager9 függvényt a Direct3D eszközkezelő létrehozásához. Ez a függvény a IDirect3DDeviceManager9 felületére mutató mutatót kap.
  3. Hívja meg a IDirect3DDeviceManager9::ResetDevice metódust a Direct3D-eszközre mutató mutatóval.
  4. Hozzon létre egy attribútumtárolót az MFCreateAttributes függvény meghívásával.
  5. Hozza létre a Forrásolvasót. Adja át az attribútumtárolót a pAttributes létrehozási függvény paraméterében.

Ha Direct3D-eszközt ad meg, a Forrásolvasó lefoglalja a DXVA videoprocesszor API-val kompatibilis videomintákat. A DXVA videófeldolgozással hardveres deinterlacingot vagy videokeverést végezhet. További információért tekintse meg a DXVAvideofeldolgozást. Ha a dekóder támogatja a DXVA 2.0-t, akkor a Direct3D-eszközt fogja használni a hardveresen gyorsított dekódoláshoz.

Fontos

Windows 8-tól kezdve IMFDXGIDeviceManager használható az IDirect3DDeviceManager9helyett. Windows Áruházbeli alkalmazások esetén IMFDXGIDeviceManagerkell használnia. További információért lásd a Direct3D 11 Videó API-kat.

 

Forrásolvasó