Fallstudie: MPEG-1-Medienquelle

In Microsoft Media Foundation wird das Objekt, das Mediendaten in die Datenpipeline einführt, als Medienquelle bezeichnet. In diesem Thema wird das MPEG-1 Media Source SDK-Beispiel ausführlich erläutert.

Voraussetzungen

Bevor Sie dieses Thema lesen, sollten Sie sich mit den folgenden Media Foundation-Konzepten vertraut machen:

Außerdem sollten Sie über ein grundlegendes Verständnis der Media Foundation-Architektur verfügen, insbesondere über die Rolle von Medienquellen in der Pipeline. (Weitere Informationen finden Sie unter Medienquellen.)

Darüber hinaus können Sie das Thema Schreiben einer benutzerdefinierten Medienquelle lesen, das einen allgemeineren Überblick über die hier beschriebenen Schritte bietet.

In diesem Thema wird nicht der gesamte Code aus dem SDK-Beispiel reproduziert, da das Beispiel ziemlich umfangreich ist.

In der MPEG-1-Quelle verwendete C++-Klassen

Die MPEG-1-Beispielquelle wird mit den folgenden C++-Klassen implementiert:

  • MPEG1ByteStreamHandler. Implementiert den Bytestreamhandler für die Medienquelle. Bei einem Bytedatenstrom erstellt der Bytestreamhandler eine instance der Quelle.
  • MPEG1Source. Implementiert die Medienquelle.
  • MPEG1Stream. Implementiert die Medienstreamobjekte. Die Medienquelle erstellt ein MPEG1Stream Objekt für jeden Audio- oder Videostream im MPEG-1-Bitstream.
  • Parser. Analysiert den MPEG-1-Bitstream. In den meisten Fällen sind die Details dieser Klasse für die Media Foundation-APIs nicht relevant.
  • SourceOp, OpQueue: Diese beiden Klassen verwalten asynchrone Vorgänge in der Medienquelle. (Siehe asynchrone Vorgänge).

Andere verschiedene Hilfsklassen werden weiter unten im Thema beschrieben.

Byte-Stream-Handler

Der Bytestreamhandler ist das Objekt, das die Medienquelle erstellt. Der Bytestreamhandler wird vom Quelllöser erstellt. -Anwendungen interagieren nicht direkt mit dem Bytestreamhandler. Der Quelllöser ermittelt den Bytestreamhandler, indem er in der Registrierung sucht. Handler werden nach Dateinamenerweiterung oder MIME-Typ registriert. Für die MPEG-1-Quelle wird der Bytestreamhandler für die Dateinamenerweiterung ".mpg" registriert.

Hinweis

Wenn Sie benutzerdefinierte URL-Schemas unterstützen möchten, können Sie auch einen Schemahandler schreiben. Die MPEG-1-Quelle ist für lokale Dateien konzipiert, und Media Foundation stellt bereits einen Schemahandler für "file://" URLs bereit.

 

Der Bytestreamhandler implementiert die IMFByteStreamHandler-Schnittstelle . Diese Schnittstelle verfügt über zwei wichtige Methoden, die implementiert werden müssen:

Zwei weitere Methoden sind optional und nicht im SDK-Beispiel implementiert:

  • CancelObjectCreation. Bricht die BeginCreateObject-Methode ab. Diese Methode ist nützlich für eine Netzwerkquelle, die beim Start möglicherweise eine hohe Latenz hat.
  • GetMaxNumberOfBytesRequiredForResolution. Ruft die maximale Anzahl von Bytes ab, die der Handler aus dem Quelldatenstrom liest. Implementieren Sie diese Methode, wenn Sie wissen, wie viele Daten der Bytestreamhandler vor der Erstellung der Medienquelle hat. Andernfalls geben Sie einfach E_NOTIMPL zurück.

Dies ist die Implementierung der BeginCreateObject-Methode :

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

