Streamer la caméra du Raspberry Pi

Introduction

En tant que possesseur d'une caméra sur mon Raspberry Pi 3B, je voulais pouvoir streamer le flux vidéo à la demande, sans avoir recours à des programmes externes.

Après avoir testé pendant quelques temps le mode serveur TCP du programme raspivid via l'option -o tcp://0.0.0.0:3333, je n'étais pas très satisfait. Cela m'obligeait à avoir un script qui relance automatiquement le programme après chaque déconnexion d'un client. De plus, le programme raspivid, plus le script shell, plus la session tmux, ça commence à faire beaucoup pour un truc qui n'est quasiment jamais utilisé!

Vu la distribution Raspbian qui équipe le Raspberry Pi est basé sur Debian, et que celui-ci utilise systemd depuis stretch, j'ai décidé d'ajouter un service pour le streaming.

Plusieurs avantages à utiliser systemd ici:

  • Pas besoin d'installer un gestionnaire de service réseau tel que inetd ou xinetd car c'est déjà possible avec systemd.
  • Pas de processus supplémentaire qui s'exécute tant qu'il n'y a aucun client qui souhaite recevoir le flux vidéo.
  • On bénéficie naturellement du journal de systemd pour le suivi du service.
  • C'est l'avenir, n'en déplaise à ses détracteurs.

Mise en place du service

Pour commencer, on va stocker dans un fichier séparé les options à utiliser pour lancer raspivid. Si dessous le fichier /etc/default/raspivid:

RASPIVID_OPTIONS = -w 1280 -h 720 -a 12 -fl -ih -b 800000 -fps 25 -pf main -g 25 -awb auto

Bien sur, vous pouvez adapter ces options là à votre besoin.

Ensuite on attaque la configuration du service. Pour un service activé à la demande via une connexion réseau, systemd requière la mise en place de 2 unités:

  • Un fichier nom.socket qui décrit le type de connexion réseaux qui va activer le service.
  • Un fichier nom@.service qui décrit le processus à exécuter lorsqu'un client se connecte à la connexion réseaux décrite par nom.socket.

Si dessous, mon fichier raspvid.socket qui active une socket TCP écoutant sur le port 3333 sur toutes les interfaces réseaux locales, en IPv4 ou IPv6:

[Unit]
Description=Raspivid Streaming Socket

[Socket]
ListenStream=3333
Accept=yes
MaxConnections=1
SendBuffer=1M

[Install]
WantedBy=sockets.target

Cette unité systemd sera mise en place dans la cible socket.target quand elle sera activée. Elle indique aussi que c'est à systemd d'accepter la connexion entrante, comme le fait inetd ou xinetd et qu'il ne peut y avoir qu'une seule connexion à la fois.

Si dessous le service associé qui sera lancé à chaque nouvelle connexion. Le fichier doit avoir le même nom de base mais suivi de @ et avec l'extension .service (voir man systemd.socket). Ici, c'est donc raspivid@.service:

[Unit]
Description=Raspivid Streaming Instance

[Service]
EnvironmentFile=-/etc/default/raspivid
ExecStart=-/usr/bin/env raspivid $RASPIVID_OPTIONS -v -n -t 0 -o -
StandardInput=socket
StandardError=journal

Le service est configuré pour utiliser la socket acceptée comme entrée/sortie standard et la sortie d'erreur est redirigée vers le journal.

Le caractère - juste après le = est nécessaire pour ignorer une éventuelle erreur lorsque le service se termine. Idem pour le fichier de configuration.

Il ne reste plus qu'à l'activer:

raspberrypi:~# systemctl enable raspivid.socket
Created symlink /etc/systemd/system/sockets.target.wants/raspivid.socket → /etc/systemd/system/raspivid.socket.
raspberrypi:~# systemctl status raspivid.socket
● raspivid.socket - Raspivid Streaming Socket
   Loaded: loaded (/etc/systemd/system/raspivid.socket; enabled; vendor preset: enabled)
   Active: active (listening) since Tue 2019-01-08 12:31:12 CET; 1 day 3h ago
   Listen: [::]:3333 (Stream)
 Accepted: 0; Connected: 0

janv. 08 12:31:12 raspberrypi systemd[1]: Listening on Raspivid Streaming Socket.

That's all! Maintenant, utiliser votre application de streaming préféré et ouvrez le flux réseau tcp://raspberrypi.local:3333.

