web-dev-qa-db-fra.com

Meilleure approche de la diffusion HTTP en temps réel vers un client vidéo HTML5

Je suis vraiment coincé pour essayer de comprendre le meilleur moyen de diffuser en temps réel la sortie de ffmpeg sur un client HTML5 à l'aide de node.js, car de nombreuses variables sont en jeu et que je n'ai pas beaucoup d'expérience dans ce domaine, avoir passé de nombreuses heures à essayer différentes combinaisons.

Mon cas d'utilisation est:

1) Le flux RTSP H.264 de la caméra vidéo IP est capté par FFMPEG et recalculé dans un conteneur mp4 à l’aide des paramètres FFMPEG suivants dans le noeud, sortie vers STDOUT. Cette opération est exécutée uniquement sur la connexion client initiale, de sorte que les demandes de contenu partielles ne tentent pas de générer à nouveau FFMPEG.

liveFFMPEG = child_process.spawn("ffmpeg", [
                "-i", "rtsp://admin:[email protected]:554" , "-vcodec", "copy", "-f",
                "mp4", "-reset_timestamps", "1", "-movflags", "frag_keyframe+empty_moov", 
                "-"   // output to stdout
                ],  {detached: false});

2) J'utilise le serveur HTTP du nœud pour capturer le STDOUT et le diffuser au client à la 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 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 de la diffusion en continu 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 les balises vidéo HTML5.

Je n'ai aucun problème avec la lecture en continu (avec fs.createReadStream avec un contenu partiel 206 HTTP) vers le 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), je connais donc 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 nœud HTTP.

Cependant, essayer de diffuser en direct à partir de 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 qu'elle soit compatible avec le client vidéo HTML5. J'ai essayé diverses solutions, telles que l'utilisation de HTTP 206 (contenu partiel) et de 200 réponses, en plaçant les données dans un tampon, puis en les diffusant sans succès. Je dois donc revenir aux principes de base pour m'assurer que le contenu est correct. façon.

Voici comment cela fonctionne, corrigez-moi si je me trompe:

1) FFMPEG doit être configuré pour fragmenter la sortie et utiliser un moov vide (drapeaux FFMPEG frag_keyframe et empty_moov). Cela signifie que le client n'utilise pas le moov atom qui se trouve généralement à la fin du fichier, ce qui n'est pas pertinent lors de la transmission en continu (pas de fin de fichier), mais signifie qu'aucune recherche n'est possible, ce qui me convient parfaitement. Cas.

2) Même si j'utilise des fragments MP4 et des MOOV vides, je dois quand même utiliser un contenu partiel HTTP, car le lecteur HTML5 attendra que le flux entier soit téléchargé avant d'être lu, ce qui avec un flux en direct ne se termine jamais.

3) Je ne comprends pas pourquoi la canalisation du flux STDOUT vers la réponse HTTP ne fonctionne pas lorsque la diffusion est en direct mais que si je sauvegarde dans un fichier, je peux diffuser ce fichier facilement vers des clients HTML5 utilisant un code similaire. C’est peut-être un problème de synchronisation car il faut une seconde au spawn de FFMPEG pour se connecter, se connecter à la caméra IP et envoyer des fragments au noeud, et les événements de données du noeud sont également irréguliers. Cependant, le bytestream devrait être identique à l'enregistrement dans un fichier, et HTTP devrait pouvoir traiter les retards.

4) Lors de la vérification du journal réseau du client HTTP lors de la diffusion d’un fichier MP4 créé par FFMPEG à partir de la caméra, je vois trois demandes client: une demande GET générale pour la vidéo, à laquelle le serveur HTTP renvoie environ 40 Ko, puis une demande partielle. demande de contenu avec une plage d'octets pour les 10 derniers Ko du fichier, puis une demande finale pour les bits du milieu non chargés. Peut-être que le client HTML5, une fois la première réponse reçue, demande à la dernière partie du fichier de charger l’atome MP4 MOOV? Si tel est le cas, cela ne fonctionnera pas pour la diffusion en continu, car il n'y a ni fichier MOOV ni fin du fichier.

