Ich bin wirklich festgefahren, um zu verstehen, wie man die Echtzeitausgabe von ffmpeg mit node.js am besten auf einen HTML5-Client streamen kann, da eine Reihe von Variablen im Spiel sind und ich nicht viel Erfahrung in diesem Bereich habe. Ich habe viele Stunden damit verbracht, verschiedene Kombinationen auszuprobieren.
Mein Anwendungsfall ist:
1) Der RTSP H.264-Stream der IP-Videokamera wird von FFMPEG aufgenommen und unter Verwendung der folgenden FFMPEG-Einstellungen im Knoten in einen MP4-Container übertragen und an STDOUT ausgegeben. Dies wird nur bei der ersten Clientverbindung ausgeführt, damit Teilinhaltsanforderungen nicht erneut versuchen, FFMPEG zu erzeugen.
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) Ich verwende den Knoten-HTTP-Server, um das STDOUT zu erfassen und es auf Clientanforderung an den Client zurückzusenden. Wenn der Client zum ersten Mal eine Verbindung herstellt, spawne ich die obige FFMPEG-Befehlszeile und leite dann den STDOUT-Stream an die HTTP-Antwort weiter.
liveFFMPEG.stdout.pipe(resp);
Ich habe das Stream-Ereignis auch verwendet, um die FFMPEG-Daten in die HTTP-Antwort zu schreiben, aber es macht keinen Unterschied
xliveFFMPEG.stdout.on("data",function(data) {
resp.write(data);
}
Ich verwende den folgenden HTTP-Header (der auch beim Streamen 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 der Streaming-Wiedergabe (unter Verwendung von fs.createReadStream mit 206 HTTP-Teilinhalten) auf dem HTML5-Client eine Videodatei, die zuvor mit der obigen FFMPEG-Befehlszeile aufgezeichnet wurde (aber in einer Datei anstelle von STDOUT gespeichert wurde), sodass ich den FFMPEG-Stream kenne ist korrekt, und ich kann das Video-Live-Streaming in VLC sogar korrekt sehen, wenn eine Verbindung zum HTTP-Knotenserver hergestellt wird.
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, das Problem ist, dass ich die HTTP-Verbindung nicht so einrichte, dass sie mit dem HTML5-Video-Client kompatibel ist. Ich habe eine Vielzahl von Dingen ausprobiert, z. B. die Verwendung von HTTP 206 (Teilinhalt) und 200 Antworten, das Speichern der Daten in einen Puffer und das Streaming ohne Glück. Daher muss ich zu den ersten Prinzipien zurückkehren, um sicherzustellen, dass ich dies richtig einrichte 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 wird und ein leerer 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, was beim Streaming nicht relevant ist (kein Dateiende), sondern dass keine Suche möglich ist, was für meinen Anwendungsfall in Ordnung ist.
2) Obwohl ich MP4-Fragmente und leeres MOOV verwende, muss ich immer noch HTTP-Teilinhalt verwenden, da der HTML5-Player vor dem Abspielen wartet, bis der gesamte Stream heruntergeladen ist, was bei einem Live-Stream niemals endet und daher nicht funktioniert.
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 handelt es sich um ein Zeitproblem, da es eine Sekunde dauert, bis der FFMPEG-Spawn gestartet, eine Verbindung zur IP-Kamera hergestellt und Chunks an den Knoten gesendet werden. Die Knotendatenereignisse sind ebenfalls unregelmäßig. Der Bytestream sollte jedoch genau dem Speichern in einer Datei entsprechen, und HTTP sollte Verzögerungen berücksichtigen 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, dann eine teilweise Inhaltsanforderung mit einem Bytebereich für die letzten 10 KB der Datei, dann eine endgültige Anforderung für die nicht geladenen Bits in der Mitte. 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 keine MOOV-Datei und kein Dateiende vorhanden ist.
5) Wenn ich das Netzwerkprotokoll überprüfe, wenn ich versuche, live zu streamen, erhalte ich eine abgebrochene erste Anforderung mit nur etwa 200 empfangenen Bytes, dann eine erneut abgebrochene erneute Anforderung mit 200 Bytes und eine dritte Anforderung, die nur 2 KB lang ist. Ich verstehe nicht, warum der HTML5-Client die Anforderung abbrechen würde, da der Bytestream genau der gleiche ist, den ich beim Streaming 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 Ereignisroutine .on sehen, sodass er zum HTTP-Server des FFMPEG-Knotens gelangt.
6) Obwohl ich denke, dass das Weiterleiten des STDOUT-Streams an den HTTP-Antwortpuffer funktionieren sollte, muss ich einen Zwischenpuffer und einen Stream erstellen, damit die HTTP-Clientanforderungen für Teilinhalte ordnungsgemäß funktionieren, wenn eine Datei (erfolgreich) gelesen wird ? Ich denke, dies ist der Hauptgrund für meine Probleme, aber ich bin mir in Node nicht ganz sicher, wie ich das am besten einrichten soll. Und ich weiß nicht, wie ich eine Client-Anfrage für die Daten am Ende der Datei behandeln soll, da es kein Dateiende gibt.
7) Bin ich auf dem falschen Weg, wenn ich versuche, 206 Teilinhaltsanforderungen zu bearbeiten, und sollte dies mit normalen 200 HTTP-Antworten funktionieren? HTTP 200-Antworten funktionieren für VLC einwandfrei. Ich vermute also, dass der HTML5-Video-Client nur mit Teilinhaltsanforderungen funktioniert.
Da ich dieses Zeug noch lerne, ist es schwierig, die verschiedenen Ebenen dieses Problems (FFMPEG, Knoten, Streaming, HTTP, HTML5-Video) zu bearbeiten, sodass alle Hinweise sehr geschätzt werden. Ich habe stundenlang auf dieser Website 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, dies sollte funktionieren (irgendwie) !).
Content-Type
in deinen Kopf gesetzt? Verwenden Sie die Chunk-Codierung? Dort würde ich anfangen. Außerdem bietet HTML5 nicht unbedingt die Funktionalität zum Streamen. Mehr dazu lesen Sie hier . Sie müssen höchstwahrscheinlich eine Möglichkeit implementieren, den Videostream mit Ihren eigenen Mitteln zu puffern und abzuspielen ( siehe hier ), obwohl dies wahrscheinlich nicht gut unterstützt wird. Google auch in MediaSource API.