Sdílet prostřednictvím


Případová studie: Zdroj médií MPEG-1

Ve službě Microsoft Media Foundation se objekt, který do datového kanálu zavádí mediální data, nazývá zdroj médií. Toto téma podrobně popisuje ukázku MPEG-1 Media Source SDK.

Požadavky

Než si přečtete toto téma, měli byste porozumět následujícím konceptům Media Foundation:

Měli byste také mít základní znalosti o architektuře Media Foundation, zejména o roli zdrojů médií v potrubí. (Další informace najdete v tématu zdroje médií.)

Kromě toho si můžete přečíst téma Psaní vlastního zdroje médií, což poskytuje obecnější přehled kroků popsaných zde.

Toto téma nereprodukuje veškerý kód z ukázky sady SDK, protože ukázka je poměrně velká.

Třídy C++ používané ve zdroji MPEG-1

Ukázkový zdroj MPEG-1 je implementován s následujícími třídami C++:

  • MPEG1ByteStreamHandler. Implementuje obslužnou rutinu bajtového streamu pro zdroj médií. Při použití bajtového datového proudu vytvoří obslužná rutina byte-stream instanci zdroje.
  • MPEG1Source. Implementuje zdroj médií.
  • MPEG1Stream. Implementuje objekty datového proudu médií. Zdroj médií vytvoří jeden MPEG1Stream objekt pro každý datový proud zvuku nebo videa v bitovém streamu MPEG-1.
  • Parser. Parsuje bitový stream MPEG-1. Ve většině případů nejsou podrobnosti této třídy relevantní pro rozhraní API Media Foundation.
  • SourceOp, OpQueue: Tyto dvě třídy spravují asynchronní operace ve zdroji médií. (Viz asynchronní operace).

Další různé pomocné třídy jsou popsány dále v tématu.

Byte-Stream Handler

Obslužná rutina bajtového streamu je objekt, který vytvoří zdroj médií. Obslužná rutina byte-stream je vytvořena překladačem zdroje; aplikace nepracují přímo s obslužnou rutinou byte-stream. Zdrojový resolver objeví zpracovatele byte-streamu vyhledáním v registru. Obslužné rutiny jsou registrovány podle přípony názvu souboru nebo MIME typu. Pro zdroj MPEG-1 je obslužná rutina byte-stream registrována pro příponu názvu souboru ".mpg".

Poznámka

Pokud chcete podporovat vlastní schémata adres URL, můžete také napsat obslužnou rutinu schématu . Zdroj MPEG-1 je určený pro místní soubory a Media Foundation již poskytuje obslužný modul schématu pro adresy URL "file://".

 

Obslužná rutina byte-stream implementuje IMFByteStreamHandler rozhraní. Toto rozhraní má dvě nejdůležitější metody, které je potřeba implementovat:

Dvě další metody jsou volitelné a nejsou implementovány v ukázce sady SDK:

  • ZrušitVytvořeníObjektu. Ruší metodu BeginCreateObject. Tato metoda je užitečná pro zdroj sítě, který může mít při spuštění vysokou latenci.
  • GetMaxNumberOfBytesRequiredForResolution. Získá maximální počet bajtů, které obslužná rutina přečte ze zdrojového datového proudu. Tuto metodu implementujte, pokud víte, kolik dat potřebuje obslužná rutina byte-streamu, než může vytvořit zdroj médií. V opačném případě jednoduše vraťte E_NOTIMPL.

Zde je implementace metody BeginCreateObject:

HRESULT MPEG1ByteStreamHandler::BeginCreateObject(
        /* [in] */ IMFByteStream *pByteStream,
        /* [in] */ LPCWSTR pwszURL,
        /* [in] */ DWORD dwFlags,
        /* [in] */ IPropertyStore *pProps,
        /* [out] */ IUnknown **ppIUnknownCancelCookie,  // Can be NULL
        /* [in] */ IMFAsyncCallback *pCallback,
        /* [in] */ IUnknown *punkState                  // Can be NULL
        )
{
    if (pByteStream == NULL)
    {
        return E_POINTER;
    }

    if (pCallback == NULL)
    {
        return E_POINTER;
    }

    if ((dwFlags & MF_RESOLUTION_MEDIASOURCE) == 0)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;

    IMFAsyncResult *pResult = NULL;
    MPEG1Source    *pSource = NULL;

    // Create an instance of the media source.
    hr = MPEG1Source::CreateInstance(&pSource);

    // Create a result object for the caller's async callback.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateAsyncResult(NULL, pCallback, punkState, &pResult);
    }

    // Start opening the source. This is an async operation.
    // When it completes, the source will invoke our callback
    // and then we will invoke the caller's callback.
    if (SUCCEEDED(hr))
    {
        hr = pSource->BeginOpen(pByteStream, this, NULL);
    }

    if (SUCCEEDED(hr))
    {
        if (ppIUnknownCancelCookie)
        {
            *ppIUnknownCancelCookie = NULL;
        }

        m_pSource = pSource;
        m_pSource->AddRef();

        m_pResult = pResult;
        m_pResult->AddRef();
    }

