Media Foundation verstehen – Warum sich die API wie COM anfühlt

· · 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

  1. Zuerst das Fazit (in Kürze)
  2. Begriffe und Gesamtbild
    • 2.1. Zunächst die Bedeutung der wichtigsten Begriffe
    • 2.2. Das Gesamtbild von Media Foundation (Diagramm)
  3. Wo Media Foundation ihr COM-Gesicht zeigt
    • 3.1. Bei der Initialisierung stehen CoInitializeEx und MFStartup nebeneinander
    • 3.2. Die Übergabe von Objekten ist schnittstellenzentriert
    • 3.3. Einstellungen und Typinformationen drehen sich um IMFAttributes und GUIDs
    • 3.4. Activation Objects tauchen auf
    • 3.5. Auch der Umgang mit Asynchronität, Callbacks und Threads ist COM-artig
  4. Media Foundation ist jedoch nicht gleich COM
  5. 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
  6. Checkliste für die Praxis
  7. Zusammenfassung
  8. 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.

Modell, bei dem die Anwendung Daten direkt verarbeitetSource Reader (+ Decoder)Media SourceAnwendungSink Writer (+ Encoder)Media SinkModell, das die gesamte Pipeline nutztMFTMedia SourceMedia SinkMedia Session

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.

  • CoInitializeEx initialisiert die COM-Bibliothek
  • MFStartup initialisiert 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 / MFShutdown sowie CoInitializeEx / CoUninitialize trä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.

  • IMFSourceReader
  • IMFMediaType
  • IMFTransform
  • IMFActivate
  • IMFSample
  • IMFMediaBuffer

Bemerkenswert ist, dass nicht nur die eigentlichen Daten, sondern auch Typinformationen und Konfigurationsobjekte als Schnittstellen dargestellt werden.

Zum Beispiel:

  • IMFTransform ist die Schnittstelle, die einen MFT repräsentiert
  • IMFAttributes ist ein Key/Value-Speicher
  • IMFMediaType ist eine „Beschreibung des Medienformats“, die von IMFAttributes erbt

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.

IUnknownIMFAttributesIMFMediaTypeIMFActivateIMFSourceReaderIMFTransform

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
IMFMediaTypeMF_MT_MAJOR_TYPEMF_MT_SUBTYPEGröß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,
        &timestamp,
        &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:

  1. Native Typen aufzählenIMFSourceReader::GetNativeMediaType(streamIndex, typeIndex, &pType) wird aufgerufen, wobei typeIndex beginnend bei 0 hochgezählt wird. Überschreitet man den Bereich, wird MF_E_NO_MORE_TYPES zurückgegeben – das markiert das Ende der Aufzählung (liegt streamIndex außerhalb des gültigen Bereichs, kommt MF_E_INVALIDSTREAMNUMBER). Bei Dateien gibt es pro Stream oft nur eine Ausprägung, eine Webcam kann dagegen mehrere Formate anbieten
  2. Den major type prüfen – Aus dem beim Aufzählen erhaltenen media type liest man MF_MT_MAJOR_TYPE aus 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
  3. Das gewünschte Ausgabeformat zusammensetzen und festlegen – Mit MFCreateMediaType wird ein neuer media type erzeugt, MF_MT_MAJOR_TYPE und MF_MT_SUBTYPE werden gesetzt, und SetCurrentMediaType wird 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_PCM und Ähnliches). Den Decoder lädt der Source Reader automatisch
  4. Das festgelegte Format erneut auslesen – Nach SetCurrentMediaType ruft man GetCurrentMediaType auf 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.

IMFTransform / Sink usw.IMFActivateEnumerations-APIAnwendungIMFTransform / Sink usw.IMFActivateEnumerations-APIAnwendungEnumeration aufrufenArray von IMFActivate*Attribute prüfenActivateObject(...)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.

IMFSourceReaderCallbackMF Work Queue (MTA)Source ReaderAnwendungs-ThreadIMFSourceReaderCallbackMF Work Queue (MTA)Source ReaderAnwendungs-ThreadReadSample(...)Kehrt sofort zurückVerarbeitung internOnReadSample(...)

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.

Partial TopologySource -&gt; OutputTopology LoaderFull TopologySource -&gt; Decoder MFT -&gt; 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.

Frames / Samples lesenIn eine Datei schreibenWiedergabesteuerung oder A/V-SynchronisationEigenen Converter einfügenWas Sie erreichen wollenWas brauchen Sie zuerst?Source ReaderSink WriterMedia SessionMFT

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:

  1. Die erste Einstiegs-API nicht falsch wählen
    • Zuerst klären, ob wirklich Source Reader, Sink Writer oder Media Session benötigt wird
  2. 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
  3. 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:

  1. Zunächst klären, ob überhaupt Source Reader, Sink Writer, Media Session oder MFT benötigt wird
  2. Die Vorgehensweise für Apartment und Callback vorab festlegen
  3. 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

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

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.

Zurück zum Blog