Diffusion des flux vidéos de caméra IP en HTTP Live Streaming (HLS)

HTTP Live Streaming (aussi appelé HLS) est un protocole de streaming basé sur le protocole HTTP. Il fonctionne en segmentant le flux multimédia en une séquence de petits fichiers (ts). Ces fichiers sont ensuite lus dans une liste de lecture (playlist) de type M3U8. l’HTTP Live Streaming est capable de traverser les pare-feux ou serveur proxy qui laissent passer le trafic HTTP standard. Ce protocole nécessite le format MPEG-4 (H.264).

La liste des clients et serveurs compatibles avec HLS est disponible sur Wikipedia : clients et serveurs supportant HTTP Live Streaming. La diffusion des flux HLS est réalisé avec l’application FFmpeg tournant sur le même serveur que le serveur HTTP.

Le serveur HTTP doit être configuré pour "pointé" vers le répertoire où l’application FFmpeg génère les fichiers fragments (ts) et liste de lecture (m3u8). Nginx est utilisé comme serveur HTTP. Il est nécessaire que le module ngx_http_secure_link_module soit installé pour que Nginx puisse sécuriser les liens HLS.

image_hls_telesurveillance
Figure: Télésurveillance Architecture

Matèriels et logiciels

Maèriels :

  • Serveur maison
  • Caméra TRENDnet TV-IP8621C
  • Caméra WansView W2
  • Routeur TP-Link 1750

L’utilisation d’un Raspberry PI doit être possible sous condition que la sortie de vos caméras soient déjà dans un format compatible avec HLS. La transformation RTSP vers HLS sans devoir réencoder la vidéo ne requière pas beaucoup de resource processeur par FFmpeg (occupation de 5% pour un seul processeur pour deux caméra). Par contre, l’encodage de la vidéo peu facilement monopoliser le(s) processeur et prendre trop de temps pour un affichage fluide coté client.

Il faut aussi prendre en compte la fréquence des accès disques pour la génération des fichiers de la liste de lecture et des fragments. Les Raspberry PI sont souvent équipé d’une carte SD dont la durée de vie est inversement proporsioné aux accès disque (surtout écriture) : Comment prolonger la durée de vie de vos cartes SD sur Raspberry Pi.

Logiciels :

  • Système d’exploitation du serveur : Debian GNU/Linux 9.5 (stretch)
  • Logiciel pour encoder la sortie des caméras en HLS : FFmpeg v3.2.12
  • Serveur HTTP : Nginx v1.14.0
  • Moteur PHP : PHP-FPM 7.1 v7.1.23
  • Lecteur vidéo web : Videojs v7.2.3
  • Lecteur vidéo : VLC, FFplay

HTTP Live Streaming

Un fichier de liste de lecture est identifiable par le chemin de son URI (.m3u8 ou .m3u) ou par le Content-Type de la réponse du serveur HTTP (application/vnd.apple.mpegurl ou audio/mpegurl).

Une liste de lecture est une liste de lecture multimédia si toutes les URL qui la compose sont des URL vers des fragments multimédia (*.ts par example). Une liste de le lecture est une liste de lecture maitresse is toutes les URL qui la compose sont des URL vers des liste de lecture multimédia. L’utilisation de liste de lecture maitresse pour l’utilisation de flux adaptatif ne sera pas aborder dans ce tutoriel. Cela fera surement le sujet d’un prochain tutoriel.

Avec une liste de lecture en direct ("live"), le serveur peut limiter le nombre de segments dans la liste en enlevant les segments dans l’ordre de chronologique (les plus vieux en premier). L’étiquette EXT-X-MEDIA-SEQUENCE doit être valorisée et incrémentée de 1 à chaque fois qu’un segment est enlevé de la liste. Les fragements enlevés de la liste doivent rester disponible pour le client pendant une pèriode égale à la durée du segment plus le temps de la plus longue liste de lecture qui a distribué ce segement. L’étiquette EXT-X-PLAYLIST-TYPE ne peut pas être utilisée dans une liste de lecture en direct.

Une liste de lecture simple :

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:12
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:3
#EXTINF:2.005000,
/hls/cours-12.ts
#EXTINF:1.950000,
/hls/cours-13.ts
#EXTINF:2.006000,
/hls/cours-14.ts

