Aus Linux-Magazin 05/2006

Gstreamer-Plugins entwickeln

Gstreamer ist ein Framework zur Entwicklung von Multimedia-Anwendungen in C, C++, Python oder Mono. Plugins übernehmen die Ein- und Ausgabe sowie die Verarbeitung der medialen Daten.

Die schöne neue Multimediawelt bringt dem Anwender unzählige Dateiformate und Gerätschaften. Mit Gstreamer [1] steht ein Multimedia-Framework zur Verfügung, das beliebige Inhalte verarbeitet. Es verwaltet dabei in einer Pipeline drei Arten von Elementen: Die erste empfängt Audio- oder Videosignale, die zweite verarbeitet sie, die dritte reicht die Signale an einen Ausgang wie die Soundkarte weiter. Der Gnome-Desktop verwendet Gstreamer für seine Media-Anwendungen (CD-Player, Videoplayer, Lautstärkeregler und vieles mehr), die Quellen und Pakete gibt es auf der Gstreamer-Webseite.

Reges Echo

Am Beispiel eines Echoeffekts (Delay mit Feedback) beschreibt der Artikel, wie jeder C-Kundige eigene Plugins für das Framework entwickelt. Abbildung 1 stellt zunächst den Signalfluss schematisch dar: Der Echoeffekt beruht auf einem Ringpuffer, der eingehende Audiodaten zwischenspeichert und verzögert wiedergibt. Die ausgehenden Daten kehren etwas leiser in den Ringpuffer zurück, dabei entstehen die Echos. Die Länge des Ringpuffers bestimmt die Echoabstände, die Dämpfung für die Rückkopplung steuert das Abklingverhalten. Bevor das Signal ertönt, durchläuft es noch eine Mischstufe, die die Intensität des Effekts regelt.

Abbildung 1: Ein Ringpuffer speichert eingehende Daten zwischen und gibt sie dann verzögert wieder. Die ausgehenden Daten kehren anschließend etwas leiser in den Ringpuffer zurück, so entsteht ein Echo.

Abbildung 1: Ein Ringpuffer speichert eingehende Daten zwischen und gibt sie dann verzögert wieder. Die ausgehenden Daten kehren anschließend etwas leiser in den Ringpuffer zurück, so entsteht ein Echo.

Das Effektelement besitzt später also drei Parameter:

  • Delaytime: Die Länge des Ringpuffers bestimmt die Zeit
    zwischen zwei Echos in Millisekunden.
  • Feedback: Die Echorückführung in Prozent sorgt
    für die Dämpfung der Rückkopplung.
  • Dry/Wet: Beeinflusst die Intensität des Effekts.

Klingt kompliziert, ist es aber nicht. Ein einfaches Array realisiert den Ringpuffer, zum Hineinschreiben und Auslesen merkt sich der Algorithmus zwei Array-Indizes, die Mischstufe besteht aus einer schlichten Addition.

So sieht das Ganze nun als Gstreamer-Element aus: Gstreamer selbst ist in der Programmiersprache C geschrieben. Um auch in C objektorientiert zu programmieren [2], kommt die Gobject-Bibliothek zum Einsatz. Obwohl dieser Artikel C für das Beispiel-Plugin verwendet, kann ein Entwickler Gstreamer-Anwendungen und -Plugins auch in C++, Python, Mono oder anderen Sprachen realisieren.

Nur den oben beschriebenen Algorithmus in C umzusetzen, genügt leider nicht: Ein Element muss sich auch um die Ressourcenverwaltung kümmern und ein paar Spielregeln einhalten, damit die Kooperation der unterschiedlichen Elemente funktioniert. Gstreamer bringt bereits mehrere Basisklassen mit, das Beispielelement nutzt eine solche Klasse und stützt sich zum Teil auf ererbte Methoden.

Gstreamer-ABC

Das Gstreamer-Konzept ist recht einfach: Signale aus verschiedenen Quellen, etwa dem Internet oder aus Gerätedateien, erreichen die Pipeline, die aus Elementen besteht. Elemente gehören zur wichtigsten Gstreamer-Objektklasse »GstElement«. Sie beeinflussen und verwandeln die eingehenden Signale, demultiplexen oder konvertieren sie. Mehrere Elemente hintereinander geschaltet bilden eine Pipeline: Das Eingangssignal durchläuft also die Pipeline und landet zum Beispiel beim Audio-Ausgang.

