Symposium sur la sécurité des technologies de l'information et des communications

Journal du comité d'organisation

Live streaming SSTIC - Transcodage des flux

· par le comité d'organisation
Seconde étape du livestreaming : le transcodage des flux en plusieurs qualités. Ce billet détaille le pipeline mis en place sur le serveur de transcodage et explore les différentes options et fonctionnalités possibles.

Ce billet est le quatrième d’une série de 5 :

  1. introduction et considérations pour le SSTIC ;
  2. l’architecture de streaming ;
  3. la chaîne de capture audio/vidéo sur place ;
  4. le transcodage des flux en différentes qualités ;
  5. les serveurs frontaux accédés par les internautes.

Objectif

Le transcodage permet d’obtenir plusieurs flux de qualités différentes, afin que les clients puissent lire en temps réel la meilleure qualité vidéo permise par leur connexion. Il est possible ensuite, côté client, de basculer dynamiquement ou manuellement le flux regardé, par exemple lorsque la qualité de réception d’une connexion mobile varie.

L’étape de transcodage est impérative si l’on veut gérer l’infrastructure de bout en bout, sous peine de ne fournir aux clients qu’un unique flux qui ne satisfaira pas forcément le débit de leur connexion. En revanche, si on diffuse vers un service tiers, ce dernier procèdera alors au transcodage de votre flux en plusieurs qualités. Les paramètres d’encodage sont importants puisqu’ils auront un impact direct sur la compatibilité avec les lecteurs et sur les pics de débit que subiront les clients.

Pour le SSTIC, le serveur de transcodage remplit également d’autres fonctions :

  • il appose un timestamp sur le flux, utile pour identifier les problèmes de synchronisation, de cache, de proxy ou de tampons, y compris par les clients finaux ;
  • il pousse les flux transcodés vers les serveurs frontaux dès que possible, pour minimiser les délais de reprise en cas d’interruption de la connexion (quelque soit l’endroit impacté) ;
  • il sauvegarde les flux localement ;
  • il rejoue l’enregistrement local des flux tous les soirs à 20h ;
  • il joue un flux de test en permanence, entre les diffusions en direct ;
  • il découpe les vidéos pour permettre leur publication dans les instants qui suivent la fin d’une présentation.

Gardez à l’esprit qu’il est possible de cumuler le rôle de serveur de transcodage et de serveur frontal sur la même machine.

Prérequis

L’important sur cette machine est sa puissance de calcul CPU brute pour l’encodage. La RAM et la bande passante ne seront pas les plus utiles : le serveur de transcodage du SSTIC est une machine virtuelle disposant de 2 Go de RAM et de 200 Mbits/s. Il pourrait se satisfaire de 10 Mbits/s pour chaque serveur frontal ou relai à alimenter.

Matériel

Jusqu’en 2018, le serveur de transcodage du SSTIC présentait la configuration suivante :

  • serveur physique :
    • serveur dédié OVH (modèle 2014)
    • CPU : Intel Xeon E3-1245 V2 @ 3.40GHz (4 cores, 8 threads)
    • RAM : 32 Go
    • Hyperviseur Xen
  • machine virtuelle Xen
    • CPU : 6 vCPU
    • RAM : 2 Go
    • Debian

À partir de 2019, la configuration est la suivante (pas d’usage de machine virtuelle) :

  • serveur physique :
    • serveur dédié Online
    • CPU : Intel Xeon E3-1231 V3 @ 3.40GHz (4 cores, 8 threads)
    • RAM : 32 Go
    • Debian

Pour transcoder trois flux depuis et jusqu’à du 720p30, 4 vCPU suffisaient. En 2018, le transcodage de flux en 540p30, 720p30 et 1080p30 a nécessité l’emploi de 6 vCPU sur la même configuration. En 2019, le nouveau serveur permet de tenir l’ensemble des flux en 60 FPS, ou d’ajouter un flux 4K.

Logiciel

Le serveur tourne sous Linux et seuls deux composants sont strictement nécessaires :

Pour de diverses raisons liées à des tests, il est fait usage d’une vieille version de ffmpeg / avconv fournie par Debian et conservée depuis. Cela n’est pas très important, mais l’exercice de portage des arguments du programme vers une version ultérieure sera laissé au lecteur.

Version de ffmpeg utilisée jusqu’en 2018 :

$ ffmpeg -version
ffmpeg version 0.8.20-6:0.8.20-0+deb7u1, Copyright (c) 2000-2014 the Libav developers
  built on Jan 19 2017 11:13:36 with gcc 4.7.2
