Den beste metoden for http-strømming i sanntid til HTML5-videoklient

Jeg' sitter virkelig fast og prøver å forstå den beste måten å streame sanntidsutgang fra ffmpeg til en HTML5-klient ved hjelp av node.js, da det er en rekke variabler i spill, og jeg' t har mye erfaring i dette rommet, etter å ha brukt mange timer på å prøve forskjellige kombinasjoner. Mitt brukstilfelle er:

  1. IP-videokamera RTSP H.264-strøm plukkes opp av FFMPEG og remuxes til en mp4-container ved hjelp av følgende FFMPEG-innstillinger i node, utgang til STDOUT. Dette kjøres bare ved den første klienttilkoblingen, slik at delvise innholdsforespørsler ikke prøver å starte FFMPEG på nytt.
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. Jeg bruker nodens http-server til å fange opp STDOUT og strømme den tilbake til klienten ved en klientforespørsel. Når klienten først kobler seg til, spawner jeg FFMPEG-kommandolinjen ovenfor og sender deretter STDOUT-strømmen til HTTP-svaret.
liveFFMPEG.stdout.pipe(resp);

Jeg har også brukt stream-hendelsen til å skrive FFMPEG-dataene til HTTP-svaret, men det gjør ingen forskjell.

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

Jeg bruker følgende HTTP-header (som også brukes og fungerer ved strømming av forhåndsinnspilte filer)

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. Klienten må bruke HTML5-videotagger. Jeg har ingen problemer med å strømme avspilling (ved hjelp av fs.createReadStream med 206 HTTP-delinnhold) til HTML5-klienten av en videofil som tidligere er spilt inn med FFMPEG-kommandolinjen ovenfor (men lagret i en fil i stedet for STDOUT), så jeg vet at FFMPEG-strømmen er korrekt, og jeg kan til og med se videoen live i VLC når jeg kobler til HTTP-nodeserveren. Men å prøve å streame live fra FFMPEG via node HTTP ser ut til å være mye vanskeligere da klienten vil vise en ramme og deretter stoppe. Jeg mistenker at problemet er at jeg ikke konfigurerer HTTP-tilkoblingen slik at den er kompatibel med HTML5-videoklienten. Jeg har prøvd en rekke ting som å bruke HTTP 206 (delvis innhold) og 200-svar, legge dataene i en buffer og deretter strømme uten hell, så jeg må gå tilbake til de første prinsippene for å sikre at jeg konfigurerer dette på riktig måte. Her er min forståelse av hvordan dette skal fungere, vennligst korriger meg hvis jeg tar feil:
  2. FFMPEG bør settes opp til å fragmentere utdataene og bruke en tom moov (FFMPEG frag_keyframe og empty_moov mov flags). Dette betyr at klienten ikke bruker moov-atomet som vanligvis er på slutten av filen, noe som ikke er relevant når du streamer (ingen filslutt), men betyr at det ikke er mulig å søke, noe som er greit for mitt bruksområde.
  3. Selv om jeg bruker MP4-fragmenter og tomme MOOV-atomer, må jeg fortsatt bruke HTTP-delvis innhold, ettersom HTML5-spilleren venter til hele strømmen er lastet ned før avspilling, noe som med en direktesendt strøm aldri tar slutt, så det fungerer ikke.
  4. Jeg forstår ikke hvorfor det ikke fungerer å sende STDOUT-strømmen til HTTP-responsen når jeg strømmer live, men hvis jeg lagrer til en fil, kan jeg enkelt strømme denne filen til HTML5-klienter ved hjelp av lignende kode. Kanskje det er et tidsproblem, siden det tar et sekund for FFMPEG-spawnen å starte, koble til IP-kameraet og sende biter til noden, og node-datahendelsene er også uregelmessige. Bytestrømmen bør imidlertid være nøyaktig den samme som å lagre til en fil, og HTTP bør kunne ta høyde for forsinkelser.
  5. Når jeg sjekker nettverksloggen fra HTTP-klienten når jeg streamer en MP4-fil opprettet av FFMPEG fra kameraet, ser jeg at det er 3 klientforespørsler: En generell GET-forespørsel om videoen, som HTTP-serveren returnerer ca. 40 kB, deretter en delvis innholdsforespørsel med et byteområde for de siste 10 kB av filen, og deretter en endelig forespørsel om bitene i midten som ikke er lastet inn. Kanskje HTML5-klienten, når den mottar det første svaret, ber om den siste delen av filen for å laste inn MP4 MOOV-atomet? Hvis dette er tilfelle, vil det ikke fungere for streaming, ettersom det ikke finnes noen MOOV-fil og ingen slutt på filen.
  6. Når jeg sjekker nettverksloggen når jeg prøver å streame live, får jeg en avbrutt første forespørsel med bare ca. 200 byte mottatt, deretter en ny forespørsel som igjen er avbrutt med 200 byte og en tredje forespørsel som bare er 2K lang. Jeg forstår ikke hvorfor HTML5-klienten vil avbryte forespørselen, ettersom bytestreamen er nøyaktig den samme som jeg kan bruke når jeg streamer fra en innspilt fil. Det ser også ut til at noden ikke sender resten av FFMPEG-strømmen til klienten, men jeg kan se FFMPEG-dataene i .on-hendelsesrutinen, så det kommer til FFMPEG node HTTP-serveren.
  7. Selv om jeg tror at piping av STDOUT-strømmen til HTTP-responsbufferen burde fungere, må jeg bygge en mellomliggende buffer og strøm som gjør at HTTP-forespørsler om delvis innhold fra klienten fungerer som den gjør når den (vellykket) leser en fil? Jeg tror dette er hovedårsaken til problemene mine, men jeg er ikke helt sikker på hvordan jeg best kan sette det opp i Node. Og jeg vet ikke hvordan jeg skal håndtere en klientforespørsel om dataene på slutten av filen, da det ikke er noen filslutt.
  8. Er jeg på feil spor med å prøve å håndtere 206 forespørsler om delvis innhold, og bør dette fungere med normale 200 HTTP-svar? HTTP 200-svar fungerer fint for VLC, så jeg mistenker at HTML5-videoklienten bare vil fungere med delvis innholdsforespørsler? Siden jeg fremdeles lærer meg dette, er det vanskelig å jobbe gjennom de forskjellige lagene i dette problemet (FFMPEG, node, streaming, HTTP, HTML5-video), så alle tips vil bli satt stor pris på. Jeg har brukt timer på å undersøke på dette nettstedet og på nettet, og jeg har ikke kommet over noen som har klart å gjøre sanntids streaming i node, men jeg kan ikke være den første, og jeg tror dette burde kunne fungere (på en eller annen måte!).