5) Lors de la vérification du journal du réseau lors de la tentative de diffusion en direct, je reçois une demande initiale annulée avec seulement environ 200 octets reçus, puis une nouvelle demande abandonnée avec 200 octets et une troisième demande ne faisant que 2K de long. Je ne comprends pas pourquoi le client HTML5 annulerait la demande, car le flux bytestream est exactement le même que celui que je peux utiliser avec succès lors de la diffusion à partir d'un fichier enregistré. Il semble également que node n’envoie pas le reste du flux FFMPEG au client, mais je peux voir les données FFMPEG dans la routine d’événement .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 intermédiaire et un flux permettant aux demandes de client de contenu partiel HTTP de fonctionner correctement comme il le fait quand il lit (avec succès) un fichier ? Je pense que c’est la principale raison de mes problèmes, mais je ne suis pas tout à fait certain dans Node comment le configurer au mieux. Et je ne sais pas comment traiter 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 piste en essayant de traiter 206 demandes de contenu partielles, et cela devrait-il fonctionner avec 200 réponses HTTP normales? Les réponses HTTP 200 fonctionnent bien pour VLC, alors je suppose que le client vidéo HTML5 ne fonctionnera qu'avec des demandes de contenu partielles.

Comme je suis encore en train d'apprendre ce truc, 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 ait été capable de faire du streaming en temps réel dans node, mais je ne peux pas être le premier, et je pense que cela devrait pouvoir fonctionner (d'une manière ou d'une autre). !).

200
deandob

EDIT 3: À partir de IOS 10, HLS prendra en charge les fichiers mp4 fragmentés. La solution à présent est de créer des ressources mp4 fragmentées, avec un manifeste DASH et HLS. > Le flash imaginaire, iOS9 et inférieur et IE 10 et inférieur n'existe pas.

Tout ce qui se trouve sous cette ligne est obsolète. Le garder ici pour la postérité.