Die -Methode führt die folgenden Schritte aus:

  1. Erstellt eine neue Instanz eines MPEG1Source-Objekts.
  2. Erstellen Sie ein asynchrones Ergebnisobjekt. Dieses Objekt wird später verwendet, um die Rückrufmethode des Quelllösers aufzurufen.
  3. Ruft MPEG1Source::BeginOpeneine in der MPEG1Source -Klasse definierte asynchrone Methode auf.
  4. Legt ppIUnknownCancelCookie auf NULL fest, wodurch der Aufrufer darüber informiert wird, dass CancelObjectCreation nicht unterstützt wird.

Die MPEG1Source::BeginOpen -Methode führt die eigentliche Arbeit aus dem Lesen des Bytedatenstroms und der Initialisierung des Objekts aus MPEG1Source . Diese Methode ist nicht Teil der öffentlichen API. Sie können einen beliebigen Mechanismus zwischen dem Handler und der Medienquelle definieren, der Ihren Anforderungen entspricht. Wenn Sie den Größten Teil der Logik in die Medienquelle einfügen, bleibt der Bytestreamhandler relativ einfach.

Kurz gesagt, BeginOpen führt Folgendes aus:

  1. Ruft IMFByteStream::GetCapabilities auf, um zu überprüfen, ob der Quellbytestream sowohl lesbar als auch suchbar ist.
  2. Ruft IMFByteStream::BeginRead auf, um eine asynchrone E/A-Anforderung zu starten.

Der Rest der Initialisierung erfolgt asynchron. Die Medienquelle liest genügend Daten aus dem Stream, um die MPEG-1-Sequenzheader zu analysieren. Anschließend wird ein Präsentationsdeskriptor erstellt, bei dem es sich um das Objekt handelt, das zum Beschreiben der Audio- und Videostreams in der Datei verwendet wird. (Weitere Informationen finden Sie unter Presentation Descriptor.) Nach Abschluss des BeginOpen Vorgangs ruft der Bytestreamhandler die Rückrufmethode des Quelllösers auf. An diesem Punkt ruft der Quellrelöser IMFByteStreamHandler::EndCreateObject auf. Die EndCreateObject-Methode gibt den status des Vorgangs zurück.

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

Wenn während dieses Prozesses zu irgendeinem Zeitpunkt ein Fehler auftritt, wird der Rückruf mit einem Fehler status Code aufgerufen.

Präsentationsdeskriptor

Der Präsentationsdeskriptor beschreibt den Inhalt der MPEG-1-Datei, einschließlich der folgenden Informationen:

  • Die Anzahl der Streams.
  • Das Format der einzelnen Datenströme.
  • Streambezeichner.
  • Die Auswahl status jedes Streams (ausgewählt oder deaktiviert).

In Bezug auf die Media Foundation-Architektur enthält der Präsentationsdeskriptor mindestens einen Streamdeskriptor. Jeder Streamdeskriptor enthält einen Medientyphandler, der zum Abrufen oder Festlegen von Medientypen für den Stream verwendet wird. Media Foundation stellt Bestandsimplementierungen für den Präsentationsdeskriptor und den Streamdeskriptor bereit. diese eignen sich für die meisten Medienquellen.

Führen Sie die folgenden Schritte aus, um einen Präsentationsdeskriptor zu erstellen:

  1. Für jeden Stream:
    1. Geben Sie eine Stream-ID und ein Array möglicher Medientypen an. Wenn der Stream mehrere Medientypen unterstützt, ordnen Sie die Liste der Medientypen nach Präferenz an, falls vorhanden. (Setzen Sie den optimalen Typ an erster Stelle und den geringsten optimalen Typ an die letzte.)
    2. Rufen Sie MFCreateStreamDescriptor auf, um den Streamdeskriptor zu erstellen.
    3. Rufen Sie IMFStreamDescriptor::GetMediaTypeHandler für den neu erstellten Streamdeskriptor auf.
    4. Rufen Sie IMFMediaTypeHandler::SetCurrentMediaType auf, um das Standardformat für den Stream festzulegen. Wenn mehrere Medientypen vorhanden sind, sollten Sie in der Regel den ersten Typ in der Liste festlegen.
  2. Rufen Sie MFCreatePresentationDescriptor auf, und übergeben Sie das Array der Streamdeskriptorzeiger.
  3. Rufen Sie für jeden Stream IMFPresentationDescriptor::SelectStream oder DeselectStream auf, um den Standardauswahlstatus festzulegen. Wenn mehr als ein Stream desselben Typs (Audio oder Video) vorhanden ist, sollte standardmäßig nur ein Stream ausgewählt werden.

