Delen via


Case Study: MPEG-1-mediabron

In Microsoft Media Foundation wordt het object dat mediagegevens in de gegevenspijplijn introduceert een mediabrongenoemd. In dit onderwerp wordt uitgebreid gekeken naar het MPEG-1 Media Source SDK-voorbeeld.

Voorwaarden

Voordat u dit onderwerp leest, moet u de volgende Media Foundation-concepten begrijpen:

U moet ook basiskennis hebben van de Media Foundation-architectuur, met name de rol van mediabronnen in de pijplijn. (Zie Mediabronnenvoor meer informatie.)

Daarnaast kunt u het onderwerp lezen Een aangepaste mediabron schrijven, wat een algemener overzicht geeft van de stappen die hier worden beschreven.

In dit onderwerp worden niet alle code uit het SDK-voorbeeld gereproduceerd, omdat het voorbeeld redelijk groot is.

C++-klassen die worden gebruikt in de MPEG-1-bron

De MPEG-1-voorbeeldbron wordt geïmplementeerd met de volgende C++-klassen:

  • MPEG1ByteStreamHandler. Implementeert de byte-stream-handler voor de mediabron. Met behulp van een bytestream maakt de byte-stream-handler een exemplaar van de bron.
  • MPEG1Source. Implementeert de mediabron.
  • MPEG1Stream. Hiermee worden de mediastroomobjecten geïmplementeerd. De mediabron maakt één MPEG1Stream object voor elke audio- of videostream in de MPEG-1-bitstream.
  • Parser. Parseert de MPEG-1-bitstream. Voor het grootste deel zijn de details van deze klasse niet relevant voor de Media Foundation-API's.
  • SourceOp, OpQueue: deze twee klassen beheren asynchrone bewerkingen in de mediabron. (Zie Asynchrone bewerkingen).

Andere diverse helperklassen worden verderop in het onderwerp beschreven.

Byte-Stream handler

De bytestream handler is het object waarmee de mediabron wordt gemaakt. De byte-stream-handler wordt gemaakt door de bron-resolver; toepassingen communiceren niet rechtstreeks met de byte-stream-handler. De bron-resolver detecteert de byte-stream-handler door in het register te zoeken. Handlers worden geregistreerd op basis van bestandsnaamextensie of MIME-type. Voor de MPEG-1-bron is de byte-stream-handler geregistreerd voor de bestandsnaamextensie '.mpg'.

Notitie

Als u aangepaste URL-schema's wilt ondersteunen, kunt u ook een schemahandlerschrijven. De MPEG-1-bron is ontworpen voor lokale bestanden en Media Foundation biedt al een schemahandler voor URL's voor 'file://'.

 

De byte-stream-handler implementeert de IMFByteStreamHandler interface. Deze interface heeft twee belangrijkste methoden die moeten worden geïmplementeerd:

Twee andere methoden zijn optioneel en niet geïmplementeerd in het SDK-voorbeeld:

  • CancelObjectCreation. Annuleert de methode BeginCreateObject. Deze methode is handig voor een netwerkbron die een hoge latentie kan hebben bij het opstarten.
  • GetMaxNumberOfBytesRequiredForResolution. Hiermee haalt u het maximum aantal bytes op dat de handler uit de bronstroom leest. Implementeer deze methode als u weet hoeveel gegevens de byte-stream-handler moet bevatten voordat de mediabron kan worden gemaakt. Anders kunt u gewoon E_NOTIMPLretourneren.

Hier volgt de implementatie van de methode 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;
}

De methode voert de volgende stappen uit:

  1. Hiermee maakt u een nieuw exemplaar van het MPEG1Source-object.
  2. Maak een asynchroon resultaatobject. Dit object wordt later gebruikt om de callbackmethode van de bron-resolver aan te roepen.
  3. Roept MPEG1Source::BeginOpenaan, een asynchrone methode die is gedefinieerd in de klasse MPEG1Source.
  4. Hiermee stelt u ppIUnknownCancelCookie- in op NULL-, waarmee de beller wordt geïnformeerd dat CancelObjectCreation- niet wordt ondersteund.