The ffmpeg program is only provided for script compatibility and will be removed
in a future release. It has been deprecated in the Libav project to allow for
incompatible command line syntax improvements in its replacement called avconv
(see Changelog for details). Please use avconv instead.
ffmpeg 0.8.20-6:0.8.20-0+deb7u1
libavutil    51. 22. 3 / 51. 22. 3
libavcodec   53. 35. 0 / 53. 35. 0
libavformat  53. 21. 1 / 53. 21. 1
libavdevice  53.  2. 0 / 53.  2. 0
libavfilter   2. 15. 0 /  2. 15. 0
libswscale    2.  1. 0 /  2.  1. 0
libpostproc  52.  0. 0 / 52.  0. 0

Version de ffmpeg utilisée à partir de 2019 :

$ ffmpeg -version
ffmpeg version 3.2.14-1~deb9u1 Copyright (c) 2000-2019 the FFmpeg developers
built with gcc 6.3.0 (Debian 6.3.0-18+deb9u1) 20170516
configuration: --prefix=/usr --extra-version='1~deb9u1' --toolchain=hardened --libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu --enable-gpl --disable-stripping --enable-avresample --enable-avisynth --enable-gnutls --enable-ladspa --enable-libass --enable-libbluray --enable-libbs2b --enable-libcaca --enable-libcdio --enable-libebur128 --enable-libflite --enable-libfontconfig --enable-libfreetype --enable-libfribidi --enable-libgme --enable-libgsm --enable-libmp3lame --enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libssh --enable-libtheora --enable-libtwolame --enable-libvorbis --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx265 --enable-libxvid --enable-libzmq --enable-libzvbi --enable-omx --enable-openal --enable-opengl --enable-sdl2 --enable-libdc1394 --enable-libiec61883 --enable-chromaprint --enable-frei0r --enable-libopencv --enable-libx264 --enable-shared
libavutil      55. 34.101 / 55. 34.101
libavcodec     57. 64.101 / 57. 64.101
libavformat    57. 56.101 / 57. 56.101
libavdevice    57.  1.100 / 57.  1.100
libavfilter     6. 65.100 /  6. 65.100
libavresample   3.  1.  0 /  3.  1.  0
libswscale      4.  2.100 /  4.  2.100
libswresample   2.  3.100 /  2.  3.100
libpostproc    54.  1.100 / 54.  1.100

Le point important à vérifier sur son ffmpeg pour l’usage décrit dans ce billet est le support des éléments suivants :

  • RTMP (normalement par défaut) ;
  • codecs libx264, aac (normalement par défaut) ;
  • formats flv, s16le (normalement par défaut) ;
  • filtre drawtext (à vérifier) ;
  • multithread (à vérifier).

Mise en place

L’objectif n’est pas de fournir un tutorial détaillé de compilation et d’installation de nginx ou de ffmpeg. Libre à vous d’installer et de paramétrer ce dont vous avez besoin. Aussi, la sécurité et la défense en profondeur du serveur ne sont pas abordés. Par ailleurs, pour le SSTIC nous employons des machines «jetables» dédiées à cette tâche, dont la perte ne sera pas réellement importante. Il est possible de les louer uniquement lors de l’évènement.

La configuration détaillée ici tourne sur Debian 9, et nécessite l’installation des paquets suivants avec leurs dépendances :

$ apt-get install ffmpeg build-essentials unzip libssl-dev zlib1g-dev fonts-dejavu-core fonts-dejavu-extra

Le routage vidéo suivant sera mis en place :

Représentation graphique de la configuration nginx-rtmp-module en 2018

Représentation graphique de la configuration nginx-rtmp-module en 2018 (pleine taille)

Le «point d’entrée» du flux provenant de la régie est le flux nommé cam1 dans l’application camsrc, sur la gauche. C’est lui qui est renseigné dans OBS Studio comme URL de diffusion RTMP. Un autre point d’entrée est le flux «hors ligne» généré localement (default, dans l’application srcswitcher).

Vous pourrez suivre plus facilement le fichier de configuration détaillé plus bas en le faisant correspondre à cette illustration.

Compilation de nginx avec son module RTMP

Sous Debian, il est relativement simple de recompiler et d’installer nginx. L’exemple suivant reprend les étapes utilisées pour l’édition du SSTIC 2019, avec nginx 1.16.0 :

apt-get install build-essential libpcre3 libpcre3-dev libssl-dev unzip
wget https://nginx.org/download/nginx-1.16.0.tar.gz
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
tar zxvf nginx-1.16.0.tar.gz
unzip master.zip
cd nginx-1.16.0
./configure --add-module=../nginx-rtmp-module-master --with-http_ssl_module
make && make install

Vous pouvez également créer les dossiers nécessaires :

mkdir -p /var/www/stat /var/www/hls /var/rec/video /var/rec/audio /var/rec/talks/mp4/1080p/ /var/rec/talks/mp4/540p/
chown -R nobody /var/www/stat /var/www/hls /var/rec/video /var/rec/audio /var/rec/talks

Copiez aussi stat.xsl depuis le dossier du module rtmp dans /var/www/stat/ :

cp ../nginx-rtmp-module-master/stat.xsl /var/www/stat/

