Flumotion est un projet de serveur de streaming vidéo open-source distribué sous licence GPL. Développé en Python et basé sur les frameworks Twisted et GStreamer, il permet de diffuser sur un réseau des vidéos venant de sources lives (caméra, tv…) ou stockées dans des fichiers (on parle alors de VoD pour Video à la demande) en proposant un interface utilisateur de type Web (vous pouvez voir une démo ici).
Étiquette : gstreamer
J’aime la musique, elle m’accompagne tout au long de la journée. Quelle soit rock, pop, classique, électronique peut importe… tant que le son est bon ! Nous sommes à un tournant au niveau des supports de diffusion de la musique au grand public. Le bon vieux CD laisse petit à petit sa place aux fichiers informatiques. Au revoir support physique et bienvenue au monde dématérialisé ou les frontières en terme de qualité sont repoussées aux limites de nos oreilles.
Nous allons voir dans ce billet quels sont les impacts de cette petite révolution sur le rendu final en nous focalisant sur des logiciels et des formats libre.
Nous allons dans ce billet essayer d’optimiser le streaming d’un flux SD sur un réseau local (LAN de 100 Mbps) en utilisant le framework GStreamer.
Environnement des tests
Deux PC Ubuntu connectés sur un même switch (100 Mbps full-duplex).
- PC serveur: Intel(R) Core(TM)2 Duo CPU P8400 @ 2.26GHz / 2 Go RAM
- PC client: Intel(R) Core(TM)2 Duo CPU E6750 @ 2.66GHz / 1 Go RAM
GStreamer version 0.10.25.
Vidéo source: Big Buck Bunny 480p
Tests avec le codec X.264
Ligne de commande sur la machine générant le streaming (serveur):
serveur> gst-launch -v \ filesrc location= »../Vidéos/big_buck_bunny_480p_stereo.ogg » \ ! queue ! decodebin \ ! queue ! videoscale method=1 ! video/x-raw-yuv,width=854,height=480 \ ! queue ! videorate ! video/x-raw-yuv,framerate=\(fraction\)24/1 \ ! queue ! x264enc byte-stream=true bitrate=2000 bframes=4 ref=4 me=hex subme=4 weightb=true threads=0 \ ! queue ! rtph264pay \ ! queue ! udpsink port=5000 host=192.168.29.150 sync=false async=false
Ligne de commande sur la machine recevant le streaming (client):
client> gst-launch -v udpsrc caps= »application/x-rtp, media=\(string\)video, clock-rate=\(int\)90000, encoding-name=\(string\)H264, payload=\(int\)96″ port=5000 \
! queue ! rtph264depay \
! queue ! ffdec_h264 ! xvimagesink
Résultat:
Visuel: vidéo saccadé (environ 2 img/sec)
Bande passante mesurée: entre 2 et 3 Mbps
Resource serveur: %CPU=135 / %MEM=5
Resource client: %CPU=10 / %MEM=2
On ajoute un buffer juste avant le depay et le décodage (au niveau du client):
client> gst-launch -v udpsrc caps= »application/x-rtp, media=\(string\)video, clock-rate=\(int\)90000, encoding-name=\(string\)H264, payload=\(int\)96″ port=5000 \
! queue ! gstrtpjitterbuffer latency=3000 \
! queue ! rtph264depay \
! queue ! ffdec_h264 ! xvimagesink
Résultat:
Visuel: vidéo beaucoup plus fluide mais variation de la gigue (accéleration de la video par moment). On a par contre un décalage de 3 secondes, donc inutilisable pour des flux lives.
Bande passante mesurée: entre 2 et 3 Mbps
Resource serveur: %CPU=140 / %MEM=6
Resource client: %CPU=14 / %MEM=2
On modifie ensuite les paramètres d’encodage X.264 (au niveau du serveur):
serveur> gst-launch -v –gst-debug-level=2 \
filesrc location= »../Vidéos/big_buck_bunny_480p_stereo.ogg » \
! queue ! decodebin \
! queue ! videoscale method=1 ! video/x-raw-yuv,width=720,height=480 \
! queue ! videorate ! video/x-raw-yuv,framerate=\(fraction\)24/1 \
! queue ! x264enc vbv-buf-capacity=3000 byte-stream=true bitrate=900 subme=4 ref=2 bframes=1 b-pyramid=true weightb=true \
! queue ! rtph264pay \
! queue ! udpsink port=5000 host=192.168.29.150 sync=false async=false
Résultat:
Visuel: Presque plus de sacade ni de variation de gigue. On a par contre un décalage de 3 secondes, donc inutilisable pour des flux lives.
Bande passante mesurée: entre 2 et 3 Mbps
Resource serveur: %CPU=120 / %MEM=4
Resource client: %CPU=10 / %MEM=2
GStreamer et le codec X.264
Souhaitant effectuer un streaming RTP à partir d’une Webcam, voici un billet sur l’optimisation des paramètres du codec libre X.264 (implémentation libre du codec H.264 ou MPEG-4 AVC – WIKI) dont l’équipe de VideoLAN est à l’origine. Pour tester ce codec et ensuite mettre en place le streaming, j’utiliserai le framework GStreamer.
Edit: j’ai ajouté un script SHELL « qui fait tout pour vous » en fin de billet…
Voici une petite procédure pour compiler la dernière version du framework multimédia GStreamer tout en préservant la version installée depuis les dépôts officiels. La procédure a été validé sur une GNU/Linux Ubuntu 9.04 mais doit facilement être adaptable à d’autres distribution (pour une procèdure équivalente sous Mac OS X, vous pouvez lire ce billet).
GStreamer étant un framework, il se base sur de nombreuses librairies externes. Pour nous simplifier la tache, nous allons utiliser les dépôts pour l’installation de ces librairies:
sudo aptitude build-dep gstreamer0.10-ffmpeg gstreamer0.10-plugins-bad gstreamer0.10-plugins-bad-multiverse gstreamer0.10-plugins-base gstreamer0.10-plugins-good gstreamer0.10-plugins-ugly gstreamer0.10-plugins-ugly-multiverse bison flex git
Ensuite on récupère les dernières versions disponibles de GStreamer et de ses plugins:
mkdir ~/src
cd ~/src
wget http://gstreamer.freedesktop.org/src/gstreamer/gstreamer-0.10.25.tar.gz
wget http://gstreamer.freedesktop.org/src/gst-plugins-base/gst-plugins-base-0.10.25.tar.gz
wget http://gstreamer.freedesktop.org/src/gst-plugins-bad/gst-plugins-bad-0.10.17.tar.gz
wget http://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-0.10.17.tar.gz
wget http://gstreamer.freedesktop.org/src/gst-plugins-ugly/gst-plugins-ugly-0.10.13.tar.gz
wget http://gstreamer.freedesktop.org/src/gst-ffmpeg/gst-ffmpeg-0.10.8.tar.gz
On commence par la compilation de GStreamer (core). L’installation se fera dans le répertoire /opt/gstreamer/gstreamer-0.10.24:
tar zxvf gstreamer-0.10.25.tar.gz
cd gstreamer-0.10.25
./configure –prefix=/opt/gstreamer/gstreamer-0.10.25
make
sudo make install
cd ..
… puis les plugins « base »:
tar zxvf gst-plugins-base-0.10.25.tar.gz
cd gst-plugins-base-0.10.25
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-0.10.25/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-0.10.25
make
sudo make install
cd ..
… puis les plugins « good »:
tar zxvf gst-plugins-good-0.10.17.tar.gz
cd gst-plugins-good-0.10.17
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-0.10.25/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-0.10.25
make
sudo make install
cd ..
… puis les plugins « bad »:
tar zxvf gst-plugins-bad-0.10.17.tar.gz
cd gst-plugins-bad-0.10.17
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-0.10.25/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-0.10.25
make
sudo make install
cd ..
… les plugins « ugly »:
tar zxvf gst-plugins-ugly-0.10.13.tar.gz
cd gst-plugins-ugly-0.10.13
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-0.10.25/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-0.10.25
make
sudo make install
cd ..
… et enfin les plugins FFMPEG (streaming):
tar zxvf gst-ffmpeg-0.10.8.tar.gz
cd gst-ffmpeg-0.10.8
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-0.10.25/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-0.10.25
make
sudo make install
cd ..
Puis on créé un lien symbolique entre le répertoire /opt/gstreamer/current et /opt/gstreamer/gstreamer-0.10.25. Ce lien nous permet d’avoir plusieur version de GStreamer sur notre système.
sudo cp /opt/gstreamer/gstreamer-0.10.25/lib/gstreamer-0.10.25/* /opt/gstreamer/gstreamer-0.10.25/lib/
sudo ln -s /opt/gstreamer/gstreamer-0.10.25/lib /opt/gstreamer/current
On teste enfin l’installation:
/opt/gstreamer/gstreamer-0.10.25/bin/gst-inspect –gst-plugin-path=/opt/gstreamer/current
Pour les plus faineant, voici un script sheel automatisant ces quelques taches
[shell]
#!/bin/sh
# A simple script to get/compile/install GStreamer
# Nicolas Hennion – GPL
#
# Remarks: the version will be installed in the /opt/gstreamer folder
# Change this to the latest version
GST_CORE=0.10.25
GST_BASE=0.10.25
GST_GOOD=0.10.17
GST_BAD=0.10.17
GST_UGLY=0.10.13
GST_FFMPEG=0.10.8
# Do not edit under this line
sudo aptitude build-dep gstreamer0.10-ffmpeg gstreamer0.10-plugins-bad gstreamer0.10-plugins-bad-multiverse gstreamer0.10-plugins-base gstreamer0.10-plugins-good gstreamer0.10-plugins-ugly gstreamer0.10-plugins-ugly-multiverse
sudo aptitude install bison flex git
if [ ! -e gstreamer-$GST_CORE.tar.gz ]
then
wget http://gstreamer.freedesktop.org/src/gstreamer/gstreamer-$GST_CORE.tar.gz
fi
if [ ! -e gst-plugins-base-$GST_BASE.tar.gz ]
then
wget http://gstreamer.freedesktop.org/src/gst-plugins-base/gst-plugins-base-$GST_BASE.tar.gz
fi
if [ ! -e gst-plugins-good-$GST_GOOD.tar.gz ]
then
wget http://gstreamer.freedesktop.org/src/gst-plugins-good/gst-plugins-good-$GST_GOOD.tar.gz
fi
if [ ! -e gst-plugins-bad-$GST_BAD.tar.gz ]
then
wget http://gstreamer.freedesktop.org/src/gst-plugins-bad/gst-plugins-bad-$GST_BAD.tar.gz
fi
if [ ! -e gst-plugins-ugly-$GST_UGLY.tar.gz ]
then
wget http://gstreamer.freedesktop.org/src/gst-plugins-ugly/gst-plugins-ugly-$GST_UGLY.tar.gz
fi
if [ ! -e gst-ffmpeg-$GST_FFMPEG.tar.gz ]
then
wget http://gstreamer.freedesktop.org/src/gst-ffmpeg/gst-ffmpeg-$GST_FFMPEG.tar.gz
fi
sudo mkdir /opt/gstreamer
if [ ! -e gstreamer-$GST_CORE ]
then
tar zxvf gstreamer-$GST_CORE.tar.gz
cd gstreamer-$GST_CORE
./configure –prefix=/opt/gstreamer/gstreamer-$GST_CORE
make
sudo make install
cd ..
fi
if [ ! -e gst-plugins-base-$GST_BASE ]
then
tar zxvf gst-plugins-base-$GST_BASE.tar.gz
cd gst-plugins-base-$GST_BASE
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-$GST_CORE/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-$GST_CORE
make
sudo make install
cd ..
fi
if [ ! -e gst-plugins-good-$GST_GOOD ]
then
tar zxvf gst-plugins-good-$GST_GOOD.tar.gz
cd gst-plugins-good-$GST_GOOD
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-$GST_CORE/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-$GST_CORE
make
sudo make install
cd ..
fi
if [ ! -e gst-plugins-bad-$GST_BAD ]
then
tar zxvf gst-plugins-bad-$GST_BAD.tar.gz
cd gst-plugins-bad-$GST_BAD
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-$GST_CORE/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-$GST_CORE
make
sudo make install
cd ..
fi
if [ ! -e gst-plugins-ugly-$GST_UGLY ]
then
tar zxvf gst-plugins-ugly-$GST_UGLY.tar.gz
cd gst-plugins-ugly-$GST_UGLY
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-$GST_CORE/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-$GST_CORE
make
sudo make install
cd ..
fi
if [ ! -e gst-ffmpeg-$GST_FFMPEG ]
then
tar zxvf gst-ffmpeg-$GST_FFMPEG.tar.gz
cd gst-ffmpeg-$GST_FFMPEG
PKG_CONFIG_PATH=/opt/gstreamer/gstreamer-$GST_CORE/lib/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig ./configure –prefix=/opt/gstreamer/gstreamer-$GST_CORE
make
sudo make install
cd ..
fi
sudo cp /opt/gstreamer/gstreamer-$GST_CORE/lib/gstreamer-$GST_CORE/* /opt/gstreamer/gstreamer-$GST_CORE/lib/
sudo ln -s /opt/gstreamer/gstreamer-$GST_CORE/lib /opt/gstreamer/current
/opt/gstreamer/gstreamer-$GST_CORE/bin/gst-inspect –gst-plugin-path=/opt/gstreamer/current
[/shell]
Speex (wiki / site officiel) est un codec audio libre (licence dérivée de BSD) dont les perfomances sont très intéressantes, surtout pour transporter un signal comme la voix humaine. Le but de ce billet est de tester ce codec dans le cadre d’une application de streaming audio.
Mise en place du test
Pour tester ce codec, j’utilise le framework GStreamer et deux scripts shell tournant sur deux machines différentes. Le script rtpserver.sh prend comme source le microphone par défaut de votre machine, encode le flux en Speex, puis le diffuse en RTP/UDP vers la seconde machine. Le script rtpclient.sh récupére le flux RTP/UDP venant de la première machine, le décode et le diffuse sur la sortie son par défaut.
Le script rtpserver.sh:
#!/bin/sh
SEND_TO_ADDR=192.168.29.111
SEND_TO_RTP_PORT=5003
SEND_TO_RTCP_PORT=5004
RECV_FROM_RTCP_PORT=5005ENCODER=speexenc
PAYLOADER=rtpspeexpaySPEEX_PARAMS= »quality=4 vad=true dtx=true »
SPEEX_CAPS= »audio/x-raw-int,rate=16000″ENCODER_PARAMS=${SPEEX_PARAMS}
RTP_PARAMS= »latency=200″
AUDIO_CAPS=${SPEEX_CAPS}gst-launch -v gstrtpbin name=rtpbin ${RTP_PARAMS} \
alsasrc \
! queue ! audioresample ! ${AUDIO_CAPS} ! ${ENCODER} ${ENCODER_PARAMS} ! ${PAYLOADER} \
! rtpbin.send_rtp_sink_1 \
rtpbin.send_rtp_src_1 ! udpsink port=${SEND_TO_RTP_PORT} host=${SEND_TO_ADDR} \
rtpbin.send_rtcp_src_1 ! udpsink port=${SEND_TO_RTCP_PORT} host=${SEND_TO_ADDR} sync=false async=false \
udpsrc port=${RECV_FROM_RTCP_PORT} ! rtpbin.recv_rtcp_sink_1
Le script rtpclient.sh:
#!/bin/sh
SEND_TO_RTCP_PORT=5005
RECV_FROM_ADDR=192.168.29.146
RECV_FROM_RTP_PORT=5003
RECV_FROM_RTCP_PORT=5004
AUDIORATE=16000gst-launch-0.10 -v gstrtpbin name=rtpbin latency=200 \
udpsrc caps= »application/x-rtp, media=(string)audio, clock-rate=(int)${AUDIORATE}, encoding-name=(string)SPEEX, encoding-params=(string)1, ssrc=(guint)419764010, payload=(int)110, clock-base=(guint)3478167082, seqnum-base=(guint)57894″ port=${RECV_FROM_RTP_PORT} \
! rtpbin.recv_rtp_sink_1 \
rtpbin. ! rtpspeexdepay ! decodebin ! alsasink \
udpsrc port=${RECV_FROM_RTCP_PORT} ! rtpbin.recv_rtcp_sink_1 \
rtpbin.send_rtcp_src_1 ! udpsink port=${SEND_TO_RTCP_PORT} host=${RECV_FROM_ADDR} sync=false async=false
Attention, il faut lancer le script rtpclient.sh avant le rtpserver.sh . Les variables suivantes sont modifiables dans les scripts:
Script rtpserver.sh:
SEND_TO_ADDR: Adresse IP du client
SEND_TO_RTP_PORT: Numéro de port sur lequel les paquets RTP seront envoyés
SEND_TO_RTCP_PORT: Numéro de port sur lequel les paquets RTCP seront envoyés
RECV_FROM_RTCP_PORT: Numéro de port sur lequel les paquets RTCP seront reçus
SPEEX_PARAMS: Paramètres du plugin d’encodage Speexenc
SPEEX_CAPS: Caps du flux audio (audio rate)
Script rtpclient.sh:
SEND_TO_RTCP_PORT: Numéro de port sur lequel les paquets RTCP seront envoyés
RECV_FROM_ADDR: Adresse IP du serveur
RECV_FROM_RTP_PORT: Numéro de port sur lequel les paquets RTP seront reçus
RECV_FROM_RTCP_PORT: Numéro de port sur lequel les paquets RTCP seront reçus
AUDIORATE: Audio rate, mettre la même valeur que celle dans le caps du serveur
Résultats des tests
Le premier test a été effectué avec un audio rate de 44000 (bref un taux d’échantillonnage proche de celui d’un CD audio).
Paramètres:
rate=44000 / vad=false / dtx=false
Tests:
En jouant sur l’option quality, on obtient
quality=10 -> bande passante de 80 Kbps
quality=8 -> bande passante de 60 Kbps
quality=6 -> bande passante de 50 Kbps
quality=4 -> bande passante de 40 Kbps
quality=3 -> bande passante de 36 Kbps
quality=2 -> bande passante de 35 Kbps
Conclusion:
On obtient une dégradation de la qualité de réception de la voix en dessous d’une valeur de quality de 4. Il est clairement dit dans la documentation que la qualité > 4 son a réserver pour les sources audio plus complexes que la voix humaine (musique, film…). Par contre la consommation de CPU est plus importante (rapport de 5 contre 1 entre quality=10 et quality=1).
La bande passante est constante (à quelques Kbps) que l’on parle ou que l’on ne parle pas.
Paramètres:
En jouant sur les paramètres vad et dtx, on va essayer d’optimiser la bande passante en gardant la même qualité.
VAD (dixit Wiki): « Quand cette option est activé, le VAD détecte quand l’audio encodé est du dialogue ou du silence/bruit de fond. VAD est toujours implicitement activé en encodage VBR, donc cette option est utile uniquement en mode non VBR. Dans ce cas Speex détecte les périodes sans dialogue et les encode avec le strict minimum de bits pour reproduire le bruit de fond. Cette fonction est appelée Comfort Noise Generation (CNG). »
DTX (dixit Wiki): « la transmission discontinue est une fonctionnalité qui s’ajoute aux
opérations de VAD et de VBR, qui permet de couper la transmission
complètement quand le bruit de fond est stationnaire. Dans un fichier,
5 bits sont utilisés pour chaque frame manquante (correspondant à 250 bits/s) »
Tests:
On ajoute les paramètres vad=true et dtx=true au niveau du serveur (SPEEX_PARAMS)
La bande passante reste la même (40 Kbps) quand on parle, par contre elle passe à 23 Kbps quand on ne parle pas.
Conclusion:
Les algorithmes VAD et DTX fonctionnent très bien. Ils permettent de gagner de la bande passante quand l’activité au niveau sonore est faible (ce qui est le cas dans une conversation ou, normalement, une seule personne parle à un instant t).
En conservant les paramètres du premier test (quality=4, vad=true, dtx=true), on change seulement l’audio rate (au niveau du serveur et du client).
Paramètres:
rate=16000
Tests:
Bande passante de 28 Kbps lorsque l’on parle, 17 Kbps pendant les silences.
Conclusion:
La qualité reste excellente. Sur une conversation (voix humaine), on ne voit pas la différence avec un rate de 44000 (44 KHz).
Paramètres:
rate=8000
Tests:
Bande passante de 23 Kbps lorsque l’on parle, 16 Kbps pendant les silences.
Conclusion:
La qualité est dégradée mais la conversation reste compréhensible. On gagne seulement 5 Kbps par rapport au test précédant.
Pour conclure
D’après les tests effectués, le meilleur compromis bande passante qualité est:
- audio rate = 16000 (16 Khz)
- quality = 4
- vad = true
- dtx = true
Voici la procédure à suivre pour compiler un programme C utilisant le framework GStreamer sur une distribution GNU/Linux Ubuntu.
Installation des librairies
On commence par installer les packages suivants:
sudo apt-get install libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev libxml2-dev
Puis on créer les liens symboliques suivants:
sudo ln -s /usr/include/gstreamer-0.10/gst /usr/include/gst
sudo ln -s /usr/include/libxml2/libxml /usr/include/libxml
Compilation de HelloGStreamer.c
Prenons en exemple le fichier hellogstreamer.c contenant le code ci-dessous (source ici):
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
const gchar *nano_str;
guint major, minor, micro, nano;gst_init (&argc, &argv);
gst_version (&major, &minor, µ, &nano);
if (nano == 1)
nano_str = « (CVS) »;
else if (nano == 2)
nano_str = « (Prerelease) »;
else
nano_str = « »;printf (« This program is linked against GStreamer %d.%d.%d %s\n »,
major, minor, micro, nano_str);return 0;
}
La compilation devra se faire via la commande suivante:
gcc `pkg-config –cflags –libs gstreamer-0.10` hellogstreamer.c -o hellogstreamer
Le résultat de l’exécution du programme hellogstreamer devrait alors donner:
# ./hellogstreamer
GStreamer version 0.10.22
A vous les beaux programmes utilisant ce superbe framework multimedia.

La toute fraiche version 9.04 d’Ubuntu est livrée de base avec le framework GStreamer (puisque ce dernier est utilisé par des applications Gnome). Il manque cependant un certain nombre de plugins: encodeur H.264, plugin pour la communication avec FFMpeg…
Voici donc la ligne de commande à saisir pour palier à ce manque et disposer d’une version complète de GStreamer:
sudo apt-get install gstreamer0.10-plugins-bad gstreamer0.10-plugins-ugly gstreamer0.10-plugins-bad-multiverse gstreamer0.10-plugins-ugly-multiverse
Vous pouvez vérifier la liste des plugins GStreamer installés sur votre système avec la commande suivante:
# gst-inspect-0.10
…
Nombre total : 195 greffons, 960 fonctionnalités
Si vous lisez régulièrement ce blog, vous savez que je m’intéresse au FrameWork multimédia GStreamer (cliquez ici pour voir la liste des billets sur le sujet). Nous allons poursuivre la découverte de cette superbe trousse à outil multimédia en l’appliquant sur la récupération et l’exploitation de flux vidéo venant de caméras IP. Nous nous focaliserons ici sur les caméras IP AXIS , non pas que j’ai des actions dans cette société mais il faut avouer que leurs caméras sont de très bonne qualité et l’accès aux flux vidéos assez simple.
Avant de nous plonger dans le vif du sujet et si vous souhaitez faire ces tests chez vous, il faut au préhalable installer GStreamer sur votre système.
Ma configuration de test est la suivante:

Lors de la rédaction de ce billet, j’ai utilisé la caméra AXIS 213:
Format CIF
Compression 50%
Frame rate: 25 images/s
Configuration du PC1:
OS: GNU/Linux Debian 5.0 + Gstreamer 0.10.19-3
Hardware: Pentium Quad core CPU 2.8 Ghz + 4 Go RAM
Configuration du PC2:
OS: GNU/Linux Ubuntu 8.10 + Gstreamer 0.10.21-4
Hardware: Pentium Dual core CPU 3.0 Ghz + 512 Mo RAM
Affichage du flux vidéo
Cette caméra (comme toutes les caméras AXIS) permet la diffusion sur le réseau en utilisant deux formats:
- MJPEG sur HTTP
- MPEG-4 sur RTSP
Affichage du flux MJPEG/HTTP
On lance la commande suivante sur la machine PC1:
gst-launch gnomevfssrc location=http://cam01/axis-cgi/mjpg/video.cgi?resolution=CIF ! jpegdec ! ffmpegcolorspace ! autovideosink
Un rapide ntop sur notre machine PC1 nous indique que le fux est gourmand en bande passante (de l’ordre de 3.3 Mbps). L’occupation CPU varie entre 20% et 60%. La vidéo est fluide.
Affichage du flux MPEG4/RTSP
On lance la commande suivante sur la machine PC1:
gst-launch-0.10 rtspsrc location=rtsp://cam01:554/mpeg4/media.amp latency=0 ! decodebin ! ffmpegcolorspace ! autovideosink
La bande passante entre la caméra et le PC1 est alors de 1 Mbps en pics (moyenne de 400 Kbps quand il y a peu de mouvement devant la caméra). L’occupation CPU varie entre 5% et 15%. La vidéo est fluide.
Le paramètre latency (qui est par défaut à 3000, soit 3 secondes) permet de réduire la taille du buffer d’entrée. Si vous êtes sur un réseau LAN, vous pouvez sans problème mettre comme valeur 0 (comme je l’ai fait dans mon exemple). Par contre sur des réseaux moins performant (en terme de débit, de perte de paquets…), il vaut mieux conserver un buffer un peu plus élevé.
Encodage du flux vidéo dans un fichier
Nous allons continuer notre test en essayant d’encoder « à la volée » le flux vidéo venant de la caméra IP. Détaillons un peu notre pipeline:
- récupérer le flux MPEG4/RTSP de la caméra
- l’afficher sur l’écran
- réduire le nombre d’images par seconde (fps) à 1
- encoder le flux en MJPG
- sauvegarder dans un fichier AVI (output.avi)
La ligne de commande correspondante à lancer sur PC1 est:
gst-launch rtspsrc location=rtsp://cam01:554/mpeg4/media.amp latency=0 ! queue ! decodebin ! ffmpegcolorspace ! tee name=save ! queue ! autovideosink save. ! queue ! videorate ! capsfilter caps= »video/x-raw-yuv,framerate=(fraction)1/1″ ! queue ! jpegenc ! avimux ! filesink location=output.avi .save
Le fichier généré (output.avi) occupe un espace disque d’environ 15 Ko par seconde (soit 54 Mo/heure).
Afin d’optimiser cette taille, il est possible d’utiliser Theora (dans un fichier OGG), un codec vidéo libre et efficace. La commande devient alors:
gst-launch rtspsrc location=rtsp://cam01:554/mpeg4/media.amp latency=0 ! queue ! decodebin ! ffmpegcolorspace ! tee name=save ! queue ! autovideosink save. ! queue ! videorate ! capsfilter caps= »video/x-raw-yuv,framerate=(fraction)1/1″ ! queue ! theoraenc ! oggmux ! filesink location=output.ogg .save
On a alors une taille de fichier de sortie (output/ogg) d’environ 6 Ko par seconde (soit 21 Mo/heure).
Mixer plusieurs vidéos en une
Si vous disposé de plusieurs caméras, il peut être utile de mixer ces différentes sources dans une même image (un peu comme le mode PIN des télévisions).
Je vais dans l’exemple ci-dessous, prendre deux sources (Camera AXIS + Webcam USB) et les mixer:

La pipeline est la suivante:
gst-launch v4l2src ! queue ! videoscale ! capsfilter caps= »video/x-raw-yuv,width=64,height=48,framerate=(fraction)5/1″ ! ffmpegcolorspace ! videobox border-alpha=0 alpha=1.0 top=-230 left=-278 ! videomixer name=mix ! ffmpegcolorspace ! autovideosink mix. rtspsrc location=rtsp://cam01:554/mpeg4/media.amp latency=0 ! queue ! decodebin ! queue ! videorate ! capsfilter caps= »video/x-raw-yuv,width=352,height=288,framerate=(fraction)25/1″ ! ffmpegcolorspace ! mix.
Attention de bien fixer les framerates (videorate ou videoscale + capsfilter), car videomixer (le plugin qui s’occupe de faire le mixage vidéo) semble assez sensible sur ce point.
Streaming vers une autre machine
Nous allons maintenant voir comment transcoder le flux vidéo d’une caméra IP pour le diffuser (streamer vers une autre machine).
La description du pipeline du PC1 est la suivante:
- récupérer le flux MPEG4/RTSP de la caméra
- réencodage en Theora (à 250 Kbps)
- diffusion en UDP vers le PC2
puis celle du PC2:
- récupérer de flux Theora/UDP venant du PC1
- décodage Theora
- affichage de la vidéo
et les commandes correspondantes, sur le PC2 (il faut lancer cette commande en premier):
gst-launch -v udpsrc port=1234 ! theoradec ! autovideosink
puis sur le PC1:
gst-launch rtspsrc location=rtsp://cam01:554/mpeg4/media.amp latency=0 ! queue ! decodebin ! ffmpegcolorspace ! queue ! videorate ! capsfilter caps= »video/x-raw-yuv,framerate=(fraction)25/1″ ! queue ! theoraenc bitrate=250 ! queue ! udpsink host=pc2 port=1234
En moyenne, le débit observé entre PC1 et PC2 est de l’ordre de 250 Kbps (conforme donc a ce que l’on a configuré dans le plugin theoraenc), on observe cependant des pics à 250 Kps+30%. La consommation de CPU est de l’ordre de 25% sur PC1 et de 5% sur PC2. La vidéo est recue de manière fluide sur le PC2. Là encore, il ne faut pas oublier de fixer le nombre d’images par seconde avec videorate + capsfilter.
L’avantage avec GStreamer, c’est qu’il intégre une liste de plugins assez impressionnante, il est alors facile de les insérer dans notre pipeline. Par exemple, si l’on souhaite reprendre l’exemple ci-dessus et y ajouter un texte en sur-impression (overlay), il suffit d’utiliser le plugin cairotextoverlay.
La commande sur le PC1 devient alors:
gst-launch-0.10 rtspsrc location=rtsp://cam01:554/mpeg4/media.amp latency=0 ! queue ! decodebin ! ffmpegcolorspace ! queue ! cairotextoverlay text= »Attention Tigrou ! » shaded-background=true ! queue ! videorate ! capsfilter caps= »video/x-raw-yuv,framerate=(fraction)25/1″ ! queue ! theoraenc bitrate=250 ! queue ! udpsink host=pc2 port=1234
et le résultat sur PC2:

Pour conclure
Ce billet nous a permis de mettre le pied dans le monde passionnant du traitement des flux vidéo. Ce n’est qu’une introduction et la seule limite est votre imagination. Je vous rappelle que GStreamer peut être simplement intégré à vos applications grâce aux API disponibles. Si vous avez des questions et remarques sur le sujet, il existe une section spéciale dans le forum !

GStreamer, la théorie…
Nous allons dans ce billet aller un peu plus dans le détail du fonctionnement de GStreamer (le Framework multimédia libre).
C’est quoi donc ?
GStreamer est un framework multimédia: c’est à dire un ensemble de bibliothèques, d’outils et de conventions permettant le développement d’applications (source Wikipédia). Par multimédia, on attend ici la manipulation d’objets vidéo et audio.
Il est composé:
- d’une architecture logicielle comprenant un coeur et de nombreux plugins (plus de 150 pour la version 0.10)
- d’API pour le développement avec le support de nombreux langages
- d’outils de tests (gst-launch), de documentation (gst-inspect) et de présentation (gsteditor)
Architecture générale de GStreamer

GStreamer est composé d’un cœur (GStreamer Core) permettant de construire une séquence d’actions à effectuer sur vos objets multimédia. Ce cœur s’occupe de la communication entre les différentes briques en gérant les entrées/sorties. Il manipule les données sous forme de blocs et se base sur une architecture logicielle objet.
Les plugins manipulent les objets multimédia. On peut diviser les plugins en différentes catégories:
- gestion des entrées (sources) audio et vidéo: les entrées peuvent être des fichiers multimédia, des équipements (Webcam, caméra DV) ou des flux réseaux (par exemple venant d’un streaming).
- gestion des protocoles: permet une gestion des protocoles de communication réseau. Avec le support de protocoles simples (UDP, TCP) ou plus complexes (RTP/RTSP).
- gestions des formats: les données multimédias sont dans des conteneurs (AVI, OGG, MPEG…). Gstreamer permet, via des plugins, de gérer ces conteneurs (lecture, mux, demux…).
- gestions des codecs: le fait que les codecs audio/vidéo soit proposé dans Gstreamer sous la forme de plugins comporte de gros avantages (normalisation d’un codec à l’autre, facilité de changement de codec dans une application, cicle de développement plus cours pour inclure un plugin dans GStreamer).
- gestion des filtres: il existe autant de possibilité de filtres audio et vidéo que votre imagination peut produire.
- gestion des sorties (sinks) audio et vidéo: Les sorties peuvent être des fichiers, des équipements (écrans) ou des flux réseaux.
Il faut noter que les plugins GStreamer sont livrés sous la forme de 3 packages:
gst-plugins-good: ceux sont les plugins jugés de bonnes qualités par les développeurs et qui sont distribué sous licences LGPL.
gst-plugins-ugly: ceux sont les plugins jugés de bonnes qualités mais qui peuvent poser des problèmes de licenses (non libres).
gst-plugins-bad: ceux sont les plugins en développement dont la qualité et/ou stabilité doivent être améliorés.
Comment fonctionne GStreamer
Afin d’expliquer le plus simplement le fonctionnement de GStreamer, nous allons prendre l’exemple d’une application ayant pour but de lire un fichier multimédia au format OGG (contenant des données audio encodées en Vorbis et vidéo encodées en Theroa) pour l’afficher sur votre écran et l’écouter sur vos haut parleurs.
Le schéma du framework correspondant est le suivant:

Pipeline
La classe principal est nommé pipeline. C’est l’enveloppe qui va contenir l’ensemble des actions a effectuer.

Element
Le Pipeline contient une chaine d’élements (elements). L’élément est la classe la plus importante dans GStreamer. A chaque élément est associé une fonction (plugin).
On distingue 3 types d’élements:
- Les éléments sources (« source element »). Ils produisent des données.

- Les éléments de sorties (« sink element »). Ils ne produisent pas de donnée. Ils sont donc souvent en bout de pipeline (exemple: xvideosink, permet d’afficher une vidéo à l’écran).

- Les éléments de filtrage (« filter element »). Ils ont une ou plusieurs entrées et une ou plusieurs sorties. Ils effectuent des traitements sur les données et les réinjecte dans le pipeline.

Voici par exemple l’élément qui permet la lecture d’un fichier depuis votre disque dur: file-source.

Pour vous donner une idée, voici le code C qui permet la création d’un élément de test (fakesrc):
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
GstElement *element;
/* init GStreamer */
gst_init (&argc, &argv);
/* create element */
element = gst_element_factory_make (« fakesrc », « source »);
if (!element) {
g_print (« Failed to create element of type ’fakesrc’\n »);
return -1;
}
gst_object_unref (GST_OBJECT (element));
return 0;
}
Les lecteurs familiers avec GLib ne seront pas dépaysés…
Pads
Les pads sont les entrées/sorties des éléments. Ils servent à interconnecter les éléments entre eux. Il peut inclure des fonctions de vérification afin de s’assurer que le format des données correspond à ce qu’il attend. On peut distinguer deux sous ensembles de pad:
- les pads de type « sink », permettant de faire entrer des données dans un élément
- les pads de type « src » (source), permettant de faire sortie des données d’un élément
Il est possible d’associer plusieurs pads sinks et srcs à un même élément.
Exemples de pads pour l’élément vorbis-decoder:

