HTML5ビデオクライアントへのリアルタイムhttpストリーミングへの最適なアプローチ

node.jsを使用してffmpegのリアルタイム出力をHTML5クライアントにストリーミングする最善の方法を理解するのにとても困っています、なぜならば、多くの変数が関係しており、私はこの分野で多くの経験を持っていません。 私のユースケースは

  1. IPビデオカメラのRTSP H.264ストリームをFFMPEGでピックアップし、nodeで以下のFFMPEG設定を使ってmp4コンテナにリミックスし、STDOUTに出力する。これは最初のクライアント接続時にのみ実行され、部分的なコンテンツリクエストが再び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});
  1. STDOUTをキャプチャするためにノードのhttpサーバを使用し、クライアントからのリクエストに応じてそのストリームをクライアントに返します。クライアントが最初に接続したとき、私は上記のFFMPEGコマンドラインを生成し、HTTPレスポンスにSTDOUTストリームをパイプします。
liveFFMPEG.stdout.pipe(resp);

ストリームイベントを使用して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
});
  1. クライアントは、HTML5のvideoタグを使用する必要があります。 上記のFFMPEGコマンドラインで事前に録画したビデオファイル(ただしSTDOUTではなくファイルに保存)をHTML5クライアントにストリーミング再生(fs.createReadStream with 206 HTTP partial content)することに問題はなく、FFMPEGストリームが正しいことはわかりますし、HTTPノードサーバに接続してVLCでビデオのライブストリーミングを見ることもできます。 しかし、ノードHTTP経由でFFMPEGからライブストリーミングを行おうとすると、クライアントが1フレームを表示した後に停止してしまうため、かなり難しいようです。問題は、HTML5ビデオクライアントと互換性のあるHTTP接続を設定していないことにあると思います。HTTP 206 (partial content) や 200 レスポンスを使用したり、データをバッファに入れてからストリーミングするなど、さまざまなことを試してみましたが、うまくいきませんでしたので、第一原理に戻って正しい方法で設定する必要があります。 以下は、私が理解している動作方法ですが、間違っていたらご指摘ください。
  2. FFMPEGは、出力をフラグメント化し、空のmoovを使用するように設定する必要があります(FFMPEG frag_keyframe and empty_moov mov flags)。これは、クライアントがmoovアトムを使用しないことを意味します。moovアトムは通常、ファイルの最後にあり、ストリーミング時には関係ありませんが(ファイルの終わりがない)、シークができないことを意味し、私のユースケースでは問題ありません。
  3. MP4フラグメントと空のMOOVを使用しても、HTTPパーシャルコンテンツを使用しなければなりません。なぜなら、HTML5プレーヤーはストリーム全体がダウンロードされるまで待ってから再生するからです。
  4. ライブ・ストリーミングでは、HTTPレスポンスにSTDOUTストリームをパイプしてもうまくいかないのに、ファイルに保存すると、同様のコードを使ってHTML5クライアントに簡単にファイルをストリーミングできるのはなぜなのか理解できません。FFMPEGスポーンの開始、IPカメラへの接続、ノードへのチャンク送信に1秒かかり、ノードのデータイベントも不定期なので、タイミングの問題かもしれません。しかし、バイトストリームはファイルに保存するのと全く同じであり、HTTPは遅延に対応しているはずです。
  5. FFMPEGで作成したMP4ファイルをカメラからストリーミングする際に、HTTPクライアントのネットワークログを確認したところ、3つのクライアントリクエストがありました。一般的なビデオのGETリクエストで、HTTPサーバーは約40Kbを返し、次にファイルの最後の10Kのバイト範囲の部分的なコンテンツリクエスト、そしてロードされていない中間のビットの最終リクエストです。もしかしたら、HTML5クライアントは最初のレスポンスを受け取ると、MP4 MOOV atomを読み込むためにファイルの最後の部分を要求しているのではないでしょうか?この場合、MOOVファイルがなく、ファイルの終わりもないので、ストリーミングには使えません。
  6. ライブ・ストリーミングを試みる際にネットワーク・ログをチェックすると、最初のリクエストが200バイト程度しか受信できずに中止され、次に再リクエストが200バイトで中止され、さらに3回目のリクエストが2K程度しか受信できませんでした。HTML5クライアントがリクエストを中断する理由がわかりません。bytestreamは録画ファイルからのストリーミングに成功したのと全く同じものです。また、nodeはFFMPEGストリームの残りの部分をクライアントに送信していないようですが、.onイベント・ルーチンでFFMPEGデータを見ることができるので、FFMPEG nodeのHTTPサーバーには届いているようです。
  7. STDOUTストリームをHTTPレスポンス・バッファにパイプすることで動作すると思いますが、HTTPパーシャル・コンテンツ・クライアント・リクエストが、ファイルを読み込んだ時のように適切に動作するためには、中間バッファとストリームを構築する必要がありますか?これが私の問題の主な原因だと思いますが、Nodeでどのように設定するのがベストなのか正確にはわかりません。また、ファイルの終わりがないので、ファイルの終わりのデータに対するクライアントのリクエストをどのように扱えばいいのかわかりません。
  8. 206 個の部分的なコンテンツ要求を処理しようとしていることは間違っていますか?これは通常の 200 HTTP 応答で動作すべきですか?HTTP 200レスポンスはVLCでは問題なく動作するので、HTML5ビデオクライアントはパーシャルコンテンツリクエストでのみ動作するのではないでしょうか? 私はまだこの分野を勉強中なので、この問題の様々なレイヤー(FFMPEG、ノード、ストリーミング、HTTP、HTML5ビデオ)を理解するのは難しいので、何かアドバイスがあれば非常に助かります。このサイトやネットで何時間も調べましたが、nodeでリアルタイム・ストリーミングを実現した人には出会っていません。
