2013年11月28日木曜日

javaをつかったコンバートプログラムを書いていました。

表題どおり、javaで動くメディアデータの変換プログラムを書いていました。
といっても、pureJavaで書いているわけではなく、ffmpegとかつかって変換させるというものです。

概念的には次の図のようなやつです。

動画のデータのUnitを入れると、変換した後のデータがでてくるというもの。
xuggleを使う場合はこんな感じ。テストコード
avconv(ffmpeg)を使う場合はこんな感じ。テストコード
となりました。

やってることは同じで
1:TranscodeManagerを宣言。
2:MediaUnitを解釈するためのプログラム設置
xuggleならpacketizer、ffmpegならdeunitizerとunitizer
3:TrackManagerをTranscodeManagerに作らせる
4:TrackListenerをくっつけて出力を拾わせる。
となってます。

これをうまくつかって、高速、安定、字幕も使えて、複数トラック出力もできる、ライブ用のHttpLiveStreaming、はやいところつくってみたいですね。

2013年11月18日月曜日

jsegmenterのプログラム作り直しました。

最近myLib.chunk*というライブラリをつくりました。
というのも、いままでのmyLib.packetのmpegtsの切り方が気に入らなかったからです。

今日はその説明
ffmpegでmpegtsを出力すると音声の分割がbyte量単位になります。
よって無音部や音が弱い部分があると、音声の塊の長さが長くなることがあります。
また、音声と映像のデータがきちんと同期しているわけではありません。

概念図を書いてみます。
まずは定義、映像のキーフレーム、中間フレーム、音声のフレームの3つがあります。

本来のデータは次のようになります。

ファイルに格納されるデータ
これがffmpegが出力するファイルだと次のような感じになっています。(mpegtsの場合)

いままでの僕のプログラム(myLib.packet)での分割だと、単にキーフレームの位置のみをみて、分割しているので、次のようになっていました。
キーフレームをみつけたら、時間を確認して、必要な時間分経っていたらファイルを分割するという感じ。
これだと、1つ目は音声がない映像だけ、
2つ目は音声も映像もあるけど、映像のデータよりだいぶ前の音声データがはいっている形になっています。
実際によく使うマリオのプロモ動画ははじめの方で分割後のデータが音声抜けていたりしていました。

で、今回はこうした。(myLib.chunk)

これで1つ1つの分割単位が、その単位の再生に必要な動画の1セットと対応する音声データを保持する形になっています。
なお、この概念図は時間の長さのみを考慮しているので、実際の出力ファイルのサイズは乱高下します。

プログラム
https://github.com/taktod/jsegmenter/tree/new
利用しているライブラリは
https://github.com/taktod/myLib

一応型変換できるようになったので、flvやmp4、mkvのデータからコーデックさえ合えばHLS形式のデータ分割できるようにしたいですね。
一応下地はすでにつくってあるけど。

2013年11月16日土曜日

11/16すべきこと

誰かに見られてないとダレそうなのでブログに書いてみる。

とりあえずしたいこと。
・HLSのwebvttによるトラック動作の動作確認。
・rtmpのDL→xuggle変換→ULのプログラムをとりあえず完成させてどのくらいの早さがでるか見てみたい。
・HLSのシステムを作りたい。

とりあえずこの3つやりたい。
1つ目は会社での作業がかかわってくるのでやっときたいところ。
2、3は単なる趣味。まぁ、うまく動作したら会社でつかってもいいけど。

で、1つ目だ。
iOS6の後期からHttpLiveStreamingでwebvttを使った字幕動作が対応になっています。
具体的にiOS6の状態で動作するのを確認したことがないので個人的にはiOS7から対応と見ています。

わかっていること
サンプル:http://49.212.39.17/mario/index.m3u8

■レベル1
・iOSの古いバージョンでも動作可能。ただし字幕は見れない。
・日本語等も使える。
・m3u8を複数組み合わせた状態でしか実行できない。
上記のサンプルでは、index.m3u8がsubPlaylist.m3u8とmario.m3u8を内包している形になっている。

■レベル2(つかってみてわかったこと)
・字幕トラックの時間がかぶるのはあまりよろしくない。
00:00:07.500 --> 00:00:08.500 line:100% align:left position:0%
hello

00:00:08.000 --> 00:00:08.500 line:100% align:left position:0%

こんにちは

こういう字幕をつくると、互いの字幕が干渉する。

