Bester Ansatz für Echtzeit-Http-Streaming zu HTML5-Video-Client

I' m wirklich stecken versucht, den besten Weg zu verstehen, Echtzeit-Ausgabe von ffmpeg zu einem HTML5-Client mit node.js zu streamen, da es eine Reihe von Variablen im Spiel sind und ich don' t haben eine Menge Erfahrung in diesem Raum, nachdem viele Stunden versucht, verschiedene Kombinationen. Mein Anwendungsfall ist:

  1. IP-Videokamera RTSP H.264-Stream wird von FFMPEG abgeholt und in einen mp4-Container mit den folgenden FFMPEG-Einstellungen in Node remuxed, Ausgabe auf STDOUT. Dies wird nur bei der ersten Client-Verbindung ausgeführt, so dass partielle Inhaltsanforderungen nicht versuchen, FFMPEG erneut zu starten.
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});
  1. Ich verwende den Node-Http-Server, um STDOUT zu erfassen und bei einer Client-Anfrage an den Client zurückzusenden. Wenn der Client zum ersten Mal eine Verbindung herstellt, starte ich die obige FFMPEG-Befehlszeile und leite den STDOUT-Stream in die HTTP-Antwort.
liveFFMPEG.stdout.pipe(resp);

Ich habe auch das Stream-Ereignis verwendet, um die FFMPEG-Daten in die HTTP-Antwort zu schreiben, aber das macht keinen Unterschied

