Dans mon travail, on utilise souvent de vieux équipements basés sur des OS qui ne sont plus maintenus (coucou Windows XP!), et qu'il serait dangereux de connecter à internet. Pourtant, nous avons besoin de savoir ce qu'il se passe sur l'ordinateur sans être constamment devant. La solution la plus simple qui nous est venue est de brancher une webcam devant l'écran et de streamer le tout sur un serveur web moderne (et sécurisé).
La première solution que nous avons employée est MotionEye, notamment dans sa version OS pour Raspberry pi.
ça fait 5 ans qu'on l'utilise, on peut dire que c'est plutôt pas mal, même si ça repose sur une succession d'images fixes balancées vers le navigateur.
Mais, il y a un mais, ça ne fonctionne pas toujours. Et on ne sait pas pourquoi. Boot, marche pas, reboot marche pas, reboot marche pas, reboot marche.
On s'est donc penché sur une autre solution, basé sur du streaming de vraies vidéos. L'idée est que, de nos jours avec toutes ces visios, “on” doit pouvoir maîtriser les flux vidéos.
Qui dit stream vidéo, dit ffmpeg (surtout sur une base linux). En vrai, ça n'a pas été si facile que ça, mais au final ça marche et la solution est assez élégante.
Allez, je vous montre comment on a fait.
Dans les grandes lignes, on va demander à ffmpeg de capturer le flux vidéo depuis la webcam et de l'envoyer vers un serveur (nginx avec le bon module), qui se chargera de le diffuser à tous les clients qui se connectent en http.
On notera que ffmpeg ne peut diffuser que s'il y a un récepteur actif, c'est le serveur nginx qui joue ce rôle, même si aucun client n'est connecté.
On aura un usage headless, or les versions “modernes” de Raspberry pi OS n'incluent pas de ssh par défaut, ni d'utilisateur avec login/mdp. Il faut les gérer au premier démarrage… ou rajouter des petits fichiers sur la partition boot
avant le premier démarrage. J'ai noté ici comment faire.
Installation : apt install nginx libnginx-mod-rtmp
.
On configure nginx, le bloc rtmp doit être à la racine, il ne doit pas être inclus dans le bloc http comme le son les vhost :
# nginx.conf # Configuration de nginx en relais rtmp rtmp { server { listen 1935; # Listen on standard RTMP port chunk_size 4096; # les paquets feront 4096 octets, le standard application live { live on; record off; } } }
On peut envoyer un flux vers le serveur en indiquant son ip `rtmp:192.168.xxx.xxx:1935/live/stream` Le port est facultatif, car nous avons utilisé 1935 qui est le standard. Le choix de “live” comme indicateur (sous dossier) est arbitraire, ce pourrait être n'importe quoi, mais il faut que ce soit le même entre la config nginx et la config de ffmpeg. Le terme “stream” est également arbitraire, il s'agit du nom du flux, on pourrait imaginer un autre nom comme GeoCamera2. On pourrait ensuite se connecter au serveur avec un client comme VLC en indiquant l'adresse `rtmp:192.168.xxx.xxx:1935/live/stream` (mais il faut d'abord diriger le flux de la webcam au serveur)
C'est bien, mais on ne peut pas accéder au flux RTMP dans le navigateur (peut-être certains lecteurs media en JS le peuvent, mais je ne les ai pas trouvés)
On va diffuser jusqu'à un navigateur web grâce au protocole/format HLS
# nginx.conf # Configuration pour servir le flux en HLS rtmp { server { listen 1935; # Listen on standard RTMP port chunk_size 4096; # les paquets feront 4096 octets, le standard application live { live on; record off; # Turn on HLS hls on; hls_path /dev/shm/hls/; hls_fragment 3; hls_playlist_length 60; } } }
On notera que nous avons choisi de mettre les fichiers écrits par HLS dans un dossier dans la RAM (/dev/shm, pour être plus rapide que sur la carte SD et éviter d'user celle-ci), il convient que ce dossier existe et soit accessible en écriture à l'user du serveur web (www-data ou nginx, selon la config).
Il faut ensuite gérer 2 vhost : un pour le flux HLS, l'autre pour la page web qui contiendra le lecteur qui consommera le flux. Ce coup-ci, cela se fait dans un vhoist classique (fichier xxx.conf dans le repertoire “httpd” ou “sites-enabled” selon la config).
# Servir le flux HLS (de simples fichiers, mais avec les headers qui vont bien) # mon_vhost.conf ou inclus dans le bloc http server { listen 8080; location /hls { # Disable cache add_header 'Cache-Control' 'no-cache'; # CORS setup add_header Access-Control-Allow-Origin *; add_header 'Access-Control-Allow-Origin' '*' always; add_header 'Access-Control-Expose-Headers' 'Content-Length'; # allow CORS preflight requests if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } root /dev/shm; } }
On notera l'accès au dossier parent de la où se trouve le flux HLS, `/dev/shm/hls → /dev/shml`. On notera les directives pour autoriser les flux qui viennent d'ailleurs que le site web de base (CORS, Cross Origins…)
# Servir le site web qui contiendra le player (simple fichier html) # mon_vhost.conf ou autre ou inclus dans le bloc http # dans un bloc server { }, on peut juste servir le fichier root /path/to/file.html location / { add_header Access-Control-Allow-Origin *; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; } # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; }
On notera les entêtes pour les autorisations de CORS (Cross Origins) bla bla…
On va s'appuyer sur `videojs` en version > 7.0 (important, sinon le flux HLS nécessite une lib JS additionnelle). On peut utiliser le CDN ou le proposer en statique (on préfèrera mais le code demo ci-dessous utlise le CDN) ``` <!DOCTYPE html> <html lang=“en”>
<head> <meta charset="UTF-8"> <title>Live Streaming</title> <link href="//vjs.zencdn.net/8.21.1/video-js.css" rel="stylesheet"> </head> <body> <p>VIDEO.JS HLS</p>
<video class="video-js" controls preload="auto" width="640" height="480" data-setup="{}" > <source src="http://192.168.0.63:8080/hls/stream.m3u8" type="application/x-mpegURL" /> <p class="vjs-no-js"> To view this video please enable JavaScript, and consider upgrading to a web browser that <a href="https://videojs.com/html5-video-support/" target="_blank" >supports HTML5 video</a > </p> </video>
<script src="https://vjs.zencdn.net/8.21.1/video.min.js"></script>
</body>
</html> ``` On notera l'url vers le flux HLS configuré précédemment.
Voilà, on a ce qu'il faut pour consommer le flux video, il faut maintenant le capturer, c'est le travail de ffmpeg.
On utilise le paquet v4l-utils
, préinstallé.
Il fournit la commande v4l2-ctl –list-devices
, on obtient qque chose dans le genre :
user@raspberrypi:~ $ v4l2-ctl --list-devices bcm2835-codec-decode (platform:bcm2835-codec): /dev/video10 /dev/video11 [...] /dev/media2 bcm2835-isp (platform:bcm2835-isp): /dev/video13 /dev/video14 [...] /dev/media0 /dev/media1 # et si la caméra webcam est branchée Microsoft® LifeCam Studio(TM): (usb-3f980000.usb-1.1.3): /dev/video0 /dev/video1 /dev/media3
Les bcm2835 ne nous intéressent pas, il s'agit probablement de la gestion de la caméra par le connecteur CSI (nappe). La webcam que nous avons est bien de marque Microsoft, et elle est branchée en USB. C'est donc ce dernier bloc qui nous intéresse.
⇒ On apprend qu'elle accessible par /dev/video0
.
ffmpeg permet la capture de video depuis la webcam. C'est un outil qui se lance en ligne de commande, et qui nécessite un certain nombre de paramètres pour bien définir et optimiser le flux. Il n'est pas rare de passer 1h (voir beaucoup plus) à peaufiner les paramètres de cette mono-ligne de commande quand on est débutant sur ffmpeg. Mais quelle jouissance quand ça fonctionne :)
Sur mon Raspberry Pi OS, l'installation de ffmpeg nécessite 156 paquets et 400 Mo.
On peut voir quels sont les formats
On peut lire un peu partout que WebRTC est un protocole moderne (post-covid) qui gère la transmission de flux vidéo avec des latences sub-seconde, et pris en charge nativement par les navigateurs web modernes.
Ce serait donc une solution intéressante pour améliorer notre solution qui souffre d'une latence entre 5 et 10 secondes, et qui nécessite une librairie JS côté client.
Liens (pas testés):
Je note ici en vrac des pistes que nous avons abordées, puis écartées ou pas creusées.
Elles ont toutes le point commun du mode opératoire en 2 temps : capture d'images fixes, puis diffusion par un serveur web.
On l'installe : apt install fswebcam
fswebcam : generate images for a webcam options interessantes : -d /dev/video1 # sélection device -r 1920x1080 # choisir résolution --timestamp "%Y-%m-%d %H:%M:%S" # afficher le timestamp sur l'image # semble inutile, y'a déjà par défaut --loop X # mettre le programme en boucle, toutes les X secondes /!\ pas de conditions de fin, loop jusqu'à la fin des temps --save pic%Y-%m-%d_%H:%M:%S.jpg # pour enregistrer les images sous un nom différent, par exemple avec un horodatage -b # run in background, n'a pas l'air de fonctionner
3 usages :
Il convient ensuite de créer un serveur web qui diffuse les images.
Idées :
/dev/shm
, qui n'est autre qu'une zone de la RAM pour éviter d'user la carte SD (et gagner en vitesse)
rpicam-still –list-camera
: aucune caméra USB
Pourtant v4l2-ctl en trouve
⇒ rpicam ne fonctionne pas avec les caméras USB (fonctionne qu'avec la caméra Pi sur port CSI, dédié avec la nappe ?)
L'idée est d'utiliser la lib picamera2 pour faire les captures, puis de traiter les photos avec un serveur web comme pour fswebcam. L'intérêt est qu'on peut intégrer la capture (picamera2 est un module Python) et la diffusion par le serveur web (Python aussi). Cette solution n'a pas été testée. Voici des liens qui en parle : https://raspberrytips.com/how-to-live-stream-pi-camera/ (Méthode 2) https://github.com/EbenKouao/pi-camera-stream-flask/blob/master/main.py