Como acabamos de decir la programación multimedia tiene que ver con todo lo relacionado con distintos tipos de contenidos o información. La adquisición de los datos, su manipulación, su codificación para su posterior almacenamiento en diversos formatos de fichero o su transmisión en directo o en diferido, son todos elementos que de una forma u otra vamos a utilizar en una aplicación de este tipo.
Tratar todos y cada uno de estos conceptos en profundidad sería una
tarea bastante complicada, así que vamos a explorar el apasionante mundo
de la programación multimedia de la mano de la librería
gstreamer
, la cual nos va a permitir obviar algunos de los
detalles más complicados. Intentaremos daros, de todas formas,
suficiente información sobre esos temas de bajo nivel para que los más
curiosos podáis explorar en profundidad las áreas que más os interesen,
y probablemente, en casos concretos, nos adentraremos en un vertiginoso
descenso al averno del bajo nivel :).
Aunque todo el mundo tiene una idea aproximada de lo que es una aplicación multimedia, merece la pena clarificar algunos conceptos, especialmente porque vamos a abordar este tema desde el punto de vista de la programación y no como simples usuarios. Así que vamos a ponernos un poco en contexto antes de saltar de lleno en el código.
Las principales tareas relacionadas con cualquier aplicación multimedia se podrían resumir en estas tres categorías:
- Captura/Reprodución de información
- Codificación
- Almacenamiento/Transmisión
Captura/Reprodución de información.
Esta tarea implica la programación del hardware para poder obtener
imágenes y sonido. Por ejemplo, utilizar el interfaz v4l2
del kernel para adquirir imágenes de algún dispositivo de captura o
alsa
para adquirir audio. Esta parte del proceso está más
relacionada con la programación de sistemas y de hardware que con la
multimedia en si, si bien, cuando capturamos imágenes o sonido aplican
ciertas particularidades.
De la misma manera, otros sistemas y librerías nos permiten
reproducir la información que hemos capturado. Para el caso del sonido,
en muchas ocasiones el mismo subsistema se encarga de ambos procesos
(captura y reproducción) como por ejemplo cuando utilizamos
alsa
. Sin embargo, para el video, los sistemas suelen ser
muy diferentes y tendremos que vérnoslas con frame buffers,
Sistemas de Ventanas o Overlays Hardware.
gstreamer
soluciona toda esta complejidad por nosotros
si bien, necesitamos conocer los fundamentos de como funcionan estos
procesos para programar correctamente nuestra aplicación multimedia.
Codificación
La información que obtenemos del hardware se suele obtener en crudo (RAW), lo que significa que: los datos requieren muchísima memoria y el formato puede que no sea el más adecuado para la tarea que pretendemos solucionar con nuestro programa. Así que normalmente deberemos transformar esta información en formatos más convenientes.
Esta es la parte de la programación multimedia que se encarga del desarrollo de los famosos codecs. La palabra codec no es más que la contracción de las palabras Coder y Decoder puesto que el código para codificar y decodificar distintos formatos suele ser muy similar y suele venir en parejas.
Se trata de una parte complicada de la programación multimedia que requiere saber bastante de matemáticas y del funcionamiento del procesador a muy bajo nivel (para el caso de los codecs software) o de la arquitectura del procesador y sus componentes (para el caso de los codecs hardware). Este proceso suele implicar la compresión de los datos. El desarrollo de codecs como VP8/9, Theora o Vorbis caería en esta categoría, así como la programación de cualquier nuevo formato/codificación que pueda aparecer en el futuro.
gstreamer
nos ofrece implementaciones de prácticamente
cualquier codec que podamos imaginar con soporte para su
ejecución en hardware especial si es el caso. Una vez más entender los
fundamentos del funcionamiento de determinados codificadores nos va a
permitir ajustar sus parámetros para nuestra aplicación o ser capaces de
elegir el que mejor se adapte a nuestras necesidades.
Almacenamiento o Transmisión
Una vez codificados los datos habrá distintas cosas que queramos hacer con ellos. Normalmente los almacenaremos en un fichero o los enviaremos a través de la red. Para ambas cosas es necesario añadir información adicional a los datos que obtenemos de nuestros codecs, si queremos almacenarlos o transmitirlos de forma eficiente.
En muchos casos es necesario combinar más de una secuencia de datos en un mismo fichero y en ese caso hablamos de formatos contenedores que permiten almacenar múltiples secuencias de datos multimedias codificadas de distintas maneras. Pensad por ejemplo en un vídeo. La secuencia de imágenes puede estar codificada con Theora o H.264 y la de audio como Vorbis o mp4. Para almacenar el vídeo y la imagen juntos usamos un formato contenedor como Matrovska (.mkv) que nos permite almacenar distintos tipos de información y sincronizarla. Si todo esto os suena a chino, o simplemente os suena… estáis en el lugar correcto. Poco a poco iremos descubriendo los detalles de todos esos nombres raros.
Muchos de estos formatos contenedores no se limitan solo a vídeo y audio, permitiéndonos almacenar además texto (subtítulos por ejemplo), gráficos 3D o pequeños programas para añadir interactividad a la experiencia.
De igual forma, transmitir información multimedia por la red requiere
el uso de protocolos especiales para asegurar la fluidez del contenido y
si bien gstreamer
nos va a solucionar un montón de
problemas, es necesario escribir código adicional para integrar todos
esos elementos en una aplicación útil.
LA PROGRAMACIÓN MULTIMEDIA ES DIFÍCIL
Quizás hayas oído esta frase y te preguntes que tiene de especial todo esto…
Pues bien, la principal razón que hace que la programación multimedia sea complicada son sus características de tiempo real. Una aplicación multimedia se puede considerar, técnicamente, como una aplicación de tiempo real blando (Soft Real-Time).
Un sistema en tiempo real es aquel que tiene que cumplir ciertas restricciones temporales, o en otras palabras, se trata de sistemas en los que ciertas tareas se tienen que ejecutar en un determinado momento. Si el sistema no permite que una tarea se retarde (produciendo un error en ese caso), estaríamos ante un sistema de tiempo real duro o Hard Real-time.
En general, la gente piensa que tiempo real significa que hay que hacer las cosas muy rápido, pero las restricciones pueden ser de distintos tipos.
La primera vez que me hablaron de sistemas en tiempo real me pusieron el ejemplo de una central nuclear en la que cierto proceso debía activarse en un momento dado. Ese momento podía ser 2 o 3 días más tarde, no hay prisa, pero fuera lo que fuera lo que el proceso involucraba, tenía que ejecutarse en el momento justo que le toca, ni antes ni después. Como podéis ver, en este ejemplo, nuestro programa no tiene que ser super rápido, tenemos días para realizar todos los cálculos que queremos, pero si tiene que hacer lo que tenga que hacer en el momento preciso… Y esa es la razón fundamental de la complejidad de los sistemas en tiempo real.
Afortunadamente las aplicaciones multimedia, como os dijimos antes, se consideran sistemas de tiempo real blando, lo que hace las cosas más fáciles. En los sistemas de tiempo real blando, las tareas también tienen que ejecutarse con restricciones tempoirales (normalmente terminar en un determinado tiempo), pero si una determinada tarea no es capaz de cumplir las restricciones de tiempo asignado, esa tarea se descarta y aquí na’pasao nada.
Existe un tercer tipo en el que… bueno, el sistema hace lo que puede, pero si no es capaz de terminar la tarea en tiempo, pues bueno, se termina más tarde. Estos realmente no se pueden considerar sistemas de tiempo real, aunque esto es lo que la mayoría de la gente entiende por tiempo real. Este es el caso de los videojuegos… si tu hardware es potente, todo funciona bien, pero si no… el juego va lento y no pasa nada.
TIEMPO REAL EN APLICACIONES MULTIMEDIA
Para clarificar todo esto veamos un sencillo ejemplo.
Imaginemos un sistema de telefonía digital. En cada lado de la comunicación, capturamos el audio, lo codificamos de alguna forma para posteriormente enviarlo como paquetes que, una vez recibidos en el otro extremo se decodifican y se convierten en sonido. Bastante obvio verdad?.
Sin embargo, todo este proceso requiere de un tiempo y, como cualquier sistema de comunicaciones es susceptible de errores. La forma tradicional de lidiar con los errores es repitiendo la operación. Para el caso de la transmisión del audio en nuestro ejemplo, el paquete se puede corromper o perder, y aquí se nos plantean dos opciones.
La opción 1 es… REPETIR. Añadimos al protocolo una forma de confirmar la recepción de los paquetes, y si no recibimos esa confirmación reenviamos el último paquete hasta que llegue. Así es como funciona el protocolo TCP y es la forma de asegurar que los datos llegan a su destino. Pero el problema que tenemos aquí es que cada vez que se produzca un error (y se producen a menudo), acumularemos un retardo en nuestra transmisión, hasta que llegue un momento en el que el tiempo que pasa desde que hablamos hasta que nuestro interlocutor recibe el audio sea demasiado largo para poder mantener una conversación. Este es el caso de un sistema que no aplica restricciones de tiempo real. Esta opción nos vale, por ejemplo en un sistema de visualización de videos, donde nos podemos permitir esperar un ratito para hacer buffering, pero no es una opción viable para un sistema de comunicación bidireccional.
La opción 2 es… PASAR DE TODO. ¿Se ha perdido el paquete? bueno, ¿que le vamos a hacer?. En el receptor se perderá un pedacito del sonido, pero el resto de paquetes seguirán llegando a tiempo y, aunque ese cacho no lo hayamos entendido muy bien, podemos seguir hablando. Normalmente los paquetes son pequeños y, para el caso de audio esa pérdida de paquete se convierte en una sílaba perdida junto con un chasquido (lo que se llama un error de fase, un salto brusco en los valores de las muestras).
En general, además de todo esto, para poder hacer todo lo que tenemos
que hacer en un tiempo razonable, tenemos que hacerlo muy rápido, y esa
es otra de las complicaciones de la programación multimedia. Los
denominados codecs
son difíciles de programar de forma
eficiente y suelen requerir implementaciones en ensamblador o una
interacción muy cercana con el Hardware.
Así que resumiendo. Muchos sistemas multimedia presenta características de tiempo real. Afortunadamente se trata de tiempo real blando, pero la programación en tiempo real es complicada. Así son las cosas. Pero tranquila, al terminar esta serie vas a ser una crack de la programación multimedia.
GStreamer
En esta serie de artículos vamos a utilizar gstreamer
,
el cual, además de ser muy potente y flexible funciona en prácticamente
cualquier plataforma. Como siempre nos centraremos en su uso en sistemas
GNU/Linux, pero puedes encontrar gran cantidad de tutoriales sobre como
configurarlo para otras plataformas… Una vez configurado puedes seguir
el curso en la plataforma que quieras. Salvo la forma de compilar, la
mayoría del código debería funcionar sin apenas modificaciones.
gstreamer
esta formado de tres componentes
fundamentales:
- Una librería con los elementos principales para construir nuestra aplicaciones multimedia
- Un conjunto de plug-ins que implementan todo tipo de elementos que vamos a necesitar en nuestras aplicaciones
- Un conjunto de herramientas que nos resultarán muy útiles para escribir nuestros programas.
El sistema de plug-ins de gstreamer
clasifica los
plug-ins en tres categorías…. y si esto fuera un vídeo, en este momento
comenzaría a sonar Erio
Morricone.
Good
. EL BUENO. Estos son los plug-ins pata negra totalmente soportados y probados que van como un tiro.Bad
. EL MALO. Estos plug-ins funcionan bien, pero les falta algo, ya sea alguna opción, documentación, testing y a veces simplemente caen en esta categoría porque no se utilizan mucho.Ugly
. EL FEO. Estos plug-ins tienen buena calidad y funcionan correctamente, pero su distribución puede acarrear problemas, por ejemplo, de licencias, patentes ya sea con el propio plug-in o alguna librería que utiliza.
Los plug-ins nos ofrecen elementos que podremos utilizar en las
pipelines de gstreamer
. En este punto me tengo que
disculpar, pero habrá ciertas palabras que utilizaremos a menudo y que
no voy a traducir ya que creo que resultarán en mayor confusión y además
os complicará la tarea de buscar información online. Hablaremos de los
pipelines hasta la saciedad, y empezaremos en un rato, pero
primero vamos a configurar nuestro sistema.
INSTALACIÓN
gstreamer
es un sistema muy maduro y está disponible en
los repositorios de todas las distribuciones modernas. Con el fin de
explicaros que contiene cada paquete vamos a tomar como ejemplo la lista
de paquetes para distribuciones basadas en Debian. Si tenéis problemas
siempre podéis usar las instrucciones para Debian en un contender
docker
. Podéis encontrar más detalles sobre otras formas de
instalarlo en la página
oficial
El comando para instalar gstreamer
en Debian sería:
apt-get install
libgstreamer1.0-dev \ # Paquete de desarrollo principal
libgstreamer-plugins-base1.0-dev\# Paquete desarollo para elementos comunes
libgstreamer-plugins-bad1.0-dev\ # Paquete desarrollo Plugins Bad
gstreamer1.0-plugins-base # Elementos comunes
gstreamer1.0-plugins-good # Plug-ins Good
gstreamer1.0-plugins-bad # Plug-ins Bad
gstreamer1.0-plugins-ugly # Plug-ins ugly
gstreamer1.0-libav # Interfaz con ffmpeg/libav
gstreamer1.0-tools # Herramientas CLI
gstreamer1.0-x # Interfaz con X-Windows
gstreamer1.0-alsa # Interfaz con alsa
gstreamer1.0-gl # Interfaz con OpenGL
gstreamer1.0-gtk3 # Interfaz con GTK3
gstreamer1.0-qt5 # Interfaz con QT5
gstreamer1.0-pulseaudio # Interfaz con PulseAudio
Este es el conjunto de paquetes recomendado por la web oficial. Al
final podéis ver varios paquetes relacionados con la visualización de
imagen y reproducción de sonido, así como soporte para integrar
gstreamer
en GUIs como GTK o QT.
Otro paquete interesante es el que permite la integración con OpenCV
y así desarrollar aplicaciones de procesado de imagen y vídeo, usando
toda la potencia de gstreamer
para adquirir los datos.
Tras esta no-tan-corta introducción, vamos a empezar con la parte práctica e ir introduciendo los conceptos teóricos que necesitemos según haga falta.
REPRODUCTOR MULTIMEDIA EN LA LÍNEA DE COMANDOS
Antes de escribir ningún código, me gustaría presentaros a dos
herramientas clave de gstreamer
. La primera es
gst-launch-1.0
. Esta herramienta nos permite probar
pipelines fácilmente en la línea de comandos y, la verdad, se
pueden hacer cosas bastante interesantes simplemente usando esta
utilidad.
Vamos a comenzar con un simple ejemplo, un reproductor multimedia básico. Si, podemos hacer esto en una sola línea tal que así:
gst-launch-1.0 playbin uri=https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm
El parámetro uri
acepta exactamente eso…
URI
s, así que podéis reproducir cualquier fichero accesible
a través de la red o localmente (usando file:///
en lugar
de https://
). Es bastante alucinante eh?
Todo eso lo hace el elemento playbin
de
gstreamer
automáticamente. En unos minutos veremos como
usar ese elemento en nuestros propios programas y veréis que, para casos
sencillos como estos, es tan fácil como usar
gst-launch-1.0
.
La otra herramienta que tenemos que conocer es
gst-inspect-1.0
. Si la ejecutamos sin más nos listará todos
los plug-ins y elementos que cada uno de ellos proporciona. Además, si
le pasamos como parámetro el nombre de un elemento, nos ofrecerá todos
los detalles sobre las propiedades y señales que acepta ese componente.
Podéis probar:¨
gst-inspect-1.0 playbin
Y veréis que este componente nos ofrece un montón de propiedades para configurar diferentes aspectos de la reproducción de un determinado fichero.
Por el momento hemos estado usando muchos palabros: plug-ins, pipelines, elementos,… Es hora de dejar claro que representa cada una de esas palabras.
CONCEPTOS BÁSICOS
Para introducir todos esos conceptos vamos a crear un pipeline muy sencillo de ejemplo, para que sea más fácil entender todo esto.
>gst-launch-1.0 videotestsrc ! autovideosink
Está es quizás el pipeline más sencillo que podemos escribir, pero primero… ¿qué es un pipeline?
Un pipeline o tubería es la forma en la que
gstreamer
realiza el proceso multimedia, construye una
cadena de elementos que toman datos, hacen algo con ellos y luego los
pasan al siguiente elemento. Conceptualmente funcionan exactamente igual
que los pipes
en bash
.
Los pipelines
son un tipo especial de bin
que es como gstreamer
llama a los contenedores, o si lo
prefieres, aquellos elementos que pueden contener otros elementos. Así
que un pipeline
es un contenedor de objetos (un
bin
) que contiene distintos elementos y a veces los
trataremos de esta forma.
Cuando usamos gst-launch-1.0
(y algunas funciones C que
veremos más tarde), los elementos del pipeline se enlazan
usando !
(signo de admiración), que lo podemos ver como
equivalente al |
que usamos en la shell para enlazar
comandos uno tras otro.
ELEMENTOS
En nuestro ejemplo anterior estamos usando dos elementos:
videotestsrc
y autovideosink
Como os acabamos de decir, los elementos reciben datos y generan
datos de/para otros elementos. Esto lo hacen a través de
pads
o conectores.
Cada elemento tiene pads
de entrada que se conocen como
sink pads
o sumideros (ya que la información entra en
ellos) y pads
de salida que se conocen como
source pads
o fuentes ya que producen datos.
Algunos elementos solo tienen src pads
y su nombre suele
terminar en src
. videotestsrc
es uno de esos
elementos. Genera una imagen de test (una carta de ajuste), pero no
recibe datos de ningún otro elemento. Los elementos src
suelen estar al principio del pipeline ya que son los que producen los
datos para el resto del pipeline.
Los elementos que solo tienen pads
de entrada tienen
nombres terminados sink
. El elemento
autovideosink
es un elemento que solo tiene
pads
de entrada como podemos deducir por su nombre
terminado en sink
. Los elementos sink
terminan
el pipeline ya que, al no tener pads
de salida no pueden
seguir propagando los datos.
El elemento autovideosink
es capaz de elegir, a partir
de los datos que recibe, cual es el mejor sink
de video a
utilizar, si bien podemos decidir usar un sink
de vídeo
especifico si así lo deseamos:
gst-launch-1.0 videotestsrc ! aasink
En este caso estamos diciéndole a gstreamer
que use el
ASCII Art sink. También podéis probar cacasink
si
queréis colores.
Resumiendo. Un
pipeline
es un contenedor obin
que contieneelements
enlazados a través depads
que utilizan para pasar la información de unos elementos a otro siguiendo una determinada dirección. Losplugins
son, efectivamente, librerías dinámicas que proporcionan la implementación de loselements
.
PROPIEDADES
Como vimos anteriormente gst-inspect-1.0
nos permite
examinar todos los detalles de un determinado elemento (incluyendo
información sobre los pads
, pero sobre eso volveremos más
adelante). Si examinamos nuestro elemento videotestsrc
veremos un montón de propiedades que podemos modificar.
Por ejemplo podemos cambiar nuestra aburrida carta de ajuste por una divertida pelota que rebota en los bordes de la pantalla.
gst-launch-1.0 videotestsrc pattern=18 motion=0 ! autovideosink
Para cada elemento de nuestra pipeline podemos modificar sus propiedades, si bien, dependiendo de la naturaleza del elemento puede que no haya mucho que modificar.
Por ejemplo, el elemento autovideosink
solo proporciona
un conjunto básico de propiedades necesarias para todos los
sinks
… no hay mucho que modificar. Aunque la propiedad
sync
merece una mención especial.
Hablaremos en detalle sobre esto más adelante, pero a modo de
introducción os diremos que gstreamer
se encarga de la
sincronización entre los distintos componentes para que los datos
lleguen en el momento adecuado y podamos, por ejemplo, ver nuestra
película sin saltos aleatorios o como si fuera uno de los filmes de los
hermanos Lumiere. La propiedad sync
de los elementos
sinks
permite desactivar esta sincronización haciendo que
gstreamer
mueva los datos lo más rápido que pueda. Activar
esta propiedad es a veces útil durante la depuración.
NUESTRO PRIMER PROGRAMA
Con todo lo que hemos visto hasta el momento, estamos en condiciones
de poder escribir y entender nuestro primer programa
gstreamer
. Va a ser muy cortito, pero nos va a permitir
introducir los elementos básicos de todos los programas que veremos en
las siguientes entregas.
Este es el código completo:
#include <gst/gst.h>
#include <stdio.h>
#include <string.h>
int
(int argc, char *argv[])
main {
*pipeline;
GstElement *bus;
GstBus *msg;
GstMessage char cmd[2048];
(&argc, &argv);
gst_init
(cmd, 2048, "playbin uri=%s", argv[1]);
snprintf = gst_parse_launch (cmd, NULL);
pipeline
(pipeline, GST_STATE_PLAYING);
gst_element_set_state
= gst_element_get_bus (pipeline);
bus = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
msg | GST_MESSAGE_EOS);
GST_MESSAGE_ERROR
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
("ERROR!. Inicializa la variable de entorno GST_DEBUG=*:WARN"
g_error "para más detalles");
}
(msg);
gst_message_unref (bus);
gst_object_unref (pipeline, GST_STATE_NULL);
gst_element_set_state (pipeline);
gst_object_unref return 0;
}
Lo primero que debemos hacer es ejecutar gst_init
para
inicializar gstreamer
. Como podéis ver, le podemos pasar
los parámetros recibidos por la línea de comandos. Esto nos permite
procesar ciertos flags generales que podemos pasar a todas las
aplicaciones gstreamer
, pero por ahora simplemente nos
interesa inicializar la librería.
El PIPELINE
Tras eso construimos el pipeline. Como podéis ver está
declarado como un GstElement
, sí, los bins
también son elementos, aunque veremos más adelante como utilizar sus
características especiales.
(cmd, 2048, "playbin uri=%s", argv[1]);
snprintf = gst_parse_launch (cmd, NULL); pipeline
Como podéis ver estamos usando el elemento playbin
e
inicializando su propiedad uri
con el parámetro que
recibimos por la línea de comandos. De esta forma, nuestro pequeño
reproductor multimedia puede reproducir ficheros locales o remotos.
En las siguientes entregas veremos como construir nuestros
pipelines manualmente ya que en ciertas aplicaciones es
necesario hacerlo así. En casos sencillos como estos podemos utilizar la
función gst_parse_launch
que nos permite usar exactamente
las mismas secuencias que usamos con gst-launch-1.0
en la
línea de comandos. Prueba por ejemplo a sustituir cmd
con
nuestra videotetsrc ! autovideosink
pipeline.
Una vez que el pipeline está creado, es decir, todos los
elementos que lo componen han sido creados y enlazados sus
pads
de entrada y salida. Debemos decirle al
pipeline que los datos comiencen a fluir, desde los nodos
src
a los nodos sink
. Eso lo hacemos poniendo
el pipeline en estado `GST_STATE_PLAYING_.
Las pipelines pueden estar en varios estados, y las
transiciones entre ellos deben hacerse en un determinado orden. En este
caso tan sencillo, solo tenemos que activar el estado
PLAYING
. En próximas entregas veremos todos los detalles
sobre como controlar las transiciones entre estados.
El BUS
Una aplicación gstreamer
funciona en un bucle infinito,
en el cual, en cada ciclo, los datos se van moviendo de componente a
componente para que la información multimedia sea procesada de la forma
que hayamos decidido, es decir, con el pipeline que hayamos
proporcionado. Además, gstreamer
hace esto utilizando
múltiples thread que gestiona internamente y por tanto diferentes a la
hebra principal de nuestra aplicación (la que ejecuta nuestra función
main
).
Durante esos ciclos, los distintos elementos de la pipeline pueden generar errores (la conexión de red se ha cortado), eventos (el fichero se ha terminado) u otros tipos de señales que la aplicación principal necesita conocer para actuar en consecuencia.
La forma en la que esta información se envía a la aplicación
principal es usando un bus
. En nuestra aplicación de
ejemplo esto sucede en las siguientes dos líneas:
= gst_element_get_bus (pipeline);
bus = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
msg | GST_MESSAGE_EOS); GST_MESSAGE_ERROR
La primera línea nos permite obtener el bus asociado a nuestra pipeline.
La siguiente línea es la que implementa el bucle infinito. Esta
función repite el bucle principal del pipeline indefinidamente
(GST_CLOCK_TIME_NONE
este parámetro es un timeout que
significa no timeout) hasta que, o se produzca un error
GST_MESSAGE_ERROR
o el stream se haya consumido totalmente
(GST_MESSAGE_EOS
End Of Stream).
El segundo parámetro de gst_bus_timed_pop_filtered
es un
time-out que hará que la función retorne el control al programa
principal cada cierto tiempo. En este caso, nuestro programa principal
no hace nada, así que simplemente dejamos que gstreamer
haga su magia hasta que la peli termine o se produzca un error.
ERRORES
En una aplicación multimedia un montón de cosas pueden ir mal e iremos viendo ejemplos a lo largo de esta serie. En este ejemplo la gestión de errores es muy básica y se reduce a una sola línea:
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_ERROR) {
("ERROR!. Inicializa la variable de entorno GST_DEBUG=*:WARN"
g_error "para más detalles");
}
Como dijimos en la sección anterior,
gst_bus_timed_pop_filtered
solo retornará si se ha
terminado el fichero (mensaje GST_MESSAGE_EOS
) o en caso de
error. En cualquier caso retorna un mensaje, el cual podemos comprobar
en nuestra aplicación y actuar en consecuencia. La línea de código
anterior comprueba el tipo de mensaje, y si se trata de un error muestra
un mensaje en stderr
.
Para los más curiosos las variable de entornos GST_DEBUG
nos permite generar información de debug interna que es útil para la
depuración de pipelines más complejas. Acabaremos utilizándola más
adelante…
GESTIÓN DE MEMORIA
gstreamer
mantiene un contador de referencias para todos
los objetos que son creados por la librería. No existen funciones para
destruir los objetos directamente, sino que tenemos que de-referenciar
los objetos. Cuando el contador de referencias asociado a un determinado
objeto llega a cero, el objeto es destruido.
La parte final del código hace esto:
(msg);
gst_message_unref (bus);
gst_object_unref (pipeline, GST_STATE_NULL);
gst_element_set_state (pipeline); gst_object_unref
Observad que antes de de-referenciar el pipeline debemos
poner su estado a GST_STATE_NULL
lo cual le dice al
pipeline que libere los recursos que haya podido reservar
durante su operación). La llamada a gst_object_unref
al
final es la encargada de eliminar todos los elementos creados dentro de
la pipeline y la pipeline propiamente dicha.
gstreamer
hace un montón en lo referente a la gestión de
memoria, pero como podéis ver, siempre hay algunas acciones que dependen
de nosotros.
COMPILACIÓN
gstreamer
usa pkg-config
para encapsular
los parámetros de compilación, lo cual está genial ya que no tenemos que
preocuparnos de actualizar nuestros makefiles
con nuevas
versiones. La forma de compilar nuestro programa anterior sería:
gcc gs-01.c -o gs-01 `pkg-config --cflags --libs gstreamer-1.0`
Para los menos experimentados o los que no hayáis visto este tipo de
línea de compilación observad que las comillas entorno a
pkg-config
son las comillas inversas que significan:
“Añade aquí lo que sea que escriba en la consola el comando que te
paso”.
Y ahora ya podéis disfrutar de vuestro propio reproductor multimedia super básico:
./gs-01 https://gstreamer.freedesktop.org/data/media/sintel_trailer-480p.webm
CONCLUSIONES
En esta primera entrega os hemos contado los fundamentos de la programación multimedia y su relación con los sistemas en tiempo real lo cual es una de las razones por las que las programación multimedia puede resultar complicada.
Además hemos dado nuestros primeros pasos con gstreamer
introducido los elementos más básicos y creado una aplicación capaz de
reproducir varios tipos de elementos multimedia disponibles desde
distintas fuentes, incluyendo la red.
En la próxima entrega vamos a profundizar en nuevos conceptos de
gstreamer
y crear nuevas y emocionantes aplicaciones.
■