Media Foundation verstehen – Warum sich die API wie COM anfühlt
· Go Komura · Media Foundation, COM, C++, Windows-Entwicklung
Wer anfängt, mit Media Foundation zu arbeiten, hat schnell das Gefühl: „Eigentlich wollte ich nur die Windows-APIs für Video und Audio benutzen, und plötzlich dreht sich alles um COM.“ CoInitializeEx, MFStartup, IMFSourceReader, IMFMediaType, IMFTransform, IMFActivate, HRESULT, GUIDs – all das taucht auf einmal auf, die Atmosphäre wird plötzlich sehr Win32/COM-lastig, und was Media Foundation eigentlich ist, wird schwer zu erkennen.
Dieser Artikel deckt Media Foundation nicht wie ein Lexikon vollständig ab, sondern beschränkt sich auf diese drei Punkte:
- Warum kommt beim Arbeiten mit Media Foundation ganz natürlich COM zur Sprache?
- An welchen Stellen wird die COM-Färbung besonders deutlich?
- Womit sollte man zu Beginn arbeiten – Source Reader, Sink Writer, Media Session oder MFT?
Die Codebeispiele basieren auf C++, aber die Denkweise ist im Wesentlichen dieselbe, wenn man über einen Wrapper etwa aus .NET zugreift.
Inhaltsverzeichnis
- Zuerst das Fazit (in Kürze)
- Begriffe und Gesamtbild
- 2.1. Zunächst die Bedeutung der wichtigsten Begriffe
- 2.2. Das Gesamtbild von Media Foundation (Diagramm)
- Wo Media Foundation ihr COM-Gesicht zeigt
- 3.1. Bei der Initialisierung stehen
CoInitializeExundMFStartupnebeneinander - 3.2. Die Übergabe von Objekten ist schnittstellenzentriert
- 3.3. Einstellungen und Typinformationen drehen sich um
IMFAttributesund GUIDs - 3.4. Activation Objects tauchen auf
- 3.5. Auch der Umgang mit Asynchronität, Callbacks und Threads ist COM-artig
- 3.1. Bei der Initialisierung stehen
- Media Foundation ist jedoch nicht gleich COM
- Womit man einsteigt (die Wahl des Einstiegspunkts)
- 5.1. Der Fall, dass man zunächst mit dem Source Reader einsteigt
- 5.2. Für das Schreiben in eine Datei: der Sink Writer
- 5.3. Für Wiedergabe und Synchronisation: die Media Session
- 5.4. Für eigene Bausteine: MFT
- Checkliste für die Praxis
- Zusammenfassung
- Quellen
1. Zuerst das Fazit (in Kürze)
- Media Foundation ist eine Plattform zur Verarbeitung von Video und Audio; die gesamte API ist nicht einfach reines COM
- Allerdings werden die Grenzen zwischen Source, Transform, Sink, Activation, Attributes und Callback durch COM-Schnittstellen dargestellt, weshalb beim Arbeiten damit ganz natürlich
IUnknown,HRESULT, GUID und Apartment ins Spiel kommen - Am einfachsten lässt es sich einordnen, wenn man zunächst mit Source Reader / Sink Writer einsteigt, bei Bedarf an Wiedergabesteuerung zur Media Session übergeht und bei Bedarf an eigenen Transformationen zum MFT
Kurz gesagt: Media Foundation ist eine Medienverarbeitungsplattform, in deren Grenzflächen COM tief eingebettet ist.
Behält man das im Hinterkopf, wird deutlich klarer, „warum sich plötzlich das COM-Gesicht zeigt“.
2. Begriffe und Gesamtbild
Bevor es um COM geht, klären wir zunächst nur die in diesem Artikel verwendeten Begriffe und den groben Rahmen von Media Foundation.
2.1. Zunächst die Bedeutung der wichtigsten Begriffe
| Begriff | Bedeutung in diesem Artikel |
|---|---|
| Media Source | Der Eingang, über den Mediendaten in die Pipeline gelangen. Datei, Netzwerk, Aufnahmegerät und Ähnliches |
| MFT | Media Foundation Transform. Gemeinsames Modell für Decoder, Encoder, Bildkonverter und Ähnliches |
| Media Sink | Das Ziel der Mediendaten. Bildschirmanzeige, Audioausgabe, Dateiausgabe und Ähnliches |
| Media Session | Der Mechanismus, der den Ablauf der gesamten Pipeline verwaltet. Zuständig für Wiedergabe und Synchronisation |
| Topology | Das Verbindungsschema, das darstellt, wie Source, Transform und Sink verknüpft werden |
| Activation Object | Ein Hilfsobjekt, um das eigentliche Objekt erst später zu erzeugen. Wird durch IMFActivate dargestellt |
| Attributes | Ein Key/Value-Speicher mit GUIDs als Schlüssel. Wird in ganz Media Foundation intensiv genutzt |
| Apartment | Die Einheit, mit der COM Threads gruppiert. Legt fest, „von welchem Thread aus dieses Objekt aufgerufen werden darf“, bestimmt über das Argument von CoInitializeEx (3.1, 3.5) |
| STA / MTA | Arten von Apartments. STA (Single-Threaded Apartment) ist an genau einen Thread gebunden; Aufrufe von anderen Threads werden über die Message Pump herangeführt. MTA (Multi-Threaded Apartment) teilen sich mehrere Threads dasselbe Apartment und können direkt aufrufen. Mehr dazu in „Grundwissen zu COM STA/MTA, um Hänger zu vermeiden“ |
| Work Queue | Der Thread-Mechanismus, mit dem Media Foundation asynchrone Verarbeitung ausführt. Callbacks werden aus diesem Thread heraus aufgerufen (3.5) |
Hat man diese Begriffe vorab parat, stolpert man beim Lesen der Dokumentation deutlich seltener.
Apartment wird erst in 3.5 zum eigentlichen Thema, hier genügt es zu wissen: „Ein Callback von Media Foundation kann von einem anderen Thread kommen als dem, von dem aus man ReadSample aufgerufen hat (der Work-Queue-Thread im MTA).“
2.2. Das Gesamtbild von Media Foundation (Diagramm)
Media Foundation ist im Großen und Ganzen eine Frage der Medienpipeline. COM ist wichtig, aber es ist einfacher, sich zuerst das Gesamtbild anzusehen.
flowchart TB
subgraph Pipeline["Modell, das die gesamte Pipeline nutzt"]
Source1["Media Source"] --> Transform1["MFT"]
Transform1 --> Sink1["Media Sink"]
Session["Media Session"] --- Source1
Session --- Transform1
Session --- Sink1
end
subgraph Direct["Modell, bei dem die Anwendung Daten direkt verarbeitet"]
Source2["Media Source"] --> Reader["Source Reader (+ Decoder)"]
Reader --> App["Anwendung"]
App --> Writer["Sink Writer (+ Encoder)"]
Writer --> Sink2["Media Sink"]
end
Media Foundation lässt sich grob in zwei Nutzungsweisen einteilen:
- Modell, das die gesamte Pipeline nutzt
- Source, Transform und Sink werden verbunden, und die Media Session verwaltet Datenfluss und A/V-Synchronisation
- Modell, bei dem die Anwendung Daten direkt verarbeitet
- Der Source Reader holt Daten aus der Source, der Sink Writer schreibt sie in die Sink
Für Fälle, in denen man Frames oder Samples selbst verarbeiten möchte, ist das zweite Modell der einfachere Einstieg. Will man dagegen Wiedergabe und Synchronisation vollständig der Plattform überlassen, ist das erste Modell der eigentliche Kern.
Wichtig festzuhalten ist: Media Foundation ist im Kern eine Medienverarbeitungsplattform, und das unterscheidet sich etwas vom Gefühl, direkt eine Ansammlung von COM-Objekten anzufassen.
Sobald man jedoch die Grenzen zwischen den Bausteinen genauer betrachtet, tritt das COM-Gesicht plötzlich deutlich hervor. Im nächsten Kapitel gehen wir diese Stellen der Reihe nach durch.
3. Wo Media Foundation ihr COM-Gesicht zeigt
Die Stellen, an denen die COM-Färbung stärker wird, lassen sich grob in diese fünf Punkte einordnen.
| Stelle | Was auftaucht | Was man zuerst verstehen sollte |
|---|---|---|
| 3.1. Initialisierung | CoInitializeEx, MFStartup |
COM-Initialisierung und Media-Foundation-Initialisierung sind getrennt |
| 3.2. Objekterzeugung und -übergabe | IMFSourceReader, IMFMediaType, IMFTransform |
Meist Interface-Zeiger + HRESULT |
| 3.3. Konfiguration | IMFAttributes, GUID |
Einstellungswerte und Typinformationen werden als Key/Value + GUID dargestellt |
| 3.4. Enumeration und verzögerte Erzeugung | IMFActivate, ActivateObject |
Das Enumerationsergebnis ist nicht immer schon das eigentliche Objekt |
| 3.5. Asynchronität | IMFSourceReaderCallback, Work Queue |
Callback und Apartment müssen bewusst beachtet werden |
| (Kapitel 4) Wiedergabesteuerung | Topology, Media Session | Der Ablauf der gesamten Pipeline ist ein Media-Foundation-eigenes Konzept |
Nur die Wiedergabesteuerung am Ende hat einen anderen Charakter: Sie ist keine allgemeine COM-Angelegenheit, sondern eine Funktion von Media Foundation selbst. Deshalb wird sie in Kapitel 4 gesondert behandelt.
Im Folgenden gehen wir die Punkte der Reihe nach durch. Der Code ist kein vollständiges Beispiel, sondern nur ein Auszug, der zeigt, an welcher Stelle sich das COM-Gesicht zeigt.
3.1. Bei der Initialisierung stehen CoInitializeEx und MFStartup nebeneinander
Hier stutzen die meisten Menschen zuerst. Noch bevor man eine Datei öffnen oder von einer Kamera lesen kann, tauchen CoInitializeEx und MFStartup auf.
CoInitializeExinitialisiert die COM-BibliothekMFStartupinitialisiert die Media-Foundation-Plattform
Das heißt: Die COM-Initialisierung allein genügt nicht, auch die Media-Foundation-seitige Initialisierung ist nötig. Hier wird deutlich: „Das ist keine reine Video-API, sondern darunter steckt ein ziemlich COM-basierter Vertrag.“
template <class T>
void SafeRelease(T** pp)
{
if (pp != nullptr && *pp != nullptr)
{
(*pp)->Release();
*pp = nullptr;
}
}
HRESULT InitializeMediaFoundationForCurrentThread()
{
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr))
{
return hr;
}
hr = MFStartup(MF_VERSION);
if (FAILED(hr))
{
CoUninitialize();
return hr;
}
return S_OK;
}
void UninitializeMediaFoundationForCurrentThread()
{
MFShutdown();
CoUninitialize();
}
Dass CoInitializeEx und MFStartup hier nebeneinanderstehen, ist die erste Stelle, an der man beim Umgang mit Media Foundation plötzlich die COM-Atmosphäre spürt.
In der Praxis lohnt es sich, an dieser Stelle schon Folgendes festzulegen:
- Welcher Thread Media Foundation verwendet
- Ob dieser Thread STA oder MTA sein soll
- Wer die Verantwortung für
MFStartup/MFShutdownsowieCoInitializeEx/CoUninitializeträgt
In manchen Implementierungen übernimmt bereits eine andere Schicht die COM-Initialisierung. Auch dann ist es sicherer, vorab festzulegen, wer die Verantwortung trägt. Bleibt dieses Design vage, wird es später bei Callbacks und der UI-Anbindung schnell unübersichtlich.
Der Code in diesem Artikel verwaltet rohe Interface-Zeiger mit einem selbst geschriebenen SafeRelease. Die Beispiele in Microsofts Dokumentation sind so geschrieben, und dadurch sieht man, wo genau AddRef / Release greifen. Das ist jedoch kein Grund, in produktivem Code auf Smart Pointer zu verzichten. Wer in C++ neu schreibt, sollte sich für eine der beiden folgenden Optionen entscheiden:
| Option | Herkunft | Anmerkung |
|---|---|---|
Microsoft::WRL::ComPtr<T> |
<wrl/client.h> |
Im Windows SDK enthalten, keine zusätzliche Abhängigkeit nötig. Get() liefert den rohen Zeiger, GetAddressOf() / & für Out-Parameter, As<U>() für QueryInterface |
wil::com_ptr<T> |
wil/com.h der WIL (Windows Implementation Libraries) |
Separat z. B. über NuGet einzubinden. Lässt sich mit Hilfsfunktionen kombinieren, die HRESULT in Ausnahmen umwandeln |
Schreibt man mit ComPtr, entfällt die Kombination aus goto done; und SafeRelease, wie sie in der zweiten Hälfte von 3.1 auftaucht – Release erfolgt automatisch beim Verlassen des Gültigkeitsbereichs. Dieser Artikel belässt es bei rohen Zeigern, damit die COM-Konventionen sichtbar bleiben, aber für neuen Code empfiehlt sich ComPtr von Anfang an.
3.2. Die Übergabe von Objekten ist schnittstellenzentriert
Liest man sich durch die Media-Foundation-API, sind die meisten Rückgabewerte und Out-Parameter COM-Schnittstellen.
IMFSourceReaderIMFMediaTypeIMFTransformIMFActivateIMFSampleIMFMediaBuffer
Bemerkenswert ist, dass nicht nur die eigentlichen Daten, sondern auch Typinformationen und Konfigurationsobjekte als Schnittstellen dargestellt werden.
Zum Beispiel:
IMFTransformist die Schnittstelle, die einen MFT repräsentiertIMFAttributesist ein Key/Value-SpeicherIMFMediaTypeist eine „Beschreibung des Medienformats“, die vonIMFAttributeserbt
Selbst so etwas wie einen media type, der eher wie „Konfigurationsdaten“ wirkt, hält man über eine COM-Schnittstelle. Hier kommen ganz natürlich IUnknown, QueryInterface, AddRef / Release und HRESULT ins Spiel.
flowchart TD
IUnknown["IUnknown"]
IUnknown --> IMFAttributes["IMFAttributes"]
IMFAttributes --> IMFMediaType["IMFMediaType"]
IMFAttributes --> IMFActivate["IMFActivate"]
IUnknown --> IMFSourceReader["IMFSourceReader"]
IUnknown --> IMFTransform["IMFTransform"]
An dieser Stelle wird deutlich: „Media Foundation ist zwar eine Medien-API, aber die Art, wie ihre Grenzen dargestellt werden, ist ziemlich COM-lastig.“
3.3. Einstellungen und Typinformationen drehen sich um IMFAttributes und GUIDs
Beim Arbeiten mit Media Foundation gibt es eine Stelle, an der Konfigurationen plötzlich nur noch aus GUIDs zu bestehen scheinen. Im Zentrum steht IMFAttributes, ein Key/Value-Speicher mit GUIDs als Schlüssel. Er wird in ganz Media Foundation sehr häufig verwendet.
Besonders wichtig ist IMFMediaType, das von IMFAttributes erbt und Informationen zum Medienformat als Attribute hält.
Zum Beispiel Informationen wie:
- Major Type (Audio oder Video)
- Subtype (H.264, AAC, RGB32, PCM und Ähnliches)
- Framegröße
- Framerate
- Sample-Rate
- Kanalanzahl
flowchart LR
MediaType["IMFMediaType"] --> Major["MF_MT_MAJOR_TYPE"]
MediaType --> Subtype["MF_MT_SUBTYPE"]
MediaType --> Detail["Größe / FPS / Sample-Rate usw."]
Man empfindet das leicht als „Wald aus GUIDs“, aber tatsächlich ist das Prinzip ziemlich geradlinig.
- Einstellungen werden über einen Attribute-Speicher gehalten
- Auch der media type wird als Attribute-Speicher dargestellt
- Zwischen Source, Transform und Sink wird das Format über diese Attribute abgeglichen
Es geht schlicht darum, dass für die Darstellung von Einstellungen und Typinformationen COM-artige Schnittstellen und GUIDs verwendet werden.
Im Code sieht das bis hierhin so aus. Ein Beispiel, das mit dem Source Reader nur einen einzigen Frame aus einem Video liest.
HRESULT ReadOneVideoSample(PCWSTR path)
{
IMFSourceReader* pReader = nullptr;
IMFMediaType* pType = nullptr;
IMFSample* pSample = nullptr;
HRESULT hr = MFCreateSourceReaderFromURL(path, nullptr, &pReader);
if (FAILED(hr)) goto done;
hr = MFCreateMediaType(&pType);
if (FAILED(hr)) goto done;
hr = pType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Video);
if (FAILED(hr)) goto done;
hr = pType->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_RGB32);
if (FAILED(hr)) goto done;
hr = pReader->SetCurrentMediaType(
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
nullptr,
pType);
if (FAILED(hr)) goto done;
DWORD streamFlags = 0;
LONGLONG timestamp = 0;
hr = pReader->ReadSample(
MF_SOURCE_READER_FIRST_VIDEO_STREAM,
0,
nullptr,
&streamFlags,
×tamp,
&pSample);
if (FAILED(hr)) goto done;
// Aus pSample den IMFMediaBuffer herausholen und weiterverarbeiten
done:
SafeRelease(&pSample);
SafeRelease(&pType);
SafeRelease(&pReader);
return hr;
}
Hier wird Folgendes sichtbar:
- Sowohl reader als auch media type sind COM-Schnittstellen
- Einstellungen basieren auf GUIDs
- Der Rückgabewert ist ein
HRESULT - Im synchronen Modus blockiert
ReadSample
Selbst wenn man „nur einen Frame lesen“ möchte, zeigt sich an den Grenzen von Media Foundation ein ziemlich COM-artiges Gesicht. Zum synchronen Modus am Ende mehr in 3.5.
Der Ablauf der media type negotiation (dieser Punkt wird in der Checkliste in Kapitel 6 unter „diese drei zuerst prüfen“ genannt)
Der obige Code erklärt lediglich „ich hätte gern RGB32“, in der eigentlichen Praxis sind davor und danach noch Schritte nötig. Für den Source Reader zeigt Microsofts Dokumentation folgenden vierstufigen Ablauf:
- Native Typen aufzählen –
IMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType)wird aufgerufen, wobeitypeIndexbeginnend bei 0 hochgezählt wird. Überschreitet man den Bereich, wirdMF_E_NO_MORE_TYPESzurückgegeben – das markiert das Ende der Aufzählung (liegtstreamIndexaußerhalb des gültigen Bereichs, kommtMF_E_INVALIDSTREAMNUMBER). Bei Dateien gibt es pro Stream oft nur eine Ausprägung, eine Webcam kann dagegen mehrere Formate anbieten - Den major type prüfen – Aus dem beim Aufzählen erhaltenen media type liest man
MF_MT_MAJOR_TYPEaus und stellt fest, ob es sich um Audio oder Video handelt. Überspringt man diesen Schritt und geht fest von einem Typ aus, landet die Videokonfiguration womöglich auf dem Audiostream - Das gewünschte Ausgabeformat zusammensetzen und festlegen – Mit
MFCreateMediaTypewird ein neuer media type erzeugt,MF_MT_MAJOR_TYPEundMF_MT_SUBTYPEwerden gesetzt, undSetCurrentMediaTypewird aufgerufen. Möchte man die Daten weiterhin komprimiert erhalten, übergibt man den in Schritt 1 erhaltenen Typ unverändert; möchte man dekodierte Daten, gibt man ein unkomprimiertes Format an (MFVideoFormat_RGB32,MFAudioFormat_PCMund Ähnliches). Den Decoder lädt der Source Reader automatisch - Das festgelegte Format erneut auslesen – Nach
SetCurrentMediaTyperuft manGetCurrentMediaTypeauf und holt sich die Details des tatsächlich festgelegten Formats (Framegröße, Stride, Sample-Rate und Ähnliches). Was man in Schritt 3 übergibt, ist nur eine Teilangabe – den endgültigen Wert liest man deshalb an dieser Stelle aus, das ist die korrekte Reihenfolge
Überspringt man diese vier Schritte und geht nach dem Motto „das wird wohl dieses Format sein“ vor, kommt entweder MF_E_INVALIDMEDIATYPE zurück, oder man liest – selbst wenn es durchläuft – am Ende einen Puffer in einem anderen als dem erwarteten Format.
3.4. Activation Objects tauchen auf
Besonders deutlich zeigt sich die COM-Nähe von Media Foundation bei den Activation Objects.
IMFActivate ist ein Hilfsobjekt, um das eigentliche Objekt erst später zu erzeugen. Man kann es sich am ehesten wie eine COM-Class-Factory vorstellen.
An den Stellen, an denen es auftaucht, liefert eine Enumerations-API nicht direkt „das fertig nutzbare Objekt“, sondern zunächst ein Array von IMFActivate*.
Nur die tatsächlich benötigten Objekte werden dann per ActivateObject konkret erzeugt.
sequenceDiagram
participant App as Anwendung
participant Enum as Enumerations-API
participant Act as IMFActivate
participant Obj as IMFTransform / Sink usw.
App->>Enum: Enumeration aufrufen
Enum-->>App: Array von IMFActivate*
App->>Act: Attribute prüfen
App->>Act: ActivateObject(...)
Act-->>App: Konkretes COM-Objekt
Dieses Muster passt gut zu dem Umstand, dass Media Foundation so entworfen ist, dass austauschbare Bausteine erst später gesucht und kombiniert werden.
Da das Activation Object selbst Attribute besitzen kann, entsteht leicht ein Ablauf von „erst die Attribute des Kandidaten ansehen“, „bei Bedarf konfigurieren“ und „später erst konkret erzeugen“. Auch das ist ziemlich COM-artig.
Enumeriert man tatsächlich mit MFTEnumEx MFTs und erzeugt sie konkret, sieht das so aus:
HRESULT FindH264Decoder(IMFTransform** ppTransform)
{
*ppTransform = nullptr;
IMFActivate** ppActivate = nullptr;
UINT32 count = 0;
MFT_REGISTER_TYPE_INFO inputType = {};
inputType.guidMajorType = MFMediaType_Video;
inputType.guidSubtype = MFVideoFormat_H264;
HRESULT hr = MFTEnumEx(
MFT_CATEGORY_VIDEO_DECODER,
MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_LOCALMFT,
&inputType,
nullptr,
&ppActivate,
&count);
if (FAILED(hr))
{
return hr;
}
if (count == 0)
{
CoTaskMemFree(ppActivate);
return MF_E_TOPO_CODEC_NOT_FOUND;
}
hr = ppActivate[0]->ActivateObject(
__uuidof(IMFTransform),
reinterpret_cast<void**>(ppTransform));
for (UINT32 i = 0; i < count; ++i)
{
ppActivate[i]->Release();
}
CoTaskMemFree(ppActivate);
return hr;
}
Das Enumerationsergebnis ist von Anfang an kein IMFTransform*, sondern kommt als IMFActivate** zurück, und erst der Aufruf von ActivateObject liefert das konkrete IMFTransform. Dieser Ablauf zeigt sehr gut das Gefühl, dass Media Foundation „plötzlich sein COM-Gesicht zeigt“.
3.5. Auch der Umgang mit Asynchronität, Callbacks und Threads ist COM-artig
In der Praxis wird bei Media Foundation leicht übersehen: die asynchrone Verarbeitung und das Thread-Modell.
Der Source Reader arbeitet zum Beispiel standardmäßig im synchronen Modus. Im synchronen Modus blockiert ReadSample.
Je nach Zustand von Datei, Netzwerk oder Gerät kann diese Wartezeit spürbar werden.
Um den asynchronen Modus zu nutzen, übergibt man beim Erzeugen des Source Readers einen Callback.
Man erstellt ein Objekt, das IMFSourceReaderCallback implementiert, setzt es im MF_SOURCE_READER_ASYNC_CALLBACK-Attribut und erzeugt den Reader dann.
HRESULT CreateSourceReaderAsync(
PCWSTR path,
IMFSourceReaderCallback* pCallback,
IMFSourceReader** ppReader)
{
IMFAttributes* pAttributes = nullptr;
HRESULT hr = MFCreateAttributes(&pAttributes, 1);
if (FAILED(hr))
{
return hr;
}
hr = pAttributes->SetUnknown(MF_SOURCE_READER_ASYNC_CALLBACK, pCallback);
if (SUCCEEDED(hr))
{
hr = MFCreateSourceReaderFromURL(path, pAttributes, ppReader);
}
SafeRelease(&pAttributes);
return hr;
}
Das heißt:
- Der Callback selbst ist eine COM-Schnittstelle
- Die asynchrone Konfiguration erfolgt über
IMFAttributes - Der Modus wird bei der Erzeugung festgelegt
Etwas wichtiger noch ist das Apartment. Die asynchrone Verarbeitung von Media Foundation läuft über eine Work Queue, und der Thread der Work Queue ist MTA. Deshalb wird die Implementierung einfacher, wenn auch die Anwendungsseite im MTA arbeitet.
sequenceDiagram
participant App as Anwendungs-Thread
participant Reader as Source Reader
participant Queue as MF Work Queue (MTA)
participant Cb as IMFSourceReaderCallback
App->>Reader: ReadSample(...)
Reader-->>App: Kehrt sofort zurück
Reader->>Queue: Verarbeitung intern
Queue->>Cb: OnReadSample(...)
Rund um Callbacks sollte man auf Folgendes achten:
- STA-Objekte des UI-Threads nicht direkt aus dem Callback heraus anfassen
- Die Callback-Implementierung threadsicher gestalten
- Bei nötigen UI-Aktualisierungen nur die Ergebnisse an den UI-Thread zurückreichen
- Von Anfang an festlegen, „von welchem Thread ein Media-Foundation-Callback kommt“
Media Foundation nimmt einem die Sorge um STA-Objekte nicht automatisch ab. Deshalb ist es klarer, Worker, die Media Foundation nutzen, auf MTA auszurichten und die Brücke zur UI explizit zu bauen.
4. Media Foundation ist jedoch nicht gleich COM
Liest man bis hierhin, entsteht leicht der Eindruck: „Im Grunde ist Media Foundation doch einfach COM.“ Das stimmt aber nicht ganz.
Media Foundation hat plattform-eigene Konzepte, die sich nicht allein mit allgemeinem COM-Wissen erklären lassen.
MFStartup/MFShutdown- Media Session
- Topology
- Topology Loader
- Presentation Clock
- Source Reader / Sink Writer
Das alles gehört zu der Frage, wie die Medienpipeline fließt – einer Aufgabe von Media Foundation selbst.
In der Media Session gibt es zum Beispiel den Ablauf, dass die Anwendung eine Partial Topology übergibt und der Topology Loader die fehlenden Transforms ergänzt, um daraus eine Full Topology aufzulösen. Das ist keine allgemeine COM-Angelegenheit, sondern eine Funktion, die Media Foundation als Medienverarbeitungsplattform besitzt.
flowchart LR
Partial["Partial Topology<br/>Source -> Output"] --> Loader["Topology Loader"]
Loader --> Full["Full Topology<br/>Source -> Decoder MFT -> Output"]
Media Foundation nutzt COM, um die Verträge seiner Bausteine darzustellen, und funktioniert darüber hinaus als Medienverarbeitungsplattform. Betrachtet man es in dieser zweistufigen Struktur, verliert man weniger leicht den Überblick.
5. Womit man einsteigt (die Wahl des Einstiegspunkts)
Für die Wahl des ersten Einstiegspunkts reicht oft schon dieses Diagramm.
flowchart TD
Start["Was Sie erreichen wollen"] --> Q1{"Was brauchen Sie zuerst?"}
Q1 -- "Frames / Samples lesen" --> A1["Source Reader"]
Q1 -- "In eine Datei schreiben" --> A2["Sink Writer"]
Q1 -- "Wiedergabesteuerung oder A/V-Synchronisation" --> A3["Media Session"]
Q1 -- "Eigenen Converter einfügen" --> A4["MFT"]
Als Tabelle sieht das so aus:
| Ziel | Womit man zuerst arbeitet | COM-Intensität | Anmerkung |
|---|---|---|---|
| Frames / Samples aus Datei oder Kamera holen | Source Reader | mittel | Kümmert sich bei Bedarf auch um den Decoder |
| Erzeugte Audio-/Videodaten in eine Datei schreiben | Sink Writer | mittel | Kann bei Bedarf Encoder und Media Sink gebündelt handhaben |
| Wiedergabe, Stopp, Suche, A/V-Synchronisation und Qualitätssteuerung | Media Session | hoch | Verständnis von Topology und Session nötig |
| Eigenen Converter oder codec-artigen Baustein einfügen | MFT | hoch | Dreht sich zentral um IMFTransform |
| Erst enumerierte Kandidaten ansehen, dann nur die benötigten konkretisieren | IMFActivate |
hoch | Was zurückkommt, ist manchmal nicht das Objekt selbst, sondern ein Activation Object |
5.1. Der Fall, dass man zunächst mit dem Source Reader einsteigt
Der Source Reader ist als Einstiegspunkt, um Daten aus Datei oder Gerät zu holen, ziemlich einfach zu handhaben.
Geeignet ist er zum Beispiel für Fälle wie:
- Frames aus einer Videodatei holen
- Eine Audiodatei dekodieren und Samples holen
- Frames von einer Kamera holen
- Eine Media-Foundation-Source an eine eigene Verarbeitungspipeline anschließen
Der Source Reader lädt bei Bedarf einen Decoder und reicht Daten an die Anwendung weiter. Um die Verwaltung der Presentation Clock, die A/V-Synchronisation oder das eigentliche Rendering kümmert er sich dagegen nicht.
Am einfachsten versteht man ihn als Einstiegspunkt zum „Datenholen“, nicht zum „Abspielen“.
5.2. Für das Schreiben in eine Datei: der Sink Writer
Der Sink Writer ist der Einstiegspunkt, um Audio oder Video in eine Datei zu schreiben.
Typische Einsatzfälle sind etwa:
- Erzeugte Frames in einer Videodatei speichern
- Audio-Samples kodieren und schreiben
- Ausgelesene Daten in ein anderes Format konvertieren und speichern
Der Sink Writer sucht und lädt bei Bedarf einen Encoder und verwaltet den Datenfluss zur Media Sink. Er wird oft mit dem Source Reader kombiniert, aber beide sind unabhängige Bausteine – man muss sie nicht zwingend gemeinsam einsetzen.
5.3. Für Wiedergabe und Synchronisation: die Media Session
Geht es nicht nur ums „Daten holen“, sondern soll die Wiedergabe richtig ablaufen, ist es einfacher, sich an der Media Session zu orientieren.
Die Media Session kommt bei Anforderungen wie diesen ins Spiel:
- Wiedergabe / Stopp / Suche sollen gehandhabt werden
- Die Synchronisation von Audio und Video soll der Plattform überlassen werden
- Qualitätssteuerung und Formatwechsel sollen als Teil der Pipeline behandelt werden
- Der Ablauf von Source, Transform und Sink soll über eine Topology aufgebaut werden
Steigt man auf diese Ebene, nähert man sich stärker dem „eigentlichen Media Foundation“ als bei Source Reader / Sink Writer. Entsprechend nehmen auch Media-Foundation-eigene Konzepte wie Topology und Session-Ereignisse zu.
5.4. Für eigene Bausteine: MFT
MFT ist das gemeinsame Modell für Transforms in Media Foundation.
Hierhin geht man in Fällen wie diesen:
- Man möchte einen eigenen Decoder oder Encoder bauen
- Man möchte einen Baustein zur Bild- oder Audioverarbeitung in die Pipeline einfügen
- Man möchte Codecs oder Converter enumerieren und selbst auswählen
- Man möchte tiefer eingreifen, als die automatische Standardauflösung erlaubt
In der Welt der MFTs treten COM-artige Verträge wie IMFTransform, IMFActivate, media type negotiation sowie Sample-/Puffer-Verwaltung deutlich in den Vordergrund.
Deshalb ist es klarer, nicht gleich als ersten Einstiegspunkt direkt zum MFT zu gehen, sondern zuerst zu prüfen, ob wirklich Source Reader, Sink Writer oder Media Session benötigt wird.
6. Checkliste für die Praxis
Zum Schluss fassen wir zusammen, worauf man in der Praxis zuerst achten sollte.
| Punkt | Worauf zu achten ist | Was bei Übersehen leicht passiert |
|---|---|---|
| Initialisierungsverantwortung | Festlegen, wo CoInitializeEx und MFStartup aufgerufen werden und wo die Beendigung liegt |
Fehlende Initialisierung, verwirrte Beendigungsreihenfolge |
| Apartment | Vorab festlegen, ob der Media-Foundation-Thread STA oder MTA ist | Verwirrung rund um Callbacks, Konflikte mit der UI |
| Modus des Source Readers | Synchron oder asynchron bei der Erzeugung festlegen | ReadSample blockiert unerwartet, lässt sich später nicht mehr umschalten |
| media type negotiation | Ausgabeformate enumerieren und das tatsächlich genutzte Format explizit festlegen. Ablauf gemäß den vier Schritten aus 3.3 (Enumeration mit GetNativeMediaType → major type prüfen → SetCurrentMediaType → mit GetCurrentMediaType den festgelegten Wert auslesen) |
MF_E_INVALIDMEDIATYPE, ein anderes als das erwartete Format kommt an |
| Objektlebensdauer | Verantwortung für Release, Unlock, ShutdownObject klar festlegen |
Speicherlecks, festgehaltene Puffer, Inkonsistenzen beim Beenden |
| Activation Object | Unterscheiden, ob das Enumerationsergebnis das eigentliche Objekt oder ein IMFActivate ist |
Man erwartet fälschlich, QueryInterface aufrufen zu können, und scheitert |
| Topology | Erkennen, ob man mit einer Partial oder einer Full Topology arbeitet | Man geht von „verbindet sich schon automatisch“ aus und bleibt stecken |
| Fehlerprüfung | Jedes Mal HRESULT, Stream-Flags und Events prüfen |
Ein teilweiser Fehlschlag wird übersehen |
| UI-Anbindung | Die UI nicht direkt aus dem Callback heraus anfassen, nur Ergebnisse an den UI-Thread zurückreichen | Hänger, Race Conditions, schwer nachvollziehbare Fehler |
Besonders hohe Priorität haben diese drei Punkte:
- Die erste Einstiegs-API nicht falsch wählen
- Zuerst klären, ob wirklich Source Reader, Sink Writer oder Media Session benötigt wird
- Das Apartment vorab festlegen
- Mischt man die STA-UI mit der Work Queue von Media Foundation, muss man vorab festlegen, wie die Brücke geschlagen wird
- Die media type negotiation nicht auf die leichte Schulter nehmen
- Geht man nach dem Motto „das wird wohl dieses Format sein“ vor, wird es später ziemlich unübersichtlich
- Der konkrete Ablauf ist unter „Der Ablauf der media type negotiation“ in 3.3 zusammengefasst
7. Zusammenfassung
Dass beim Arbeiten mit Media Foundation plötzlich viel von COM die Rede ist, ist kein Zufall.
- Media Foundation ist eine Medienverarbeitungsplattform
- Die Grenzen zwischen Source, Transform, Sink, Activation, Callback und Ähnlichem werden durch COM-Schnittstellen dargestellt
- Deshalb kommen ganz natürlich
IUnknown,HRESULT, GUID, Apartment und Callback zur Sprache - Der Kern von Media Foundation ist jedoch eine Medienpipeline mit Media Session und Topology – nicht bloß eine Neuauflage von COM
In der Praxis ist es deutlich übersichtlicher, wenn man in dieser Reihenfolge vorgeht:
- Zunächst klären, ob überhaupt Source Reader, Sink Writer, Media Session oder MFT benötigt wird
- Die Vorgehensweise für Apartment und Callback vorab festlegen
- Media type negotiation und Objektlebensdauer sorgfältig behandeln
Man muss nicht von Anfang an alles verstehen. Behält man zunächst nur im Kopf, dass „Media Foundation eine Medienverarbeitungsplattform ist, in deren Grenzflächen COM tief eingebettet ist“, lassen sich Dokumentation und Code deutlich leichter verfolgen.
8. Quellen
- Media Foundation and COM - Microsoft Learn
- Overview of the Media Foundation Architecture - Microsoft Learn
- Initializing Media Foundation - Microsoft Learn
- Source Reader - Microsoft Learn
- Using the Source Reader to Process Media Data - Microsoft Learn
- Using the Source Reader in Asynchronous Mode - Microsoft Learn
- Sink Writer - Microsoft Learn
- Activation Objects - Microsoft Learn
- About Topologies - Microsoft Learn
- IMFAttributes interface - Microsoft Learn
- IMFMediaType interface - Microsoft Learn
- IMFTransform interface - Microsoft Learn
- MFTEnumEx function - Microsoft Learn
- IMFSourceReader::GetNativeMediaType - Microsoft Learn
- ComPtr Class (Microsoft::WRL) - Microsoft Learn
- Grundwissen zu COM STA/MTA, um Hänger zu vermeiden | KomuraSoft Blog
- Native DLLs aus C# aufrufen: C++/CLI-Wrapper vs. P/Invoke | KomuraSoft Blog
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
MP4-Frames mit Media Foundation Bilder und Text einbrennen
Wie man mit Media Foundation Bild und Text in jeden Frame eines MP4-Videos einbrennt und daraus ein neues MP4 erzeugt – die Aufgabenteilu...
YUV mit Media Foundation in RGB konvertieren
Wie Sie YUV-Frames in Media Foundation nach RGB konvertieren: automatische Konvertierung über den Source Reader sowie manuelle NV12-/YUY2...
Ein Standbild aus einer MP4 zu einem bestimmten Zeitpunkt mit Media Foundation extrahieren
Wie man mit dem Source Reader die einem bestimmten Zeitpunkt nächstgelegene Frame aus einer MP4 holt, Stride und den RGB32-Alphakanal kor...
Abwärtskompatibilität von DLL- und COM-Schnittstellen — Eine Entscheidungstabelle dafür, welche Änderungen Aufrufer brechen
Welche Änderungen an einer DLL oder COM-Komponente brechen tatsächlich deren Aufrufer? Wir legen die drei Ebenen der Kompatibilität dar —...
Laufen Business-Anwendungen unter Windows on Arm? ── Die Realität von x64-Emulation (Prism) und nativen DLLs/COM
Eine Antwort für Entwicklerinnen, Entwickler und IT-Verantwortliche auf die Frage „Läuft unsere Business-Anwendung unter Windows on Arm?“...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
ActiveX-Migration
Entscheidungen zum Beibehalten, Kapseln oder Ersetzen von COM / ActiveX / OCX.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Windows-Medienverarbeitung mit Media Foundation, COM und HRESULT liegt nahe an den Implementierungsthemen, die wir als Windows-Anwendungsentwicklung behandeln.
Technische Beratung und Design-Review
Wenn Sie COM-artige Grenzen und die Initialisierungsreihenfolge zuerst klären wollen, steigen wir über technische Beratung und Design-Review von der Entwurfsseite ein.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Was ist Media Foundation eigentlich? Ist das nicht dasselbe wie COM?
- Media Foundation ist eine Medienverarbeitungsplattform für Video und Audio unter Windows; die gesamte API ist nicht einfach reines COM. Allerdings werden die Grenzen zwischen Bausteinen wie Source, Transform, Sink, Activation, Attributes und Callback durch COM-Schnittstellen dargestellt, weshalb beim Arbeiten damit ganz natürlich IUnknown, HRESULT, GUID und Apartment ins Spiel kommen. Treffender ist es, es als „Medienverarbeitungsplattform, in deren Grenzflächen COM tief eingebettet ist“ zu verstehen.
- Warum braucht man sowohl MFStartup als auch CoInitializeEx?
- Weil beide unterschiedliche Aufgaben haben. CoInitializeEx initialisiert die COM-Bibliothek, MFStartup initialisiert die Media-Foundation-Plattform. Die COM-Initialisierung allein genügt nicht, auch die Media-Foundation-seitige Initialisierung ist nötig. In der Praxis lohnt es sich, vorab festzulegen, welcher Thread Media Foundation verwendet, ob dieser STA oder MTA ist und wer die Verantwortung für MFStartup/MFShutdown sowie CoInitializeEx/CoUninitialize trägt – das erleichtert später den Umgang mit Callbacks und der UI-Anbindung erheblich.
- Wie unterscheidet man Source Reader, Sink Writer, Media Session und MFT in der Anwendung?
- Wollen Sie Frames oder Samples aus einer Datei oder Kamera herausholen, ist der Source Reader der Einstiegspunkt; wollen Sie erzeugte Audio- oder Videodaten in eine Datei schreiben, ist es der Sink Writer. Sollen Wiedergabe, Stopp, Suche, A/V-Synchronisation und Qualitätssteuerung von der Plattform übernommen werden, denkt man vor allem in der Media Session. Wollen Sie einen eigenen Decoder oder Converter in die Pipeline einfügen, geht es in Richtung MFT – da hier der COM-artige Vertrag stark in den Vordergrund tritt, lohnt es sich, zuerst zu prüfen, ob wirklich einer der drei vorherigen Bausteine benötigt wird.
- Worauf muss man bei asynchronen Callbacks in Media Foundation achten?
- Die asynchrone Verarbeitung von Media Foundation nutzt eine Work Queue, deren Thread im MTA läuft; richtet man die Anwendungsseite ebenfalls auf MTA aus, wird die Implementierung einfacher. Die Implementierung von IMFSourceReaderCallback muss threadsicher sein, und STA-Objekte des UI-Threads dürfen im Callback nicht direkt angefasst werden – nur die Ergebnisse werden bei Bedarf an den UI-Thread zurückgereicht. Zu beachten ist außerdem, dass der synchrone bzw. asynchrone Modus des Source Readers bei der Erstellung festgelegt wird und sich später nicht mehr umschalten lässt.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.