2013年4月25日木曜日

audioPlayerでつかっているmp4の話

 最近注力しているaudioPlayerのhtml5のプログラムですがaudioタグでmp4が再生できない場合に、Flashをつかって再生させるようにしてあります。(firefoxとかIEとか)
 で、既に読み込みが完了している部分ではシークができるんですが、読み込みが完了していない部分ではシークができません。
これはFlashの仕様に依存しています。
 で、これをなんとかしてやりたいなぁと思っているわけです。

nginxのモジュールには似たような問題の解決方法があります。
http://nginx.org/en/docs/http/ngx_http_mp4_module.html
何をやっているかというと、getパラメーターで与えた値以降のみで構成されているmp4を生成して応答してやるというもの。
10秒からを選択した場合に、10秒の部分から始まるmp4をhtmlの応答として返せばいいじゃね?というわけです。
ただこのnginxのモジュールはローカルにあるファイルに対して実行する修正になります。
 僕があつかっているデータはあくまで元データはyoutubeにあり、データ転送のproxyを実行することで転送途中で速度を損ねることなくデータ調整するところがミソになっています。というわけでnginxのモジュールでハイ解決とはいきません。


 で、自作する必要があるわけなのでいままでのおさらいということでメモしておきます。


■作成プログラム第1号:(単なるproxyサーバー:nettyバージョン)
 はじめにつくったプログラムはnettyから起こしたサーバーでした。
なぜnettyかというとflazrつながりでnettyにハマっていたからです。
確かにnettyでhttpサーバーを書くことができたのですが1つ問題がありました。
nettyで動作させたところ、応答データの追記書き込みができないみたいです。
httpのpartial Content(206応答)の肝はデータを少しずつ応答する・・・なんですが、この少しずつというのがnettyにとっては苦手みたいです。(すくなくともhttpサーバー動作としてつかったクラスは苦手だった模様です。)
しょっちゅうtimeoutとか起こして使いにくいなぁと思っていました。
 このころはまずは動作を・・・ということでテストでつくってみたproxyサーバーになります。

■作成プログラム第2号:(単なるproxyサーバー:jettyバージョン)
 上記のnettyバージョンのproxyサーバーを書いているときに、jettyのproxyServletの動作を参考にしていました。proxyServletを使うと手軽にproxyサーバーをつくることができます。
ここでいうproxyサーバーというのは、ネットワーク設定にいれて、全部のhttpの通信をこのproxyサーバーを通して動作させるというたぐいのproxyです。
 mp4ファイルへのアクセスがどのようになっているのか調査するのにちょうどいい感じだったので重宝していました。
 で、nettyでつくっていた動作の応答が芳しくなかったし、動作の調査してるときに、ちょうどjettyのプログラムにいろいろとログを挟んだりしていたので、じゃぁ、jettyに乗り換えるか・・・ということでjettyベースのプログラムを構築しました。
もちろんつくったのは、特定のファイルの通信にのみ間に入って手を加えるproxyです。
 これははっきりいってあっさりできました。だって、アクセスパスから目標mp4のアドレスを調べてしまえばあとはproxyServletのコピペで終わっちゃいましたからね。

■作成プログラム第3号:(映像のないmp4をつくるバージョン)
 単なるproxyがうまくいったので、そろそろ転送しているmp4のデータに口をだそうと思いました。とりあえず目標は通常の動画から映像部分を取り除いて音楽のみのデータに変えてやってiOS6.1のiPhoneでもBGM再生させること。これが目標です。
でやったこと
・音声用trakデータ、映像用trakデータの2種類がある。
・tag名をfreeに変更すると無視するtagに変更できる
以上の2点がmp4をいじっていてわかったのでproxy転送しているデータのtrakの部分をfreeに書き換えておくってやれば音声のみのmp4にできそうということになりました。
で、つくったところ、いい感じの動作をしてくれたのでさくさくっとjqmobiバージョンのmusicTubeをつくってベータリリースしました。

