Najlepsze podejście do przesyłania strumieniowego http w czasie rzeczywistym do klienta wideo HTML5

Naprawdę utknąłem próbując zrozumieć najlepszy sposób na strumieniowe przesyłanie w czasie rzeczywistym danych wyjściowych ffmpeg do klienta HTML5 za pomocą węzła.js, ponieważ w grze jest wiele zmiennych i nie mam dużego doświadczenia w tej przestrzeni, spędzając wiele godzin na próbowaniu różnych kombinacji.

Mój przypadek użycia to:

1) Kamera Wideo IP RTSP H. 264 strumień jest pobierany przez FFMPEG i remuxed do kontenera mp4 przy użyciu następujących ustawień FFMPEG w węźle, wyjście na STDOUT. To jest uruchamiane tylko na początkowe połączenie z klientem, aby częściowe żądania zawartości nie próbowały ponownie wywołać FFMPEG.

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) używam serwera HTTP węzła do przechwytywania STDOUT i przesyłania strumieniowego z powrotem do klienta na żądanie klienta. Gdy klient po raz pierwszy się połączy, spawnuję powyższy wiersz poleceń FFMPEG, a następnie przesyłam strumień STDOUT do odpowiedzi HTTP.

liveFFMPEG.stdout.pipe(resp);

Użyłem również zdarzenia stream do zapisu danych FFMPEG do odpowiedzi HTTP, ale nie robi różnicy

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

Używam po nagłówku HTTP (który jest również używany i działa podczas strumieniowania wstępnie nagranych plików)

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) klient musi użyć znaczników wideo HTML5.

Nie mam problemów z odtwarzaniem strumieniowym (za pomocą fs.createReadStream z zawartością częściową HTTP 206) do klienta HTML5 plik wideo wcześniej nagrany z powyższą linią poleceń FFMPEG (ale zapisany do pliku zamiast STDOUT), więc wiem, że strumień FFMPEG jest poprawny, a nawet mogę poprawnie zobaczyć wideo na żywo w VLC, gdy połączenie z serwerem węzła HTTP.

Jednak próba streamowania na żywo z FFMPEG przez węzeł HTTP wydaje się być o wiele trudniejsza, ponieważ klient wyświetli jedną klatkę, a następnie zatrzyma. Podejrzewam, że problem polega na tym, że nie konfiguruję połączenia HTTP, aby było kompatybilne z klientem wideo HTML5. Próbowałem różnych rzeczy, takich jak używanie HTTP 206 (częściowa zawartość) i odpowiedzi 200, umieszczanie danych w buforze, a następnie przesyłanie strumieniowe bez powodzenia, więc muszę wrócić do pierwszych zasad, aby upewnić się, że jestem ustawiam to we właściwy sposób.

Oto moje zrozumienie, jak to powinno działać, proszę mnie poprawić, jeśli się mylę:

1) FFMPEG powinien być ustawiony tak, aby fragmentował wyjście i używał pustego moov (FFmpeg frag_keyframe i empty_moov MOV flagi). Oznacza to, że Klient nie używa atomu moov, który zazwyczaj znajduje się na końcu pliku, co nie jest istotne podczas przesyłania strumieniowego (bez końca pliku), ale oznacza brak możliwości szukania, co jest dobre dla mojego przypadku użycia.

2) mimo że używam MP4 fragmenty i puste MOOV, nadal muszę korzystać z częściowej zawartości HTTP, ponieważ odtwarzacz HTML5 będzie czekał, aż cały strumień zostanie pobrany przed odtwarzaniem, co przy strumieniu na żywo nigdy się nie kończy, więc jest niewykonalne.

3) nie rozumiem, dlaczego strumieniowanie strumienia STDOUT do odpowiedzi HTTP nie działa podczas streamingu na żywo jeszcze Jeśli zapisuję do pliku mogę łatwo streamować ten plik do klientów HTML5 za pomocą podobnego kodu. Może to problem z wyczuciem czasu, bo uruchomienie FFmpeg spawn zajmuje sekundę, podłącz do kamery IP i wysyłać kawałki do węzła, a zdarzenia danych węzła są również nieregularne. Jednak strumień bajtowy powinien być dokładnie taki sam jak zapis do pliku, a HTTP powinien być w stanie zaspokoić opóźnienia.

4) podczas sprawdzania logu sieciowego z klienta HTTP podczas przesyłania strumieniowego pliku MP4 utworzonego przez FFMPEG z kamery, widzę, że są 3 żądania klienta: ogólne żądanie GET dla wideo, które serwer HTTP zwraca około 40Kb, a następnie żądanie częściowej zawartości z zakresem bajtów dla Ostatnie 10K pliku, a następnie ostateczne żądanie dla bitów w środku nie załadowane. Może Klient HTML5 po otrzymaniu pierwszej odpowiedzi prosi o załadowanie ostatniej części pliku MP4 Moov atom? W takim przypadku nie będzie działać do przesyłania strumieniowego, ponieważ nie ma pliku MOOV i nie ma końca pliku.

