HTML5 video istemcisine gerçek zamanlı http akışı için en iyi yaklaşım

ffmpeg'in gerçek zamanlı çıktısını node.js kullanarak bir HTML5 istemcisine aktarmanın en iyi yolunu anlamaya çalışırken gerçekten takıldım, çünkü oyunda çok sayıda değişken var ve bu alanda çok fazla deneyimim yok, farklı kombinasyonları denemek için saatler harcadım. Benim kullanım durumum:

  1. IP video kamera RTSP H.264 akışı FFMPEG tarafından alınır ve node'da aşağıdaki FFMPEG ayarları kullanılarak bir mp4 kabına remuxlanır, STDOUT'a çıktı verilir. Bu yalnızca ilk istemci bağlantısında çalıştırılır, böylece kısmi içerik istekleri FFMPEG'i tekrar oluşturmaya çalışmaz.
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. STDOUT'u yakalamak ve bir istemci isteği üzerine bunu istemciye geri aktarmak için node http sunucusunu kullanıyorum. İstemci ilk bağlandığında yukarıdaki FFMPEG komut satırını oluşturuyorum ve ardından STDOUT akışını HTTP yanıtına aktarıyorum.
liveFFMPEG.stdout.pipe(resp);

FFMPEG verilerini HTTP yanıtına yazmak için stream olayını da kullandım ancak bir fark yaratmadı

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