00:00:07.500 --> 00:00:08.000 line:100% align:left position:0%
hello

00:00:08.000 --> 00:00:08.500 line:100% align:left position:0%
hello

こんにちは

こうやった方が綺麗に字幕がでる。
干渉しすぎると字幕が極端に小さくなって見えなくなったりする。

・内部m3u8でエラーが発生したら動画が止まる。(一度でもm3u8を応答した場合)
→動画が読み込めませんでした。とでて前のページに戻されます。

・黒画面のまま固まる。(初出力の前に内部でredirectとかした場合)
→アクセス権限の問題でhttpのredirect等をやっているときで内部のm3u8でredirectやってしまうと、そのredirectは実行されますが、動画ではないのでそのまま表示待ちになります。

■知りたいこと
・字幕の出現時間をmpegtsのpcrに合わせるといい感じに動作するけど、どうやら
EXT-X-DISCONTINUITYを挟んで別の動画に切り替えると時間の概念がおかしくなるらしい。
字幕側もいったん切らないとだめなのか、pcrの時間ベースではなく、経過時間で時間を出力をしないとだめなのかがわからないところ。

・mpegtsのpcrはやく1日流し続けるとデータがoverflowしてカウンターが0にもどります。
この現象が発生したときにvttの記述をどうすべきか不明な点。

このあたり知っておきたい。
調査してなにかわかったら、この記事に追記しようと思う。

さぁ、今日もがんばろう。

■で、実際にやったこと。
とりあえず、rtypeDeltaのデータをmpegts化して動作させて、別トラックとして利用しようとおもいました。
で、avconvで変換しようとおもったら、動作しない・・・
仕方ないので、vlcで変換させてみました。aacに直接できなかったので、ac3にしてみた。(ac3のmpegts作成ははじめて)
変換してみたら、いつもと違うmpegtsができました。
違う点1:patやpmtのデータのデータがちょっと違う。
左がvlc出力、右がffmpeg出力(avconv)
vlcはヘッダ + adaptationField + PATデータになってますが
ffmpegはヘッダ + PATデータ + 埋めとなってます。

違う点2:pmtデータに見慣れないdescriptorがありました。
存在したのは、RegistrationDescriptorとISO639LanguageDescriptorの2つ
とりあえず解説しているサイトを参考に読み込みできるように調整。

違う点3:始めのデータが音声のみのpmtを出力していました。
途中から音声 + 映像のpmtに変わってました。
[このあたりは、vlcが再生データを変換していることに起因している気がします。(これは想像)]
このままだと、ffmpegにまわしたときに音声しか変換に拾われませんでした。
先頭部分の音声のみの部分を削ぎ落としたファイルをつくったら、音声 + 映像のデータとffmpegにも認識されました。

で、ライブラリも書き換え。
https://github.com/taktod/myLib
きちんと動作するようにしました。

本題に入る前に分割用のtsファイルを作るのに時間かかっちゃったw

■途中経過2
その1のhttpLiveStreamingの字幕入りのときに、中途で動画データが切り替わったらどうすればよい?というのに回答がでました。
(まぁ動作させた上での実験結果ですけど)

結論としては、字幕側のm3u8にも#EXT-X-DISCONTINUITYをいれること
字幕のtimestampも始めからやり直しにすること
この2点できればいけるみたいです。

サンプル:
字幕付きのストリーム
http://49.212.39.17/m3u8/index.m3u8
字幕のストリーム(中途切り替えあり)
http://49.212.39.17/m3u8/subPlaylist.m3u8
映像のみのストリーム(中途切り替えあり)
http://49.212.39.17/m3u8/movie.m3u8
マリオのデータ
http://49.212.39.17/m3u8/mario.m3u8
rtypeのデータ
http://49.212.39.17/m3u8/rtype.m3u8

字幕のやり方:
再生する。
右下にある字幕ボタンを押してcommentを選択
シークバーを巻き戻して視聴する
すると左下に秒数カウントがでてきます。

iPadの場合はフルスクリーンにしてからやらないと字幕選択ボタンがでてきません。

端末による動作確認結果:
iOS7のiphone4S、問題なし。
Nexus7 android、映像はみれるけど、字幕は選択できず。シークしようとするとこの動画を再生できませんとでてくる。

備考:EXT-X-ENDLISTをいれると強制的に始めからにできるけど、VODではなくライブ用の動作検証なので、今回はあえていれてません。