ソリューション

EDIT 3:IOS10では、HLSは断片化されたmp4ファイルをサポートします。答えは フラッシュ、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は成熟しました。 Media Source Extensionsは成熟してきており、今では実行可能な選択肢の一つになりつつあります。ほとんどの主要なブラウザでサポートされており ほとんどの主要ブラウザでサポートされています。IOSはまだ対応していません。

次に、VOD(ビデオ・オン・デマンド)とライブ・ビデオは全く異なるものであることを理解する必要があります。確かに同じ映像ですが、問題点が異なるため、フォーマットも異なります。例えば、パソコンの時計が1%早くなっても、VODでは気がつきません。ライブ映像では、その前に映像を再生しようとすることになります。進行中のライブ映像に参加するには、デコーダーの初期化に必要なデータが必要なので、ストリームの中で繰り返すか、帯域外に送信する必要があります。VODでは、ファイルの先頭を読み込んで、好きなところまでシークすることができます。

さて、ここからは少し掘り下げてみましょう。

プラットフォーム

  • iOS
  • PC
  • Mac
  • アンドロイド

コーデック

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

ブラウザでのライブビデオの一般的な配信方法です。

  • DASH (HTTP)
  • HLS (HTTP)
  • フラッシュ (RTMP)
  • フラッシュ (HDS)

ブラウザでのVODの一般的な配信方法。

  • DASH (HTTPストリーミング)
  • HLS (HTTPストリーミング)
  • フラッシュ(RTMP)
  • フラッシュ (HTTP ストリーミング)
  • MP4 (HTTP疑似ストリーミング)
  • I'm not going to talk about MKV and OOG because I do not know them very well.

html5のビデオタグです。

  • MP4
  • webm
  • ogg

どのブラウザがどのフォーマットをサポートしているか見てみましょう。

