Es muy sencillo construirse un reproductor/codificador/decodificador multimedia usando Gstreamer y Python. Aquí, haremos un reproductor de Ogg Vorbis básico.

Ingredientes

  • python-gst0.10

La instalación, como ya sabrás, es muy compleja y tediosa, pero no hay mas remedio que tragar :-P :

 # apt-get install python-gst0.10

Introducción

Antes de empezar a escribir código, unas breves líneas de como rula Gstreamer. Gstreamer es básicamente un framework para la manipulación multimedia. Con él se puede hacer casi de todo, desde un simple reproductor de Ogg Vorbis (como aqui veremos) hasta complejas aplicaciones distribuidas de streaming multimedia. Todo depende de los modulos y plugins de que dispongamos.

Gstreamer funciona como un flujo de datos dentro de una tubería. Se selecciona una fuente de datos, un camino para estos (en el que se pueden modificar o no) y un destino. Podemos manipular los eventos que se produzcan en la tubería, controlar su estado e incluso inyectar más datos a nuestro antojo. Interiormente, en la tubería, los datos también pueden seguir diferentes caminos. Por ejemplo, el vídeo puede ir por un lado, mientras que el audio procesarse por otro.

En Gstreamer, todas las partes que usamos para construir la tubería (la fuente, el destino, los codificadores, etc.) se llaman “Elementos”, que tienen ‘enchufes’ (Pads). Entonces, cuando creamos la tubería conectamos unos enchufes con otros, para que el flujo siga su curso. Y estos Pads, tienen establecido un sentido para el flujo de datos: de salida (llamados Source) y de entrada (llamados Sink). Cada elemento de Gstreamer puede tener uno o varios pads, o ninguno (e incluso, es muy común que los pads se creen dinámicamente, segun sean necesarios).

Por ejemplo, podemos crear una tubería con una fuente que lea de un fichero de audio en Ogg Vorbis, más un demultiplexor para el envoltorio del Vorbis (es decir, un elemento que extraiga del Ogg los datos
codificados con Vorbis), más un decodificador de Vorbis, por supuesto. Además necesitamos un conversor de audio y por último, un destino, que podría ser por ejemplo ALSA. Simplemente con esto, nos construimos un reproductor de Ogg/Vorbis, que es lo que vamos a hacer.

Manos a la obra

Pues bien, como todo lo vamos a hacer en Python, lo primero que necesitamos es importar los modulos que vamos a usar. Es decir:

  • gst: evidentemente, y además en su version 0.10, con lo que también importaremos pygst para cargarlo.
  • sys: es muy común.
  • gobject: porque lo usa Gstreamer.

Con lo que tendríamos:

import pygst  
pygst.require("0.10")
import sys, gst, gobject
gobject.threads_init()  

Bien, ahora creamos una función que haga las veces de main, y comprobamos que tenemos los argumentos suficientes:

def main(args):
    
    if len(args) != 2:
        print "Uso: " + args[0] + " <archivo Ogg>"
        return -1

Hasta ahí nada nuevo. Ahora tenemos que crear la tubería. Para esto hay diversas maneras. Podemos crear cada elemento por separado, enlazar los Pads correspondientes y después meterlos todos en un contenedor. De esta forma, tenemos un mejor control sobre lo que hacemos, pero existe una herramienta con la que es mucho más sencillo todo. Se llama ‘parse_launch’. Acepta una cadena con la estructura final de nuestra tubería, y nos devuelve la tubería hecha. Así, para nuestra tubería, la cadena que tendríamos que formar es:

    pipestr = "filesrc location= %s ! oggdemux ! vorbisdec ! audioconvert ! alsasink" % args[1]

Esta cadena contiene cada elemento (con el nombre por el cual está definido) más algun atributo de ese elemento, separados con el símbolo de admiración (!). ‘filesrc’ es el elemento que se encarga de leer de un fichero del disco, y con la propiedad ‘location’ establecemos la ruta al fichero que se tiene que leer; ‘oggdemux’ es el demultiplexor de Ogg, etc.

Pues si le pasamos esta cadena a parse_launch, intentará crearnos la tubería correspondiente, y además completamente funcional.

    try:
        pipeline = gst.parse_launch(pipestr)
    except gobject.GError, e:
        print "No es posible crear la tuberia, " + str(e)
        return -1