EXT-X-VERSION : version de la compatibilité de liste de lecture. Lire le paragraphe 7 de la RFC8216 pour connaitre les étiquettes (tags) supportés dans la liste de lecture en fonction de la version.

EXT-X-MEDIA-SEQUENCE : numéro du premier fragments multimédia dans la liste de lecture.

EXT-X-ALLOW-CACHE: notifie le client s’il devrait mémoriser les fragments téléchargé pour les re-lire ultèrieurement. Cette étiquette a été supprimée dans la version 7 de la RFC8216.

EXT-X-TARGETDURATION : taille maximale d’un segment multimédia composant la liste de lecture (en secondes). Cette taille ne peut pas changer.

EXTINF : durée du segment multimédia qui suit cette étiquette (en secondes).

EXT-X-ENDLIST : indique la fin de la liste de lecture. plus aucun fragment ne sera ajouté. FFmpeg ajoute cette etiquette dans la dernière iteration de la liste de lecture avant d’arrêter l’encodage des données.

TODO: A vérifier :

   If the server wishes to remove segments from a Media Playlist
   containing an EXT-X-DISCONTINUITY tag, the Media Playlist MUST
   contain an EXT-X-DISCONTINUITY-SEQUENCE tag.  Without the EXT-X-
   DISCONTINUITY-SEQUENCE tag, it can be impossible for a client to
   locate corresponding segments between Renditions.

   If the server removes an EXT-X-DISCONTINUITY tag from the Media
   Playlist, it MUST increment the value of the EXT-X-DISCONTINUITY-
   SEQUENCE tag so that the Discontinuity Sequence Numbers of the
   segments still in the Media Playlist remain unchanged.  The value of
   the EXT-X-DISCONTINUITY-SEQUENCE tag MUST NOT decrease or wrap.
   Clients can malfunction if each Media Segment does not have a
   consistent Discontinuity Sequence Number.

TODO: A faire ? : avec le flag "modified" ? https://nginx.org/en/docs/http/ngx_http_headers_module.html#expires

   If a server plans to remove a Media Segment after it is delivered to
   clients over HTTP, it SHOULD ensure that the HTTP response contains
   an Expires header that reflects the planned time-to-live.

Utiliser FFmpeg pour convertir la sortie RTSP des caméra en HLS

Les commandes suivantes permettent de tester pendant 30 secondes (-t 00:00:30) la génération des fichiers HLS par FFmpeg pour les caméras TV-IP8621C et W2 dans le répertoire /tmp/hls/ du serveur.

La description des paramètres compatible avec la version de FFmpeg peut être afficher avec la commande : man ffmpeg-formats.

TRENDnet TV-IP8621C

La caméra TRENDnet nécessite de convertir le flux vidéo en H264 et de forcer la création des frames clefs. Le flux audio de la TRENDnet est déjà dans un format compatible avec HLS (AAC). Il n’est pas utile de le réencoder.

$: ffmpeg -nostdin -t 00:00:30 -fflags nobuffer 
 -rtsp_transport tcp 
 -i rtsp://service:mon_mot_de_passe@192.168.1.120:554/play1.sdp 
 -r 15  
 -g 30 
 -c:v libx264 
 -profile:v baseline 
 -b:v 350k 
 -pix_fmt yuv420p 
 -force_key_frames "expr:gte(t,n_forced*3)"  
 -c:a copy 
 -movflags frag_keyframe+empty_moov 
 -hls_flags delete_segments+append_list 
 -f segment  
 -segment_time 1 
 -segment_format mpegts 
 -segment_list /tmp/hls/test_trendnet.m3u8 
 -segment_list_type m3u8 
 -segment_list_flags live 
 -segment_list_entry_prefix /hls/ 
 -segment_list_size 3 
 /tmp/hls/test_trendnet-%d.ts

WansView W2

La caméra W2 ne nécessite pas de réencoder son flux vidéo (-codec:v copy). La sortie de la caméra utilise déjà H264. La caméra W2 n’a pas de micro. Il n’est pas utile d’ajouter un piste audio (-an).

$: ffmpeg -nostdin -t 00:00:30 -fflags nobuffer 
 -rtsp_transport tcp 
 -i rtsp://service:mon_mot_de_passe@192.168.1.121:554/live/ch1 
 -vsync passthrough 
 -copyts 
 -codec:v copy 
 -an 
 -movflags frag_keyframe+empty_moov 
 -hls_flags delete_segments+append_list 
 -f segment 
 -segment_time 1 
 -segment_format mpegts 
 -segment_list /tmp/hls/test_w2.m3u8 
 -segment_list_type m3u8 -segment_list_flags live 
 -segment_list_entry_prefix /hls/ 
 -segment_list_size 3 
 /tmp/hls/test_w2-%d.ts

