Bester Ansatz für Echtzeit-HTTP-Streaming zum HTML5-Video-Client

Ich bin wirklich festgefahren, um zu verstehen, wie die Echtzeitausgabe von ffmpeg mit node.js an einen HTML5-Client gestreamt werden kann, da eine Reihe von Variablen im Spiel sind und ich nicht viel Erfahrung in diesem Bereich habe. nachdem ich viele Stunden damit verbracht habe, verschiedene Kombinationen auszuprobieren.

Mein Anwendungsfall ist:

1) Der RTSP-H.264-Stream der IP-Videokamera wird von FFMPEG aufgenommen und mit den folgenden FFMPEG-Einstellungen im Knoten in einen MP4-Container remuxt, der an STDOUT ausgegeben wird. Dies wird nur bei der ersten Client-Verbindung ausgeführt, damit Teilinhaltsanforderungen nicht erneut versuchen, FFMPEG zu erzeugen.

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) Ich benutze den http-Server des Knotens, um das STDOUT zu erfassen und es auf eine Client-Anfrage an den Client zurückzusenden. Wenn der Client zum ersten Mal eine Verbindung herstellt, spawne ich die obige FFMPEG-Befehlszeile und leite den STDOUT-Stream an die HTTP-Antwort weiter.

liveFFMPEG.stdout.pipe(resp);

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

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

Ich verwende den folgenden HTTP-Header (der auch beim Streaming von aufgezeichneten 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
});

3) Der Client muss HTML5-Video-Tags verwenden.

Ich habe keine Probleme mit dem Streaming der Wiedergabe (unter Verwendung von fs.createReadStream mit 206 HTTP-Teilinhalten) einer Videodatei, die zuvor mit der obigen FFMPEG-Befehlszeile aufgezeichnet wurde (aber in einer Datei anstelle von STDOUT gespeichert wurde), auf den HTML5-Client. Daher kenne ich den FFMPEG-Stream ist richtig, und ich kann sogar das Video-Live-Streaming in VLC korrekt sehen, wenn ich eine Verbindung zum HTTP-Knotenserver herstelle.

Der Versuch, live von FFMPEG über den Knoten HTTP zu streamen, scheint jedoch viel schwieriger zu sein, da der Client einen Frame anzeigt und dann stoppt. Ich vermute, dass das Problem darin besteht, dass ich die HTTP-Verbindung nicht so einrichte, dass sie mit dem HTML5-Video-Client kompatibel ist. Ich habe eine Vielzahl von Dingen ausprobiert, wie die Verwendung von HTTP 206 (Teilinhalt) und 200 Antworten, wobei die Daten in einem Puffer zwischengespeichert und dann ohne Erfolg gestreamt wurden. Daher muss ich zu den ersten Grundsätzen zurückkehren, um sicherzustellen, dass ich dies richtig einstelle Weg.

Hier ist mein Verständnis, wie dies funktionieren sollte. Bitte korrigieren Sie mich, wenn ich falsch liege:

1) FFMPEG sollte so eingerichtet werden, dass die Ausgabe fragmentiert und ein leeres moov verwendet wird (FFMPEG frag_keyframe und empty_moov mov flags). Dies bedeutet, dass der Client nicht das moov-Atom verwendet, das sich normalerweise am Ende der Datei befindet und beim Streaming nicht relevant ist (kein Dateiende), sondern dass keine Suche möglich ist, was für meinen Anwendungsfall in Ordnung ist.

2) Auch wenn ich MP4-Fragmente und leeres MOOV verwende, muss ich immer noch HTTP-Teilinhalte verwenden, da der HTML5-Player vor dem Abspielen warten muss, bis der gesamte Stream heruntergeladen ist, was bei einem Live-Stream niemals endet.

3) Ich verstehe nicht, warum das Weiterleiten des STDOUT-Streams an die HTTP-Antwort beim Live-Streaming noch nicht funktioniert. Wenn ich in einer Datei speichere, kann ich diese Datei mit ähnlichem Code problemlos an HTML5-Clients streamen. Möglicherweise ist es ein Zeitproblem, da es eine Sekunde dauert, bis der FFMPEG-Spawn gestartet, eine Verbindung zur IP-Kamera hergestellt, eine Ära erstellt und Blöcke an den Knoten gesendet wird. Die Datenereignisse des Knotens sind ebenfalls unregelmäßig. Der Bytestream sollte jedoch genau so sein wie das Speichern in einer Datei, und HTTP sollte Verzögerungen ausgleichen können.

4) Beim Überprüfen des Netzwerkprotokolls vom HTTP-Client beim Streamen einer von FFMPEG erstellten MP4-Datei von der Kamera werden drei Clientanforderungen angezeigt: Eine allgemeine GET-Anforderung für das Video, die der HTTP-Server etwa 40 KB zurückgibt, und eine teilweise Inhaltsanforderung mit einem Byte-Bereich für die letzten 10 KB der Datei, dann eine letzte Anforderung für die Bits in der Mitte, die nicht geladen werden. Vielleicht fragt der HTML5-Client nach Erhalt der ersten Antwort nach dem letzten Teil der Datei, um das MP4-MOOV-Atom zu laden? In diesem Fall funktioniert das Streaming nicht, da es keine MOOV-Datei und kein Dateiende gibt.

5) Wenn ich das Netzwerkprotokoll überprüfe, wenn ich versuche, live zu streamen, erhalte ich eine abgebrochene anfängliche Anforderung mit nur etwa 200 Bytes, dann wird eine erneute Anforderung mit 200 Bytes und einer dritten Anforderung, die nur 2 KB lang ist, abgebrochen. Ich verstehe nicht, warum der HTML5-Client die Anforderung abbrechen würde, da der Bytestream genau derselbe ist, den ich beim Streamen von einer aufgezeichneten Datei erfolgreich verwenden kann. Es scheint auch, dass der Knoten den Rest des FFMPEG-Streams nicht an den Client sendet, aber ich kann die FFMPEG-Daten in der .on-Ereignisroutine sehen, sodass sie zum HTTP-Server des FFMPEG-Knotens gelangen.

6) Obwohl ich denke, dass das Weiterleiten des STDOUT-Streams an den HTTP-Antwortpuffer funktionieren sollte, muss ich einen Zwischenpuffer und -stream erstellen, der es den HTTP-Teilinhalts-Client-Anforderungen ermöglicht, ordnungsgemäß zu funktionieren, wie es der Fall ist, wenn eine Datei (erfolgreich) gelesen wird ? Ich denke, dies ist der Hauptgrund für meine Probleme, aber ich bin mir nicht ganz sicher, wie ich das am besten einrichten soll. Und ich weiß nicht, wie ich mit einer Client-Anfrage nach den Daten am Ende der Datei umgehen soll, da es kein Dateiende gibt.

7) Bin ich auf dem falschen Weg mit dem Versuch, 206 Teilinhaltsanforderungen zu verarbeiten, und sollte dies mit normalen 200 HTTP-Antworten funktionieren? HTTP 200-Antworten funktionieren für VLC einwandfrei. Ich vermute, dass der HTML5-Video-Client nur mit Teilinhaltsanforderungen funktioniert.

Da ich immer noch lerne, dass es schwierig ist, die verschiedenen Ebenen dieses Problems (FFMPEG, Node, Streaming, HTTP, HTML5-Video) zu durcharbeiten, werden alle Hinweise sehr geschätzt. Ich habe stundenlang auf dieser Site und im Internet recherchiert und bin auf niemanden gestoßen, der in der Lage war, Echtzeit-Streaming in Node durchzuführen, aber ich kann nicht der erste sein, und ich denke, das sollte funktionieren (irgendwie) !).

Antworten auf die Frage(9)

Ihre Antwort auf die Frage