xliveFFMPEG.stdout.on("data",function(data) {
        resp.write(data);
}

Ich verwende den folgenden HTTP-Header (der auch beim Streaming voraufgezeichneter Dateien verwendet wird und funktioniert)

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
});
  1. Der Client muss HTML5-Video-Tags verwenden. Ich habe keine Probleme mit der Streaming-Wiedergabe (unter Verwendung von fs.createReadStream mit 206 HTTP-Teilinhalten) an den HTML5-Client einer Videodatei, die zuvor mit der oben genannten FFMPEG-Befehlszeile aufgezeichnet wurde (aber in einer Datei statt in STDOUT gespeichert wurde). Der Versuch, live von FFMPEG über HTTP-Knoten zu streamen, scheint jedoch viel schwieriger zu sein, da der Client ein Bild anzeigt und dann anhält. Ich vermute, dass das Problem darin liegt, dass ich die HTTP-Verbindung nicht so einrichte, dass sie mit dem HTML5-Video-Client kompatibel ist. Ich habe versucht, eine Vielzahl von Dingen wie mit HTTP 206 (partielle Inhalte) und 200 Antworten, die Daten in einen Puffer dann Streaming mit kein Glück, so muss ich zurück zu den ersten Prinzipien zu gehen, um sicherzustellen, dass ich diese Einrichtung der richtige Weg. Hier ist mein Verständnis von wie dies funktionieren sollte, bitte korrigieren Sie mich, wenn ich falsch bin:
  2. FFMPEG sollte so eingestellt sein, dass es die Ausgabe fragmentiert und ein leeres Moov verwendet (FFMPEG frag_keyframe und empty_moov mov flags). Das bedeutet, dass der Client das Moov-Atom nicht verwendet, das sich normalerweise am Ende der Datei befindet, was beim Streaming nicht relevant ist (kein Ende der Datei), aber bedeutet, dass kein Suchen möglich ist, was für meinen Anwendungsfall in Ordnung ist.
  3. Auch wenn ich MP4-Fragmente und leere MOOV verwende, muss ich immer noch HTTP-Teilinhalte verwenden, da der HTML5-Player wartet, bis der gesamte Stream heruntergeladen ist, bevor er abgespielt wird, was bei einem Live-Stream nie endet und daher nicht funktioniert.
  4. Ich verstehe nicht, warum die Weiterleitung des STDOUT-Streams an die HTTP-Antwort nicht funktioniert, wenn ich live streame, aber wenn ich in einer Datei speichere, kann ich diese Datei mit ähnlichem Code problemlos an HTML5-Clients streamen. Vielleicht ist es ein Timing-Problem, da es eine Sekunde dauert, bis der FFMPEG-Spawn startet, sich mit der IP-Kamera verbindet und Chunks an den Knoten sendet, und die Datenereignisse des Knotens sind ebenfalls unregelmäßig. Der Bytestream sollte jedoch genau dasselbe sein wie das Speichern in einer Datei, und HTTP sollte in der Lage sein, Verzögerungen auszugleichen.
  5. Bei der Überprüfung des Netzwerkprotokolls des HTTP-Clients beim Streaming einer mit FFMPEG erstellten MP4-Datei von der Kamera sehe ich, dass es drei Client-Anfragen gibt: Eine allgemeine GET-Anforderung für das Video, die der HTTP-Server mit etwa 40 KB zurückgibt, dann eine Teilinhaltsanforderung mit einem Byte-Bereich für die letzten 10 KB der Datei, dann eine letzte Anforderung für die nicht geladenen Bits in der Mitte. Vielleicht fragt der HTML5-Client, sobald er die erste Antwort erhält, nach dem letzten Teil der Datei, um das MP4 MOOV-Atom zu laden? Wenn dies der Fall ist, wird es nicht für Streaming funktionieren, da es keine MOOV-Datei und kein Ende der Datei gibt.
  6. Wenn ich das Netzwerkprotokoll überprüfe, wenn ich versuche, live zu streamen, erhalte ich eine abgebrochene erste Anfrage mit nur etwa 200 empfangenen Bytes, dann eine erneute Anfrage, die ebenfalls mit 200 Bytes abgebrochen wird, und eine dritte Anfrage, die nur 2K lang ist. Ich verstehe nicht, warum der HTML5-Client die Anfrage abbrechen würde, da der Bytestream genau derselbe ist, den ich erfolgreich verwenden kann, wenn ich von einer aufgezeichneten Datei streame. Es scheint auch, dass Node nicht den Rest des FFMPEG-Streams an den Client sendet, aber ich kann die FFMPEG-Daten in der .on-Ereignisroutine sehen, so dass es an den FFMPEG-Node-HTTP-Server gelangt.
  7. Obwohl ich denke, dass die Weiterleitung des STDOUT-Streams an den HTTP-Antwortpuffer funktionieren sollte, muss ich einen Zwischenpuffer und einen Stream erstellen, der es ermöglicht, dass die HTTP-Teilinhaltsanfragen des Clients ordnungsgemäß funktionieren, wie es der Fall ist, wenn er (erfolgreich) eine Datei liest? Ich denke, dies ist der Hauptgrund für meine Probleme, aber ich bin nicht genau sicher in Node, wie man das am besten einrichtet. Und ich weiß nicht, wie man eine Client-Anfrage für die Daten am Ende der Datei zu behandeln, da es kein Ende der Datei ist.
  8. Bin ich auf dem falschen Weg, wenn ich versuche, 206 Teilinhaltsanfragen zu behandeln, und sollte dies mit normalen 200 HTTP-Antworten funktionieren? HTTP 200 Antworten funktioniert gut für VLC, so dass ich vermute, dass der HTML5-Video-Client nur mit partiellen Content-Anfragen funktionieren wird? Da ich dieses Zeug immer noch lerne, ist es schwierig, die verschiedenen Ebenen dieses Problems zu durchdringen (FFMPEG, Node, Streaming, HTTP, HTML5 Video), so dass ich jeden Hinweis sehr zu schätzen weiß. Ich habe Stunden damit verbracht, auf dieser Website und dem Netz zu recherchieren, und ich habe nicht über jemanden, der in der Lage, Echtzeit-Streaming in Knoten zu tun, aber ich kann nicht der erste sein, und ich denke, dies sollte in der Lage sein, zu arbeiten (irgendwie!).
Lösung

EDIT 3: Ab IOS 10 wird HLS fragmentierte mp4-Dateien unterstützen. Die Lösung ist es, fragmentierte mp4-Assets mit einem DASH- und HLS-Manifest zu erstellen. Tun Sie so, als ob Flash, iOS9 und darunter und IE 10 und darunter nicht existieren würden.

Alles unterhalb dieser Zeile ist nicht mehr aktuell. Ich behalte es hier für die Nachwelt.


EDIT 2: Wie die Leute in den Kommentaren anmerken, ändern sich die Dinge. Fast alle Browser werden AVC/AAC-Codecs unterstützen. iOS benötigt immer noch HLS. Aber über Adaptoren wie hls.js kann man

HLS in MSE. Die neue Antwort ist HLS+hls.js, wenn Sie iOS benötigen. oder einfach Fragmentiertes MP4 (d.h. DASH), wenn Sie es nicht brauchen