Il est aussi possible d’utiliser le muxer HLS de FFmpeg. La commande suivante demande à FFmpeg de supprimer les anciens segements (-hls_flags delete_segments). La date local est utilisée pour le nomage des fichiers segments (-use_localtime 1).

$: ffmpeg -nostdin -t 00:00:30 -fflags nobuffer 
 -rtsp_transport tcp 
 -i rtsp://service:mon_mot_de_passe@192.168.1.121:554/live/ch1 
 -vsync passthrough 
 -copyts 
 -codec:v copy 
 -an 
 -segment_format mpegts 
 -use_localtime 1 
 -hls_segment_filename '/tmp/hls/test_w2-%Y%m%d-%s.ts' 
 -hls_time 1 
 -hls_allow_cache 0 
 -hls_flags delete_segments+append_list 
 -hls_list_size 3 
 -hls_base_url /hls/ 
 /tmp/hls/test_w2.m3u8

FFmpeg options 3 -fflags nobuffer :

réduit la latence en désactivant la mémoire tampon durant la phase d’analyse initialle des flux d’entrées.

FFmpeg options 2 -rtsp_transport tcp -i rtsp://... :

configure le protole de transport TCP pour le flux d’entrée RTSP à transferer.

FFmpeg options 1 -vsync passthrough -copyts -codec:v copy -an :

transfert les piste avec toutes leurs frames, sans changer le timestamp, ni l’encodage video. Le flux RTSP en entrée doit être dans un format compatible avec ceux supportés en HLS (ex: H264 et audio ACC). L’enregistrement de la piste audio sera desactivée.

FFmpeg options 3 -movflags frag_keyframe+empty_moov :

fragment le flux d’entrée à chaque frame principale (keyframe) (i.e Smooth Streaming). Le "moov atom" (utilisé pour les fichiers MOV/MP4) du premier fragment sera écrit vide.

FFmpeg options 3 -hls_flags delete_segments+append_list :

les fichiers fragments *.ts sont enlevés de la liste de lecture (playlist) après + . Ajoute les nouveaux segments à la liste des segemnts en mettant à jour la liste de lecture et l’index sur #EXT-X-ENDLIST.

FFmpeg options 3 -f segment -segment_time 1 -segment_format mpegts :

force la sortie à être fragmentée toutes les 1 seconde au format MPEG transport stream 4 normalement déduit de l’extension du fichier.

FFmpeg options 3 -segment_list /tmp/hls/cours.m3u8 -segment_list_type m3u8 -segment_list_flags live :

crée le fichier de la liste de lecture (playlist) au format m3u8 (HTTP Live Streaming) pour un affichage en direct (sans cache, EXT-X-ALLOW-CACHE:NO).

FFmpeg options 3 segment_list_entry_prefix /hls/ -segment_list_size 3 :

prefix les uri des 3 fichiers segemnts de la liste de lecture par "/hls/" (à ajuster en fonction de la configuration du serveur web)

Configuration du serveur Nginx

Configuration de HLS dans Nginx

Éditer la configuration HTTP de votre site dans Nginx

$: /etc/nginx/sites-available/mondomain.fr