Container innerhalb dieser Pipeline, die selbst Elemente sind, aber zugleich mehrere Elemente in sich versammeln (Abbildung 2), heißen Bins. Elemente lassen sich nicht nur im Verhältnis 1:1 verschalten, sondern auch 1:n, n:1 oder n:m. Auf diese Weise besteht beispielsweise die Möglichkeit, ein eingehendes AV-Signal (Source) in ein Sound- und ein Videosignal (Sink) zu zerlegen.

Abbildung 2: Eine Gstreamer-Pipeline besteht aus mehreren miteinander verschalteten Elementen, die ein eingehendes Signal verwandeln, bei Bedarf auch in mehrere Elemente aufsplitten und wieder ausgeben.

Abbildung 2: Eine Gstreamer-Pipeline besteht aus mehreren miteinander verschalteten Elementen, die ein eingehendes Signal verwandeln, bei Bedarf auch in mehrere Elemente aufsplitten und wieder ausgeben.

Plug&Play

Gstreamer-Elemente gibt es in verschiedenen Ausprägungen: Die einen senden Daten (Source), andere empfangen (Sink), wieder andere verarbeiten sie (Transformer oder Filter genannt). Alle Elemente haben Pads, die vergleichbar wie Buchsen funktionieren und Elemente verbinden. Pads wiederum besitzen Caps (kurz für Capabilities, Fähigkeiten), die Eigenschaften wie etwa den Medientyp beschreiben. Caps verbinden nur zueinander passende Pads, ganz so wie Stecker ausschließlich in entsprechend geformte Buchsen passen.

Elemente bettet der Programmierer in der Regel in Plugins ein, die als Shared Object oder dynamische Bibliotheken dem Gstreamer-System zur Verfügung stehen – sie lassen sich bei Bedarf jederzeit nachinstallieren. Das System erkennt automatisch neue Plugins, die auch mehrere Elemente enthalten können. Entwickler sind dadurch in der Lage, unabhängig vom Gstreamer-Projekt die Unterstützung für ganz bestimmte Dateiformate nachzuliefern.

Basisklassen

Für Sources, Sinks und Transformatoren gibt es jeweils eigene Basisklassen. Das ankommende Audiosignal soll als Effekt beispielsweise ein Echo erhalten, hierfür bietet sich die Transformatorklasse an. Viele Wege führen zur Grundstruktur eines Plugin: Am einfachsten ist es, ein ähnliches Plugin auszuwählen und es umzuschreiben. Alternativ entnimmt man das Grundgerüst für Plugins oder Anwendungen dem Gstreamer-CVS [3], wie es der Kasten “Gstreamer aus dem CVS” zeigt.

Gstreamer aus dem
CVS

Zunächst zieht der Entwickler das Grundgerüst für das Plugin aus dem CVS:

export CVSROOT=:pserver:anoncvs@anoncvs.freedesktop.org:/cvs/gstreamer 
cvs co -P gst-template

Anschließend legt er ein Projektverzeichnis an und wechselt dorthin:

mkdir gst-echo 
cd gst-echo

Die nötigen Dateien kopiert er aus dem Verzeichnis, in dem sich die Plugins befinden:

cp -R ../gst-template/gst-plugin/* .

Jetzt sucht und löscht er die CVS-Dateien und wechselt ins Source-Verzeichnis:

find . -name "CVS" -exec rm -rf {} ; 
cd src

Abschließend erzeugt er aus einer Vorlage die Quellen für das neue Plugin:

../tools/make_element Audiodelay  gsttransform

Das ist die Ausgangsbasis für das Echo-Plugin. Nun ändert der Entwickler im Editor die Zeile 14 der Datei »configure.ac«, indem er »gst-plugin« durch »gst-echo« ersetzt. Ähnlich verfährt er mit der Datei »src/Makefile.am«, in der er sechs Vorkommen von »gstplugin« durch »gstaudiodelay« ersetzt. Es fehlen noch Ergänzungen der Variablen »_CFLAGS« und »_LIBADD«. Listing 1 zeigt, wie die Datei anschließend aussieht.

Jetzt startet der Benutzer den Kompiliervorgang, richtet zunächst über »./autogen.sh –prefix= die Autotools ein, kompiliert und installiert schließlich über »make all install« die Quellen. Superuser-Rechte braucht er für die Installation nicht, die Bibliotheken liegen nach der Installation unter »/.gstreamer-0.10/plugins«. Ein »gst-inspect audiodelay« testet, ob Gstreamer das Plugin erkennt (Abbildung 3). Soll es später einmal allen Anwendern zur Verfügung stehen, installiert Root die Quellen nach »–prefix=/usr/local«.

Listing 1:
»src/Makefile.am«

01 plugin_LTLIBRARIES = libgstaudiodelay.la
02 [...]
03 libgstaudiodelay_la_SOURCES = gstaudiodelay.c
04 [...]
05 libgstaudiodelay_la_CFLAGS = $(GST_CFLAGS) $(GST_BASE_CFLAGS) $(GSTCTRL_CFLAGS)
06 libgstaudiodelay_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) $(GSTCTRL_LIBS)
07 libgstaudiodelay_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
08 [...]
09 noinst_HEADERS = gstaudiodelay.h
Abbildung 3: Der Aufruf von »gst-inspect audiodelay« verrät die Spezifikationen des neuen Gstreamer-Plugin, gibt man »gst-inspect« ein, zeigt Gstreamer alle verfügbaren Plugins an.

Abbildung 3: Der Aufruf von »gst-inspect audiodelay« verrät die Spezifikationen des neuen Gstreamer-Plugin, gibt man »gst-inspect« ein, zeigt Gstreamer alle verfügbaren Plugins an.

Plugin-Grundgerüst

Das so weit implementierte Audiodelay-Plugin klingt nicht sonderlich eindrucksvoll: Es reicht alle eingehenden Daten unverändert weiter, den kompletten Sourcecode findet man auf der Linux-Magazin-Webseite [4]. Zur Funktionsweise des generierten Plugin: Gstreamer wertet nach dem Laden zuerst die Struktur »plugin_desc« aus, abhängig von den vorgefundenen Informationen erfolgt der Eintrag in die Plugin-Datenbank. Die Struktur befindet sich meist am Ende des Quelltextes, sie enthält Konstanten und Bezeichnungen (Listing 2).

Listing 2:
»plugin_desc«

01 /* this is the structure that gstreamer looks for to 
02  * register plugins exchange the strings 'plugin' and 
03  * 'Template   plugin' with you plugin name and description */
04 GST_PLUGIN_DEFINE (
05     GST_VERSION_MAJOR,
06     GST_VERSION_MINOR,
07     "audiodelay",
08     "Audio echo plugin",
09     plugin_init,
10     VERSION,
11     "LGPL",
12     "GStreamer",
13     "http://gstreamer.net/"
14 )

»GST_VERSION« steht für die Gstreamer-Version, mit der das Plugin kompiliert wurde. Das Configure-Skript erkennt die Version automatisch. Gstreamer registriert später das Plugin nur, wenn die Hauptversionen korrelieren. Die weiteren Einträge wie Kurzname, Beschreibung, Version, Lizenz, Paket und Homepage vergibt der Entwickler nach Gusto. Der Abschnitt »plugin_init()« (Listing 3) registriert das Plugin – eine Funktion, die jedes Plugin benötigt.

Listing 3:
»plugin_init«

01 static gboolean
02 plugin_init (GstPlugin * plugin)
03 {
04   /* initialize gst controller library */
05   gst_controller_init(NULL, NULL);
06 
07   return gst_element_register (plugin, "audiodelay", GST_RANK_NONE, GST_TYPE_AUDIODELAY);
08 }

Wie in diesem Beispiel registrieren Plugins oft nur ein Element. Die Funktion »gst_element_register()« bekommt in Zeile 7 als letzten Parameter den Typ der Gobject-Klasse übergeben (»GST_TYPE_AUDIODELAY«), wobei die Klasse instantiiert wird. Für das Verständnis des Gobject-Klassensystems empfiehlt sich das einführende Tutorium der Gobject-API-Dokumentation [2].

Boilerplate

Das eingebaute Makro »GST_BOILERPLATE_FULL()« erspart praktischerweise einen Teil der Tipparbeit: Es erzeugt die »gst_audiodelay_get_type()«-Funktion – jede Gobject-Klasse setzt eine »_get_type()«-Funktion voraus – und registriert nun die wichtigsten Gobject-Methoden. Die Funktion »gst_audiodelay_base_init()« meldet indes die Metadaten für das neue Element an (Listing 4). Das Element heißt Audiodelay, zusätzlich gibt man die Klassifikation, eine Kurzbeschreibung und den Autor an. Für die Klassifikation existiert noch keine genaue Richtlinie, daher kommt das Audiodelay, dem Beispiel anderer Elemente folgend, nach »Filter/Effect/Audio«.

