Sono davvero bloccato nel cercare di capire il modo migliore per trasmettere l'output in tempo reale di ffmpeg su un client HTML5 usando node.js, poiché ci sono un certo numero di variabili in gioco e non ho molta esperienza in questo spazio, avendo trascorso molte ore a provare combinazioni diverse.
Il mio caso d'uso è:
1) Il flusso RTSP H.264 della videocamera IP viene prelevato da FFMPEG e rimodellato in un contenitore mp4 utilizzando le seguenti impostazioni FFMPEG nel nodo, in uscita su STDOUT. Questo viene eseguito solo sulla connessione client iniziale, in modo che le richieste di contenuto parziale non provino a generare nuovamente 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) Uso il server http del nodo per acquisire STDOUT e trasmetterlo in streaming al client su richiesta del client. Quando il client si connette per la prima volta, ho generato la riga di comando FFMPEG sopra, quindi reindirizza il flusso STDOUT alla risposta HTTP.
liveFFMPEG.stdout.pipe(resp);
Ho anche usato l'evento stream per scrivere i dati FFMPEG nella risposta HTTP, ma non fa differenza
xliveFFMPEG.stdout.on("data",function(data) {
resp.write(data);
}
Uso la seguente intestazione HTTP (che viene anche utilizzata e funzionante durante lo streaming di file preregistrati)
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) Il client deve utilizzare tag video HTML5.
Non ho problemi con la riproduzione in streaming (utilizzando fs.createReadStream con 206 contenuti parziali HTTP) sul client HTML5 un file video precedentemente registrato con la riga di comando FFMPEG sopra (ma salvato in un file anziché STDOUT), quindi conosco il flusso FFMPEG è corretto e posso persino vedere correttamente lo streaming live del video in VLC durante la connessione al server del nodo HTTP.
Tuttavia, provare a trasmettere in diretta da FFMPEG tramite il nodo HTTP sembra essere molto più difficile in quanto il client visualizzerà un fotogramma e poi si fermerà. Ho il sospetto che il problema sia che non sto configurando la connessione HTTP per essere compatibile con il client video HTML5. Ho provato una varietà di cose come l'uso di HTTP 206 (contenuto parziale) e 200 risposte, mettendo i dati in un buffer e poi lo streaming senza fortuna, quindi ho bisogno di tornare ai primi principi per assicurarmi di impostare questo giusto modo.
Ecco la mia comprensione di come dovrebbe funzionare, per favore correggimi se sbaglio:
1) FFMPEG deve essere configurato per frammentare l'output e utilizzare un moov vuoto (FFMPEG frag_keyframe e empty_moov mov flags). Ciò significa che il client non utilizza l'atomo moov che è in genere alla fine del file, il che non è rilevante durante lo streaming (nessuna fine del file), ma significa che non è possibile cercare ciò che va bene per il mio caso d'uso.
2) Anche se utilizzo frammenti MP4 e MOOV vuoti, devo comunque utilizzare il contenuto parziale HTTP, poiché il lettore HTML5 attenderà il download dell'intero flusso prima di riprodurlo, che con uno streaming live non termina mai, quindi non è realizzabile.
3) Non capisco perché il piping dello stream STDOUT alla risposta HTTP non funzioni quando lo streaming è attivo, ma se lo salvo su un file posso trasmettere facilmente questo file ai client HTML5 usando un codice simile. Forse è un problema di temporizzazione in quanto ci vuole un secondo per l'avvio della spawn FFMPEG, la connessione alla telecamera IP e l'invio di blocchi al nodo e anche gli eventi dei dati del nodo sono irregolari. Tuttavia, il bytestream dovrebbe essere esattamente uguale al salvataggio su un file e HTTP dovrebbe essere in grado di gestire i ritardi.
4) Quando controllo il registro di rete dal client HTTP durante lo streaming di un file MP4 creato da FFMPEG dalla telecamera, vedo che ci sono 3 richieste client: una richiesta GET generale per il video, che il server HTTP restituisce circa 40Kb, quindi un parziale richiesta di contenuto con un intervallo di byte per gli ultimi 10 KB del file, quindi una richiesta finale per i bit nel mezzo non caricata. Forse il client HTML5 una volta ricevuta la prima risposta chiede all'ultima parte del file di caricare l'atomo MOOV MP4? In questo caso, non funzionerà per lo streaming in quanto non esiste alcun file MOOV e nessuna fine del file.
5) Quando controllo il registro di rete quando provo a trasmettere in streaming, ricevo una richiesta iniziale interrotta con solo circa 200 byte ricevuti, quindi una nuova richiesta nuovamente interrotta con 200 byte e una terza richiesta che è lunga solo 2K. Non capisco perché il client HTML5 interrompa la richiesta poiché il bytestream è esattamente lo stesso che posso usare con successo quando eseguo lo streaming da un file registrato. Sembra anche che il nodo non stia inviando il resto del flusso FFMPEG al client, ma posso vedere i dati FFMPEG nella routine dell'evento .on, quindi sta arrivando al server HTTP del nodo FFMPEG.
6) Anche se penso che il piping dello stream STDOUT al buffer di risposta HTTP dovrebbe funzionare, devo creare un buffer e un flusso intermedi che consentano alle richieste client di contenuto parziale HTTP di funzionare correttamente come fa quando legge (con successo) un file ? Penso che questo sia il motivo principale dei miei problemi, tuttavia non sono esattamente sicuro su Node come configurarlo al meglio. E non so come gestire una richiesta client per i dati alla fine del file in quanto non esiste una fine del file.
7) Sono sulla strada sbagliata con il tentativo di gestire 206 richieste di contenuto parziale e questo dovrebbe funzionare con normali 200 risposte HTTP? Le risposte HTTP 200 funzionano bene per VLC, quindi sospetto che il client video HTML5 funzionerà solo con richieste di contenuto parziali?
Dato che sto ancora imparando queste cose, è difficile lavorare attraverso i vari livelli di questo problema (FFMPEG, nodo, streaming, HTTP, video HTML5), quindi tutti i suggerimenti saranno molto apprezzati. Ho passato ore a fare ricerche su questo sito e sulla rete e non ho incontrato nessuno che sia stato in grado di eseguire lo streaming in tempo reale nel nodo ma non posso essere il primo e penso che questo dovrebbe essere in grado di funzionare (in qualche modo !).
Content-Type
in testa? Stai usando la codifica di blocco? Ecco da dove iniziare. Inoltre, HTML5 non fornisce necessariamente le funzionalità per lo streaming, puoi leggere di più su questo qui . Molto probabilmente dovrai implementare un modo per bufferizzare e riprodurre il flusso video usando i tuoi mezzi ( vedi qui ), probabilmente non è ben supportato. Anche google nell'API MediaSource.