[解決済み】HTML5ビデオクライアントへのリアルタイムhttpストリーミングに最適なアプローチ【非公開
質問
node.jsを使用してffmpegのリアルタイム出力をHTML5クライアントにストリーミングする最良の方法を理解しようとして、本当に行き詰っています。
私のユースケースは
1) IP ビデオカメラ RTSP H.264 ストリームを FFMPEG でピックアップし、ノードで以下の FFMPEG 設定を使用して MP4 コンテナにリマックスし、STDOUT に出力します。これは、最初のクライアント接続時にのみ実行され、部分的なコンテンツ要求が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) ノードのhttpサーバーを使ってSTDOUTをキャプチャし、クライアントのリクエストに応じてそれをクライアントにストリームバックします。クライアントが最初に接続したとき、私は上記のFFMPEGコマンドラインを生成し、HTTPレスポンスにSTDOUTストリームをパイプします。
liveFFMPEG.stdout.pipe(resp);
また、stream イベントを使用して、FFMPEG データを HTTP レスポンスに書き込みましたが、違いはありません。
xliveFFMPEG.stdout.on("data",function(data) {
resp.write(data);
}
以下のようなHTTPヘッダを使用しています(録画済みファイルのストリーミング再生時にも使用され、動作しています)。
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) クライアントがHTML5のvideoタグを使用すること。
上記のFFMPEGコマンドラインで事前に録画したビデオファイル(ただしSTDOUTではなくファイルに保存)をHTML5クライアントにストリーミング再生(206 HTTP partial contentでfs.createReadStreamを使用)しても問題がないので、FFMPEGストリームが正しいことが分かっており、HTTPノードサーバーに接続するとVLCでビデオのライブストリーミングも正しく見ることができるのです。
しかし、FFMPEGからノードHTTP経由でライブストリーミングしようとすると、クライアントが1フレームを表示して停止してしまうため、かなり困難なようです。HTML5ビデオクライアントと互換性のあるHTTP接続を設定していないことが問題だと思われます。HTTP 206 (partial content) や 200 レスポンスを使用したり、データをバッファに入れてからストリーミングしたりと、さまざまなことを試しましたが、うまくいきませんでした。
以下は私の理解ですが、間違っていたらご指摘ください。
1) FFMPEGは、出力を断片化し、空のmoovを使用するように設定する必要があります(FFMPEG frag_keyframe and empty_moov mov flags)。これは、クライアントが、通常ファイルの終わりにあるmoovアトムを使用しないことを意味し、ストリーミング時には関係ありませんが(ファイルの終わりがない)、私のユースケースには問題ないシークができないことを意味します。
2) MP4フラグメントと空のMOOVを使用しても、HTML5プレーヤーはストリーム全体がダウンロードされるまで待ってから再生するので、HTTPパーシャルコンテンツを使用する必要があり、ライブストリームでは決して終わらないので実行不可能です。
3) HTTPレスポンスにSTDOUTストリームをパイピングすると、ライブストリーミング時に動作しないのに、ファイルに保存すると、同様のコードを使用してHTML5クライアントに簡単にこのファイルをストリーミングできる理由がわかりません。FFMPEGの起動、IPカメラとの接続、ノードへのチャンクの送信に1秒程度かかっており、ノードのデータイベントも不規則なので、タイミングの問題なのかもしれません。しかし、バイトストリームはファイルへの保存と全く同じであるべきで、HTTPは遅延に対応することができるはずです。
4) FFMPEGで作成したMP4ファイルをカメラからストリーミングする際、HTTPクライアントからネットワークログを確認すると、3つのクライアントリクエストがあることがわかります。映像の一般的なGETリクエストで、HTTPサーバーは約40Kbを返し、次にファイルの最後の10Kbのバイトレンジでコンテンツの部分リクエスト、最後にロードされていない途中のビットのリクエストです。HTML5クライアントは、最初の応答を受け取ると、MP4 MOOVアトムを読み込むために、ファイルの最後の部分を要求しているのかもしれませんね。もしそうであれば、MOOVファイルもファイルの終わりもないので、ストリーミングには使えません。
5) ライブストリーミングをしようとしたときにネットワークログをチェックすると、最初のリクエストが約200バイトしか受信できずに中止され、次に再リクエストが200バイトで中止され、3番目のリクエストは2K長さしかない。バイトストリームは、記録されたファイルからストリーミングするときに正常に使用できるものとまったく同じなので、HTML5クライアントがリクエストを中断する理由が分かりません。また、nodeはFFMPEGストリームの残りをクライアントに送信していないようですが、.onイベントルーチンでFFMPEGデータを見ることができるので、FFMPEG node HTTPサーバーに届いているのでしょう。
6) STDOUTストリームをHTTPレスポンスバッファにパイプするのはうまくいくと思いますが、HTTPパーシャルコンテンツクライアント要求が(うまく)ファイルを読み込むときのように適切に動作するように、中間バッファとストリームを構築しなければならないのでしょうか。私はこれが私の問題の主な理由だと思いますが、私はNodeでどのようにそれを設定するのが最善かよく分かっていません。そして、ファイルの終わりがないので、ファイルの終わりのデータに対するクライアントリクエストをどのように処理したらよいのかわかりません。
7) 206の部分的なコンテンツ要求を処理しようとするのは間違った方向でしょうか、これは通常の200 HTTPレスポンスで動作すべきでしょうか。HTTP 200 レスポンスは VLC では正常に機能するので、HTML5 ビデオ クライアントは部分的なコンテンツ リクエストでのみ機能するのではないでしょうか?
私はまだこのようなことを学んでいるので、この問題の様々なレイヤー(FFMPEG、ノード、ストリーミング、HTTP、HTML5ビデオ)を通して作業するのは難しいので、どんな指摘でも大いに感謝されます。このサイトやネットで何時間も研究しましたが、nodeでリアルタイムストリーミングを行える人に出会ったことはありません。
どのように解決するのですか?
<ブロッククオートEDIT 3: IOS 10では、HLSはフラグメント化されたMP4ファイルをサポートする予定です。そのため Flash、iOS9以下、IE10以下は存在しないことにしてください。
この行より下はすべて古いものです。後世に残すためにここに残しておきます。
<ブロッククオート
EDIT 2: コメントで指摘されているように、状況は変化します。 ほぼすべてのブラウザがAVC/AACコーデックをサポートすることになるでしょう。 iOSはまだHLSを必要とします。しかし、hls.jsのようなアダプターを使用すると、HLSを再生することができます。 MSEでHLS。新しい答えは、iOSが必要な場合はHLS+hls.jsです。 フラグメント化されたMP4(つまりDASH)でない場合
動画、特にライブ動画が非常に難しい理由はたくさんあります。(なお、元の質問ではHTML5動画が要件と明記されていますが、質問者はコメントでFlashも可能だと述べています。そのため、直ちにこの質問は誤解を招きます)
まず言い直します。 html5でのライブストリーミングは公式にはサポートされていません。 . ハックすることはできますが、あなたのマイレージは異なるかもしれません。
<ブロッククオートEDIT: この回答を書いてから、Media Source Extensionsは成熟してきました。 となり、実行可能なオプションに非常に近づいています。サポートされています。 ほとんどの主要なブラウザで使用できます。IOSはまだ保留中です。
次に、VOD(ビデオ・オン・デマンド)とライブ映像は全く異なるものであることを理解する必要があります。確かにどちらもビデオですが、問題が違うので、フォーマットも違います。例えば、パソコンの時計が1%速くなったとしても、VODでは気づきません。ライブ映像の場合は、その前に映像を再生しようとすることになります。進行中のライブ映像に参加する場合、デコーダーを初期化するためのデータが必要なので、ストリーム内で繰り返すか、帯域外に送信する必要があります。VODでは、ファイルの先頭を読み込んで、好きなところまでシークすることができます。
では、少し掘り下げてみましょう。
プラットフォーム
- iOS
- PC
- Mac
- アンドロイド
コーデック
- vp8/9
- h.264
- ソラ(vp3)
ブラウザでのライブ映像の一般的な配信方法。
- DASH (HTTP)
- HLS(HTTP)
- フラッシュ (RTMP)
- フラッシュ (HDS)
ブラウザでのVODの一般的な配信方法。
- DASH (HTTPストリーミング)
- HLS (HTTPストリーミング)
- フラッシュ (RTMP)
- フラッシュ (HTTPストリーミング)
- MP4 (HTTP疑似ストリーミング)
- MKVとOOGについては、よく知らないので割愛します。
html5のビデオタグです。
- MP4
- ウェブム
- オグ
どのブラウザがどのようなフォーマットをサポートしているかを見てみましょう。
サファリ
- HLS(iOS、macのみ)
- h.264
- MP4
ファイアフォックス
- DASH (MSE経由、ただしh.264は不可)
- h.264はFlash経由のみ
- VP9
- MP4
- OGG
- ウェブム
IE
- フラッシュ
- DASH (MSE IE 11+経由のみ)
- h.264
- MP4
クローム
- フラッシュ
- DASH (MSE経由)
- h.264
- VP9
- MP4
- ウェブム
- オグ
MP4はライブ映像には使えません(注:DASHはMP4のスーパーセットなので、混同しないでください)。MP4はmoovとmdatという2つの部分に分かれています。mdatには生のオーディオ・ビデオデータが含まれています。しかし、インデックス化されていないので、moovがなければ意味がない。moovには、mdatに含まれる全データのインデックスが含まれています。しかし、そのフォーマット上、EVERY FRAMEのタイムスタンプとサイズがわかるまで、「フラット化」することができない。フレームサイズを「ファイバー化」したmoovを作成することは可能かもしれませんが、帯域幅の点で非常に無駄があります。
ですから、どこでも配信したいのであれば、最小公倍数を見つける必要があるのです。フラッシュに頼らずとも、液晶がないことはおわかりいただけると思います。 の例です。
- iOSはh.264ビデオしかサポートしていません。また、ライブではHLSしかサポートしていません。
- Firefoxは、フラッシュを使用しない限り、h.264を全くサポートしていません。
- iOSではFlashが動作しません
LCDに最も近いのは、iOSユーザーを獲得するためにHLSを使用し、それ以外の人にはフラッシュを使用することです。 私の個人的なお気に入りは、HLSをエンコードし、他の人たちにはフラッシュを使ってHLSを再生することです。JW player 6 を使ってフラッシュで HLS を再生することもできますし、私のように AS3 で HLS を FLV に書き込むこともできます。
まもなく、iOS/MacではHLS、それ以外の場所ではMSE経由のDASHという方法が一般的になるでしょう(Netflixもまもなくこれを採用する予定です)。しかし、まだみんながブラウザをアップグレードするのを待っているところです。また、Firefox 用の DASH/VP9 が別途必要になる可能性があります (open264 のことは知っています。最低です。メインやハイプロファイルの動画ができません。ですから、現状では役に立ちません)。
関連
-
[解決済み】Markdownで'target="_blank"'でリンクを作成することはできますか?
-
[解決済み】Telegramマークダウンの構文。太字 *と* イタリック?(2018年9月)
-
js プログラミング共通のエラーです。Uncaught TypeError。XXXは関数ソリューションではありません
-
[解決済み] CSS "margin: 0 auto" が中央揃えにならない
-
[解決済み] カーソルを指ポインタに変更
-
[解決済み] ChromeのデベロッパーツールでHTML Bodyにcz-shortcut-listen="true "が表示される?
-
[解決済み] HTMLファイルへのリンクは可能ですか?
-
[解決済み] HTML Divのボーダーが表示されない
-
[解決済み] HTML CSS インビジブルボタン
-
[解決済み] Font Awesome アイコンをプレースホルダーで使用する
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] CSS "margin: 0 auto" が中央揃えにならない
-
[解決済み] フレックスアイテムを右にフロートさせる
-
[解決済み] bodyタグに追加された奇妙なiframe、`rufous-sandbox`について
-
[解決済み] 以下のHTMLフォームの例で、「mailto:[email protected]」とは何ですか?
-
[解決済み] 送信ボタンが機能しない
-
[解決済み] Bootstrapのカラム内で画像を中央に配置する方法 [重複]について
-
[解決済み] divが重ならないようにするには?
-
[解決済み] デフォルトで空白を選択
-
[解決済み] Railsアプリケーションにアイコンを追加する
-
[解決済み] アトムでキーバインドリゾルバーのポップアップを消すにはどうしたらいいですか?