2012年12月20日木曜日

rtmp -> iphoneにストリームを流すでの由無し言。

iPhoneのリアルタイムストリーミングに興味があるし、会社でもやっている僕ですが・・・
Flashが生成するrtmpのストリームはコンバートしようとすると、いろいろとやっかいな事象があります。
で、いくつか書きなぐっておきます。

まずrtmp(flv)の問題点
1:timestampが0から始まるとは限らない。
過去の記事にも書きましたが、Flazrでダウンロードするデータを確認してみると、timestampが0から始まらないことがあります。
timestampが1秒後とかからはじまるならいざ知らず。
たまにtimestampが負の数のときもあります。

負の数の場合、ffmpegに通すと、どうやら4時間時間ちょいの値になるみたいですね。
特に次の前後がいれかわるのと組合わさると最悪です。

2:timestampの順にデータがくるとは限らない。
これは、現在作成しているgithubのプログラムでおもいっきり利用しています。
特に音声と映像のデータが入れ替わることがあります。
それでもちゃんと成立して動作してくれるところがFlashのすごいところなんですが・・・
いじろうとするとこまったことになります。
去年だかにつくった、HttpTakStreamingの始めにつくったバージョンでも内部のバグになっていました。セグメントごとに区切っている切れ目のところで、timestampが入れ子になっていると、動作がおかしくなってました。特にその中途のセグメントから再生する場合(途中から視聴開始した場合)大きな問題になってました。

ちなみに1と2が組み合わさってパケットデータが0映像 -3音声 -1音声 1音声 2映像 2
音声 3音声
みたいな順にデータがならんでいるとffmpegの解釈が
0映像 16777213音声という風に解釈してしまうみたいです。
たとえば-3のタイムスタンプの場合HexにするとFF FF FDになってしまうので、16777213というタイムスタンプとなってしまう。
中途のパケットがないので、コンバートがおかしくなる
という事態に陥ってしまうみたいです。

3:中途でデータが欠如することが許可されている。
rtmpの仕様上、中途で音声や映像データが送られなくなったとしても成立します。

4:扱っているコーデックがffmpegで変換できるとは限らない。
G.711やハードウェア依存の音声コーデックもflvに定義されているのですが、こういうデータが流れてきた場合はコンバートうまくできないみたいです。

5:timestampと映像のデータの長さが一致していなくてもいいっぽい。
現在httpTakStreamingのデモで再生してもらうとわかるかとおもいますが、再生していると特に途切れているわけではないのにデータがたまっていきます。
これは映像データと再生時間が一致せず、微妙に再生速度の方がはやいためです。
rtmpで視聴しても同じようにずれていきます。

rtmpは、だいたいどんなPCでも簡単に使えるし、red5とかもあるし導入も手軽にできるし、flvをベースにしているのでフォーマットもわかりやすいんですが・・・
その分自由度が大きいというか、変な形でも再生できちゃうというか・・・というところが問題ですね。

で、これをffmpegでmpegtsにするわけです。
ffmpegの変換について
1:メディアデータが欠損していると、変換が止まる。
これこまります。flvの3で記述したとおり、rtmpの仕様上メディアデータが欠損することがありえます。
AaBbCcDdEeFf...(大文字は映像、小文字は音声の要素とします。)
こんな風に音声と映像がきれいにそろっていると普通に変換ができますが
AaBCDdEeFf...みたいに中途で音声が途切れているデータをffmpegで変換すると

asyncがない場合
AaB     dCeDfEgFhみたいな感じで音声と映像の同期がこわれます。
この場合dの音声がくるまでコンバートがとまった上に、その後データがずれます。
コンバートは遅れたままになり、実際のライブにおいつくことはありません。

asyncをいれると
AaB     CDdEeFfとCのあとの欠損している音声のかわりに無音を挿入してくれるのでずれはないのですが、やはりコンバートがとまってしまいます。
この場合、コンバートはすごい勢いで実行されライブに追いつきます。
ただし、iPhoneとかで視聴していると再生にかかる時間が短くなるわけではないので、追いつきません。(シークとかすれば話は別ですが・・・)
遅延なしにやろうとすれば、ここをどうするかが重要です。
そこでxuggleをつかった自力の変換処理がでてくるわけですが・・・

2:音声や映像トラックがあるかの指定について
flvのヘッダ部分の13バイトの5バイト目
1と4の組み合わせで音声のみ、映像のみ、音声 + 映像等の指定ができます。
この指定ですが、単にflashで再生するだけの場合は適当に(いっそのこととりあえず5の両方ありに)しておけば動作するのですが、プレーヤーによっては、設定があわないとエラーになります。
ffmpegのエラーになる派のプログラムなので、そのあたりきちんとしておかないと、コンバートがなかなか始まらない等の問題がでることがあります。

3:mediaSequenceHeaderについて
flvでコーデックがavc(h.264のこと)もしくはaacの場合は、mediaSequenceHeaderというものがあります。flvの再生にも必要なデータなんですが、ffmpegもやっぱりこのデータがないと、コンバートはじめてくれません。
一応mediaSequenceHeaderが先頭になくても、それまでのデータをすてて動作してくれるんですが、再生までに時間がかかってしまいます。