Une nouvelle instance du service raspivid@.service est lancée. Par exemple raspivid@0-192.168.0.6:3333-192.168.0.4:41722.service.

raspberrypi:~# journalctl -u raspivid@0-192.168.0.6:3333-192.168.0.4:41722.service
-- Logs begin at Thu 2016-11-03 18:16:42 CET, end at Wed 2019-01-09 16:20:01 CET. --
janv. 08 15:05:55 raspberrypi systemd[1]: Started Raspivid Streaming Instance (192.168.0.4:41722).
janv. 08 15:05:55 raspberrypi raspivid[18482]: "raspivid" Camera App (commit 68e08be38191)
janv. 08 15:05:55 raspberrypi raspivid[18482]: Camera Name imx219
janv. 08 15:05:55 raspberrypi raspivid[18482]: Width 1280, Height 720, filename -
janv. 08 15:05:55 raspberrypi raspivid[18482]: Using camera 0, sensor mode 0
janv. 08 15:05:55 raspberrypi raspivid[18482]: GPS output Disabled
janv. 08 15:05:55 raspberrypi raspivid[18482]: bitrate 800000, framerate 25, time delay 0
janv. 08 15:05:55 raspberrypi raspivid[18482]: H264 Profile main
janv. 08 15:05:55 raspberrypi raspivid[18482]: H264 Level 4
janv. 08 15:05:55 raspberrypi raspivid[18482]: H264 Quantisation level 0, Inline headers Yes
janv. 08 15:05:55 raspberrypi raspivid[18482]: H264 Fill SPS Timings No
janv. 08 15:05:55 raspberrypi raspivid[18482]: H264 Intra refresh type (null), period 25
janv. 08 15:05:55 raspberrypi raspivid[18482]: H264 Slices 1
janv. 08 15:05:55 raspberrypi raspivid[18482]: Wait method : Capture forever
janv. 08 15:05:55 raspberrypi raspivid[18482]: Initial state 'record'
janv. 08 15:05:55 raspberrypi raspivid[18482]: Preview No, Full screen Yes
janv. 08 15:05:55 raspberrypi raspivid[18482]: Preview window 0,0,1024,768
janv. 08 15:05:55 raspberrypi raspivid[18482]: Opacity 255
janv. 08 15:05:55 raspberrypi raspivid[18482]: Sharpness 0, Contrast 0, Brightness 50
janv. 08 15:05:55 raspberrypi raspivid[18482]: Saturation 0, ISO 0, Video Stabilisation No, Exposure compensation 3
janv. 08 15:05:55 raspberrypi raspivid[18482]: Exposure Mode 'nightpreview', AWB Mode 'auto', Image Effect 'none'
janv. 08 15:05:55 raspberrypi raspivid[18482]: Flicker Avoid Mode 'off'
janv. 08 15:05:55 raspberrypi raspivid[18482]: Metering Mode 'average', Colour Effect Enabled No with U = 128, V = 128
janv. 08 15:05:55 raspberrypi raspivid[18482]: Rotation 0, hflip No, vflip No
janv. 08 15:05:55 raspberrypi raspivid[18482]: ROI x 0.000000, y 0.000000, w 1.000000 h 1.000000
janv. 08 15:05:55 raspberrypi raspivid[18482]: Camera component done
janv. 08 15:05:55 raspberrypi raspivid[18482]: Encoder component done
janv. 08 15:05:55 raspberrypi raspivid[18482]: Starting component connection stage
janv. 08 15:05:55 raspberrypi raspivid[18482]: Connecting camera video port to encoder input port
janv. 08 15:05:56 raspberrypi raspivid[18482]: Enabling encoder output port
janv. 08 15:05:56 raspberrypi raspivid[18482]: Starting video capture
janv. 08 15:06:47 raspberrypi raspivid[18482]: mmal: Failed to write buffer data (4096 from 5109)- aborting
janv. 08 15:06:47 raspberrypi raspivid[18482]: Finished capture
janv. 08 15:06:47 raspberrypi raspivid[18482]: Closing down
janv. 08 15:06:47 raspberrypi raspivid[18482]: Close down completed, all components disconnected, disabled and destroyed

Attention, le programme raspivid a été patché pour qu'il se termine correctement quand le client se déconnecte. Voir le patch soumis au projet raspberrypi/userland sur Github.

Commentaires

links

social