De methode MPEG1Source::BeginOpen voert het werkelijke werk uit van het lezen van de bytestroom en het initialiseren van het MPEG1Source-object. Deze methode maakt geen deel uit van de openbare API. U kunt elk mechanisme definiëren tussen de handler en de mediabron die aan uw behoeften voldoet. Door de meeste logica in de mediabron te plaatsen, blijft de bytestream-handler relatief eenvoudig.

Kortom, BeginOpen doet het volgende:

  1. Roept IMFByteStream::GetCapabilities om te controleren of de bron-bytestream zowel leesbaar als zoekbaar is.
  2. Roept IMFByteStream::BeginRead aan om een asynchrone I/O-aanvraag te starten.

De rest van de initialisatie vindt asynchroon plaats. De mediabron leest voldoende gegevens uit de stream om de MPEG-1-reeksheaders te parseren. Vervolgens wordt er een presentatiedescriptorgemaakt. Dit is het object dat wordt gebruikt om de audio- en videostreams in het bestand te beschrijven. (Zie Presentatiedescriptorvoor meer informatie.) Wanneer de BeginOpen bewerking is voltooid, roept de byte-stream-handler de callback-methode van de bron-resolver aan. Op dat moment roept de bron-resolver IMFByteStreamHandler::EndCreateObjectaan. De methode EndCreateObject retourneert de status van de bewerking.

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

Als er tijdens dit proces een fout optreedt, wordt de callback aangeroepen met een foutcode.

Presentatiedescriptor

De presentatiedescriptor beschrijft de inhoud van het MPEG-1-bestand, inclusief de volgende informatie:

  • Het aantal streams.
  • De indeling van elke stream.
  • Stroomidentificaties.
  • De selectiestatus van elke stroom (geselecteerd of gedeselecteerd).

Wat betreft de Media Foundation-architectuur bevat de presentatiedescriptor een of meer stroomdescriptoren. Elke stroomdescriptor bevat een mediatypehandler, die wordt gebruikt om mediatypen op de stream op te halen of in te stellen. Media Foundation biedt stock-implementaties voor de presentatiedescriptor en streamdescriptor; deze zijn geschikt voor de meeste mediabronnen.

Voer de volgende stappen uit om een presentatiedescriptor te maken:

  1. Voor elke stream:
    1. Geef een stream-id en een matrix van mogelijke mediatypen op. Als de stream meer dan één mediatype ondersteunt, rangschikt u de lijst met mediatypen op voorkeur, indien van toepassing. (Plaats eerst het optimale type en het minst optimale type als laatste.)
    2. Roep MFCreateStreamDescriptor aan om de stroomdescriptor te maken.
    3. Roep IMFStreamDescriptor::GetMediaTypeHandler aan op de zojuist gemaakte streambeschrijver.
    4. Roep IMFMediaTypeHandler::SetCurrentMediaType om de standaardindeling voor de stream in te stellen. Als er meer dan één mediatype is, moet u over het algemeen het eerste type in de lijst instellen.
  2. Roep MFCreatePresentationDescriptor aan en geef de matrix van stroomdescriptoraanwijzers door.
  3. Roep voor elke stream IMFPresentationDescriptor aan::SelectStream of DeselectStream om de standaardselectiestatus in te stellen. Als er meer dan één stream van hetzelfde type (audio of video) is, moet er standaard slechts één stroom worden geselecteerd.

Het MPEG1Source-object maakt de presentatiedescriptor in de InitPresentationDescriptor methode:

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

De toepassing haalt de presentatiedescriptor op door IMFMediaSource::CreatePresentationDescriptoraan te roepen. Met deze methode maakt u een ondiepe kopie van de presentatiedescriptor door IMFPresentationDescriptor::Cloneaan te roepen. (De kopie bevat aanwijzers naar de oorspronkelijke stroomdescriptors.) De toepassing kan de presentatiedescriptor gebruiken om het mediatype in te stellen, een stream te selecteren of de selectie van een stream op te heffen.

Optioneel kunnen presentatiedescriptors en stroomdescriptors kenmerken bevatten die aanvullende informatie geven over de bron. Zie de volgende onderwerpen voor een lijst met dergelijke kenmerken:

Eén kenmerk verdient een speciale vermelding: het kenmerk MF_PD_DURATION bevat de totale duur van de bron. Stel dit kenmerk in als u de duur vooraf kent; De duur kan bijvoorbeeld worden opgegeven in de bestandsheaders, afhankelijk van de bestandsindeling. De toepassing kan deze waarde weergeven of deze gebruiken om een voortgangsbalk of zoekbalk in te stellen.