Das MPEG1Source Objekt erstellt den Präsentationsdeskriptor in seiner 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;
}

Die Anwendung ruft den Präsentationsdeskriptor durch Aufrufen von IMFMediaSource::CreatePresentationDescriptor ab. Diese Methode erstellt eine flache Kopie des Präsentationsdeskriptors, indem IMFPresentationDescriptor::Clone aufgerufen wird. (Die Kopie enthält Zeiger auf die ursprünglichen Streamdeskriptoren.) Die Anwendung kann den Präsentationsdeskriptor verwenden, um den Medientyp festzulegen, einen Stream auszuwählen oder die Auswahl eines Datenstroms aufzuheben.

Optional können Präsentationsdeskriptoren und Streamdeskriptoren Attribute enthalten, die zusätzliche Informationen zur Quelle geben. Eine Liste dieser Attribute finden Sie in den folgenden Themen:

Ein Attribut verdient besondere Erwähnung: Das attribut MF_PD_DURATION enthält die Gesamtdauer der Quelle. Legen Sie dieses Attribut fest, wenn Sie die Dauer im Voraus kennen. Beispielsweise kann die Dauer je nach Dateiformat in den Dateiheadern angegeben werden. Die Anwendung kann diesen Wert anzeigen oder zum Festlegen einer Status- oder Suchleiste verwenden.

Streamingzustände

Eine Medienquelle definiert die folgenden Zustände:

State BESCHREIBUNG
Gestartet Die Quelle akzeptiert und verarbeitet Beispielanforderungen.
Angehalten Die Quelle akzeptiert Beispielanforderungen, verarbeitet sie jedoch nicht. Anforderungen werden in die Warteschlange gestellt, bis die Quelle gestartet wird.
Beendet. Die Quelle lehnt Beispielanforderungen ab.

 

Start

Die IMFMediaSource::Start-Methode startet die Medienquelle. Hierfür werden die folgenden Parameter verwendet:

  • Eine Präsentationsbeschreibung.
  • Eine GUID im Zeitformat.
  • Eine Startposition.

Die Anwendung muss den Präsentationsdeskriptor abrufen, indem createPresentationDescriptor für die Quelle aufgerufen wird. Es gibt keinen definierten Mechanismus zum Überprüfen eines Präsentationsdeskriptors. Wenn die Anwendung den falschen Präsentationsdeskriptor angibt, sind die Ergebnisse nicht definiert.

Die Zeitformat-GUID gibt an, wie die Startposition interpretiert werden soll. Das Standardformat ist 100 Nanosekundeneinheiten (ns), die durch GUID_NULL angegeben werden. Jede Medienquelle muss 100-ns-Einheiten unterstützen. Optional kann eine Quelle andere Zeiteinheiten unterstützen, z. B. Framenummer oder Zeitcode. Es gibt jedoch keine Standardmethode zum Abfragen einer Medienquelle für die Liste der von ihr unterstützten Zeitformate.

Die Startposition wird als PROPVARIANT angegeben, sodass je nach Zeitformat unterschiedliche Datentypen möglich sind. Für 100-ns ist der PROPVARIANT-Typ entweder VT_I8 oder VT_EMPTY. Wenn VT_I8, enthält propvariant die Startposition in 100-ns-Einheiten. Der Wert VT_EMPTY hat die besondere Bedeutung "Start an der aktuellen Position".

