Schreiben eines EVR-Presenters
In diesem Artikel wird beschrieben, wie Sie eine benutzerdefinierte Präsentation für den erweiterten Videorenderer (EVR) schreiben. Eine benutzerdefinierte Präsentation 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 Reihenfolge der Vorgänge variieren kann.
Der Beispielcode in diesem Thema wurde aus dem EVRPresenter-Beispielangepasst, das im Windows SDK bereitgestellt wird.
Dieses Thema enthält folgende Abschnitte:
- Voraussetzungen
- Presenter-Objektmodell
- Daten Flow innerhalb der EVR
- Presenter States
- Presenter-Schnittstellen
- Implementieren von IMPLEMENTVIDEODeviceID
- Implementieren von IMPLEMENTTOPOLOGYServiceLookupClient
- Implementieren von IMPLEMENTVIDEOPresenter
- Implementieren von IMPLEMENTClockStateSink
- Implementieren von IMPLEMENTRATESupport
- Senden von Ereignissen an die EVR
- Aushandeln von Formaten
- Verwalten des Direct3D-Geräts
- Verarbeiten der Ausgabe
- Einzelschritt im Rahmen
- Festlegen des Presenters für die EVR
- Zugehörige Themen
Voraussetzungen
Bevor Sie eine benutzerdefinierte Präsentation schreiben, sollten Sie mit den folgenden Technologien vertraut sein:
- Der erweiterte Videorenderer. Weitere Informationen finden Sie unter Erweiterter Videorenderer.
- Direct3D-Grafiken. Sie müssen keine 3D-Grafiken verstehen, um eine Präsentation zu schreiben, aber Sie müssen wissen, wie Sie ein Direct3D-Gerät erstellen und Direct3D-Oberflächen verwalten. Wenn Sie nicht mit Direct3D 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 verwendet.
- Media Foundation Transformiert. Der EVR-Mixer ist eine Media Foundation-Transformation, und der Moderator ruft Methoden direkt auf dem Mixer auf.
- Implementieren von COM-Objekten. Der Presenter ist ein Prozess-COM-Objekt mit Freethreading.
Presenter-Objektmodell
Dieser Abschnitt enthält eine Übersicht über das Presenter-Objektmodell und die Schnittstellen.
Daten Flow innerhalb der EVR
Die EVR verwendet zwei Plug-In-Komponenten zum Rendern von Videos: den Mixer und den Presenter. Der Mixer kombiniert die Videostreams und deinterlaciert das Video bei Bedarf. Die Moderatorin zeichnet (oder präsentiert) das Video auf der Anzeige und plant, wann jeder Frame gezeichnet wird. Anwendungen können eines dieser Objekte durch eine benutzerdefinierte Implementierung ersetzen.
Die 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 Streams sind Unterstreams, die der Mixer alpha-blendet in den Verweisstream einblendet. Der Verweisstream bestimmt die Masterbildrate für das zusammengesetzte Video. Für jeden Verweisrahmen nimmt der Mixer den neuesten Frame aus jedem Unterstream, blendet sie alpha auf den Referenzrahmen und gibt einen einzelnen zusammengesetzten Frame aus. Der Mixer führt bei Bedarf auch Deinterlacing und Farbkonvertierung von YUV in RGB durch. Die EVR fügt den Mixer immer in die Videopipeline ein, unabhängig von der Anzahl der Eingabestreams oder dem Videoformat. Die folgende Abbildung veranschaulicht diesen Prozess.