Safariです。

  • HLS(iOSとmacのみ
  • h.264
  • MP4

ファイアフォックス

  • DASH(MSE経由ですが、h.264はありません
  • Flash経由のh.264のみ!
  • VP9
  • MP4
  • OGG
  • Webm

IE

  • フラッシュ
  • DASH (MSE IE 11+経由のみ)
  • h.264
  • MP4

クローム

  • フラッシュ
  • DASH(MSE経由)
  • h.264
  • VP9
  • MP4
  • Webm
  • ogg

MP4はライブビデオには使用できません(注:DASHはMP4のスーパーセットなので、混同しないように)。MP4はmoovとmdatの2つに分かれています。mdatには生のオーディオビデオデータが入っています。しかし、インデックスが付いていないので、moovがなければ意味がありません。moovには、mdatに含まれるすべてのデータのインデックスが含まれています。しかし、そのフォーマットのために、各フレームのタイムスタンプとサイズがわかるまでは 'flattened'することができません。フレームのサイズを偽装するmoovを作成することは可能かもしれませんが、帯域幅を非常に無駄にしてしまいます。

ですから、どこでも配信したいのであれば、最小公倍数を見つける必要があります。フラッシュに頼らずとも、ここにはLCDがないことがわかるでしょう。 の例です。

  • iOSはh.264ビデオしかサポートしておらず、ライブではHLSしかサポートしていません。
  • iOSはh.264ビデオにしか対応しておらず、ライブではHLSにしか対応していません。
  • FlashはiOSでは動作しません。

LCDに最も近い方法は、iOSユーザーにはHLSを使用し、それ以外のユーザーにはFlashを使用することです。 私の個人的な好みは、HLSをエンコードして、他のユーザーにはflashを使ってHLSを再生することです。HLSをフラッシュで再生するには、JW player 6を使用します(私のようにAS3でFLVに変換する独自のHLSを作成することもできます)。

近いうちに、iOS/MacではHLS、それ以外の場所ではMSE経由のDASHというのが最も一般的な方法になるでしょう(これはNetflixが近々行う予定です)。しかし、私たちはまだ皆さんがブラウザをアップグレードするのを待っています。また、Firefox用のDASH/VP9も別途必要になるでしょう(open264は知っていますが、最悪です。メインのビデオやハイプロファイルのビデオができません。だから今のところ使い物になりません)。)

解説 (6)

これは複雑な問題で、多くのレイヤーがあり、ライブビデオを配信する前にすべてが機能していなければなりませんので、皆さん、特にszatmaryさん、ありがとうございます。私の最初の質問、HTML5ビデオの使用とフラッシュの比較を明確にすると、私のユースケースでは、汎用性があり、クライアントへの実装が容易で、将来性のあるHTML5を強く希望しています。Flashはその次という感じですので、この質問ではHTML5を選択します。

この演習を通して多くのことを学びましたが、ライブストリーミングはVOD(HTML5ビデオでうまく機能する)よりもはるかに難しいことは同意できます。しかし、私のユースケースではこれを満足に動作させることができました。MSE、flash、Nodeでの凝ったバッファリングスキームなど、より複雑なオプションを追求した結果、解決策は非常にシンプルなものになりました。問題は、FFMPEGが断片化されたMP4を破損していたことで、FFMPEGのパラメータを調整する必要がありましたが、もともと使っていたhttp上の標準的なノード・ストリーム・パイプ・リダイレクトが必要だったのです。

MP4には 'fragmentation'というオプションがあり、MP4をより小さなフラグメントに分割して独自のインデックスを持ち、MP4のライブストリーミングのオプションを実現しています。しかし、ストリームに戻ってシークすることはできず(私のユースケースではOK)、FFMPEGの後期バージョンではフラグメンテーションをサポートしています。

タイミングが問題になることもあり、私のソリューションでは、リミックス(事実上、FFMPEGはライブストリームを受信し、それをリミックスし、HTTPで提供するためにノードに送信しなければなりません)の組み合わせにより、2〜6秒のラグが発生します。これについてはあまり対処できませんが、Chromeではビデオができるだけ追いつこうとするため、ビデオが少し飛びますが、IE11(私が好んで使用するクライアント)よりも最新の状態になります。

この記事でコードの仕組みを説明するよりも、コメント付きのGISTをご覧ください(クライアントのコードは含まれておらず、標準的なHTML5のvideoタグとノードのhttpサーバーアドレスです)。GISTはこちら: https://gist.github.com/deandob/9240090

このユースケースの類似例を見つけることができませんでしたので、上記の説明とコードが他の方のお役に立てれば幸いです。

これは私の特定の質問に対する回答ですが、szatmary'さんの回答が最も包括的であるため、受け入れられるものとして選択しました。

解説 (25)

これは非常によくある誤解です。HTML5のライブビデオはサポートされていません(iOSとMacのSafariではHLSをサポートしています)。webmコンテナを使ってハックすることはできるかもしれませんが、普遍的にサポートされているとは思えません。あなたが探しているものはMedia Source Extensionsに含まれており、フラグメントを1つずつブラウザに送り込むことができます。

解説 (15)