Streamingtoestanden

Een mediabron definieert de volgende statussen:

Staat Beschrijving
Gestart De bron accepteert voorbeeldaanvragen en verwerkt deze.
Onderbroken De bron accepteert voorbeeldaanvragen, maar verwerkt ze niet. Aanvragen worden in de wachtrij geplaatst totdat de bron is gestart.
Gestopt. De bron weigert voorbeeldaanvragen.

 

Beginnen

De methode IMFMediaSource::Start start de mediabron. Hiervoor worden de volgende parameters gebruikt:

  • Een presentatiedescriptor.
  • Een tijdformaat-GUID.
  • Een beginpositie.

De toepassing moet de presentatiedescriptor verkrijgen door CreatePresentationDescriptor aan te roepen op de bron. Er is geen gedefinieerd mechanisme voor het valideren van een presentatiedescriptor. Als de toepassing de verkeerde presentatiedescriptor opgeeft, zijn de resultaten niet gedefinieerd.

De GUID met de tijdnotatie geeft aan hoe de beginpositie moet worden geïnterpreteerd. De standaardindeling is 100 nanoseconden (ns) eenheden, aangegeven door GUID_NULL. Elke mediabron moet 100-nanoseconden-eenheden ondersteunen. Een bron kan eventueel andere tijdseenheden ondersteunen, zoals framenummer of tijdcode. Er is echter geen standaardmethode om een query uit te voeren op een mediabron voor de lijst met tijdnotaties die worden ondersteund.

De beginpositie wordt gegeven als een PROPVARIANT, waardoor verschillende gegevenstypen kunnen worden gebruikt, afhankelijk van de tijdnotatie. Voor 100 ns is het PROPVARIANT- type VT_I8 of VT_EMPTY. Als VT_I8is, bevat de PROPVARIANT de beginpositie in eenheden van 100 ns. De waarde VT_EMPTY heeft de speciale betekenis 'beginnen bij de huidige positie'.

Implementeer de methode Start als volgt:

  1. Parameters en status valideren:
    • Controleer op null- parameters.
    • Controleer de tijdnotatie-GUID. Als de waarde ongeldig is, geeft u MF_E_UNSUPPORTED_TIME_FORMATals resultaat.
    • Controleer het gegevenstype van de PROPVARIANT dat de beginpositie bevat.
    • Valideer de beginpositie. Als dit ongeldig is, retourneer dan MF_E_INVALIDREQUEST.
    • Als de bron is afgesloten, retourneer MF_E_SHUTDOWN.
  2. Als er geen fout optreedt in stap 1, kun je een asynchrone bewerking in de wachtrij plaatsen. Alles na deze stap vindt plaats in een werkwachtrij-thread.
  3. Voor elke stream:
    1. Controleer of de stream al actief is vanaf een eerdere Start aanvraag.

    2. Roep IMFPresentationDescriptor::GetStreamDescriptorByIndex aan om te controleren of de toepassing de stream heeft geselecteerd of gedeselecteerd.

    3. Als een eerder geselecteerde stream nu is gedeselecteerd, leegt u alle niet-bezorgde voorbeelden voor die stream.

    4. Als de stream actief is, verzendt de mediabron (niet de stream) een van de volgende gebeurtenissen:

      Voor beide gebeurtenissen zijn de gebeurtenisgegevens de IMFMediaStream aanwijzer voor de stream.

    5. Als de bron opnieuw wordt opgestart vanuit de onderbroken status, kunnen er voorbeeldaanvragen in behandeling zijn. Zo ja, dan kunt u deze nu leveren.

    6. Als de bron een nieuwe positie zoekt, verzendt elk streamobject een MEStreamSeeked-gebeurtenis. Anders zendt elke stream een MEStreamStarted evenement.

  4. Wanneer de bron op zoek is naar een nieuwe positie, verzendt de mediabron een MESourceSeeked-gebeurtenis. Anders wordt een MESourceStarted gebeurtenis verzonden.

Als er op elk moment na stap 2 een fout optreedt, verzendt de bron een MESourceStarted gebeurtenis met een foutcode. Hiermee wordt de toepassing gewaarschuwd dat de startmethode asynchroon is mislukt.

