So schreiben Sie einen EVR-Referenten

[Die auf dieser Seite beschriebene Komponente, Erweiterter Videorenderer, ist ein Legacyfeature. Sie wurde durch den Simple Video Renderer (SVR) abgelöst, der über die Komponenten MediaPlayer und IMFMediaEngine verfügbar gemacht wurde. Um Videoinhalte wiederzugeben, sollten Sie Daten an eine dieser Komponenten senden und es ihnen ermöglichen, den neuen Videorenderer zu instanziieren. Diese Komponenten wurden für Windows 10 und Windows 11 optimiert. Microsoft empfiehlt dringend, dass neuer Code MediaPlayer-APIs oder die IMFMediaEngine-APIs auf niedrigerer Ebene anstelle des EVR verwendet, um Videomedien in Windows wiederzugeben. Microsoft schlägt vor, dass vorhandener Code, der die Legacy-APIs verwendet, wenn möglich umgeschrieben wird, um die neuen APIs zu verwenden.]

In diesem Artikel wird beschrieben, wie Sie einen benutzerdefinierten Referenten für den erweiterten Videorenderer (EVR) schreiben. Ein benutzerdefinierter Referent kann sowohl mit DirectShow als auch mit Media Foundation verwendet werden. Die Schnittstellen und das Objektmodell sind für beide Technologien identisch, obwohl die genaue Abfolge von Vorgängen variieren kann.

Der Beispielcode in diesem Thema wird aus dem EVRPresenter-Beispiel angepasst, das im Windows SDK bereitgestellt wird.

Dieses Thema enthält folgende Abschnitte:

Voraussetzungen

Bevor Sie einen benutzerdefinierten Referenten schreiben, sollten Sie mit den folgenden Technologien vertraut sein:

  • Der erweiterte Videorenderer. Siehe Erweiterter Videorenderer.
  • Direct3D-Grafiken. Sie müssen 3D-Grafiken nicht verstehen, um einen Referenten zu schreiben, aber Sie müssen wissen, wie Sie ein Direct3D-Gerät erstellen und Direct3D-Oberflächen verwalten. Wenn Sie mit Direct3D nicht vertraut sind, lesen Sie die Abschnitte „Direct3D-Geräte“ und „Direct3D-Ressourcen“ in der DirectX Graphics SDK-Dokumentation.
  • DirectShow-Filterdiagramme oder die Media Foundation-Pipeline, je nachdem, welche Technologie Ihre Anwendung zum Rendern von Videos verwenden wird.
  • Media Foundation-Transformationen. Der EVR-Mixer ist eine Media Foundation-Transformation und der Referent ruft Methoden direkt auf dem Mixer auf.
  • Implementieren von COM-Objekten. Der Referent ist ein prozessinternes COM-Objekt mit freiem Durchlauf.

Referentenobjektmodell

Dieser Abschnitt enthält eine Übersicht über das Referentenobjektmodell und die Schnittstellen.

Datenfluss innerhalb des EVR

Der EVR verwendet zwei Plug-In-Komponenten zum Rendern von Videos: Den Mixer und den Referenten. Der Mixer mischt die Videostreams und „deinterlaciert“ das Video bei Bedarf. Der Referent zeichnet (oder präsentiert) das Video auf dem Bildschirm und plant, wann jeder Frame gezeichnet wird. Anwendungen können eines dieser Objekte durch eine benutzerdefinierte Implementierung ersetzen.

Der EVR verfügt über einen oder mehrere Eingabestreams und der Mixer verfügt über eine entsprechende Anzahl von Eingabestreams. Stream 0 ist immer der Verweisstream. Die anderen Datenströme sind Substreams, die der Mixer Alpha-Blend in den Verweisstream einfügt. Der Verweisstream bestimmt die Master-Framerate für das zusammengesetzte Video. Für jeden Verweisframe nimmt der Mixer den letzten Frame aus jedem Substream, mischt ihn mittels Alpha-Blend mit dem Verweisframe und gibt einen einzelnen zusammengesetzten Frame aus. Der Mixer führt bei Bedarf auch Deinterlacing und Farbkonvertierung von YUV nach RGB durch. Der EVR fügt den Mixer immer unabhängig von der Anzahl der Eingabestreams oder des Videoformats in die Videopipeline ein. Das folgende Bild zeigt diesen Prozess.

diagram showing the reference stream and substream pointing to the mixer, which points to the presenter, which points to the display

Der Referent führt die folgenden Aufgaben aus:

  • Legt das Ausgabeformat auf dem Mixer fest. Bevor das Streaming beginnt, legt der Referent einen Medientyp für den Ausgabestream des Mixers fest. Dieser Medientyp definiert das Format des zusammengesetzten Bilds.
  • Erstellt das Direct3D-Gerät.
  • Ordnet Direct3D-Oberflächen zu. Der Mixer blendet die zusammengesetzten Frames auf diese Oberflächen.
  • Ruft die Ausgabe des Mixer ab.
  • Plant, wann die Frames angezeigt werden. Der EVR stellt die Präsentationssystemuhr zur Verfügung und der Referent plant die Frames nach dieser Systemuhr.
  • Stellt jeden Frame mit Direct3D dar.
  • Führt Frame Stepping und Scrubbing aus.

Referentenstatus

Der Referent befindet sich jederzeit in einem der folgenden Zustände:

  • Gestartet. Die Präsentationssystemuhr des EVR wird ausgeführt. Der Referent plant Videoframes für die Präsentation, sobald sie eintreffen.
  • Angehalten. Die Präsentationssystemuhr wird angehalten. Der Referent stellt keine neuen Beispiele (Bildpunkte) vor, verwaltet aber seine Warteschlange mit geplanten Beispielen (Bildpunkten). Wenn neue Bildpunkte empfangen werden, fügt der Referent sie der Warteschlange hinzu.
  • Stopped(Beendet): Dies ist der anfängliche Status des Kanals nach seiner Erstellung (es sei denn, im Portal wurde das automatische Starten gewählt). Die Präsentationssystemuhr ist angehalten. Der Referent verwirft alle geplanten Beispiele (Bildpunkte).
  • Herunterfahren. Der Referent gibt alle Ressourcen im Zusammenhang mit Streaming frei, z. B. Direct3D-Oberflächen. Dies ist der Anfangszustand des Referenten und der letzte Zustand, bevor der Referent gelöscht wird.

Im Beispielcode in diesem Thema wird der Referentenstatus durch eine Aufzählung dargestellt:

enum RENDER_STATE
{
    RENDER_STATE_STARTED = 1,
    RENDER_STATE_STOPPED,
    RENDER_STATE_PAUSED,
    RENDER_STATE_SHUTDOWN,  // Initial state.
};

Einige Vorgänge sind ungültig, während sich der Referent im Zustand „Herunterfahren“ befindet. Der Beispielcode überprüft diesen Zustand durch Aufrufen einer Hilfsmethode:

    HRESULT CheckShutdown() const
    {
        if (m_RenderState == RENDER_STATE_SHUTDOWN)
        {
            return MF_E_SHUTDOWN;
        }
        else
        {
            return S_OK;
        }
    }

Referentenschnittstellen

Ein Referent ist erforderlich, um die folgenden Schnittstellen zu implementieren:

Schnittstelle Beschreibung
IMFClockStateSink Benachrichtigt den Referenten, wenn sich der Zustand der Systemuhr des EVR ändert. Siehe Implementieren von IMFClockStateSink.
IMFGetService Bietet eine Möglichkeit für die Anwendung und andere Komponenten in der Pipeline, um Schnittstellen vom Referenten abzurufen.
IMFTopologyServiceLookupClient Ermöglicht dem Referenten das Abrufen von Schnittstellen vom EVR oder dem Mixer. Siehe Implementieren von IMFTopologyServiceLookupClient.
IMFVideoDeviceID Stellt sicher, dass der Referent und der Mixer kompatible Technologien verwenden. Siehe Implementieren von IMFVideoDeviceID.
IMFVideoPresenter Verarbeitet Meldungen aus dem EVR. Siehe Implementieren von IMFVideoPresenter.

 

Die folgenden Schnittstellen sind optional:

Schnittstelle Beschreibung
IEVRTrustedVideoPlugin Ermöglicht es dem Referenten, mit geschützten Medien zu arbeiten. Implementieren Sie diese Schnittstelle, wenn Ihr Referent eine vertrauenswürdige Komponente ist, die im geschützten Medienpfad (PMP) arbeiten soll.
IMFRateSupport Meldet den Bereich der Wiedergaberaten, die der Referent unterstützt. Siehe Implementieren von IMFRateSupport.
IMFVideoPositionMapper Ordnet Koordinaten für den Ausgabevideoframe zu, um Koordinaten für den Eingabevideoframe zu erstellen.
IQualProp Meldet Leistungsinformationen. Der EVR verwendet diese Informationen für das Qualitätskontrollmanagement. Diese Schnittstelle ist im DirectShow SDK dokumentiert.

 

Sie können auch Schnittstellen für die Anwendung bereitstellen, um mit dem Referenten zu kommunizieren. Der Standardreferent implementiert hierfür die IMFVideoDisplayControl-Schnittstelle. Sie können diese Schnittstelle implementieren oder eigene definieren. Die Anwendung ruft Schnittstellen vom Referenten ab, indem IMFGetService::GetService für das EVR aufgerufen wird. Wenn die Dienst-GUID MR_VIDEO_RENDER_SERVICE lautet, übergibt der EVR die GetService-Anforderung an den Referenten.

Implementieren von IMFVideoDeviceID

Die IMFVideoDeviceID-Schnittstelle enthält eine Methode, GetDeviceID, die eine Geräte-GUID zurückgibt. Die Geräte-GUID stellt sicher, dass der Referent und der Mixer kompatible Technologien verwenden. Wenn die Geräte-GUIDs nicht übereinstimmen, kann der EVR nicht initialisiert werden.

Sowohl der Standardmixer als auch der Referenten verwenden Direct3D 9, wobei die Geräte-GUID gleich IID_IDirect3DDevice9 ist. Wenn Sie den benutzerdefinierten Referenten mit dem Standardmixer verwenden möchten, muss die Geräte-GUID des Referenten IID_IDirect3DDevice9 lauten. Wenn Sie beide Komponenten ersetzen, können Sie eine neue Geräte-GUID definieren. Im weiteren Verlauf dieses Artikels wird davon ausgegangen, dass der Referent Direct3D 9 verwendet. Hier ist die Standardimplementierung von GetDeviceID:

HRESULT EVRCustomPresenter::GetDeviceID(IID* pDeviceID)
{
    if (pDeviceID == NULL)
    {
        return E_POINTER;
    }

    *pDeviceID = __uuidof(IDirect3DDevice9);
    return S_OK;
}

Die Methode sollte auch dann erfolgreich ausgeführt werden, wenn der Referent heruntergefahren wird.

Implementieren von IMFTopologyServiceLookupClient