Der Presenter führt die folgenden Aufgaben aus:
- Legt das Ausgabeformat für den Mixer fest. Bevor das Streaming beginnt, legt die Moderatorin 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 blitt die zusammengesetzten Frames auf diese Oberflächen.
- Ruft die Ausgabe des Mixers ab.
- Plant, wann die Frames angezeigt werden. Die EVR stellt die Präsentationsuhr bereit, und der Moderator plant Frames entsprechend dieser Uhr.
- Stellt jeden Frame mit Direct3D dar.
- Führt die Einzelschritt- und Bereinigungsschritte des Frames aus.
Presenter States
Der Presenter befindet sich jederzeit in einem der folgenden Zustände:
- Gestartet. Die Präsentationsuhr der EVR wird ausgeführt. Die Moderatorin plant Videoframes für die Präsentation, sobald sie eintreffen.
- Angehalten. Die Präsentationsuhr wird angehalten. Der Presenter stellt keine neuen Beispiele vor, behält aber seine Warteschlange mit geplanten Stichproben bei. Wenn neue Beispiele empfangen werden, fügt die Moderatorin 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äsentationsuhr wird beendet. Die Präsentation verwirft alle geplanten Beispiele.
- Fahren Sie herunter. Die Präsentation gibt alle Ressourcen frei, die sich auf das Streaming beziehen, z. B. Direct3D-Oberflächen. Dies ist der Anfangszustand des Moderators und der endgültige Zustand, bevor der Presenter zerstört wird.
Im Beispielcode in diesem Thema wird der Darstellungszustand durch eine -Enumeration 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 die Darstellung im Zustand "Herunterfahren" befindet. Der Beispielcode überprüft diesen Zustand, indem eine Hilfsmethode aufgerufen wird:
HRESULT CheckShutdown() const
{
if (m_RenderState == RENDER_STATE_SHUTDOWN)
{
return MF_E_SHUTDOWN;
}
else
{
return S_OK;
}
}
Presenter-Schnittstellen
Ein Presenter ist erforderlich, um die folgenden Schnittstellen zu implementieren:
| Schnittstelle | BESCHREIBUNG |
|---|---|
| ÜBER DIE UHRClockStateSink | Benachrichtigt den Moderator, wenn sich der Zustand der EVR-Uhr ändert. Weitere Informationen finden Sie unter Implementieren von IMPLEMENTClockStateSink. |
| VERZRGETService | Bietet der Anwendung und anderen Komponenten in der Pipeline eine Möglichkeit, Schnittstellen vom Presenter abzurufen. |
| SHOPPERTopologyServiceLookupClient | Ermöglicht dem Presenter das Abrufen von Schnittstellen von der EVR oder dem Mixer. Weitere Informationen finden Sie unter Implementieren von IMPLEMENTTOPOLOGYServiceLookupClient. |
| DINNERVideoDeviceID | Stellt sicher, dass der Presenter und der Mixer kompatible Technologien verwenden. Weitere Informationen finden Sie unter Implementieren von IMPLEMENTVIDEODeviceID. |
| DINNERVideoPresenter | Verarbeitet Nachrichten von der EVR. Weitere Informationen finden Sie unter Implementieren von IMPLEMENTVideoPresenter. |
Die folgenden Schnittstellen sind optional:
| Schnittstelle | BESCHREIBUNG |
|---|---|
| IEVRTrustedVideoPlugin | Ermöglicht der Präsentation das Arbeiten mit geschützten Medien. Implementieren Sie diese Schnittstelle, wenn Ihr Presenter eine vertrauenswürdige Komponente ist, die für die Arbeit im geschützten Medienpfad (Protected Media Path, PMP) konzipiert ist. |
| 1000000000 | Gibt den Bereich der Wiedergaberaten an, der von der Präsentation unterstützt wird. Weitere Informationen finden Sie unter Implementieren von IMPLEMENTRATESupport. |
| CITRIXVideoPositionMapper | Karten Koordinaten auf dem Ausgabevideorahmen zu Koordinaten für den Eingabevideorahmen. |
| IQualProp | Meldet Leistungsinformationen. Die EVR verwendet diese Informationen für die Qualitätskontrollverwaltung. Diese Schnittstelle ist im DirectShow SDK dokumentiert. |
Sie können auch Schnittstellen für die Kommunikation der Anwendung mit dem Presenter bereitstellen. Der Standard-Presenter implementiert zu diesem Zweck die INTERFACESVideoDisplayControl-Schnittstelle. Sie können diese Schnittstelle implementieren oder ihre eigene definieren. Die Anwendung ruft Schnittstellen vom Presenter ab, indem SIE IM EVR DENGET-Dienst::GetService aufruft. Wenn die Dienst-GUID MR_VIDEO_RENDER_SERVICE ist, übergibt die EVR die GetService-Anforderung an die Moderatorin.
Implementieren von IMPLEMENTVIDEODeviceID
Die INTERFACESVideoDeviceID-Schnittstelle enthält eine Methode, GetDeviceID,die eine Geräte-GUID zurückgibt. Die Geräte-GUID stellt sicher, dass der Presenter und der Mixer kompatible Technologien verwenden. Wenn die Geräte-GUIDs nicht übereinstimmen, kann die EVR nicht initialisiert werden.
Sowohl der Standardmixer als auch der Presenter verwenden Direct3D 9, wobei die Geräte-GUID gleich IID_IDirect3DDevice9 ist. Wenn Sie Ihre benutzerdefinierte Präsentation mit dem Standardmixer verwenden möchten, muss die Geräte-GUID des Moderators IID_IDirect3DDevice9 sein. Wenn Sie beide Komponenten ersetzen, können Sie eine neue Geräte-GUID definieren. Für den Rest dieses Artikels wird davon ausgegangen, dass der Moderator Direct3D 9 verwendet. Hier ist die Standardimplementierungen 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 sein, wenn der Presenter heruntergefahren wird.
Implementieren vonTOPTOPOLOGYServiceLookupClient
Mithilfe der SCHNITTSTELLE FÜR TOPOLOGIEServiceLookupClient kann der Presenter Wie folgt Schnittstellenzeiger von der EVR und vom Mixer abrufen:
- Wenn die EVR den Presenter initialisiert, ruft sie die METHODE VONTOPTOPOLOGYServiceLookupClient::InitServicePointers des Moderators auf. Das Argument ist ein Zeiger auf die POINTERTopologyServiceLookup-Schnittstelle des EVR.
- Der Presenter ruft DENTOPTOPOLOGYServiceLookup::LookupService auf, um Schnittstellenzeiger von der EVR oder dem Mixer abzurufen.
Die LookupService-Methode ähnelt der METHODE VOMGETSERVICE::GetService. 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 die EVR abgefragt.
- Wenn die Dienst-GUID MR_VIDEO_MIXER_SERVICE ist, wird der Mixer abgefragt.
In Ihrer Implementierung von InitServicePointerserhalten Sie die folgenden Schnittstellen von der EVR:
| EVR-Schnittstelle | BESCHREIBUNG |
|---|---|
| IMediaEventSink | Bietet dem Moderator die Möglichkeit, Nachrichten an die EVR zu senden. Diese Schnittstelle wird im DirectShow SDK definiert, sodass die Nachrichten dem Muster für DirectShow-Ereignisse folgen, nicht Media Foundation Ereignissen. |
| 1000000000 | Stellt die Uhr der EVR dar. Die Moderatorin verwendet diese Schnittstelle, um Beispiele für die Präsentation zu planen. Die EVR kann ohne Uhr ausgeführt werden, sodass diese Schnittstelle möglicherweise nicht verfügbar ist. Wenn nicht, ignorieren Sie den Fehlercode von LookupService. Die Uhr implementiert auch die INTERFACESTimer-Schnittstelle. In der Media Foundation-Pipeline implementiert die Uhr die SCHNITTSTELLE VON DERPRESENTPRESENTATIONClock. Diese Schnittstelle wird in DirectShow nicht implementiert. |
Abrufen der folgenden Schnittstellen aus dem Mixer:
| Mixer Schnittstelle | BESCHREIBUNG |
|---|---|
| ÜBERTRANSFORM | Ermöglicht der Präsentation die Kommunikation mit dem Mixer. |
| DINNERVideoDeviceID | Ermöglicht dem Moderator, die Geräte-GUID des Mixers zu überprüfen. |
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 DENTOPTOPOLOGYServiceLookupClient::ReleaseServicePointersauf. Geben Sie in dieser Methode alle Schnittstellenzeiger frei, und legen Sie den Presenterzustand auf heruntergefahren fest:
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, einschließlich:
- Trennen oder erneutes Verbinden von Pins (DirectShow) oder Hinzufügen oder Entfernen von Datenstromsenken (Media Foundation).
- Ändern des Formats.
- Festlegen einer neuen Uhr.
- Endgültiges Herunterfahren der EVR.
Während der Lebensdauer des Presenters kann die EVR InitServicePointers und ReleaseServicePointers mehrmals aufrufen.
Implementieren von IMPLEMENTVIDEOPresenter
Die INTERFACESVideoPresenter-Schnittstelle erbt DENClockStateSink und fügt zwei Methoden hinzu:
| Methode | BESCHREIBUNG |
|---|---|
| GetCurrentMediaType | Gibt den Medientyp der zusammengesetzten Videoframes zurück. |
| ProcessMessage | Signalisiert dem Moderator, verschiedene Aktionen auszuführen. |
Die GetCurrentMediaType-Methode gibt den Medientyp des Moderators zurück. (Ausführliche Informationen zum Festlegen des Medientyps finden Sie unter Aushandeln von Formaten.) Der Medientyp wird als POINTERVideoMediaType-Schnittstellenzeiger zurückgegeben. Im folgenden Beispiel wird davon ausgegangen, dass der Presenter den Medientyp als EINEN POINTERMediaType-Zeiger speichert. Rufen Sie QueryInterface auf, um die SCHNITTSTELLE "ARCHEVideoMediaType" vom Medientyp abzurufen:
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 primäre Mechanismus für die Evr-Kommunikation mit dem Moderator. Die folgenden Meldungen sind definiert. Die Details zum Implementieren der einzelnen Nachrichten werden im restliche Teil dieses Themas erläutert.
Message |
BESCHREIBUNG |
|---|---|
| MFVP_MESSAGE_INVALIDATEMEDIATYPE | Der Ausgabemedientyp des Mixers ist ungültig. Der Moderator sollte einen neuen Medientyp mit dem Mixer aushandeln. Weitere Informationen finden Sie unter Aushandeln von Formaten. |
| MFVP_MESSAGE_BEGINSTREAMING | Das Streaming wurde gestartet. Für diese Nachricht ist keine bestimmte Aktion erforderlich, aber Sie können sie zum Zuordnen von Ressourcen verwenden. |
| MFVP_MESSAGE_ENDSTREAMING | Das Streaming wurde beendet. Geben Sie alle Ressourcen frei, die Sie als Reaktion auf die MFVP_MESSAGE_BEGINSTREAMING zugeordnet haben. |
| MFVP_MESSAGE_PROCESSINPUTNOTIFY | Der Mixer hat ein neues Eingabebeispiel erhalten und kann möglicherweise einen neuen Ausgaberahmen generieren. Die Moderatorin sollte IMTRANSFORM::P rocessOutput für den Mixer aufrufen. Weitere Informationen finden Sie unter Verarbeiten der Ausgabe. |
| MFVP_MESSAGE_ENDOFSTREAM | Die Präsentation wurde beendet. Weitere Informationen finden Sie unter Ende des Datenstroms. |
| MFVP_MESSAGE_FLUSH | Der EVR leert die Daten in seiner Renderingpipeline. Der Moderator sollte alle Videoframes verwerfen, die für die Präsentation geplant sind. |
| MFVP_MESSAGE_STEP | Fordert den Moderator an, N Frames schrittweise vorwärts zu bringen. Der Moderator sollte die nächsten N-1 Frames verwerfen und den N. Frame anzeigen. Weitere Informationen finden Sie unter Frame Stepping (Frameschritt). |
| MFVP_MESSAGE_CANCELSTEP | Bricht frame stepping ab. |
Implementieren von DEADClockStateSink
Der Präsentationser muss im Rahmen seiner Implementierung von VORZEIGERVideoPresenter,der DEN NAMEN ZUNEClockStateSink erbt, die INTERFACEClockStateSink-Schnittstelle implementieren. Der EVR verwendet diese Schnittstelle, um den Moderator zu benachrichtigen, wenn sich der Status der EVR-Uhr ändert. Weitere Informationen zu den Uhrzuständen finden Sie unter Presentation Clock.
Hier sind einige Richtlinien für die Implementierung der Methoden in dieser Schnittstelle. Alle Methoden sollten fehlschlagen, wenn der Presenter heruntergefahren wird.
| | | OnClockStart |
- Legen Sie den Presenterzustand auf Gestartet fest.
- Wenn llClockStartOffset nicht PRESENTATION_CURRENT_POSITIONist, leeren Sie die Warteschlange der Beispiele des Moderators. (Dies entspricht dem Empfangen einer MFVP_MESSAGE_FLUSH Nachricht.)
- Wenn eine vorherige Frameschrittanforderung noch aussteht, verarbeiten Sie die Anforderung (siehe Frame Stepping). Versuchen Sie andernfalls, die Ausgabe des Mixers zu verarbeiten (siehe Verarbeiten der Ausgabe.
- Legen Sie den Presenterzustand auf Beendet fest.
- Leeren Sie die Warteschlange der Warteschlange mit Beispielen des Moderators.
- Brechen Sie alle ausstehenden Frameschritt-Operation ab.
- Wenn die Rate von null in ungleich 0 (null) geändert wird, brechen Sie frame stepping ab.
- Store die neue Taktrate. Die Taktrate wirkt sich darauf aus, wenn Stichproben dargestellt werden. Weitere Informationen finden Sie unter Zeitplanungsbeispiele.
Implementieren vonTRATERateSupport
Um andere Wiedergaberaten als 1× zu unterstützen, muss die Moderatorin die BERATERateSupport-Schnittstelle implementieren. Hier sind einige Richtlinien für die Implementierung der Methoden in dieser Schnittstelle. Alle Methoden sollten fehlschlagen, nachdem der Presenter heruntergefahren wurde. Weitere Informationen zu dieser Schnittstelle finden Sie unter Rate Control.
| Wert | BESCHREIBUNG |
|---|---|
| GetSlowestRate | Geben Sie 0 (null) zurück, um keine minimale Wiedergaberate anzugeben. |
| GetFastestRate | Bei nicht schlanker Wiedergabe sollte die Wiedergaberate die Aktualisierungsrate des Monitors nicht überschreiten: maximale Aktualisierungsrate (Hz) / Videobildrate = (FPS). Die Videobildrate wird im Medientyp des Moderators angegeben. Bei der schlanken Wiedergabe ist die Wiedergaberate ungebunden. gibt den Wert FLT_MAX. In der Praxis sind die Quelle und der Decoder die begrenzenden Faktoren bei der schlanken Wiedergabe. Geben Sie für die umgekehrte Wiedergabe den negativen Wert der maximalen Rate zurück. |
| IsRateSupported | Gibt MF_E_UNSUPPORTED_RATE zurück, wenn der absolute Wert von flRate die maximale Wiedergaberate des Moderators ü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 die Hilfsmethode GetMaxRate zum Berechnen der maximalen Vorwärtswiedergaberate aufruft:
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 die EVR
Der Moderator muss den EVR über verschiedene Ereignisse benachrichtigen. Zu diesem Zwecke wird die IMediaEventSink-Schnittstelle des EVR verwendet, die beim Aufrufen der METHODETOPOLOGYServiceLookupClient::InitServicePointers des Moderators durch den EVR ermittelt wird. (Die IMediaEventSink-Schnittstelle ist ursprünglich eine DirectShow-Schnittstelle, wird jedoch sowohl im DirectShow EVR als auch im Media Foundation.) Der folgende Code zeigt, wie sie ein Ereignis an die EVR senden:
// 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 Moderator zusammen mit den Ereignisparametern sendet.
| Ereignis | BESCHREIBUNG |
|---|---|
| EC_COMPLETE | Der Moderator hat das Rendern aller Frames nach der MFVP_MESSAGE_ENDOFSTREAM abgeschlossen.
|
| EC_DISPLAY_CHANGED | Das Direct3D-Gerät wurde geändert.
|
| EC_ERRORABORT | Es ist ein Fehler aufgetreten, der das Beenden des Streamings erfordert.
|
| EC_PROCESSING_LATENCY | Gibt die Zeit an, die der Presenter zum Rendern der einzelnen Frames in Zeit nimmt. (Optional.)
|
| EC_SAMPLE_LATENCY | Gibt die aktuelle Verzögerungszeit in Renderingbeispielen an. Wenn der Wert positiv ist, liegen die Stichproben hinter dem Zeitplan. Wenn der Wert negativ ist, liegen die Stichproben dem Zeitplan voraus. (Optional.)
|
| EC_SCRUB_TIME | Wird sofort nach EC_STEP_COMPLETE gesendet, wenn die Wiedergaberate 0 (null) ist. Dieses Ereignis enthält den Zeitstempel des angezeigten Frames.
|
| EC_STEP_COMPLETE | Die Präsentation hat einen Rahmenschritt abgeschlossen oder abgebrochen.
[!Note] |
Aushandeln von Formaten
Wenn die Moderatorin eine MFVP_MESSAGE_INVALIDATEMEDIATYPE Nachricht von der EVR empfängt, muss sie das Ausgabeformat für den Mixer wie folgt festlegen:
Rufen Sie ÜBERTRANSFORM::GetOutputAvailableType auf dem Mixer auf, um einen möglichen Ausgabetyp abzurufen. Dieser Typ beschreibt ein Format, das der Mixer mit den Eingabestreams und den Videoverarbeitungsfunktionen des Grafikgeräts erzeugen kann.
Überprüfen Sie, ob der Presenter diesen Medientyp als Renderingformat verwenden kann. Hier sind einige Punkte zu überprüfen, obwohl Ihre Implementierung möglicherweise eigene Anforderungen hat:
- Das Video muss unkomprimiert sein.
- Das Video darf nur progressive Frames aufweisen. Ü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 erhalten Sie den nächsten vorgeschlagenen Mixertyp.
Erstellen Sie einen neuen Medientyp, der ein Klon des ursprünglichen Typs ist, und ändern Sie dann die folgenden Attribute:
- Legen Sie das MF_MT_FRAME_SIZE Attribut auf die gewünschte Breite und Höhe für die Direct3D-Oberflächen fest, die Sie zuordnen möchten.
- Legen Sie das attribut MF_MT_PAN_SCAN_ENABLED auf FALSE fest.
- Legen Sie das MF_MT_PIXEL_ASPECT_RATIO-Attribut auf den PAR der Anzeige fest (in der Regel 1:1).
- Legen Sie die geometrische Öffnung (MF_MT_GEOMETRIC_APERTURE Attribut) auf ein Rechteck innerhalb der Direct3D-Oberfläche fest. Wenn der Mixer einen Ausgaberahmen generiert, blitt er das Quellbild auf diesem Rechteck. Die geometrische Öffnung kann so groß sein wie die Oberfläche, oder sie kann ein Subrectangle innerhalb der Oberfläche sein. Weitere Informationen finden Sie unter Quell- und Zielrechtecke.
Um zu testen, ob der Mixer den geänderten Ausgabetyp akzeptiert, rufen Sie MITTRANSFORM::SetOutputType mit dem flag MFT_SET_TYPE_TEST_ONLY auf. Wenn der Mixer den Typ ablehnt, kehren Sie zu Schritt 1 zurück, und rufen Sie den nächsten Typ ab.
Ordnen Sie einen Pool von Direct3D-Oberflächen zu, wie unter Zuordnen von Direct3D-Oberflächenbeschrieben. Der Mixer verwendet diese Oberflächen, wenn er die zusammengesetzten Videoframes zeichnet.
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 dem Mixer die Typen ausgehen, gibt die GetOutputAvailableType-Methode MF_E_NO_MORE_TYPES zurück. Wenn die Darstellung keinen geeigneten Ausgabetyp für den Mixer finden kann, kann der Stream nicht gerendert werden. In diesem Fall kann DirectShow oder Media Foundation ein anderes Streamformat ausprobieren. Daher kann der Presenter mehrere MFVP_MESSAGE_INVALIDATEMEDIATYPE Nachrichten in einer Zeile empfangen, bis ein gültiger Typ gefunden wird.
Der Mixer postiert das Video automatisch unter Berücksichtigung des Pixelaspekt-Verhältnisses (PAR) der Quelle und des Ziels. Um optimale Ergebnisse zu erzielen, sollten die Breite und Höhe der Oberfläche und die geometrische Öffnung der tatsächlichen Größe entsprechen, die das Video auf der Anzeige anzeigen soll. Die folgende Abbildung veranschaulicht diesen Prozess.

Der folgende Code zeigt die Kontur des Prozesses. Einige der Schritte werden in Hilfsfunktionen platziert, deren genaue Details von den Anforderungen Ihres Moderators 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
Die Moderatorin erstellt das Direct3D-Gerät und behandelt alle Geräteverluste während des Streamings. Der Presenter hostet auch den Direct3D-Geräte-Manager, der anderen Komponenten die Verwendung desselben Geräts ermöglicht. Beispielsweise verwendet der Mixer das Direct3D-Gerät, um Substreams zu mischen, zu deinterlacen und Farbanpassungen durchzuführen. 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 zum Einrichten des Direct3D-Geräts die folgenden Schritte aus:
- Erstellen Sie das Direct3D-Objekt, indem Sie Direct3DCreate9 oder Direct3DCreate9Ex aufrufen.
- Erstellen Sie das Gerät, indem Sie IDirect3D9::CreateDevice oder IDirect3D9Ex::CreateDevice aufrufen.
- Erstellen Sie den Geräte-Manager, indem Sie DXVA2CreateDirect3DDeviceManager9aufrufen.
- Legen Sie das Gerät im Geräte-Manager fest, indem Sie IDirect3DDeviceManager9::ResetDeviceaufrufen.
Wenn eine andere Pipelinekomponente den Geräte-Manager benötigt, ruft sie AUFGETService::GetService auf der EVR auf und gibt MR_VIDEO_ACCELERATION_SERVICE für die Dienst-GUID an. Die EVR übergibt die Anforderung an die Moderatorin. Nachdem das -Objekt den IDirect3DDeviceManager9-Zeiger erhalten hat, kann es ein Handle für das Gerät abrufen, indem IDirect3DDeviceManager9::OpenDeviceHandleaufgerufen wird. Wenn das Objekt das Gerät verwenden muss, übergibt es das Gerätehandle an die IDirect3DDeviceManager9::LockDevice-Methode, die einen IDirect3DDevice9-Zeiger zurückgibt.
Wenn das Gerät nach der Erstellung des Geräts zerstört und ein neues erstellt wird, muss die Moderatorin ResetDevice erneut aufrufen. Die ResetDevice-Methode macht alle vorhandenen Gerätehandles ungültig, wodurch LockDevice DXVA2_E_NEW_VIDEO_DEVICE zurückgibt. Dieser Fehlercode signalisiert anderen Objekten, die das Gerät verwenden, dass sie ein neues Gerätehandle öffnen sollten. Weitere Informationen zur Verwendung des Geräte-Managers finden Sie unter Direct3D Geräte-Manager.
Die Moderatorin 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 Standard-Presenter implementiert zu diesem Zweck die METHODE THICKNESSVideoDisplayControl::SetVideoWindow. Sie müssen das Gerät erstellen, wenn die Präsentation zum ersten Mal erstellt wird. In der Regel kennen Sie zu diesem Zeitpunkt nicht alle Geräteparameter, z. B. das Fenster oder das Backpufferformat. Sie können ein temporäres Gerät erstellen und es später&ersetzen. Denken Sie # daran, ResetDevice im Geräte-Manager 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 die EVR. Dieses Ereignis benachrichtigt die EVR, den Medientyp neu auszuhandeln. Die EVR ignoriert die Ereignisparameter für dieses Ereignis.
Zuordnen von Direct3D-Oberflächen
Nachdem die Moderatorin den Medientyp festgelegt hat, kann sie die Direct3D-Oberflächen zuordnen, die der Mixer zum Schreiben der Videoframes verwendet. Die Oberfläche muss mit dem Medientyp des Moderators übereinstimmen:
- Das Oberflächenformat muss mit dem Medienuntertyp übereinstimmen. Wenn der Untertyp beispielsweise MFVideoFormat_RGB24 ist, muss das Oberflächenformat D3DFMT_X8R8G8B8 sein. Weitere Informationen zu Untertypen und Direct3D-Formaten finden Sie unter Video-Untertyp-GUIDs.
- Die Breite und Höhe der Oberfläche muss mit den Dimensionen übereinstimmen, die im MF_MT_FRAME_SIZE-Attribut des Medientyps angegeben sind.
Die empfohlene Methode zum Zuordnen von Oberflächen hängt davon ab, ob die Präsentation im Fenster oder im Vollbildmodus ausgeführt wird.
Wenn das Direct3D-Gerät eingeblendet ist, können Sie mehrere Swapketten erstellen, die jeweils einen einzelnen Hintergrundpuffer haben. Mit diesem Ansatz können Sie jede Oberfläche unabhängig darstellen, da die Darstellung einer Swapkette die anderen Swapketten nicht beeinträchtigt. Der Mixer kann Daten in eine Oberfläche schreiben, während eine andere Oberfläche für die Präsentation geplant ist.
Entscheiden Sie zunächst, wie viele Swapketten erstellt werden sollen. Es werden mindestens drei empfohlen. Gehen Sie für jede Swapkette wie folgt vor:
- Rufen Sie IDirect3DDevice9::CreateAdditionalSwapChain auf, um die Swapkette zu erstellen.
- Rufen Sie IDirect3DSwapChain9::GetBackBuffer auf, um einen Zeiger auf die Hintergrundpufferoberfläche der Swapkette abzurufen.
- Rufen Sie MFCreateVideoSampleFromSurface auf, und übergeben Sie einen Zeiger auf die Oberfläche. Diese Funktion gibt einen Zeiger auf ein Videobeispielobjekt zurück. Das Videobeispielobjekt implementiert die INTERFACESSample-Schnittstelle, und die Moderatorin verwendet diese Schnittstelle, um die Oberfläche an den Mixer zu übermitteln, wenn die Moderatorin die MIXERTransform::P rocessOutput-Methode des Mixers aufruft. Weitere Informationen zum Videobeispielobjekt finden Sie unter Videobeispiele.
- Store den POINTERSample-Zeiger in einer Warteschlange. Die Moderatorin pullt während der Verarbeitung Stichproben aus dieser Warteschlange, wie unter Verarbeiten von Ausgabebeschrieben.
- Behalten Sie einen Verweis auf den IDirect3DSwapChain9-Zeiger bei, damit die Swapkette nicht freigegeben wird.
Im exklusiven Vollbildmodus darf das Gerät nicht mehr als eine Auslagerungskette aufweisen. Diese Swapkette wird implizit erstellt, wenn Sie das Vollbildgerät erstellen. Die Swapkette kann mehr als einen Hintergrundpuffer aufweisen. Wenn Sie jedoch beim Schreiben in einen anderen Rückpuffer in derselben Swapkette einen Rückpuffer darstellen, gibt es keine einfache Möglichkeit, die beiden Vorgänge zu koordinieren. Dies liegt daran, wie Direct3D Oberflächenspiegelung implementiert. Wenn Sie Present aufrufen, aktualisiert der Grafiktreiber die Oberflächenzeiger im Grafikspeicher. Wenn Sie IDirect3DSurface9-Zeiger beim Aufrufen von Present gedrückt halten, zeigen sie auf unterschiedliche Puffer, nachdem der Present-Aufruf zurückgegeben wurde.
Die einfachste Möglichkeit besteht darin, ein Videobeispiel für die Swapkette zu erstellen. Wenn Sie diese Option auswählen, führen Sie die gleichen Schritte für den Fenstermodus aus. Der einzige Unterschied besteht darin, dass die Beispielwarteschlange ein einzelnes Videobeispiel enthält. Eine weitere Möglichkeit besteht darin, Offscreenoberflächen zu erstellen und sie dann in den Hintergrundpuffer zu blitten. Die oberflächen, die Sie erstellen, müssen die IDirectXVideoProcessor::VideoProcessBlt-Methode unterstützen, die der Mixer verwendet, um die Ausgabeframes zu zusammengesetzt.
Überwachungsbeispiele
Wenn die Moderatorin die Videobeispiele zum ersten Mal zuordnet, platziert sie sie in eine Warteschlange. Die Moderatorin zieht immer dann aus dieser Warteschlange, wenn sie einen neuen Frame vom Mixer abrufen muss. Nachdem der Mixer den Frame ausgegeben hat, verschiebt der Moderator das Beispiel in eine zweite Warteschlange. Die zweite Warteschlange ist für Beispiele bestimmt, die auf ihre geplanten Präsentationszeiten warten.
Um die Nachverfolgung des Status jedes Beispiels zu vereinfachen, implementiert das Videobeispielobjekt die INTERFACESTrackedSample-Schnittstelle. Sie können diese Schnittstelle wie folgt verwenden:
Implementieren Sie die INTERFACESAsyncCallback-Schnittstelle in Ihrem Presenter.
Bevor Sie ein Beispiel in der geplanten Warteschlange platzieren, fragen Sie das Videobeispielobjekt für die INTERFACESTrackedSample-Schnittstelle ab.
Rufen Sie ÜBER EINEN Zeiger auf Ihre Rückrufschnittstelle DIE CALLTRACKEDSample::SetAllocator-Schnittstelle auf.
Wenn das Beispiel zur Präsentation bereit ist, entfernen Sie es aus der geplanten Warteschlange, stellen Sie es vor, und geben Sie alle Verweise auf das Beispiel frei.
Im Beispiel wird der Rückruf aufgerufen. (Das Beispielobjekt wird in diesem Fall nicht gelöscht, da es einen Verweiszähler für sich selbst enthält, bis der Rückruf aufgerufen wird.)
Geben Sie innerhalb des Rückrufs das Beispiel an die verfügbare Warteschlange zurück.
Es ist nicht erforderlich, dass ein Presenter ZUM Nachverfolgen von Beispielen DIESTRACKEDSample verwendet. Sie können jede Technik implementieren, die für Ihren Entwurf am besten geeignet ist. Ein Vorteil von ADVANTAGETrackedSample besteht darin, dass Sie die Planungs- und Renderingfunktionen des Moderators in Hilfsobjekte verschieben können, und diese Objekte benötigen keinen speziellen Mechanismus zum Aufrufen an die Moderatorin, wenn sie Videobeispiele freigeben, da das Beispielobjekt diesen Mechanismus bereitstellt.
Der folgende Code zeigt, wie der Rückruf festgelegt wird:
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 Rückruf DAS ASYNCHRONOUSAsyncResult::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;
}
Verarbeiten der Ausgabe
Wenn der Mixer ein neues Eingabebeispiel empfängt, sendet die EVR eine MFVP_MESSAGE_PROCESSINPUTNOTIFY Nachricht an die Moderatorin. Diese Meldung weist darauf hin, dass der Mixer möglicherweise über einen neuen Videoframe für die Bereitstellung verfügen kann. Als Antwort ruft die Moderatorin ÜBERTRANSFORM::P rocessOutput auf dem Mixer auf. Wenn die Methode erfolgreich ausgeführt wird, plant die Moderatorin das Beispiel für die Präsentation.
Führen Sie die folgenden Schritte aus, um die Ausgabe vom Mixer abzurufen:
Überprüfen Sie den Uhrzustand. Wenn die Uhr angehalten wird, ignorieren Sie die MFVP_MESSAGE_PROCESSINPUTNOTIFY Meldung, es sei denn, dies ist der erste Videoframe. Wenn die Uhr ausgeführt wird oder es sich um den ersten Videoframe handelt, fahren Sie fort.
Abrufen eines Beispiels aus der Warteschlange der verfügbaren Beispiele. Wenn die Warteschlange leer ist, bedeutet dies, dass derzeit alle zugeordneten Stichproben für die Präsentation geplant sind. Ignorieren Sie in diesem Fall die MFVP_MESSAGE_PROCESSINPUTNOTIFY Nachricht zu diesem Zeitpunkt. Wenn das nächste Beispiel verfügbar ist, wiederholen Sie die hier aufgeführten Schritte.
(Optional.) Wenn die Uhr verfügbar ist, rufen Sie die aktuelle Uhrzeit (T1) ab, indem Sie DENClock::GetCorrelatedTimeaufrufen.
Rufen Sie ÜBERTRANSFORM::P rocessOutput 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 mehr Eingaben, bevor er einen neuen Ausgaberahmen erzeugen kann.
Wenn Sie diesen Fehlercode erhalten, überprüfen Sie, ob die EVR das Ende des Streams erreicht hat, und reagieren Sie entsprechend, wie unter Ende von Streambeschrieben. Andernfalls ignorieren Sie diese MF_E_TRANSFORM_NEED_MORE_INPUT Meldung. Die EVR sendet eine andere, wenn der Mixer mehr Eingaben erhält.MF_E_TRANSFORM_STREAM_CHANGE Der Ausgabetyp des Mixers ist ungültig geworden, möglicherweise aufgrund einer Formatänderung im Upstream.
Wenn Sie diesen Fehlercode erhalten, legen Sie den Medientyp des Moderators auf NULL fest. Die EVR fordert ein neues Format an.MF_E_TRANSFORM_TYPE_NOT_SET Der Mixer erfordert einen neuen Medientyp.
Wenn Sie diesen Fehlercode erhalten, müssen Sie den Ausgabetyp des Mixers neu aushandeln, wie unter Aushandeln von Formatenbeschrieben.Wenn ProcessOutput erfolgreich ist, fahren Sie fort.
(Optional.) Wenn die Uhr verfügbar ist, erhalten Sie die aktuelle Uhrzeit (T2). Die vom Mixer eingeführte Latenz beträgt (T2 - T1). Senden Sie ein EC_PROCESSING_LATENCY-Ereignis mit diesem Wert an die EVR. Die EVR verwendet diesen Wert für die Qualitätskontrolle. Wenn keine Uhr verfügbar ist, gibt es keinen Grund, das ereignis EC_PROCESSING_LATENCY zu senden.
(Optional.) Fragen Sie das Beispiel für DIE NACHVERFOLGUNGNACHVERFOLGUNGSAMPLE ab, und rufen Sie DIESETRACKEDSample::SetAllocator auf, wie unter Nachverfolgungsbeispielebeschrieben.
Planen Sie das Beispiel für die Präsentation.
Diese Sequenz von Schritten kann beendet werden, bevor der Moderator eine Ausgabe vom Mixer erhält. Um sicherzustellen, dass keine Anforderungen gelöscht werden, sollten Sie die gleichen Schritte wiederholen, wenn Folgendes auftritt:
- Die PRESENTClockStateSink::OnClockStart- oder DIEClockStateSink::OnClockStart-Methode des Moderators wird aufgerufen. Dies behandelt den Fall, in dem der Mixer Eingaben ignoriert, da die Uhr angehalten wird (Schritt 1).
- Der RÜCKRUF FÜR DIE NACHVERFOLGUNGSAMPLE-Methode wird aufgerufen. Dies behandelt den Fall, in dem der Mixer Eingaben empfängt, aber alle Videobeispiele der Moderatorin verwendet werden (Schritt 2).
In den nächsten Codebeispielen werden diese Schritte ausführlicher dargestellt. Der Presenter ruft die ProcessInputNotify -Methode (wie 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 über eine neue Eingabe verfügt. Anschließend ruft sie die ProcessOutputLoop -Methode auf, die im nächsten Beispiel gezeigt wird. Diese Methode versucht, so viele Stichproben wie möglich aus dem Mixer zu pullen:
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 ProcessOutput -Methode, die im nächsten Beispiel gezeigt wird, versucht, einen einzelnen Videoframe vom Mixer abzurufen. Wenn kein Videoframe verfügbar ist, ProcessSample gibt S_FALSE oder einen Fehlercode zurück, der die Schleife in unterbricht. ProcessOutputLoop 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 Sammlungsobjekt angenommen, das die Warteschlange verfügbarer Videobeispiele enthält. Die -Methode des -Objekts
GetSamplegibt 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 die Darstellung das flag m_fSampleNotify löscht.
- Die
TrackSample-Methode, mit der der RÜCKRUF VONTRACKEDSAMPLE festgelegt wird, wird im Abschnitt Nachverfolgungsbeispiele gezeigt.
Neumalen von Frames
Gelegentlich muss die Moderatorin möglicherweise den neuesten Videoframe neu malen. Beispielsweise bemalt der Standard-Presenter den Frame in den folgenden Situationen neu:
- Im Fenstermodus als Reaktion auf WM_PAINT Nachrichten, die vom Fenster der Anwendung empfangen werden. Weitere Informationen finden Sie unter DEM VIDEODisplayControl::RepaintVideo.
- Wenn sich das Zielrechteck ändert, wenn die Anwendung DEN AUFRUF VON THEVIDEODisplayControl::SetVideoPosition aufruft.
Verwenden Sie die folgenden Schritte, um den Mixer anzufordern, den neuesten Frame neu zu erstellen:
- Abrufen eines Videobeispiels aus der Warteschlange.
- Fragen Sie das Beispiel für die INTERFACESDesiredSample-Schnittstelle ab.
- Rufen Sie DANNDESiredSample::SetDesiredSampleTimeAndDuration auf. Geben Sie den Zeitstempel des letzten Videoframes an. (Sie müssen diesen Wert zwischenspeichern und für jeden Frame aktualisieren.)
- Rufen Sie ProcessOutput für den Mixer auf.
Beim Neupaintieren eines Frames können Sie die Präsentationsuhr ignorieren und den Rahmen sofort präsentieren.
Zeitplanungsbeispiele
Videoframes können den EVR jederzeit erreichen. Der Presenter ist dafür verantwortlich, jeden Frame zum richtigen Zeitpunkt basierend auf dem Zeitstempel des Frames zu präsentieren. Wenn der Moderator ein neues Beispiel aus dem Mixer erhält, wird das Beispiel in die geplante Warteschlange eingereiht. In einem separaten Thread ruft der Moderator kontinuierlich das erste Beispiel vom Anfang der Warteschlange ab und bestimmt, ob Dies der Fall ist:
- Stellen Sie das Beispiel vor.
- Behalten Sie das Beispiel in der Warteschlange bei, da es früh ist.
- Verwerfen Sie das Beispiel, da es zu spät ist. Obwohl Sie möglichst vermeiden sollten, Frames zu löschen, müssen Sie dies möglicherweise machen, wenn der Moderator ständig ins Rückstand geraten wird.
Um den Zeitstempel für einen Videoframe zu erhalten, rufen Sie IMTSample::GetSampleTime im Videobeispiel auf. Der Zeitstempel ist relativ zur Präsentationsuhr des EVR. Um die aktuelle Uhrzeit zu erhalten, rufen Sie DIEClock::GetCorrelatedTime auf. Wenn der EVR keine Präsentationsuhr hat oder ein Beispiel keinen Zeitstempel hat, können Sie das Beispiel sofort nach dem Erhalten präsentieren.
Um die Dauer jedes Beispiels zu erhalten, rufen Sie DIESAMPLE::GetSampleDuration auf. Wenn das Beispiel über keine Dauer verfügt, 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, wird die Uhr mit einer schnelleren oder langsameren Geschwindigkeit ausgeführt. Das bedeutet, dass der Zeitstempel für ein Beispiel immer die richtige Zielzeit relativ zur Präsentationsuhr liefert. Wenn Sie die Präsentationszeiten jedoch in eine andere Uhrzeit (z. B. den Leistungsindikator für hohe Auflösung) übersetzen, müssen Sie die Zeiten basierend auf der Taktgeschwindigkeit skalieren. Wenn sich die Taktgeschwindigkeit ändert, ruft der EVR die METHODE DER MODERATORClockStateSink::OnClockSetRate auf.
- Die Wiedergaberate kann bei umgekehrter Wiedergabe negativ sein. Wenn die Wiedergaberate negativ ist, wird die Präsentationsuhr rückwärts ausgeführt. Anders ausgedrückt: Die Zeit N + 1 tritt vor der Zeit N auf.
Im folgenden Beispiel wird berechnet, wie früh oder spät ein Beispiel relativ 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 Audiodaten verwendet.) Im Allgemeinen wird die Präsentationsuhr nicht mit der Aktualisierungsrate des Monitors synchronisiert.
Wenn Ihre Direct3D-Präsentationsparameter D3DPRESENT_INTERVAL_DEFAULT oder D3DPRESENT_INTERVAL_ONE für das Präsentationsintervall angeben, wartet der Present-Vorgang auf die vertikale Rückverziehung des Monitors. Dies ist eine einfache Möglichkeit, um Dasins zu verhindern, verringert jedoch die Genauigkeit Ihres Planungsalgorithmus. Wenn das Präsentationsintervall hingegen D3DPRESENT_INTERVAL_IMMEDIATE ist, wird die Present-Methode sofort ausgeführt. Dies führt zu einer Tränkung, es sei denn, Ihr Planungsalgorithmus ist genau genug, dass Sie Present nur während der vertikalen Rücklaufzeit aufrufen.
Mit den folgenden Funktionen können Sie genaue Zeitsteuerungsinformationen erhalten:
- IDirect3DDevice9::GetRasterStatus gibt Informationen zum Raster zurück, einschließlich der aktuellen Scanzeile und ob sich das Raster im vertikalen leeren Bereich befindet.
- DwmGetCompositionTimingInfo gibt Zeitsteuerungsinformationen 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 Austauschkette für jede Oberfläche erstellt haben, wie unter Allocating Direct3D Surfaces (Zuordnen von Direct3D-Oberflächen) beschrieben. Um ein Beispiel zu präsentieren, erhalten Sie die Swap-Kette wie folgt aus dem Videobeispiel:
- Rufen Sie IMBSAMPLE::GetBufferByIndex im Videobeispiel auf, um den Puffer zu erhalten.
- Fragen Sie den Puffer für die BENUTZEROBERFLÄCHEGetService-Schnittstelle ab.
- Rufen SieGEGETService::GetService auf, um die IDirect3DSurface9-Schnittstelle der Direct3D-Oberfläche zu erhalten. (Sie können diesen Schritt und den vorherigen Schritt zu einem Schritt kombinieren, indem Sie MFGetService aufrufen.)
- Rufen Sie IDirect3DSurface9::GetContainer auf der Oberfläche auf, um einen Zeiger auf die Austauschkette zu erhalten.
- Rufen Sie IDirect3DSwapChain9::P in der Auslagerungskette zurück.
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 Anzuzeigende Teil des Videoframes. Sie wird relativ zu einem normalisierten Koordinatensystem definiert, bei dem der gesamte Videoframe ein Rechteck mit den Koordinaten {0, 0, 1, 1} einnimmt. Das Zielrechteck ist der Bereich innerhalb der Zieloberfläche, in dem der Videoframe gezeichnet wird. Die Standardvorschübe ermöglicht einer Anwendung das Festlegen dieser Rechtecke durch Aufrufen von RECTANGLEVideoDisplayControl::SetVideoPosition.
Es gibt mehrere Optionen zum Anwenden von Quell- und Zielrechtecke. Die erste Option besteht in der Anwendung durch den Mixer:
- Legen Sie das Quellrechteck mithilfe des VIDEO_ZOOM_RECT fest. Der Mixer wird das Quellrechteck anwenden, wenn er das Video auf die Zieloberfläche blitt. Das Standardquellenrechteck des Mixers ist der gesamte Frame.
- Legen Sie das Zielrechteck als geometrische Öffnung im Ausgabetyp des Mixers fest. Weitere Informationen finden Sie unter Aushandeln von Formaten.
Die zweite Option besteht in der Anwendung der Rechtecke, wenn Sie IDirect3DSwapChain9::P resent verwenden, indem Sie die Parameter pSourceRect und pDestRect in der Present-Methode angeben. Sie können diese Optionen kombinieren. Beispielsweise können Sie das Quellrechteck für den Mixer festlegen, aber das Zielrechteck in der Present-Methode anwenden.
Wenn die Anwendung das Zielrechteck ändert oder die Größe des Fensters ändert, müssen Sie möglicherweise neue Oberflächen zuordnen. Wenn dies der Fall ist, müssen Sie darauf achten, diesen Vorgang mit Ihrem Planungsthread zu synchronisieren. Leeren Sie die Planungswarteschlange, und verwerfen Sie die alten Stichproben, bevor Sie neue Oberflächen zuordnen.
Ende des Streams
Wenn jeder Eingabestream auf der EVR beendet wurde, sendet der EVR eine MFVP_MESSAGE_ENDOFSTREAM nachricht an den Moderator. An dem Punkt, an dem Sie die Nachricht empfangen, müssen jedoch möglicherweise noch einige Videoframes verarbeitet werden. Bevor Sie auf die Nachricht zum Ende des Streams antworten, müssen Sie die ganze Ausgabe des Mixers leeren und alle verbleibenden Frames anzeigen. Nachdem der letzte Frame angezeigt wurde, senden Sie ein EC_COMPLETE an die EVR.
Das nächste Beispiel zeigt eine Methode, die das EC_COMPLETE sendet, wenn verschiedene Bedingungen erfüllt sind. Andernfalls wird S_OK zurückgegeben, ohne das Ereignis zu senden:
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 true ist, bedeutet dies, dass der Mixer über einen oder mehrere Frames verfügt, die noch nicht verarbeitet wurden. (Weitere Informationen finden Sie unter Processing Output.)
- Die m_fEndStreaming variable ist ein boolesches Flag, dessen Anfangswert FALSE ist. Der Moderator legt das Flag auf TRUE fest, wenn der EVR die MFVP_MESSAGE_ENDOFSTREAM sendet.
- Es wird davon ausgegangen, dass die Methode TRUE zurück gibt, solange ein oder mehrere Frames
AreSamplesPendingin der geplanten Warteschlange warten.
Legen Sie m_fEndStreaming der METHODE VERANMELDEVIDEOPresenter::P rocessMessage auf TRUE fest, und rufen Sie auf, wenn die EVR die MFVP_MESSAGE_ENDOFSTREAM CheckEndOfStream 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 darüber hinaus auf, CheckEndOfStream wenn die METHODE VERFORMTransform::P rocessOutput des Mixers MF_E_TRANSFORM_NEED_MORE_INPUT. Dieser Fehlercode gibt an, dass der Mixer keine Eingabebeispiele mehr enthält (siehe Verarbeiten der Ausgabe).
Rahmenschritt
Der EVR ist für die Unterstützung von Rahmenschritten in DirectShow und bereinigung in Media Foundation. Frameschritt- und Bereinigungsschritte sind konzeptionell ähnlich. In beiden Fällen fordert die Anwendung einen Videoframe nach dem anderen an. Intern verwendet der Presenter denselben Mechanismus, um beide Features zu implementieren.
Die Rahmenschritte in DirectShow funktionieren wie folgt:
- Die Anwendung ruft IVideoFrameStep::Step auf. Die Anzahl der Schritte wird im dwSteps-Parameter angegeben. Der EVR sendet eine MFVP_MESSAGE_STEP nachricht an den Presenter, wobei der Message-Parameter (ulParam) die Anzahl der Schritte ist.
- Wenn die Anwendung IVideoFrameStep::CancelStep aufruft oder den Graphzustand ändert (wird ausgeführt, angehalten oder beendet), sendet die EVR eine MFVP_MESSAGE_CANCELSTEP Nachricht.
Die Bereinigung in Media Foundation funktioniert wie folgt:
- Die Anwendung legt die Wiedergaberate auf 0 (null) fest, indem sie DURCH AUFRUFEN VONTRATEControl::SetRate aufruft.
- Um einen neuen Frame zu rendern, ruft die Anwendung DIETMEDIASession::Start mit der gewünschten Position auf. Der EVR sendet eine MFVP_MESSAGE_STEP nachricht mit ulParam gleich 1.
- Um die Bereinigung zu beenden, legt die Anwendung die Wiedergaberate auf einen Wert ungleich 0 (null) fest. Der EVR sendet die MFVP_MESSAGE_CANCELSTEP Nachricht.
Nachdem die MFVP_MESSAGE_STEP empfangen wurde, wartet die Moderatorin, bis der Zielframe eintrifft. Wenn die Anzahl der Schritte N beträgt, verwirft der Moderator die nächsten (N - 1) Beispiele und stellt das N-te Beispiel vor. Wenn die Moderatorin den Rahmenschritt abgeschlossen hat, sendet sie ein EC_STEP_COMPLETE-Ereignis an die EVR, bei dem lParam1 auf FALSE festgelegt ist. Wenn die Wiedergaberate 0 (null) ist, sendet der Moderator außerdem ein EC_SCRUB_TIME Ereignis. Wenn die EVR frame stepping abbricht, während ein Frameschrittvorgang noch aussteht, sendet die Moderatorin ein EC_STEP_COMPLETE-Ereignis, wobei lParam1 auf TRUE festgelegt ist.
Die Anwendung kann schritt- oder bereinigungsschritte mehrmals umrahmen, sodass der Moderator möglicherweise mehrere MFVP_MESSAGE_STEP empfangen kann, bevor er eine MFVP_MESSAGE_CANCELSTEP erhält. Außerdem kann der Moderator die MFVP_MESSAGE_STEP empfangen, bevor die Uhr gestartet wird oder während die Uhr ausgeführt wird.
Implementieren von Rahmenschritten
In diesem Abschnitt wird ein Algorithmus zum Implementieren von Rahmenschritten beschrieben. Der Frameschrittalgorithmus verwendet die folgenden Variablen:
step_count. Eine ganze Zahl ohne Vorzeichen, die die Anzahl der Schritte im aktuellen Rahmenschrittvorgang angibt.
step_queue. Eine Warteschlange von WARTESCHLANGENsample-Zeigern.
step_state. Der Moderator kann sich in Bezug auf die Rahmenschritte jederzeit in einem der folgenden Zustände befingen:
Diese Zustände sind unabhängig von den Presenterzuständen, die im Abschnitt Presenter States aufgeführt sind.
Die folgenden Verfahren sind für den Algorithmus für die Frameschritt-Prozedur definiert:
PrepareFrameStep-Prozedur
- Inkrement step_count.
- Legen step_state auf WAITING fest.
- Wenn die Uhr ausgeführt wird, rufen Sie StartFrameStep auf.
StartFrameStep-Prozedur
- Wenn step_state warte, legen Sie step_state ausstehend fest. Rufen Sie für jedes Beispiel in step_queue deliverFrameStepSample auf.
- Wenn step_state gleich NOT_STEPPING, entfernen Sie alle Beispiele aus step_queue und planen Sie sie für die Präsentation.
CompleteFrameStep-Prozedur
- Legen step_state auf COMPLETE fest.
- Senden Sie das EC_STEP_COMPLETE mit lParam1 = FALSE.
- Wenn die Taktrate null ist, senden Sie das EC_SCRUB_TIME mit der Beispielzeit.
DeliverFrameStepSample-Prozedur
- Wenn die Taktrate 0 (null) und die Stichprobendauer + der Stichprobendauer < ist, verwerfen Sie die Stichprobe. Beenden
- Wenn step_state SCHEDULED oder COMPLETE entspricht, fügen Sie das Beispiel zu step_queue. Beenden
- Dekrementierung step_count.
- Wenn step_count > 0 ist, verwerfen Sie das Beispiel. Beenden
- Wenn step_state warte, fügen Sie das Beispiel zu step_queue. Beenden
- Planen Sie das Beispiel für die Präsentation.
- Legen step_state auf SCHEDULED fest.
CancelFrameStep-Prozedur
- Legen step_state auf NOT_STEPPING
- Setzen step_count null zurück.
- Wenn der vorherige Wert von step_state WAITING, PENDING oder SCHEDULED war, senden Sie EC_STEP_COMPLETE lParam1 = TRUE.
Rufen Sie diese Verfahren wie folgt auf:
| Presenter-Nachricht oder -Methode | Vorgehensweise |
|---|---|
| MFVP_MESSAGE_STEP Nachricht | PrepareFrameStep |
| MFVP_MESSAGE_STEP Nachricht | CancelStep |
| DEADClockStateSink::OnClockStart | StartFrameStep |
| DEADClockStateSink::OnClockRestart | StartFrameStep |
| RÜCKRUFTrackedSample-Rückruf | CompleteFrameStep |
| DEADClockStateSink::OnClockStop | CancelFrameStep |
| DEADClockStateSink::OnClockSetRate | CancelFrameStep |
Das folgende Flussdiagramm zeigt die Prozeduren für die Frameschritte.