In de volgende code ziet u stap 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;
}

De resterende stappen worden weergegeven in het volgende voorbeeld:

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

Pauze

De methode IMFMediaSource::P ause onderbreekt de mediabron. Implementeer deze methode als volgt:

  1. Een asynchrone bewerking in de wachtrij plaatsen.
  2. Elke actieve stream verzendt een MEStreamPaused gebeurtenis.
  3. De mediabron verzendt een MESourcePaused gebeurtenis.

Wanneer hij is gepauzeerd, zet de bron voorbeeldaanvragen in de wachtrij zonder ze te verwerken. (Zie voorbeeldaanvragen.)

Stoppen

De methode IMFMediaSource::Stop stopt de mediabron. Implementeer deze methode als volgt:

  1. Een asynchrone bewerking in de wachtrij plaatsen.
  2. Elke actieve stream verzendt een MEStreamStopped-gebeurtenis.
  3. Wis alle voorbeelden in de wachtrij en voorbeeldaanvragen.
  4. De mediabron verstuurt een MESourceStopped gebeurtenis.

Terwijl de bron is gestopt, worden alle aanvragen voor voorbeelden geweigerd.

Als de bron wordt gestopt terwijl een I/O-aanvraag wordt uitgevoerd, kan de I/O-aanvraag worden voltooid nadat de bron in de gestopte staat terechtkomt. In dat geval moet de bron het resultaat van die I/O-aanvraag negeren.

Voorbeeldaanvragen

Media Foundation gebruikt een pull- model, waarin de pijplijn voorbeelden van de mediabron aanvraagt. Dit verschilt van het model dat wordt gebruikt door DirectShow, waarin de bronnen voorbeelden 'doorsturen'.

Als u een nieuw voorbeeld wilt aanvragen, roept de Media Foundation-pijplijn IMFMediaStream::RequestSampleaan. Deze methode gebruikt een IUnknown- aanwijzer die een token object vertegenwoordigt. De implementatie van het tokenobject is aan de aanroeper; het biedt gewoon een manier voor de beller om voorbeeldaanvragen bij te houden. De tokenparameter kan ook worden NULL-.

Ervan uitgaande dat de bron asynchrone I/O-aanvragen gebruikt om gegevens te lezen, wordt het genereren van voorbeelden niet gesynchroniseerd met voorbeeldaanvragen. Als u voorbeeldaanvragen wilt synchroniseren met het genereren van voorbeelden, doet een mediabron het volgende:

  1. Aanvraagtokens worden in een wachtrij geplaatst.
  2. Wanneer er voorbeelden worden gegenereerd, worden ze in een tweede wachtrij geplaatst.
  3. De mediabron voltooit een voorbeeldaanvraag door een aanvraagtoken op te halen uit de eerste wachtrij en een voorbeeld uit de tweede wachtrij.
  4. De mediabron verzendt een MEMediaSample-gebeurtenis. De gebeurtenis bevat een aanwijzer naar het voorbeeld en het voorbeeld bevat een aanwijzer naar het token.

In het volgende diagram ziet u de relatie tussen de MEMediaSample gebeurtenis, het voorbeeld en het aanvraagtoken.

diagram met memediasample en een voorbeeldwachtrij die naar imfsample verwijst; imfsample en de aanvraagwachtrij verwijzen naar iunknown

In het voorbeeld van de MPEG-1-bron wordt dit proces als volgt geïmplementeerd:

  1. De methode RequestSample plaatst de aanvraag in een FIFO-wachtrij.
  2. Wanneer I/O-aanvragen zijn voltooid, maakt de mediabron nieuwe voorbeelden en plaatst deze in een tweede FIFO-wachtrij. (Deze wachtrij heeft een maximale grootte om te voorkomen dat de bron te ver vooruit leest.)
  3. Wanneer beide wachtrijen ten minste één item (één aanvraag en één voorbeeld) hebben, voltooit de mediabron de eerste aanvraag uit de aanvraagwachtrij door het eerste voorbeeld uit de voorbeeldwachtrij te verzenden.
  4. Als u een voorbeeld wilt leveren, verzendt het streamobject (niet het bronobject) een MEMediaSample-gebeurtenis.
    • De gebeurtenisgegevens zijn een aanwijzer naar de IMFSample interface van het voorbeeld.
    • Als de aanvraag een token bevat, koppelt u het token aan het voorbeeld door het kenmerk MFSampleExtension_Token in het voorbeeld in te stellen.

