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
Installation des dépendances
1apt-get install nginx libnginx-mod-rtmp ffmpeg mediainfo certbot python-certbot-nginx stunnel4
nginx
etlibnginx-mod-rtmp
sont obligatoiresffmpeg
etmediainfo
sont des outils de transcodage ou d'analyses de fichiers audiovisuelscertbot
etpython-certbot-nginx
nous aideront à installer un certificat Let's Encrypt pour servir les données en httpsstunnel4
va servir à chiffrer un fluxrtmp
enrtmps
pour l'envoi versFacebook
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...