L'intégration de flux vidéo en direct provenant de caméras de surveillance dans des applications web est un défi courant. Souvent, les caméras émettent via le protocole RTSP, qui n'est pas directement compatible avec les navigateurs modernes. Cet article explore diverses stratégies pour transcoder des flux RTSP vers des formats lisibles par les navigateurs, tels que RTMP, HLS et HTTP-FLV, en utilisant Nginx et FFmpeg, et comment orchestrer ce processus avec Java.
Le contexte initial implique un besoin de visualiser des flux de caméras de surveillance dans un navigateur sans l'aide d'un serveur de streaming cloud dédié. Les solutions présentées ici sont basées sur une implémentation Windows mais sont entièrement transposables à des environnements Linux pour la production.
Comparaison des Protocoles de Streaming Média
Avant de plonger dans les solutions, il est essentiel de comprendre les caractéristiques des principaux protocoles de streaming.
| Protocole | HttpFlv | RTMP | HLS | Dash |
|---|---|---|---|---|
| Nom complet | FLASH VIDEO over HTTP | Real Time Message Protocol | HTTP Live Streaming | Dynamic Adaptive Streaming over HTTP |
| Méthode de transmission | Connexion HTTP longue | Connexion TCP longue | Connexions HTTP courtes | Connexions HTTP courtes |
| Format d'encapsulation | FLV | FLV TAG | Fichiers TS | Mp4, 3gp, webm |
| Principe | Identique à RTMP, utilise HTTP (port 80) | Données transférées instantanément à la réception | Collecte des données sur une période, création de segments TS et mise à jour de l'index M3U8 | Segmentation du contenu en petits fichiers, offre une diffusion adaptative |
| Latence | Faible (1-3 secondes) | Faible (1-3 secondes) | Élevée (5-20 secondes, dépend de la taille des segments) | Élevée |
| Segmentation des données | Flux continu | Flux continu | Fichiers segmentés | Fichiers segmentés |
| Lecture HTML5 | Oui (via flv.js) | Non pris en charge directement | Oui (via hls.js) | Oui (si fichiers MP4/webm) |
| Remarques | Nécessite Flash, ne prend pas en charge plusieurs pistes audio/vidéo, difficile à manipuler (avance/retour rapide) | Support multiplateforme limité, nécessite Flash | Multiples requêtes, exige une bonne qualité réseau | Bonne adaptabilité au réseau, support multi-formats |
Première Approche : Conversion RTSP en RTMP
Cette méthode exploite Nginx avec le module RTMP pour la diffusion, complété par FFmpeg pour le transcodage.
1. Installation de Nginx et du module Nginx-RTMP
- Téléchargez Nginx (version Windows si nécessaire) : http://nginx-win.ecsds.eu/download/
- Téléchargez le module Nginx-RTMP : https://github.com/arut/nginx-rtmp-module
Décompressez le module Nginx-RTMP dans le répertoire racine de Nginx et renommez le dossier en nginx-rtmp-module (il pourrait être nommé nginx-rtmp-module-master initialement).
2. Configuration de Nginx pour le RTMP
Copiez le fichier de configuration Nginx par défaut (par exemple, nginx-win.conf) et renommez-le en nginx_rtmp_config.conf. Ajoutez les directives RTMP suivantes :
worker_processes 2;
events {
worker_connections 8192;
}
rtmp {
server {
listen 1935; # Port standard pour RTMP
chunk_size 4000;
application live {
live on;
# on_play http://localhost:8081/auth; # Exemple de callback pour authentification à la lecture
}
application hls {
live on;
hls on;
hls_path html/hls; # Chemin pour les segments HLS
hls_fragment 8s; # Durée d'un segment HLS
}
}
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 5080; # Port HTTP pour l'accès aux statistiques ou HLS
server_name localhost;
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root nginx-rtmp-module/;
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
expires -1;
add_header Access-Control-Allow-Origin *;
}
location / {
root html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
3. Démarrage de Nginx
Ouvrez une invite de commande, naviguez jusqu'au répertoire racine de Nginx et exécutez :
nginx.exe -c conf\nginx_rtmp_config.conf
Pour vérifier le succès de l'installation, accédez à http://localhost:5080/stat dans votre navigateur. Vous devriez voir une page de statistiques RTMP.
4. Installation de FFmpeg
FFmpeg est l'outil essentiel pour le transcodage. Téléchargez-le depuis : https://www.gyan.dev/ffmpeg/builds/. La version "essential" est suffisante pour la plupart des besoins.
5. Configuration des variables d'environnement FFmpeg
Ajoutez le chemin du répertoire bin de FFmpeg (après décompression) à la variable d'environnement Path de votre système.
6. Vérification de l'installation de FFmpeg
Ouvrez une nouvelle invite de commande et tapez ffmpeg -version. Si l'installation est correcte, les informations de version s'afficheront.
7. Lecteur VLC
VLC est utile pour tester les flux RTMP et HTTP-FLV. Téléchargez-le depuis : https://www.videolan.org/vlc/.
8. Obtention de l'URL RTSP de la Caméra
Chaque caméra a une URL RTSP spécifique. Par exemple, une URL de test courante est : rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4. Utilisez celle de votre caméra.
9. Exécution de la commande FFmpeg pour la conversion RTSP vers RTMP
Utilisez FFmpeg pour convertir le flux RTSP et le pousser vers Nginx RTMP :
ffmpeg -re -rtsp_transport tcp -stimeout 20000000 -i "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4" -buffer_size 1024000 -max_delay 500000 -codec:v libx264 -r 25 -rtbufsize 10M -s 1280x720 -map:v 0 -an -f flv rtmp://127.0.0.1:1935/live/test_stream
-re: Lecture en temps réel (pour les sources de fichiers).-rtsp_transport tcp: Spécifie le protocole de transport RTSP (TCP est plus fiable).-stimeout 20000000: Délai d'attente pour le flux RTSP (20 secondes).-i "rtsp://...": L'URL du flux RTSP source.-buffer_size 1024000: Taille du tampon.-max_delay 500000: Délai maximum.-codec:v libx264: Codec vidéo H.264.-r 25: 25 images par seconde.-rtbufsize 10M: Taille du tampon pour le flux en temps réel.-s 1280x720: Résolution de la sortie.-map:v 0: Sélectionne la piste vidéo de la première entrée.-an: Désactive l'audio (si vous voulez seulement la vidéo).-f flv: Format de sortie FLV.rtmp://127.0.0.1:1935/live/test_stream: L'URL RTMP de destination sur Nginx.
10. Test du flux RTMP avec VLC
Dans VLC, allez dans Média > Ouvrir un flux réseau. Entrez l'URL RTMP : rtmp://127.0.0.1:1935/live/test_stream et cliquez sur Lire. Si la vidéo démarre après quelques secondes, la conversion est réussie.
11. Pourquoi le RTMP a été écarté
Bien que le RTMP offre une faible latence, sa dépendance à Adobe Flash le rend obsolète pour les navigateurs web modernes. Seules les anciennes versions de navigateurs ou les environnements spécifiques nécessitant Flash peuvent l'utiliser. D'autres protocoles sont préférables pour le web contemporain.
Deuxième Approche : Conversion RTSP en HLS
HLS (HTTP Live Streaming) est un protocole de streaming populaire, largement supporté par les navigateurs via HTML5.
1. Configuration Nginx pour HLS
La section HLS a déjà été incluse dans la configuration Nginx précédente (nginx_rtmp_config.conf) sous le bloc rtmp et http. Assurez-vous que les directives hls on;, hls_path html/hls; et hls_fragment 8s; sont présentes.
2. Commande FFmpeg pour RTSP vers HLS
Poussez le flux RTSP vers l'application HLS configurée dans Nginx :
ffmpeg -i "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4" -vcodec libx264 -acodec aac -f flv rtmp://127.0.0.1:1935/hls/my_hls_stream
Cette commande transcode le flux RTSP en FLV et le pousse vers l'application RTMP nommée hls, que Nginx se chargera ensuite de segmenter en HLS.
3. Vérification de la génération des fichiers HLS
Après l'exécution de la commande FFmpeg, vérifiez le répertoire nginx/html/hls (tel que défini par hls_path). Vous devriez y trouver des fichiers .ts (segments vidéo) et un fichier .m3u8 (index de la playlist).
4. Lecture d'un flux M3U8 dans un navigateur
Utilisez une page HTML simple avec une bibliothèque comme video.js ou hls.js pour lire le flux :
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Lecture de Vidéo M3U8 en HTML5</title>
<link href="https://cdn.bootcss.com/video.js/7.6.5/alt/video-js-cdn.min.css" rel="stylesheet">
<script src="https://cdn.bootcss.com/video.js/6.6.2/video.js"></script>
<script src="https://cdn.bootcss.com/videojs-contrib-hls/5.15.0/videojs-contrib-hls.min.js"></script>
</head>
<body>
<video id="videoStreamPlayer" class="video-js vjs-default-skin vjs-big-play-centered" controls preload="auto" width="1080" height="708" data-setup='{}'>
<source id="hlsSource" src="http://127.0.0.1:5080/hls/my_hls_stream.m3u8" type="application/x-mpegURL">
</video>
<script>
const streamPlayer = videojs('videoStreamPlayer', {
bigPlayButton: true,
textTrackDisplay: false,
posterImage: false,
errorDisplay: false,
});
streamPlayer.play(); // Démarrer la lecture
// streamPlayer.pause(); // Mettre en pause
</script>
</body>
</html>
Assurez-vous que l'URL http://127.0.0.1:5080/hls/my_hls_stream.m3u8 pointe correctement vers votre serveur Nginx.
5. Comprendre le format M3U8
Le ficheir .m3u8 n'est pas la vidéo elle-même, mais une playlist ou un index. Il contient des liens vers de petits fichiers vidéo .ts (Transport Stream) qui sont les segments réels du flux. Voici un exemple de contenu M3U8 :
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:56
#EXT-X-TARGETDURATION:13
#EXTINF:10.381,
my_hls_stream-56.ts
#EXTINF:10.422,
my_hls_stream-57.ts
#EXTINF:13.453,
my_hls_stream-58.ts
6. Pourquoi le HLS a été écarté pour la surveillance en temps réel
Le processus HLS implique une attente pour la création de segments TS, leur stockage sur le serveur, puis l'ajout d'index au fichier M3U8. Le navigateur, à son tour, doit lire l'M3U8, demander les segments TS, les télécharger et les décoder. Ce cycle entraîne une latence plus élevée (typiquement 5-20 secondes) et une consommation de bande passante accrue en raison des multiples requêtes HTTP. Pour la surveillance vidéo où une faible latence est cruciale, HLS n'est pas optimal, bien qu'il soit excellent pour la compatibilité et le VOD (Video On Demand).
Troisième Approche : Conversion RTSP en HTTP-FLV (Solution Adoptée)
HTTP-FLV est une excellente alternative offrant une faible latence et une bonne compatibilité HTML5 via des bibliothèques JavaScript.
1. Installation du module Nginx-FLV
Le module nginx-flv-module est nécessaire pour diffuser des flux FLV via HTTP. Ce module requiert généralement une compilation personnalisée de Nginx. Une version pré-compilée peut être disponible ou vous pouvez suivre un tutoriel pour la compilation. Une implémentation pré-compilée est disponible ici : https://gitee.com/isyuesen/nginx-flv-file.
2. Configuration Nginx pour HTTP-FLV
La configuration de Nginx doit inclure les directives RTMP pour recevoir le flux de FFmpeg, et une directive location spécifique pour le module FLV. Les sections pertinentes de la configuration (par exemple, de nginx.conf du dépôt Gitee) sont :
rtmp {
server {
listen 1935;
chunk_size 4000;
application liveapp {
live on;
# Hooks d'authentification et de contrôle
on_publish http://localhost:8081/auth; # Au début de la diffusion
on_publish_done http://localhost:8081/auth; # À la fin de la diffusion
on_play http://localhost:8081/auth; # Au début de la lecture
on_play_done http://localhost:8081/auth; # À la fin de la lecture
}
}
}
http {
include mime.types;
default_type application/octet-stream;
server {
listen 18080; # Port HTTP pour le flux FLV et l'API d'authentification
server_name localhost;
location /live {
flv_live on; # Active la diffusion FLV en direct
chunked_transfer_encoding on; # Transfert par morceaux pour une meilleure performance
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Origin' '*';
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header 'Cache-Control' 'no-cache'; # Important pour éviter la mise en cache
}
# ... autres configurations ...
}
}
3. Implémentation de l'authentification Java
Le module Nginx-RTMP permet de configurer des "hooks" HTTP (on_publish, on_play, etc.) qui appellent une URL pour valider les connexions. Si la réponse HTTP n'est pas un code 200, Nginx refuse la connexion.
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
public class StreamAuthController {
@PostMapping("/auth")
public void authenticateStream(@RequestParam("token") String clientToken,
HttpServletRequest request,
HttpServletResponse response) throws IOException {
final String expectedToken = "secureToken123"; // Token sécurisé attendu
if (clientToken != null && clientToken.equals(expectedToken)) {
response.setStatus(HttpServletResponse.SC_OK); // Code 200 OK
} else {
response.setStatus(HttpServletResponse.SC_FORBIDDEN); // Code 403 Forbidden
// Ou HttpServletResponse.SC_INTERNAL_SERVER_ERROR pour une erreur générique
}
}
}
4. Commande FFmpeg pour RTSP vers HTTP-FLV
La commande FFmpeg pousse le flux RTSP vers l'application RTMP de Nginx, qui est ensuite servie via HTTP-FLV.
ffmpeg -re -rtsp_transport tcp -i "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4" -f flv -vcodec h264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 640x360 -q 10 "rtmp://127.0.0.1:1935/liveapp/camera_feed?token=secureToken123"
Notez l'ajout du ?token=secureToken123 à l'URL RTMP pour l'authentification.
4.1. Exécution de FFmpeg via du code Java
Il est préférable de démarrer et gérer le processus FFmpeg programmatiquement en Java, en utilisant ProcessBuilder. La bibliothèque JavaCV simplifie l'obtention du chemin de l'exécutable FFmpeg.
Ajoutez la dépendance Maven suivante pour JavaCV :
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.2</version>
</dependency>
Code Java pour exécuter FFmpeg :
import org.bytedeco.ffmpeg.ffmpeg;
import org.bytedeco.javacpp.Loader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class StreamTranscoder {
public static void main(String[] args) throws IOException, InterruptedException {
final String streamIdentifier = "camera_feed";
final String rtspSourceUrl = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4";
// L'adresse RTMP de Nginx avec le token d'authentification
final String rtmpDestinationUrl = "rtmp://192.168.0.140:1935/liveapp/" + streamIdentifier + "?token=secureToken123";
// Chargement du chemin de l'exécutable FFmpeg
String ffmpegExecutablePath = Loader.load(ffmpeg.class);
List<String> commandArguments = new ArrayList<>();
commandArguments.add(ffmpegExecutablePath);
commandArguments.add("-re");
commandArguments.add("-rtsp_transport");
commandArguments.add("tcp");
commandArguments.add("-i");
commandArguments.add(rtspSourceUrl);
commandArguments.add("-f");
commandArguments.add("flv");
commandArguments.add("-vcodec");
commandArguments.add("h264");
commandArguments.add("-vprofile");
commandArguments.add("baseline");
commandArguments.add("-acodec");
commandArguments.add("aac");
commandArguments.add("-ar");
commandArguments.add("44100");
commandArguments.add("-strict");
commandArguments.add("-2");
commandArguments.add("-ac");
commandArguments.add("1");
commandArguments.add("-f");
commandArguments.add("flv");
commandArguments.add("-s");
commandArguments.add("640x360"); // Note: résolution '640x360' et non '640*360'
commandArguments.add("-q");
commandArguments.add("10");
commandArguments.add(rtmpDestinationUrl);
ProcessBuilder processBuilder = new ProcessBuilder(commandArguments);
processBuilder.inheritIO().start().waitFor(); // Exécute FFmpeg et attend sa terminaison
}
}
5. Test du lien HTTP-FLV
Après avoir démarré Nginx et le processus FFmpeg (via la commande bash ou le code Java), testez l'URL HTTP-FLV dans VLC : http://127.0.0.1:18080/live?port=1935&app=liveapp&stream=camera_feed&token=secureToken123. Si la lecture est fluide, le pipeline est fonctionnel.
6. Lecture front-end avec flv.js
La bibliothèque flv.js est conçue pour lire les flux FLV directement dans les navigateurs HTML5 sans plugins. C'est la solution idéale pour le front-end.
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Lecture de Flux HTTP-FLV</title>
</head>
<body>
<video id="videoDisplay" controls autoplay muted></video>
<script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
<script>
// Vérifie si flv.js est supporté par le navigateur
if (flvjs.isSupported()) {
const videoElement = document.getElementById('videoDisplay');
const flvStreamUrl = 'http://127.0.0.1:18080/live?port=1935&app=liveapp&stream=camera_feed&token=secureToken123';
// Crée un lecteur FLV
const flvPlayer = flvjs.createPlayer({
type: 'flv',
url: flvStreamUrl
});
// Attache le lecteur à l'élément vidéo HTML
flvPlayer.attachMediaElement(videoElement);
// Charge le flux
flvPlayer.load();
// Démarre la lecture
flvPlayer.play();
// Gestion des erreurs (optionnel)
flvPlayer.on(flvjs.Events.ERROR, (errorType, errorDetail, errorInfo) => {
console.error('Erreur flv.js:', errorType, errorDetail, errorInfo);
// Tenter de recharger le flux en cas d'erreur
// flvPlayer.destroy();
// setTimeout(() => {
// const newPlayer = flvjs.createPlayer({ type: 'flv', url: flvStreamUrl });
// newPlayer.attachMediaElement(videoElement);
// newPlayer.load();
// newPlayer.play();
// }, 1000);
});
} else {
console.error("flv.js n'est pas supporté par ce navigateur.");
}
</script>
</body>
</html>