5) sprawdzając dziennik sieci podczas próby przesyłania strumieniowego na żywo, otrzymuję przerwane początkowe żądanie z tylko około 200 bajtów odebranych, a następnie ponownie żądanie ponownie przerwane z 200 bajtów i trzecia Prośba, która ma tylko 2K długości. Nie rozumiem, dlaczego klient HTML5 przerwałby żądanie, ponieważ bajt jest dokładnie taki sam, jak mogę z powodzeniem używać podczas przesyłania strumieniowego z nagranego pliku. Wygląda na to, że node nie wysyła reszty strumienia FFmpeg do klienta, ale widzę dane FFMPEG w .na rutynowych zdarzeniach, więc dostaje się do serwera HTTP węzła FFmpeg.

6) chociaż myślę, że Orurowanie strumienia STDOUT do bufora odpowiedzi HTTP powinno działać, czy mam zbudować bufor pośredni i strumień, który pozwoli żądaniom klienta HTTP partial content poprawnie działać tak, jak to robi, gdy (pomyślnie) czyta plik? Myślę, że jest to główny powód moich problemów, jednak nie jestem dokładnie pewien w węźle, jak najlepiej to skonfigurować. I nie wiem, jak poradzić sobie z żądaniem Klienta o dane na końcu pliku, ponieważ nie ma końca pliku.

7) czy jestem na złej drodze, próbując obsłużyć 206 częściowych żądań treści i czy to praca z normalnymi odpowiedziami HTTP 200? Odpowiedzi HTTP 200 działa dobrze dla VLC, więc podejrzewam, że klient wideo HTML5 będzie działał tylko z częściowymi żądaniami treści?

Ponieważ wciąż uczę się tych rzeczy, trudno jest przejść przez różne warstwy tego problemu (FFmpeg, node, streaming, HTTP, HTML5 video), więc wszelkie wskaźniki będą bardzo mile widziane. Spędziłem wiele godzin na badaniach na tej stronie i w sieci, i nie natknąłem się na nikogo, kto był w stanie zrobić strumieniowanie w czasie rzeczywistym w węźle ale nie mogę być pierwszy i myślę, że to powinno się udać (jakoś!).

Author: Universal Electricity, 2014-02-21

9 answers

EDIT 3: od IOS 10 HLS będzie obsługiwał fragmentowane pliki mp4. Odpowiedź teraz jest tworzenie fragmentarycznych zasobów mp4, z myślnikiem i manifestem HLS. > Flash, iOS9 i poniżej oraz IE 10 i poniżej nie istnieją.

Wszystko poniżej tej linii jest nieaktualne. Trzymanie go tutaj dla potomności.

EDIT 2: Jak ludzie w komentarzach wskazują, rzeczy się zmieniają. Prawie wszystkie przeglądarki obsługują kodeki AVC / AAC. iOS nadal wymaga HLS. Ale przez Adaptery jak hls.js możesz grać HLS w MSE. Nowa odpowiedź to HLS+hls.js jeśli potrzebujesz iOS. lub po prostu Fragmented MP4 (tj. DASH) if you don ' t

Istnieje wiele powodów, dla których wideo, a w szczególności wideo na żywo, jest bardzo trudne. (Należy pamiętać, że pierwotne pytanie określiło, że wideo HTML5 jest wymogiem, ale asker stwierdził, że Flash jest możliwy w komentarzach. Więc od razu to pytanie wprowadza w błąd)

Najpierw powtórzę: nie ma oficjalnego wsparcia DO PRZESYŁANIA STRUMIENIOWEGO NA ŻYWO PRZEZ HTML5 . Istnieją hacki, ale przebieg może się różnić.

EDIT: od kiedy napisałem tę odpowiedź rozszerzenia źródeł mediów dojrzały, i są teraz bardzo blisko stania się realną opcją. Są one obsługiwane na większości głównych przeglądarek. IOS nadal działa.

Następnie musisz zrozumieć, że wideo na żądanie (Vod) i wideo na żywo są bardzo różne. Tak, oba są Wideo, ale problemy są różne, stąd formaty są inaczej. Na przykład, jeśli zegar w komputerze działa 1% szybciej niż powinien, nie zauważysz w VOD. W przypadku wideo na żywo będziesz próbował odtworzyć wideo, zanim to nastąpi. Jeśli chcesz dołączyć do trwającego strumienia wideo na żywo, potrzebujesz danych niezbędnych do zainicjowania dekodera, więc musi on zostać powtórzony w strumieniu lub wysłany poza pasmo. Dzięki VOD możesz odczytać początek pliku, którego szukają w dowolnym punkcie.