Op dit moment zijn er drie mogelijkheden:

  • Er is nog een voorbeeld in de voorbeeldwachtrij, maar er is geen overeenkomende aanvraag.
  • Er is een aanvraag, maar geen voorbeeld.
  • Beide wachtrijen zijn leeg; er zijn geen voorbeelden en geen aanvragen.

Als de sample wachtrij leeg is, controleert de bron of het einde van de stream is bereikt (zie Einde van Stream). Anders wordt een andere I/O-aanvraag voor gegevens gestart. Als er een fout optreedt tijdens dit proces, verzendt de stream een MEError-gebeurtenis.

Met de volgende code wordt de methode IMFMediaStream::RequestSample geïmplementeerd:

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

De DispatchSamples-methode haalt proeven op uit de proefwachtrij, koppelt ze aan lopende aanvraagverzoeken en plaatst MEMediaSample-gebeurtenissen in de wachtrij.

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

De methode DispatchSamples wordt aangeroepen in de volgende omstandigheden:

  • In de methode RequestSample.
  • Wanneer de mediabron opnieuw wordt opgestart vanuit de onderbroken status.
  • Wanneer een I/O-aanvraag is voltooid.

Einde van de stream

Wanneer een stream geen gegevens meer bevat en alle voorbeelden voor die stream zijn geleverd, verzendt de streamobjecten een MEEndOfStream-gebeurtenis.

Wanneer alle actieve streams zijn voltooid, verzendt de mediabron een MEEndOfPresentation-gebeurtenis.

Asynchrone bewerkingen

Misschien is het moeilijkste deel van het schrijven van een mediabron het asynchrone Media Foundation-model te begrijpen.

Alle methoden op een mediabron die streaming beheren, zijn asynchroon. In elk geval voert de methode een eerste validatie uit, zoals het controleren van parameters. De bron verzendt vervolgens de rest van het werk naar een werkwachtrij. Nadat de bewerking is voltooid, stuurt de mediabron een gebeurtenis terug naar de beller, via de IMFMediaEventGenerator interface van de mediabron. Het is daarom belangrijk om inzicht te hebben in werkwachtrijen.

Als u een item in een werkwachtrij wilt plaatsen, kunt u MFPutWorkItem- of MFPutWorkItemEx-aanroepen. De MPEG-1-bron gebruikt MFPutWorkItem, maar de twee functies doen hetzelfde. De functie MFPutWorkItem gebruikt de volgende parameters:

  • Een DWORD--waarde waarmee de werkwachtrij wordt geïdentificeerd. U kunt een privéwerkwachtrij maken of MFASYNC_CALLBACK_QUEUE_STANDARDgebruiken.
  • Een aanwijzer naar de IMFAsyncCallback-interface. Deze callback-interface wordt aangeroepen om het werk uit te voeren.
  • Een optioneel statusobject dat IUnknown-moet implementeren.

De werkwachtrij wordt aangestuurd door een of meer werkthreads die op ononderbroken wijze het volgende werkitem uit de wachtrij halen en de IMFAsyncCallback::Aanroepen methode van de interface van de terugroep aanroepen.

Werkitems worden niet gegarandeerd uitgevoerd in dezelfde volgorde als u ze in de wachtrij plaatst. Houd er rekening mee dat meerdere threads dezelfde werkwachtrij kunnen gebruiken, zodat aanroepen elkaar kunnen overlappen of niet in volgorde plaatsvinden. Daarom is het aan de mediabron om de juiste interne status te behouden door werkwachtrijitems in de juiste volgorde in te dienen. Alleen wanneer de vorige bewerking is voltooid, start de bron de volgende bewerking.

Voor het weergeven van bewerkingen die in behandeling zijn, definieert de MPEG-1-bron een klasse met de naam 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.
};

De Operation opsomming geeft aan welke bewerking in afwachting is. De klasse bevat ook een PROPVARIANT om aanvullende gegevens voor de bewerking over te brengen.

Bewerkingswachtrij

Voor het serialiseren van bewerkingen onderhoudt de mediabron een wachtrij met SourceOp objecten. Er wordt een helperklasse gebruikt om de wachtrij te beheren:

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

