Naprawdę utknąłem, próbując zrozumieć najlepszy sposób przesyłania strumieniowego wyniku ffmpeg w czasie rzeczywistym do klienta HTML5 za pomocą node.js, ponieważ istnieje wiele zmiennych i nie mam dużego doświadczenia w tym obszarze, spędziłem wiele godzin próbując różnych kombinacji.
Mój przypadek użycia to:
1) Strumień wideo kamery RTSP H.264 IP jest pobierany przez FFMPEG i ponownie umieszczany w kontenerze mp4 przy użyciu następujących ustawień FFMPEG w węźle, wysyłany do STDOUT. Jest to uruchamiane tylko przy początkowym połączeniu klienta, aby częściowe żądania treści nie próbowały ponownie odrodzić 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) Używam węzłowego serwera HTTP do przechwytywania STDOUT i przesyłania strumieniowego z powrotem do klienta na żądanie klienta. Kiedy klient po raz pierwszy się łączy, spawnuję powyższy wiersz poleceń FFMPEG, a następnie przesyłam potokiem strumień STDOUT do odpowiedzi HTTP.
liveFFMPEG.stdout.pipe(resp);
Użyłem również zdarzenia stream do zapisania danych FFMPEG do odpowiedzi HTTP, ale nie robi to różnicy
xliveFFMPEG.stdout.on("data",function(data) {
resp.write(data);
}
Używam następującego nagłówka HTTP (który jest również używany i działa podczas przesyłania strumieniowego wcześniej 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żywać tagów wideo HTML5.
Nie mam problemów z odtwarzaniem strumieniowym (przy użyciu fs.createReadStream z 206 częściową zawartością HTTP) do klienta HTML5 plik wideo wcześniej nagrany przy użyciu powyższego wiersza polecenia FFMPEG (ale zapisany w pliku zamiast STDOUT), więc znam strumień FFMPEG jest poprawny i nawet poprawnie widzę transmisję wideo na żywo w VLC podczas łączenia się z serwerem węzła HTTP.
Jednak próba przesyłania strumieniowego na żywo z FFMPEG przez węzeł HTTP wydaje się dużo trudniejsza, ponieważ klient wyświetli jedną ramkę, a następnie zatrzyma się. Podejrzewam, że problem polega na tym, że nie konfiguruję połączenia HTTP, aby było zgodne z klientem wideo HTML5. Próbowałem różnych rzeczy, takich jak użycie HTTP 206 (częściowa treść) i 200 odpowiedzi, umieszczanie danych w buforze, a następnie przesyłanie strumieniowe bez powodzenia, więc muszę wrócić do pierwszych zasad, aby upewnić się, że skonfigurowałem to poprawnie sposób.
Oto moje rozumienie tego, jak to powinno działać, proszę mnie poprawić, jeśli się mylę:
1) FFMPEG powinien zostać skonfigurowany w celu fragmentacji danych wyjściowych i użycia pustego moov (flagi FFMPEG frag_keyframe i empty_moov mov flagi). Oznacza to, że klient nie używa atomu moov, który zwykle znajduje się na końcu pliku, co nie jest istotne podczas przesyłania strumieniowego (bez końca pliku), ale oznacza, że nie można szukać, co jest dobre w moim przypadku użycia.
2) Mimo że używam fragmentów MP4 i pustego MOOV, nadal muszę korzystać z częściowej zawartości HTTP, ponieważ odtwarzacz HTML5 będzie czekał na pobranie całego strumienia przed rozpoczęciem odtwarzania, co w przypadku transmisji na żywo nigdy się nie kończy, więc jest to niemożliwe.
3) Nie rozumiem, dlaczego przesyłanie strumieniowe strumienia STDOUT do odpowiedzi HTTP nie działa jeszcze podczas przesyłania strumieniowego na żywo, jeśli zapiszę do pliku, mogę łatwo przesyłać strumieniowo ten plik do klientów HTML5 przy użyciu podobnego kodu. Być może jest to kwestia synchronizacji, ponieważ uruchomienie FFMPEG zajmuje sekundę, połączenie z kamerą IP i wysyłanie fragmentów do węzła, a zdarzenia danych węzła również są nieregularne. Jednak bajtowanie powinno być dokładnie takie samo jak zapisywanie w pliku, a HTTP powinien być w stanie zaspokoić opóźnienia.
4) Podczas sprawdzania dziennika sieciowego z klienta HTTP podczas przesyłania strumieniowego pliku MP4 utworzonego przez FFMPEG z kamery widzę, że istnieją 3 żądania klienta: Ogólne żądanie GET dla wideo, które serwer HTTP zwraca około 40 KB, a następnie częściowe żądanie zawartości z zakresem bajtów dla ostatnich 10 KB pliku, a następnie końcowe żądanie dla bitów w środku nie załadowane. Może klient HTML5 po otrzymaniu pierwszej odpowiedzi prosi o załadowanie atomu MP4 MOOV ostatniej części pliku? W takim przypadku nie będzie działać w przypadku przesyłania strumieniowego, ponieważ nie ma pliku MOOV i nie ma końca pliku.
5) Podczas sprawdzania dziennika sieciowego podczas próby przesyłania strumieniowego na żywo otrzymuję przerwane początkowe żądanie z odebranymi około 200 bajtami, a następnie ponowne żądanie ponownie przerwane z 200 bajtami i trzecim żądaniem, które ma tylko 2 KB długości. Nie rozumiem, dlaczego klient HTML5 przerwałby żądanie, ponieważ strumień bajtów jest dokładnie taki sam, jak mogę z powodzeniem używać podczas przesyłania strumieniowego z nagranego pliku. Wygląda również na to, że węzeł nie wysyła pozostałej części strumienia FFMPEG do klienta, ale widzę dane FFMPEG w procedurze zdarzeń .on, więc dociera ono do serwera HTTP węzła FFMPEG.
6) Chociaż myślę, że potokowanie strumienia STDOUT do bufora odpowiedzi HTTP powinno działać, czy muszę zbudować bufor pośredni i strumień, który pozwoli, aby żądania klienta częściowej treści HTTP działały poprawnie tak jak wtedy, gdy (z powodzeniem) odczyta plik ? Myślę, że to jest główny powód moich problemów, ale nie jestem do końca pewien w Node, jak najlepiej to skonfigurować. I nie wiem, jak obsłużyć żądanie klienta dotyczące danych na końcu pliku, ponieważ nie ma końca pliku.
7) Czy jestem na niewłaściwej ścieżce, próbując obsłużyć 206 częściowych żądań treści i czy powinno to działać z normalnymi 200 odpowiedziami HTTP? Odpowiedzi HTTP 200 działają 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ę tego, trudno jest przejść przez różne warstwy tego problemu (FFMPEG, węzeł, streaming, HTTP, HTML5 wideo), więc wszelkie wskazówki będą mile widziane. Spędziłem godziny badając tę stronę i sieć, i nie spotkałem nikogo, kto byłby w stanie transmitować w czasie rzeczywistym w węźle, ale nie mogę być pierwszy i myślę, że to powinno zadziałać (jakoś !).
Content-Type
sobie głowę? Czy używasz kodowania fragmentów? Od tego bym zaczął. Ponadto HTML5 nie musi zapewniać funkcji przesyłania strumieniowego, więcej informacji na ten temat można znaleźć tutaj . Najprawdopodobniej będziesz musiał zaimplementować sposób buforowania i odtwarzania strumienia wideo przy użyciu własnych środków ( patrz tutaj ), ponieważ prawdopodobnie nie jest to dobrze obsługiwane. Również Google w API MediaSource.