Teraz kopmy w trochę.

Platformy:

  • iOS
  • PC
  • Mac
  • Android

Kodeki:

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

Popularne metody dostarczania wideo na żywo w przeglądarkach:

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

Popularne metody Dostarczania VOD w przeglądarkach:

  • DASH (Streaming HTTP)
  • HLS (HTTP Streaming)
  • flash (RTMP)
  • flash (Streaming HTTP)
  • MP4 (http pseudo streaming)
  • Nie będę mówił o MKV i OOG, ponieważ nie znam ich zbyt dobrze.

Html5 tag wideo:

  • MP4
  • webm
  • ogg

Zobaczmy, które przeglądarki obsługują jakie formaty

Safari:

  • HLS (iOS i mac tylko)
  • h.264
  • MP4

Firefox

  • DASH (via MSE, ale nie h. 264)
  • h. 264 tylko przez Flash!
  • VP9
  • MP4
  • OGG
  • Webm

IE

  • Flash
  • DASH (tylko przez MSE IE 11+)
  • h.264
  • MP4

Chrome

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

MP4 nie może być używany do wideo na żywo (Uwaga: DASH to superset MP4, więc nie daj się z tym pomylić). MP4 jest podzielone na dwie części: moov i mdat. mdat zawiera surowe dane audio video. Ale nie jest indeksowany, więc bez moov jest bezużyteczny. Moov zawiera indeks wszystkich danych w mdat. Ale ze względu na swój format nie może być "spłaszczony" do czasu znaczników czasu i rozmiaru każdej klatki jest znany. Może być możliwe skonstruowanie moov 'a, który "fibs" rozmiar ramki, ale jest bardzo marnotrawny pod względem przepustowości.

Więc jeśli chcesz dostarczać wszędzie, musimy znaleźć najmniejszy wspólny mianownik. Zobaczysz, że nie ma tu LCD bez uciekania się do lampy błyskowej przykład:
  • iOS obsługuje tylko wideo h. 264. i obsługuje tylko HLS na żywo.
  • Firefox nie obsługuje w ogóle h.264, chyba że używasz Flasha]}
  • Flash nie działa w iOS

Najbliższą rzeczą do LCD jest korzystanie z HLS, aby uzyskać użytkowników iOS, a flash dla wszystkich innych. Moim ulubionym jest kodowanie HLS, a następnie użyj Flasha, aby odtworzyć HLS dla wszystkich innych. Możesz grać w HLS we flashu poprzez JW player 6, (lub napisać własny HLS do FLV w AS3 tak jak ja)

Wkrótce najczęstszym sposobem na to będzie HLS na iOS / Mac i DASH za pośrednictwem MSE wszędzie indziej (to właśnie Netflix będzie robić wkrótce). Ale nadal czekamy na wszystkich, aby uaktualnić swoje przeglądarki. Prawdopodobnie będziesz też potrzebował osobnego DASH/VP9 dla Firefoksa (wiem o open264; jest do bani. Nie może robić wideo w głównym lub wysokim profilu. Więc obecnie jest bezużyteczny).

 201
Author: szatmary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-11 22:46:26

Dziękuję wszystkim szczególnie szatmary, ponieważ jest to skomplikowane pytanie i ma wiele warstw do niego, wszystkie, które muszą działać, zanim można streamować wideo na żywo. Aby wyjaśnić moje oryginalne pytanie i wykorzystanie wideo HTML5 vs flash-mój przypadek użycia ma silne preferencje dla HTML5, ponieważ jest ogólny, łatwy do wdrożenia na kliencie i w przyszłości. Flash jest odległym drugim najlepszym więc pozwala trzymać się HTML5 na to pytanie.

Wiele się nauczyłem dzięki temu ćwiczeniu i zgadzam się, że transmisja na żywo jest dużo trudniejsze niż VOD (który działa dobrze z wideo HTML5). Ale udało mi się to działa zadowalająco w moim przypadku użycia i rozwiązanie okazało się bardzo proste, po pogoni za bardziej złożonymi opcjami, takimi jak MSE, flash, rozbudowane Schematy buforowania w węźle. Problem polegał na tym, że FFmpeg był uszkodzony pofragmentowany MP4 i musiałem dostroić parametry FFMPEG, a standardowe przekierowanie strumienia węzła przez http, którego używałem pierwotnie, było wszystkim, co było potrzebne.

W MP4 jest opcja "fragmentacja", która rozbija mp4 na znacznie mniejsze fragmenty, które mają swój własny indeks i sprawia, że opcja przesyłania strumieniowego na żywo mp4 jest opłacalna. Ale nie można szukać z powrotem do strumienia (OK dla mojego przypadku użycia), a późniejsze wersje FFmpeg wsparcie fragmentacji.