Festlegen des Presenters auf der EVR
Nach der Implementierung des Presenters besteht der nächste Schritt im Konfigurieren der EVR für die Verwendung.
Festlegen des Presenters in DirectShow
Legen Sie in einer DirectShow-Anwendung den Presenter auf der EVR wie folgt fest:
- Erstellen Sie den EVR-Filter, indem Sie CoCreateInstance aufrufen. Die CLSID ist CLSID_EnhancedVideoRenderer.
- Fügen Sie die EVR dem Filterdiagramm hinzu.
- Erstellen Sie eine Instanz Ihrer Präsentation. Ihre Präsentation kann die Standardmäßigerstellung von COM-Objekten über IClassFactory unterstützen, dies ist jedoch nicht zwingend erforderlich.
- Fragen Sie den EVR-Filter für die BENUTZEROBERFLÄCHEVideoRenderer-Schnittstelle ab.
- Rufen Sie DIE NSDVideoRenderer::InitializeRenderer auf.
Festlegen des Presenters in Media Foundation
In Media Foundation haben Sie mehrere Optionen, je nachdem, ob Sie die EVR-Mediensenke oder das EVR-Aktivierungsobjekt erstellen. Weitere Informationen zu Aktivierungsobjekten finden Sie unter Activation Objects.
Gehen Sie für die EVR-Mediensenke wie folgt vor:
- Rufen Sie MFCreateVideoRenderer auf, um die Mediensenke zu erstellen.
- Erstellen Sie eine Instanz Ihrer Präsentation.
- Fragen Sie die EVR-Mediensenke für die BENUTZEROBERFLÄCHEVideoRenderer-Schnittstelle ab.
- Rufen Sie DIE NSDVideoRenderer::InitializeRenderer auf.
Gehen Sie für das EVR-Aktivierungsobjekt wie folgt vor:
Rufen Sie MFCreateVideoRendererActivate auf, um das Aktivierungsobjekt zu erstellen.
Legen Sie eines der folgenden Attribute für das Aktivierungsobjekt fest:
attribute BESCHREIBUNG MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_ACTIVATE Zeiger auf ein Aktivierungsobjekt für den Presenter.
Mit diesem Flag müssen Sie ein Aktivierungsobjekt für Ihre Präsentation bereitstellen. Das Aktivierungsobjekt muss die -SCHNITTSTELLE FÜR DIE-Aktivierung implementieren.MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_CLSID CLSID des Presenters.
Mit diesem Flag muss Ihr Presenter die Com-Standardobjekterstellung über IClassFactory unterstützen.Legen Sie optional das attribut MF_ACTIVATE_CUSTOM_VIDEO_PRESENTER_FLAGS für das Aktivierungsobjekt fest.