nginx est désormais installé dans /usr/local/nginx/. Sa configuration est située dans /usr/local/nginx/conf/nginx.conf.

Pour démarrer le serveur en tâche de fond, lancez la commande :

/usr/local/nginx/sbin/nginx

Pour le stopper complètement, par exemple en cas de modification de la configuration RTMP, lancez :

/usr/local/nginx/sbin/nginx -s stop

Configuration de nginx

La plupart des applications RTMP de la configuration sont simples : elles reçoivent un ou plusieurs flux nommés, puis soit elles les relayent à un serveur distant ou à une autre application locale, soit elles exécutent un traitement local, via ffmpeg, qui lit un flux RTMP source et publie son résultat vers une autre application.

Cependant, pour permettre plusieurs usages (flux «hors ligne» automatique, backup des vidéos, envoi vers un service tiers, autorisation de relais tiers…), la configuration du transcodeur fait usage de plusieurs astuces plus ou moins compliquées. Le transcodage en lui-même exploite la possibilité d’exécuter un programme externe lorsqu’un flux est publié.

Voici donc un commentaire détaillé de la configuration nginx du serveur. Elle est téléchargeable ici et la documentation des directives est accessible par là.

N’hésitez pas à suivre ces commentaires en conservant l’illustration plus haut sous les yeux pour situer le cheminement du flux vidéo.

worker_processes  1;
events {
    worker_connections  1024;
}

Des directives utilisées plus bas (auto_push et exec_pull) sont incompatibles avec l’usage de multiples workers. worker_processes est donc forcé à 1. Le nombre maximal de connexions peut être adapté.