■作成プログラム第4号:(映像部のデータ転送やめましたバージョン)
 上記のプログラムでもうまく動作していたんですが、映像の部分を無視するようにしたとはいえ、なくなったわけではないので使わないデータ転送がある状態でした。
使わないなら消しちまえということで、削除したかったのですが、そうなってくると、4バイト書き換えるだけでは済まなくなります。
 で、やったこと
・映像用trakデータは必要ないので、削除する。
・削除にあわせてヘッダデータも書き換える。
・mdatの実データ内容もうまく変更してやる必要がある。
という3つを満たしつつ
・データ実体は持たない、http proxyとしてきちんと成立させる。
(転送速度に影響がでないようにする)
というちと無謀なことに挑戦してみました。まぁ、実際にできちゃったわけですが
 まず1つ目の大問題
httpの応答では、これから応答するデータがどういう大きさであるか応答しなければいけません。初回アクセス時にこのサイズがどの程度になるかは誰にもわかりませんし、さすがにこの計算を実施してから応答を開始する・・・では遅すぎます。
たまたまiPhoneのアクセスでは何度が分けてアクセスが来ることが先のproxyServletの動作ログからわかっていたので、なにもわからなければデータ元のサーバーが応答したサイズをとりいそぎ返すことにしました。
 続いて2つ目の問題
httpの全体のサイズはごまかしましたが、mp4の内部データでごまかしを使うわけにはいきません。(応答データが中途で変わることはまずありえないし)
というわけで、moovのタグの開始部(先頭から30バイト程度の部分があるやつ)の大きさは正しい値にする必要があります。この計算を高速に実施しないとhttp proxyとしては成立できなくなっちゃいます。
幸い次の2点のおかげで解決することができました。
・partialContentの要求を使うことで、originalデータの問い合わせ時に必要な部分の問い合わせに狙い撃ちできたこと。
・mp4の内部データがサイズ + タグ + 内容という構成だったので、あらかじめ大きさが把握できたこと。
いくつかのタグは非常に大きいので頭から律儀に読み込みを実施しているとタイムアウトになりそうなくらい時間がかかってしまうのですが、そのあたりはすっぱりあきらめサイズだけに重点を置いたところ解決できました。
 3つ目の問題
moovの内容の応答を勧めていくことになるのですが、先ほどスキップしたデータの非常に大きなタグが次の問題になります。具体的には、stco(co64)のタグが問題です。
このタグは、mp4ファイルの内部のどこに目的のデータがあるか記述しているタグです。
データを書き換えつつ応答しなければいけないので、以下のような手法をとりました。
データ元のサーバーに2本httpUrlConnectionの読み込みストリームを作成して、映像と音声のstcoのデータを並列で取得しつつ、計算してたたき出した値をiPhoneに返していくという動作にしました。
後述のmdatの編集もそうなんですが、前から順にデータが並んでいるという構造であるため、逐次データをつくって応答していくということができて助かりました。
このときに平行してサーバー上のデータとして、mdatのデータのどの部分を抜き出して応答すべきかというデータを保持するようにしています。
 あとは、mdatの部分の応答ですがデータ元サーバーからは全部DLしつつもクライアントには、計算して出した必要な部分のみ応答を返していくという動作をすることで、映像のデータを完全に除外したデータ転送を実施することに成功しました。
 一応まだmetaデータ部(udat)がまぁなくてもいい命令なんですが、データ元サーバーのサインみたいなものだと思うので、そこには手を出してません。
あと、html5のaudioタグの動作仕様だと思うのですが、プレーヤー側でシークした場合に新しくparticalContentsでサーバー側にDLの要求が飛んでくるので、その場合は対応するデータ元サーバーへの問い合わせ位置を計算したあと、その場所からのDLを行いつつクライアントには必要なデータのみ応答するという動作に仕上げることができました。
これが現状のproxyサーバーの動作となります。