Implementieren Sie die Start-Methode wie folgt:

  1. Überprüfen von Parametern und Zustand:
    • Suchen Sie nach NULL-Parametern .
    • Überprüfen Sie die ZEITformat-GUID. Wenn der Wert ungültig ist, geben Sie MF_E_UNSUPPORTED_TIME_FORMAT zurück.
    • Überprüfen Sie den Datentyp des PROPVARIANT , der die Startposition enthält.
    • Überprüfen Sie die Startposition. Wenn ungültig, geben Sie MF_E_INVALIDREQUEST zurück.
    • Wenn die Quelle heruntergefahren wurde, geben Sie MF_E_SHUTDOWN zurück.
  2. Wenn in Schritt 1 kein Fehler auftritt, stellen Sie einen asynchronen Vorgang in die Warteschlange. Alles nach diesem Schritt erfolgt in einem Arbeitswarteschlangenthread.
  3. Für jeden Stream:
    1. Überprüfen Sie, ob der Stream bereits aus einer vorherigen Startanforderung aktiv ist.

    2. Rufen Sie IMFPresentationDescriptor::GetStreamDescriptorByIndex auf, um zu überprüfen, ob die Anwendung den Stream ausgewählt oder deaktiviert hat.

    3. Wenn ein zuvor ausgewählter Stream jetzt deaktiviert ist, leeren Sie alle nicht zugestellten Beispiele für diesen Stream.

    4. Wenn der Stream aktiv ist, sendet die Medienquelle (nicht der Stream) eines der folgenden Ereignisse:

      Bei beiden Ereignissen sind die Ereignisdaten der IMFMediaStream-Zeiger für den Stream.

    5. Wenn die Quelle aus dem angehaltenen Zustand neu gestartet wird, gibt es möglicherweise ausstehende Beispielanforderungen. Wenn ja, liefern Sie diese jetzt.

    6. Wenn die Quelle eine neue Position anstrebt, sendet jedes Streamobjekt ein MEStreamSeeked-Ereignis . Andernfalls sendet jeder Stream ein MEStreamStarted-Ereignis .

  4. Wenn die Quelle eine neue Position anstrebt, sendet die Medienquelle ein MESourceSeeked-Ereignis . Andernfalls wird ein MESourceStarted-Ereignis gesendet.

Wenn nach Schritt 2 jederzeit ein Fehler auftritt, sendet die Quelle ein MESourceStarted-Ereignis mit einem Fehlercode. Dadurch wird die Anwendung benachrichtigt, dass die Startmethode asynchron fehlgeschlagen ist.

Der folgende Code zeigt die Schritte 1 bis 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;
}

Die restlichen Schritte werden im nächsten Beispiel gezeigt:

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

Anhalten

Die IMFMediaSource::P ause-Methode hält die Medienquelle an. Implementieren Sie diese Methode wie folgt:

  1. Einen asynchronen Vorgang in die Warteschlange stellen.
  2. Jeder aktive Stream sendet ein MEStreamPaused-Ereignis .
  3. Die Medienquelle sendet ein MESourcePaused-Ereignis .

Während sie angehalten wird, stellt die Quelle Beispielanforderungen in die Warteschlange, ohne sie zu verarbeiten. (Siehe Beispielanforderungen.)

Beenden

Die IMFMediaSource::Stop-Methode beendet die Medienquelle. Implementieren Sie diese Methode wie folgt:

  1. Einen asynchronen Vorgang in die Warteschlange stellen.
  2. Jeder aktive Stream sendet ein MEStreamStopped-Ereignis .
  3. Löschen Sie alle in der Warteschlange befindlichen Beispiele und Beispielanforderungen.
  4. Die Medienquelle sendet ein MESourceStopped-Ereignis .

Während der Beendigung lehnt die Quelle alle Anforderungen für Beispiele ab.

Wenn die Quelle beendet wird, während eine E/A-Anforderung ausgeführt wird, kann die E/A-Anforderung abgeschlossen werden, nachdem die Quelle den Status beendet hat. In diesem Fall sollte die Quelle das Ergebnis dieser E/A-Anforderung verwerfen.

Beispielanforderungen

Media Foundation verwendet ein Pullmodell , in dem die Pipeline Beispiele von der Medienquelle anfordert. Dies unterscheidet sich von dem von DirectShow verwendeten Modell, in dem die Quellen Beispiele "pushen".