rtmp {
    server {
        listen 1935;
        chunk_size 4096;

        application srcswitcher {
            live on;
          
            allow publish 127.0.0.1;
            deny publish all;
            allow play 127.0.0.1;
            deny play all;
            
            exec_options on;
            
            # cette application reçoit en permanence un flux "default" qui est 
            # l'image offline
            
            # elle reçoit aussi tout flux publié dans l'application camsrc
                        
            exec_pull ffmpeg -f image2 -loop 1 -re -r 30 -i /var/www/sstic_offline_1080p.png 
            -ar 48000 -ac 2 -f s16le -i /dev/zero -acodec aac -strict experimental -vcodec libx264 -preset ultrafast 
            -b:v 2500k -maxrate 4000k -bufsize 4000k -s 1920x1080 
            -pix_fmt yuv420p -force_key_frames expr:gte\(t,n_forced*1\)
            -threads 2 -f flv rtmp://127.0.0.1/transcoder/live name=default;
                        
            exec_pull ffmpeg -re -i rtmp://localhost/camsrc/cam1 -vsync 0 
            -vcodec copy -acodec copy -s 1920x1080 -r 30 
            -f flv rtmp://127.0.0.1/transcoder/live name=cam1;
        }

L’application RTMP srcswitcher n’est pas la première à traiter le flux vidéo venant de la capture, mais elle est celle qui doit délivrer un flux en permanence pour générer la mire lorsque le flux est hors ligne. Son fonctionnement est contre-intuitif, puisqu’elle est contrôlée en partie par des triggers de l’application camsrc et qu’elle ne récupère en réalité aucun flux (regardez les URLs des lignes de commande ffmpeg).

L’idée est de lancer ffmpeg pour encoder une image fixe sous forme de flux vidéo continu. La résolution doit idéalement être identique à celle qui sera fournie de la capture. Ce flux se nomme initialement default.

Lorsqu’un flux arrivera de la régie, il passera d’abord par l’application camsrc ci-dessous, qui redirigera les clients de l’application srcswitcher vers le flux cam1. Il existe bien un client d’un flux de cette application srcswitcher : il s’agit de l’application dummyclient, décrite plus bas.

Cette astuce permet de faire en sorte que la commande ffmpeg ne soit lancée qu’à la demande, lorsqu’aucun flux n’arrive de la régie, pour éviter de consommer une charge CPU non négligeable lorsqu’il faut transcoder autre chose qu’une image fixe.

Notez l’usage du format yuv420p : il permet la compatibilité avec le profil baseline h264 et donc la compatibilité avec de plus nombreux appareils. La création d’une keyframe par seconde permet quant à elle de faire en sorte que ffmpeg n’attende pas de nombreuses secondes l’arrivée de cette keyframe pour détecter les paramètres du flux. Lors de la rupture du flux provenant de la régie, il faut en effet attendre qu’une keyframe arrive pour que le flux «hors ligne» soit correctement pris en compte et débute son streaming. Par défaut cela peut prendre plusieurs secondes, ce qui mène à une rupture de continuité qui est mal gérée par les lecteurs. Il est possible de descendre sous la seconde, mais augmenter les keyframes à outrance augmentera significativement la charge CPU du serveur.

        application camsrc {
            live on;
            
            allow publish 127.0.0.1;
            deny publish all;
            allow play 127.0.0.1;
            deny play all;
            
            # trigger pour changer de flux lorsque l'on publie un flux dans 
            # cette application (passage vers la nouvelle caméra)
            on_publish http://127.0.0.1/control/redirect/subscriber?app=srcswitcher&addr=127.0.0.1&newname=cam1 ;
            
            # trigger lorsque la caméra est coupée, on revient vers l'image fixe
            on_publish_done http://127.0.0.1/control/redirect/subscriber?app=srcswitcher&addr=127.0.0.1&newname=default ;
            
            # on pousse tous les flux vers l'application srcswitcher
            push rtmp://localhost/srcswitcher/;
            
            recorder audio {
                record audio;
                record_path /var/rec/audio;
                record_suffix -%d-%b-%y-%T.audio.flv;
            }
            recorder video {
                record all;
                record_path /var/rec/video;
                record_suffix -%d-%b-%y-%T.video.flv;
            }
            recorder talks {
                record all manual;
                record_path /var/rec/talks;
                record_suffix _SSTIC_%F_PXX_%T.flv;
                exec_record_done ffmpeg -y -i $path 
                    -acodec copy -vcodec copy $dirname/mp4/1080p/$basename.mp4;
                exec_record_done ffmpeg -y -i $path 
                    -vcodec libx264 -preset veryfast -vprofile baseline 
                    -s 960x540 -r 30 -crf 25 -acodec copy 
                    -threads 2 $dirname/mp4/540p/$basename.mp4 ; 
            }
        }

L’application camsrc recevra ponctuellement le flux vidéo capturé dans la salle. Elle déclenche l’enregistrement automatique de tout flux reçu, afin d’assurer une sauvegarde en cas de soucis ou d’oubli côté capture. Pour les recorders audio et video, les fichiers de ces captures ne seront découpés que par les interruptions de diffusion. Le format de conteneur flv est utilisé car il permet de ne pas corrompre les vidéos en cas d’interruption de l’écriture sur le disque. Enfin, dans notre cas, la copie du flux audio seul est réalisée mais n’a jamais été utilisée jusqu’ici.

Le recorder talks est configuré en mode manuel : il ne démarre et ne termine l’enregistrement que sur commande. Cela permet de découper les vidéos des présentations directement sur le serveur, en intégrant des appels aux URLs de contrôle dans le script employé sur le portable de capture pour changer les titres des présentations. La conversion en mp4 1080p ne demandera aucun réencodage (et sera déclenchée automatiquement lorsque l’on déclenchera la fin de l’enregistrement). En revanche, le serveur réencode également la vidéo découpée en 540p. D’après les tests, ce réencodage est effectué à environ 200 FPS, ce qui prend moins de 5 minutes pour une présentation de 30 minutes. Il ne reste plus qu’à copier les fichiers vers le serveur web.

Cette application camsrc déclenche aussi les triggers lorsque le flux de capture commence à être publié, et lorsqu’il cesse. Ces triggers modifient la source lue par une troisième application, dummyclient. Ils utilisent une API de contrôle exposée en HTTP, ce qui impose la configuration de la partie HTTP du serveur nginx (détaillée en fin de configuration).

        application dummyclient {
            live on;
            
            allow publish 127.0.0.1;
            deny publish all;
            allow play 127.0.0.1;
            deny play all;
            
            pull rtmp://localhost/srcswitcher/default name=live static;
        }

Même si aucun flux vidéo réel n’est présent dans l’application RTMP srcswitcher, la présence de l’application RTMP dummyclient aura pour effet de déclencher l’exécution de la directive exec_pull de srcswitcher correspondant initialement au flux default, puis celle du flux cam1 lorsque la capture est en cours. En effet, l’option static de la directive pull déclenche la requête d’abonnement au flux même si aucun client n’est connecté à cette application.

dummyclient ne reçoit pas de flux vidéo, elle s’abonne juste à un flux «vide» pour déclencher l’exécution du ffmpeg, qui va récupérer un flux de l’application camsrc ou depuis une image fixe, et qui va dupliquer ce flux bêtement en le publiant sur l’application transcoder ci-dessous.

        application transcoder {
            # Cette application reçoit un flux principal de qualité maximale
            
            # enable live streaming
            live on;
            meta copy;
            
            # on n'autorise à pousser un flux que depuis localhost.
            # du coup : utiliser un tunnel SSH pour pouvoir publier
            # sur le poste de capture : 
            # ssh -L 1935:localhost:1935 <user>@<host>
            allow publish 127.0.0.1;
            deny publish all;
            
            # on n'autorise la lecture que depuis localhost ; l'autorisation est
            # importante pour le reste des applications
            allow play 127.0.0.1;
            deny play all;
            
            # Le flux publié va être transcodé vers de multiples destinations
            # Chaque flux transcodé est poussé vers une application locale :
            # /hls_relay/xxxxx
            # Cette application va ensuite pousser le flux vers les serveurs 
            # relai
            
            # ======
            # Transcodage pour service HLS, h.264 baseline profile, 
            # Max total bitrate : 7672 Kbits/s (avec audio AAC 224 Kbits/s)
            # ======

            # Transcodage HLS 1 Mbits/s 960x540 hardcapped
            exec ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec libx264 -preset veryfast -vprofile baseline 
            -b:v 1000k -maxrate 1000k -bufsize 2000k -ar 48000 -ac 2 -f s16le -acodec aac -b:a 224k -strict experimental -s 960x540 -r 30 
            -vf "drawtext=box=1:boxcolor=black@0.5:fontsize=24:fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansCondensed-Bold.ttf:text='%{localtime} UTC+2 - 540p - LIVESTREAM - Please report issues to streaming@lists.sstic.org':fontcolor=white@0.6:x=0:y=0"
            -threads 2 -f flv rtmp://localhost/hls_relay/live_low;

            # Transcodage HLS 2 Mbits/s 720p hardcapped
            exec ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec libx264 -preset veryfast -vprofile baseline
            -b:v 2000k -maxrate 2000k -bufsize 4000k -ar 48000 -ac 2 -f s16le -acodec aac -b:a 224k -strict experimental -s 1280x720 -r 30 
            -vf "drawtext=box=1:boxcolor=black@0.5:fontsize=24:fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansCondensed-Bold.ttf:text='%{localtime} UTC+2 - 720p - LIVESTREAM - Please report issues to streaming@lists.sstic.org':fontcolor=white@0.6:x=0:y=0"
            -threads 2 -f flv rtmp://localhost/hls_relay/live_mid;

            # Transcodage HLS 4 Mbits/s 1080p hardcapped
            exec ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec libx264 -preset veryfast -vprofile baseline
            -b:v 4000k -maxrate 4000k -bufsize 8000k -ar 48000 -ac 2 -f s16le -acodec aac -b:a 224k -strict experimental -s 1920x1080 -r 30 
            -vf "drawtext=box=1:boxcolor=black@0.5:fontsize=24:fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansCondensed-Bold.ttf:text='%{localtime} UTC+2 - 1080p - LIVESTREAM - Please report issues to streaming@lists.sstic.org':fontcolor=white@0.6:x=0:y=0"
            -threads 2 -f flv rtmp://localhost/hls_relay/live_hi;
        }

L’application RTMP transcoder réalise le transcodage à proprement parler :

  • chaque flux publié ici déclenche l’exécution de 3 instances de ffmpeg ;
  • chaque instance de ffmpeg réencode la vidéo et le son, applique un watermark semi-transparent avec le timestamp courant, et publie le résultat dans une autre application RTMP (hls_relay).

Notez le paramétrage des tampons et limites de débit : ils sont placés ainsi pour éviter un débit en pointe dépassant le maximum souhaité. Pour fournir un flux le plus stable possible aux connexions mobiles, il faut éviter à tout prix les hausses soudaines de débit. L’encodage CBR pourrait lisser le débit, mais donnerait une consommation constante, un prix bien élevé pour une majorité de slides statiques. L’encodage VBR est difficilement paramétrable pour limiter les crêtes. La combinaison retenue offre les meilleurs résultats.

Pour tester cette configuration, nous conseillons de tester des changements de scène et de mettre un sujet mobile en plein écran, cela maximisera les besoins en keyframes et en débit.

        application hls_relay {
            # Application gérant l'écriture des _chunks_ HLS dans un path 
            # accessible en HTTP
            live on;
            
            # publication en local seulement
            allow publish 127.0.0.1;
            deny publish all;
            # lecture en local seulement 
            allow play 127.0.0.1;
            deny play all;
            
            # on pousse le flux vers les applications HLS locales ou distantes
            push_reconnect 1s;
            push rtmp://localhost/hls/ ;
            push rtmp://b.streaming.sstic.org/hls/ ;
            push rtmp://d.streaming.sstic.org/hls/ ;
            
        }

Cette application n’est qu’un passe-plat :

  • elle pousse les 3 flux reçus après transcodage vers les serveurs frontend, on utilise un push pour limiter les délais dus au timeouts et retries ;
  • éventuellement, elle pousse les 3 flux vers l’application de création des chunks HLS locale ;
  • on peut rajouter ici une directive push vers un service tiers comme Twitch ou YouTube. Il suffit pour cela d’avoir une URL de publication RTMP, et de s’assurer que le flux réencodé correspond au standard attendu par le service en ligne (codec, profil, débit, framerate, keyframes…). Attention, la plupart des services n’attendent qu’un seul flux de votre part. Il faudra bien limiter l’envoi à un seul des flux reçus (push <URL> name=<flux>).

Il n’est pas possible de rajouter des serveurs distants à la volée, sans devoir arrêter le serveur nginx complètement. Il reste possible de laisser une directive push vers un service tiers en backup, et de ne l’activer qu’à la demande : pour celà, une règle iptables de filtrage sortant permettra de ne pas laisser passer la connexion par défaut. Désactiver ou retirer la règle de filtrage permettra de pousser instantanément le flux sur le service distant.

Si vous préférez envoyer votre flux brut (avant transcodage et watermarking) à un service tiers, vous pouvez mettre la directive push correspondant soit dans l’application camsrc, auquel cas le service tiers subira ses interruptions de diffusion en fonction de votre régie. Ou bien, vous pouvez la mettre dans l’application transcoder, ce qui enverra le flux brut avant transcodage (c’est celui qui est lu par les ffmpeg) ainsi que les mires générées lorsque le flux est hors ligne (ce qui est utilisé pendant les pauses pour indiquer le programme).

        application hls {
            # Application gérant l'écriture des _chunks_ HLS dans un path 
            # accessible en HTTP
            live on;
            hls on;
            hls_path /var/www/hls;
            hls_nested on;

            hls_variant _low BANDWIDTH=1024000;
            hls_variant _mid BANDWIDTH=2048000;
            hls_variant _hi  BANDWIDTH=4096000;
                        
            # publication en local seulement
            allow publish 127.0.0.1;
            allow publish 178.33.71.43; # on autorise le serveur de transcodage
            deny publish all;
            
            # lecture autorisée pour gérer les relais tiers 
            # blocage effectué au niveau du parefeu 
            allow play all;
        }
    }
}

Enfin, l’application hls fournit le service final de découpage des chunks vidéos pour être publiés en HTTP, mais elle n’est pas nécessaire sur le serveur de transcodage. Notez qu’elle expose également les flux RTMP qu’elle reçoit (live_low, live_mid et live_hi).

Il est donc possible de lire les flux depuis celle-ci en RTMP directement, qui seront identiques à ce qui sera vu par un client HLS, mais bénéficient d’une latence proche du direct. Ce sont ces flux qui sont lus par les relais tiers, grâce à une directive pull. Des règles de filtrage entrantes explicites empêchent par défaut toute IP publique de venir se connecter sur le port 1935. Pour autoriser un relai tiers, il nous suffit d’ajouter une exception, ce qui évite d’avoir à relancer le serveur de transcodage. C’est pour cette raison que les ACLs positionnées sur cette application autorisent la lecture depuis n’importe quelle IP source.

La configuration de cette application hls peut ainsi être dupliquée telle quelle sur un serveur frontal nominal : il sera possible d’y publier des flux depuis l’IP du serveur de transcodage grâce à l’ACL renseignée.

# HTTP can be used for accessing RTMP stats
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen      80;
        server_name  localhost;

        # This URL provides RTMP statistics in XML
        location /stat {
            rtmp_stat all;

            # Use this stylesheet to view XML as web page
            # in browser
            rtmp_stat_stylesheet stat.xsl;
            allow 127.0.0.1;
            deny all;
        }

L’URL de statistiques permet d’afficher en temps réel les flux RTMP gérés par le serveur, y compris en local, mais n’est pas indispensable.

        location /control {
            rtmp_control all;
            allow 127.0.0.1;
            deny all;
        }

L’URL de contrôle est indispensable aux triggers utilisés pour basculer la source vidéo lorsque la capture publie un flux provenant de la salle.

        location /stat.xsl {
            # XML stylesheet to view RTMP stats.
            # Copy stat.xsl wherever you want
            # and put the full directory path here
            root /var/www/stat/;
            allow 127.0.0.1;
            deny all;
        }

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

Les dernières directives configurent les chemins relatifs aux erreurs ou aux styles.

Notez qu’il est possible de configurer le serveur de transcodage pour fournir également le service de streaming aux clients finaux : il suffit d’héberger le lecteur HTML / JavaScript et de le rendre accessible, tout en rendant accessible le dossier stockant les chunks HLS et les playlists générés par l’application RTMP hls. Référez vous au billet relatif au serveur frontal pour avoir le détail du paramétrage.

Replay

Le replay automatique des flux enregistrés la journée est déclenché par une crontab dont voici le script :

#!/bin/bash
#
# replay.sh

for i in `ls /var/rec/video/*$(date +%d-%b-%y)*.flv`; do 
        filemtime=`date -r $i +%s`
        curtime=`date +%s`
        fileage=$((curtime-filemtime))

        # debug
        # echo "$filemtime $curtime $fileage"

        if [ $fileage -lt 43200 ]; then
                ffmpeg -re -i $i -vsync 0 -vcodec copy -acodec copy -r 30 -s 1920x1080 -f flv rtmp://127.0.0.1/camsrc/cam1
        fi
done

Tout simplement, il lance ffmpeg sur les fichiers enregistrés par nginx il y a moins de 12h (depuis 8h du matin), et les diffuse en temps réel (-re) en RTMP vers le flux normalement occupé par la caméra venant de la salle. Le reste se passe comme une diffusion normale. Il n’y a pas de réencodage de cet enregistrement.

Notez qu’il n’est pas possible d’alimenter directement l’application de transcodage, car le flux vidéo de la mire «hors ligne» ne serait pas coupé.

Notez également que cette nouvelle diffusion va également être enregistrée localement. Pour être propre, la configuration nginx plus haut pourrait être modifiée pour dupliquer l’application RTMP camsrc sans conserver les recorders.

La crontab est configurée pour lancer le script tous les soirs à 20h :

0 20 * * *      /path/to/replay.sh

Flux de test

Avant la conférence, un flux de test est diffusé en continu, incluant des vidéos libres de droit.

De la même manière que le script de replay à 20h, le script suivant lit en boucle des vidéos en incrustant les crédits. Cette fois, il réencode donc complètement la vidéo source avant de la transmettre au serveur RTMP.

#!/bin/bash
#
# loop.sh

while true; do
        echo "Streaming demo reel"

        credits="Blender Foundation - Big buck Bunny - CC by - www.bigbuckbunny.org"
        ffmpeg -re -i /path/to/demoreel/Big\ Buck\ Bunny\ animation\ \(1080p\ HD\)-XSGBVzeBUbk.mp4 -vsync 0 -vcodec libx264 -preset superfast -vprofile baseline -b:v 6000k -maxrate 6000k -bufsize 6000k -acodec copy -r 30 -s 1920x1080 -vf "drawtext=box=1:boxcolor=black@0.3:fontsize=24:fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansCondensed-Bold.ttf:text='$credits':fontcolor=white@0.6:x=8:y=1056" -f flv rtmp://127.0.0.1/camsrc/cam1 ;
        sleep 120

        credits="Caminandes - Llama Drama - CC by - caminandes.com"
        ffmpeg -re -i /path/to/demoreel/01_llama_drama_1080p.mp4 -vsync 0 -vcodec libx264 -preset superfast -vprofile baseline -b:v 6000k -maxrate 6000k -bufsize 6000k -acodec copy -r 30 -s 1920x1080 -vf "drawtext=box=1:boxcolor=black@0.3:fontsize=24:fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansCondensed-Bold.ttf:text='$credits':fontcolor=white@0.6:x=8:y=1056" -f flv rtmp://127.0.0.1/camsrc/cam1 ;
        sleep 120

        credits="Caminandes - Gran Dillama - CC by - caminandes.com"
        ffmpeg -re -i /path/to/demoreel/02_gran_dillama_1080p.mp4 -vsync 0 -vcodec libx264 -preset superfast -vprofile baseline -b:v 6000k -maxrate 6000k -bufsize 6000k -acodec copy -r 30 -s 1920x1080 -vf "drawtext=box=1:boxcolor=black@0.3:fontsize=24:fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansCondensed-Bold.ttf:text='$credits':fontcolor=white@0.6:x=8:y=1056" -f flv rtmp://127.0.0.1/camsrc/cam1 ;
        sleep 120

        credits="Caminandes - Llamingos - CC by - caminandes.com"
        ffmpeg -re -i /path/to/demoreel/03_caminandes_llamigos_1080p.mp4 -vsync 0 -vcodec libx264 -preset superfast -vprofile baseline -b:v 6000k -maxrate 6000k -bufsize 6000k -acodec copy -r 30 -s 1920x1080 -vf "drawtext=box=1:boxcolor=black@0.3:fontsize=24:fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansCondensed-Bold.ttf:text='$credits':fontcolor=white@0.6:x=8:y=1056" -f flv rtmp://127.0.0.1/camsrc/cam1 ; 
        sleep 120

done

L’objectif est de tester le transcodage afin de vérifier que la charge CPU permet de traiter des vidéos dont l’encodage des mouvements est relativement gourmand en temps de calcul.

Évolutions

Au fil des années, quelques évolutions ont été apportées :

  • enregistrement local sur le serveur en cas de soucis, cela permet d’avoir une autre copie des vidéos au cas où l’enregistrement effectué sur l’ordinateur de capture soit corrompu ;
  • normalisation des profils d’encodage utilisés pour assurer une compatibilité avec un maximum de lecteurs vidéo.

On pourrait envisager d’autres améliorations, mais certaines n’apportent que peu de bénéfices :

  • résolution 4K : les slides sont volontairement limitées à 1080p pour éviter des problèmes de compatibilité des portables des orateurs avec le système de projection, ou des problèmes d’utilisation CPU trop intense sur leur machine ;
  • framerate 60 fps : il a été envisagé et même testé grandeur nature. Cependant, l’utilisation CPU engendrée sur le serveur de transcodage était telle qu’il n’était plus tenable d’assurer une diffusion fluide de 3 qualités différentes.

Malgré cela, les possibilités sont sans limite et les fonctionnalités peuvent être réparties sur plusieurs machines facilement. On pourrait imaginer avoir deux serveurs de transcodage, pour différentes qualités vidéo, un premier renvoyant le flux brut au second. On pourrait également faire de la composition vidéo automatique (picture-in-picture), en poussant simultanément les flux provenant de la capture des slides et de la caméra.

Une autre fonctionnalité pourrait être la diffusion d’un flux audio seul, permettant par exemple aux gens d’écouter malgré tout la conférence dans les transports ou lorsque leur bande passante disponible devient vraiment faible.

Problèmes rencontrés

Pour terminer ce billet, parlons de quelques problèmes rencontrés au fil des éditions du SSTIC.

Charge CPU

Le principal soucis est qu’il n’est pas acceptable d’atteindre 100% de charge CPU (sur chacun des cores) : cela veut dire que ffmpeg n’arrive pas à encoder en temps réel et vous subirez du frame drop.

Veillez à bien tester votre encodage dans diverses situations. Celles qui posent le plus de problèmes sont le traitement des keyframes trop fréquentes, et le changement de scène se traduisant par une génération de multiples keyframe et une charge CPU subitement plus élevée. Typiquement pour le SSTIC, les transitions de plein écran entre l’orateur et les slides génèrent un pic d’utilisation CPU important. Il en va de même pour les mouvements de caméra lorsque l’orateur se déplace.

Si vous observez du frame drop, vérifiez également l’activation du multithread : à partir des résolutions 1080p, il devient indispensable. En 720p, vous prenez un risque lors des situations complexes si vous ne l’utilisez pas.

Encodage multithread

Par défaut, sous Debian, la version de ffmpeg utilisée n’activait pas le multithread contrairement à ce qui était documenté. Veillez bien à vérifier que votre version l’utilise, et configurez le nombre de thread à quelque chose de raisonnable (2 ou 3 par instance devraient suffire, sauf pour de la résolution 4K).

Relais tiers et services de diffusion tiers

Il est imposible de reconfigurer la partie RTMP du serveur nginx sans devoir le relancer complètement, ce qui n’est pas acceptable sur le serveur de transcodage.

Pour servir des flux à des miroirs, à des relais tiers ou à des services de diffusion tiers comme Twitch ou YouTube, vous devrez préconfigurer leurs serveurs et l’URL de diffusion (notamment la stream key «secrète», qui est en fait le nom d’un flux au sein d’une application RTMP).

Si vous souhaitez pouvoir activer et désactiver ce relayage à la volée, il est plus simple d’agir au niveau du parefeu local, en bloquant les flux sortant pour les directives push de votre configuration, ou bien en bloquant les flux entrants sur votre port 1935 pour les serveurs tiers venant se connecter au votre.

Une autre solution serait de mettre un place un second serveur, par exemple le premier frontal, qui assurerait le transfert des flux vers les services et les relais tiers. Cela revient à modifier la configuration détaillée dans le billet dédié aux serveurs frontaux.

Nous avons choisi de gérer cela au niveau du serveur de transcodage et de ne pas exposer celui-ci directement aux clients finaux, afin d’éviter autant que possible la congestion réseau due aux pics de popularité, mais aussi pour «garantir» autant que possible la bande passante vers les miroirs et relais.

Flux hors ligne et changement de la mire

Il est possible de changer à la volée le fichier PNG utilisé pour générer le flux hors ligne, lorsque la régie n’envoit pas sa capture vidéo. On se sert de cette possibilité pour modifier le programme affiché durant les pauses.

En théorie il est possible de scripter un carousel qui bouclerait sur plusieurs images. En pratique, ffmpeg se révèle instable lorsque le fichier d’image change alors qu’il l’utilise pour générer un flux vidéo. De manière aléatoire, cela génère parfois un plantage complet du flux qui n’est rattrapé qu’en relançant complètement le serveur nginx.

Nous modifions cette image manuellement, pendant qu’une conférence est en cours, afin d’être sûr qu’aucune instance de ffmpeg ne l’utilise.

Délai

En théorie, l’usage de RTMP n’induit aucun délai. Si cela est vrai pour l’usage des directives push et pull de la configuration (excepté le délai de connexion TCP et de transmission des paquets), notre transcodage introduit deux usages successifs de ffmpeg. Ce dernier possède ses propres buffers ce qui ajoutera quelques secondes de délai, y compris s’il ne réencode pas un flux.

En plus de ffmpeg sur le serveur de transcodage, la chaîne de capture et d’encodage en régie introduit son propre délai, de même que les tampons des lecteurs vidéos des clients finaux. Au final, il a été observé 5 à 10 secondes de délai par rapport au direct en RTMP.

En HLS, les clients ont tendance à attendre que quelques chunks d’avance soient disponibles pour remplir leur propre buffer. Cela augmente d’autant plus le délai observé, qui passe à environ 30 - 60 secondes.