■これからつくるやつ案
とりあえず、Flashの動作のために、moovをもっと改造して中途から始まるmp4をつくる必要があります。
で、さくっとmp4ファイルをいくつか確認して関係ありそうなタグをあつめてみました。
trakの中のstbl以下の次のデータが関係ありそうです。
stsd (Sample descriptions)
stts (Map decoding time to sample)
stsc (Map sample to chunk)
stsz (Sample sizes)
stco (Chunk offsets)
stss (Sync sample table) (これは映像onlyっぽいです。もともと映像と音声を同期させるためのデータみたい)

まずstsd
ざっと調べたところ手持ちのmp4のデータではどれも1つのデータのみはいっているみたいです。とりあえずそのままコピーでいいかも
続いてstts
サンプルの時間の記述みたいです。中途で変わることがあるデータみたいなので、中途からはじめるためには、それにあわせて変更しないとだめみたいです。
stsc
Chunkの構成とそのデコード方法?が記述されているみたいです。
audioのデータをみるとそれほどデータ数がおおいわけではなさそうです。単なるサンプルとデコード方法の対応表だったら変更なしでもとりあえずいけるのだろうか?
stsz
サンプル1つ1つのサイズ定義
mp4の解説からするとまぁ、なくてもいいよというデータっぽいですが、いまのところ消せたらラッキーくらいに思っています。DL済みデータのシークができなくなるかもしれませんね。
stco
前回修正したmdat上のどこにデータがあるか指定
moovが先頭にきているデータなので、変更にあわせて書き換えが必要。
stss
映像と音声の同期用、今回は音声のみの抽出なのでばっさり削除でいいはず。(もともと映像側にしかないデータなので切ってるし)

http://d.hatena.ne.jp/SofiyaCat/20080430
こちらを今日は参考にしました。


まじめにmp4をつくるなら上記のデータの修正が必要になりそうです。

で、そんなことをしていると大変なので、proxy用に不真面目にやる方法も考えてみました。
1:DL済みデータのシークをすっぱりあきらめて参照用のindexがmdatの先頭にしかないデータをつくるやり方。
これが可能なら結構楽です。中途の位置を計算する方法がそもそも必要なくなりますので、moovの内容もとってもコンパクトになりそうです。
ただ、きちんと成立するデータがつくれるかちょっと疑問ですが・・・
 問題点は、サイドシークした場合にDL済みの位置だとしても再DLを強制されるところ。
3G回線のandroid端末だと何度もmediaデータの転送が走ってイライラするかもしれません。
2:あたまの方にあるデータをすべて0byteだったことにするやり方。
これも可能なら非常に楽です。stcoのoffsetデータを目標の時間のデータまですべて0にしてしまって全部DLしているが見かけ上は中途再生にみえるみたいなことができるかもしれません。
 問題点は、うごかない端末が出てくる可能性があることくらいでしょうか。
3:FLVにしちゃう。
osmfのプラグインにHLS対応のがあるんですが、mpegts→flv変換をactionScriptでやってるみたいです。同じようにmp4→flvのコンテナ変換を自力でやっちゃってflashに送るデータをflvにしてしまえば中途データにするのは非常に楽です。
これができたら汎用性かなり高くなりますね。flashをつかっている限り・・・はですが。
需要としては、flash→mp4変換がproxyでできてしまえばすばらしく良いということになりますが、そちらはおそらく無理でしょう。mp4の作成に必要なデータがflvの先頭だけで収集完了できるとは思えないです。
4:結局つくらない。
 wifi環境(使えるところが光回線ばっかりだからだと思うのですが)で何度かFlashの動作を確認しているのですが、4時間ほどあるffのmp4でもDL完了までにかかる時間が50秒程度
DL済みの部分はシークできるので、まぁストレスはあるものの使えなくはない程度かなぁという感じです。通常の動画だと数秒でDL終わるし

とりあえず、いろいろ試していけそうな予感です。

jettyのwebSocketServletで古いWebSocketにも対応してみる。

webSocketとbookmarkletを利用して、iPhoneみたいな端末上でもwebのデバッグができるプログラムを実は書いて使っています。(chromeのconsoleや要素解析みたいなことができます。簡易的なものだけど)
で、古いiOSの端末でサーバーへの接続がうまくいかなかったのでなんとかする方法を見つけました。

なんとかする方法はgithubにあげました。
https://github.com/taktod/websocketJetty/commit/bb9502d4040daccae498d805e5e22d40987f9c30
web.xmlの記述にinit-paramのデータを追加して、minVersionの値を-1にすれば、全部のwebSocketに対応するみたいです。(0じゃだめ)
デフォルトでは、RFC6455以降に対応するみたいになっているみたいですね。

これは推測ですが、webSocketのバージョンによっては、binaryの転送はサポートされていないとかあるので、minVersionの値によっては使えない動作がでてくると思います。
その点だけは注意しなければいけないです。


init-paramの値には他にも

  • bufferSize
  • maxIdleTime
  • maxTextMessagesSize
  • maxBinaryMessagesSize
  • minVersion

というのがあるみたいです。

http://download.eclipse.org/jetty/stable-7/apidocs/org/eclipse/jetty/websocket/WebSocketServlet.html


とりあえず、いまのところ僕が扱っているwebSocketの動作ではtextデータのやり取りができたらそれでいいので、minVersion -1だけをいれておけばいいかなと思っています。

2013年4月21日日曜日

MusicTubeのプログラムをbootstrapで書き直してみた。ただしまだ全機能ではないけど


MusicTubeの音楽を聴くプログラムをbootstrapで書き直してみました。

senchaよりあっさりしてるけど、まぁそこそこな動作になったと思う。
上の部分にプレーヤーを持ってきて、動作詳細を隠しておくのはいいかもしれないですね。sencha側のプログラムもコレに合わせてしまった方がいいかも。


いまのところの仕様は
http://taktodtools.appspot.com/audioPlayer/index.html#list/(youtubeの動画ID),(youtubeの動画ID)....という感じに並べればそれが再生リストになるというものです。

今回はwebkit以外のブラウザでもきちんと動作するように、audioタグでmp4が再生不能なブラウザかどうか判定して無理な場合はFlashで再生するようにしてみました。

bootstrapをつかったとはいえ、やっぱりIEが鬼門ですね。
とりあえず手元にあるwinXPのIE8での動作確認はやっておきましたが、幅が小さくなったときの動作が気に入らないですね。

上記のiframe内のリストはbeatmaniaの曲で固めてみました。個人的な趣味です。
ではでは

2013年4月16日火曜日

HttpLiveStreamingとmp4の動作の違いについて

youtubeの動画をiphoneでみると、mp4のデータが視聴できます。

今回はこのmp4と僕が好きなmpegtsベースのHttpLiveStreamingという形式の違いについてです。

まずはmp4
mp4はxmlみたいなタグ+データの形式のコンテナです。webmのmatroskaも似た感じです。
たいていのデータは詰め込むことができますが、一番メジャーなのは、h.264 + aacの組み合わせです。3gpというちょっと前の携帯電話で扱える動画なんかも基本mp4だし、adobeが出しているhttpDynamicStreamingやf4vもベースはmp4です。

データの中身は大きくわけでmoovとmdatに分かれます。moovは本でいうところの目次にあたる部分で、シークしたりするときにどこにデータがあるかという情報が書かれています。mdatはメディアデータの実データ部分になります。
ffmpegでコンバートした場合、mdatの後にmoovが出力されますが、moovが先頭にある方が、シーク等に有利なので通常はMP4Boxあたりを利用して、入れ替えます。
例:Flashで再生させるとき、moovが後にあると、全部読み込まないと再生が始まりませんが、moovが前にある場合はすぐに再生が始まります。

iPhoneでmp4にアクセスした場合は、はじめにmoovのデータをダウンロードしようとします。それが完了すると、再生がはじまります。なお、3Gの状態でこのmoovの部分が基底時間以内にDLできない場合timeoutになります。サーバーが原因のtimeoutかクライアントが原因のtimeoutかはまだわかっていません。


続いてHttpLiveStreaming(略してHLS)
こちらはmpegをベースにしたストリーミング規格です。内部データとしては、mp3やmpegtsが対応しています。以前mp4でつくろうとしたことがありますが、mp4では残念ながら無理でした。
こちらはmpegデータを複数のファイルに分割し、それを順番にDLしては再生させることで動作します。また、その複数ファイルを管理するindexファイルが必要になります。

index用のファイルがm3u8ファイル、実データは*.tsもしくは*.mp3となります。
appleの公式ではたしか、30秒ごとに区切ったデータがよいみたいなことが書いてあったかと思うのですが、別に1秒で区切ってもOKです。
ライブストリームを配信する場合にはなるべく短くした方がよりリアルタイムになりますが、逆にデータの複合に失敗しやすくなるという問題もあります。(僕がつくったjsegmenterとかの分割プログラムでは、keyFrameに注意してそのあたり対処してあります。)

mp4のmoovと違い、m3u8ファイルは非常にサイズが小さいので、再生開始時のオーバーヘッドが小さく、3Gでもさくさくっと再生開始ができるという利点があります。


で、どの両者の違いについて・・・特にmusicTubeをやっているのでaudioの場合の違いについて。です。
1:Flashでの再生
mp4は再生できて、hlsは再生できない。
単に対応していないだけです。ただし、iOSアプリをAirから起こした場合は再生可能になります。
2:iPhoneでの再生
aacベースのmp4は普通に再生できます。aacのmpegtsベースのhlsは再生できません。
mp3ベースのhlsは再生できました。もちろんaudio扱いになるので、両者とも、別のアプリに移動しても流しっぱなしにできます。
前は確かaacのmpegtsベースのhls再生可能だったはずです。動画のBGM再生つぶされたときについでにつぶされたのかもしれません。
確認したブラウザはchrome Mercury Sleipnir Safariについて調べてあります。operaはたぶん、safariで再生させられることになると思います。
3:androidでの再生
nexus7のchrome, opera, firefoxで調べてみました。
まずmp4
chrome、OK
opera、DLになる。
firefox、OK
続いてHLS
chrome、OKだが、accのmpegtsベースのみ、かつ再生中に別のアプリには移動できない。mp3ベースはエラーになる。
operaとfirefoxはm3u8ファイルのDLになる。
4:3G状態での再生について(iPhone)
 mp4は始めのmoovのDLがきついときがある、ただし、それがDLできてしまえばメモリー上に乗るので、その後はメディアデータに専念できる。
よって、wifi状態でmoovだけ落としてからでかければ比較的快適に視聴できる。
HLSより先読みがしっかりされるので、地下鉄とか乗っていてもつっかかりにくい感じがする。
 HLSは開始時の動作がスムーズ。ただし、内部の分割データにも余計な情報が乗っているのであとで落とすデータが少々大きめ。(あまり違いないけど。)
ファイルベースで先読みするのでmp4より先読みは弱め。(mp4は細かくDL、hlsは大雑把にDL)よって地下鉄等でDLがとまると、つっかかることあり。

youtubeにiphoneでアクセスするとmp4で落ちてくるのでmusicTubeのサーバーサイドアプリでは、iphoneとyoutubeの間に入ってproxyサーバーとしてデータの転送をしつつmp4から映像データを取り去ることで、audioデータとして動作し、bgmとして再生できるようにしています。
で、いま最大の不満は外出中の3Gの状態でトラック遷移がうまくいかなかったり、長いデータの再生ができないことがある・・・ということです。
hlsにすると再生のオーバーヘッドが減りますからね。一度試してみたいんですが調査した結果mp3onlyになってしまったのでちょっと難しい話になってきました。
どうするかなぁ・・・

2013年4月11日木曜日

htmlの状態を簡単に確認するbookmarkletつくってみた。

別件でwebsocket経由で見ているページの内容をdumpするプログラムを書いたので、個人的にはそっちで事足りるのですが、iphoneでページをみているときに、ページのhtmlがどうなっているのか、確認したいことがあるので、調べることができるようにするbookmarkletを書いてみました。


javascript:var d=document,s=d.createElement('script');s.src="http://taktodtools.appspot.com/idump.js";d.body.appendChild(s);void(0);


正直websocketで動作するPCとかでdumpと実行ができるプログラムの方が便利。
ただ、自分のwebsocketサーバーがないと動作できないので、なかなか公開できないため、とりあえずつくってみたのが今回のbookmarkletとなります。


2013年4月4日木曜日

jettyサーバーでwebsocket動作を組んでみました。

会社でやろうとおもったので、mavenとjettyについていろいろと学習しています。
でいろいろとハマりました。
今回はそのはまった話。

https://github.com/taktod/websocketJetty/commits/master

例によってgithubにあげています。
今回はmaven2をつかってつくっていますので、動作確認したければ、websocketJettyのディレクトリでmvn jetty:run
を実行してもらえればjettyサーバーが勝手に起動して、確認できるかと思います。

mvn経由でjettyを実行する場合、webアプリケーションのrootは/になります。
よって、昨日の時点では、web.xmlの記述で設定しているpathをsocket/にしてあります。

で、jettyのtarballをダウンロードしてきて展開した場合の動作ですが、
warファイルの名前がrootになります。
コンパイル時には、websocketという名前で実行しているので、rootがwebsocket/になります。
実験はしていませんが、元のプログラムのままだと、pathにsocketをいれてあるので
websocket/socket/でアクセスしないとservletのクラスにコネクトしないことになります。

ところが、web.xmlに設定した値でいけると思い込んでしまったため、socket/でずっとアクセステストをしていました。
で、うごかなかったというわけです。

さて、ハマっていた状態だったわけでそこから脱出できた理由について書いておきます。
jettyサーバー8.1.10を利用しているのですが、通常モードでつかっているとログがなにもでてきませんが、ログレベルをDEBUGに変更すると、詳細動作ログがでてきます。
普通にjettyを起動する場合は
$ java -jar start.jar
というコマンドになりますが、ログの詳細を出したい場合は
$ java -Dorg.eclipse.jetty.LEVEL=DEBUG -jar start.jar
という形で出すことができます。
この状態でログを出したところ


2013-04-04 21:29:37.650:DBUG:oeji.nio:created SCEP@5d295c59{l(/0:0:0:0:0:0:0:1:50573)<->r(/0:0:0:0:0:0:0:1:8080),d=false,open=true,ishut=false,oshut=false,rb=false,wb=false,w=true,i=0}-{AsyncHttpConnection@466e06d7,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-14,l=0,c=0},r=0}
2013-04-04 21:29:37.653:DBUG:oejh.HttpParser:filled 303/303
2013-04-04 21:29:37.659:DBUG:oejs.Server:REQUEST /socket/ on AsyncHttpConnection@466e06d7,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-5,l=22,c=0},r=1
2013-04-04 21:29:37.659:DBUG:oejsh.ContextHandler:scope null||/sockettt/ @ o.e.j.w.WebAppContext{/,null},/Users/todatakahiko/Downloads/jetty-distribution-8.1.10.v20130312/webapps/test.war

こんなログがでてきました。
追加しているwarファイルがwebsocket.warなのに、参照しているのがtest.warになっています。
このおかげで、見ているservletプログラムが自作したものと違う状態になっているというのに気づけました。

ちなみに、http://www.websocket.org/echo.htmlこちらのwebsocketのechoアクセステストのページで実行テストをするとエラーだった場合に以下のようなログがでてきます。

あーそれにしてもなんとかなってよかった・・・
mvn jetty:runのときとアクセスパスがかわるのは、musicTubeM4aのyoutubeのmp4のコンバートプロキシ書いたときにわかっていたのにハマるとは・・・

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にあげてますが、細かい微妙な部分での差異等は伝えきれていないと思っています。
 その辺りどういう情報が必要なのかわかりましたら、ブログの記事にしてどんどん情報公開していきたいですね。