Løsning

REDIGER 3: Fra og med IOS 10 støtter HLS fragmenterte mp4-filer. Løsningen nå er å lage fragmenterte mp4-filer med et DASH- og HLS-manifest. Lat som om flash, iOS9 og lavere og IE 10 og lavere ikke eksisterer.

Alt under denne linjen er utdatert. Beholder det her for ettertiden.


EDIT 2: Som folk i kommentarfeltet påpeker, endrer ting seg. Nesten alle nettlesere vil støtte AVC/AAC-kodeker. iOS krever fortsatt HLS. Men via adaptere som hls.js kan du spille av HLS i MSE. Det nye svaret er HLS+hls.js hvis du trenger iOS. eller bare fragmentert MP4 (dvs. DASH) hvis du ikke trenger det.

Det er mange grunner til at video, og spesielt livevideo, er svært vanskelig. (Vær oppmerksom på at det opprinnelige spørsmålet spesifiserte at HTML5-video er et krav, men spørreren opplyste i kommentarfeltet at Flash er mulig. Så umiddelbart er dette spørsmålet misvisende).

Først vil jeg gjenta: DET FINNES INGEN OFFISIELL STØTTE FOR DIREKTESTRØMMING VIA HTML5. Det finnes hacks, men din erfaring kan variere.

EDIT: siden jeg skrev dette svaret har Media Source Extensions modnet, og er nå veldig nær ved å bli et levedyktig alternativ. De støttes i de fleste store nettlesere. IOS fortsetter å holde igjen.

Dernest må du forstå at Video on demand (VOD) og live video er svært forskjellige. Ja, begge deler er video, men problemene er forskjellige, og derfor er formatene forskjellige. Hvis for eksempel klokken på datamaskinen din går 1 % raskere enn den burde, vil du ikke merke det på VOD. Med live-video prøver du å spille av videoen før det skjer. Hvis du vil bli med i en pågående direktesendt videostrøm, trenger du dataene som er nødvendige for å initialisere dekoderen, så de må gjentas i strømmen eller sendes ut av båndet. Med VOD kan du lese begynnelsen av filen og søke til det punktet du ønsker.

Nå går vi litt dypere inn i materien.

Plattformer:

  • iOS
  • PC
  • Mac
  • Android

Kodeker:

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

Vanlige leveringsmetoder for live video i nettlesere:

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

Vanlige leveringsmetoder for VOD i nettlesere:

  • DASH (HTTP-strømming)
  • HLS (HTTP-strømming)
  • flash (RTMP)
  • Flash (HTTP-strømming)
  • MP4 (HTTP-pseudostrømming)
  • Jeg kommer ikke til å snakke om MKV og OOG fordi jeg ikke kjenner dem så godt.

html5 video tag:

  • MP4
  • webm
  • ogg

La oss se på hvilke nettlesere som støtter hvilke formater

Safari:

  • HLS (kun iOS og mac)
  • h.264
  • MP4

Firefox

  • DASH (via MSE, men ikke h.264)
  • h.264 kun via Flash!
  • VP9
  • MP4
  • OGG
  • Webm