Um ein neues Beispiel anzufordern, ruft die Media Foundation-Pipeline IMFMediaStream::RequestSample auf. Diese Methode verwendet einen IUnknown-Zeiger , der ein Tokenobjekt darstellt. Die Implementierung des Tokenobjekts liegt beim Aufrufer. Es bietet einfach eine Möglichkeit für den Aufrufer, Beispielanforderungen nachzuverfolgen. Der Tokenparameter kann auch NULL sein.

Wenn die Quelle asynchrone E/A-Anforderungen zum Lesen von Daten verwendet, wird die Beispielgenerierung nicht mit Beispielanforderungen synchronisiert. Um Beispielanforderungen mit der Beispielgenerierung zu synchronisieren, führt eine Medienquelle folgendes aus:

  1. Anforderungstoken werden in einer Warteschlange platziert.
  2. Wenn Beispiele generiert werden, werden sie in einer zweiten Warteschlange platziert.
  3. Die Medienquelle schließt eine Beispielanforderung ab, indem ein Anforderungstoken aus der ersten Warteschlange und ein Beispiel aus der zweiten Warteschlange abgerufen wird.
  4. Die Medienquelle sendet ein MEMediaSample-Ereignis. Das Ereignis enthält einen Zeiger auf das Beispiel, und das Beispiel enthält einen Zeiger auf das Token.

Das folgende Diagramm zeigt die Beziehung zwischen dem MEMediaSample-Ereignis , dem Beispiel und dem Anforderungstoken.

Diagramm mit memediasample und einer Beispielwarteschlange, die auf imfsample zeigt; imfsample und der Anforderungswarteschlangenpunkt auf iunknown

Die MPEG-1-Beispielquelle implementiert diesen Prozess wie folgt:

  1. Die RequestSample-Methode platziert die Anforderung in einer FIFO-Warteschlange.
  2. Wenn E/A-Anforderungen abgeschlossen sind, erstellt die Medienquelle neue Beispiele und legt sie in einer zweiten FIFO-Warteschlange ab. (Diese Warteschlange hat eine maximale Größe, um zu verhindern, dass die Quelle zu weit vorliest.)
  3. Wenn beide Warteschlangen mindestens ein Element (eine Anforderung und ein Beispiel) aufweisen, schließt die Medienquelle die erste Anforderung aus der Anforderungswarteschlange ab, indem das erste Beispiel aus der Beispielwarteschlange gesendet wird.
  4. Um ein Beispiel zu liefern, sendet das Streamobjekt (nicht das Quellobjekt) ein MEMediaSample-Ereignis .

An dieser Stelle gibt es drei Möglichkeiten:

  • Es gibt ein weiteres Beispiel in der Beispielwarteschlange, aber keine übereinstimmende Anforderung.
  • Es gibt eine Anforderung, aber kein Beispiel.
  • Beide Warteschlangen sind leer. es gibt keine Beispiele und keine Anforderungen.

Wenn die Beispielwarteschlange leer ist, überprüft die Quelle das Ende des Datenstroms (siehe Ende des Datenstroms). Andernfalls wird eine weitere E/A-Anforderung für Daten gestartet. Wenn während dieses Prozesses ein Fehler auftritt, sendet der Stream ein MEError-Ereignis .

Der folgende Code implementiert die IMFMediaStream::RequestSample-Methode :

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

Die DispatchSamples -Methode ruft Beispiele aus der Beispielwarteschlange ab, gleicht sie mit ausstehenden Beispielanforderungen und MEMediaSample-Ereignissen in Warteschlangen ab:

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

Die DispatchSamples Methode wird unter den folgenden Umständen aufgerufen:

  • Innerhalb der RequestSample-Methode .
  • Wenn die Medienquelle aus dem angehaltenen Zustand neu gestartet wird.
  • Wenn eine E/A-Anforderung abgeschlossen ist.

Ende des Streams

