Con esto en mente voy a tratar de escribir una serie de pequeñas y simples guías que puedan servir para iniciarse con GStreamer y sus distintos "bindings", Python en esta ocasión. Como ya se ha mencionado por aquí antes, el lenguaje nativo de GStreamer es C. Este tutorial podría ser de especial interés para involucrarse con proyectos como Jokosher, PiTiVi o Elisa puesto que están, como muchos otros, escritos en Python usando el framework de GStreamer.
Los pre-requisitos son los siguientes:
Ya hemos hablado de GStreamer con anterioridad. Se trata de un framework multimedia que te permite crear, editar y reproducir archivos multimedia de forma sencilla, mediante la creación de pipelines con elementos multimedia especiales.
Su forma de trabajar es increiblemente sencilla. Simplemente, creas el pipeline que contiene un puñado de elementos que hacen que la operación multimedia sea una realidad. Los pipelines son muy muy parecidos a los "pipes" (|) de la línea de comandos de Linux/*BSD/UNIX. Así, por ejemplo, en una línea de comandos normal podrías introducir un comando como este:
user@pc:~$ ps ax | grep apache | wc -1
Lo que hace este comando es, en primer lugar, sacar un listado de procesos ("ps ax"). Este listado se pasará por el filtro de grep, que mostrará por la salida estándar stdout todas las líneas que contengan la palabra "apache". Y las líneas seleccionadas se pasan al comando "wc" que se encarga de contar cuantas son por el argumento "-l". En resumen, todo ese comando muestra cuántas instancias de Apache se están ejecutando en la máquina local.
Con este simple ejemplo se puede comprobar como cada comando está enlazado con el siguiente mediante el simbolo "|", y que la salida del comando a la izquierda del símbolo es la entrada que recibe el comando a la derecha. Es, a grandes rasgos, similar a como funciona GStreamer.
Con GStreamer se concatenan elementos juntos, y cada elemento hace algo en particular. Para demostrar esto, localiza un Ogg y guárdalo en alguna carpeta de tu disco duro, abre una terminal en ese directorio y ejecuta el siguiente comando:
user@pc:~$ gst-launch-0.10 filesrc location=somefile.ogg ! decodebin ! audioconvert ! alsasink
(se puede parar la reproducción en cualquier momento con la combinación de Control+C)
Al ejecutarlo, deberías de escuchar el sonido de la pista de audio. Veamos lo que ha pasado.
El comando "gst-launch-0.10" se usa como una especie de "máquina virtual" que ejecuta las pipelines de GStreamer. Es suficiente con pasar, en el comando, los elementos que se desean encadenar uno a uno, y cada elemento se enlaza con el siguiente mediante el símbolo "!", equivalente al "|" que vimos antes. El pipeline que acabamos de definir contiene unos cuantos elementos, veamos lo que hace cada uno:
Llegados a este punto es buen momento para que empieces a jugar con pipelines y a experimentar. Para ello, necesitas entender qué elementos están disponibles en tu sistema. Para ello, se puede ejecutar el siguiente comando:
user@pc:~$ gst-inspect-0.10
que mostrará una lista de todos los elementos disponibles, y puedes usar el comando para encontrar detalles sobre un elemento en concreto. Veamos, a modo de ejemplo, la información disponible sobre el elemento "filesrc":
user@pc:~$ gst-inspect-0.10 filesrc
Una vez sabido lo anterior, veamos más a fondo la terminología de GStreamer. Es fácil sentir cierta confusión hacia algunos términos como "caps" y "pads", por no mencionar "bins" o "ghost pads", pero no son tan fieros como los pintan.
Ya hemos visto lo que es una pipeline, y cómo los elementos están contenidos ahí. Cada elemento tiene cierto número de propiedades. Se trata de ajustes que se pueden aplicar a un elemento particular (como se hace con los reguladores de un amplificador de guitarra). Como ejemplo, el elemento de control de volumen (que ajusta el volumen de un pipeline) tiene propiedades como "volume" o "mute", que puede ser usado para silenciar el elemento. Al crear tus propios pipelines, será necesario configurar las propiedades en la mayor parte de los elementos.
Cada elemento tiene "enchufes" virtuales a través de los cuales los datos pueden entrar o salir, llamados "pads". Si se imagina que un elemento es como una caja negra que hace algo con la información que se le hace llegar, a la izquierda y a la derecha de la caja es de esperar que haya enganches donde acoplar el cable para proporcionar esa información a la caja. Esto es lo que hacen los "pads". La mayor parte de los elementos tienen un pad de entrada (que se conoce como "sink") y un pad de salida (llamado "src"). A la hora de la visualización mental, el pad de entrada es un agujero donde la información va cayendo, en este caso para ser procesada por el elemento, y el pad de salida es la fuente de donde mana la información procesada que recibirá el siguiente elemento en su pad "sink". Un poco ASCII Art, pero sería algo así:
Así pues, sabemos que tenemos pads, y que los datos fluyen a través de ellos desde el primer elemento del pipeline hasta el último, así que ahora es necesario hablar de "caps". Cada elemento tiene sus "caps" particulares ("caps" es el diminutivo de "capabilities") y esto define qué tipo de información puede recibir el elemento (como por ejemplo si es audio o video). Se puede imaginar las "caps" como un equivalente a las especificaciones de un cargador (el cargador de batería de un móvil o un portátil, por ejemplo) que definen que la electricidad que usan es de un determinado voltaje.
Hablemos ahora de los "bins". Aunque pueda parecer confuso, son muy simples. Se trata de un contenedor, un elemento muy conveniente para agrupar juntos elementos dentro de un contenedor. Por ejemplo, supongamos que tenemos unos cuantos elementos para decodificar un video y aplicarle ciertos efectos. Para hacerlo más fácil de manejar, podrías juntar todos estos elementos en un "bin" (que es como un contenedor) y a partir de ese momento referirte a ese "bin" para, por extensión, referirte a todos los elementos que contiene. De esta forma, tu "bin" se convierte en un elemento. Es decir, si creas una supuesta pipeline, a ! b ! c ! d, podrías incluir todos esos elementos en "mi-bin" y a partir de ese momento, al usar "mi-bin" es realidad estarías usando a ! b ! c ! d de forma transparente. Mola, eh?
Lo que nos lleva a hablar de "ghost pads". Cuando creas un "bin", un contenedor, y le metes un montón de elementos dentro, se convierte en tu elemento personalizado y que, por debajo, usa los elementos que contiene. Naturalmente, para poder hacer eso nuestro "mi-bin" necesita sus propios "pads" con los que conectar con los elementos de su interior. Y eso es exactamente lo que son los "ghost pads". Cuando creas tu "bin", creas tambien "ghost pads" a los que indicar a qué elementos del interior del "bin" van a conectarse.
Para que toda esta maravilla funcione con un script Python, solo se requieren unos pocos conocimientos previos, que son:
user@pc:~$ gst-launch-0.10 audiotestsrc ! alsasink
#!/usr/bin/python
import pygst
pygst.require("0.10")
import gst
import pygtk
import gtk
class Main:
def __init__(self):
self.pipeline = gst.Pipeline("mypipeline")
self.audiotestsrc = gst.element_factory_make("audiotestsrc", "audio")
self.pipeline.add(self.audiotestsrc)
self.sink = gst.element_factory_make("alsasink", "sink")
self.pipeline.add(self.sink)
self.audiotestsrc.link(self.sink)
self.pipeline.set_state(gst.STATE_PLAYING)
start=Main()
gtk.main()
Veamos como funciona el snippet que acabamos de escribir. Lo primero, es importar algunos módulos imprescindibles de Python:
import pygst
pygst.require("0.10")
import gst
import pygtk
import gtk
Los módulos de GStreamer (pygst y gst) son importados, así como los módulos gtk que utilizaremos para el mainloop (mainloop se refiere al proceso que ejecuta el código, y vamos a necesitar uno de algún tipo, por lo que, en este caso, usamos el de GTK).
Ahora creamos la clase Python y su constructor:
class Main:
def __init__(self):
Ahora viene lo gordo. Primero, la creación del pipeline:
self.pipeline = gst.Pipeline("mypipeline")
Hemos creado un pipeline al que podremos hacer referencia más adelante en nuestro script Python como "self.pipeline" y al que hemos dado el nombre de mypipeline. Este nombre se usará para los mensajes de error que puedan emitirse o para guardar un log de depuración (de lo que hablaremos más adelante).
Creemos el elemento:
self.audiotestsrc = gst.element_factory_make("audiotestsrc", "audio")
El elemento es el "audiotestsrc" del que hablamos al principio del ejercicio, creado a través del método element_factory_make(). Este método recibe dos argumentos, el nombre del elemento que se quiere crear y, de nuevo, un nombre con el que identificar esa instancia del elemento. A continuación, se añaden al pipeline:
self.pipeline.add(self.audiotestsrc)
Hemos usado el método add() que es parte del pipeline para añadir nuestro nuevo elemento. Hagamos lo mismo para el elemento "alsasink".
self.sink = gst.element_factory_make("alsasink", "sink")
self.pipeline.add(self.sink)
Una vez tenemos nuestros dos elementos en el pipeline, es hora de enlazar uno con otro:
self.audiotestsrc.link(self.sink)
Aquí cogemos el primero de los elementos (self.audiotestsrc) y usamos el método link() para enlazarlo con el otro elemento (self.link).
Finalmente, hagamos que el pipeline se ejecute:
self.pipeline.set_state(gst.STATE_PLAYING)
Usando el método set_state() del pipeline para llevarlo a un estado en concreto, en este caso el de reproducción. Hay unos cuantos estados distintos, como NULL, READY y PAUSED.
Finalmente, está el código que crea la instancia Main y la ejecuta:
start=Main()
gtk.main()
Para ejecutar el script al completo, lo hacemos ejecutable y lo invocamos desde el intérprete:
user@pc:~$ chmod a+x GStest-1.py
user@pc:~$ python ./GStest-1.py
Deberías estar escuchando el tono a través de los altavoces. Con la secuencia de escape Ctrl+C finalizaremos la ejecución.
Añadamos una línea al script de nuestro ejemplo para ajustar alguna propiedad a un elemento. Bajo la línea self.audiotestsrc = gst.element_factory_make("audiotestsrc", "audio") escribimos lo siguiente:
self.audiotestsrc.set_property("freq", 200)
Con el método set_property() es posible ajustar una propiedad en concreto de un elemento. En la linea de más arriba es la propiedad "freq" a la que le damos un valor de 200. Esta propiedad especifica a qué frecuencia se ha de reproducir el tono de "audiotestsrc". Con la nueva linea incluida, ejecuta el script de Python de nuevo. Juega con los valores, cambiando 200 por 400 y escuchando la diferencia. De nuevo, puede ser útil gst-inspect-0.10 para conocer más a fondo las propiedades disponibles para cada elemento.
Es posible cambiar las propiedades de un elemento mientras que el pipeline está en reproducción, lo que es increiblemente útil. Por ejemplo, es posible tener un control de volumen para poder ajustarlo mientras se reproduce una canción. Esto hace al pipeline interactivo cuando lo usamos desde un GUI.
Y como podemos usar todo esto desde una Interfaz Gráfica? De nuevo es algo sencillo. Esta sección da por sentado conocimientos sobre cómo integrar una interfaz Glade con Python.
La creación de la interfaz corre por vuestra cuenta (en el artículo se llama gui.glade). El script de Python sería el siguiente:
#!/usr/bin/python
import pygst
pygst.require("0.10")
import gst
import pygtk
import gtk
import gtk.glade
class Main:
def __init__(self):
# Create gui bits and bobs
self.wTree = gtk.glade.XML("gui.glade", "mainwindow")
signals = {
"on_play_clicked" : self.OnPlay,
"on_stop_clicked" : self.OnStop,
"on_quit_clicked" : self.OnQuit,
}
self.wTree.signal_autoconnect(signals)
# Create GStreamer bits and bobs
self.pipeline = gst.Pipeline("mypipeline")
self.audiotestsrc = gst.element_factory_make("audiotestsrc", "audio")
self.audiotestsrc.set_property("freq", 200)
self.pipeline.add(self.audiotestsrc)
self.sink = gst.element_factory_make("alsasink", "sink")
self.pipeline.add(self.sink)
self.audiotestsrc.link(self.sink)
self.window = self.wTree.get_widget("mainwindow")
self.window.show_all()
def OnPlay(self, widget):
print "play"
self.pipeline.set_state(gst.STATE_PLAYING)
def OnStop(self, widget):
print "stop"
self.pipeline.set_state(gst.STATE_READY)
def OnQuit(self, widget):
gtk.main_quit()
start=Main()
gtk.main()
Con este script básicamente creamos nuestro pipeline en el constructor (así como el código necesario para presentar la GUI). A continuación tenemos unos pocos métodos de clase diferentes, para gestionar los clicks en los botones de la GUI por parte del usuario. Por ejemplo, los botones de Play y Stop ejecutarían los métodos de clase que a su vez ajustarían el estado del pipeline bien a PLAYING o bien a READY, respectivamente.
La depuración siempre es importante cuando las cosas van mal. Hay dos técnicas muy útiles para poder echar un vistazo sobre lo que está ocurriendo en las pipelines del interior de un programa GStreamer. Primero, has de conocer como generar un registro con el resultado de la ejecución para depurar (debug): mediante el uso de algunas variables de entorno previas a la ejecución del programa. A modo de ejemplo, para hacer correr el programa anterior y generar un registro de debug llamado "log", ejecutaríamos el siguiente comando:
user@pc:~$ GST_DEBUG=3,python:5,gnl*:5 ./GStest-1.py > log 2>&1
Lo que nos daría como resultado un registro que deberías chequear. Incluidos en el archivo están códigos ANSI para colorear cada linea y hacer más fácil la tarea de localizar errores, avisos u otra información. Podemos emplear la herramienta de línea de comandos "less" para ver el archivo manteniendo esos colores:
user@pc:$ less -R log
Mencionará que es un archivo binario y te preguntará si deseas verlo. Pulsando "y" tendremos el registro, que dirá qué elementos han sido creados y cómo han sido enlazados entre ellos.
Hasta aquí por hoy, una introducción rápida al uso de GStreamer con Python. Hay, por supuesto, mucho más, pero este artículo pretende ser introductorio. Usa los comentarios de este post para dar ideas, sugerencias o dudas. Intentaré responder tantas como pueda, o quizás otros usuarios lo hagan antes.
Hola.
Estoy haciendo mis primeras incursiones en gstreamer y me ha resultado muy provechoso tu artículo.
Muchas gracias por todo. Saludos,
José Luis
Che, muy buen artículo. Si bien es una introducción, me agrada la claridad con la que fue explicado.
Soy programador de C, pero me sirvió para entender la idea.
Me llama la atención que nadie halla posteado nada. En fin. Muchas gracias!
¿Sabes si gstreamer va a ser portado a plataformas moviles? Ando buscando algo que sea capaz de reproducir video en cualquier plataforma desde python... muy importantes iphone y nokia tanto como mac windows y linux.