IE

  • Flash
  • DASH (kun via MSE IE 11+)
  • h.264
  • MP4

Chrome

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

MP4 kan ikke brukes til live video (OBS: DASH er en overbygning av MP4, så ikke bli forvirret av det). MP4 er delt opp i to deler: moov og mdat. mdat inneholder de rå lyd- og videodataene. Men den er ikke indeksert, så uten moov er den ubrukelig. Moov inneholder en indeks over alle dataene i mdat. Men på grunn av formatet kan den ikke flates ut før tidsstemplene og størrelsen på HVER frame er kjent. Det kan være mulig å konstruere en moov som 'fibrer' rammestørrelsene, men det er veldig sløsing med båndbredde.

Så hvis du vil levere overalt, må vi finne minste fellesnevner. Du vil se at det ikke finnes noen LCD her uten å ty til flash. eksempel:

  • iOS støtter bare h.264-video, og det støtter bare HLS for live.
  • Firefox støtter ikke h.264 i det hele tatt, med mindre du bruker flash.
  • Flash fungerer ikke i iOS

Det nærmeste man kommer en LCD, er å bruke HLS for iOS-brukere og flash for alle andre. Min personlige favoritt er å kode HLS, og deretter bruke flash til å spille av HLS for alle andre. Du kan spille av HLS i flash via JW player 6, (eller skrive din egen HLS til FLV i AS3 som jeg gjorde).

Snart vil den vanligste måten å gjøre dette på være HLS på iOS/Mac og DASH via MSE alle andre steder (dette er hva Netflix vil gjøre snart). Men vi venter fortsatt på at alle skal oppgradere nettleserne sine. Du vil sannsynligvis også trenge en egen DASH/VP9 for Firefox (jeg kjenner til open264; den suger. Den kan ikke gjøre video i hoved- eller høyprofil. Så den er for øyeblikket ubrukelig).

Kommentarer (6)

Takk til alle, spesielt szatmary, da dette er et komplekst spørsmål med mange lag, som alle må fungere før du kan streame live video. For å avklare det opprinnelige spørsmålet mitt og HTML5-videobruk vs flash - min brukssak har en sterk preferanse for HTML5 fordi det er generisk, enkelt å implementere på klienten og fremtiden. Flash kommer langt etter, så la oss holde oss til HTML5 i dette spørsmålet.

Jeg lærte mye gjennom denne øvelsen og er enig i at live streaming er mye vanskeligere enn VOD (som fungerer bra med HTML5-video). Men jeg fikk dette til å fungere tilfredsstillende for mitt brukstilfelle, og løsningen viste seg å være veldig enkel, etter å ha jaktet på mer komplekse alternativer som MSE, flash, forseggjorte bufferordninger i Node. Problemet var at FFMPEG ødela den fragmenterte MP4-filen, og jeg måtte justere FFMPEG-parametrene, og standard node stream pipe-omdirigering over http som jeg opprinnelig brukte, var alt som trengtes.

I MP4 er det et 'fragmenterings' -alternativ som bryter mp4 i mye mindre fragmenter som har sin egen indeks og gjør mp4 live streaming-alternativet levedyktig. Men det er ikke mulig å søke tilbake i strømmen (OK for mitt bruksområde), og senere versjoner av FFMPEG støtter fragmentering.

Legg merke til at timing kan være et problem, og med min løsning har jeg en forsinkelse på mellom 2 og 6 sekunder forårsaket av en kombinasjon av remuxing (FFMPEG må i praksis motta livestrømmen, remuxe den og deretter sende den til node for servering over HTTP). Det er ikke så mye å gjøre med dette, men i Chrome prøver videoen å ta igjen så mye den kan, noe som gjør videoen litt hakkende, men mer aktuell enn i IE11 (min foretrukne klient).

I stedet for å forklare hvordan koden fungerer i dette innlegget, kan du ta en titt på GIST med kommentarer (klientkoden er ikke inkludert, det er en standard HTML5-videotagg med node http-serveradressen). GIST finner du her: https://gist.github.com/deandob/9240090

Jeg har ikke vært i stand til å finne lignende eksempler på dette brukstilfellet, så jeg håper forklaringen og koden ovenfor kan hjelpe andre, spesielt siden jeg har lært så mye fra dette nettstedet og fortsatt anser meg selv som nybegynner!

Selv om dette er svaret på mitt spesifikke spørsmål, har jeg valgt szatmary' s svar som det aksepterte, da det er det mest omfattende.

Kommentarer (25)

Dette er en svært vanlig misforståelse. Det finnes ingen støtte for live HTML5-video (bortsett fra HLS på iOS og Mac Safari). Du kan kanskje 'hacke' det ved hjelp av en webm-container, men jeg forventer ikke at det støttes av alle. Det du er ute etter, er inkludert i Media Source Extensions, der du kan mate fragmentene til nettleseren ett om gangen. men du må skrive litt javascript på klientsiden.

Kommentarer (15)