De OpQueue klasse is ontworpen om te worden overgenomen door het onderdeel dat asynchrone werkitems uitvoert. De parameter OP_TYPE van het sjabloon is het objecttype dat gebruikt wordt om werkitems in de wachtrij weer te geven, en in dit geval zal OP_TYPESourceOpzijn. De OpQueue-klasse implementeert de volgende methoden:

  • QueueOperation een nieuw item in de wachtrij plaatst.
  • ProcessQueue verstuurt de volgende bewerking uit de wachtrij. Deze methode is asynchroon.
  • ProcessQueueAsync voltooit de asynchrone ProcessQueue-methode.

Er moeten nog twee methoden worden geïmplementeerd door de afgeleide klasse:

  • ValidateOperation controleert of het geldig is om een opgegeven bewerking uit te voeren, gezien de huidige status van de mediabron.
  • DispatchOperation het asynchrone werkitem uitvoert.

De bewerkingswachtrij wordt als volgt gebruikt:

  1. De Media Foundation-pijplijn roept een asynchrone methode aan op de mediabron, zoals IMFMediaSource::Start.
  2. De asynchrone methode roept QueueOperationaan, waardoor de -startbewerking in de wachtrij wordt geplaatst en roept ProcessQueue aan (in de vorm van een SourceOp-object).
  3. ProcessQueue roept MFPutWorkItemaan.
  4. De werkwachtrij-thread roept ProcessQueueAsyncaan.
  5. De methode ProcessQueueAsync roept ValidateOperation en DispatchOperationaan.

Met de volgende code wordt een nieuwe bewerking in de MPEG-1-bron in de wachtrij geplaatst:

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

Met de volgende code wordt de wachtrij verwerkt:

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

De methode ValidateOperation controleert of de MPEG-1-bron de volgende bewerking in de wachtrij kan verzenden. Als er een andere bewerking wordt uitgevoerd, retourneert ValidateOperationMF_E_NOTACCEPTING. Dit zorgt ervoor dat DispatchOperation niet wordt aangeroepen terwijl er op dat moment een andere bewerking in behandeling is.

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

De DispatchOperation-methode schakelt het bewerkingstype in:

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

Samenvatten:

  1. De pijplijn roept een asynchrone methode aan, zoals IMFMediaSource::Start.
  2. De asynchrone methode roept OpQueue::QueueOperationaan, waarbij een aanwijzer wordt doorgegeven aan een SourceOp-object.
  3. De methode QueueOperation plaatst de bewerking in de m_OpQueue wachtrij en roept OpQueue::ProcessQueueaan.
  4. De methode ProcessQueue roept MFPutWorkItemaan. Vanaf dit punt gebeurt alles op een thread van de Media Foundation-werkwachtrij. De asynchrone methode keert terug naar de aanroeper.
  5. De thread van de werkwachtrij roept de OpQueue::ProcessQueueAsync-methode aan.
  6. De methode ProcessQueueAsync roept MPEG1Source:ValidateOperation aan om de bewerking te valideren.
  7. De methode ProcessQueueAsync roept MPEG1Source::DispatchOperation aan om de bewerking te verwerken.

Dit ontwerp heeft verschillende voordelen:

  • Methoden zijn asynchroon, zodat ze de thread van de aanroepende toepassing niet blokkeren.
  • Bewerkingen worden uitgevoerd op een Media Foundation-werkwachtrij-thread, die wordt gedeeld tussen onderdelen van de pijplijn. Daarom maakt de mediabron geen eigen thread, waardoor het totale aantal threads dat wordt gemaakt, wordt verminderd.
  • De mediabron wordt niet geblokkeerd terwijl er gewacht wordt tot de bewerkingen voltooid zijn. Dit vermindert de kans dat een mediabron per ongeluk een impasse veroorzaakt en helpt bij het verminderen van contextwisselingen.
  • De mediabron kan asynchrone I/O gebruiken om het bronbestand te lezen (door IMFByteStream::BeginReadaan te roepen). De mediabron hoeft niet te worden geblokkeerd terwijl je wacht tot de I/O-routine is voltooid.

Als u het patroon volgt dat wordt weergegeven in het SDK-voorbeeld, kunt u zich richten op de specifieke details van uw mediabron.

Mediabronnen

een aangepaste mediabron schrijven