Aşağıdaki HTTP başlığını kullanıyorum (önceden kaydedilmiş dosyaları yayınlarken de kullanılır ve çalışır)

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. İstemci HTML5 video etiketlerini kullanmalıdır. Daha önce yukarıdaki FFMPEG komut satırıyla kaydedilmiş (ancak STDOUT yerine bir dosyaya kaydedilmiş) bir video dosyasını HTML5 istemcisine oynatırken (206 HTTP kısmi içeriğiyle fs.createReadStream kullanarak) hiçbir sorun yaşamıyorum, bu nedenle FFMPEG akışının doğru olduğunu biliyorum ve hatta HTTP düğüm sunucusuna bağlanırken videoyu VLC'de canlı olarak doğru şekilde görebiliyorum. Ancak FFMPEG'den HTTP düğümü aracılığıyla canlı yayın yapmaya çalışmak, istemci bir kare gösterip duracağından çok daha zor görünüyor. Sorunun HTTP bağlantısını HTML5 video istemcisi ile uyumlu olacak şekilde ayarlamamamdan kaynaklandığından şüpheleniyorum. HTTP 206 (kısmi içerik) ve 200 yanıtlarını kullanmak, verileri bir tampona koymak ve ardından akış yapmak gibi çeşitli şeyler denedim, bu yüzden bunu doğru şekilde ayarladığımdan emin olmak için ilk ilkelere geri dönmem gerekiyor. İşte bunun nasıl çalışması gerektiğine dair anlayışım, eğer yanılıyorsam lütfen beni düzeltin:
  2. FFMPEG çıktıyı parçalayacak ve boş bir moov kullanacak şekilde ayarlanmalıdır (FFMPEG frag_keyframe ve empty_moov mov bayrakları). Bu, istemcinin genellikle dosyanın sonunda bulunan moov atomunu kullanmadığı anlamına gelir, bu da akış sırasında alakalı değildir (dosyanın sonu yoktur), ancak kullanım durumum için iyi olan arama yapılamayacağı anlamına gelir.
  3. MP4 parçaları ve boş MOOV kullanmama rağmen, HTML5 oynatıcı oynatmadan önce tüm akış indirilene kadar bekleyeceğinden, HTTP kısmi içeriğini kullanmak zorundayım, bu da canlı bir akışla asla bitmez, bu yüzden çalışamaz.
  4. STDOUT akışını HTTP yanıtına aktarmanın canlı akış sırasında neden işe yaramadığını anlamıyorum, ancak bir dosyaya kaydedersem bu dosyayı benzer kod kullanarak HTML5 istemcilerine kolayca aktarabilirim. Belki de FFMPEG'in başlaması, IP kameraya bağlanması ve parçaları düğüme göndermesi bir saniye sürdüğü ve düğüm veri olayları da düzensiz olduğu için bir zamanlama sorunu olabilir. Ancak bytestream bir dosyaya kaydetmekle tamamen aynı olmalı ve HTTP gecikmeleri karşılayabilmelidir.
  5. Kameradan FFMPEG tarafından oluşturulan bir MP4 dosyasını yayınlarken HTTP istemcisinden ağ günlüğünü kontrol ederken, 3 istemci isteği olduğunu görüyorum: HTTP sunucusunun yaklaşık 40Kb döndürdüğü video için genel bir GET isteği, ardından dosyanın son 10K'sı için bir bayt aralığı içeren kısmi bir içerik isteği, ardından ortadaki yüklenmemiş bitler için son bir istek. Belki de HTML5 istemcisi ilk yanıtı aldıktan sonra MP4 MOOV atomunu yüklemek için dosyanın son kısmını istiyordur? Eğer durum buysa, MOOV dosyası ve dosyanın sonu olmadığı için akış için çalışmayacaktır.
  6. Canlı yayın yapmaya çalışırken ağ günlüğünü kontrol ederken, yalnızca yaklaşık 200 bayt alınan iptal edilmiş bir ilk istek, ardından yine 200 bayt ile iptal edilmiş bir yeniden istek ve yalnızca 2K uzunluğunda üçüncü bir istek alıyorum. Kaydedilmiş bir dosyadan yayın yaparken başarıyla kullanabildiğim bytestream ile tamamen aynı olduğu için HTML5 istemcisinin isteği neden iptal ettiğini anlamıyorum. Ayrıca node, FFMPEG akışının geri kalanını istemciye göndermiyor gibi görünüyor, ancak FFMPEG verilerini .on olay rutininde görebiliyorum, bu yüzden FFMPEG node HTTP sunucusuna ulaşıyor.
  7. STDOUT akışını HTTP yanıt arabelleğine bağlamanın işe yaraması gerektiğini düşünmeme rağmen, HTTP kısmi içerik istemci isteklerinin bir dosyayı (başarıyla) okuduğunda olduğu gibi düzgün çalışmasına izin verecek bir ara arabellek ve akış oluşturmam gerekiyor mu? Sanırım sorunlarımın ana nedeni bu, ancak Node'da bunu en iyi şekilde nasıl ayarlayacağımdan tam olarak emin değilim. Ve dosyanın sonu olmadığı için dosyanın sonundaki veriler için bir istemci isteğini nasıl ele alacağımı bilmiyorum.
  8. 206 kısmi içerik isteğini ele almaya çalışırken yanlış yolda mıyım ve bu normal 200 HTTP yanıtlarıyla çalışmalı mı? HTTP 200 yanıtları VLC için iyi çalışıyor, bu yüzden HTML5 video istemcisinin yalnızca kısmi içerik istekleriyle çalışacağından şüpheleniyorum? Bu işi hala öğrenmekte olduğum için bu sorunun çeşitli katmanlarında (FFMPEG, düğüm, akış, HTTP, HTML5 video) çalışmak zor, bu nedenle herhangi bir işaretçi çok takdir edilecektir. Bu sitede ve internette saatlerce araştırma yaptım ve node'da gerçek zamanlı akış yapabilen kimseye rastlamadım, ancak ben ilk olamam ve bunun işe yarayabileceğini düşünüyorum (bir şekilde!).
Çözüm
  1. DÜZENLE: IOS 10'dan itibaren HLS, parçalanmış mp4 dosyalarını destekleyecektir. Cevap şimdi, bir DASH ve HLS bildirimi ile parçalanmış mp4 varlıkları oluşturmaktır. flash, iOS9 ve altı ve IE 10 ve altı yokmuş gibi davranın.

Bu çizginin altındaki her şey güncel değildir. Gelecek nesiller için burada tutuyorum.


DÜZENLEME 2: Yorumlardaki insanların da belirttiği gibi, işler değişiyor. Neredeyse tüm tarayıcılar AVC/AAC codec bileşenlerini destekleyecektir. iOS hala HLS gerektiriyor. Ancak hls.js gibi adaptörler aracılığıyla MSE'de HLS. iOS'a ihtiyacınız varsa yeni cevap HLS+hls.js'dir. veya sadece Parçalanmış MP4 (yani DASH) eğer yapmazsanız't

