Je suis vraiment coincé à essayer de comprendre la meilleure façon de diffuser la sortie en temps réel de ffmpeg vers un client HTML5 en utilisant node.js, car il y a un certain nombre de variables en jeu et je n'ai pas beaucoup d'expérience dans cet espace, après avoir passé de nombreuses heures à essayer différentes combinaisons.
Mon cas d'utilisation est:
1) Le flux de la caméra vidéo IP RTSP H.264 est capté par FFMPEG et remuxé dans un conteneur mp4 en utilisant les paramètres FFMPEG suivants dans le nœud, sortie vers STDOUT. Ceci n'est exécuté que sur la connexion client initiale, de sorte que les demandes de contenu partielles n'essaient pas de réapparaître FFMPEG.
liveFFMPEG = child_process.spawn("ffmpeg", [
"-i", "rtsp://admin:12345@192.168.1.234:554" , "-vcodec", "copy", "-f",
"mp4", "-reset_timestamps", "1", "-movflags", "frag_keyframe+empty_moov",
"-" // output to stdout
], {detached: false});
2) J'utilise le serveur http de noeud pour capturer le STDOUT et le diffuser au client sur demande du client. Lorsque le client se connecte pour la première fois, je génère la ligne de commande FFMPEG ci-dessus, puis je dirige le flux STDOUT vers la réponse HTTP.
liveFFMPEG.stdout.pipe(resp);
J'ai également utilisé l'événement stream pour écrire les données FFMPEG dans la réponse HTTP mais cela ne fait aucune différence
xliveFFMPEG.stdout.on("data",function(data) {
resp.write(data);
}
J'utilise l'en-tête HTTP suivant (qui est également utilisé et fonctionne lors du streaming de fichiers préenregistrés)
var total = 999999999 // fake a large file
var partialstart = 0
var partialend = total - 1
if (range !== undefined) {
var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
}
var start = parseInt(partialstart, 10);
var end = partialend ? parseInt(partialend, 10) : total; // fake a large file if no range reques
var chunksize = (end-start)+1;
resp.writeHead(206, {
'Transfer-Encoding': 'chunked'
, 'Content-Type': 'video/mp4'
, 'Content-Length': chunksize // large size to fake a file
, 'Accept-Ranges': 'bytes ' + start + "-" + end + "/" + total
});
3) Le client doit utiliser des balises vidéo HTML5.
Je n'ai aucun problème avec la lecture en continu (en utilisant fs.createReadStream avec 206 HTTP partiel) au client HTML5 un fichier vidéo précédemment enregistré avec la ligne de commande FFMPEG ci-dessus (mais enregistré dans un fichier au lieu de STDOUT), donc je connais le flux FFMPEG est correct, et je peux même voir correctement la vidéo en direct en streaming dans VLC lors de la connexion au serveur de noeud HTTP.
Cependant, essayer de diffuser en direct depuis FFMPEG via le nœud HTTP semble être beaucoup plus difficile car le client affichera une image puis s'arrêtera. Je soupçonne que le problème est que je ne configure pas la connexion HTTP pour être compatible avec le client vidéo HTML5. J'ai essayé une variété de choses comme l'utilisation de HTTP 206 (contenu partiel) et de 200 réponses, en mettant les données dans un tampon puis en les diffusant sans succès, donc je dois revenir aux premiers principes pour m'assurer de bien configurer cela. façon.
Voici ma compréhension de la façon dont cela devrait fonctionner, veuillez me corriger si je me trompe:
1) FFMPEG doit être configuré pour fragmenter la sortie et utiliser un moov vide (drapeaux mov FFMPEG frag_keyframe et empty_moov). Cela signifie que le client n'utilise pas l'atome moov qui se trouve généralement à la fin du fichier, ce qui n'est pas pertinent lors de la diffusion (pas de fin de fichier), mais signifie qu'aucune recherche n'est possible, ce qui est bien pour mon cas d'utilisation.
2) Même si j'utilise des fragments MP4 et des MOOV vides, je dois encore utiliser du contenu partiel HTTP, car le lecteur HTML5 attendra que le flux entier soit téléchargé avant de jouer, ce qui avec un flux en direct ne se termine jamais, donc c'est impossible.
3) Je ne comprends pas pourquoi le transfert du flux STDOUT vers la réponse HTTP ne fonctionne pas encore lors de la diffusion en direct.Si j'enregistre dans un fichier, je peux diffuser ce fichier facilement vers des clients HTML5 en utilisant un code similaire. C'est peut-être un problème de timing car il faut une seconde pour que le spawn FFMPEG démarre, se connecte à la caméra IP et envoie des morceaux au nœud, et les événements de données du nœud sont également irréguliers. Cependant, le bytestream devrait être exactement le même que l'enregistrement dans un fichier, et HTTP devrait être capable de répondre aux retards.
4) Lors de la vérification du journal réseau du client HTTP lors de la diffusion en continu d'un fichier MP4 créé par FFMPEG à partir de la caméra, je vois qu'il y a 3 demandes client: une demande GET générale pour la vidéo, que le serveur HTTP renvoie environ 40 Ko, puis une partie demande de contenu avec une plage d'octets pour les 10K derniers du fichier, puis une demande finale pour les bits du milieu non chargés. Peut-être que le client HTML5, une fois qu'il reçoit la première réponse, demande la dernière partie du fichier pour charger l'atome MP4 MOOV? Si c'est le cas, cela ne fonctionnera pas pour le streaming car il n'y a pas de fichier MOOV et pas de fin de fichier.
5) Lors de la vérification du journal réseau lorsque j'essaie de diffuser en direct, j'obtiens une demande initiale abandonnée avec seulement environ 200 octets reçus, puis une nouvelle demande à nouveau abandonnée avec 200 octets et une troisième demande qui ne fait que 2K de long. Je ne comprends pas pourquoi le client HTML5 abandonnerait la demande car le bytestream est exactement le même que je peux utiliser avec succès lors de la diffusion à partir d'un fichier enregistré. Il semble également que le nœud n'envoie pas le reste du flux FFMPEG au client, mais je peux voir les données FFMPEG dans la routine d'événements .on afin qu'elles parviennent au serveur HTTP du nœud FFMPEG.
6) Bien que je pense que la canalisation du flux STDOUT vers le tampon de réponse HTTP devrait fonctionner, dois-je créer un tampon et un flux intermédiaires qui permettront aux demandes du client de contenu partiel HTTP de fonctionner correctement comme il le fait lorsqu'il lit (avec succès) un fichier ? Je pense que c'est la principale raison de mes problèmes, mais je ne sais pas exactement dans Node comment configurer cela au mieux. Et je ne sais pas comment gérer une demande client pour les données à la fin du fichier car il n'y a pas de fin de fichier.
7) Suis-je sur la mauvaise voie en essayant de gérer 206 demandes de contenu partiel, et cela devrait-il fonctionner avec 200 réponses HTTP normales? Les réponses HTTP 200 fonctionnent bien pour VLC, donc je soupçonne que le client vidéo HTML5 ne fonctionnera qu'avec des demandes de contenu partielles?
Comme j'apprends encore ce genre de choses, il est difficile de travailler à travers les différentes couches de ce problème (FFMPEG, nœud, streaming, HTTP, vidéo HTML5), donc tous les pointeurs seront grandement appréciés. J'ai passé des heures à faire des recherches sur ce site et sur le net, et je n'ai rencontré personne qui a pu faire du streaming en temps réel dans le nœud, mais je ne peux pas être le premier, et je pense que cela devrait pouvoir fonctionner (en quelque sorte !).
Content-Type
votre tête dans votre tête? Utilisez-vous un codage par blocs? C'est là que je commencerais. De plus, HTML5 ne fournit pas nécessairement la fonctionnalité à diffuser, vous pouvez en savoir plus ici . Vous aurez très probablement besoin de mettre en œuvre un moyen de mettre en mémoire tampon et de lire le flux vidéo à l'aide de vos propres moyens ( voir ici ), car cela n'est probablement pas bien pris en charge. Google aussi dans l'API MediaSource.