EDIT 2: Comme le soulignent les commentaires, les choses changent. Presque tous les navigateurs supporteront les codecs AVC/AAC. iOS nécessite toujours HLS. Mais via des adaptateurs comme hls.js, vous pouvez jouer HLS dans MSE. La nouvelle réponse est HLS + hls.js si vous avez besoin d’iOS. ou simplement MP4 fragmenté (c'est-à-dire DASH) si vous ne le faites pas

La vidéo, et en particulier la vidéo en direct, est très difficile pour de nombreuses raisons. (Veuillez noter que la question d'origine spécifiait que la vidéo HTML5 était obligatoire, mais le demandeur avait déclaré que Flash était possible dans les commentaires. Donc, immédiatement, cette question est trompeuse)

Je vais commencer par rappeler: ICI IS PAS DE SUPPORT OFFICIEL POUR LE LIVE STREAMING SUR HTML5. Il y a des hacks, mais votre kilométrage peut varier.

EDIT: depuis que j'ai écrit cette réponse Media Source Extensions ont mûri, et sont maintenant très proches de devenir une option viable. Ils sont pris en charge sur la plupart des navigateurs principaux. IOS continue d'être un problème.

Ensuite, vous devez comprendre que la vidéo à la demande (VOD) et la vidéo en direct sont très différentes. Oui, ce sont deux vidéos, mais les problèmes sont différents, donc les formats sont différents. Par exemple, si l'horloge de votre ordinateur est 1% plus rapide qu'elle ne le devrait, vous ne le remarquerez pas sur un VOD. Avec la vidéo en direct, vous essayez de lire la vidéo avant que cela ne se produise. Si vous souhaitez rejoindre un flux vidéo en direct en cours, vous avez besoin des données nécessaires pour initialiser le décodeur. Vous devez donc les répéter dans le flux ou les envoyer hors bande. Avec la VOD, vous pouvez lire le début du fichier, qu’ils cherchent à tout moment.

Maintenant, creusons un peu.

Plateformes:

  • iOS
  • PC
  • Mac
  • Android

Codecs:

  • vp8/9
  • h.264
  • thora (vp3)

Méthodes de livraison courantes pour la vidéo en direct dans les navigateurs:

  • DASH (HTTP)
  • HLS (HTTP)
  • flash (RTMP)
  • flash (HDS)

Méthodes de livraison courantes pour la VOD dans les navigateurs:

  • DASH (streaming HTTP)
  • HLS (streaming HTTP)
  • flash (RTMP)
  • flash (streaming HTTP)
  • MP4 (pseudo-streaming HTTP)
  • Je ne vais pas parler de MKV et de OOG, car je ne les connais pas très bien.

tag vidéo html5:

  • MP4
  • webm
  • ogg

Permet de regarder quels navigateurs supportent quels formats

Safari:

  • HLS (iOS et Mac uniquement)
  • h.264
  • MP4

Firefox

  • DASH (via MSE mais pas h.264)
  • h.264 via Flash uniquement!
  • VP9
  • MP4
  • OGG
  • Webm

IE

  • Flash
  • DASH (via MSE IE 11+ uniquement)
  • h.264
  • MP4

Chrome

  • Flash
  • DASH (via MSE)
  • h.264
  • VP9
  • MP4
  • webm
  • ogg

MP4 ne peut pas être utilisé pour la vidéo en direct (REMARQUE: DASH est un sur-ensemble de MP4, ne vous y trompez pas). MP4 est divisé en deux: moov et mdat. Le mdat contient les données audio/vidéo brutes. Mais il n'est pas indexé, donc sans le moov, il est inutile. Le moov contient un index de toutes les données du mdat. Mais en raison de son format, il ne peut pas être "aplati" tant que les horodatages et la taille de CHAQUE image ne sont pas connus. Il est peut-être possible de construire un modèle qui "modifie" la taille de la trame, mais consomme très peu de largeur de bande.

Donc, si vous voulez livrer partout, nous devons trouver le plus petit dénominateur commun. Vous verrez qu'il n'y a pas de LCD ici sans recourir à l'exemple du flash:

  • iOS ne prend en charge que la vidéo h.264. et il ne supporte que HLS pour le live.
  • Firefox ne supporte pas du tout h.264, sauf si vous utilisez flash
  • Flash ne fonctionne pas sous iOS

La chose la plus proche d'un LCD utilise HLS pour obtenir vos utilisateurs iOS et la mise à jour flash pour tous les autres. Mon préféré est d'encoder HLS, puis d'utiliser flash pour lire HLS pour tous les autres. Vous pouvez jouer à HLS en flash via JW player 6 (ou écrire votre propre fichier HLS au format FLV dans AS3 comme je l’ai fait)

Bientôt, le moyen le plus courant de le faire sera HLS sur iOS/Mac et DASH via MSE partout ailleurs (c'est ce que Netflix fera bientôt). Mais nous attendons toujours que tout le monde mette à jour son navigateur. Vous aurez probablement aussi besoin d’un DASH/VP9 séparé pour Firefox (je connais l’open open264; c’est nul. Il ne peut pas faire de la vidéo dans un profil principal ou élevé. C’est donc actuellement inutile).

206
szatmary

Merci à tous, surtout à vous, car cette question est complexe et comporte de nombreuses couches, qui doivent toutes fonctionner avant de pouvoir diffuser de la vidéo en direct. Pour clarifier ma question initiale et l'utilisation de la vidéo HTML5 par rapport à la technologie Flash, mon cas d'utilisation a une nette préférence pour HTML5 car il est générique, facile à implémenter pour le client et pour l'avenir. Flash est la deuxième meilleure destination alors restons avec HTML5 pour cette question.

J'ai beaucoup appris grâce à cet exercice et je suis d'accord pour dire que la diffusion en direct est beaucoup plus difficile que la VOD (qui fonctionne bien avec la vidéo HTML5). Mais cela a fonctionné de manière satisfaisante pour mon cas d'utilisation et la solution s'est révélée très simple, après la recherche d'options plus complexes telles que MSE, flash, schémas de mise en mémoire tampon élaborés dans Node. Le problème était que FFMPEG corrompait le MP4 fragmenté et que je devais ajuster les paramètres FFMPEG, et la redirection du canal de flux de nœud standard sur http que j’utilisais à l’origine était tout ce qui était nécessaire.

Dans MP4, il existe une option de "fragmentation" qui décompose le mp4 en fragments beaucoup plus petits, qui possède son propre index et rend viable l'option de diffusion en direct de mp4. Mais impossible de rechercher dans le flux (OK pour mon cas d'utilisation), et les versions ultérieures de FFMPEG prennent en charge la fragmentation.

Le timing des notes peut être un problème, et avec ma solution, j'ai un décalage de 2 à 6 secondes dû à une combinaison du remuxage (effectivement, FFMPEG doit recevoir le flux en direct, le remuer puis l'envoyer au noeud pour le servir via HTTP). . Peu de choses peuvent être faites à ce sujet, cependant, dans Chrome, la vidéo tente de rattraper le plus possible, ce qui la rend un peu nerveuse mais plus actuelle que IE11 (mon client préféré).

Plutôt que d'expliquer le fonctionnement du code dans cet article, consultez Gist avec des commentaires (le code client n'est pas inclus, il s'agit d'une balise vidéo HTML5 standard avec l'adresse du serveur http du nœud). Gist est ici: https://Gist.github.com/deandob/924009

Je n'ai pas été en mesure de trouver des exemples similaires de ce cas d'utilisation. J'espère donc que l'explication et le code ci-dessus aident les autres, d'autant que j'ai beaucoup appris de ce site et que je me considère toujours comme un débutant!

Bien que ce soit la réponse à ma question spécifique, j'ai sélectionné la réponse de szatmary comme étant la plus acceptée, car elle est la plus complète.

74
deandob

Jetez un oeil à JSMPEG projet. Une excellente idée a été mise en œuvre ici: décoder le MPEG dans le navigateur à l'aide de JavaScript. Les octets de l'encodeur (FFMPEG, par exemple) peuvent être transférés au navigateur à l'aide de WebSockets ou de Flash, par exemple. Je pense que si la communauté se rattrape, ce sera la meilleure solution de streaming vidéo en direct HTML5 pour le moment.

13
Michael Romanenko

J'ai écrit un lecteur vidéo HTML5 autour du codec broadway h264 (emscripten) pouvant lire des vidéos en direct (sans délai) h264 sur tous les navigateurs (de bureau, iOS, ...).

Le flux vidéo est envoyé au client par websocket, image par image décodée et affichée dans un canva (en utilisant webgl pour accélérer)

Découvrez https://github.com/131/h264-live-player sur github.

12
131

Une façon de diffuser en direct une webcam basée sur RTSP sur un client HTML5 (implique un réencodage, donc attendez-vous à une perte de qualité et nécessite de la puissance de calcul)

  • Configurez un serveur icecast (peut-être sur la même machine que votre serveur Web ou sur la machine qui reçoit le flux RTSP de la caméra)
  • Sur la machine recevant le flux de la caméra, n'utilisez pas FFMPEG mais gstreamer. Il est capable de recevoir et de décoder le flux RTSP, de le recoder et de le diffuser sur le serveur icecast. Exemple de pipeline (uniquement vidéo, pas audio):

    gst-launch-1.0 rtspsrc location=rtsp://192.168.1.234:554 user-id=admin user-pw=123456 ! rtph264depay ! avdec_h264 ! vp8enc threads=2 deadline=10000 ! webmmux streamable=true ! shout2send password=pass ip=<IP_OF_ICECAST_SERVER> port=12000 mount=cam.webm
    

=> Vous pouvez ensuite utiliser la balise <video> avec l'URL du flux icecast-stream ( http://127.0.0.1:12000/cam.webm ) et cela fonctionnera dans tous les navigateurs et appareils qui supporte webm

11
Jannis

Que diriez-vous de l’utilisation de la solution jpeg, laissez le serveur le distribuer un par un au navigateur, puis utilisez l’élément canvas pour dessiner ces jpeg? http://thejackalofjavascript.com/rpi-live-streaming/

3
Kiki.J.Hu

Regardez cette solution . Comme je le sais, Flashphoner permet de lire des flux audio + vidéo en direct dans la page HTML5 pure.

Ils utilisent les codecs MPEG1 et G.711 pour la lecture. Le hack rend la vidéo décodée en élément de toile HTML5 et lit l'audio décodé via le contexte audio HTML5.

3
ankitr

Essayez binaryjs. Son juste comme socket.io mais la seule chose qu'il fait bien, c'est qu'il stream vidéo audio. Binaryjs google il

2
Siddharth

C'est une idée fausse très commune. Il n'y a pas de support vidéo HTML5 en direct (sauf HLS sur iOS et Mac Safari). Vous pourrez peut-être le "pirater" à l'aide d'un conteneur Webm, mais je ne m'attendrais pas à ce qu'il soit universellement pris en charge. Ce que vous recherchez est inclus dans les extensions de source multimédia, où vous pouvez envoyer les fragments au navigateur, un à la fois. mais vous aurez besoin d'écrire du javascript côté client.

2
szatmary