Videonun ve özellikle de canlı videonun çok zor olmasının birçok nedeni var. (Orijinal soruda HTML5 videonun bir gereklilik olduğunun belirtildiğini, ancak soruyu soranın yorumlarda Flash'ın mümkün olduğunu belirttiğini lütfen unutmayın. Dolayısıyla, bu soru hemen yanıltıcıdır)

Önce tekrar ifade edeyim: HTML5 ÜZERINDEN CANLI YAYIN IÇIN RESMI BIR DESTEK YOKTUR. Hack'ler var, ancak kilometreniz değişebilir.

DÜZENLEME: Bu yanıtı yazdığımdan beri Medya Kaynağı Uzantıları olgunlaştı, ve artık uygulanabilir bir seçenek olmaya çok yakınlar. Destekleniyorlar çoğu büyük tarayıcıda. IOS bir engel olmaya devam ediyor.

Ardından, Talep Üzerine Video (VOD) ve canlı videonun çok farklı olduğunu anlamanız gerekir. Evet, her ikisi de videodur, ancak sorunlar farklıdır, dolayısıyla formatlar da farklıdır. Örneğin, bilgisayarınızdaki saat olması gerekenden %1 daha hızlı çalışıyorsa, VOD'da bunu fark etmezsiniz. Canlı videoda, videoyu gerçekleşmeden önce oynatmaya çalışacaksınız. Devam eden bir canlı video akışına katılmak istiyorsanız, kod çözücüyü başlatmak için gerekli verilere ihtiyacınız vardır, bu nedenle akış içinde tekrarlanmalı veya bant dışına gönderilmelidir. VOD ile, dosyanın başlangıcını okuyabilir ve istediğiniz noktaya kadar arayabilirsiniz.

Şimdi biraz daha derine inelim.

Platformlar:

  • iOS
  • PC
  • Mac
  • Android

Codec'ler:

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

Tarayıcılarda canlı video için yaygın Teslim yöntemleri:

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

Tarayıcılarda VOD için yaygın Teslimat yöntemleri:

  • DASH (HTTP Akışı)
  • HLS (HTTP Akışı)
  • flash (RTMP)
  • flash (HTTP Akışı)
  • MP4 (HTTP sözde akış)
  • MKV ve OOG hakkında konuşmayacağım çünkü onları çok iyi tanımıyorum.

html5 video etiketi:

  • MP4
  • webm
  • ogg

Hangi tarayıcıların hangi formatları desteklediğine bakalım

Safari:

  • HLS (yalnızca iOS ve mac)
  • h.264
  • MP4

Firefox

  • DASH (MSE aracılığıyla ancak h.264 yok)
  • Sadece Flash üzerinden h.264!
  • VP9
  • MP4
  • OGG
  • Webm

IE

  • Flaş
  • DASH (yalnızca MSE IE 11+ aracılığıyla)
  • h.264
  • MP4

Krom

  • Flaş
  • DASH (MSE aracılığıyla)
  • h.264
  • VP9
  • MP4
  • webm
  • ogg

MP4 canlı video için kullanılamaz (NOT: DASH, MP4'ün bir üst kümesidir, bu yüzden bununla karıştırmayın). MP4 iki parçaya ayrılır: moov ve mdat. mdat ham ses video verilerini içerir. Ancak indekslenmez, bu nedenle moov olmadan işe yaramaz. Moov, mdat'taki tüm verilerin bir dizinini içerir. Ancak formatı nedeniyle, HER karenin zaman damgaları ve boyutu bilinene kadar 'düzleştirilemez'. Çerçeve boyutlarını 'fibs' eden bir moov oluşturmak mümkün olabilir, ancak bant genişliği açısından çok savurgandır.

Dolayısıyla, her yere teslimat yapmak istiyorsanız, en az ortak paydayı bulmamız gerekiyor. Burada flaşa başvurmadan LCD olmadığını göreceksiniz Örnek:

  • iOS yalnızca h.264 videoyu destekler. ve yalnızca canlı yayın için HLS'yi destekler.
  • Firefox, flash kullanmadığınız sürece h.264'ü hiç desteklemiyor
  • Flash iOS'ta çalışmıyor

LCD'ye en yakın şey, iOS kullanıcılarınızı almak için HLS kullanmak ve diğer herkes için flaş kullanmaktır. Benim kişisel favorim HLS'yi kodlamak, ardından diğer herkes için HLS'yi oynatmak için flash kullanmaktır. JW player 6 aracılığıyla flash'ta HLS oynatabilirsiniz (veya benim yaptığım gibi AS3'te FLV'ye kendi HLS'nizi yazabilirsiniz)

Yakında, bunu yapmanın en yaygın yolu iOS/Mac'te HLS ve diğer her yerde MSE aracılığıyla DASH olacaktır (Netflix yakında bunu yapacak). Ancak hala herkesin tarayıcılarını yükseltmesini bekliyoruz. Ayrıca muhtemelen Firefox için ayrı bir DASH/VP9'a ihtiyacınız olacak (open264'ü biliyorum; berbat. Ana veya yüksek profilli video yapamıyor. Yani şu anda işe yaramaz).

Yorumlar (6)

Herkese, özellikle de szatmary'ye teşekkürler çünkü bu karmaşık bir soru ve canlı video akışı yapabilmeniz için hepsinin çalışıyor olması gereken birçok katmanı var. Asıl sorumu ve HTML5 video kullanımını flash'a karşı açıklığa kavuşturmak için - kullanım durumum HTML5'i güçlü bir şekilde tercih ediyor çünkü genel, istemcide uygulanması kolay ve gelecek. Flash uzak bir ikinci en iyidir, bu nedenle bu soru için HTML5'e bağlı kalalım.

Bu alıştırma sayesinde çok şey öğrendim ve canlı yayın yapmanın VOD'dan (HTML5 video ile iyi çalışır) çok daha zor olduğuna katılıyorum. Ancak bunu kullanım durumum için tatmin edici bir şekilde çalıştırdım ve MSE, flash, Node'da ayrıntılı arabelleğe alma şemaları gibi daha karmaşık seçenekleri takip ettikten sonra çözümün çok basit olduğu ortaya çıktı. Sorun FFMPEG'in parçalanmış MP4'ü bozmasıydı ve FFMPEG parametrelerini ayarlamam gerekiyordu ve başlangıçta kullandığım http üzerinden standart node akış borusu yönlendirmesi gereken tek şeydi.

MP4'te, mp4'ü kendi dizinine sahip olan ve mp4 canlı akış seçeneğini uygulanabilir kılan çok daha küçük parçalara ayıran bir 'parçalama' seçeneği vardır. Ancak akışa geri dönmek mümkün değil (benim kullanım durumum için sorun yok) ve FFMPEG'in sonraki sürümleri parçalanmayı destekliyor.

Not zamanlama bir sorun olabilir ve benim çözümümde remuxing'in bir kombinasyonundan kaynaklanan 2 ila 6 saniye arasında bir gecikme yaşıyorum (etkili bir şekilde FFMPEG'in canlı akışı alması, remux etmesi ve ardından HTTP üzerinden servis için node'a göndermesi gerekiyor). Bu konuda pek bir şey yapılamaz, ancak Chrome'da video olabildiğince yetişmeye çalışıyor, bu da videoyu biraz titretiyor ancak IE11'den (tercih ettiğim istemci) daha güncel.

Bu yazıda kodun nasıl çalıştığını açıklamak yerine, yorumlarla birlikte GIST'e göz atın (istemci kodu dahil değildir, node http sunucu adresini içeren standart bir HTML5 video etiketidir). GIST burada: https://gist.github.com/deandob/9240090

Bu kullanım durumuna benzer örnekler bulamadım, bu nedenle yukarıdaki açıklama ve kodun başkalarına yardımcı olmasını umuyorum, özellikle de bu siteden çok şey öğrendiğim ve kendimi hala acemi olarak gördüğüm için!

Bu benim özel sorumun cevabı olmasına rağmen, szatmary'nin cevabını en kapsamlı olduğu için kabul edilen cevap olarak seçtim.

Yorumlar (25)

Bu çok yaygın bir yanılgıdır. Canlı HTML5 video desteği yoktur (iOS ve Mac Safari'deki HLS hariç). Bir webm konteyneri kullanarak bunu 'hackleyebilirsiniz' ancak bunun evrensel olarak desteklenmesini beklemiyorum. Aradığınız şey, parçaları tarayıcıya teker teker besleyebileceğiniz Medya Kaynağı Uzantılarına dahil edilmiştir. ancak bazı istemci tarafı javascript yazmanız gerekecektir.

Yorumlar (15)