Die IMFTopologyServiceLookupClient-Schnittstelle ermöglicht es dem Referenten, Schnittstellenzeiger aus dem EVR und vom Mixer wie folgt abzurufen:

  1. Wenn der EVR den Referenten initialisiert, ruft er die IMFTopologyServiceLookupClient::InitServicePointers-Methode des Referenten auf. Das Argument ist ein Zeiger auf die IMFTopologyServiceLookup-Schnittstelle des EVR.
  2. Der Referent ruft IMFTopologyServiceLookup::LookupService auf, um Schnittstellenzeiger vom EVR oder vom Mixer abzurufen.

Die LookupService-Methode ähnelt der IMFGetService::GetService-Methode. Beide Methoden verwenden eine Dienst-GUID und einen Schnittstellenbezeichner (IID) als Eingabe, aber LookupService gibt ein Array von Schnittstellenzeigern zurück, während GetService einen einzelnen Zeiger zurückgibt. In der Praxis können Sie die Arraygröße jedoch immer auf 1 festlegen. Das abgefragte Objekt hängt von der Dienst-GUID ab:

  • Wenn die Dienst-GUID MR_VIDEO_RENDER_SERVICE ist, wird der EVR abgefragt.
  • Wenn die Dienst-GUID MR_VIDEO_MIXER_SERVICE ist, wird der Mixer abgefragt.

Rufen Sie in Ihrer Implementierung von InitServicePointers die folgenden Schnittstellen aus dem EVR ab:

EVR-Schnittstelle Beschreibung
IMediaEventSink Ermöglicht dem Referenten das Senden von Nachrichten an den EVR. Diese Schnittstelle ist im DirectShow SDK definiert, sodass die Meldungen dem Muster für DirectShow-Ereignisse und nicht für Media Foundation-Ereignisse folgen.
IMFClock Stellt die Systemuhr des EVR dar. Der Referent verwendet diese Schnittstelle, um Beispiele für die Präsentation zu planen. Der EVR kann ohne Systemuhr ausgeführt werden, sodass diese Schnittstelle möglicherweise nicht verfügbar ist. Wenn nicht, ignorieren Sie den Fehlercode von LookupService.
Die Systemuhr implementiert auch die IMFTimer-Schnittstelle. In der Media Foundation-Pipeline implementiert die Systemuhr die IMFPresentationClock-Schnittstelle. Diese Schnittstelle wird in DirectShow nicht implementiert.

 

Rufen Sie die folgenden Schnittstellen vom Mixer ab:

Mixer-Schnittstelle Beschreibung
IMFTransform Ermöglicht es dem Referenten, mit dem Mixer zu kommunizieren.
IMFVideoDeviceID Ermöglicht dem Referenten die Überprüfung der Geräte-GUID des Mixers.

 

Der folgende Code implementiert die InitServicePointers-Methode:

HRESULT EVRCustomPresenter::InitServicePointers(
    IMFTopologyServiceLookup *pLookup
    )
{
    if (pLookup == NULL)
    {
        return E_POINTER;
    }

    HRESULT             hr = S_OK;
    DWORD               dwObjectCount = 0;

    EnterCriticalSection(&m_ObjectLock);

    // Do not allow initializing when playing or paused.
    if (IsActive())
    {
        hr = MF_E_INVALIDREQUEST;
        goto done;
    }

    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    // Ask for the clock. Optional, because the EVR might not have a clock.
    dwObjectCount = 1;

    (void)pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL,   // Not used.
        0,                          // Reserved.
        MR_VIDEO_RENDER_SERVICE,    // Service to look up.
        IID_PPV_ARGS(&m_pClock),    // Interface to retrieve.
        &dwObjectCount              // Number of elements retrieved.
        );

    // Ask for the mixer. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_MIXER_SERVICE, IID_PPV_ARGS(&m_pMixer), &dwObjectCount
        );

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

    // Make sure that we can work with this mixer.
    hr = ConfigureMixer(m_pMixer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Ask for the EVR's event-sink interface. (Required.)
    dwObjectCount = 1;

    hr = pLookup->LookupService(
        MF_SERVICE_LOOKUP_GLOBAL, 0,
        MR_VIDEO_RENDER_SERVICE, IID_PPV_ARGS(&m_pMediaEventSink),
        &dwObjectCount
        );

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

    // Successfully initialized. Set the state to "stopped."
    m_RenderState = RENDER_STATE_STOPPED;

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Wenn die von LookupService abgerufenen Schnittstellenzeiger nicht mehr gültig sind, ruft der EVR IMFTopologyServiceLookupClient::ReleaseServicePointers auf. Geben Sie in dieser Methode alle Schnittstellenzeiger frei und setzen Sie den Referentenstatus auf Herunterfahren:

HRESULT EVRCustomPresenter::ReleaseServicePointers()
{
    // Enter the shut-down state.
    EnterCriticalSection(&m_ObjectLock);

    m_RenderState = RENDER_STATE_SHUTDOWN;

    LeaveCriticalSection(&m_ObjectLock);

    // Flush any samples that were scheduled.
    Flush();

    // Clear the media type and release related resources.
    SetMediaType(NULL);

    // Release all services that were acquired from InitServicePointers.
    SafeRelease(&m_pClock);
    SafeRelease(&m_pMixer);
    SafeRelease(&m_pMediaEventSink);

    return S_OK;
}

Der EVR ruft ReleaseServicePointers aus verschiedenen Gründen auf, darunter:

  • Trennen oder erneutes Verbinden von Pins (DirectShow) oder Hinzufügen oder Entfernen von Stream Sinks (Media Foundation).
  • Format wird geändert.
  • Einstellung einer neuen Systemuhr.
  • Endgültiges Herunterfahren des EVR.

Während der Lebensdauer des Referenten ruft der EVR möglicherweise InitServicePointers und ReleaseServicePointers mehrmals auf.

Implementieren von IMFVideoPresenter

Die IMFVideoPresenter-Schnittstelle erbt IMFClockStateSink und fügt zwei Methoden hinzu:

Methode Beschreibung
GetCurrentMediaType Gibt den Medientyp der zusammengesetzten Videoframes zurück.
ProcessMessage Signalisiert dem Referenten, verschiedene Aktionen auszuführen.

 

Die GetCurrentMediaType-Methode gibt den Medientyp des Referenten zurück. (Ausführliche Informationen zur Einstellung des Medientyps finden Sie unter Verhandlungsformate.) Der Medientyp wird als IMFVideoMediaType-Schnittstellenzeiger zurückgegeben. Im folgenden Beispiel wird davon ausgegangen, dass der Referent den Medientyp als IMFMediaType-Zeiger speichert. Um die IMFVideoMediaType-Schnittstelle vom Medientyp zu erhalten, rufen Sie QueryInterface auf:

HRESULT EVRCustomPresenter::GetCurrentMediaType(
    IMFVideoMediaType** ppMediaType
    )
{
    HRESULT hr = S_OK;

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

    *ppMediaType = NULL;

    EnterCriticalSection(&m_ObjectLock);

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

    if (m_pMediaType == NULL)
    {
        hr = MF_E_NOT_INITIALIZED;
        goto done;
    }

    hr = m_pMediaType->QueryInterface(IID_PPV_ARGS(ppMediaType));

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Die ProcessMessage-Methode ist der wichtigste Mechanismus für die Kommunikation des EVR mit dem Referenten. Die folgenden Meldungen werden definiert. Die Details zur Implementierung der einzelnen Meldungen finden Sie im weiteren Verlauf dieses Themas.

`Message` Beschreibung
MFVP_MESSAGE_INVALIDATEMEDIATYPE Der Ausgabemedientyp des Mixers ist ungültig. Der Referent sollte einen neuen Medientyp mit dem Mixer aushandeln. Siehe Verhandlungsformate.
MFVP_MESSAGE_BEGINSTREAMING Streaming wurde gestartet. Diese Meldung erfordert keine besondere Aktion, aber Sie können sie nutzen, um Ressourcen zuzuweisen.
MFVP_MESSAGE_ENDSTREAMING Streaming wurde beendet. Geben Sie alle Ressourcen frei, die Sie als Reaktion auf die MFVP_MESSAGE_BEGINSTREAMING-Meldung zugewiesen haben.
MFVP_MESSAGE_PROCESSINPUTNOTIFY Der Mixer hat ein neues Eingabebeispiel erhalten und kann möglicherweise einen neuen Ausgabeframe generieren. Der Referent sollte IMFTransform::ProcessOutput auf dem Mixer aufrufen. Siehe Verarbeitungsausgabe.
MFVP_MESSAGE_ENDOFSTREAM Die Präsentation wurde beendet. Siehe Ende des Streams.
MFVP_MESSAGE_FLUSH Der EVR löscht die Daten in der Renderingpipeline. Der Referent sollte alle Videoframes verwerfen, die für die Präsentation geplant sind.
MFVP_MESSAGE_STEP Fordert den Referenten auf, N-Frames vorwärts zu leiten. Der Referent sollte die nächsten N-1-Frames verwerfen und den N-ten Frame anzeigen. Siehe Frame Stepping.
MFVP_MESSAGE_CANCELSTEP Bricht Frame Stepping ab.

 

Implementieren von IMFClockStateSink

Der Referent muss die IMFClockStateSink-Schnittstelle als Teil seiner Implementierung von IMFVideoPresenter implementieren, die IMFClockStateSink erbt. Der EVR verwendet diese Schnittstelle, um den Referenten zu benachrichtigen, wenn sich der Status der Systemuhr des EVR ändert. Weitere Informationen zu den Systemuhrzuständen finden Sie unter Presentation Clock (Präsentationssystemuhr).

Hier sind einige Richtlinien für die Implementierung der Methoden in dieser Schnittstelle. Alle Methoden sollten fehlschlagen, wenn der Referent heruntergefahren wird.

Methode Beschreibung
OnClockStart
  1. Legen Sie den Referentenstatus so fest, dass er gestartet wird.
  2. Wenn llClockStartOffset nicht PRESENTATION_CURRENT_POSITION ist, leeren Sie die Beispielwarteschlange des Referenten. (Dies entspricht dem Empfang einer MFVP_MESSAGE_FLUSH-Nachricht.)
  3. Wenn eine vorherige Frame-Step-Anforderung noch aussteht, verarbeiten Sie die Anforderung (siehe FrameStep). Versuchen Sie andernfalls, die Ausgabe des Mixers zu verarbeiten (siehe Verarbeitungsausgabe).
OnClockStop
  1. Stellen Sie den Referentenstatus auf beendet ein.
  2. Leeren Sie die Beispielwarteschlange des Referenten.
  3. Abbrechen eines ausstehenden Frame-Step-Vorgangs.
OnClockPause Stellen Sie den Referentenstatus auf angehalten ein.
OnClockRestart Behandeln Sie es wie OnClockStart, aber leeren Sie die Beispielwarteschlange nicht.
OnClockSetRate
  1. Wenn sich die Rate von Null auf einen Wert ungleich Null ändert, heben Sie den Frame Stepping-Vorgang auf.
  2. Speichern Sie die neue Taktfrequenz. Die Taktfrequenz wirkt sich auf die Darstellung von Beispielen aus. Weitere Informationen finden Sie unter Planungsbeispiele.

 

Implementieren von IMFRateSupport

Um andere Wiedergaberaten als 1× Geschwindigkeit zu unterstützen, muss der Referent die Schnittstelle IMFRateSupport implementieren. Hier sind einige Richtlinien für die Implementierung der Methoden in dieser Schnittstelle. Alle Methoden sollten nach dem Herunterfahren des Referenten fehlschlagen. Weitere Informationen zu dieser Schnittstelle finden Sie unter Ratenkontrolle.

Wert Beschreibung
GetSlowestRate Geben Sie Null zurück, um anzuzeigen, dass es keine Mindestwiedergaberate gibt.
GetFastestRate Bei nicht ausgedünnter Wiedergabe sollte die Wiedergaberate die Aktualisierungsrate des Monitors nicht überschreiten: Maximale Rate = Aktualisierungsrate (Hz)/Videoframerate (FPS). Die Videoframerate wird im Medientyp des Referenten angegeben.
Bei der ausgedünnten Wiedergabe ist die Wiedergaberate unbegrenzt. Geben Sie den Wert FLT_MAX zurück. In der Praxis sind die Quelle und der Decoder die begrenzenden Faktoren bei der ausgedünnten Wiedergabe.
Geben Sie bei umgekehrter Wiedergabe die negative Rate der maximalen Rate zurück.
IsRateSupported Geben Sie MF_E_UNSUPPORTED_RATE zurück, wenn der absolute Wert von flRate die maximale Wiedergaberate des Referenten überschreitet. Berechnen Sie die maximale Wiedergaberate, wie für GetFastestRate beschrieben.

 

Das folgende Beispiel zeigt, wie die GetFastestRate-Methode implementiert wird:

float EVRCustomPresenter::GetMaxRate(BOOL bThin)
{
    // Non-thinned:
    // If we have a valid frame rate and a monitor refresh rate, the maximum
    // playback rate is equal to the refresh rate. Otherwise, the maximum rate
    // is unbounded (FLT_MAX).

    // Thinned: The maximum rate is unbounded.

    float   fMaxRate = FLT_MAX;
    MFRatio fps = { 0, 0 };
    UINT    MonitorRateHz = 0;

    if (!bThin && (m_pMediaType != NULL))
    {
        GetFrameRate(m_pMediaType, &fps);
        MonitorRateHz = m_pD3DPresentEngine->RefreshRate();

        if (fps.Denominator && fps.Numerator && MonitorRateHz)
        {
            // Max Rate = Refresh Rate / Frame Rate
            fMaxRate = (float)MulDiv(
                MonitorRateHz, fps.Denominator, fps.Numerator);
        }
    }

    return fMaxRate;
}

Im vorherigen Beispiel wird eine Hilfsmethode GetMaxRate aufgerufen, um die maximale Vorwärtswiedergaberate zu berechnen:

Das folgende Beispiel zeigt, wie die IsRateSupported-Methode implementiert wird:

HRESULT EVRCustomPresenter::IsRateSupported(
    BOOL bThin,
    float fRate,
    float *pfNearestSupportedRate
    )
{
    EnterCriticalSection(&m_ObjectLock);

    float   fMaxRate = 0.0f;
    float   fNearestRate = fRate;  // If we support fRate, that is the nearest.

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

    // Find the maximum forward rate.
    // Note: We have no minimum rate (that is, we support anything down to 0).
    fMaxRate = GetMaxRate(bThin);

    if (fabsf(fRate) > fMaxRate)
    {
        // The (absolute) requested rate exceeds the maximum rate.
        hr = MF_E_UNSUPPORTED_RATE;

        // The nearest supported rate is fMaxRate.
        fNearestRate = fMaxRate;
        if (fRate < 0)
        {
            // Negative for reverse playback.
            fNearestRate = -fNearestRate;
        }
    }

    // Return the nearest supported rate.
    if (pfNearestSupportedRate != NULL)
    {
        *pfNearestSupportedRate = fNearestRate;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Senden von Ereignissen an den EVR

Der Referent muss den EVR über verschiedene Ereignisse benachrichtigen. Dazu wird die IMediaEventSink-Schnittstelle des EVR verwendet, die abgerufen wird, wenn der EVR die IMFTopologyServiceLookupClient::InitServicePointers-Methode aufruft. (Die Die IMediaEventSink-Schnittstelle ist ursprünglich eine DirectShow-Schnittstelle, wird jedoch sowohl in DirectShow EVR als auch in Media Foundation verwendet.) Der folgende Code zeigt, wie ein Ereignis an den EVR gesendet wird:

    // NotifyEvent: Send an event to the EVR through its IMediaEventSink interface.
    void NotifyEvent(long EventCode, LONG_PTR Param1, LONG_PTR Param2)
    {
        if (m_pMediaEventSink)
        {
            m_pMediaEventSink->Notify(EventCode, Param1, Param2);
        }
    }

In der folgenden Tabelle sind die Ereignisse aufgeführt, die der Referent sendet, zusammen mit den Ereignisparametern.

Event BESCHREIBUNG
EC_COMPLETE Der Referent hat das Rendern aller Frames nach der MFVP_MESSAGE_ENDOFSTREAM-Meldung abgeschlossen.
  • Param1: HRESULT, der den Status des Vorgangs angibt.
  • Param2: Nicht verwendet.
Weitere Informationen finden Sie unter Ende des Streams.
EC_DISPLAY_CHANGED Das Direct3D-Gerät wurde geändert.
  • Param1: Nicht verwendet.
  • Param2: Nicht verwendet.
Weitere Informationen finden Sie unter Verwalten des Direct3D-Geräts.
EC_ERRORABORT Es ist ein Fehler aufgetreten, bei dem das Streaming beendet werden muss.
  • Param1: HRESULT, der den aufgetretenen Fehler angibt.
  • Param2: Nicht verwendet.
EC_PROCESSING_LATENCY Gibt die Zeitspanne an, die der Referent zum Rendern jedes Frames benötigt. (Optional.)
  • Param1: Zeiger auf einen konstanten LONGLONG-Wert, der die Zeitspanne zum Verarbeiten des Frames in 100-Nanosekunden-Einheiten enthält.
  • Param2: Nicht verwendet.
Weitere Informationen finden Sie unter Verarbeitungsausgabe.
EC_SAMPLE_LATENCY Gibt die aktuelle Verzögerungszeit beim Rendern von Beispielen an. Wenn der Wert positiv ist, liegen die Beispiele hinter dem Zeitplan. Wenn der Wert negativ ist, liegen die Beispiele vor dem Zeitplan. (Optional.)
  • Param1 : Zeiger auf einen konstanten LONGLONG-Wert, der die Verzögerungszeit in 100-Nanosekunden-Einheiten enthält.
  • Param2: Nicht verwendet.
EC_SCRUB_TIME Wird unmittelbar nach EC_STEP_COMPLETE gesendet, wenn die Wiedergaberate Null ist. Dieses Ereignis enthält den Zeitstempel des angezeigten Frames.
  • Param1: Untere 32 Bit des Zeitstempels.
  • Param2: Obere 32 Bit des Zeitstempels.
Weitere Informationen finden Sie unter Frame Stepping.
EC_STEP_COMPLETE Der Referent hat einen Frame Step abgeschlossen oder abgebrochen.
- Param1: Nicht verwendet.
- Param2: Nicht verwendet.
Weitere Informationen finden Sie unter Frame Stepping.
Hinweis: Eine frühere Version der Dokumentation, die Param1 falsch beschrieben hat. Dieser Parameter wird für dieses Ereignis nicht verwendet.

 

Verhandlungsformate

Wenn der Referent eine MFVP_MESSAGE_INVALIDATEMEDIATYPE-Meldung vom EVR empfängt, muss er das Ausgabeformat auf dem Mixer wie folgt einstellen:

  1. Rufen Sie IMFTransform::GetOutputAvailableType auf dem Mixer auf, um einen möglichen Ausgabetyp zu erhalten. Dieser Typ beschreibt ein Format, das der Mixer aufgrund der Eingabestreams und der Videoverarbeitungsfunktionen des Grafikgeräts erzeugen kann.

  2. Überprüfen Sie, ob der Referent diesen Medientyp als Renderingformat verwenden kann. Hier sind einige Dinge zu überprüfen, obwohl Ihre Implementierung möglicherweise eigene Anforderungen hat:

    • Das Video muss nicht komprimiert werden.
    • Das Video darf nur progressive Frames enthalten. Überprüfen Sie, ob das MF_MT_INTERLACE_MODE-Attribut MFVideoInterlace_Progressive entspricht.
    • Das Format muss mit dem Direct3D-Gerät kompatibel sein.

    Wenn der Typ nicht akzeptabel ist, kehren Sie zu Schritt 1 zurück, und rufen Sie den nächsten vorgeschlagenen Typ des Mixers ab.

  3. Erstellen Sie einen neuen Medientyp, der ein Klon des ursprünglichen Typs ist, und ändern Sie dann die folgenden Attribute:

    • Stellen Sie das MF_MT_FRAME_SIZE-Attribut auf die gewünschte Breite und Höhe für die Direct3D-Oberflächen ein, die Sie zuordnen möchten.
    • Stellen Sie das MF_MT_PAN_SCAN_ENABLED auf FALSCH ein.
    • Stellen Sie das MF_MT_PIXEL_ASPECT_RATIO-Attribut gleich dem PAR der Anzeige ein (in der Regel 1:1).
    • Stellen Sie das Attribut der geometrischen Blende (MF_MT_GEOMETRIC_APERTURE) auf ein Rechteck innerhalb der Direct3D-Oberfläche ein. Wenn der Mixer einen Ausgabeframe erzeugt, blendet er das Quellbild auf dieses Rechteck. Die geometrische Blende kann so groß wie die Oberfläche sein, oder es kann sich um ein Unterrechteck innerhalb der Oberfläche handeln. Weitere Informationen finden Sie unter Quell- und Zielrechtecke.
  4. Um zu testen, ob der Mixer den geänderten Ausgabetyp akzeptiert, rufen Sie IMFTransform::SetOutputType mit dem MFT_SET_TYPE_TEST_ONLY-Flag auf. Wenn der Mixer den Typ ablehnt, wechseln Sie zurück zu Schritt 1 und rufen Sie den nächsten Typ ab.

  5. Weisen Sie einen Pool mit Direct3D-Oberflächen zu, wie in der Zuordnung von Direct3D-Oberflächen beschrieben. Der Mixer verwendet diese Oberflächen, wenn er die zusammengesetzten Videoframes zeichnet.

  6. Legen Sie den Ausgabetyp auf dem Mixer fest, indem Sie SetOutputType ohne Flags aufrufen. Wenn der erste Aufruf von SetOutputType in Schritt 4 erfolgreich war, sollte die Methode erneut erfolgreich sein.

Wenn der Mixer keine Typen enthält, gibt die GetOutputAvailableType-Methode MF_E_NO_MORE_TYPES zurück. Wenn der Referent keinen geeigneten Ausgabetyp für den Mixer finden kann, kann der Datenstrom nicht gerendert werden. In diesem Fall kann DirectShow oder Media Foundation ein anderes Streamformat ausprobieren. Daher empfängt der Referent möglicherweise mehrere MFVP_MESSAGE_INVALIDATEMEDIATYPE-Meldungen in einer Zeile, bis ein gültiger Typ gefunden wird.

Der Mischer nimmt automatisch eine Letterbox-Bearbeitung des Videos vor und berücksichtigt dabei das Pixelseitenverhältnis (PAR) der Quelle und des Ziels. Die besten Ergebnisse erzielen Sie, wenn die Breite und Höhe der Oberfläche und die geometrische Blende der tatsächlichen Größe entsprechen, in der das Video auf dem Bildschirm erscheinen soll. Das folgende Bild zeigt diesen Prozess.

diagram showing a composited fram leading to a direct3d surface, which leads to a window

Der folgende Code zeigt die Gliederung des Prozesses. Einige der Schritte werden in Hilfsfunktionen eingefügt, die genauen Details, die von den Anforderungen Ihres Referenten abhängen.

HRESULT EVRCustomPresenter::RenegotiateMediaType()
{
    HRESULT hr = S_OK;
    BOOL bFoundMediaType = FALSE;

    IMFMediaType *pMixerType = NULL;
    IMFMediaType *pOptimalType = NULL;
    IMFVideoMediaType *pVideoType = NULL;

    if (!m_pMixer)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Loop through all of the mixer's proposed output types.
    DWORD iTypeIndex = 0;
    while (!bFoundMediaType && (hr != MF_E_NO_MORE_TYPES))
    {
        SafeRelease(&pMixerType);
        SafeRelease(&pOptimalType);

        // Step 1. Get the next media type supported by mixer.
        hr = m_pMixer->GetOutputAvailableType(0, iTypeIndex++, &pMixerType);
        if (FAILED(hr))
        {
            break;
        }

        // From now on, if anything in this loop fails, try the next type,
        // until we succeed or the mixer runs out of types.

        // Step 2. Check if we support this media type.
        if (SUCCEEDED(hr))
        {
            // Note: None of the modifications that we make later in CreateOptimalVideoType
            // will affect the suitability of the type, at least for us. (Possibly for the mixer.)
            hr = IsMediaTypeSupported(pMixerType);
        }

        // Step 3. Adjust the mixer's type to match our requirements.
        if (SUCCEEDED(hr))
        {
            hr = CreateOptimalVideoType(pMixerType, &pOptimalType);
        }

        // Step 4. Check if the mixer will accept this media type.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, MFT_SET_TYPE_TEST_ONLY);
        }

        // Step 5. Try to set the media type on ourselves.
        if (SUCCEEDED(hr))
        {
            hr = SetMediaType(pOptimalType);
        }

        // Step 6. Set output media type on mixer.
        if (SUCCEEDED(hr))
        {
            hr = m_pMixer->SetOutputType(0, pOptimalType, 0);

            assert(SUCCEEDED(hr)); // This should succeed unless the MFT lied in the previous call.

            // If something went wrong, clear the media type.
            if (FAILED(hr))
            {
                SetMediaType(NULL);
            }
        }

        if (SUCCEEDED(hr))
        {
            bFoundMediaType = TRUE;
        }
    }

    SafeRelease(&pMixerType);
    SafeRelease(&pOptimalType);
    SafeRelease(&pVideoType);

    return hr;
}

Weitere Informationen zu Videomedientypen finden Sie unter Videomedientypen.

Verwalten des Direct3D-Geräts

Der Referent erstellt das Direct3D-Gerät und behandelt alle Geräteverluste während des Streamings. Der Referent hostet auch den Direct3D-Gerätemanager, der anderen Komponenten die Möglichkeit bietet, dasselbe Gerät zu verwenden. Der Mixer verwendet zum Beispiel das Direct3D-Gerät, um Substreams zu mischen, das Zeilensprungverfahren zu entfernen (Deinterlace) und Farbanpassungen vorzunehmen. Decoder können das Direct3D-Gerät für die videobeschleunigte Decodierung verwenden. (Weitere Informationen zur Videobeschleunigung finden Sie unter DirectX Video Acceleration 2.0.)

Führen Sie die folgenden Schritte aus, um das Direct3D-Gerät einzurichten:

  1. Erstellen Sie das Direct3D-Objekt, indem Sie Direct3DCreate9 oderDirect3DCreate9Ex aufrufen.
  2. Erstellen Sie das Gerät durch Aufrufen von IDirect3D9::CreateDevice oder IDirect3D9Ex::CreateDevice.
  3. Erstellen Sie den Gerätemanager, indem Sie DXVA2CreateDirect3DDeviceManager9 aufrufen.
  4. Legen Sie das Gerät im Gerätemanager fest, indem Sie IDirect3DDeviceManager9::ResetDevice aufrufen.

Wenn eine andere Pipelinekomponente den Gerätemanager benötigt, ruft sie IMFGetService::GetService für den EVR auf und gibt MR_VIDEO_ACCELERATION_SERVICE für die Dienst-GUID an. Der EVR übergibt die Anforderung an den Referenten. Nachdem das Objekt den IDirect3DDeviceManager9-Zeiger erhalten hat, kann es durch den Aufruf von IDirect3DDeviceManager9::OpenDeviceHandle einen Ziehpunkt für das Gerät erhalten. Wenn das Objekt das Gerät verwenden muss, übergibt es den Geräteziehpunkt an die Methode IDirect3DDeviceManager9::LockDevice, die einen IDirect3DDevice9-Zeiger zurückgibt.

Nachdem das Gerät erstellt wurde, zerstört der Referent das Gerät und erstellt ein neues Gerät, muss der Referent ResetDevice erneut aufrufen. Mit der ResetDevice-Methode werden alle vorhandenen Geräteziehpunkte ungültig, wodurch LockDeviceDXVA2_E_NEW_VIDEO_DEVICE zurückgibt. Dieser Fehlercode signalisiert anderen Objekten, die das Gerät verwenden, dass sie einen neuen Geräteziehpunkt öffnen sollten. Weitere Informationen zur Verwendung des Gerätemanagers finden Sie unter Direct3D-Gerätemanager.

Der Referent kann das Gerät im Fenstermodus oder im exklusiven Vollbildmodus erstellen. Für den Fenstermodus sollten Sie eine Möglichkeit für die Anwendung bereitstellen, das Videofenster anzugeben. Der Standardreferent implementiert hierfür die IMFVideoDisplayControl::SetVideoWindow-Methode. Sie müssen das Gerät erstellen, wenn der Referent zum ersten Mal erstellt wird. Normalerweise kennen Sie derzeit nicht alle Geräteparameter, z. B. das Fenster oder das Hintergrundpufferformat. Sie können ein temporäres Gerät erstellen und später ersetzen.&#;Denken Sie nur daran, ResetDevice im Gerätemanager aufzurufen.

Wenn Sie ein neues Gerät erstellen oder IDirect3DDevice9::Reset oder IDirect3DDevice9Ex::ResetEx auf einem vorhandenen Gerät aufrufen, senden Sie ein EC_DISPLAY_CHANGED-Ereignis an den EVR. Dieses Ereignis benachrichtigt das EVR, den Medientyp neu zu verhandeln. Der EVR ignoriert die Ereignisparameter für dieses Ereignis.

Zuordnen von Direct3D-Oberflächen

Nachdem der Referenten den Medientyp festgelegt hat, kann er die Direct3D-Oberflächen zuordnen, die der Mixer zum Schreiben der Videoframes verwendet. Die Oberfläche muss mit dem Medientyp des Referenten übereinstimmen:

  • Das Oberflächenformat muss mit dem Medienuntertyp übereinstimmen. Wenn der Untertyp beispielsweise MFVideoFormat_RGB24 ist, muss das Oberflächenformat D3DFMT_X8R8G8B8 werden. Weitere Informationen zu Subtypen und Direct3D-Formaten finden Sie unter Videosubtyp-GUIDs.
  • Die Breite und Höhe der Oberfläche muss mit den Abmessungen übereinstimmen, die im Attribut MF_MT_FRAME_SIZE des Medientyps angegeben sind.

Die empfohlene Methode zum Zuordnen von Oberflächen hängt davon ab, ob der Referent Fenster- oder Vollbildmodus ausführt.

Wenn das Direct3D-Gerät mit Fenstern versehen ist, können Sie mehrere Swapchains erstellen, jeweils mit einem einzelnen Hintergrundpuffer. Mit diesem Ansatz können Sie jede Oberfläche unabhängig darstellen, da die Darstellung einer Swapchain die anderen Swapchains nicht beeinträchtigt. Der Mixer kann Daten auf eine Oberfläche schreiben, während eine andere Oberfläche für die Präsentation geplant ist.

Entscheiden Sie zunächst, wie viele Swapchains erstellt werden sollen. Es werden mindestens drei empfohlen. Gehen Sie für jede Swapchain wie folgt vor:

  1. Rufen Sie IDirect3DDevice9::CreateAdditionalSwapChain auf, um die Swapchain zu erstellen.
  2. Rufen Sie IDirect3DSwapChain9::GetBackBuffer auf, um einen Zeiger auf die Hintergrundpufferoberfläche der Swapchain abzurufen.
  3. Rufen Sie MFCreateVideoSampleFromSurface auf und übergeben Sie einen Zeiger an die Oberfläche. Diese Funktion gibt einen Zeiger auf ein Videobeispielobjekt zurück. Das Videobeispielobjekt implementiert die IMFSample-Schnittstelle und der Referent verwendet diese Schnittstelle, um die Oberfläche an den Mixer zu liefern, wenn der Referent die IMFTransform::ProcessOutput-Methode des Mixers aufruft. Weitere Informationen zum Videobeispielobjekt finden Sie unter Videobeispiele.
  4. Speichern Sie den IMFSample-Zeiger in einer Warteschlange. Der Referent ruft während der Verarbeitung Beispiele aus dieser Warteschlange ab, wie in der Verarbeitungsausgabe beschrieben.
  5. Bewahren Sie einen Verweis auf den IDirect3DSwapChain9-Zeiger auf, damit die Swapchain nicht freigegeben wird.

Im exklusiven Vollbildmodus darf das Gerät nicht über mehrere Swapchains verfügen. Diese Swapchain wird implizit erstellt, wenn Sie das Vollbildgerät erstellen. Die Swapchain kann mehrere Hintergrundpuffer aufweisen. Wenn Sie jedoch einen Hintergrundpuffer präsentieren, während Sie in derselben Swapchain in einen anderen Hintergrundpuffer schreiben, gibt es keine einfache Möglichkeit, die beiden Vorgänge zu koordinieren. Das liegt an der Art und Weise, wie Direct3D das Spiegeln von Oberflächen implementiert. Wenn Sie „Present“ aufrufen, aktualisiert der Grafiktreiber die Surface-Zeiger im Grafikspeicher. Wenn Sie beim Aufruf von Present einen IDirect3DSurface9-Zeiger halten, zeigen diese nach der Rückkehr des Present-Aufrufs auf andere Puffer.

Die einfachste Option besteht darin, ein Videobeispiel für die Swapchain zu erstellen. Wenn Sie diese Option auswählen, führen Sie die gleichen Schritte aus, die für den Fenstermodus angegeben sind. Der einzige Unterschied besteht darin, dass die Beispielwarteschlange ein einzelnes Videobeispiel enthält. Eine andere Möglichkeit besteht darin, Flächen außerhalb des Bildschirms zu erstellen und sie dann in den Hintergrundpuffer zu blenden. Die von Ihnen erstellten Oberflächen müssen die IDirectXVideoProcessor::VideoProcessBlt-Methode unterstützen, die der Mixer verwendet, um die Ausgabeframes zusammenzusetzen.

Nachverfolgungsbeispiele

Wenn der Referent die Videobeispiele zum ersten Mal zuweist, platziert er sie in einer Warteschlange. Der Referent zeichnet aus dieser Warteschlange, wenn er einen neuen Frame vom Mixer abrufen muss. Nachdem der Mixer den Frame ausgegeben hat, verschiebt der Referent das Beispiel in eine zweite Warteschlange. Die zweite Warteschlange ist für Beispiele, die auf ihre geplante Präsentationszeit warten.

Um das Nachverfolgen des Status jedes Beispiels zu vereinfachen, implementiert das Videobeispielobjekt die IMFTrackedSample-Schnittstelle. Sie können diese Schnittstelle wie folgt verwenden:

  1. Implementieren Sie die IMFAsyncCallback-Schnittstelle in Ihrem Referenten.

  2. Bevor Sie ein Beispiel in der geplanten Warteschlange platzieren, fragen Sie das Videobeispielobjekt für die IMFTrackedSample-Schnittstelle ab.

  3. Rufen Sie IMFTrackedSample::SetAllocator mit einem Zeiger auf die Rückrufschnittstelle auf.

  4. Wenn das Beispiel für die Präsentation bereit ist, entfernen Sie es aus der geplanten Warteschlange, präsentieren Sie es und geben Sie alle Verweise auf das Beispiel frei.

  5. Im Beispiel wird der Rückruf aufgerufen. (Das Beispielobjekt wird in diesem Fall nicht gelöscht, da es eine Verweisanzahl für sich selbst enthält, bis der Rückruf aufgerufen wird.)

  6. Geben Sie innerhalb des Rückrufs das Beispiel an die verfügbare Warteschlange zurück.

Ein Referent ist nicht erforderlich, um Beispiele mithilfe von IMFTrackedSample nachzuverfolgen. Sie können jede beliebige Technik einsetzen, die für Ihr Design am besten geeignet ist. Ein Vorteil von IMFTrackedSample ist, dass Sie die Planungs- und Renderingfunktionen des Referenten in Hilfsobjekte verschieben können, und diese Objekte benötigen keinen speziellen Mechanismus zum Aufrufen des Referenten, wenn sie Videobeispiele freigeben, da das Beispielobjekt diesen Mechanismus bereitstellt.

Der folgende Code zeigt, wie Sie den Callback einstellen:

HRESULT EVRCustomPresenter::TrackSample(IMFSample *pSample)
{
    IMFTrackedSample *pTracked = NULL;

    HRESULT hr = pSample->QueryInterface(IID_PPV_ARGS(&pTracked));

    if (SUCCEEDED(hr))
    {
        hr = pTracked->SetAllocator(&m_SampleFreeCB, NULL);
    }

    SafeRelease(&pTracked);
    return hr;
}

Rufen Sie im Callback IMFAsyncResult::GetObject für das asynchrone Ergebnisobjekt auf, um einen Zeiger auf das Beispiel abzurufen:

HRESULT EVRCustomPresenter::OnSampleFree(IMFAsyncResult *pResult)
{
    IUnknown *pObject = NULL;
    IMFSample *pSample = NULL;
    IUnknown *pUnk = NULL;

    // Get the sample from the async result object.
    HRESULT hr = pResult->GetObject(&pObject);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pObject->QueryInterface(IID_PPV_ARGS(&pSample));
    if (FAILED(hr))
    {
        goto done;
    }

    // If this sample was submitted for a frame-step, the frame step operation
    // is complete.

    if (m_FrameStep.state == FRAMESTEP_SCHEDULED)
    {
        // Query the sample for IUnknown and compare it to our cached value.
        hr = pSample->QueryInterface(IID_PPV_ARGS(&pUnk));
        if (FAILED(hr))
        {
            goto done;
        }

        if (m_FrameStep.pSampleNoRef == (DWORD_PTR)pUnk)
        {
            // Notify the EVR.
            hr = CompleteFrameStep(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        // Note: Although pObject is also an IUnknown pointer, it is not
        // guaranteed to be the exact pointer value returned through
        // QueryInterface. Therefore, the second QueryInterface call is
        // required.
    }

    /*** Begin lock ***/

    EnterCriticalSection(&m_ObjectLock);

    UINT32 token = MFGetAttributeUINT32(
        pSample, MFSamplePresenter_SampleCounter, (UINT32)-1);

    if (token == m_TokenCounter)
    {
        // Return the sample to the sample pool.
        hr = m_SamplePool.ReturnSample(pSample);
        if (SUCCEEDED(hr))
        {
            // A free sample is available. Process more data if possible.
            (void)ProcessOutputLoop();
        }
    }

    LeaveCriticalSection(&m_ObjectLock);

    /*** End lock ***/

done:
    if (FAILED(hr))
    {
        NotifyEvent(EC_ERRORABORT, hr, 0);
    }
    SafeRelease(&pObject);
    SafeRelease(&pSample);
    SafeRelease(&pUnk);
    return hr;
}

Verarbeitungsausgabe

Wenn der Mixer ein neues Eingabebeispiel empfängt, sendet der EVR eine MFVP_MESSAGE_PROCESSINPUTNOTIFY-Meldung an den Referenten. Diese Meldung gibt an, dass der Mixer möglicherweise einen neuen Videoframe zum Übermitteln hat. Als Reaktion ruft der Referent IMFTransform::P rocessOutput auf dem Mixer auf. Wenn die Methode erfolgreich ist, plant der Referent das Beispiel für die Präsentation.

Führen Sie die folgenden Schritte aus, um die Ausgabe des Mixers zu erhalten:

  1. Überprüfen Sie den Systemuhrzustand. Wenn die Systemuhr angehalten wird, ignorieren Sie die MFVP_MESSAGE_PROCESSINPUTNOTIFY-Meldung, es sei denn, dies ist der erste Videoframe. Wenn die Systemuhr ausgeführt wird oder dies der erste Videoframe ist, fahren Sie fort.

  2. Rufen Sie ein Beispiel aus der Warteschlange der verfügbaren Beispiele ab. Wenn die Warteschlange leer ist, bedeutet dies, dass alle zugeordneten Beispiele zurzeit für die Präsentation geplant sind. Ignorieren Sie in diesem Fall die MFVP_MESSAGE_PROCESSINPUTNOTIFY-Meldung zu diesem Zeitpunkt. Wenn das nächste Beispiel verfügbar ist, wiederholen Sie die hier aufgeführten Schritte.

  3. (Optional.) Wenn die Systemuhr verfügbar ist, rufen Sie die aktuelle Uhrzeit (T1) ab, indem Sie IMFClock::GetCorrelatedTime aufrufen.

  4. Rufen Sie IMFTransform::ProcessOutput auf dem Mixer auf. Wenn ProcessOutput erfolgreich ist, enthält das Beispiel einen Videoframe. Wenn die Methode fehlschlägt, überprüfen Sie den Rückgabecode. Die folgenden Fehlercodes von ProcessOutput sind keine kritischen Fehler:

    Fehlercode Beschreibung
    MF_E_TRANSFORM_NEED_MORE_INPUT Der Mixer benötigt weitere Eingaben, bevor er einen neuen Ausgabeframe erzeugen kann.
    Wenn Sie diesen Fehlercode erhalten, überprüfen Sie, ob das EVR das Ende des Streams erreicht hat und reagieren Sie entsprechend, wie in Ende des Streams beschrieben. Ignorieren Sie andernfalls diese MF_E_TRANSFORM_NEED_MORE_INPUT-Meldung. Der EVR sendet eine weitere, wenn der Mixer weitere Eingaben erhält.
    MF_E_TRANSFORM_STREAM_CHANGE Der Ausgabetyp des Mixers ist ungültig geworden, möglicherweise aufgrund einer Formatänderung im Vorfeld.
    Wenn Sie diesen Fehlercode erhalten, legen Sie den Medientyp des Referenten auf NULL fest. Das EVR fordert ein neues Format an.
    MF_E_TRANSFORM_TYPE_NOT_SET Der Mixer erfordert einen neuen Medientyp.
    Wenn Sie diesen Fehlercode erhalten, sollten Sie den Ausgabetyp des Mixers neu verhandeln, wie in Verhandlungsformaten beschrieben.

     

    Wenn ProcessOutput erfolgreich ist, fahren Sie fort.

  5. (Optional.) Wenn die Systemuhr verfügbar ist, rufen Sie die aktuelle Uhrzeit (T2) ab. Die vom Mixer eingeführte Latenz ist (T2 - T1). Senden Sie ein EC_PROCESSING_LATENCY-Ereignis mit diesem Wert an den EVR. Der EVR verwendet diesen Wert für die Qualitätskontrolle. Wenn keine Systemuhr verfügbar ist, gibt es keinen Grund, das EC_PROCESSING_LATENCY-Ereignis zu senden.

  6. (Optional.) Fragen Sie das Beispiel für IMFTrackedSample ab, und rufen Sie IMFTrackedSample::SetAllocator auf, wie in den Nachverfolgungsbeispielen beschrieben.

  7. Planen Sie das Beispiel für die Präsentation.

Diese Abfolge von Schritten kann beendet werden, bevor der Referent eine Ausgabe vom Mixer erhält. Um sicherzustellen, dass keine Anfragen verworfen werden, sollten Sie die gleichen Schritte wiederholen, wenn Folgendes eintritt:

  • Die IMFClockStateSink::OnClockStart - oder IMFClockStateSink::OnClockStart-Methode wird aufgerufen. Damit wird der Fall behandelt, dass der Mixer die Eingabe ignoriert, weil die Systemuhr angehalten wurde (Schritt 1).
  • Der IMFTrackedSample-Callback wird aufgerufen. Dies behandelt den Fall, in dem der Mixer Eingaben empfängt, aber alle Videobeispiele des Referenten werden verwendet (Schritt 2).

Die nächsten Codebeispiele zeigen diese Schritte ausführlicher. Der Referent ruft die ProcessInputNotify Methode (im folgenden Beispiel gezeigt) auf, wenn er die MFVP_MESSAGE_PROCESSINPUTNOTIFY-Meldung erhält.

//-----------------------------------------------------------------------------
// ProcessInputNotify
//
// Attempts to get a new output sample from the mixer.
//
// This method is called when the EVR sends an MFVP_MESSAGE_PROCESSINPUTNOTIFY
// message, which indicates that the mixer has a new input sample.
//
// Note: If there are multiple input streams, the mixer might not deliver an
// output sample for every input sample.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessInputNotify()
{
    HRESULT hr = S_OK;

    // Set the flag that says the mixer has a new sample.
    m_bSampleNotify = TRUE;

    if (m_pMediaType == NULL)
    {
        // We don't have a valid media type yet.
        hr = MF_E_TRANSFORM_TYPE_NOT_SET;
    }
    else
    {
        // Try to process an output sample.
        ProcessOutputLoop();
    }
    return hr;
}

Diese ProcessInputNotify Methode legt ein boolesches Flag fest, um die Tatsache aufzuzeichnen, dass der Mixer neue Eingaben hat. Anschließend wird die ProcessOutputLoop-Methode aufgerufen, die im nächsten Beispiel gezeigt wird. Diese Methode versucht, so viele Beispiele wie möglich aus dem Mixer zu ziehen:

void EVRCustomPresenter::ProcessOutputLoop()
{
    HRESULT hr = S_OK;

    // Process as many samples as possible.
    while (hr == S_OK)
    {
        // If the mixer doesn't have a new input sample, break from the loop.
        if (!m_bSampleNotify)
        {
            hr = MF_E_TRANSFORM_NEED_MORE_INPUT;
            break;
        }

        // Try to process a sample.
        hr = ProcessOutput();

        // NOTE: ProcessOutput can return S_FALSE to indicate it did not
        // process a sample. If so, break out of the loop.
    }

    if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
    {
        // The mixer has run out of input data. Check for end-of-stream.
        CheckEndOfStream();
    }
}

Die im nächsten Beispiel gezeigte ProcessOutput-Methode versucht, einen einzelnen Videoframe vom Mixer zu erhalten. Wenn kein Videoframe verfügbar ist, ProcessSample wird S_FALSE oder ein Fehlercode zurückgegeben, von dem die Schleife ProcessOutputLoop unterbrochen wird. Der Großteil der Arbeit erfolgt innerhalb der ProcessOutput-Methode:

//-----------------------------------------------------------------------------
// ProcessOutput
//
// Attempts to get a new output sample from the mixer.
//
// Called in two situations:
// (1) ProcessOutputLoop, if the mixer has a new input sample.
// (2) Repainting the last frame.
//-----------------------------------------------------------------------------

HRESULT EVRCustomPresenter::ProcessOutput()
{
    assert(m_bSampleNotify || m_bRepaint);  // See note above.

    HRESULT     hr = S_OK;
    DWORD       dwStatus = 0;
    LONGLONG    mixerStartTime = 0, mixerEndTime = 0;
    MFTIME      systemTime = 0;
    BOOL        bRepaint = m_bRepaint; // Temporarily store this state flag.

    MFT_OUTPUT_DATA_BUFFER dataBuffer;
    ZeroMemory(&dataBuffer, sizeof(dataBuffer));

    IMFSample *pSample = NULL;

    // If the clock is not running, we present the first sample,
    // and then don't present any more until the clock starts.

    if ((m_RenderState != RENDER_STATE_STARTED) &&  // Not running.
         !m_bRepaint &&             // Not a repaint request.
         m_bPrerolled               // At least one sample has been presented.
         )
    {
        return S_FALSE;
    }

    // Make sure we have a pointer to the mixer.
    if (m_pMixer == NULL)
    {
        return MF_E_INVALIDREQUEST;
    }

    // Try to get a free sample from the video sample pool.
    hr = m_SamplePool.GetSample(&pSample);
    if (hr == MF_E_SAMPLEALLOCATOR_EMPTY)
    {
        // No free samples. Try again when a sample is released.
        return S_FALSE;
    }
    else if (FAILED(hr))
    {
        return hr;
    }

    // From now on, we have a valid video sample pointer, where the mixer will
    // write the video data.
    assert(pSample != NULL);

    // (If the following assertion fires, it means we are not managing the sample pool correctly.)
    assert(MFGetAttributeUINT32(pSample, MFSamplePresenter_SampleCounter, (UINT32)-1) == m_TokenCounter);

    if (m_bRepaint)
    {
        // Repaint request. Ask the mixer for the most recent sample.
        SetDesiredSampleTime(
            pSample,
            m_scheduler.LastSampleTime(),
            m_scheduler.FrameDuration()
            );

        m_bRepaint = FALSE; // OK to clear this flag now.
    }
    else
    {
        // Not a repaint request. Clear the desired sample time; the mixer will
        // give us the next frame in the stream.
        ClearDesiredSampleTime(pSample);

        if (m_pClock)
        {
            // Latency: Record the starting time for ProcessOutput.
            (void)m_pClock->GetCorrelatedTime(0, &mixerStartTime, &systemTime);
        }
    }

    // Now we are ready to get an output sample from the mixer.
    dataBuffer.dwStreamID = 0;
    dataBuffer.pSample = pSample;
    dataBuffer.dwStatus = 0;

    hr = m_pMixer->ProcessOutput(0, 1, &dataBuffer, &dwStatus);

    if (FAILED(hr))
    {
        // Return the sample to the pool.
        HRESULT hr2 = m_SamplePool.ReturnSample(pSample);
        if (FAILED(hr2))
        {
            hr = hr2;
            goto done;
        }
        // Handle some known error codes from ProcessOutput.
        if (hr == MF_E_TRANSFORM_TYPE_NOT_SET)
        {
            // The mixer's format is not set. Negotiate a new format.
            hr = RenegotiateMediaType();
        }
        else if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
        {
            // There was a dynamic media type change. Clear our media type.
            SetMediaType(NULL);
        }
        else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
        {
            // The mixer needs more input.
            // We have to wait for the mixer to get more input.
            m_bSampleNotify = FALSE;
        }
    }
    else
    {
        // We got an output sample from the mixer.

        if (m_pClock && !bRepaint)
        {
            // Latency: Record the ending time for the ProcessOutput operation,
            // and notify the EVR of the latency.

            (void)m_pClock->GetCorrelatedTime(0, &mixerEndTime, &systemTime);

            LONGLONG latencyTime = mixerEndTime - mixerStartTime;
            NotifyEvent(EC_PROCESSING_LATENCY, (LONG_PTR)&latencyTime, 0);
        }

        // Set up notification for when the sample is released.
        hr = TrackSample(pSample);
        if (FAILED(hr))
        {
            goto done;
        }

        // Schedule the sample.
        if ((m_FrameStep.state == FRAMESTEP_NONE) || bRepaint)
        {
            hr = DeliverSample(pSample, bRepaint);
            if (FAILED(hr))
            {
                goto done;
            }
        }
        else
        {
            // We are frame-stepping (and this is not a repaint request).
            hr = DeliverFrameStepSample(pSample);
            if (FAILED(hr))
            {
                goto done;
            }
        }

        m_bPrerolled = TRUE; // We have presented at least one sample now.
    }

done:
    SafeRelease(&pSample);

    // Important: Release any events returned from the ProcessOutput method.
    SafeRelease(&dataBuffer.pEvents);
    return hr;
}

Einige Hinweise zu diesem Beispiel:

  • Die m_SamplePool-Variable wird als Auflistungsobjekt angenommen, das die Warteschlange der verfügbaren Videobeispiele enthält. Die Methode des GetSample-Objekts gibt MF_E_SAMPLEALLOCATOR_EMPTY zurück, wenn die Warteschlange leer ist.
  • Wenn die ProcessOutput-Methode des Mixers MF_E_TRANSFORM_NEED_MORE_INPUT zurückgibt, bedeutet dies, dass der Mixer keine weitere Ausgabe erzeugen kann, sodass der Referent dasm_fSampleNotify-Flag löscht.
  • Die TrackSample Methode, die den IMFTrackedSample-Rückruf festlegt, wird im Abschnitt Nachverfolgungsbeispielegezeigt.

Aktualisierung von Frames

Gelegentlich muss der Referent möglicherweise den neuesten Videoframe neu erstellen. Beispielsweise wird der Standardreferent den Frame in den folgenden Situationen neu aufzeichnen:

Führen Sie die folgenden Schritte aus, um den Mixer anzufordern, den neuesten Frame neu zu erstellen:

  1. Rufen Sie ein Videobeispiel aus der Warteschlange ab.
  2. Abfragen des Beispiels für die IMFDesiredSample-Schnittstelle.
  3. Rufen Sie IMFDesiredSample::SetDesiredSampleTimeAndDuration auf. Geben Sie den Zeitstempel des aktuellen Videoframes an. (Sie müssen diesen Wert zwischenspeichern und für jeden Frame aktualisieren.)
  4. Rufen Sie ProcessOutput auf dem Mixer auf.

Beim Aktualisieren eines Frames können Sie die Präsentationsuhr ignorieren und den Frame sofort präsentieren.

Planungsbeispiele

Videoframes können jederzeit den EVR erreichen. Der Referent ist für die Darstellung jedes Frames zur richtigen Zeit verantwortlich, basierend auf dem Zeitstempel des Frames. Wenn der Referent ein neues Beispiel vom Mixer erhält, wird das Beispiel in die geplante Warteschlange versetzt. In einem separaten Thread holt sich der Referent ständig die erste Probe vom Kopf der Warteschlange und entscheidet, wie er sie verwenden möchte:

  • Präsentieren Sie das Beispiel.
  • Behalten Sie das Beispiel in der Warteschlange, denn es ist früh dran.
  • Verwerfen Sie das Beispiel, da es verspätet ist. Obwohl Sie das Ablegen von Frames möglichst vermeiden sollten, müssen Sie dies möglicherweise tun, wenn der Referent ständig in Verzug gerät.

Rufen Sie IMFSample::GetSampleTime im Videobeispiel auf, um den Zeitstempel für einen Videoframe abzurufen. Der Zeitstempel bezieht sich auf die Präsentationsuhr des EVR. Rufen Sie IMFClock::GetCorrelatedTime auf, um die aktuelle Uhrzeit abzurufen. Wenn der EVR keine Präsentationsuhr hat oder ein Beispiel keinen Zeitstempel hat, können Sie das Beispiel unmittelbar nach dem Abrufen präsentieren.

Rufen Sie zur Ermittlung der Dauer jedes Beispiels IMFSample::GetSampleDuration auf. Wenn das Beispiel keine Dauer hat, können Sie die Funktion MFFrameRateToAverageTimePerFrame verwenden, um die Dauer aus der Framerate zu berechnen.

Beachten Sie beim Planen von Beispielen Folgendes:

  • Wenn die Wiedergaberate schneller oder langsamer als die normale Geschwindigkeit ist, läuft die Uhr schneller oder langsamer. Das bedeutet, dass der Zeitstempel für ein Beispiel immer die richtige Zielzeit relativ zur Präsentationsuhr gibt. Wenn Sie jedoch Präsentationszeiten in eine andere Uhrzeit übersetzen (z. B. den Leistungsindikator mit hoher Auflösung), müssen Sie die Uhrzeiten basierend auf der Taktgeschwindigkeit skalieren. Wenn sich die Taktgeschwindigkeit ändert, ruft die EVR die IMFClockStateSink::OnClockSetRate-Methode des Referenten auf.
  • Die Wiedergaberate kann für die umgekehrte Wiedergabe negativ sein. Wenn die Wiedergaberate negativ ist, wird die Präsentationsuhr rückwärts ausgeführt. Mit anderen Worten, Zeit N + 1 tritt vor der Zeit N auf.

Das folgende Beispiel berechnet, wie früh oder spät ein Beispiel im Verhältnis zur Präsentationsuhr ist:

    LONGLONG hnsPresentationTime = 0;
    LONGLONG hnsTimeNow = 0;
    MFTIME   hnsSystemTime = 0;

    BOOL bPresentNow = TRUE;
    LONG lNextSleep = 0;

    if (m_pClock)
    {
        // Get the sample's time stamp. It is valid for a sample to
        // have no time stamp.
        hr = pSample->GetSampleTime(&hnsPresentationTime);

        // Get the clock time. (But if the sample does not have a time stamp,
        // we don't need the clock time.)
        if (SUCCEEDED(hr))
        {
            hr = m_pClock->GetCorrelatedTime(0, &hnsTimeNow, &hnsSystemTime);
        }

        // Calculate the time until the sample's presentation time.
        // A negative value means the sample is late.
        LONGLONG hnsDelta = hnsPresentationTime - hnsTimeNow;
        if (m_fRate < 0)
        {
            // For reverse playback, the clock runs backward. Therefore, the
            // delta is reversed.
            hnsDelta = - hnsDelta;
        }

Die Präsentationsuhr wird in der Regel von der Systemuhr oder dem Audiorenderer gesteuert. (Der Audiorenderer leitet die Zeit von der Rate ab, mit der die Soundkarte Audio verbraucht.) Im Allgemeinen wird die Präsentationsuhr nicht mit der Aktualisierungsrate des Monitors synchronisiert.

Wenn in Ihren Direct3D-Präsentationsparametern D3DPRESENT_INTERVAL_DEFAULT oder D3DPRESENT_INTERVAL_ONE für das Präsentationsintervall angegeben ist, wartet der Vorgang Präsent auf den vertikalen Rücklauf des Monitors. Dies ist ein einfacher Weg, um das Zerreißen zu verhindern, verringert aber die Genauigkeit Ihres Planungsalgorithmus. Umgekehrt wird die Präsent-Methode sofort ausgeführt, wenn das Präsentationsintervall D3DPRESENT_INTERVAL_IMMEDIATE lautet. Dies führt zu Tearing, es sei denn, Ihr Planungsalgorithmus ist so genau, dass Sie Präsent nur während des vertikalen Rücklaufs aufrufen.

Die folgenden Funktionen können Ihnen helfen, genaue Anzeigedauerinformationen zu erhalten:

  • IDirect3DDevice9::GetRasterStatus gibt Informationen zum Raster zurück, einschließlich der aktuellen Scanzeile und ob sich das Raster in der vertikalen leeren Periode befindet.
  • DwmGetCompositionTimingInfo gibt Anzeigedauerinformationen für den Desktopfenster-Manager zurück. Diese Informationen sind nützlich, wenn die Desktopkomposition aktiviert ist.

Präsentieren von Beispielen

In diesem Abschnitt wird davon ausgegangen, dass Sie eine separate Swapchain für jede Oberfläche erstellt haben, wie unter Zuweisung Direct3D-Oberflächen beschrieben. Rufen Sie zum Präsentieren eines Beispiels die Swapchain aus dem Videobeispiel wie folgt ab:

  1. Rufen Sie IMFSample::GetBufferByIndex im Videobeispiel auf, um den Puffer abzurufen.
  2. Abfragen des Puffers für die IMFGetService-Schnittstelle.
  3. Rufen Sie IMFGetService::GetService auf, um die IDirect3DSurface9-Schnittstelle der Direct3D-Oberfläche abzurufen. (Sie können diesen Schritt und den vorherigen Schritt durch Aufrufen von MFGetService kombinieren.)
  4. Rufen Sie IDirect3DSurface9::GetContainer auf der Oberfläche auf, um einen Zeiger auf die Swapchain abzurufen.
  5. Rufen Sie IDirect3DSwapChain9::Present in der Swapchain auf.

Diese Schritte sind im folgenden Code dargestellt:

HRESULT D3DPresentEngine::PresentSample(IMFSample* pSample, LONGLONG llTarget)
{
    HRESULT hr = S_OK;

    IMFMediaBuffer* pBuffer = NULL;
    IDirect3DSurface9* pSurface = NULL;
    IDirect3DSwapChain9* pSwapChain = NULL;

    if (pSample)
    {
        // Get the buffer from the sample.
        hr = pSample->GetBufferByIndex(0, &pBuffer);
        if (FAILED(hr))
        {
            goto done;
        }

        // Get the surface from the buffer.
        hr = MFGetService(pBuffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&pSurface));
        if (FAILED(hr))
        {
            goto done;
        }
    }
    else if (m_pSurfaceRepaint)
    {
        // Redraw from the last surface.
        pSurface = m_pSurfaceRepaint;
        pSurface->AddRef();
    }

    if (pSurface)
    {
        // Get the swap chain from the surface.
        hr = pSurface->GetContainer(IID_PPV_ARGS(&pSwapChain));
        if (FAILED(hr))
        {
            goto done;
        }

        // Present the swap chain.
        hr = PresentSwapChain(pSwapChain, pSurface);
        if (FAILED(hr))
        {
            goto done;
        }

        // Store this pointer in case we need to repaint the surface.
        CopyComPointer(m_pSurfaceRepaint, pSurface);
    }
    else
    {
        // No surface. All we can do is paint a black rectangle.
        PaintFrameWithGDI();
    }

done:
    SafeRelease(&pSwapChain);
    SafeRelease(&pSurface);
    SafeRelease(&pBuffer);

    if (FAILED(hr))
    {
        if (hr == D3DERR_DEVICELOST || hr == D3DERR_DEVICENOTRESET || hr == D3DERR_DEVICEHUNG)
        {
            // We failed because the device was lost. Fill the destination rectangle.
            PaintFrameWithGDI();

            // Ignore. We need to reset or re-create the device, but this method
            // is probably being called from the scheduler thread, which is not the
            // same thread that created the device. The Reset(Ex) method must be
            // called from the thread that created the device.

            // The presenter will detect the state when it calls CheckDeviceState()
            // on the next sample.
            hr = S_OK;
        }
    }
    return hr;
}

Quell- und Zielrechtecke

Das Quellrechteck ist der Teil des anzuzeigenden Videoframes. Es wird im Verhältnis zu einem normalisierten Koordinatensystem definiert, in dem der gesamte Videoframe ein Rechteck mit Koordinaten {0, 0, 1, 1} einnimmt. Das Zielrechteck ist der Bereich innerhalb der Zieloberfläche, in dem der Videoframe gezeichnet wird. Der Standardreferent ermöglicht es einer Anwendung, diese Rechtecke durch Aufrufen von IMFVideoDisplayControl::SetVideoPosition festzulegen.

Es gibt mehrere Optionen zum Anwenden von Quell- und Zielrechtecken. Die erste Option besteht darin, den Mixer anzuwenden:

  • Legen Sie das Quellrechteck mithilfe des VIDEO_ZOOM_RECT-Attributs fest. Der Mixer wendet das Quellrechteck an, wenn er das Video auf die Zielfläche blendet. Das Standardquellrechteck des Mixers ist der gesamte Frame.
  • Legen Sie das Zielrechteck als geometrische Blende im Ausgabetyp des Mixers fest. Weitere Informationen finden Sie unter Verhandlungsformate.

Die zweite Möglichkeit besteht darin, die Rechtecke anzuwenden, wenn Sie IDirect3DSwapChain9::Present verwenden, indem Sie die Parameter pSourceRect und pDestRect in der Methode Präsent angeben. Sie können diese Optionen kombinieren. Sie können z. B. das Quellrechteck auf dem Mixer festlegen, aber das Zielrechteck in der Präsent-Methode anwenden.

Wenn die Anwendung das Zielrechteck ändert oder die Größe des Fensters ändert, müssen Sie möglicherweise neue Oberflächen zuweisen. In diesem Fall müssen Sie darauf achten, diesen Vorgang mit Ihrem Planungsthread zu synchronisieren. Leeren Sie die Planungswarteschlange, und verwerfen Sie die alten Beispiele, bevor Sie neue Oberflächen zuordnen.

Ende des Streams

Wenn jeder Eingabestream auf dem EVR beendet wurde, sendet der EVR eine MFVP_MESSAGE_ENDOFSTREAM-Meldung an den Referenten. Zu dem Zeitpunkt, an dem Sie die Meldung erhalten, sind jedoch möglicherweise noch einige Videoframes zu verarbeiten. Bevor Sie auf die End-of-Stream-Meldung reagieren, müssen Sie die gesamte Ausgabe aus dem Mixer ablassen und alle verbleibenden Frames präsentieren. Senden Sie nach der Präsentation des letzten Frames ein EC_COMPLETE-Ereignis an das EVR.

Das nächste Beispiel zeigt eine Methode, die das EC_COMPLETE-Ereignis sendet, wenn verschiedene Bedingungen erfüllt sind. Andernfalls wird S_OK ohne Senden des Ereignisses zurückgegeben:

HRESULT EVRCustomPresenter::CheckEndOfStream()
{
    if (!m_bEndStreaming)
    {
        // The EVR did not send the MFVP_MESSAGE_ENDOFSTREAM message.
        return S_OK;
    }

    if (m_bSampleNotify)
    {
        // The mixer still has input.
        return S_OK;
    }

    if (m_SamplePool.AreSamplesPending())
    {
        // Samples are still scheduled for rendering.
        return S_OK;
    }

    // Everything is complete. Now we can tell the EVR that we are done.
    NotifyEvent(EC_COMPLETE, (LONG_PTR)S_OK, 0);
    m_bEndStreaming = FALSE;
    return S_OK;
}

Diese Methode überprüft die folgenden Zustände:

  • Wenn die m_fSampleNotify-Variable WAHRist, bedeutet dies, dass der Mixer einen oder mehrere Frames aufweist, die noch nicht verarbeitet wurden. (Weitere Informationen finden Sie unter Verarbeitungsausgabe.)
  • Die m_fEndStreaming-Variable ist ein boolesches Flag, dessen Anfangswert FALSCH ist. Der Referent legt das Flag auf WAHR fest, wenn der EVR die MFVP_MESSAGE_ENDOFSTREAM-Meldung sendet.
  • Es wird davon ausgegangen, dass die AreSamplesPending-Methode WAHR zurückgibt, solange einer oder mehrere Frames in der geplanten Warteschlange warten.

Setzen Sie in der Methode IMFVideoPresenter::ProcessMessagem_fEndStreaming auf WAHR und rufen Sie CheckEndOfStream auf, wenn der EVR die Nachricht MFVP_MESSAGE_ENDOFSTREAM sendet:

HRESULT EVRCustomPresenter::ProcessMessage(
    MFVP_MESSAGE_TYPE eMessage,
    ULONG_PTR ulParam
    )
{
    HRESULT hr = S_OK;

    EnterCriticalSection(&m_ObjectLock);

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

    switch (eMessage)
    {
    // Flush all pending samples.
    case MFVP_MESSAGE_FLUSH:
        hr = Flush();
        break;

    // Renegotiate the media type with the mixer.
    case MFVP_MESSAGE_INVALIDATEMEDIATYPE:
        hr = RenegotiateMediaType();
        break;

    // The mixer received a new input sample.
    case MFVP_MESSAGE_PROCESSINPUTNOTIFY:
        hr = ProcessInputNotify();
        break;

    // Streaming is about to start.
    case MFVP_MESSAGE_BEGINSTREAMING:
        hr = BeginStreaming();
        break;

    // Streaming has ended. (The EVR has stopped.)
    case MFVP_MESSAGE_ENDSTREAMING:
        hr = EndStreaming();
        break;

    // All input streams have ended.
    case MFVP_MESSAGE_ENDOFSTREAM:
        // Set the EOS flag.
        m_bEndStreaming = TRUE;
        // Check if it's time to send the EC_COMPLETE event to the EVR.
        hr = CheckEndOfStream();
        break;

    // Frame-stepping is starting.
    case MFVP_MESSAGE_STEP:
        hr = PrepareFrameStep(LODWORD(ulParam));
        break;

    // Cancels frame-stepping.
    case MFVP_MESSAGE_CANCELSTEP:
        hr = CancelFrameStep();
        break;

    default:
        hr = E_INVALIDARG; // Unknown message. This case should never occur.
        break;
    }

done:
    LeaveCriticalSection(&m_ObjectLock);
    return hr;
}

Rufen Sie außerdem CheckEndOfStream auf, wenn die IMFTransform::P rocessOutput-Methode des Mixers MF_E_TRANSFORM_NEED_MORE_INPUT zurückgibt. Dieser Fehlercode gibt an, dass der Mixer keine Eingabebeispiele mehr enthält (siehe Verarbeitungsausgabe).

Frame Stepping

Der EVR wurde entwickelt, um Frame Stepping in DirectShow und Scrubbing in Media Foundation zu unterstützen. Frame Stepping und Scrubbing sind konzeptionell ähnlich. In beiden Fällen fordert die Anwendung jeweils einen Videoframe an. Intern verwendet der Referent denselben Mechanismus, um beide Funktionen zu implementieren.

Frame Stepping in DirectShow funktioniert wie folgt:

  • Die Anwendung ruft IVideoFrameStep::Step auf. Die Anzahl der Schritte wird im dwSteps-Parameter angegeben. Der EVR sendet eine MFVP_MESSAGE_STEP-Meldung an den Referenten, wobei der Nachrichtenparameter (ulParam) die Anzahl der Schritte angibt.
  • Wenn die Anwendung IVideoFrameStep::CancelStep aufruft oder den Diagrammzustand ändert (ausgeführt, angehalten oder angehalten), sendet der EVR eine MFVP_MESSAGE_CANCELSTEP-Meldung.

Scrubbing in Media Foundation funktioniert wie folgt:

  • Die Anwendung legt die Wiedergaberate auf Null fest, indem IMFRateControl::SetRate aufgerufen wird.
  • Um einen neuen Frame zu rendern, ruft die Anwendung IMFMediaSession::Start mit der gewünschten Position auf. Der EVR sendet eine MFVP_MESSAGE_STEP-Meldung mit ulParam gleich 1.
  • Um das Scrubbing zu beenden, legt die Anwendung die Wiedergaberate auf einen Wert ungleich Null fest. Der EVR sendet die MFVP_MESSAGE_CANCELSTEP-Meldung.

Nachdem die MFVP_MESSAGE_STEP-Meldung empfangen wurde, wartet der Referent auf das Eintreffen des Zielframes. Wenn die Anzahl der Schritte N beträgt, verwirft der Referent die nächsten (N - 1) Beispiele und präsentiert das N-te Beispiel. Wenn der Referent den Frameschritt abgeschlossen hat, sendet er ein EC_STEP_COMPLETE-Ereignis an den EVR, wobei lParam1 auf FALSCH eingestellt ist. Wenn die Wiedergaberate Null ist, sendet der Referent außerdem ein EC_SCRUB_TIME-Ereignis. Wenn der EVR das Frame-Stepping abbricht, während ein Frame-Step-Vorgang noch läuft, sendet der Referent ein EC_STEP_COMPLETE-Ereignis, wobei lParam1 auf WAHR gesetzt ist.

Die Anwendung kann mehrere Frame-Steps oder Scrubs durchführen, so dass der Präsentator möglicherweise mehrere MFVP_MESSAGE_STEP-Meldungen erhält, bevor er eine MFVP_MESSAGE_CANCELSTEP-Meldung erhält. Außerdem kann der Referent die MFVP_MESSAGE_STEP-Meldung empfangen, bevor die Systemuhr beginnt oder während diese läuft.

Implementieren von Frame Stepping

In diesem Abschnitt wird ein Algorithmus zum Implementieren von Frame Stepping beschrieben. Der Frame-Stepping-Algorithmus verwendet die folgenden Variablen:

  • step_count. Eine ganze Zahl ohne Vorzeichen, welche die Anzahl der Schritte im aktuellen Frame-Stepping-Vorgang angibt.

  • step_queue. Eine Warteschlange mit IMFSample-Zeigern.

  • step_state. Der Referent kann sich zu jedem Zeitpunkt in einem der folgenden Zustände befinden, was das Frame Stepping betrifft:

    State Beschreibung
    NOT_STEPPING Kein Frame Stepping.
    WARTEN Der Referent hat die MFVP_MESSAGE_STEP-Meldung empfangen, aber die Systemuhr wurde nicht gestartet.
    PENDING Der Referent hat die MFVP_MESSAGE_STEP-Meldung empfangen und die Systemuhr wurde gestartet, der Referent wartet jedoch darauf, den Zielframe zu empfangen.
    GEPLANT Der Referent hat den Zielframe empfangen und für die Präsentation geplant, aber der Frame wurde nicht präsentiert.
    COMPLETE Der Referent hat den Zielframe präsentiert und das EC_STEP_COMPLETE-Ereignis gesendet und wartet auf die nächste MFVP_MESSAGE_STEP oder MFVP_MESSAGE_CANCELSTEP-Meldung.

     

    Diese Zustände sind unabhängig von den Referentenzuständen, die im Abschnitt Referentenstatus aufgeführt sind.

Die folgenden Verfahren werden für den Frame-Stepping-Algorithmus definiert:

PrepareFrameStep-Prozedur

  1. Inkrementieren Sie step_count.
  2. Stellen Sie step_state auf WARTEN ein.
  3. Wenn die Systemuhr ausgeführt wird, rufen Sie StartFrameStep auf.

StartFrameStep-Prozedur

  1. Wenn step_state gleich WARTEN ist, setzen Sie step_state auf AUSSTEHEND. Rufen Sie für jedes Beispiel in step_queue DeliverFrameStepSample auf.
  2. Wenn step_state gleich „NOT_STEPPING“ ist, entfernen Sie alle Beispiele aus step_queue und planen sie für die Präsentation ein.

CompleteFrameStep-Prozedur

  1. Setzen Sie step_state auf ABGESCHLOSSEN.
  2. Senden Sie das EC_STEP_COMPLETE-Ereignis mit lParam1 = FALSCH.
  3. Wenn die Taktfrequenz Null ist, senden Sie das EC_SCRUB_TIME Ereignis mit der Beispielzeit.

DeliverFrameStepSample-Prozedur

  1. Wenn die Taktfrequenz Null ist und die Beispielzeit + für die Dauer der Stichprobenzeit<liegt, verwerfen Sie das Beispiel. Beenden
  2. Wenn step_state GEPLANT oder ABGESCHLOSSEN ist, fügen Sie das Beispiel zu step_queue hinzu. Beenden
  3. Dekrementieren Sie step_count.
  4. Wenn step_count> 0 ist, verwerfen Sie das Beispiel. Beenden
  5. Wenn step_state gleich WARTEN ist, fügen Sie das Beispiel zu step_queue hinzu. Beenden
  6. Planen Sie das Beispiel für die Präsentation.
  7. Setzen Sie step_state auf GEPLANT.

CancelFrameStep-Prozedur

  1. Festsetzen von step_state auf „NOT_STEPPING“
  2. Setzen Sie step_count auf Null zurück.
  3. Wenn der vorherige Wert von step_state WARTEN, AUSSTEHEND oder GEPLANT war, senden Sie EC_STEP_COMPLETE mit lParam1 = WAHR.

Rufen Sie diese Verfahren wie folgt auf:

Referentennachricht oder -methode Prozedur
MFVP_MESSAGE_STEP-Meldung PrepareFrameStep
MFVP_MESSAGE_STEP-Meldung CancelStep
IMFClockStateSink::OnClockStart StartFrameStep
IMFClockStateSink::OnClockRestart StartFrameStep
IMFTrackedSample-Callback CompleteFrameStep
IMFClockStateSink::OnClockStop CancelFrameStep
IMFClockStateSink::OnClockSetRate CancelFrameStep

 

Der folgende Chart zeigt die Frame-Stepping-Verfahren.

flow chart showing paths that start with mfvp-message-step and mfvp-message-processinputnotify and end at

Festlegen des Referenten auf dem EVR

Nach der Implementierung des Referenten besteht der nächste Schritt darin, den EVR so zu konfigurieren, dass er verwendet wird.

Festlegen des Referenten in DirectShow

Legen Sie in einer DirectShow-Anwendung den Referenten auf dem EVR wie folgt fest:

  1. Erstellen Sie den EVR-Filter durch Aufrufen von CoCreateInstance. Die CLSID ist CLSID_EnhancedVideoRenderer.
  2. Fügen Sie das EVR zum Filterdiagramm hinzu.
  3. Erstellen Sie eine Instanz Ihres Referenten. Ihr Referent kann die standardmäßige COM-Objekterstellung über IClassFactory unterstützen, dies ist jedoch nicht obligatorisch.
  4. Abfragen des EVR-Filters für die IMFVideoRenderer-Schnittstelle.
  5. Rufen Sie IMFVideoRenderer::InitializeRenderer auf.

Festlegen des Referenten in Media Foundation

In Media Foundation haben Sie verschiedene Optionen, je nachdem, ob Sie die EVR-Mediensenke oder das EVR-Aktivierungsobjekt erstellen. Weitere Informationen zu Aktivierungsobjekten finden Sie unter Aktivierungsobjekte.

Gehen Sie für die EVR-Mediensenke wie folgt vor:

  1. Rufen Sie MFCreateVideoRenderer auf, um die Mediensenke zu erstellen.
  2. Erstellen Sie eine Instanz Ihres Referenten.
  3. Abfragen der EVR-Mediensenke für die IMFVideoRenderer-Schnittstelle.
  4. Rufen Sie IMFVideoRenderer::InitializeRenderer auf.

Gehen Sie für das EVR-Aktivierungsobjekt wie folgt vor:

  1. Rufen Sie MFCreateVideoRendererActivate auf, um das Aktivierungsobjekt zu erstellen.

  2. Legen Sie eines der folgenden Attribute für das Aktivierungsobjekt fest:

    attribute BESCHREIBUNG
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Zeigen Sie auf ein Aktivierungsobjekt für den Referenten.
    Mit diesem Flag müssen Sie ein Aktivierungsobjekt für den Referenten bereitstellen. Das Aktivierungsobjekt muss die IMFActivate-Schnittstelle implementieren.
    MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID des Referenten.
    Mit dieser Kennzeichnung muss Ihr Referent die standardmäßige COM-Objekterstellung über IClassFactory unterstützen.

     

  3. Legen Sie optional das Attribut MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS für das Aktivierungsobjekt fest.

Verbesserter Videorenderer

EVRPresenter-Beispiel