Ahora sería útil añadir un pequeño manipulador de eventos, ya que si se produce algún mensaje dentro de la tubería, debemos gestionarlo nosotros. En esta ocasión, solo haremos caso de dos mensajes: EOF, que
nos indica que debemos terminar (End Of Stream, o fin de flujo) y ERROR, que también nos obliga a terminar, pero advirtiendo de que se produjo un error interno. El resto lo ignoramos. Para ello crearemos una mini función interna que lo gestione todo.

    def eventos(bus, msg):
        t = msg.type
        if t == gst.MESSAGE_EOS: 
            loop.quit()
            
        elif t == gst.MESSAGE_ERROR:
            e, d = msg.parse_error()
            print "ERROR:",e
            loop.quit()
        
        return True    

Y la añadimos al bus de comunicaciones que tiene la tubería.

    pipeline.get_bus().add_watch(eventos)

Ya tenemos la tubería completamente creada. Ahora sólo nos queda establecer el estado de la misma. El estado de la tubería especifica que es lo que pasa con el flujo dentro de ella. Disponemos básicamente de NULL, READY, PLAYING y PAUSED. El estado inicial de la tubería es NULL, y si queremos que empieze a funcionar, tendremos que cambiarlo
a PLAYING. Una vez ahí, el flujo de datos puede fluir por todos los elementos.

    pipeline.set_state(gst.STATE_PLAYING)

¿Qué pasa? ¿No debería sonar ya? Hombre, pues no exactamente. Todavía nos queda un pequeño detalle. Como antes dije, Gstreamer usa Gobject, y por tanto, todos sus elementos bailan al son de Gboject. Por ello, es necesario crear un MainLoop y ponerlo en marcha. También sería bueno poder salir de una manera adecuada cuando se pulsa Ctrl+C.

    loop = gobject.MainLoop()
    try:
        print "Reproduciendo..."    
        loop.run()
        
    except KeyboardInterrupt: # Por si se pulsa Ctrl+C
        pass
    
    print "Parando... \n¡Adios!"

Como nota, he de decir que la función ‘run’ de gobject es un bucle de eventos, es decir, el programa se quedará en ese punto hasta que ‘run’ retorne. En nuestro caso, eso sucede cuando se pulsa Ctrl+C o cuando se produce un evento EOS o ERROR (ya que entonces se llama a ‘quit’ de Gobject).

Ya esta sonando todo… bien. Ahora solo nos queda decidir que hacer cuando termine el flujo o se pulse Ctrl+C. Lo lógico es poner la tubería en estado NULL y salir. Eso haremos.

    pipeline.set_state(gst.STATE_NULL)
    return 0

Bien, pues ya tenemos nuestra función principal. Yo añado un par de lineas para que se llame a esa función principal cuando el script se ejecuta como tal, y no cuando se importa como módulo, pero esto es a gusto del consumidor ;-)

if __name__ == "__main__":
    sys.exit(main(sys.argv))

¡Y ya está! Así de sencillo ;)

Comentarios

Como ves, es muy fácil. Casi todo el codigo necesario es para manipular eventos o parte del programa principal. La esencia de todo radica en la cadena de texto que se pasa a ‘parse_launch’. Si cambiamos los elementos, creamos tuberías diferentes. Por ejemplo, en vez de dirigir la salida a ALSA, la puedes dirigir a un fichero (con filesink). Esto lo dejo para tu imaginación ;-p . Gstreamer es muy potente, así que con poca cosa se pueden hacer cosas muy interesantes… ¡que te diviertas!

Todo junto

Para que no tengas que copiar y pegar muchas veces, aquí dejo todo el codigo del programa completo, todo juntito, ¡testeado, funcionando y todo!

import pygst  
pygst.require("0.10")
import sys, gst, gobject
gobject.threads_init()  

def main(args):
    
    if len(args) != 2:
        print "Uso:", args[0], " <archivo Ogg>"
        return -1

    pipestr = "filesrc location= %s  ! oggdemux ! vorbisdec ! audioconvert ! alsasink" % args[1]

    try:
        pipeline = gst.parse_launch(pipestr)
    except gobject.GError, e:
        print "No es posible crear la tubería,", str(e)
        return -1

    def eventos(bus, msg):
        t = msg.type
        if t == gst.MESSAGE_EOS: 
            loop.quit()
            
        elif t == gst.MESSAGE_ERROR:
            e, d = msg.parse_error()
            print "ERROR:", e
            loop.quit()
        
        return True    

    pipeline.get_bus().add_watch(eventos)

    pipeline.set_state(gst.STATE_PLAYING)

    loop = gobject.MainLoop()
    try:
        print "Reproduciendo..."    
        loop.run()
    except KeyboardInterrupt: # Por si se pulsa Ctrl+C
         pass
    
    print "Parando... \n¡Adios!"

    pipeline.set_state(gst.STATE_NULL)
    return 0

if __name__ == "__main__":
    sys.exit(main(sys.argv))

Referencias

Más:



blog comments powered by Disqus