Wenn ein Stream keine weiteren Daten enthält und alle Beispiele für diesen Stream übermittelt wurden, senden die Streamobjekte ein MEEndOfStream-Ereignis .

Wenn alle aktiven Streams abgeschlossen sind, sendet die Medienquelle ein MEEndOfPresentation-Ereignis .

Asynchrone Vorgänge

Der schwierigste Teil des Schreibens einer Medienquelle ist möglicherweise das Verständnis des asynchronen Media Foundation-Modells.

Alle Methoden für eine Medienquelle, die das Streaming steuern, sind asynchron. In jedem Fall führt die Methode eine erste Überprüfung durch, z. B. die Überprüfung von Parametern. Die Quelle sendet dann den Rest der Arbeit an eine Arbeitswarteschlange. Nach Abschluss des Vorgangs sendet die Medienquelle über die IMFMediaEventGenerator-Schnittstelle der Medienquelle ein Ereignis zurück an den Aufrufer. Daher ist es wichtig, Arbeitswarteschlangen zu verstehen.

Um ein Element in einer Arbeitswarteschlange zu platzieren, können Sie ENTWEDER MFPutWorkItem oder MFPutWorkItemEx aufrufen. Die MPEG-1-Quelle verwendet zwar MFPutWorkItem, aber die beiden Funktionen tun dasselbe. Die MFPutWorkItem-Funktion akzeptiert die folgenden Parameter:

  • Ein DWORD-Wert , der die Arbeitswarteschlange identifiziert. Sie können eine private Arbeitswarteschlange erstellen oder MFASYNC_CALLBACK_QUEUE_STANDARD verwenden.
  • Ein Zeiger auf die IMFAsyncCallback-Schnittstelle . Diese Rückrufschnittstelle wird aufgerufen, um die Arbeit auszuführen.
  • Ein optionales Zustandsobjekt, das IUnknown implementieren muss.

Die Arbeitswarteschlange wird von einem oder mehreren Workerthreads verwaltet, die kontinuierlich das nächste Arbeitselement aus der Warteschlange ziehen und die IMFAsyncCallback::Invoke-Methode der Rückrufschnittstelle aufrufen.

Es ist nicht garantiert, dass Arbeitselemente in der gleichen Reihenfolge ausgeführt werden, in der Sie sie in der Warteschlange platzieren. Denken Sie daran, dass mehr als ein Thread dieselbe Arbeitswarteschlange bedienen kann, sodass Aufrufaufrufe sich überlappen oder in einer anderen Reihenfolge auftreten können. Daher ist es an der Medienquelle, den richtigen internen Zustand beizubehalten, indem Arbeitswarteschlangenelemente in der richtigen Reihenfolge übermittelt werden. Erst wenn der vorherige Vorgang abgeschlossen ist, startet die Quelle den nächsten Vorgang.

Um ausstehende Vorgänge darzustellen, definiert die MPEG-1-Quelle eine Klasse mit dem Namen 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.
};

Die Operation Enumeration gibt an, welcher Vorgang aussteht. Die -Klasse enthält auch eine PROPVARIANT-Eigenschaft , um alle zusätzlichen Daten für den Vorgang zu übermitteln.

Vorgangswarteschlange

Zum Serialisieren von SourceOp Vorgängen verwaltet die Medienquelle eine Objektwarteschlange. Sie verwendet eine Hilfsklasse, um die Warteschlange zu verwalten:

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

Die OpQueue -Klasse ist so konzipiert, dass sie von der Komponente geerbt wird, die asynchrone Arbeitselemente ausführt. Der OP_TYPE Vorlagenparameter ist der Objekttyp, der verwendet wird, um Arbeitselemente in der Warteschlange darzustellen. In diesem Fall istSourceOpOP_TYPE . Die OpQueue -Klasse implementiert die folgenden Methoden:

  • QueueOperation fügt ein neues Element in die Warteschlange ein.
  • ProcessQueue sendet den nächsten Vorgang aus der Warteschlange. Diese Methode ist asynchron.
  • ProcessQueueAsync schließt die asynchrone ProcessQueue Methode ab.

