Inhaltsverzeichnis

23.9.0 Media (gb.media, gb.media.form)

Die Gambas-Komponenten gb.media und gb.media.form stellen eine Vielzahl von Klassen und Steuer-Elementen zur Verfügung, die auf dem freien MultiMedia-Framework GStreamer aufsetzen. GStreamer ist lizenziert unter LGPL und stellt Bibliotheken zur Verarbeitung von Medien-Strömen zur Verfügung und bilden damit die Grundlage zum Beispiel für Mediaplayer.

Um mit den Komponenten gb.media und gb.media.form arbeiten zu können ist es unerlässlich, sich in das Konzept von GStreamer einzuarbeiten. Hierzu ist folgende Quelle zu empfehlen:

GStreamer Webseite: https://gstreamer.freedesktop.org/

In einem ersten Schritt sollten Sie sich über den grundsätzlichen Aufbau von GStreamer-Pipelines informieren, die als Funktionskette die Grundlage für jede Medienanwendung bilden. Gambas baut solche GStreamer-Pipelines in identischer Struktur auf und verwendet dafür eine gambas-spezifische Syntax.

23.9.0.1 Die GStreamer-Pipeline und ihre Elemente

23.9.0.1.1 GStreamer-Elemente

Folgende GStreamer-Elemente stehen als Basis-Elemente für Pipelines zur Verfügung:

BILD_1

Abbildung 23.9.0.1.1: Basis-Elemente

Diese Basis-Elemente können verschiedene GStreamer-Plug-Ins mit unterschiedlichsten Funktionen repräsentieren. Eine Übersicht über die Plug-Ins erhalten Sie auf der GStreamer-Webseite: https://gstreamer.freedesktop.org/documentation/plugins_doc.html?gi-language=c .

Elemente sind mindestens mit einem Input oder einem Output versehen sind und werden über diese mit einem anderen Element verbunden. Bei so einer Verbindung dient der Input eines Elements als Senke für das verbundene Element, weshalb der Input als 'Sink' und der Ausgang eines Elements analog als 'Source' bezeichnet wird. Die Ein- und Ausgänge der Elemente entsprechen in der GStreamer-Terminologie den so genannten 'Pads'. In vielen Fällen sind Elemente mit sogenannten Properties (Eigenschaften) ausgestattet, die Sie zur Parametrierung des Elements verwenden können – hier für das Element 'filesrc' mit seiner Eigenschaft 'location':

$ gst-launch-1.0 filesrc location=mp.mp3 ! audioconvert ! autoaudiosink

23.9.0.1.2 GStreamer-Pipeline

Eine GStreamer-Pipeline repräsentiert eine vollständige Medien-Funktion und setzt sich aus hintereinander geschalteten Elementen zusammen. GStreamer-Pipelines können sich auch verzweigen, um zum Beispiel das Audio- und Video-Signal getrennt zu behandeln oder um ein Audio-Signal über einen Zweig auszugeben und es über den anderen in einer Datei aufzuzeichnen.

Hier ein Beispiel für eine Pipeline zum Abspielen einer ogg-Datei:

BILD_2

Abbildung 23.9.0.1.2: Pipeline

23.9.0.1.3 Entwicklungsschritte für eine GStreamer-Pipeline

Um eine GStreamer-Pipeline in Gambas abbilden zu können, ist die Kenntnis der GStreamer-Syntax unerlässlich. Es ist sogar zu empfehlen, vor einer Umsetzung in Gambas eine Pipeline als CLI-Kommando auszuprobieren.

Hier als einfaches Beispiel einer Pipeline in Form eines GStreamer-CLI-Kommandos, das einen 440Hz-Sinus-Test-Ton erzeugt, der auf dem Lautsprecher des PC ausgegeben wird:

$ gst-launch-1.0 audiotestsrc ! audioconvert ! autoaudiosink

Bei gst-launch-1.0 handelt es sich um das GStreamer-Kommandozeilen-Tool zur Ausführung von Pipelines, deren Wiedergabe Sie jederzeit mit Strg-C abbrechen können. Alle weiteren Ausdrücke repräsentieren Elemente, die jeweils mit einem Ausrufezeichen voneinander getrennt sind und als Funktionskette von links nach rechts aufgebaut werden und insgesamt eine Pipeline repräsentieren. Hier können Sie sich über das Tool gst-launch-1.0 informieren und eine Liste von nützlichen Beispielen finden: https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c.

Die Pipeline besteht aus den folgenden, hintereinander geschalteten Elementen:

BILD_3

Abbildung 23.9.0.1.3: Generator-Pipeline

Die Struktur der o.a. Pipeline lässt sich in einem nächsten Schritt auf die Gambas-Syntax übertragen:

Private pl As MediaPipeline
Private src As MediaControl
Private cnv As MediaControl
Private snk As MediaControl
 