// cleanup
    SafeRelease(&pSource);
    SafeRelease(&pResult);
    return hr;
}

Metoda provádí následující kroky:

  1. Vytvoří novou instanci objektu MPEG1Source.
  2. Vytvoření asynchronního objektu výsledku Tento objekt se použije později k vyvolání metody zpětného volání překladače zdroje.
  3. Volá MPEG1Source::BeginOpen, což je asynchronní metoda definovaná ve třídě MPEG1Source.
  4. Nastaví ppIUnknownCancelCookie na NULL, což informuje volajícího, že CancelObjectCreation není podporováno.

Metoda MPEG1Source::BeginOpen skutečně provádí práci čtení bajtového proudu a inicializaci objektu MPEG1Source. Tato metoda není součástí veřejného rozhraní API. Můžete definovat jakýkoli mechanismus mezi obslužnou rutinou a zdrojem médií, který vyhovuje vašim potřebám. Umístění většiny logiky do zdroje médií udržuje obslužnou rutinu bajtového streamu relativně jednoduchou.

Stručně řečeno, BeginOpen provede následující:

  1. Volá MMFByteStream::GetCapabilities k ověření, že zdrojový bajt stream je čitelný i vyhledatelný.
  2. Volá MMFByteStream::BeginRead, aby se spustil asynchronní vstupně-výstupní požadavek.

Zbytek inicializace probíhá asynchronně. Zdroj médií čte dostatek dat z datového proudu pro parsování hlaviček sekvence MPEG-1. Pak vytvoří popisovač prezentace, což je objekt použitý k popisu zvukových a video streamů v souboru. (Další informace najdete v tématu Popis prezentace.) Po dokončení BeginOpen operace byte-streamový handler zavolá metodu zpětného volání resolveru zdroje. V tomto okamžiku volá zdrojový řešitel IMFByteStreamHandler::EndCreateObject. Metoda EndCreateObject vrátí stav operace.

HRESULT MPEG1ByteStreamHandler::EndCreateObject(
        /* [in] */ IMFAsyncResult *pResult,
        /* [out] */ MF_OBJECT_TYPE *pObjectType,
        /* [out] */ IUnknown **ppObject)
{
    if (pResult == NULL || pObjectType == NULL || ppObject == NULL)
    {
        return E_POINTER;
    }

    HRESULT hr = S_OK;

    *pObjectType = MF_OBJECT_INVALID;
    *ppObject = NULL;

    hr = pResult->GetStatus();

    if (SUCCEEDED(hr))
    {
        *pObjectType = MF_OBJECT_MEDIASOURCE;

        assert(m_pSource != NULL);

        hr = m_pSource->QueryInterface(IID_PPV_ARGS(ppObject));
    }

    SafeRelease(&m_pSource);
    SafeRelease(&m_pResult);
    return hr;
}

Pokud během tohoto procesu dojde k chybě, vyvolá se zpětné volání se stavovým kódem chyby.

Deskriptor prezentace

Popisovač prezentace popisuje obsah souboru MPEG-1, včetně následujících informací:

  • Počet datových proudů.
  • Formát každého datového proudu.
  • Identifikátory datových proudů.
  • Stav výběru každého datového proudu (vybraný nebo nevybraný).

Z hlediska architektury Media Foundation obsahuje popisovač prezentace jeden nebo více popisovačů datových proudů. Každý popisovač datového proudu obsahuje obslužnou rutinu typu média, která se používá k získání nebo nastavení typů médií ve streamu. Media Foundation poskytuje standardní implementace pro popisovač prezentace a popis datového proudu; jsou vhodné pro většinu zdrojů médií.

Pokud chcete vytvořit popisovač prezentace, proveďte následující kroky:

  1. Pro každý datový proud:
    1. Zadejte ID datového proudu a pole možných typů médií. Pokud datový proud podporuje více než jeden typ média, seřadit seznam typů médií podle předvoleb, pokud existuje. (Umístěte nejprve optimální typ a nakonec nejméně optimální typ.)
    2. Zavolejte MFCreateStreamDescriptor pro vytvoření popisovače datového proudu.
    3. Volání MMFStreamDescriptor::GetMediaTypeHandler na nově vytvořeném popisovači streamu.
    4. Zavolejte IMFMediaTypeHandler::SetCurrentMediaType pro nastavení výchozího formátu streamu. Pokud existuje více typů médií, měli byste obecně nastavit první typ v seznamu.
  2. Zavolejte MFCreatePresentationDescriptor a předejte pole ukazatelů na popisovače datového proudu.
  3. Pro každý datový proud volejte IMFPresentationDescriptor::SelectStream nebo DeselectStream pro nastavení výchozího stavu výběru. Pokud existuje více datových proudů stejného typu (zvuk nebo video), měli byste ve výchozím nastavení vybrat jenom jeden datový proud.

Objekt MPEG1Source vytvoří popisovač prezentace ve své InitPresentationDescriptor metodě:

HRESULT MPEG1Source::InitPresentationDescriptor()
{
    HRESULT hr = S_OK;
    DWORD cStreams = 0;

    assert(m_pPresentationDescriptor == NULL);
    assert(m_state == STATE_OPENING);

    if (m_pHeader == NULL)
    {
        return E_FAIL;
    }

    // Get the number of streams, as declared in the MPEG-1 header, skipping
    // any streams with an unsupported format.
    for (DWORD i = 0; i < m_pHeader->cStreams; i++)
    {
        if (IsStreamTypeSupported(m_pHeader->streams[i].type))
        {
            cStreams++;
        }
    }

    // How many streams do we actually have?
    if (cStreams > m_streams.GetCount())
    {
        // Keep reading data until we have seen a packet for each stream.
        return S_OK;
    }

    // We should never create a stream we don't support.
    assert(cStreams == m_streams.GetCount());

    // Ready to create the presentation descriptor.

    // Create an array of IMFStreamDescriptor pointers.
    IMFStreamDescriptor **ppSD =
            new (std::nothrow) IMFStreamDescriptor*[cStreams];

    if (ppSD == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    ZeroMemory(ppSD, cStreams * sizeof(IMFStreamDescriptor*));

    // Fill the array by getting the stream descriptors from the streams.
    for (DWORD i = 0; i < cStreams; i++)
    {
        hr = m_streams[i]->GetStreamDescriptor(&ppSD[i]);
        if (FAILED(hr))
        {
            goto done;
        }
    }

    // Create the presentation descriptor.
    hr = MFCreatePresentationDescriptor(cStreams, ppSD,
        &m_pPresentationDescriptor);

    if (FAILED(hr))
    {
        goto done;
    }

    // Select the first video stream (if any).
    for (DWORD i = 0; i < cStreams; i++)
    {
        GUID majorType = GUID_NULL;

        hr = GetStreamMajorType(ppSD[i], &majorType);
        if (FAILED(hr))
        {
            goto done;
        }

        if (majorType == MFMediaType_Video)
        {
            hr = m_pPresentationDescriptor->SelectStream(i);
            if (FAILED(hr))
            {
                goto done;
            }
            break;
        }
    }

    // Switch state from "opening" to stopped.
    m_state = STATE_STOPPED;

    // Invoke the async callback to complete the BeginOpen operation.
    hr = CompleteOpen(S_OK);

done:
    // clean up:
    if (ppSD)
    {
        for (DWORD i = 0; i < cStreams; i++)
        {
            SafeRelease(&ppSD[i]);
        }
        delete [] ppSD;
    }
    return hr;
}

Aplikace získá popisovač prezentace voláním MMFMediaSource::CreatePresentationDescriptor. Tato metoda vytvoří mělkou kopii popisovače prezentace voláním MMFPresentationDescriptor::Clone. (Kopie obsahuje ukazatele na původní popisovače datových proudů.) Aplikace může pomocí popisovače prezentace nastavit typ média, vybrat datový proud nebo zrušit výběr datového proudu.

Volitelně mohou popisovače prezentace a popisovače datových proudů obsahovat atributy, které poskytují další informace o zdroji. Seznam takových atributů najdete v následujících tématech:

Jeden atribut si zaslouží zvláštní zmínku: Atribut MF_PD_DURATION obsahuje celkovou dobu trvání zdroje. Tento atribut nastavte, pokud znáte dobu trvání předem; Například doba trvání může být zadána v hlavičce souboru v závislosti na formátu souboru. Aplikace může tuto hodnotu zobrazit nebo ji použít k nastavení indikátoru průběhu nebo panelu hledání.

Stavy streamování

Zdroj médií definuje následující stavy:

Stát Popis
Začal Zdroj přijímá a zpracovává ukázkové žádosti.
Pozastaveno Zdroj přijímá ukázkové požadavky, ale nezpracuje je. Požadavky se zařadí do fronty, dokud se zdroj nespusť.
Zastavený. Zdroj odmítne ukázkové žádosti.

 

Začátek

Metoda MMFMediaSource::Start spustí zdroj médií. Přebírá následující parametry:

  • Popisovač prezentace.
  • Časový formát GUID.
  • Počáteční pozice.

Aplikace musí získat popisovač prezentace voláním CreatePresentationDescriptor ve zdroji. Neexistuje žádný definovaný mechanismus pro ověřování popisovače prezentace. Pokud aplikace určuje nesprávný popisovač prezentace, výsledky nebudou definovány.

Časový formát GUID určuje, jak má být interpretována počáteční pozice. Standardní formát je 100 nanosekundových jednotek (ns), které jsou označené GUID_NULL. Každý zdroj médií musí podporovat 100 ns jednotek. Volitelně může zdroj podporovat jiné časové jednotky, jako je číslo rámce nebo kód času. Neexistuje však žádný standardní způsob, jak dotazovat zdroj médií na seznam formátů času, které podporuje.

Počáteční pozice je uvedena jako PROPVARIANT, což umožňuje různé datové typy v závislosti na formátu času. Pro 100-ns je typ PROPVARIANTVT_I8 nebo VT_EMPTY. Pokud VT_I8, PROPVARIANT obsahuje počáteční pozici v jednotkách 100 nanosekund. Hodnota VT_EMPTY má zvláštní význam "začít na aktuální pozici".

Implementujte metodu Start následujícím způsobem:

  1. Ověření parametrů a stavu:
    • Zkontrolujte parametry null.
    • Zkontrolujte identifikátor GUID časového formátu. Pokud je hodnota neplatná, vraťte MF_E_UNSUPPORTED_TIME_FORMAT.
    • Zkontrolujte datový typ PROPVARIANT, který obsahuje počáteční pozici.
    • Ověřte počáteční pozici. Pokud je neplatná, vraťte MF_E_INVALIDREQUEST.
    • Pokud je zdroj vypnutý, vraťte MF_E_SHUTDOWN.
  2. Pokud v kroku 1 nedojde k žádné chybě, zařaďte do fronty asynchronní operaci. Po tomto kroku se všechno děje ve vlákně pracovní fronty.
  3. Pro každý datový proud:
    1. Zkontrolujte, jestli je stream již aktivní z předchozího požadavku Spustit.

    2. Voláním IMFPresentationDescriptor::GetStreamDescriptorByIndex zkontrolujte, zda aplikace vybrala nebo odznačila datový proud.

    3. Pokud je nyní dříve vybraný datový proud odznačený, vyprázdněte všechny nedoručené vzorky pro tento datový proud.

    4. Pokud je stream aktivní, zdroj médií (ne stream) odešle jednu z následujících událostí:

      U obou událostí jsou data události ukazatelem IMFMediaStream pro datový proud.

    5. Pokud se zdroj restartuje z pozastaveného stavu, mohou existovat čekající vzorkové požadavky. Pokud ano, doručte je teď.

    6. Pokud zdroj mění pozici, každý objekt streamu odešle událost MEStreamSeeked. V opačném případě každý datový proud odešle událost MEStreamStarted.

  4. Pokud zdroj hledá novou pozici, zdroj médií odešle událost MESourceSeeked. V opačném případě odešle událost MESourceStarted.

Pokud dojde k chybě kdykoli po kroku 2, zdroj odešle MESourceStarted událost s kódem chyby. Tím se aplikace upozorní, že metoda Start asynchronně selhala.

Následující kód ukazuje kroky 1–2:

HRESULT MPEG1Source::Start(
        IMFPresentationDescriptor* pPresentationDescriptor,
        const GUID* pguidTimeFormat,
        const PROPVARIANT* pvarStartPos
    )
{

    HRESULT hr = S_OK;
    SourceOp *pAsyncOp = NULL;

    // Check parameters.

    // Start position and presentation descriptor cannot be NULL.
    if (pvarStartPos == NULL || pPresentationDescriptor == NULL)
    {
        return E_INVALIDARG;
    }

    // Check the time format.
    if ((pguidTimeFormat != NULL) && (*pguidTimeFormat != GUID_NULL))
    {
        // Unrecognized time format GUID.
        return MF_E_UNSUPPORTED_TIME_FORMAT;
    }

    // Check the data type of the start position.
    if ((pvarStartPos->vt != VT_I8) && (pvarStartPos->vt != VT_EMPTY))
    {
        return MF_E_UNSUPPORTED_TIME_FORMAT;
    }

    EnterCriticalSection(&m_critSec);

    // Check if this is a seek request. This sample does not support seeking.

    if (pvarStartPos->vt == VT_I8)
    {
        // If the current state is STOPPED, then position 0 is valid.
        // Otherwise, the start position must be VT_EMPTY (current position).

        if ((m_state != STATE_STOPPED) || (pvarStartPos->hVal.QuadPart != 0))
        {
            hr = MF_E_INVALIDREQUEST;
            goto done;
        }
    }

    // Fail if the source is shut down.
    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    // Fail if the source was not initialized yet.
    hr = IsInitialized();
    if (FAILED(hr))
    {
        goto done;
    }

    // Perform a basic check on the caller's presentation descriptor.
    hr = ValidatePresentationDescriptor(pPresentationDescriptor);
    if (FAILED(hr))
    {
        goto done;
    }

    // The operation looks OK. Complete the operation asynchronously.
    hr = SourceOp::CreateStartOp(pPresentationDescriptor, &pAsyncOp);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pAsyncOp->SetData(*pvarStartPos);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = QueueOperation(pAsyncOp);

done:
    SafeRelease(&pAsyncOp);
    LeaveCriticalSection(&m_critSec);
    return hr;
}

Zbývající kroky jsou uvedené v následujícím příkladu:

HRESULT MPEG1Source::DoStart(StartOp *pOp)
{
    assert(pOp->Op() == SourceOp::OP_START);

    IMFPresentationDescriptor *pPD = NULL;
    IMFMediaEvent  *pEvent = NULL;

    HRESULT     hr = S_OK;
    LONGLONG    llStartOffset = 0;
    BOOL        bRestartFromCurrentPosition = FALSE;
    BOOL        bSentEvents = FALSE;

    hr = BeginAsyncOp(pOp);

    // Get the presentation descriptor from the SourceOp object.
    // This is the PD that the caller passed into the Start() method.
    // The PD has already been validated.
    if (SUCCEEDED(hr))
    {
        hr = pOp->GetPresentationDescriptor(&pPD);
    }

    // Because this sample does not support seeking, the start
    // position must be 0 (from stopped) or "current position."

    // If the sample supported seeking, we would need to get the
    // start position from the PROPVARIANT data contained in pOp.

    if (SUCCEEDED(hr))
    {
        // Select/deselect streams, based on what the caller set in the PD.
        // This method also sends the MENewStream/MEUpdatedStream events.
        hr = SelectStreams(pPD, pOp->Data());
    }

    if (SUCCEEDED(hr))
    {
        m_state = STATE_STARTED;

        // Queue the "started" event. The event data is the start position.
        hr = m_pEventQueue->QueueEventParamVar(
            MESourceStarted,
            GUID_NULL,
            S_OK,
            &pOp->Data()
            );
    }

    if (FAILED(hr))
    {
        // Failure. Send the error code to the application.

        // Note: It's possible that QueueEvent itself failed, in which case it
        // is likely to fail again. But there is no good way to recover in
        // that case.

        (void)m_pEventQueue->QueueEventParamVar(
            MESourceStarted, GUID_NULL, hr, NULL);
    }

    CompleteAsyncOp(pOp);

    SafeRelease(&pEvent);
    SafeRelease(&pPD);
    return hr;
}

Pauza

Metoda IMFMediaSource::Pause pozastaví zdroj médií. Implementujte tuto metodu následujícím způsobem:

  1. Zařaďte do fronty asynchronní operaci.
  2. Každý aktivní datový proud odešle událost MEStreamPaused.
  3. Zdroj médií odešle událost MESourcePaused.

Během pozastavení zdroj zařazuje požadavky na vzorky do fronty bez jejich zpracování. (Viz Ukázkové požadavky.)

Stop

Metoda MMFMediaSource::Stop zastaví zdroj médií. Implementujte tuto metodu následujícím způsobem:

  1. Zařadit asynchronní operaci do fronty.
  2. Každý aktivní datový proud odešle událost MEStreamStopped.
  3. Vymažte všechny vzorky ve frontě a požadavky na vzorky.
  4. Zdroj médií odešle událost MESourceStopped.

I když je zdroj zastavený, odmítne všechny požadavky na vzorky.

Pokud se zdroj zastaví v době, kdy probíhá vstupně-výstupní požadavek, může se vstupně-výstupní požadavek dokončit, jakmile zdroj přejde do zastaveného stavu. V takovém případě by zdroj měl zahodit výsledek tohoto V/V požadavku.

Ukázkové požadavky

Media Foundation používá model „pull“, ve kterém si kanál vyžádá ukázky ze zdroje médií. Toto se liší od modelu používaného DirectShow, ve kterém zdroje „tlačí“ vzorky.

Pokud chcete požádat o novou ukázku, kanál Media Foundation volá MMFMediaStream::RequestSample. Tato metoda přebírá ukazatel IUnknown, který představuje token objekt. Implementace objektu tokenu je na volajícím; jednoduše poskytuje způsob, jak volajícímu sledovat vzorkové žádosti. Parametr tokenu může být také null.

Za předpokladu, že zdroj používá asynchronní vstupně-výstupní požadavky ke čtení dat, nebude generování vzorků synchronizováno s ukázkovými požadavky. Pokud chcete synchronizovat ukázkové požadavky s generováním vzorku, zdroj médií provede toto:

  1. Požadavkové tokeny se vkládají do fronty.
  2. Při generování vzorků jsou umístěny do druhé fronty.
  3. Zdroj médií dokončí požadavek na vzorek vytažením tokenu žádosti z první fronty a vzorku z druhé fronty.
  4. Zdroj médií odešle událost MEMediaSample. Událost obsahuje ukazatel na ukázku a ukázka obsahuje ukazatel na token.

Následující diagram znázorňuje vztah mezi událostí MEMediaSample, ukázkou a tokenem požadavku.

diagram znázorňující memediasample a ukázkovou frontu směřující na imfsample; imfsample a fronta žádosti směřují na iunknown

Příklad zdroje MPEG-1 implementuje tento proces následujícím způsobem:

  1. Metoda RequestSample vloží požadavek do fronty FIFO.
  2. Když jsou dokončeny vstupně-výstupní požadavky, zdroj médií vytvoří nové vzorky a umístí je do druhé fronty FIFO. (Tato fronta má maximální velikost, aby zdroj nemohl číst příliš daleko dopředu.)
  3. Kdykoli mají obě tyto fronty alespoň jednu položku (jeden požadavek a jeden vzorek), zdroj médií dokončí první požadavek z fronty požadavků odesláním první ukázky z ukázkové fronty.
  4. K doručení ukázky odešle objekt streamu (nikoli zdrojový objekt) událost MEMediaSample.
    • Data události jsou ukazatelem na rozhraní IMFSample vzorku.
    • Pokud požadavek obsahoval token, připojte token k ukázce nastavením atributu MFSampleExtension_Token v ukázce.

V tuto chvíli existují tři možnosti:

  • Ve frontě na vzorky je další vzorek, ale žádná odpovídající žádost.
  • Existuje požadavek, ale žádný vzorek.
  • Obě fronty jsou prázdné; nejsou žádné vzorky ani požadavky.

Pokud je vzorkovací fronta prázdná, zdroj zkontroluje konec streamu (viz Konec streamu). Jinak spustí další vstupně-výstupní požadavek na data. Pokud během tohoto procesu dojde k nějaké chybě, stream odešle událost MEError.

Následující kód implementuje metodu MMFMediaStream::RequestSample:

HRESULT MPEG1Stream::RequestSample(IUnknown* pToken)
{
    HRESULT hr = S_OK;
    IMFMediaSource *pSource = NULL;

    // Hold the media source object's critical section.
    SourceLock lock(m_pSource);

    hr = CheckShutdown();
    if (FAILED(hr))
    {
        goto done;
    }

    if (m_state == STATE_STOPPED)
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    if (!m_bActive)
    {
        // If the stream is not active, it should not get sample requests.
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    if (m_bEOS && m_Samples.IsEmpty())
    {
        // This stream has already reached the end of the stream, and the
        // sample queue is empty.
        hr = MF_E_END_OF_STREAM;
        goto done;
    }

    hr = m_Requests.InsertBack(pToken);
    if (FAILED(hr))
    {
        goto done;
    }

    // Dispatch the request.
    hr = DispatchSamples();
    if (FAILED(hr))
    {
        goto done;
    }

done:
    if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
    {
        // An error occurred. Send an MEError even from the source,
        // unless the source is already shut down.
        hr = m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
    }
    return hr;
}

Metoda DispatchSamples načítá vzorky z fronty vzorků, přiřazuje je nevyřízeným žádostem a řadí do fronty události MEMediaSample .

HRESULT MPEG1Stream::DispatchSamples()
{
    HRESULT hr = S_OK;
    BOOL bNeedData = FALSE;
    BOOL bEOS = FALSE;

    SourceLock lock(m_pSource);

    // An I/O request can complete after the source is paused, stopped, or
    // shut down. Do not deliver samples unless the source is running.
    if (m_state != STATE_STARTED)
    {
        return S_OK;
    }

    IMFSample *pSample = NULL;
    IUnknown  *pToken = NULL;

    // Deliver as many samples as we can.
    while (!m_Samples.IsEmpty() && !m_Requests.IsEmpty())
    {
        // Pull the next sample from the queue.
        hr = m_Samples.RemoveFront(&pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Pull the next request token from the queue. Tokens can be NULL.
        hr = m_Requests.RemoveFront(&pToken);
        if (FAILED(hr))
        {
            goto done;
        }

        if (pToken)
        {
            // Set the token on the sample.
            hr = pSample->SetUnknown(MFSampleExtension_Token, pToken);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Send an MEMediaSample event with the sample.
        hr = m_pEventQueue->QueueEventParamUnk(
            MEMediaSample, GUID_NULL, S_OK, pSample);

        if (FAILED(hr))
        {
            goto done;
        }

        SafeRelease(&pSample);
        SafeRelease(&pToken);
    }

    if (m_Samples.IsEmpty() && m_bEOS)
    {
        // The sample queue is empty AND we have reached the end of the source
        // stream. Notify the pipeline by sending the end-of-stream event.

        hr = m_pEventQueue->QueueEventParamVar(
            MEEndOfStream, GUID_NULL, S_OK, NULL);

        if (FAILED(hr))
        {
            goto done;
        }

        // Notify the source. It will send the end-of-presentation event.
        hr = m_pSource->QueueAsyncOperation(SourceOp::OP_END_OF_STREAM);
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (NeedsData())
    {
        // The sample queue is empty; the request queue is not empty; and we
        // have not reached the end of the stream. Ask for more data.
        hr = m_pSource->QueueAsyncOperation(SourceOp::OP_REQUEST_DATA);
        if (FAILED(hr))
        {
            goto done;
        }
    }

done:
    if (FAILED(hr) && (m_state != STATE_SHUTDOWN))
    {
        // An error occurred. Send an MEError even from the source,
        // unless the source is already shut down.
        m_pSource->QueueEvent(MEError, GUID_NULL, hr, NULL);
    }

    SafeRelease(&pSample);
    SafeRelease(&pToken);
    return S_OK;
}

Metoda DispatchSamples se volá za následujících okolností:

  • Uvnitř metody RequestSample.
  • Když se zdroj médií restartuje z pozastaveného stavu.
  • Po dokončení I/O požadavku.

Konec streamu

Pokud datový proud už žádná data neobsahuje a všechny vzorky daného proudu byly odeslány, objekty proudu odesílají MEEndOfStream událost.

Po dokončení všech aktivních datových proudů odešle zdroj médií událost MEEndOfPresentation.

Asynchronní operace

Možná nejsnadnější součástí psaní zdroje médií je pochopení asynchronního modelu Media Foundation.

Všechny metody ve zdroji médií, které řídí streamování, jsou asynchronní. V každém případě metoda provede počáteční ověření, jako je například kontrola parametrů. Zdroj pak odešle zbytek úkolů do pracovní fronty. Po dokončení operace zdroj médií odešle událost zpět volajícímu prostřednictvím MMFMediaEventGenerator rozhraní zdroje médií. Proto je důležité porozumět pracovním frontám.

Chcete-li umístit položku do pracovní fronty, můžete zavolat na MFPutWorkItem nebo MFPutWorkItemEx. Zdroj MPEG-1 používá MFPutWorkItem, ale dvě funkce dělají totéž. Funkce MFPutWorkItem přebírá následující parametry:

  • Hodnota DWORD, která identifikuje pracovní frontu. Můžete vytvořit soukromou pracovní frontu nebo použít MFASYNC_CALLBACK_QUEUE_STANDARD.
  • Ukazatel na rozhraní IMFAsyncCallback. Toto rozhraní callbacku se vyvolává k provedení práce.
  • Volitelný stavový objekt, který musí implementovat IUnknown.

Pracovní fronta je obsluhována jedním nebo více pracovními vlákny, které nepřetržitě vybírají další pracovní úlohu z fronty a vyvolávají IMFAsyncCallback::Invoke metodu rozhraní zpětného volání.

U pracovních položek není zaručeno, že budou provedeny ve stejném pořadí, v jakém jste je umístili do fronty. Mějte na paměti, že více než jedno vlákno může obsluhovat stejnou pracovní frontu, takže volání Invoke se mohou překrývat nebo probíhat mimo pořadí. Proto je na mediálním zdroji udržovat správný vnitřní stav tím, že odesílá položky pracovní fronty ve správném pořadí. Teprve po dokončení předchozí operace spustí zdroj další operaci.

Pro reprezentaci čekajících operací zdroj MPEG-1 definuje třídu s názvem SourceOp:

// Represents a request for an asynchronous operation.

class SourceOp : public IUnknown
{
public:

    enum Operation
    {
        OP_START,
        OP_PAUSE,
        OP_STOP,
        OP_REQUEST_DATA,
        OP_END_OF_STREAM
    };

    static HRESULT CreateOp(Operation op, SourceOp **ppOp);
    static HRESULT CreateStartOp(IMFPresentationDescriptor *pPD, SourceOp **ppOp);

    // IUnknown
    STDMETHODIMP QueryInterface(REFIID iid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    SourceOp(Operation op);
    virtual ~SourceOp();

    HRESULT SetData(const PROPVARIANT& var);

    Operation Op() const { return m_op; }
    const PROPVARIANT& Data() { return m_data;}

protected:
    long        m_cRef;     // Reference count.
    Operation   m_op;
    PROPVARIANT m_data;     // Data for the operation.
};

Výčet Operation určuje, která operace čeká na vyřízení. Třída také obsahuje PROPVARIANT ke sdělení jakýchkoli dalších dat pro operaci.

Fronta operací

K serializaci operací udržuje zdroj médií frontu SourceOp objektů. Ke správě fronty používá pomocnou třídu:

template <class OP_TYPE>
class OpQueue : public IUnknown
{
public:

    typedef ComPtrList<OP_TYPE>   OpList;

    HRESULT QueueOperation(OP_TYPE *pOp);

protected:

    HRESULT ProcessQueue();
    HRESULT ProcessQueueAsync(IMFAsyncResult *pResult);

    virtual HRESULT DispatchOperation(OP_TYPE *pOp) = 0;
    virtual HRESULT ValidateOperation(OP_TYPE *pOp) = 0;

    OpQueue(CRITICAL_SECTION& critsec)
        : m_OnProcessQueue(this, &OpQueue::ProcessQueueAsync),
          m_critsec(critsec)
    {
    }

    virtual ~OpQueue()
    {
    }

protected:
    OpList                  m_OpQueue;         // Queue of operations.
    CRITICAL_SECTION&       m_critsec;         // Protects the queue state.
    AsyncCallback<OpQueue>  m_OnProcessQueue;  // ProcessQueueAsync callback.
};

Třída OpQueue je navržena tak, aby byla zděděna komponentou, která provádí asynchronní pracovní položky. Parametr šablony OP_TYPE je typ objektu, který slouží k reprezentaci pracovních položek ve frontě – v tomto případě OP_TYPE bude SourceOp. Třída OpQueue implementuje následující metody:

  • QueueOperation vloží novou položku do fronty.
  • ProcessQueue odešle další operaci z fronty. Tato metoda je asynchronní.
  • ProcessQueueAsync dokončí asynchronní metodu ProcessQueue.

Další dvě metody musí být implementovány odvozenou třídou:

  • ValidateOperation zkontroluje, jestli je platná provést zadanou operaci vzhledem k aktuálnímu stavu zdroje médií.
  • DispatchOperation provede asynchronní pracovní položku.

Fronta operací se používá takto:

  1. Kanál Media Foundation volá asynchronní metodu zdroje médií, například MMFMediaSource::Start.
  2. Asynchronní metoda volá QueueOperation, který umístí Start operace do fronty a poté volá ProcessQueue (ve formě objektu SourceOp).
  3. ProcessQueue volání MFPutWorkItem.
  4. Vlákno pracovní fronty volá ProcessQueueAsync.
  5. Metoda ProcessQueueAsync volá ValidateOperation a DispatchOperation.

Následující kód zařadí novou operaci do fronty ve zdroji MPEG-1:

template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::QueueOperation(OP_TYPE *pOp)
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_critsec);

    hr = m_OpQueue.InsertBack(pOp);
    if (SUCCEEDED(hr))
    {
        hr = ProcessQueue();
    }

    LeaveCriticalSection(&m_critsec);
    return hr;
}

Následující kód zpracovává frontu:

template <class OP_TYPE>
HRESULT OpQueue<OP_TYPE>::ProcessQueue()
{
    HRESULT hr = S_OK;
    if (m_OpQueue.GetCount() > 0)
    {
        hr = MFPutWorkItem(
            MFASYNC_CALLBACK_QUEUE_STANDARD,    // Use the standard work queue.
            &m_OnProcessQueue,                  // Callback method.
            NULL                                // State object.
            );
    }
    return hr;
}

Metoda ValidateOperation zkontroluje, zda zdroj MPEG-1 může odeslat další operaci ve frontě. Pokud probíhá jiná operace, ValidateOperation vrátí MF_E_NOTACCEPTING. Tím se zajistí, že se DispatchOperation nevolá, zatímco probíhá jiná operace.

HRESULT MPEG1Source::ValidateOperation(SourceOp *pOp)
{
    if (m_pCurrentOp != NULL)
    {
        return MF_E_NOTACCEPTING;
    }
    return S_OK;
}

Metoda DispatchOperation se přepne na typ operace:

//-------------------------------------------------------------------
// DispatchOperation
//
// Performs the asynchronous operation indicated by pOp.
//
// NOTE:
// This method implements the pure-virtual OpQueue::DispatchOperation
// method. It is always called from a work-queue thread.
//-------------------------------------------------------------------

HRESULT MPEG1Source::DispatchOperation(SourceOp *pOp)
{
    EnterCriticalSection(&m_critSec);

    HRESULT hr = S_OK;

    if (m_state == STATE_SHUTDOWN)
    {
        LeaveCriticalSection(&m_critSec);

        return S_OK; // Already shut down, ignore the request.
    }

    switch (pOp->Op())
    {

    // IMFMediaSource methods:

    case SourceOp::OP_START:
        hr = DoStart((StartOp*)pOp);
        break;

    case SourceOp::OP_STOP:
        hr = DoStop(pOp);
        break;

    case SourceOp::OP_PAUSE:
        hr = DoPause(pOp);
        break;

    // Operations requested by the streams:

    case SourceOp::OP_REQUEST_DATA:
        hr = OnStreamRequestSample(pOp);
        break;

    case SourceOp::OP_END_OF_STREAM:
        hr = OnEndOfStream(pOp);
        break;

    default:
        hr = E_UNEXPECTED;
    }

    if (FAILED(hr))
    {
        StreamingError(hr);
    }

    LeaveCriticalSection(&m_critSec);
    return hr;
}

Shrnutí:

  1. Kanál volá asynchronní metodu, například IMFMediaSource::Start.
  2. Metoda volá OpQueue::QueueOperationasynchronně a předává ukazatel na objekt SourceOp.
  3. Metoda QueueOperation umístí operaci do fronty m_OpQueue a zavolá OpQueue::ProcessQueue.
  4. Metoda ProcessQueue volá funkci MFPutWorkItem. Od tohoto okamžiku se všechno děje ve vlákně pracovní fronty Media Foundation. Asynchronní metoda se vrací volajícímu.
  5. Vlákno pracovní fronty volá metodu OpQueue::ProcessQueueAsync.
  6. Metoda ProcessQueueAsync volá MPEG1Source:ValidateOperation k ověření operace.
  7. Metoda ProcessQueueAsync volá MPEG1Source::DispatchOperation ke zpracování operace.

Tento návrh má několik výhod:

  • Metody jsou asynchronní, takže neblokují vlákno volající aplikace.
  • Operace se odesílají ve vlákně fronty práce Media Foundation, které je sdíleno mezi komponenty kanálu. Zdroj médií proto nevytvoří vlastní vlákno, čímž se sníží celkový počet vytvořených vláken.
  • Zdroj médií neblokuje při čekání na dokončení operací. Tím se snižuje pravděpodobnost, že zdroj médií omylem způsobí zablokování, a pomáhá omezit přepínání kontextu.
  • Zdroj médií může ke čtení zdrojového souboru použít asynchronní vstupně-výstupní operace (voláním MMFByteStream::BeginRead). Zdroj médií nemusí blokovat při čekání na dokončení vstupně-výstupní rutiny.

Pokud postupujete podle vzoru zobrazeného v ukázce sady SDK, můžete se zaměřit na konkrétní podrobnosti o zdroji médií.

zdroje médií

Psaní vlastního zdroje médií