Uwaga timing może być problemem, a z moim rozwiązaniem mam opóźnienie między 2 a 6 sekund spowodowane kombinacją remuxingu (skutecznie FFmpeg musi odbierać live stream, remux to następnie wysłać go do węzła do serwowania przez HTTP). Niewiele można z tym zrobić, jednak w Chrome film próbuje nadrobić tyle, ile może, co sprawia, że film jest nieco skoczny, ale bardziej aktualny niż IE11 (mój preferowany klient).

Zamiast wyjaśniać, jak działa kod w tym poście, sprawdź GIST z komentarzami (kod klienta nie jest dołączony, jest to standardowy tag wideo HTML5 z adresem serwera http węzła). GIST jest tutaj: https://gist.github.com/deandob/9240090

Nie byłem w stanie Znajdź podobne przykłady tego przypadku użycia, więc mam nadzieję, że powyższe wyjaśnienie i Kod pomoże innym, zwłaszcza, że nauczyłem się tak wiele z tej strony i nadal uważam się za początkującego!

Chociaż jest to odpowiedź na moje konkretne pytanie, wybrałem odpowiedź szatmary ' ego jako zaakceptowaną, ponieważ jest najbardziej wyczerpująca.

 72
Author: deandob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-02 10:38:02

Przyjrzyj się projektowi JSMPEG . Zaimplementowano tam świetny pomysł-dekodowanie MPEG w przeglądarce za pomocą JavaScript. Bajty z enkodera (na przykład FFMPEG) można przenieść do przeglądarki za pomocą WebSockets lub Flash, na przykład. Jeśli społeczność dogoni, myślę, że będzie to najlepsze rozwiązanie do przesyłania strumieniowego wideo NA ŻYWO HTML5 na razie.

 12
Author: Michael Romanenko,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-11 20:33:50

Jeden ze sposobów przesyłania strumieniowego na żywo kamery internetowej opartej na RTSP do klienta HTML5 (wymaga ponownego kodowania, więc spodziewaj się utraty jakości i potrzebuje trochę mocy procesora):

  • Skonfiguruj serwer icecast (może być na tej samej maszynie, na której jest serwer WWW lub na maszynie, która odbiera Strumień RTSP z kamery)
  • Na maszynie odbierającej strumień z kamery, nie używaj FFMPEG tylko gstreamer. Jest w stanie odbierać i dekodować Strumień RTSP, ponownie kodować go i przesyłać strumieniowo do serwera Icecast. Przykładowy rurociąg (tylko wideo, bez dźwięku):

    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
    

= > Możesz następnie użyć tagu

 10
Author: Jannis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-02-13 19:45:29

Napisałem odtwarzacz wideo HTML5 wokół broadway kodek h264 (emscripten), który może odtwarzać na żywo (bez opóźnienia) wideo h264 na wszystkich przeglądarkach (pulpit, iOS, ...).

W przeciwieństwie do innych programów, które nie są w pełni kompatybilne z WebGL, nie są w pełni kompatybilne z WebGL.]}

Sprawdź https://github.com/131/h264-live-player na GitHubie.

 9
Author: 131,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-29 22:23:46

Spójrz na To rozwiązanie . Jak wiem, Flashphoner pozwala na odtwarzanie strumienia audio+wideo na żywo w czystej stronie HTML5.

Używają kodeków MPEG1i G. 711 do odtwarzania. Hack jest renderowanie dekodowanego wideo do elementu HTML5 canvas i odtwarzanie dekodowanego dźwięku za pomocą kontekstu audio HTML5.

 3
Author: ankitr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-11 04:55:29

Może użyjesz rozwiązania jpeg, po prostu pozwól serwerowi rozpowszechniać jpeg jeden po drugim do przeglądarki, a następnie użyj elementu canvas do narysowania tych JPEG? http://thejackalofjavascript.com/rpi-live-streaming/

 3
Author: Kiki.J.Hu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-18 07:10:42

Jest to bardzo powszechne błędne przekonanie. Nie ma obsługi wideo HTML5 na żywo (z wyjątkiem HLS na iOS i Mac Safari). Możesz być w stanie "zhakować" go za pomocą kontenera webm, ale nie spodziewałbym się, że będzie to powszechnie obsługiwane. To, czego szukasz, znajduje się w rozszerzeniach Media Source, gdzie możesz pojedynczo przesyłać fragmenty do przeglądarki. ale będziesz musiał napisać trochę javascript po stronie klienta.

 2
Author: szatmary,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-09 07:59:45

Spróbuj binaryj. Its just like socket.io ale jedyne, co robi dobrze, to przesyłanie strumieniowe audio wideo. Binaryjs google it

 2
Author: Siddharth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-21 15:06:17