Zwei weitere Methoden müssen von der abgeleiteten Klasse implementiert werden:

  • ValidateOperation überprüft, ob die Ausführung eines angegebenen Vorgangs unter Berücksichtigung des aktuellen Zustands der Medienquelle gültig ist.
  • DispatchOperation führt das asynchrone Arbeitselement aus.

Die Vorgangswarteschlange wird wie folgt verwendet:

  1. Die Media Foundation-Pipeline ruft eine asynchrone Methode für die Medienquelle auf, z. B. IMFMediaSource::Start.
  2. Die asynchrone Methode ruft aufQueueOperation, wodurch der Startvorgang in die Warteschlange versetzt wird und (in Form eines SourceOp -Objekts) aufgerufen wird ProcessQueue .
  3. ProcessQueue ruft MFPutWorkItem auf.
  4. Der Arbeitswarteschlangenthread ruft auf ProcessQueueAsync.
  5. Die ProcessQueueAsync -Methode ruft und DispatchOperationaufValidateOperation.

Der folgende Code stellt einen neuen Vorgang für die MPEG-1-Quelle in die Warteschlange:

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

Der folgende Code verarbeitet die Warteschlange:

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

Die ValidateOperation -Methode überprüft, ob die MPEG-1-Quelle den nächsten Vorgang in der Warteschlange senden kann. Wenn ein anderer Vorgang ausgeführt wird, ValidateOperation gibt MF_E_NOTACCEPTING zurück. Dadurch wird sichergestellt, dass DispatchOperation nicht aufgerufen wird, während ein weiterer Vorgang aussteht.

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

Die DispatchOperation-Methode wechselt für den Vorgangstyp:

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

Zusammenfassung:

  1. Die Pipeline ruft eine asynchrone Methode auf, z. B. IMFMediaSource::Start.
  2. Die asynchrone Methode ruft auf OpQueue::QueueOperation, übergibt einen Zeiger auf ein SourceOp -Objekt.
  3. Die QueueOperation -Methode legt den Vorgang in die m_OpQueue Warteschlange und ruft auf OpQueue::ProcessQueue.
  4. Die ProcessQueue -Methode ruft MFPutWorkItem auf. Ab diesem Punkt geschieht alles in einem Media Foundation-Arbeitswarteschlangenthread. Die asynchrone Methode wird an den Aufrufer zurückgegeben.
  5. Der Arbeitswarteschlangenthread ruft die OpQueue::ProcessQueueAsync -Methode auf.
  6. Die ProcessQueueAsync -Methode ruft auf MPEG1Source:ValidateOperation , um den Vorgang zu überprüfen.
  7. Die ProcessQueueAsync -Methode ruft MPEG1Source::DispatchOperation auf, um den Vorgang zu verarbeiten.

Dieser Entwurf hat mehrere Vorteile:

  • Methoden sind asynchron, sodass sie den Thread der aufrufenden Anwendung nicht blockieren.
  • Vorgänge werden für einen Media Foundation-Arbeitswarteschlangenthread ausgeführt, der von Pipelinekomponenten gemeinsam genutzt wird. Daher erstellt die Medienquelle keinen eigenen Thread, wodurch die Gesamtzahl der erstellten Threads verringert wird.
  • Die Medienquelle wird während des Wartens auf den Abschluss von Vorgängen nicht blockiert. Dadurch wird das Risiko verringert, dass eine Medienquelle versehentlich einen Deadlock verursacht, und hilft, den Kontextwechsel zu reduzieren.
  • Die Medienquelle kann asynchrone E/A verwenden, um die Quelldatei zu lesen (durch Aufrufen von IMFByteStream::BeginRead). Die Medienquelle muss während des Wartens auf den Abschluss der E/A-Routine nicht blockiert werden.

Wenn Sie das im SDK-Beispiel gezeigte Muster befolgen, können Sie sich auf die spezifischen Details Ihrer Medienquelle konzentrieren.

Medienquellen

Schreiben einer benutzerdefinierten Medienquelle