Monter un serveur RTMP/HLS avec NGinx et diffuser vers Facebook

Un serveur rtmp permet de transmettre en temps réel un flux audio/vidéo. Voyons comment en monter un facilement avec des outils libres.

Dans ce projet sur GitHub, j'ai fait une image docker d'un simple nginx + nginx-rtmp-module avec une config. mini. C'est pédagogiquement utile mais on peut s'éviter la phase de compilation vu que ces paquets sont disponibles dans Debian. Voici donc maintenant un déroulé d'installation en se basant sur une installation fraîche de Debian 10, préparamétrée à ma sauce

Les étapes seront :

  • Installation des dépendances
  • Paramétrage de NGinx pour le RTMP
  • Paramétrage du HLS et HTTPS
  • Diffusion du flux vers Facebook
  • Diffusion vers d'autres services

Schéma

Installation des dépendances

1apt-get install nginx libnginx-mod-rtmp ffmpeg mediainfo certbot python-certbot-nginx stunnel4
  • nginx et libnginx-mod-rtmp sont obligatoires
  • ffmpeg et mediainfo sont des outils de transcodage ou d'analyses de fichiers audiovisuels
  • certbot et python-certbot-nginx nous aideront à installer un certificat Let's Encrypt pour servir les données en https
  • stunnel4 va servir à chiffrer un flux rtmp en rtmps pour l'envoi vers Facebook

Paramétrage de NGinx pour le RTMP

Créons déjà ces 2 répertoires de stockage avec les droits en écriture pour NGinx :

1mkdir -p /data/hls /data/records
2chown -R www-data:www-data /data

voici le fichier rtmp.conf commenté, à copier/coller avec votre éditeur de texte préféré dans le répertoire /etc/nginx/modules-available

 1rtmp_auto_push on;
 2
 3rtmp {
 4    server {
 5        listen 1935; # port standard RTMP
 6        chunk_size 4096;
 7
 8        # tous les flux sources arrivent là, dans l'app "push"
 9        application push {
10            live on;
11
12            # on enregistre tous les flux entrants
13            record all; # audio + video
14            # veiller à ce que ce répertoire existe
15            # et soit inscriptible par nginx (user www-data)
16            record_path /data/records;
17            record_suffix -%Y%m%d-%H%M%S.flv;
18            record_max_size 64M;
19
20            # ajoute un suffixe timestamp au nom du fichier .flv
21            # mais c'est doublon avec record_suffix qui a un timestamp
22            #record_unique on;
23
24            # on autorise que ces ips à publier
25            #allow publish 127.0.0.1;
26            #deny publish all;
27
28            # on n'autorise ou pas la lecture directe via rtmp
29            # deny play all;
30
31            # on envoi le flux reçu vers l'app "facebook"
32            # cf. plus bas
33            push rtmp://localhost/facebook;
34
35            # transcodage(s) vers l'app "show"
36            # ffmpeg est utile. Prend bcp de CPU, à surveiller
37            # exemple mini: upload en 720, passthru 720, transcode en 480 + 360
38            exec ffmpeg -i rtmp://localhost/$app/$name -async 1 -vsync -1
39                 -c:v libx264 -c:a aac -b:v  768k -b:a  96k -vf "scale=640:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_360
40                 -c:v libx264 -c:a aac -b:v 1024k -b:a 128k -vf "scale=854:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_480
41                 -c copy -f flv rtmp://localhost/show/$name_src;
42
43            # exemple complet: upload en 1080, transcode en 1080 + 720 + 480 + 360 + 240 + 144
44            #exec ffmpeg -i rtmp://localhost/$app/$name -async 1 -vsync -1
45            #     -c:v libx264 -c:a aac -b:v  128k -b:a  32k -vf "scale=256:trunc(ow/a/2)*2"  -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_144
46            #     -c:v libx264 -c:a aac -b:v  256k -b:a  64k -vf "scale=426:trunc(ow/a/2)*2"  -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_240
47            #     -c:v libx264 -c:a aac -b:v  768k -b:a  96k -vf "scale=640:trunc(ow/a/2)*2"  -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_360
48            #     -c:v libx264 -c:a aac -b:v 1024k -b:a 128k -vf "scale=854:trunc(ow/a/2)*2"  -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_480
49            #     -c:v libx264 -c:a aac -b:v 2048k -b:a 128k -vf "scale=1280:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_720
50            #     -c:v libx264 -c:a aac -b:v 3096k -b:a 128k -vf "scale=1920:trunc(ow/a/2)*2" -tune zerolatency -preset veryfast -g 10 -crf 23 -f flv rtmp://localhost/show/$name_1080
51            #     -c copy -f flv rtmp://localhost/show/$name_src;
52        }
53
54        application show {
55            live on; # Allows live input from above
56            hls on; # Enable HTTP Live Streaming
57
58            # Les .m3u8 et les .ts seront écrits ici
59            # le répertoire doit exister et être inscriptible par nginx (user www-data)
60            hls_path /data/hls/;
61            hls_fragment 3;
62            hls_playlist_length 60;
63
64            # On propose les versions transcodées suivant la bande passante du client
65            # cas mini
66            hls_variant _360 BANDWIDTH=864000;
67            hls_variant _480 BANDWIDTH=1152000;
68            hls_variant _src BANDWIDTH=1500000;
69
70            # cas complet
71            #hls_variant _144  BANDWIDTH=160000;
72            #hls_variant _240  BANDWIDTH=320000;
73            #hls_variant _360  BANDWIDTH=864000;
74            #hls_variant _480  BANDWIDTH=1152000;
75            #hls_variant _720  BANDWIDTH=2176000;
76            #hls_variant _1080 BANDWIDTH=3200000;
77            #hls_variant _src  BANDWIDTH=4000000;
78        }
79
80        application facebook {
81            live on;
82            record off;
83
84            # seulement alimenté par l'app "push" plus haut, donc local
85            allow publish 127.0.0.1;
86            deny publish all;
87
88            # Depuis 2019, Facebook impose que le flux soit transmis en rtmps (chiffré)
89            # or le module nginx-rtmp ne fait pas de chiffrement, 
90            # l'url réelle d'envoi pour Facebook est
91            #push rtmps://live-api-s.facebook.com:443/rtmp/<streamKey>;
92            # on va passer par `stunnel` (cf. doc plus bas)
93            push rtmp://127.0.0.1:19350/rtmp/<streamKey>;
94        }
95    }
96}

On active cette conf :

1ln -s /etc/nginx/modules-available/rtmp.conf /etc/nginx/modules-enabled/rtmp.conf

On vérifie que la syntaxe est correcte :

1nginx -t

On recharge le serveur

1systemctl reload nginx

On peut à ce stade alimenter notre serveur rtmp (appName push, streamKey test) avec une commande ffmpeg, par exemple en lui envoyant une mire :

1ffmpeg -re -f lavfi -i smptebars -crf 18 -s 1280x720 -r 25 -f flv rtmp://stream.example.com/push/test

On peut constater que le répertoire /data/records doit se remplir

Vous pouvez le lire avec vlc, mpv ...

Alimenter le serveur via le logiciel OBS:

En paramètre de sortie:

  • Service: personnalisé
  • Serveur: rtmp://stream.example.com/push/
  • Clé de stream: test

Paramétrage du HLS et HTTPS

HLS est un protocole de streaming video instauré par Apple mais généralisé. C'est avec ce format qu'on aura la meilleure compatibilité client donc on va se concentrer dessus. D'autres formats comme DASH existent cependant.

Créons le fichier de vhost dans NGinx (nommez le fichier avec le vrai domaine)

fichier stream.example.com.conf dans le répertoire /etc/nginx/sites-available :

 1server {
 2    listen 80;
 3    server_name stream.example.com;
 4    location / {
 5       return 301 https://$host$request_uri;
 6    }
 7}
 8server {
 9    listen 443 ssl http2;
10    server_name stream.example.com;
11
12    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
13    ssl_prefer_server_ciphers on;
14    ssl_ciphers "EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA256:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EDH+aRSA+AESGCM:EDH+aRSA+SHA256:EDH+aRSA:EECDH:!aNULL:!eNULL:!MEDIUM:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!RC4:!SEED";
15
16    # ces lignes seront ajoutées par Let's Encrypt
17    #ssl_certificate /etc/letsencrypt/live/stream.example.com/fullchain.pem;
18    #ssl_certificate_key /etc/letsencrypt/live/stream.example.com/privkey.pem;
19
20    location /hls {
21        # Disable cache
22        add_header Cache-Control no-cache;
23
24        # CORS setup
25        add_header 'Access-Control-Allow-Origin' '*' always;
26        add_header 'Access-Control-Expose-Headers' 'Content-Length';
27
28        # allow CORS preflight requests
29        if ($request_method = 'OPTIONS') {
30            add_header 'Access-Control-Allow-Origin' '*';
31            add_header 'Access-Control-Max-Age' 1728000;
32            add_header 'Content-Type' 'text/plain charset=UTF-8';
33            add_header 'Content-Length' 0;
34            return 204;
35        }
36
37        types {
38            application/vnd.apple.mpegurl m3u8;
39            video/mp2t ts;
40        }
41
42        # le répertoire de base des données
43        # vous l'avez déjà créé plus haut
44        root /data/;
45    }
46
47    location /stat {
48        rtmp_stat all;
49        rtmp_stat_stylesheet stat.xsl;
50        add_header Refresh "3; $request_uri";
51    }
52
53    location /stat.xsl {
54        root /data;
55    }
56}

Ajoutons une page web de stats utiles qui sera accessible via https://stream.example.com/stat.xsl

1curl https://raw.githubusercontent.com/arut/nginx-rtmp-module/master/stat.xsl -o /data/stat.xsl

On active cette conf :

1ln -s /etc/nginx/sites-available/stream.example.com.conf /etc/nginx/sites-enabled/stream.example.com.conf

On vérifie que la syntaxe est correcte :

1nginx -t

On recharge le serveur

1systemctl reload nginx

On doit générer un certificat TLS/SSL pour profiter du chiffrement HTTPS. On a précédemment installé certbot. Utilison le

1certbot --nginx

La commande va nous demander pour quel domaine créer le certificat, remplacez stream.example.com par votre domaine. Un email est demandé pour vous envoyer une notif à l'expiration du certificat (il dure 90 jours).

Si tout va bien, à ce stade on a un serveur rtmp/http/https/hls fonctionnel et on peut envoyer un flux dans l'app push et constater que /data/hls contient certains fichiers (uniquement présents si un flux est en cours d'émission) :

  • streamKey.m3u8 : la liste de lecture, c'est ce fichier à servir à votre lecteur multimedia. son url publique devrait être https://stream.example.com/hls/streamKey.m3u8
  • xxx_xxx.ts : un exemple de fichiers .ts, ce sont les flux audio/video segmentés, découpés en bouts de quelques secondes, qui seront joués à la suite par le lecteur vidéo

Diffusion du flux vers Facebook

Comme évoqué précédemment, le module rtmp de nginx ne permet pas de faire du rtmps qui est nécessaire pour streamer sur Facebook depuis mai 2019. On va utiliser stunnel (déjà installé) qui va chiffrer le flux.

créez le fichier /etc/stunnel/stunnel.conf

1setuid = stunnel4
2setgid = stunnel4
3pid=/tmp/stunnel.pid
4output = /var/log/stunnel4/stunnel.log
5include = /etc/stunnel/conf.d

dans le fichier /etc/default/stunnel4 ajouter la ligne :

1ENABLED=1

creez le fichier /etc/stunnel/conf.d/facebook.conf

1[facebook]
2client = yes
3accept = 127.0.0.1:19350
4connect = live-api-s.facebook.com:443

redémarrer le tunnel

1sudo systemctl restart stunnel4 && systemctl status stunnel4

dans /etc/nginx/modules-available/rtmp.conf, si ce n'est pas déjà fait, remplacer la ligne

1push rtmps://live-api-s.facebook.com:443/rtmp/<streamKey>;

par

1push rtmp://127.0.0.1:19350/rtmp/<streamKey>;

Le port 19350 (sur lequel écoute stunnel) n'a pas besoin d'être ouvert vers l'extérieur. C'est juste le canal de communication local entre NGinx et stunnel.

Au final on a le routage suivant :

1OBS ------> NGinx ------> stunnel -------> Facebook
2     rtmp          rtmp            rtmps

Et voilà, vous devriez pouvoir diffuser sur Facebook

Note: la clé de stream est fournie par Facebook au moment de la création de la diffusion dans https://www.facebook.com/live/producer/. Vous devez la saisir dans rtmp.conf puis redémarrer NGinx pour la prise en compte.

Diffusion vers d'autres services

La diffusion vers des services comme Youtube ou Twitch n'est pas très difficile. Bien que pas testé personnellement à ce jour, elles se résument à ajouter une ligne push rtmp://... dans la section application du fichier rtmp.conf. Peut être un transcodage particulier ffmpeg suivant les contraintes de chaque service...

comments powered by Disqus