というわけで#EXT-X-DISCONTINUITYを適当に挿入してやればよかったという落ちでした。
よかったよかった。

2013年11月2日土曜日

hlsのbyteRangeアクセスが気になったのでちょっとつくってみた。

今日書いた記事
http://poepoemix.blogspot.jp/2013/11/hlsmpegts.html

これを書いているときに、そういえばbyteRangeアクセスのやつつくったことないなとおもったので、ちょっと作ってみた。

mp4バージョン
http://49.212.39.17/rtype.mp4
hlsバージョン
http://49.212.39.17/rtype.m3u8

内容は同じです。
資料によるとiOS5以降なら対応らしいですね。

どうなんだろう・・・ちょっと早いな程度ですかね。

ちなみに作り方。
1:hls分割をavconvにやらせる。

$ avconv -i rtype.mp4 -acodec copy -vcodec copy -bsf h264_mp4toannexb -start_number 1 -f hls -hls_time 10 rtype.m3u8

2:rtype0.ts、rtype1.ts・・・とデータが326個できたので、全部のdurationとサイズを確認するし、m3u8ファイルを作成する。
$ avconv -i rtype0.ts
$ ls -l

3:分割したファイルを結合する。
$ cat rtype0.ts > rtype.ts
$ cat rtype1.ts >> rtype.ts
$ cat rtype2.ts >> rtype.ts
・・・
めんどくさかったので2と3の処理はプログラムを書いてさくっと終わらせました。

4:適当なサーバーにデータをアップロードすれば出来上がり。

ではでは

hls用のmpegts作成プログラムが進んだのでメモしておきます。

はじめにぶっちゃけて置きますが、最近はffmpegが優秀になっているので、面倒なことしなくてもよかった気がします。
まぁ、勉強になって楽しかったですが・・・

---- まずは基本 ----

hls(HttpLiveStreaming)がなにか?
 appleのiPhone等、iOSでバイスで大きめの動画を再生させる場合、もしくはライブ映像を流す場合、httpLiveStreamingという方式を使うのが推奨されてます。
 一応mp4でも再生は可能なのですがmp4の場合はファイルの先頭部にmoovという再生時の位置情報やキーフレームがどのようにはいっているか等、いろいろな情報がいっきに詰まっています。1時間の動画とかになった場合は、これが大きすぎてダウンロードがうまくいかなかったり、動作しても再生までに時間がかかったりします。
 hlsを使うとテキストでできた再生用のリストファイルと再生の実体動画がわかれているので、必要な動画データからダウンロードさせることでサクッと動作します。
 最近では、androidやWiiUにも実装されている規格となります。

利点は?
 まずhttpのやり取りだけで成立するので、たいていの環境で動作します。
 サイズの大きいデータの場合は再生まで速いです。
 ライブ映像も扱うことが可能です。
 必要に応じて転送サイズを変更することも可能です。
 iOS6以降の場合はsubtitleを挿入したりすることも可能です。
欠点は?
 mpegtsベースなので転送データサイズが増えます。
 基本動作では、ファイルが大量にできるので、管理が大変です。

どうやって使うの?
 VODの場合はVLCプレーヤーでコンバートできたはずです。
 avconvを使う場合は出力タイプをhls、出力ファイル拡張子をm3u8にすれば作成されます。
例:$ avconv -i mario.flv -acodec copy -vcodec copy -f hls output.m3u8
として実行した場合、output[n].tsが大量にできて、output.m3u8が生成されます。
(与太話:最近のプログラムは優秀ですね。以前はrtmpdump + ffmpeg + segmenterとか組み合わせてつくっていました。精度もよくなかった・・・)

---- 続いて変わった使い方 ----

ファイルが大量にできるのはヤダ
 そういう人向けに1つのmpegtsファイルの部分アクセスを実行することで動作させることも可能になっています。
0byte〜
500byte〜
1000byte〜
みたいなアクセスを実行します。
httpの206アクセスが可能なサーバーでかつ、そこそこあたらしいiOS端末の場合は動作可能になります。
詳しくは仕様をみてください。

ライブを扱いたい
 ffmpegやavconvはpipelineでデータの入力を受け付けることが可能なので
rtmpdump -> ffmpegで出力したデータにhttp経由でアクセスする
みたいなことを実施すればライブ映像を扱うことができます。

回線速度に合わせて映像のクオリティーを前後させたい
 appleの提供しているsegmentCreatorを使えば確かできるはずです。が、使ったことはありません。
仕様によると
・重ねる映像の比率が一致すること(16:9と4:3をまぜるということは不可)
・音声データは一致すること
の2点を守ればOKとのことで、実際に作成したところ動作できました。
切り替えはシームレスで、あ、急に映像が悪くなった・・・というのがわかる感じでした。

subtitleをいれたい。
 本来はCEA608でしたっけ?mpegtsのデータにclosed captionを織り込む必要があったのですが、日本語は扱えませんでした。
ところがiOS6から(僕が確認したのはiOS7からですが・・・)webVttのm3u8をつくることで任意の言語のsubtitleを挿入することが可能になった模様です。
 詳しくはappleのドキュメントをみるか、僕にコンタクトとってください。そんなに難しいものではないです。
 なお、iphone4Sでデバッグしていますが、ちょっとバグがあるみたいです。

---- 本題 ----

さて、本題ですが最近このmpegtsの動作について研究していました。
というのもffmpegやavconvで吐くmpegtsデータの音声と映像の吐き方の部分で、いくつかの要件があったためです。
1:映像と音声の吐き方に関連性がないため、分割したときに、片方のデータが欠如することがあった。
2:分割したときに先頭に必要なトラック情報がなかったため、トラック情報のデータに当たるまで再生が開始されない問題があった。
3:nullパケットや無駄に挿入された追加情報があるためそれらのデータを削除したかった。

ちなみにできてからわかったのですが、1,2に関してはavconvにhls出力を実行させると解決したデータが吐き出されることがわかっています。

で、やってみてできたデータがこちらです。
http://49.212.39.17/mario2/index.m3u8
iphoneあたりでみてみると再生できます。

やっていることはデータを受け取ってからトラック情報、映像、音声にデータを分解
音声データは一番細かいフレームデータ(0.03秒ごと程度)に分解しておく。
映像のキーフレームからキーフレームの間隔にその間に配布べき音声データを適宜挿入しつつファイルの先頭にだけ、トラック情報をいれるようにしておく。

先頭にトラック情報と必要なキーフレームが必ずくるようになっているので、どこから再生してもスパット動作するという形になります。

---- 苦労した点 ----

mpegtsのフォーマット変換に苦労しました。
・まずpcrの取り扱いの問題、単にavconvに変換させると同期処理のためかpcrの値がtimestamp値よりちょっと遅れていることがあるみたいです。これを調整するのに苦労しました。
・bufferの問題。vlcとvlc互換のライブラリをつかっている場合ですが、音声用のpesが大きいと後ろの方のデータを無視するという仕様だかバグだかわからない動作がありました。
回避策として、中間フレームでも音声データ量が大きくなった場合は、ファイルに書き出すようにしました。
pesは60kByteほどのデータを持たせることが可能なんですが、4kByte強の粒度で挿入するようにしました。
なお、ffmpegではこの問題はない模様ですが、出力データの粒度は4kByte強になっているみたいです。
・pesのデータの取り扱いの資料がなかったので、いくつかパラメーターの意味を誤解していた部分があったところ。

---- で研究してなにしたいの? ----

まずはプログラムの精度をあげたい点
・ffmpegのrtmpdumpを利用している場合もしくは、rtmpdumpからpipeしてつかっている場合は、映像配信がunpublishされた場合にプロセスごと止まってしまう点がある件。
できたら、unpublishしたら待機しておいて、publish再開したときにすぐに動作再開してもらえばいい感じの動作になる。
・flashのrtmp配信では、soundLevelによる音声欠損や中途でのコーデック切り替えやサイズ切り替えが可能だが、きちんと対応したい。音声欠損は回復するまでコンバートがとまってしまう。映像切り替えはその後ffmpegがエラー吐いて動作しなくなる。

続いてちまたにあふれるmp4のデータをmpegtsで視聴できるようにするproxy servletを書きたい。
できたら高速アクセスできるようになるはず。

変換の高速化を図りたい。
・ffmpegで変換していると変換に3秒ほどかかる形になっているが、実は型に合わせるために、待っている時間が存在しているみたいです。実際にxuggleの変換部分だけ抜き出して動作させてやると変換はそこまで時間がかからない。
よって、自力でmpegts出力ができるようになっておくと高速コンバートが実装できる可能性が高い。

あとは、単なる趣味。