Es gibt viele Gründe, warum Video und insbesondere Live-Video sehr schwierig ist. (Bitte beachten Sie, dass in der ursprünglichen Frage angegeben wurde, dass HTML5-Video eine Voraussetzung ist, der Fragesteller aber in den Kommentaren angab, dass Flash möglich ist. Daher ist diese Frage sofort irreführend)

Zunächst möchte ich wiederholen: ES GIBT KEINE OFFIZIELLE UNTERSTÜTZUNG FÜR LIVE-STREAMING ÜBER HTML5. Es gibt Hacks, aber das kann variieren.

EDIT: Seit ich diese Antwort geschrieben habe, sind Media Source Extensions gereift,

und sind nun kurz davor, eine brauchbare Option zu werden. Sie werden unterstützt auf den meisten großen Browsern. IOS ist nach wie vor ein Hindernis.

Als Nächstes müssen Sie verstehen, dass Video on Demand (VOD) und Live-Video sehr unterschiedlich sind. Ja, es handelt sich bei beiden um Video, aber die Probleme sind unterschiedlich, und daher sind auch die Formate unterschiedlich. Wenn zum Beispiel die Uhr in Ihrem Computer 1 % schneller läuft als sie sollte, werden Sie das bei einem VOD nicht bemerken. Bei Live-Videos versuchen Sie, das Video abzuspielen, bevor es passiert. Wenn Sie sich in einen laufenden Live-Videostream einklinken wollen, benötigen Sie die Daten, die zur Initialisierung des Decoders erforderlich sind, d. h. sie müssen im Stream wiederholt oder außerhalb des Bandes gesendet werden. Bei VOD können Sie den Anfang der Datei lesen und an einem beliebigen Punkt suchen.

Lassen Sie uns nun ein wenig eintauchen.

Plattformen:

  • iOS
  • PC
  • Mac
  • Android

Codecs:

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

Gängige Übertragungsmethoden für Live-Video in Browsern:

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

Gängige Bereitstellungsmethoden für VOD in Browsern:

  • DASH (HTTP-Streaming)
  • HLS (HTTP-Streaming)
  • Flash (RTMP)
  • Flash (HTTP-Streaming)
  • MP4 (HTTP-Pseudostreaming)
  • Ich werde nicht über MKV und OOG sprechen, weil ich sie nicht sehr gut kenne.

html5 video tag:

  • MP4
  • webm
  • ogg

Sehen wir uns an, welche Browser welche Formate unterstützen

Safari:

  • HLS (nur iOS und Mac)
  • h.264
  • MP4

Firefox

  • DASH (über MSE, aber kein h.264)
  • h.264 nur über Flash!
  • VP9
  • MP4
  • OGG
  • Webm

IE

  • Flash
  • DASH (nur über MSE IE 11+)
  • h.264
  • MP4

Chrome

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

MP4 kann nicht für Live-Video verwendet werden (HINWEIS: DASH ist eine Obermenge von MP4, also nicht damit verwechseln). MP4 wird in zwei Teile zerlegt: moov und mdat. mdat enthält die rohen Audio- und Videodaten. Sie sind jedoch nicht indiziert, so dass sie ohne moov nutzlos sind. Das moov enthält einen Index aller Daten im mdat. Aufgrund seines Formats kann es jedoch nicht geglättet werden, bis die Zeitstempel und die Größe JEDES Einzelbildes bekannt sind. Es ist zwar möglich, eine Moov-Datei zu erstellen, die die Frame-Größen fälscht, aber das ist sehr verschwenderisch in Bezug auf die Bandbreite.

Wenn Sie also überall liefern wollen, müssen wir den kleinsten gemeinsamen Nenner finden. Sie werden sehen, dass es hier kein LCD gibt, ohne auf Flash zurückzugreifen Beispiel:

  • iOS unterstützt nur h.264-Videos und HLS nur für Live-Übertragungen.
  • Firefox unterstützt h.264 überhaupt nicht, es sei denn, Sie verwenden Flash.
  • Flash funktioniert nicht in iOS