4:リアルタイムのコンバート負荷について
通常のffmpegはコンバートを実行するために、CPUをフルに使うわけですが、設定によっては、実際の再生時間より短い時間でコンバートが完了します。
で、リアルタイムデータのコンバート実行時には、データは再生時間どおりに入ってきます。
なのでCPUをフルに使わないコンバートになります。
また、よりお行儀のよいFLVデータとしてinputデータを送ってやると余計な処理がはしらず(音声データ欠損待ちの時間のメモリー保持データとか、timestampの順番が狂っているときに再ソートし直す処理とか)、負荷が減ってきます。
また、詰まったりしなくなるし、そのことによる負荷のムラも減ります。

ただし逆に負荷をかけすぎると、再生時間内にコンバートができなくなってしまいますので、空きがあるんだからと複数のチャンネルをいっきにコンバートしはじめると、出力データがどんどん遅れていくという弊害もあります。

で、できたデータをiPhoneで再生できるように、分割だのするわけですが・・・
iOSでの再生の話。
1:シークができてしまう件について
iOS上で動画再生をしているとライブにもかかわらず、シークができるようになることがあります。
これは仕様上、m3u8に記述しているデータが4つ以上になっているとシークができるようになってしまいます。

2:リアルタイム性について
セグメントという分割したデータを読み込ませて、順次再生していくという形なのでどうがんばってもある程度遅れてしまいます。
で、どの程度おくれるか・・・という話なのですが
 rtmpの転送にかかる時間(1秒以下)
 mpegtsに変換するのにかかる時間(最小で約2秒)
 セグメントができるだけのデータがたまるまでの時間(設定次第)
 デバイスまで転送するのにかかる時間(1秒以下)

 再生に必要な分デバイスにデータがたまるまでかかる時間(*)
これだけ遅延が発生します。
ここをどうするかが腕のみせどころですね。
個人的には、映像を自分で準備するならチューニング次第でセグメントを1秒程度にすることも可能みたいです。一般的なrtmpを変換するなら、2、3秒とった方がよさそうです。
よって上下の転送で1秒、変換に2秒、セグメントデータと必要なデータ量を3つ分とすると必要なデータ取得で6秒から9秒かかることになるので、9秒から12秒遅延するとみつもることができます。セグメントを1秒にした場合は5、6秒といったところです。

1つ注意があって、セグメントがあまりに小さい場合、(*)のついている部分の必要なデータがセグメント3つで間に合わなくなることがあります。
この場合、シーク禁止するためにm3u8に記述するデータを3つに制限してしまうと、うまく再生できなくなってしまうことがあるので、要注意です。

3:音声のみのデータについて
以前つくったjsegmenterにいれてありますが、m3u8のデータでは、mpegtsだけではなくmp3のデータも扱えるみたいです。
もちろん、音声のみのmpegtsをつくってやることも可能です。
ただ両者には挙動の違いがあります。
mpegtsの音声のみバージョンの場合はaacやmp3が対応できます。
ただし、映像と同じくsafariを閉じて別のアプリに移動すると再生が中断します。
BGMとして再び再生すると動作はしますが、アプリをかえたりするととまってしまいます。
mp3をベースにm3u8を作成すると、通常のmp3と同じく、別のアプリに切り替えてもBGMがとまらずに移動できます。
また、audioタグをつかって再生する形にしておくと、音楽をかけたまま別のタブに移動したりできます。便利です。

4:いろんなブラウザでの挙動。
このm3u8ですが、phpやrubyとかで吐いてやるようにすると便利です。
ですが、拡張子が違うと、各ブラウザによってちょっとずつ挙動が違うみたいです。
できたら、content-typeの出力だけでなくhtaccessとかで拡張子もそろえてやった方が吉です。
また内部のtsファイルへのアクセスは相対パスでもかけますが、できれば絶対パスで書く方がいいです。
とりあえず手持ちには、safari、chrome、operamini、あとairでつくるアプリもあるんですが
safari(どっちもOK)、chrome(iphoneならどっちもOK)、operamini(どっちもOK)、air(m3u8拡張子でやるべき)
こんな感じです。
とりあえず面白いのが、chromeの映像高速再生バグとoperaminiの挙動ですね。
chromeの高速再生バグはiphone4Sでしか確認していないのですが、videoタグのあるページのあるchromeに別のアプリからもどってくると、videoタグの中身の映像がなぜか早送りで再生されるというもの。
おもしろいので、そのうち録画してyoutubeにでもあげようと思います。
safariでもよくわからないですが、同じようなバグがでるときがあります。

operaminiの場合、m3u8へのアクセス用のよくわからない中継サーバーをsafariで再生するみたいな挙動します。
この場合、内部のtsデータを相対パスで書いておくと、中継サーバー内のデータにアクセスすることになるので、動作しなくなります。

なお、vlcのコマンドライン版 cvlcをつかうとffmpegではなくvlcをつかってこのhttpLiveStreamingを生成したりできるし、iOS5以降の端末の場合、m3u8のduration指定に小数をつかえるようになったり、1つのmpegtsファイルの部分を指定することでm3u8をつくることができたりと、いろいろネタはあります。

0 件のコメント:

コメントを投稿