Les liens entre les éléments se font toujours entre un pad src et un pad sink:

Les pads sont de loin les objets les plus complexes dans GStreamer. Ils contiennent un ensemble de paramètres (statique ou dynamique) permettant de définir les données attendues ou générées. Par exemple en tapant la commande « gst-inspect theoraenc » qui donne la description du plugin de décodage video Theroa:
Pad Templates:
SRC template: ‘src’
Availability: Always
Capabilities:
video/x-raw-yuv
format: I420
framerate: [ 0/1, 2147483647/1 ]
width: [ 1, 2147483647 ]
height: [ 1, 2147483647 ]SINK template: ‘sink’
Availability: Always
Capabilities:
video/x-theora
On peut y voir qu’il s’attend à avoir comme source une vidéo au format RAW-YUV, avec un nombre d’image par seconde et une taille pouvant varier selon le range affiché. En sortie, l’élement produira un vidéo au format THEORA.
Bins
Les bins sont des pipelines prédéfinis que l’on peut inclure comme de simple éléments dans une nouveau pipeline. Celà permet de simplifier grandement certaines actions complexe (rtpbin en est un bon exemple !).
Exemple de création d’un bin en langage C:
#include <gst/gst.h>
int
main (int argc,
char *argv[])
{
GstElement *bin, *pipeline, *source, *sink;
/* init */
gst_init (&argc, &argv);
/* create */
pipeline = gst_pipeline_new (« my_pipeline »);
bin = gst_bin_new (« my_bin »);
source = gst_element_factory_make (« fakesrc », « source »);
sink = gst_element_factory_make (« fakesink », « sink »);
/* First add the elements to the bin */
gst_bin_add_many (GST_BIN (bin), source, sink, NULL);
/* add the bin to the pipeline */
gst_bin_add (GST_BIN (pipeline), bin);
/* link the elements */
gst_element_link (source, sink);
[..]
}
Conclusion
J’espère que cette rapide introduction vous aura un peu éclairé sur les possibilités de GStreamer. Nhésitez pas à poser des questions et remarques sur le sujet dans l’espace GStreamer du forum de Nicolargo.
Sources:
