2013年2月17日日曜日

いままでつくったHttpLiveStreaming(iPhone用)のまとめ

 rtmpをつかったストリーミングサービスを会社で立ち上げ。その後、スマートフォンにも対応するかという話になったところから、iPhone用のストリーミングをつくるということに注力してきました。
 おかげで動画、音声コンテナについてかなり詳しくなったし、いろいろわかるようになってきました。その振り返りを残しておきたいとおもいます。

第0号:mp4の分割ファイルを順につくってhtml5のvideoタグで再生させるやり方。
 →一番はじめに上から提案されたやりかた。
ライブストリーミングとはいえないし、httpLiveStreamingという方式があることがわかったので見送り。

第1号:wowzaのnative変換、ここからhttpLiveStreamingを使い始める
 →どういうものかとりあえず触ってみたもの。
以下の弱点があった。
1:h264 + mp3もしくはaacのコーデックに適合しないとダメ
2:wowzaのプログラムとしてアクセス制御ができない。
3:安定性微妙。

注意:最新のwowzaには変換動作がついているので、そちらを使えば問題ない可能性あり

第2号:rtmpdump + ffmpeg + segmenter
 radikoの音声をこの3つの組み合わせで流しているという記事をみつけた
http://nrglog.net/blog/archives/2010/04/radiko-iphone.html
この記事だったかは不明、まぁでも似たないようだった。
動作させてみたところ、おおむねだいたいのチャンネルの放送が視聴できた。

第3号:(変換セット) + ftpで転送 + apacheで提供
 会社でftpとapacheによる提供プログラムを書いた。
単に生成ファイルをcronで拾ってftpで転送。
apacheをつかってユーザーにm3u8とtsを提供することで動作。
確実にコンバートできる自信はなかったため、コンバートできている部屋のみ提供という形だった。
 初の実践投入

第4号:rtmpClient もしくは flazr + ffmpeg + segmenter
 red5のrtmpClientの動作をライブストリームを別のサーバーに移して多人数配信させるという動作を別途研究していたので、その研究の流用。
放送者の接続、切断、リロードその他のコントロールをうまくしたかったため実験する。

第5号:flazr + avconv + segmenter
 特定のコーデックの場合変換起動をffmpegが実行できないバグがあったため、いろんなバージョンのffmpegとavconvを試してみた。結果としてavconvを選択。
rtmpのダウンロード動作はflazr一択になる。

第6号:flazr + avconv + segmenter(改造)
 3号の時点でリアルタイム処理にならなかったため、systemコマンド(だったと思う。)をつかってsegment作成時にphpをキックし、すぐにアップロードする形にした。
 実践投入第二弾

第7号:flazr + xuggle
 segmenterの吐き出すファイルをみていたところ、どうも出力がよろしくない感じがしたため、変換まわりにも口をだしたくなったため、ffmpegのjni実装であるxuggleに手をだす。
 こちらも4号と同じく、rtmpのビットレート変換やコーデック変換技術の研究の副産物。xuggle-xuggler-red5のプログラムをflazr化した。
 ちなみに出力がよくないとおもった理由は再生を開始したときに、音声だけの時間が微妙にできたり、すぐに開始しなかったりしたため。調査の結果として、どうやらmpegtsの出力ファイルの先頭に動画のキーフレームがきていないっぽいことがわかる。
 xuggleのファイル出力時に変換後データのタイプとフレーム情報がわかるので、擬似的にキーフレームを選択して、分割候補にすることに成功した。
(このときにはmpegtsのファイルの内部構造にまでは口をだしてなかった。)

第8号:flazr + xuggle(マルチ出力)
 xuggleの出力コンテナをいくつか準備することで変換自体は1度しかおこなわないが、いろんなコンテナのファイルを出力できるというものを書いてみた。
https://github.com/taktod/streaming/tree/mediastreaming
いろいろ拡張したところこうなった。
動作はそこそこだが、ここでいくつか問題があることがわかった。
 xuggleの変換だと、3つほど同時に変換を実行したら急に動作がわるくなった。
よってxuggle-xuggler-red5みたいにアクセスのあるストリームを個別に変換するという処理はありえなくなった。(1プロセスで複数変換を走らせることになるため)
また、プロセスを別にしても動作の改善がみられなかったのでJNIとマルチプロセスの相性がわるいような気がしました。
 大量のコンテナ出力を実行したところやはり負荷があがった。
どうやらコンテナ変換動作もあまり得意ではなさそう。
(rtmp inputでflv中低、mpegts中低)という出力をやってみたところ動作が芳しくなくなった。
 遅延を抑制するために、javaのプログラムをマルチスレッド化して動作するようにしてみたんですが、今度はxuggleの動作が微妙になってきた。(コンバートがおかしくなったりもした。)ここでだいぶxuggle熱が下がりました。
 ちなみにwebM出力をつくるためstream-mとmatroskaのドキュメントを解析し、webMについて詳しくなった。

第9号:flazr + avconv + jsegmenter
 変換動作をいろいろいじってみたところ結果的にavconvやffmpegをそのままプロセスとして実行してもらった方がコストがいいことがわかりました。
 仕方ないのでmpegtsの分割を自力でやってみることにしました。で、mpegtsがどういうものであるか学習していった。副産物としてmp3もHttpLiveStreamingでながせることがわかった。で、mp3がどういうものであるかも学習した。
その結果:
1:キーフレームで分割できるようになった。
2:mpegts出力ファイルのtimepositionがわかるようになった。
3:mp3でもHttpLiveStreamingできることがわかった。(appleとしては非公式サポートフォーマットかもしれませんね。)

https://github.com/taktod/jsegmenter
で、こんなものができた。
まぁ、mediaStreamingでつかっていたパケット解析の部分の流用であります。

HttpLiveStreamingを「変換して生成する」場合にはキーフレームの挿入タイミングをffmpegのコマンドで調整して、生成mpegtsをキーフレームごとに分割して配置するということをやると一番ライブストリームに適合した動作になることがわかりました。
また、mpegts出力側のtimepositionがわかるので、ffmpegの変換遅延とCPUの利用状況を確認できるようになりました。

このころのプログラムでは、この3つのプログラムをパイプでつないで動作させてました。入力データのcodecやフレーム状況がかわった場合にプロセスごと作成しなおすという動作をやっていました。

第10号:flazr + avconv
 第9号の動作をつくったあとに、まだたまに変換動作がうまくいかない放送があることに気づきました。問題点は以下2点
1:プロセスの作成停止を繰り返すのはサーバーにやさしくない。(ゾンビプロセス等ができる可能性が高い。)
2:rtmpの入力データは放送している環境に依存して、ffmpegが再生プレーヤーで扱いにくいものになる可能性がある。またそのおかげでコンバート待ちが発生したりする。

 まず1点目、6号でsegmenterからsegment作成時にphpをキックするという動作を書いていましたが、このやり方だと、大量のプロセスが使い捨てになります。また中途で放送がとまった場合や、動作開始後一定時間音声しかこない場合は、あきらめて音声のみのコンバートにまわすなど、いろいろな要素でなにかある度にプロセスを立ち上げ直すという動作を実行していました。
 →結果としてたまにCPUを占領してしまうプロセスができたりします。
 CPUのおおいサーバーをつかっている場合はこのままでも問題ないのですが、CPUがそれほどない場合は結構致命的になります。
 この対策として、10号では、flazrのプロセスは基本的に止めない形にしました。
flazrのプロセスが転送データの状態を確認して、必要があれば、avconvのプロセスをProcessBuilderで制御します。

 次に2点目、入力データがよろしくないとコンバートに影響がでる件。
まずは、silenceLevel問題
http://livedocs.adobe.com/flash/9.0_jp/ActionScriptLangRefV3/flash/media/Microphone.html#setSilenceLevel()
途中で入力音量が小さくなったら音がないと判定する件。余計な音を拾わないので放送する側にとってはあると助かる機能ですが、ffmpegにとってはあってもらったらこまる機能になります。というのも音声パケットが欠損するとffmpegのコンバートは停止してしまいます。停止するということは、視聴が止まるしどんどん実際の放送との遅れがどんどんのびていきます。
 続いて映像がない場合の問題。rtmpでライブストリームしているときにUSBカメラを物理的に抜いてしまうと映像側の転送データがなくなります。こうなると音声同様コンバートが止まる訳です。さらにsegmentの作成方法が基本映像データのキーフレームにしてあるので、そもそもsegmentができない(もしくは切れずに大きなものになっていく。)という状況になります。
というわけでこちらも対処してあります。
以前の記事で何回か無音mp3をつくるという記事をつくっていますが、flvは構造が単純なため、無音mp3や適当な動画データを織り込んでやることが可能です。

 10号では、さらにリアルタイム性の確認方法も織り込んであります。

というわけで10号は結果的に次のようになっています。
1:コンバート安定化対策
 ・音声がない場合は無音mp3を挿入する。
 ・映像がない場合は代替flvを挿入する。
 ・音声が中途で欠損した場合は最終音声を1秒ごとに挿入してコンバートが中途でとまらないようにしてある。
 ・コーデック情報を常に監視して変更した場合は速やかにコンバート書け直しを実施する。
2:動作安定化対策
 ・segmenterやアップロードすべてFlazrにやらせることでプロセスはすくなくしておく。
 ・変換入力データと出力データのtimestampを把握することで変換効率をリアルタイム監視
 ・生成データの転送効率も監視しておく。
 ・プロセス管理の方法をredis等外部kvsに依存させることで他のプロセスで動作レジュームが可能にした。
 ・復旧までにかかるタイミングを2秒以内に設定
という形に固まりました。

で、この魔改造を施したシステムですが、実際にはこうなりました。
https://github.com/taktod/TakStreaming2/commits/master
また、入力するデータにまで口を出せば遅延10秒以内はいけるみたいです。

実践投入の方も去年の終わりぎりぎりに会社のシステムに織り込めて、売り上げもあがりました。
(注:ちなみにコンバート安定化対策はそのままffmpegのコンバートコスト低減対策になります。)

で次の予定ですが、
第11号:flazr + avconv(予定)
 10号で音声が欠如したときに、最終音声をいれてコンバートを促すという手法をとったわけですが、この方法、最終音声が無音ではなかった場合、その音が繰り返されるという問題があります。今のところ無音mp3については研究してあるので、他の無音speexとかをつくれば同じことができるのですが、いろんなコーデックが選択可能だし、bitrateとかの兼ね合いもあるので、その辺りどうすべきか調査して折り込みたいところです。一応、無パケットメディアタグというのがあるみたいなので、そちらをいれて動作するなら、すごくうれしいですね。簡単だし。
 あとは、再変換要求すべき状態を見極めて、プロセスの再起動が必要ないところがあれば、再起動せず、あたらしいメディアデータを注入したいところ。
 https://github.com/taktod/stdinFlazr
こちらでrtmpによるpublish操作を書いてみましたが、変換したデータをサーバーにあげ直すという動作もやってみたいところです。wowzaでコンバート動作がサポートされてるし、ffmpegで入力出力両方にrtmpdumpプラグインを指定できるので、なくてもよさそうですが、前者はコストが高い、後者は接続の安定性が微妙という問題があるので、まぁ意味はありそうです。

 MusicTubeの件でmp4の音声部分のみ抜き出しプログラムを書いていますがmp4のmoofというボックスを使えば、再生可能端末が限られますがmp4でもliveStreamingできるみたいですね。また、webMのストリーミングも同じ手法でできるみたいです。
このあたりのことにも最近は興味あります。webMとmp4とm3u8でライブストリーミングできるようにしておけば、html5だけでライブストリーミングできるようになると思いますね。

 なお、技術協力ほしいという方おりましたら、連絡いただけるとうれしいです。
上記にあげている内容はほぼすべてこのブログとgithubにあげてますが、細かい微妙な部分での差異等は伝えきれていないと思っています。
 その辺りどういう情報が必要なのかわかりましたら、ブログの記事にしてどんどん情報公開していきたいですね。

0 件のコメント:

コメントを投稿