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をつくることができたりと、いろいろネタはあります。

2012年12月11日火曜日

red5のアプリにhttpTakStreamingの動作をいれてみましたが・・・

red5を利用して、rtmpベースのTakStreamingをつくりました。
んで、そのあとhttpベースのTakStreamingもつくってみたのですが・・・

どうも、httpベースの方の動作が芳しくないです。

原因は簡単。
red5では内部にtomcat6がはいっているのですが、このtomcat6。
静的コンテンツはデフォルトで5秒のcacheが効くみたいです。

1秒ごとという単位でflfファイルが更新されていくのに、cacheが効いてしまって変更の適応が遅くなるのが問題だという・・・

で、対処方法ですが、いまのところ2つしか見つかっていません。
1:webapps/tak/WEB-INF/red5-web.xml
こっちで設定しているdurationの値を5秒くらいにする。
セグメントのdurationを大きくしてしまうと遅延が酷くなっていきますが、まぁ動作します。
だいたい5秒〜10秒くらい遅延する感じになりますね。

2:conf/context.xmlにcacheTTL=1をいれる。

<!-- The contents of this file will be loaded for each web application -->
<Context cacheTTL="1">

    <!-- Default set of monitored resources -->

    <WatchedResource>
Contextの属性にcacheTTLをいれれば値を変更することができます。
1秒ごとに内容がかわるようにすれば、とりあえずduration=1でも動作できるようになります。

ただし、webアプリケーションのwarファイルを提供しておしまいとするにはちょっと問題があります。なにせサーバーの設定を変更しなければいけないという・・・

あとは、やっていませんが、flfファイル出力をservletの動的出力にしちゃうとかですかね・・・
いい方法ないもんですねぇ・・・

一応red5のフォーラムでどうにかなんない?って尋ねてみましたが、どうなることやら・・・

2012年12月9日日曜日

red5用のtakStream実装つくりました。

rtmp実装が作りたかったので、red5用のアプリケーション実装を書きました。

https://github.com/taktod/TakStreaming2/tree/red5Code
この中の
tak/ディレクトリの内容をそのままred5のwebappsの中にいれればそれでOKです。

あとはpublisherあたりで適当に放送を開始します。

そして、専用プレーヤーでrtmp://localhost/takにアクセス
放送名をscopeで実行します。

こんな感じにrtmpのストリームが再生されます。

まぁ、rtmpで流しているデータを使うなら、普通にrtmpをつかえばいいんですけどね。
わざわざTakStreamに変更する必要はないか?

red5バージョンはあまり積極的に更新するつもりはないのですが、以下の様な可能性があります。
・servletによるhttpTakStreaming対応。
・servletによるタイムシフト機能。
・rtmfpとの連携。
・チャンク出力をやめることで、よりリアルタイムな動作。

このあたりでしょうかね。

あとついでにブログ用のプレーヤーもつくってみました。
こんな感じで動作します。
一応rtmpTakStreamも対応してますが、デフォルトはhttpTakStreamにしてあります。



ではでは〜

2012年12月8日土曜日

プログラムをgithubに公開しました。

前回作成した、httpTakStreamのプログラム

http://49.212.39.17/player/TakStreamingPlayer.html
このデモのやつ
サーバー側のプログラムも含めてgithubに登録しました。
https://github.com/taktod/TakStreaming2

javaのプログラムはmasterにflashのプログラムはflashCodeのブランチにいれてあります。

自分としては、いろんな人に再生プレーヤーをつくってもらって広まればうれしいので、再生プレーヤーの作成はめちゃくちゃ簡単にできるようにと考えてつくりました。

netStreamとvideoは
var netStream:NetStream = TakStreamingFactory.getStream("アドレス");

var video:Video = TakStreamingFactory.getVideo();
で取得可能。あとは、netStream.play(null);
で適当に再生すればはじまりますので簡単にできたと思っています。

あとは、rtmpやrtmfp対応をつくって、データが取得できなくなったときにシームレスに他のプロトコルに切り替える動作をつくる。
Flvから直接takStreamをつくる動作をつくる。
red5用のアプリケーションをつくって、パッケージ化する。

あたりやりたいですね。

2012年12月6日木曜日

httpTakStreaming再び

iOS用にhttpLiveStreamingというのがあります。

ファイルをセグメントという小さな単位にわけ、それぞれを必要なときにダウンロードして再生させるという動作。
httpでコントロールできるので、非常に使い勝手のよいストリーミングです。

で、そのFlashバージョンにhttpDynamicStreamingというのがあるのですが、少々扱いにくいです。というのもファイルそのものが取り出しにくいというのと、live用のモジュールはタダではつかえないという。

というわけで、自分であらたなセグメントストリームを作ってみました。

こんな感じにファイルをダウンロードしまくります。

以前も一度つくったことがあるんですが、今回はrtmfpやrtmpも視野にいれて開発しています。
rtmfpあたりで安定動作させることができれば、サーバー代や回線代も削減できますからね。

デモはこちら
http://49.212.39.17/player/TakStreamingPlayer.html

仕様上、なにか放送していないと意味がないので、とりあえずlongplays.orgにあったT-0815さんのr-type finalの動画をflazrでループpublishさせてあります。


いまのところ問題かと思っていることは・・・
1:再生が微妙に遅くなる。bufferLengthの項目が微妙にたまっていきます。
2:まれにnetStreamが蒸発する。
3:aac音声の場合音声が再生されない場合がある?

といったところですね。
他にも気になったところとかありましたら、コメントなり、twitterなりで連絡いただけるとうれしいです。

ちなみにrtmfpで通信がおいつくのか?という部分に関してですが、以前のバージョンのhttpTakStreamingのパケットをrtmfpのnetGroupとnetStreamでやり取りさせたことがあるのですが、netGroupでの共有だと間に合いませんでしたが、netStreamなら問題なく動作できました。可能性はまだありますね。