Media Foundation Transcoding mit C#

Langsam aber sicher hangele ich mich durch die MF (Media Foundation) durch. Im Gegensatz zu dem Video Player aus den letzten beiden Posts geht es hier um eine andere Aufgabe: Dem Transcoding (Konvertieren). Mein Beispielprogramm lässt dem Benutzer eine Quell- und eine Zieldatei auswählen und eine neue Auflösung für das Video einstellen. Nach Klick auf “Transcode!” wird das Video entsprechend konvertiert und in die neue Zieldatei geschrieben. Rein von der MF-API her ist diese Aufgabe relativ einfach zu lösen, wie aber bei meinen letzten Posts lag die Schwierigkeit auch hier eher bei der Brücke zwischen C# und MF-API. Die Stolpersteine hier waren allerdings noch etwas größer und zeitaufwändiger.

Die Komponente MFTranscoder

Die wichtigste Komponente in meinem Beispielprogramm ist die Klasse MFTranscoder. Wie im folgenden Screenshot zu sehen besitzt sie lediglich eine öffentliche Methode: TranscodeAsync. Dieser werden lediglich die Parameter Quell- und Zieldatei und Videogröße übergeben. Die Methode arbeitet asynchron, der zurückgegebene Task ist fertig, sobald die Zieldatei fertig geschrieben ist – ganz wie man sich das als .Net Entwickler wünscht.

Beispiele auf Basis von C++

Als Spickzettel bei der Entwicklung habe ich einige C++-Beispielcodes verwendet. Einer davon ist das Beispiel “Transcoding”, welches man sich hier runterladen kann. Dazu auch sehr hilfreich ist das Kapitel Transcoding im Buch “Developing Microsoft Media Foundation Applications“.

Grundlegender Aufbau

Die Methode TranscodeAsync braucht eigentlich nicht viel machen. Die komplette Funktionalität wird quasi schon so von der Media Foundation bereitgestellt, dass man im Prinzip nur noch per Code konfigurieren muss. Ähnlich wie beim Video Player muss man ein Session-Objekt anlegen, danach über die MF-API entsprechend die Quelle und das Ziel konfigurieren und danach das Session-Objekt starten und auf das Ende warten. Das Anlegen der Quelle erfolgt genauso wie beim Video Player, das Ziel und die Topology dagegen logischerweise etwas anders. Glücklicherweise reicht es, für das Ziel einfach nur das Video-, Audio- und Container-Format anzugeben. Feineinstellungen wie die Videoauflösung, Bitrate usw. werden über Attribute gemacht. Die Topology selber ist relativ einfach zu bauen, da es dafür speziell für die Transcoding-Aufgabe entsprechende Schnittstellen und Methoden gibt, mit der sie unter Angabe von Quelle, Ziel und Container gebaut werden kann. Kern der Logik in der MF-API ist das TranscodingProfile. Folgender Code ist so ziemlich die wichtigste Stelle im Beispielprogramm und zeigt den grundsätzlichen Ablauf bei der Erstellung der Topology.

// Create the transcoding profile
MF.MediaFactory.CreateTranscodeProfile(out transcodeProfile);

// Configure audio output
ConfigureAudioOutput(transcodeOptions, transcodeProfile, MF.AudioFormatGuids.WMAudioV8);

// Configure video output
ConfigureVideoOutput(transcodeOptions, transcodeProfile, MFVideoFormats.FORMAT_WMV3);

// Configure the container
ConfigureOutputContainer(transcodeProfile, MFContainerTypes.CONTAINER_ASF);

// Create the transcode topology finally
MF.MediaFactory.CreateTranscodeTopology(
    mediaSource,
    transcodeOptions.TargetFile,
    transcodeProfile,
    out topology);

Das drum herum ist dann ähnlich wie beim Video Player. Es wird ein Session-Objekt erzeugt, diesem wird die Topology zugeordnet und danach wird es gestartet und überwacht. Die drei Methoden ConfigureAudioOutput, ConfigureVideoOutput und ConfigureOutputContainer sind eigene Entwicklungen, welche weitgehend den entsprechenden Methoden in den C++-Beispielen (siehe oben) entsprechen.

Konfiguration des Video-Formats

Ab hier möchte ich auf einen Zeitfresser eingehen, der mir einige Stunden gekostet hat. In den C++-Beispielen werden zur Angabe des Video-Formats Konstanten verwendet. Hier ein Beispiel:

// Set the encoder to be Windows Media video encoder, so that the appropriate MFTs are added to the topology.
if (SUCCEEDED(hr))
{
    hr = pVideoAttrs->SetGUID(MF_MT_SUBTYPE, MFVideoFormat_WMV3);
}

Ich spreche hier beispielsweise von der Konstante MFVideoFormat_WMV3. Natürlich gibt es noch ein paar mehr, aber in SharpDX (2.5) lassen sich diese Konstanten im Gegensatz zu denen für Audio-Formaten nicht finden. Scheinbar ist SharpDX an der Stelle noch nicht komplett. Aber gut, normalerweise wäre das ja kein Problem, man kann die Konstante(n) ja auch selber aus den Header der MF-API abschreiben. Im C++-Header sind diese aber wie folgt definiert:

DEFINE_MEDIATYPE_GUID( MFVideoFormat_WMV3,      FCC('WMV3') );

...

#define DEFINE_MEDIATYPE_GUID(name, format) \
    DEFINE_GUID(name,                       \
    format, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
#endif

...

#define FCC(ch4) ((((DWORD)(ch4) & 0xFF) << 24) |     \
                  (((DWORD)(ch4) & 0xFF00) << 8) |    \
                  (((DWORD)(ch4) & 0xFF0000) >> 8) |  \
                  (((DWORD)(ch4) & 0xFF000000) >> 24))
#endif

Bitte nicht verwirren lassen, obige Schnipsel stammen aus verschiedenen Teilen der C++-Header. Hieraus kann man lesen, dass es sich um Guids handelt und dass alle bis auf das erste Feld genau gleich aufgebaut sind (Siehe Makro DEFINE_MEDIATYPE_GUID). Das erste Feld ergibt sich aus dem Wert, der hier bei dem Makro FCC heraus kommt. Nun, mein erster Versuch diese “Bitshifterei” in C# nachzubauen ging in die Hose – warum auch immer.  Nach einer kurzen Suche im Web und einem genaueren Blick auf den Makro zeigt, welche primitive Logik dahintersteckt: Der übergebene String wird einfach so wie er ist in eine Variable vom Typ Integer geschrieben (Siehe Wikipedia). Wichtig für .Net ist, dass als Kodierung ASCII verwendet wird. Nachfolgende Methode habe ich mir schließlich gebaut, um diese Guid nach Angabe des FCC-Strings zusammen zu bauen.

/// <summary>
/// Helper function that builds the Guid for a video subtype using the given FOURCC value
/// </summary>
/// <param name="fourCCString">The FOURCC string to convert to a guid.</param>
public static Guid BuildVideoSubtypeGuid(string fourCCString)
{
    // Generate and return Guid
    return new Guid(
        GetFourCCValue(fourCCString),
        0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
}

/// <summary>
/// Gets the FourCC value for the given string.
/// More infos about FourCC:
///  see: http://msdn.microsoft.com/en-us/library/windows/desktop/bb970509(v=vs.85).aspx,
///  see: http://msdn.microsoft.com/en-us/library/windows/desktop/aa370819(v=vs.85).aspx#creating_subtype_guids_from_fourccs_and_d3dformat_values,
///  see: http://de.wikipedia.org/wiki/FourCC
/// </summary>
/// <param name="fourCCString">The FourCC string to be converted into an unsigned integer value.</param>
public static uint GetFourCCValue(string fourCCString)
{
    if (string.IsNullOrEmpty(fourCCString)) { throw new ArgumentNullException("subtype"); }
    if (fourCCString.Length > 4) { throw new ArgumentException("Given value too long!"); }

    // Build fcc value
    byte[] asciiBytes = Encoding.ASCII.GetBytes(fourCCString);
    byte[] fccValueBytes = new byte[4];
    for (int loop = 0; loop < 4; loop++)
    {
        if (asciiBytes.Length > loop) { fccValueBytes[loop] = asciiBytes[loop]; }
        else { fccValueBytes[loop] = 0x20; }
    }

    // Return guid
    return BitConverter.ToUInt32(fccValueBytes, 0);
}

Mit Hilfe dieser Methode hat sich dann relativ einfach eine Klasse basteln lassen, welche die Guids aller möglichen Video-Formate enthält – so wie man es erwarten würde.

/// <summary>
/// All video formats supported by media foundation (excluding Direct3D formats).
///  ..see mfapi.h (Guids with name MFVideoFormat_*).
/// </summary>
static class MFVideoFormats
{
    public static readonly Guid FORMAT_AI44 = Helper.BuildVideoSubtypeGuid("AI44");
    public static readonly Guid FORMAT_AYUV = Helper.BuildVideoSubtypeGuid("AYUV");
    public static readonly Guid FORMAT_YUY2 = Helper.BuildVideoSubtypeGuid("YUY2");
    public static readonly Guid FORMAT_YVYU = Helper.BuildVideoSubtypeGuid("YVYU");
    public static readonly Guid FORMAT_YVU9 = Helper.BuildVideoSubtypeGuid("YVU9");
    public static readonly Guid FORMAT_UYVY = Helper.BuildVideoSubtypeGuid("UYVY");
    public static readonly Guid FORMAT_NV11 = Helper.BuildVideoSubtypeGuid("NV11");

    // ...

Weitere Schwierigkeiten

Alles Weitere lief glücklicherweise gewöhnlich ab. Gewöhnlich in dem Sinne, wie man es sich beim Umsetzen von C++ Code in C# erwarten kann…

Quellcode

www.rolandk.de/files/Testing.SimpleMFTranscoding.zip

Verweise

  1. http://archive.msdn.microsoft.com/mediafoundation
  2. http://www.sharpdx.org/documentation/api/n-sharpdx-mediafoundation
  3. Developing Microsoft Media Foundation Applications von Anton Polinger

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.