Public Sub Form_Open()
 
'-- Generate Pipeline
    pl = New MediaPipeline
 
'-- Generate MediaControls  (GStreamer Elements)
    src = New MediaControl(pl, "audiotestsrc")
    cnv = New MediaControl(pl, "audioconvert")
    snk = New MediaControl(pl, "autoaudiosink")
 
'-- Link MediaControls (GStreamer Elements)
    src.LinkTo(cnv)
    cnv.LinkTo(snk)
 
'-- Start/play pipeline
    pl.Play()
 
End

In einem weiteren Schritt könnten Sie beispielsweise eine andere als die 440Hz-Standardfrequenz festlegen. Das CLI-Kommando zur Ausgabe eines 1000Hz-Tons müsste dann so aussehen:

gst-launch-1.0 audiotestsrc freq=1000 ! audioconvert ! autoaudiosink

Das obere Gambas-Programm müsste dafür nur um eine Zeile erweitert werden, in der die Eigenschaft 'freq' gesetzt wird:

Private pl As MediaPipeline
Private src As MediaControl
Private cnv As MediaControl
Private snk As MediaControl
 
Public Sub Form_Open()
 
'-- Generate Pipeline
    pl = New MediaPipeline
 
'-- Generate MediaControls  (GStreamer Elements)
    src = New MediaControl(pl, "audiotestsrc")
    src["freq"] = 1000
    cnv = New MediaControl(pl, "audioconvert")
    snk = New MediaControl(pl, "autoaudiosink")
 
'-- Link MediaControls (GStreamer Elements)
    src.LinkTo(cnv)
    cnv.LinkTo(snk)
 
'-- Start/play pipeline
    pl.Play()
 
End

Das zweite Programm ließe sich auf einen praktischen Tongenerator erweitern – mit einstellbarer Frequenz, anderer Wellenform und veränderbarem Ausgabepegel.

Bitte beachten Sie, dass Datentypen, die mit 'Dim' deklariert wurden, nur eine lokale Gültigkeit haben und eine Lebensdauer für den Zeitraum haben, in dem sich das Programm in der Sub-Routine befindet. Das gilt natürlich auch für alle Deklarationen, die eine Pipeline betreffen. Somit würde das o.a. Beispiel mit lokal deklarierten MediaPipelines und MediaControls nicht funktionieren, weil alle Variablen nach der Ausführung der Play()-Methode und dem Verlassen der Sub-Routine sofort wieder zerstört wären. Da Sie Pipelines in der Regel für längere Zeiträume betreiben möchten ist darauf zu achten, dass Sie alle Variablen einer Pipeline zumindest mit 'Private' deklarieren – also mit Gültigkeit auf Modul-Ebene oder auf Klassen-Ebene.

Ab Gambas Version 3.19 reicht es, wenn das Steuerelement mit dem höchsten Level, wie MediaPipeline oder MediaControl, mit einer Gültigkeit auf Modul-/Klassenebene deklariert wird.

Da sich die Auswahl von notwendigen GStreamer-Elementen für eine Pipeline oft nicht auf einfache Weise ergibt und dafür im Einzelfall viel Hintergrundwissen erforderlich ist, empfiehlt es sich, Beispiele für GStreamer-Pipelines im Web ausfindig zu machen oder auf bereits bewährte Pipelines zurück zu greifen.

23.9.0.1.4 Umsetzung bildgebender GStreamer-Pipelines

Um Videos in einem Gambas-GUI-Steuerelement wiedergeben zu können, müssen Sie zusätzlich zu einem Sink auch ein Steuerelement für die Anzeige festlegen. Hierfür können Sie beispielsweise eine Form, die Steuerelemente MediaView (gb.media.form), DrawingArea oder auch eine PictureBox verwenden.

Beispiel: Führen Sie zunächst folgende CLI-Kommandozeile aus um sehen zu können, was umgesetzt werden soll:

gst-launch-1.0 videotestsrc ! xvimagesink

Die Umsetzung in Gambas sieht beispielsweise so aus, wobei hier zur Wiedergabe des GStreamer-Testbilds die Form FMain verwendet wird:

Private pl As MediaPipeline
Private src As MediaControl
Private snk As MediaControl
 
Public Sub Form_Open()
 
  pl = New MediaPipeline As "PipeEvents"
  src = New MediaControl(pl, "videotestsrc")
  snk = New MediaControl(pl, "xvimagesink")
 
  snk.SetWindow(Me)
 
  src.LinkTo(snk)
 
  pl.Play()
 
End

23.9.0.1.5 Umsetzung einer Signal-Aufspaltung in Pipelines (TEE)

In einigen Fällen ist die Aufspaltung eines Signals erforderlich. So können Sie zum Beispiel einen Zweig dafür verwenden, ihn auf dem Lautsprecher auszugeben, während der andere das Signal in einer Datei abspeichert und damit aufzeichnet. Für solche Zwecke sieht das GStreamer-Framework das sogenannte tee-Element vor. Eine Pipeline mit einem tee-Element sieht dann so aus:

BILD_4

Abbildung 23.9.0.1.4: Pipeline zum Hören und Aufzeichnen von Web-Radio-Streams

Die o.a. Pipeline gibt eine Webradio-Station auf dem Lautsprecher aus und zeichnet das Audiosignal parallel in einer Datei im MP3-Format ab. Es folgt das entsprechende GStreamer-CLI-Kommando und kann in der Konsole ausprobiert werden.

gst-launch-1.0 uridecodebin uri="http://icecast.ndr.de/ndr/ndrinfo/schleswigholstein/mp3/128/stream.mp3" \
! audioconvert ! tee name=radio ! queue ! autoaudiosink radio. ! queue \
! lamemp3enc target=bitrate bitrate=128 cbr=true ! filesink location="/home/username/output.mp3"

Ab Gambas 3.19 könnte eine Umsetzung in Gambas so aussehen:

    Private pl As MediaPipeline
'-- Declarations for source
    Private src As MediaControl
    Private tee As MediaControl
    Private cnv As MediaControl
'-- Declarations for branch 1
    Private que1 As MediaControl
    Private snk1 As MediaControl
'-- Declarations for branch 2
    Private que2 As MediaControl
    Private enc2 As MediaControl
    Private snk2 As MediaControl
 
Public Sub Form_Open()
 
    pl = New MediaPipeline
'-- Source
    src = New MediaControl(pl, "uridecodebin")
    src["uri"] = "http://icecast.ndr.de/ndr/ndrinfo/schleswigholstein/mp3/128/stream.mp3"
    cnv = New MediaControl(pl, "audioconvert")
    tee = New MediaControl(pl, "tee")
'-- Branch 1
    que1 = New MediaControl(pl, "queue")
    snk1 = New MediaControl(pl, "autoaudiosink")
'-- Branch 2
    que2 = New MediaControl(pl, "queue")
    enc2 = New MediaControl(pl, "lamemp3enc")
    enc2["target"] = "bitrate"
    enc2["bitrate"] = 128
    enc2["cbr"] = True
    snk2 = New MediaControl(pl, "filesink")
    snk2["location"] = User.Home & "/radio_rec.mp3"
 
'-- Link source elements
    src.LinkLaterTo(cnv)
    cnv.LinkTo(tee)
 
'-- Link to TEE and link branch 1 elements
    tee.LinkTo(que1)
    que1.LinkTo(snk1)
 
'-- Link to TEE and link branch 2 elements
    tee.LinkTo(que2)
    que2.LinkTo(enc2)
    enc2.LinkTo(snk2)
 
    pl.Play()
 
End
 
Public Sub Form_Close()
 
  pl.Stop()
  pl.Close()
 
End

In allen früheren Gambas-Versionen < 3.19 werden Sie feststellen, dass die mp3-Datei nach Beendigung des Programms geleert wird. Dies ist auf einen Bug zurück zu führen, für den Sie folgende Workarounds einsetzen können:

Bitte beachten Sie, das GStreamer-Pipelines mit Buffern arbeiten, die u.U. ordentlich beendet werden müssen, um eine gültige Aufzeichnungsdatei zu erhalten. Es kann also notwendig sein weitere Maßnahmen zu ergreifen, die hier nicht weiter behandelt werden.

23.9.0.1.6 GStreamer-Message/Tag-Events mit Gambas verarbeiten

Einige GStreamer-Pipelines erzeugen Ereignisse, in denen Nachrichten/Informationen geliefert werden. Dieses Ereignisse lassen sich auch in Gambas abfangen und verarbeiten. Hierfür stellt die Gambas MediaPipeline die Ereignisse 'Message' und 'Tag' zur Verfügung. Eine entsprechende Deklarierung sieht beispielsweise so aus:

hPipeLine = New MediaPipeline As "PipeEvents"

Die Ereignisse können dann in entsprechenden Ereignis-Behandlungsroutinen abgefangen und verarbeitet werden. Für Messages sieht das beispielsweise so aus:

Public Sub PipeEvents_Event(Message As MediaMessage)
  Print "Message.Name: "; Message.Name
End

Die Namen der Messages entsprechen den Typen, die auf der GStreamer Webpräsenz beschrieben werden: https://gstreamer.freedesktop.org/documentation/additional/design/messages.html?gi-language=c .

Zum Abfangen von Media-Tags wie zum Beispiel aktueller Musik-Titel/Interpret im Webradio-Stream, kann folgende Ereignis-Behandlungsroutine als Grundlage dienen:

Public Sub PipeEvents_Tag(TagList As MediaTagList)
  For Each s As String In Taglist.Tags
      Print "Tag: "; s
  Next
End