Ajouter la location où FFmpeg génère les fichiers HLS ts et m3u8 :

  1. Il est recommendé de désactiver les log d’accès avec HLS. Autrement, tous les téléchargements des fragments `ts̀ seront enregistré.
  2. Désactiver le cache pour que le client HTTP télécharge bien le fichier même si le nom a été réutilisé pour un autre segment.
  3. Ne pas autoriser le partage de ressource de différentes origines Wikipedia : Cross-origin resource sharing. Par défaut CORS n’est pas autorisé et les règles Same-origin policy s’applique.
  4. Ajouter les type mime ts et m3u8
  5. Définir les indexes
  6. Définir le répertoire principale contenant les données HLS. Nginx recommande l’utilisation de directive root plutôt que alias lorsque la location coïncide avec la dernière partie de la valeur de la directive.

Le partage de ressource de différentes origines n’est pas autorisé dans le serveur HTTP. Il est peu souhaitable d’autoriser une application d’un autre domaine d’accèder aux flux vidéos des caméras. Si toutefois, vous souhaitez autoriser l’accès aux flux des caméras depuis un autre domaine, j’ai ajouté un chapitre pour autoriser le partage des flux vidéo des caméras – je n’ai pas encore testé cette configuration dans ce projet.

[...]

server {
    listen 80;
    listen [::]:80;
    server_name mondomain.fr;

    access_log  /var/log/nginx/mondomain.fr.access.log;
    error_log  /var/log/nginx/mondomain.fr.error.log;

[...]

    location /hls/ {
        #1. Disable access log
        access_log off;

        #2. Disable cache
        add_header Cache-Control no-cache;

        #3. CORS setup => do nothing Same-origin policy will be used.

        #4. Add ts and m3u8 mime types
        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }

        #5. Defines files that will be used as an index.  
        index index.m3u8 index.ts;

        #6. Serve content from the following location
        #alias /tmp/hls/;
        root /tmp;
    }

[...]

}

Configuration de PHP dans Nginx

Éditer la configuration HTTP de votre site dans Nginx

$: /etc/nginx/sites-available/mondomain.fr
  1. Définir le répertoire principal (root) où sont stoqué les fixhiers PHP
  2. Configurer la configuration pour PHP FPM (FastCGI Process Manager).
  3. Effacer la valeur du paramètre HTTP_PROXY de FastCGI (fastcgi_param). Réduire les risques d’une attaque d’un client malvaillant qui utiliserait la variable HTTP_PROXYde l’entète de PROXY pour rediriger les requètes vers une autre address IP que celle du serveur PHP (https://httpoxy.org/).
  4. Définir un groupe de serveur (php-handler) avec l’adresse du serveur PHP FPM (FastCGI Process Manager) (upstream). J’utilise PHP dans plusieurs bloques, je peux référencer le groupe php-handler dans tous les bloques où j’ai besoin d’utiliser un serveur PHP FPM (FastCGI Process Manager).
  5. Configurer l’adresse du serveur PHP FPM (FastCGI Process Manager) (fastcgi_pass) pour utiliser le groupe de serveur php-handler. – l’utilisation d’une socket UNIX améliore les performances par rapport à l’utilisation d’un socket TCP.
#4. Define the group for PHP FPM server(s)
upstream php-handler {
    #server 127.0.0.1:9000
    server unix:/run/php/php7.1-fpm.sock;
}
server {
    listen 80;
    listen [::]:80;
    server_name mondomain.fr;

    access_log  /var/log/nginx/mondomain.fr.access.log;
    error_log  /var/log/nginx/mondomain.fr.error.log;

[...]

    location /hls/ {

[...]

    }

    # Connecting NGINX to PHP FPM (default) FastCGI server listening php-handler
    # https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/
    location ~ ^/telesurveillance/[^/]+.php(/|$) {
        #1. Configure the root directory where the php files are located
        root /websites/mondomain.fr/www;
        #2. Include the PHP configuration for Nginx fastcgi-php module
        include snippets/fastcgi-php.conf;

        #3. Mitigate https://httpoxy.org/ vulnerabilities
        fastcgi_param HTTP_PROXY "";
        #5. Configure the FastCGI server IP address
        fastcgi_pass php-handler;
    }

[...]

}

Configuration de la sécurité des liens (recommandé)

Cette partie est optionnelle mais fortement conseillée pour des raisons de sécurité mais aussi pour mieux gérer la durée de vie des liens HLS.

Une petite introduction :

Sur HTTP (sans SSL/TLS), les messages échangés entre le serveur et le client sont tranmis de façon lisible sur le réseau. Il est donc possible d’intercepter les messages HTTP et d’en lire le comtenu. Cela inclus aussi la vidéo et l’audio des flux des caméras sortant du serveur HTTP.

Le fait de sécuriser l’URL (lien vers la resource web) permet au serveur de vérifier que le client HTTP, qui accède à une resource, utilise un lien valide. Cela protège uniquement le lien lui même et ne permet pas de controler si l’utilisateur est autorisé à accèder à la resource ou de protéger le contenu des données échangées entre le client et le serveur.

Pour vérifier qu’un lien est valide, le serveur vérifie que le checksum (somme de contrôle) contenu dans le lien n’est pas expiré et que les critères d’accès à la resource sont bien conformes à ceux attendu.

Seule l’entité fournissant les liens vers les ressources pour les clients HTTP et le serveur HTTP connaissent les critères de sécurité et la phrase secrete qui sera utilisée pour la création du checksum.

L’entité responsable de la création des liens sécurisés peut être une application tournant sur le poste du client ou une application web tournant sur un serveur quelconque.

Dans mon cas, les critères de sécurité sont :

  • la date de fin de vie (expiration) du lien
  • la localisation de la resource sur le serveur
  • le nom de la resource sur le serveur
  • l’adresse IP du client

Il est possible d’utiliser d’autres critères à partir du moment qu’ils sont partagés et connu entre le serveur et l’entité générant les liens vers les resources hébergés par le serveur. Comme par exemple le nom de l’agent HTTP (ex: Mozilla, Internet Explorer, Safari, etc)

Configuration de Nginx

Avant de configurer Nginx, il est nécessaire d’installer le module ngx_http_secure_link_module pour sécuriser les liens vers les flux HTTP Live Streaming.

Éditer la configuration HTTP de votre site dans Nginx

$: /etc/nginx/sites-available/mondomain.fr
  1. Extraire de l’URI le nom du flux
  2. Configurer le nom des paramètres dans l’URI que Nginx doit utiliser pour retrouver le checksum et la date de péremption du lien.
  3. Configurer les critères de sécurité et le secret pour la creation du checksum.
  4. Retourner une erreur au client si le checksum n’est pas valide
  5. Retourner une erreur au client si le lien à expiré.
server {
    listen 80;
    listen [::]:80;
    server_name mondomain.fr;

    access_log  /var/log/nginx/mondomain.fr.access.log;
    error_log  /var/log/nginx/mondomain.fr.error.log;

[...]

    location /hls/ {
    location /hls/ {
        #1. Extract the stream name and extension file from the incoming URI
        if ($uri ~ ([^/-]+)(?:-d+)?.(m3u8|ts)(/|$)) {
            # $1: name of the stream (ex: 'foot' in '/hls/camera_jardin.meu8')
            # $2: ext of the stream (ex: 'ts' in '/hls/camera_jardin-4.ts?cs=xxxx&e=452454')
            set $stream_name $1;
        }
        #2. configure the URI parameter names of the cheksume and lifetime (here: 'cs' and 'e')
        secure_link $arg_cs,$arg_e;
        #3. set connection secure link based on the location and stream_name of the URI
        secure_link_md5 "$secure_link_expires/hls/$stream_name$remote_addr my_precious_secret";
        #4. bad hash
        if ($secure_link = "") {
            # 403 Forbidden
            return 403;
        }
        #5. link expired
        if ($secure_link = "0") {
            # 410 Gone: Indicates that the resource requested is no longer available and will not be available again.
            return 410;
        }

        # Disable access log
        access_log off;
[...]

    }

[...]

}

La configuration pour protéger les liens, ci-dessus, est relativement classique. Il y a toutefois un point qui a son importance. Nous n’utilisons pas directement le chemin de l’URL pour la création du checksum md5 dans l’étape 3.

En fait, il aurait été plus facile d’utiliser l’instruction suivante : secure_link_md5 "$secure_link_expires$uri$remote_addr my_precious_secret";. Nginx aurait remplacé $uri par /hls/camera_jardin.m3u8 ou /hls/camera_jardin-4.ts. Ce qui imposerait de créer un checksum différent pour chaque liste de lecture mais aussi pour chaque fichier segments !

Avec ce choix, il est nécessaire de créer un checksum par client et par flux de caméra uniquement. Le même checksum sera utilisé pour la liste de lecture et l’ensemble des segements qui la compose.

Autoriser la diffusion des vidéos des caméras depuis un autre domaine (optionel)

Les navigateurs des utilisateurs bloquent les requètes qui ne sont pas à destination du domaine de la page chargée dans le navigateur. Cette protection empêche une application cliente d’obtenir des données provenant d’une source d’une autre origine que celle de la page web actuellement chargée dans le navigateur (W3 : Cross-Origin Resource Sharing).

image_cors_principale
Figure: Cross-Origin Resource Sharing (CORS)

Pour que le navigateur accepte de charger les données provenant d’une autre origine, il faut que l’autre origine l’autorise dans l’entête de sa réponse (dans le cas d’une rquète simple) ou lors de l’opération du prefligth en répondant à la requête HTTP OPTION envoyée par le navigateur de l’utilisateur.

image_simple_preflight
Figure: Wikipedia : Cross-origin resource sharing

Éditer la configuration HTTP de votre site dans Nginx

$: /etc/nginx/sites-available/mondomain.fr

Autoriser le partage de ressource de différentes origines Wikipedia : Cross-origin resource sharing en exposant l’entête Content-Length. Par défaut, uniquement 6 entêtes de la réponse sont exposées par le navigateur avec CORS :

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

Le lecteur HLS s’executant dans le navigateur de l’utilisateur a besoin de connaitre la taille du contenu pour pouvoir l’afficher correctement. La configuration du serveur doit ajouter l’entête Content-Length à cette liste pour que le navigateur de l’utilisateur autorise l’accès de cette valeur au lecteur HLS.

[...]

server {
    listen 80;
    listen [::]:80;
    server_name mondomain.fr;

    access_log  /var/log/nginx/mondomain.fr.access.log;
    error_log  /var/log/nginx/mondomain.fr.error.log;

[...]

    location /hls/ {

[...]

        #3. CORS setup
        # Allow CORS preflight requests
        if ($request_method = 'OPTIONS') {
            # Enable CORS from any domains
            add_header 'Access-Control-Allow-Origin' '*';

            # Allow CORS for GET and OPTIONS requests (do not allow POST)
            add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';

            # Do not extend the list of the allowed header for CORS requests
            #add_header 'Access-Control-Allow-Headers' '';

            # Expose 'Content-Length' header for HLS
            add_header 'Access-Control-Expose-Headers' 'Content-Length';

            # Tell client that this pre-flight info is valid for 20 days            
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'text/plain charset=UTF-8';
            add_header 'Content-Length' 0;
            return 204;
        }
        # Allow CORS GET simple request (for HLS)
        if ($request_method = 'GET') {
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
            #add_header 'Access-Control-Allow-Headers' '';
            add_header 'Access-Control-Expose-Headers' 'Content-Length';
        }
        # CORS POST requests will be not allowed

[...]

    }

[...]

}

Application et Client Web (en PHP)

Le script PHP permet de lancer les processus FFmpeg coté serveur et de créer la page HTML qui affichera la vidéo des caméras dans le navigateur de l’utilisateur.

La librairie videojs est utilisée par l’application cliente pour l’affichage de la vidéo des caméras.
La librairie jquery est utilisée pour l’affichage des commandes des caméras dans un "slideout" plus adapté aux écrans de téléphone mobile.

Deux meta information sont ajoutées dans l’entête HTML de la page WEB pour les besoin d’affichage sur des terminaux mobile avec un écran de petite taille.

  <meta name="MobileOptimized" content="320">
  <meta name="viewport" content="width=device-width, initial-scale=0.8, user-scalable=no" />

La liste des caméras (ici deux) avec leurs propriétés pour l’encodage HLS et les commandes utilisateurs disponibles (tel que le pilotage de la rotation ou de l’inclinaison de la caméra) sont regroupé dans un tableau.

Pour chaque caméra, le script vérifie au chargement si le processus FFmpeg est en cours d’exécution. Le cas échéant, les anciens fichiers sont supprimés, et un nouveau processus FFmpeg est lancé. Un seul flux par caméra est créé pour l’ensemble des clients HTTP.

Les caméras IP, grand public, ne sont pas nécessairement très rigoureuses dans leur implémentaion de RTSP ce qui provoque des erreurs dans FFmpeg qui n’arrive plus encoder les données. Il est bon de mettre en place un méchanisme de défence pour dédecter les processus FFmpeg zombie. Les processus zombies sont tués et de nouveaux processus FFmpeg sont relancés.

Les génération des flux HLS sont automatiquement arrêté après 5 minutes ($streaming_duration). Ce temps est suffisent pour permettre à l’utilisateur de vérifier l’état actuel sur site. Le fait de couper la diffusion au bout d’un temps déterminé évite de consomer en permanence des resources processeurs et disque pour la diffusion des flux vidéos des caméras s’il n’y a pas d’utilisateur surveillant le site.

Ce choix est adapté pour mon petit serveur qui n’est pas reservé à la télésurveillance. Dans un cadre plus professionel, il est possible d’utiliser des session utilisateur pour arrêter la diffusion s’il n’y a plus de session utilisateur ouverte.

$gmdate = 'gmdate'; // to use the fct gmdate in the EOT section
$cmd = <<<EOT
ffmpeg  -nostdin -t {$gmdate('H:i:s', $time)} -fflags nobuffer \
    -rtsp_transport tcp -i {$cam->rtsp} \
    {$cam->codec} \
    -movflags frag_keyframe+empty_moov \
    -hls_flags delete_segments+append_list \
    -f segment -segment_time {$cam->hls->segment_time} -segment_format mpegts \
    -segment_list {$cam->hls_path}/{$cam->name}.m3u8 -segment_list_type m3u8 -segment_list_flags live \
    -segment_list_entry_prefix /{$cam->app}/ -segment_list_size {$cam->hls->segment_list_size} \
    {$cam->hls_path}/{$cam->name}-%d.ts
EOT;
exec($cmd ." >/dev/null 2>/dev/null &");

Les liens des flux HLS sont sécurisés et ne sont valides que durant la duré de diffusion fixée à 5 minutes (par la variable $streaming_duration).

/**
 * @param $baseUrl - non protected part of the URL including hostname, e.g. http://example.com
 * @param $path - protected path to the file, e.g. /downloads/myfile.zip
 * @param $secret - the shared secret with the nginx server. Keep this info secure!!!
 * @param $ttl - the number of seconds until this link expires
 * @param $userIp - ip of the user allowed to download
 * @return string
 */
function buildSecureLink($baseUrl, $app, $name, $secret, $ttl, $userIp) {
    // NGINX : 
    //  - secure_link $arg_cs,$arg_e;
    //  - secure_link_md5 "$arg_e$arg_app/$arg_name$arg_addr mysecret";

    $expires = time() + $ttl;
    $md5 = md5("$expires/$app/$name$userIp $secret", true);
    $md5 = base64_encode($md5);
    $md5 = strtr($md5, '+/', '-_');
    $md5 = str_replace('=', '', $md5);
    return $baseUrl . $app ."/". $name . '.m3u8?cs=' . $md5 . '&e=' . $expires;
}

Une fois que l’ensemble des processus FFmpeg sont lancés, le script attend que FFmpeg crée les fichiers des liste de lecture avant générer la page HTML contenant le lecteur permettant aux utilisateurs de visualiser la sortie des caméras.

La liste des actions des caméras est affichée dans le "slideout".

<?php foreach($cameras as $key=>$cam): ?>
<?php if( isset($cam->actions)): ?>
        <div class="menu-sidebar">
            <h3>Caméra <?php echo $cam->name;?></h3>
            <ul class="menu">
<?php foreach($cam->actions as $name=>$action): ?>
                <li><a href="#" onclick="javascript:$.get('<?php echo $action;?>');$('.toggle-button').trigger('click');"><?php echo $name;?></a></li>
<?php endforeach; ?>
            </ul>           
        </div>
    </div>
<?php endif; ?>
<?php endforeach; ?>

Les lecteurs vidéos sont agencés les uns après les autres.

<?php foreach($cameras as $key=>$cam): ?>
    <section class="section-video">
        <h2 class="heading">Caméra <?php echo $cam->name;?> [<a href="<?php echo $cam->hls_url;?>">media</a>]</h2>

        <video id="cam-cours" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" data-setup="{'fluid': true}"
            onplay="var vp = this; setTimeout(function() { vp.pause(); }, <?php echo $streaming_duration * 60000;?>);">
            <source src="<?php echo $cam->hls_url;?>" 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>
    </section>
<?php endforeach; ?>

Références:

ffmpeg Documentation

FFmpeg Protocols Documentation

FFmpeg Formats Documentation

Wikipedia : MPEG transport stream

HTTP Live Streaming – draft-pantos-http-live-streaming-23

Wikipedia : clients et serveurs supportant HTTP Live Streaming

Wikipedia : Cross-origin resource sharing

Wikipedia : Same-origin policy

Securing URLs with the Secure Link Module in NGINX and NGINX Plus

Nginx : module ngx_http_core_module

Nginx: PHP FPM (FastCGI Process Manager)

W3 : Cross-Origin Resource Sharing

Cross-Origin Resource Sharing (CORS)

Enable CORS in Nginx

Laisser un commentaire

20 + = 30