Il miglior approccio allo streaming http in tempo reale sul client video HTML5


213

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 !).


4
Questo è un argomento difficile. Cominciando dall'inizio. Ti sei messo Content-Typein 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.
tsturzl,

Grazie per la risposta. Sì, il tipo di contenuto è "video / mp4" e questo codice funziona per lo streaming di file video. Sfortunatamente MediaSource è solo Chrome, devo supportare altri browser. Esiste una specifica su come il client video HTML5 interagisce con un server di streaming HTTP? Sono sicuro che cosa voglio fare, ma non so esattamente come (con node.js ma potrei usare C # o C ++ se è più semplice)
deandob,

2
Il problema non è nel tuo backend. Lo streaming video va bene. Il problema è nel tuo frontend / client, devi implementare tu stesso lo streaming. HTML5 semplicemente non gestisce i flussi. Probabilmente dovrai esplorare le opzioni per browser molto probabilmente. Leggere gli standard w3 per il tag video e le API multimediali sarebbe un buon punto di partenza.
tsturzl,

Sembra che dovrebbe essere possibile farlo funzionare. Non sto offrendo una risposta definitiva, ma sospetto che questo problema si riferisca al fatto che il browser si aspetta il resto dell'intestazione / atomi del contenitore mp4 all'inizio e non il fotogramma successivo nel flusso video. Se invii un atomo MOOV per un video molto lungo (in modo che il lettore continui a richiedere) così come le altre intestazioni previste e poi inizi a copiare da ffmpeg, questo potrebbe funzionare. Dovresti anche nascondere la barra di scorrimento usando js nel browser in modo che non possano scansionare in avanti.
jwriteclub,

Suggerirei di prendere in considerazione WebRTC che sta ottenendo un supporto migliore tra i browser di giorno in giorno.
Alex Cohn,

Risposte:


209

EDIT 3: A partire da IOS 10, HLS supporterà file mp4 frammentati. La risposta ora è creare risorse mp4 frammentate, con un manifest DASH e HLS. > Finta flash, iOS9 e versioni precedenti e IE 10 e versioni precedenti non esistono.

Tutto al di sotto di questa riga non è aggiornato. Tenerlo qui per i posteri.


EDIT 2: Come sottolineano le persone nei commenti, le cose cambiano. Quasi tutti i browser supporteranno i codec AVC / AAC. iOS richiede ancora HLS. Ma tramite adattatori come hls.js puoi giocare a HLS in MSE. La nuova risposta è HLS + hls.js se hai bisogno di iOS. o semplicemente frammentato MP4 (cioè DASH) se non lo fai

Ci sono molte ragioni per cui il video e, in particolare, il video live sono molto difficili. (Si noti che la domanda originale specificava che il video HTML5 è un requisito, ma chi l'ha chiesto ha dichiarato che Flash è possibile nei commenti. Quindi, immediatamente, questa domanda è fuorviante)

Per prima cosa riaffermerò: NON ESISTE UN SUPPORTO UFFICIALE PER LO STREAMING IN DIRETTA SU HTML5 . Ci sono hack, ma il tuo chilometraggio può variare.

EDIT: da quando ho scritto questa risposta, le estensioni di Media Source sono maturate e ora sono molto vicine a diventare un'opzione praticabile. Sono supportati sulla maggior parte dei browser principali. IOS continua a resistere.

Successivamente, devi capire che Video on demand (VOD) e video live sono molto diversi. Sì, sono entrambi video, ma i problemi sono diversi, quindi i formati sono diversi. Ad esempio, se l'orologio nel tuo computer è dell'1% più veloce di quanto dovrebbe, non noterai un VOD. Con il video live, proverai a riprodurre il video prima che accada. Se si desidera unire un flusso video live in corso, sono necessari i dati necessari per inizializzare il decodificatore, quindi è necessario ripeterlo nel flusso o inviarlo fuori banda. Con VOD, puoi leggere l'inizio del file che cercano in qualsiasi punto desideri.

Ora scaviamo un po '.

piattaforme:

  • iOS
  • PC
  • Mac
  • androide

codec:

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

Metodi di consegna comuni per i video in diretta nei browser:

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

Metodi di consegna comuni per VOD nei browser:

  • DASH (streaming HTTP)
  • HLS (streaming HTTP)
  • flash (RTMP)
  • flash (streaming HTTP)
  • MP4 (pseudo streaming HTTP)
  • Non parlerò di MKV e OOG perché non li conosco molto bene.

tag video html5:

  • MP4
  • webm
  • ogg

Vediamo quali browser supportano quali formati

Safari:

  • HLS (solo iOS e mac)
  • h.264
  • MP4

Firefox

  • DASH (tramite MSE ma non h.264)
  • h.264 solo tramite Flash!
  • VP9
  • MP4
  • OGG
  • webm

IE

  • Veloce
  • DASH (solo tramite MSE IE 11+)
  • h.264
  • MP4

Cromo

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

MP4 non può essere utilizzato per i video live (NOTA: DASH è un superset di MP4, quindi non confonderti con quello). MP4 è suddiviso in due pezzi: moov e mdat. mdat contiene i dati audio video grezzi. Ma non è indicizzato, quindi senza il moov è inutile. Il moov contiene un indice di tutti i dati nel mdat. Ma a causa del suo formato, non può essere "appiattito" fino a quando non si conoscono i timestamp e le dimensioni di OGNI fotogramma. Potrebbe essere possibile costruire un moov che "fibs" le dimensioni del frame, ma è molto dispendioso in termini di larghezza di banda.

Quindi, se vuoi consegnare ovunque, dobbiamo trovare il minimo comune denominatore. Vedrai che non c'è LCD qui senza ricorrere all'esempio flash:

  • iOS supporta solo video h.264. e supporta solo HLS per live.
  • Firefox non supporta affatto h.264, a meno che non si usi il flash
  • Flash non funziona in iOS

La cosa più vicina a un LCD è usare HLS per ottenere i tuoi utenti iOS e flash per tutti gli altri. Il mio preferito è codificare HLS, quindi utilizzare Flash per riprodurre HLS per tutti gli altri. Puoi giocare a HLS in flash tramite JW player 6 (o scrivere il tuo HLS su FLV in AS3 come ho fatto io)

Presto, il modo più comune per farlo sarà HLS su iOS / Mac e DASH via MSE ovunque (questo è ciò che Netflix farà presto). Ma stiamo ancora aspettando che tutti aggiornino il proprio browser. Probabilmente avrai anche bisogno di un DASH / VP9 separato per Firefox (Conosco open264; fa schifo. Non può fare video nel profilo principale o alto. Quindi è attualmente inutile).


Grazie szatmary per lo sfondo dettagliato e pro / contro sulle varie opzioni. Ho selezionato questa risposta come accettata in quanto i contorni dei concetti sono più importanti della correzione specifica che ho trovato per rispondere alla domanda originale. Buona fortuna con la taglia!
decano del

9
Questa non è una soluzione funzionante a questa domanda. Di seguito è disponibile una soluzione funzionante a questo problema.
jwriteclub,

2
Firefox ora supporta MSE e h.264 in modo nativo. Vai a www.youtube.com/html5 con l'ultimo browser FF per confermare. Ho testato con FF 37. Safari 8+ su Mac ora supporta anche MSE.
BigTundra

@BigTundra sì, Safari supporta MSE dal lancio di Yosemite su Mac. Ma non iOS. Non sono sicuro di Windows. (Safari su Windows è ancora una cosa?) Firefox 37.0.2 su (mio) Mac non sembra supportare affatto MSE secondo quel link. Ma supporta H.264. Firefox ha aggiunto e rimosso e aggiunto nuovamente il supporto H.264 in passato.
szatmary,


75

Grazie a tutti soprattutto szatmary in quanto questa è una domanda complessa e ha molti livelli, tutti che devono funzionare prima di poter trasmettere video in diretta. Per chiarire la mia domanda originale e l'uso del video HTML5 rispetto al flash, il mio caso d'uso ha una forte preferenza per HTML5 perché è generico, facile da implementare sul client e sul futuro. Flash è il secondo migliore in assoluto, quindi restiamo fedeli a HTML5 per questa domanda.

Ho imparato molto attraverso questo esercizio e sono d'accordo che lo streaming live è molto più difficile di VOD (che funziona bene con i video HTML5). Ma ho fatto in modo che funzionasse in modo soddisfacente per il mio caso d'uso e la soluzione ha funzionato in modo molto semplice, dopo aver inseguito opzioni più complesse come MSE, flash, elaborati schemi di buffering in Node. Il problema era che FFMPEG stava corrompendo il frammentato MP4 e dovevo mettere a punto i parametri FFMPEG e il reindirizzamento del tubo di flusso del nodo standard su http che usavo originariamente era tutto ciò che era necessario.

In MP4 esiste un'opzione 'frammentazione' che suddivide mp4 in frammenti molto più piccoli che ha il suo indice e rende praticabile l'opzione di streaming live mp4. Ma non è possibile cercare di nuovo nello stream (OK per il mio caso d'uso) e le versioni successive di FFMPEG supportano la frammentazione.

Il tempismo delle note può essere un problema e con la mia soluzione ho un ritardo compreso tra 2 e 6 secondi causato da una combinazione del remuxing (in effetti FFMPEG deve ricevere il flusso live, rimodellarlo e inviarlo al nodo per la pubblicazione su HTTP) . Non si può fare molto al riguardo, tuttavia in Chrome il video cerca di recuperare il più possibile, il che rende il video un po 'nervoso ma più attuale di IE11 (il mio client preferito).

Invece di spiegare come funziona il codice in questo post, controlla GIST con commenti (il codice client non è incluso, è un tag video HTML5 standard con l'indirizzo del server http del nodo). GIST è qui: https://gist.github.com/deandob/9240090

Non sono stato in grado di trovare esempi simili di questo caso d'uso, quindi spero che la spiegazione e il codice di cui sopra aiutino gli altri, soprattutto perché ho imparato così tanto da questo sito e mi considero ancora un principiante!

Sebbene questa sia la risposta alla mia domanda specifica, ho selezionato la risposta di szatmary come accettata in quanto è la più completa.


33
Scusa, ma l'ho trovato da solo, la scrittura della mia risposta lo rende abbastanza chiaro. Le risposte precedenti sono state tutte utili e apprezzate, ma non hanno contribuito in modo significativo, e ho anche inviato il codice di lavoro nel GIST e nessun altro ha. Non mi interessa la "reputazione", mi interessa imparare a sapere se il mio approccio e il mio codice possono essere migliorati. E la risposta che ho spuntato ha risolto il mio problema, quindi sono confuso su quale sia il problema qui. Sono abbastanza nuovo di SO, quindi sono felice di sentirmi interagire in un modo diverso, trovo questo sito utile e la mia risposta dovrebbe aiutare gli altri.
Deandob,

2
Sembra che in questa community non sia corretto selezionare la risposta come risposta accettata se hai posto la domanda anche se risolve il problema originale. Anche se questo sembra controintuitivo, la documentazione dei concetti è più importante della correzione effettiva, che sto bene perché aiuta gli altri ad imparare. Ho deselezionato la mia risposta e selezionato szatmary come il più articolato intorno ai concetti.
decano del

6
@deandob: ho pubblicato una taglia per una soluzione funzionante a questo problema che hai fornito con successo. La risposta accettata afferma che non esiste una soluzione funzionante ed è quindi chiaramente inaccurata.
jwriteclub,

2
Grazie. Sembra che altri abbiano valutato erroneamente la mia risposta originale e, dato che sono nuovo, ho appena pensato che le cose funzionino qui. Non voglio causare confusione, ma verificherò con la gente sull'overflow del meta stack. A proposito: la mia soluzione sta funzionando molto bene e dovrebbe essere fattibile per gli altri, e c'è una variazione sulla soluzione pubblicata che può ridurre il ritardo iniziale (il buffer in node.js inizialmente cerca quindi di terminare lo stream alla fine del client) .
Deandob,

4
Ho chiarito da un moderatore che il mio approccio originale era quello di rispondere alla domanda da solo e selezionarlo come risposta era l'approccio corretto. Per maggiori informazioni (o se vuoi discuterne ulteriormente) vedi il thread sul meta sito. meta.stackexchange.com/questions/224068/…
deandob

14

Dai un'occhiata al progetto JSMPEG . C'è una grande idea implementata lì: decodificare MPEG nel browser usando JavaScript. I byte dall'encoder (ad esempio FFMPEG) possono essere trasferiti al browser utilizzando WebSocket o Flash, ad esempio. Se la community raggiungerà, penso, per ora sarà la migliore soluzione di streaming video live HTML5.


10
Questo è un decodificatore video MPEG-1. Non sono sicuro che tu capisca quanto sia antico MPEG-1; è più vecchio dei DVD. È leggermente più avanzato di un file GIF.
Camilo Martin,

13

Ho scritto un lettore video HTML5 attorno al codec h264 di Broadway (emscripten) in grado di riprodurre video h264 live (senza ritardi) su tutti i browser (desktop, iOS, ...).

Il flusso video viene inviato tramite websocket al client, frame decodificato per frame e visualizzato in un canva (usando webgl per l'accelerazione)

Dai un'occhiata a https://github.com/131/h264-live-player su github.


1
github.com/Streamedian/html5_rtsp_player Questi ragazzi hanno fatto qualcosa di simile che usa rtp h264 su websocket
Victor.dMdB

12

Un modo per trasmettere in streaming live una webcam basata su RTSP su un client HTML5 (comporta la ricodifica, quindi aspettati una perdita di qualità e richiede un po 'di potenza della CPU):

  • Configurare un server icecast (potrebbe trovarsi sullo stesso computer su cui si trova il server Web o sul computer che riceve lo stream RTSP dalla cam)
  • Sulla macchina che riceve lo stream dalla telecamera, non utilizzare FFMPEG ma gstreamer. È in grado di ricevere e decodificare il flusso RTSP, ricodificarlo e trasmetterlo al server icecast. Esempio di pipeline (solo video, no 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

=> È quindi possibile utilizzare il tag <video> con l'URL dello stream icecast ( http://127.0.0.1:12000/cam.webm ) e funzionerà in tutti i browser e dispositivi che supportano webm


3

Dai un'occhiata a questa soluzione . Come so, Flashphoner consente di riprodurre streaming audio + video in diretta nella pagina HTML5 pura.

Usano i codec MPEG1 e G.711 per la riproduzione. L'hacking sta trasformando il video decodificato nell'elemento canvas HTML5 e riproducendo l'audio decodificato tramite il contesto audio HTML5.



2

Questo è un malinteso molto comune. Non è disponibile alcun supporto video HTML5 live (ad eccezione di HLS su iOS e Mac Safari). Potresti essere in grado di "hackerarlo" utilizzando un contenitore webm, ma non mi aspetto che sia universalmente supportato. Quello che stai cercando è incluso nelle estensioni dell'origine multimediale, dove puoi alimentare i frammenti nel browser uno alla volta. ma dovrai scrivere javascript sul lato client.


Ci sono solutionsma non c'è supportper lo streaming live. Questo si riferisce direttamente al mio commento visto sopra. E il webm è supportato sui principali browser, principalmente l'ultima versione stabile.
tsturzl,

1
Preferirei davvero non transcodificare da H.264 a webm e non dovrebbe essere necessario. Inoltre, poiché devo supportare IE11 e Safari, le estensioni di MediaSource non saranno di aiuto. Ma penso che se simulo un flusso di file sul lato server (che funziona!), Allora dovrebbe funzionare, ma dovrò simulare un buffer di file su node.js.
decano del

1
Come altri hanno suggerito, vorrei cercare la possibilità di utilizzare WebRTC che è nativo a differenza di VLC o plugin flash. So che questa tecnologia è ancora difficile da implementare. In bocca al lupo.

1
Ho fatto funzionare tutto questo aggiornando all'ultima versione di FFMPEG in quanto sembra che ci fosse corruzione in mp4 quando si utilizza la modalità frammentata (necessario per lo streaming live MP4 in modo che il client non stia aspettando il file di indice moov che non arriverà mai quando live streaming). E ora funziona il mio codice node.js per reindirizzare il flusso FFMPEG direttamente sul browser.
decano dal

1
Sì, funziona perfettamente su IE11 (il mio browser preferito). Ottengo una risposta nervosa in Chrome.
Deandob,

2

Prova binaryjs. È proprio come socket.io ma l'unica cosa che fa bene è che streaming audio video. Binaryjs lo google


1
Binary.JS non assomiglia a Socket.IO. E non è specifico per lo streaming multimediale.
Brad
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.