Das, was einem LCD am nächsten kommt, ist die Verwendung von HLS für iOS-Nutzer und Flash für alle anderen. Mein persönlicher Favorit ist es, HLS zu kodieren und dann Flash zu verwenden, um HLS für alle anderen abzuspielen. Sie können HLS in Flash über JW Player 6 abspielen (oder Ihr eigenes HLS in FLV in AS3 schreiben, wie ich es getan habe).

Bald wird die gängigste Methode HLS auf iOS/Mac und DASH über MSE überall sonst sein (so wird es auch Netflix bald machen). Aber wir warten immer noch darauf, dass alle ihre Browser aktualisieren. Sie werden wahrscheinlich auch ein separates DASH/VP9 für Firefox benötigen (ich weiß von open264; es ist scheiße. Es kann keine Videos im Haupt- oder Hochprofil darstellen. Also ist es derzeit nutzlos).

Kommentare (6)

Vielen Dank an alle, insbesondere an szatmary, da dies eine komplexe Frage ist und viele Ebenen hat, die alle funktionieren müssen, bevor man Live-Video streamen kann. Um meine ursprüngliche Frage und die Verwendung von HTML5-Video im Vergleich zu Flash zu klären - mein Anwendungsfall hat eine starke Präferenz für HTML5, weil es generisch ist, einfach auf dem Client zu implementieren und die Zukunft. Flash ist weit abgeschlagen an zweiter Stelle, also bleiben wir in dieser Frage bei HTML5.

Ich habe durch diese Übung viel gelernt und stimme zu, dass Live-Streaming viel schwieriger ist als VOD (das gut mit HTML5-Video funktioniert). Aber ich habe das für meinen Anwendungsfall zufriedenstellend hinbekommen und die Lösung erwies sich als sehr einfach, nachdem ich komplexere Optionen wie MSE, Flash und ausgeklügelte Puffersysteme in Node ausprobiert hatte. Das Problem war, dass FFMPEG das fragmentierte MP4 beschädigte und ich die FFMPEG-Parameter anpassen musste, und die Standard-Node-Stream-Pipe-Umleitung über http, die ich ursprünglich verwendete, war alles, was benötigt wurde.

In MP4 gibt es eine 'Fragmentierung' Option, die das MP4 in viel kleinere Fragmente zerlegt, die ihren eigenen Index haben und die MP4-Live-Streaming-Option praktikabel machen. Es ist jedoch nicht möglich, in den Stream zurückzusuchen (was für meinen Anwendungsfall OK ist), und spätere Versionen von FFMPEG unterstützen die Fragmentierung.

Beachten Sie, dass das Timing ein Problem sein kann, und bei meiner Lösung habe ich eine Verzögerung zwischen 2 und 6 Sekunden, die durch eine Kombination aus Remuxen verursacht wird (FFMPEG muss den Live-Stream empfangen, remuxen und dann an den Knotenpunkt senden, um ihn über HTTP zu übertragen). Daran lässt sich nicht viel ändern, aber in Chrome versucht das Video, so viel wie möglich aufzuholen, was das Video etwas ruckelig macht, aber aktueller als im IE11 (meinem bevorzugten Client).

Anstatt zu erklären, wie der Code funktioniert in diesem Beitrag, überprüfen Sie die GIST mit Kommentaren (der Client-Code ist nicht enthalten, es ist ein Standard-HTML5-Video-Tag mit dem Knoten http-Server-Adresse). GIST ist hier: https://gist.github.com/deandob/9240090

Ich war nicht in der Lage, ähnliche Beispiele für diesen Anwendungsfall zu finden, daher hoffe ich, dass die obige Erklärung und der Code anderen helfen, zumal ich so viel von dieser Website gelernt habe und mich immer noch als Anfänger betrachte!

Obwohl dies die Antwort auf meine spezielle Frage ist, habe ich szatmary's Antwort als die akzeptierte Antwort ausgewählt, da sie am umfassendsten ist.

Kommentare (25)

Dies ist ein weit verbreiteter Irrglaube. Es gibt keine Live-HTML5-Video-Unterstützung (außer für HLS auf iOS und Mac Safari). Möglicherweise können Sie es mit einem Webm-Container "hacken", aber ich würde nicht erwarten, dass das allgemein unterstützt wird. Was Sie suchen, ist in den Media Source Extensions enthalten, wo Sie die Fragmente an den Browser ein zu einer Zeit zu füttern. aber Sie müssen einige Client-Seite Javascript zu schreiben.

Kommentare (15)