Listing 4:
»gst_audiodelay_base_init«

01 static void
02 gst_audiodelay_base_init (gpointer klass)
03 {
04   static GstElementDetails element_details = {
05     "Audiodelay",
06     "Filter/Effect/Audio",
07     "Add echos to audio streams",
08     "Stefan Kost <ensonic@users.sf.net>"
09   };

Pad-Vorlagen

Es folgen die Vorlagen für die Pads. Die beiden »GstStaticPadTemplate«-Strukturen zu Beginn des Quelltextes beschreiben die Anschlussmöglichkeiten des Elements, wobei der Einfachheit halber eine Beschränkung auf »Mono«, »16bit signed integer Audio« erfolgt, nur die Samplingrate bleibt unbeschränkt.

Die Methode »gst_audiodelay_class_init()« überschreibt einige Methoden der Basisklassen und registriert die Parameter des Effekts: »drywet«, »delaytime« und »feedback« (Listing 6).

Listing 6:
»gst_audiodelay_class_init«

01 [...]
02 g_object_class_install_property (gobject_class, PROP_DRYWET,
03     g_param_spec_uint ("drywet", "Dry-Wet", "Intensity of effect (0 none -> 100 full)",
04         0, 100, 50,
05         G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
06 
07   g_object_class_install_property (gobject_class, PROP_DELAYTIME,
08     g_param_spec_uint ("delaytime", "Delay time", "Time difference between two echos as milli seconds.",
09         1, DELAYTIME_MAX, 100,
10         G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
11 
12   g_object_class_install_property (gobject_class, PROP_FEEDBACK,
13     g_param_spec_uint ("feedback", "Fedback", "Echo feedback in percent.",
14         0, 99, 50,
15         G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE));
16 [...]

Neben den Gobject-Methoden überschreibt man in die Datei auch vier Methoden der Klasse »GstBaseTransform«:

  • »set_caps()« wird nach dem Verbinden des
    Beispielelements mit den Elementen aufgerufen, die gekoppelten
    Elemente haben sich auf ein Format geeinigt und die Samplingrate
    für den Echoeffekt wurde ausgehandelt.
  • »start()« und »stop()« werden vor und
    nach der Berechnung aufgerufen. Diese Methoden eignen sich dazu,
    Ressourcen zu belegen und wieder freizugeben.
  • »transform_ip()« heißt die Methode, die der
    Algorithmus nutzt. Das Suffix »_ip« steht für
    “in place”, weil die Methode ihr Ergebnis im
    Eingangspuffer ablegt. Der Aufruf der Funktion erfolgt zyklisch,
    bis sie den kompletten Datenstrom abgearbeitet hat.

Die Methode »gst_audiodelay_init()« belegt die Parameter und Objektvariablen mit sinnvollen Werten, im weiteren Verlauf räumen die beiden »_{set,get}_property()«-Methoden und die »_dispose()«-Methode auf.

Nach dem Gobject-lastigen Teil folgen nun Gstreamer-Methoden: Zunächst kommt die überladene Funktion »gst_audiodelay_set_caps()« zum Einsatz und liest die Struktur aus dem übergebenen »Caps«-Objekt. Dabei handelt es sich um eine Art Hashmap, die Verbindungseigenschaften auflistet. Die Effekt-Instanz übernimmt anschließend die anhand dieser Daten ermittelte Samplingrate (in Samples pro Sekunde), die nötig ist, um später den Delaytime-Parameter von Millisekunden in Samples umzurechnen, denn der Delay-Effekt greift wie erwähnt auf einen Ringpuffer zurück.

Nun berechnet der Entwickler die maximale Ringpuffergröße, da – dank der Funktion »gst_audiodelay_set_caps()« – die Samplingrate bekannt ist und zudem eine Obergrenze für die Echozeit (»DELAYTIME_MAX«) feststeht. Die Berechnung erledigt die Funktion »gst_audiodelay_start()« und alloziert auch gleich den Speicher, die Funktion »gst_audiodelay_stop()« gibt ihn wieder frei.

Es folgt die eigentliche Verarbeitung des Signals: Dies übernimmt die Methode »gst_audiodelay_transform_ip()«. Die Parameter des Echoeffekts ändern sich über die Zeit hinweg, wenn der Nutzer zum Beispiel an den Reglern im GUI dreht oder wenn der Sequenzer aufgezeichnete Reglerbewegungen wiedergibt.

Die Methode »gst_object_sync_values()« (Listing 5) reicht Eigenschaften des Objekts an das Element weiter, auf diese Weise legen Gstreamer-Anwendungen dynamische Parameter-Verläufe für Gobject-Eigenschaften an.

Listing 5:
»gst_object_sync_values«

01 if (GST_CLOCK_TIME_IS_VALID (GST_BUFFER_TIMESTAMP (outbuf)))
02     gst_object_sync_values (G_OBJECT (filter), GST_BUFFER_TIMESTAMP (outbuf));

Finaler Testlauf

Der Aufruf »gst_object_sync_values()« weist dem Element Wertänderungen in Form von Parametern für den aktuellen Bearbeitungszeitpunkt (Zeitstempel des Puffers) zu und legt fest, ob diese sprunghaft oder geglättet verlaufen sollen. Dies gilt jedoch nur für Parameter, die die Funktion »_class_init()« (Listing 6) mit dem Flag »GST_PARAM_CONTROLLABLE« markiert hat.

Jetzt formt das Programm die Parameter des Echoeffekts von anwenderfreundlichen Einheiten (Millisekunden und Prozent) in rechnerfreundliche Einheiten um und errechnet die Position des Ringpufferzeigers zum Auslesen. Anschließend beginnt die Datenverarbeitung: Eine For-Schleife arbeitet den Inhalt des Puffers ab und erzeugt die Ergebnisdaten, der Rückgabewert »GST_FLOW_OK« signalisiert den Erfolg der Berechnung.

In der Pipeline

Es ist jetzt so weit, den Effekt erneut zu kompilieren und zu testen, was das Kommando »gst-launch« ermöglicht. Die komplette Pipeline besteht aus Kommandozeilenargumenten, die folgenden Befehle testen das Echo jeweils an einer MP3- und einer OGG-Datei:

gst-launch filesrc location="melo1.mp3" ! mad ! audioconvert ! audiodelay delaytime=25 feedback=75 ! osssink
gst-launch filesrc location=" "melo1.ogg" ! oggdemux ! vorbisdec ! audioconvert ! audiodelay delaytime=25 feedback=75 ! osssink

Natürlich sollte man Sounds verwenden, die nicht ohnehin Echos nutzen. Ist keine passende Sound-Datei zur Hand, tut es auch folgende Variante:

gst-launch osssrc ! audiodelay delaytime=25 feedback=75 ! osssink

Diese Pipeline fügt der an der Soundkarte angeschlossenen Quelle ein Echo hinzu, es kann zum Beispiel ein Mikrofon oder ein Plattenspieler sein.

Im Gstreamer-CVS gibt es noch eine Vielzahl von Plugins im Sourcecode, die sich als Vorlage eignen. Ergänzend sei Entwicklern die Lektüre des Plugin Writers Guide (PWG, [5]) besonders ans Herz gelegt, zudem gibt es einen IRC-Channel [6], der offene Fragen zum Thema Gstreamer klärt.

Last but not least steht auf der Gstreamer-Webseite eine ausführliche englischsprachige Dokumentation zum Thema Gstreamer und Plugin-Entwicklung bereit, sodass dem motivierten Plugin-Entwickler nun eigentlich kein Hindernis mehr im Wege stehen sollte. (kki)

Infos

[1] Gstreamer-Webseite: [http://gstreamer.freedesktop.org]

[2] Gobject: [http://developer.gnome.org/doc/API/2.0/gobject/index.html]

[3] Gst-Template: [http://cvs.freedesktop.org/gstreamer/gst-template/]

[4] Komplettes Echo-Plugin im Sourcecode:[https://www.linux-magazin.de/Service/Listings/2006/04/GStreamer-Plugins/gst-echo-0.10.0.1/src/gstaudiodelay.c]

[5] Plugin Writers Guide: [http://gstreamer.freedesktop.org/data/doc/gstreamer/head/pwg/html/index.html]

[6] Gstreamer IRC-Kanal: [irc://irc.freenode.net/gstreamer]

Der Autor

Stefan Kost arbeitet seit knapp zwei Jahren im Gstreamer-Projekt. Er entwickelt in seiner Freizeit eine auf der Gstreamer-Technologie basierende Software zum Musizieren [6].

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben