2013年1月31日木曜日

mp4から映像データをoffにする。

youtubeのデータがmp4であることが(たぶん)確定したので、映像データを取り去りたいとおもいます。

まず、大前提として、mp4データなんですが次のような書式になっています。
1:データはボックスという形のまとまりになっている。
2:ボックスは子ボックスを内部に持つことができるが、それはボックスのタイプ次第
3:ボックスの形式は次のとおり。
[4バイト(ボックスのデータ量)][4バイト(ヘッダ文字列)][内容]
となっています。

で、大まかな構成ですが・・・
[ftyp]
[moov]
[mdat]
これらが一番ベースとなるボックス。
でmoovはコーデック情報として、trakという子ボックスを持っています。

さて、ここから映像データを取り去るかというと、映像用のtrakデータをoffにしてやればいいみたいです。とりあえず動作しました。
で、offのやり方。
[ftyp]
[moov]
  [trak(映像用)]
  [trak(音声用)]
[mdat]
という形になっていたので、映像用のtrakをfreeに書き換えます。するとあら不思議、映像用のtrakの内容がそっくりそのままskip用の除外データになっちゃいます。


[ftyp]
[moov]
  [free(映像用だがキャンセル)]
  [trak(音声用)]
[mdat]

で、再生してみたところ、音楽のみのデータになりました。
もちろんiOS6.1(iPhone4S)で、バックグラウンド音楽再生もできました。
シーク等もばっちり。

あとはyoutubeのデータをプロキシするサーバーをぱぱっと書けば、なんとかなりそうです。

youtubeのデータについて調べる。

youtubeをBGMにして流したい。でもiOS6.1でできなくなった・・・

というわけで、どうやればいいか調べます。

まず、youtubeでつかっているデータについて調べます。
とりあえず、iPhoneとiMacのsafariを接続して、データアクセスについて調べました。


http://r11---sn-3pm7en7k.c.youtube.com/videoplayback?id=01e3e19fd.....
というURLであることがわかりました。
以前の調査から、youtubeのアドレスは一度アクセス可になった場合、同じIPアドレスからのアクセスは許可されることになるみたいです。

というわけで、同じLANネットワークにあるところからこのURLをたたいてみます。
手持ちのiMacにはwgetコマンドがはいっていないみたいだったので、ぱぱっとjavaのプログラムを書いてみました。

ものすごい乱暴ですが、こんなコード

String url = "http://r11---sn-3pm7en7k.c.youtube.com/videoplayback?id=01e3e19fd......youtube_mobile";
URL urlObj;
HttpURLConnection urlCon;
urlObj = new URL(url);
urlCon = (HttpURLConnection)urlObj.openConnection();
urlCon.setRequestMethod("GET");
ReadableByteChannel rbc = Channels.newChannel(urlCon.getInputStream());
FileChannel fc = new FileOutputStream("test.mp4").getChannel();
while(true) {
  ByteBuffer buffer = ByteBuffer.allocate(65536);
  rbc.read(buffer);
  buffer.flip();
  if(buffer.remaining() == 0) {
    break;
  }
  fc.write(buffer);
}
fc.close();
rbc.close();
urlCon.disconnect();

これでとりいそぎ、test.mp4が取得でき、vlcで再生してみたところ、問題なく再生できました。

内容を確認してみたところ、やっぱりmp4でしたね。
Video: mpeg4 (Simple Profile) 映像データがh.264じゃなかったのが少々気になりますが・・・

一応、万一httpLiveStreamingでiPhoneがやりとりしていたら困るので(そっちをとった方が音声のみにできる公算がたかかった)、wiresharkで確認してみましたが、やっぱりmp4のようでした。

ここまでで、youtubeのデータはmp4であることがほぼ決定的になりました。


HttpLiveStreamingとかの話(iOS6.1でのバックグラウンド音楽再生について)

iOS6.1になって、safari標準搭載のquickTimeで動画をBGMとして再生しつつ、他のアプリをいじるということができなくなりました。

困るんですが・・・

で、なんとかしてくて、いろいろ調べてみました。

まずBGM再生になる条件について調べてみました。
ためしたこと。
・mp3の場合→ok
・m4aの場合→ok
・mp3準拠のhttpLiveStreamingの場合→ok
・mp4の場合→ng
・h.264 + aac or mp3のhttpLiveStreamingの場合→ng
・aac準拠のhttpLiveStreamingの場合→ok
とこんな感じになりました。

これだけみると、コーデックに動画コーデックがはいっているデータならアウトみたいですね。


で、仕事しながらyoutubeを聴きたかった僕としては、httpLiveStreamingから動画データを取り去って動作するようなプロキシを書いてやろうとおもっていたわけです。
が、youtubeのデータを確認したところ、どうやらmp4でやってるみたいですね。rangeリクエストが飛んでましたし、先頭データにftypがきたので、あーmp4だってわかりました。(バイナリを見てみただけで判定つくところがすばらしいですね。)

で、結局やらなかったmpegtsから動画コーデックを排除する動作ですが、たぶん次のようにすればできると思います。
1:mpegtsのヘッダ情報から、動画データのtrackIDを取得します。
2:PMTから動画のトラックデータを排除する。
3:PCRのtrackIDを確認しておく。たぶん、動画データのtrackIDになってる。
4:3でもし、音声データがPCRになっているなら、動画データの188バイトごとのパケットをすべて削除してしまえば、たぶんOK
5:3でもし、PCRが動画データになっている場合(たぶんたいていのデータでは、PCRは動画データ)、音声パケット(PCRデータのみで実体はなし)をねつ造してやって、はさみこめば出来上がり

となる予定でした。

ただ、非常に残念なことに、youtubeでは動画データがmp4だったので、しばらくmp4から動画データを排除する方法について調べたいと思います。

とりあえず、BGMとして再生できるようにしたい場合は、以前つくったjsegmenterをつかって、m4aやmp3のデータをつくってから、分割してやってHttpLiveStreamingをつくってもらえれば、とりあえずは、BGMとして流しつつ他のアプリをいじれることはわかりました。

2013年1月30日水曜日

flvのmetaデータについてちょっと調査しておく。

flvにはmetaデータというのがあります。0x12からはじまるタグですね。
だいたいは次のような形式になっています。

1バイト データタイプ 0x12固定
3バイト タグサイズ
4バイト timestamp
3バイト トラック番号(0固定)
以降タグデータ
4バイト 終端データ
という形になっています。

で、タグデータの内容ですが、どうやら以下のようになっているみたいですね。

数値
[00] [8バイト] 8バイトのデータの部分はDoubleで書き込むみたいです。
JavaでやるならDoublteToLongBits(数値)で取り出せてlong(javaでは8バイト)で書き込めばOK

Boolean値
[01] [1バイト] 0x01でtrue 0x00でfalse

String値
[02] [2バイト] [データ] 2バイトの部分がshortの形での文字列長、データは単に文字列
日本語が扱えるかは不明

Object型
[03] [文字列の内容データ] [別の要素データ] [文字列の内容データ] [別の要素データ]...
最後は00 00 09でおわる。
という形[文字列の内容データ]という部分は[2バイト] [データ]の塊のこと。

NULL型
[05] これだけ

UNDEFINED型
[06]が予約されている不明

MAP型
[08] [4バイト] [文字列の内容データ] [別の要素データ] の繰り返し Object型の中身と同じ
終わりが00 00 09でおわるところも同じ
始めの4バイトは数値要素数がはいっているのだろうか?Flazrでは0でうめているみたい。


ARRAY型
[0A] [4バイト] [別の要素のデータ]... 4バイトの部分は要素数

DATE型
[0B] [8バイト] [2バイト] 8バイトの部分はunixミリ秒をDoubleのbyte配列にしたもの。
うしろの2バイトは0でうめるみたい。(後ろ2バイトはtimezone?)

LONG_STRING
[0C] [4バイト] [データ] stringと同じだが、長さ指定が4バイトになってる。

UNSUPPORTED
[0D] 不明

で、実際の構成は以下みたいです。
まずonMetaDataという文字列が入ります。
そこに続いてMap型が挿入されます。Map型の中身は上記のデータに従います。
たとえばdurationなら
「00 08 64 75 72 61 74 69 6F 6E」 8バイトでdurationという文字列(key)が入り
「00 40 60 74 39 58 10 62 4E」先頭に0x00があるのでNumber型 うしろの8バイトより131.632という数値であることがわかります。

こんな感じでかかれているみたいですね。

機会があったらCuePointとかKeyFramesついても調査しておきたいところですね。
http://www.buraks.com/captionate/helpfile/e05.html
http://ismano.com/media/documentation/flvmdi.html
この2つがkeyframesの参考になるかな?
どうやら先頭に余計なメッセージをいれることで、シーク可能にするみたいですね。

たしかs3って指定した場所からのデータダウンロードをサポートしているので、keyframe情報をきちんといれておけば
s3からflvの先頭をDL、その後必要なrangeのデータをDLして中途再生とかできそうな予感です。

では、metaデータはこのくらいにして、takStreamingの続きをやるとしよう。

2013年1月29日火曜日

Flashによるh.264出力についてのメモ

FlashPlayer11以降でしたか、Flash単体でh.264のデータ出力ができるようになりました。
で、以前webで見つけた。
これを私は重宝しています。
前にもブログに書いたことあったはずです。

で、ですが、このswfプレーヤー、なぜかTakStreamingとの相性がすこぶるわるかったんです。
再生してもうまく動作しなかったり、いろいろと不具合があったわけですが、やっと理由がわかりました。
たぶん、FlashPlayer11以降によるh.264の映像出力全般にいえることだと思います。

1つ目、h.264のmediaSequenceHeaderがキーフレームごとにくる。
mediaSequenceHeaderというのは、h.264やaacのデータの特殊なflvTagです。詳しくは学習がすすんでいないのですが、mediaに関する特別な情報が載っているみたいです。HttpTakStreamingではこのデータを伝搬するために、flhファイルに必要があれば、追記してあります。FlashMediaLiveEncoderで放送した場合や、Flazrをつかってflvデータからライブストリーミングをした場合には、かならず先頭にtimestamp0でやってくるのですが、Flashのh.264放送では、キーフレームごとに送信されるみたいです。

いままでのtakFlazrのプログラムでは、mediaSequenceHeaderはうけとったまま保持していました。これだと、このheaderがはいっている場合にtimestampが0にならず、複合したときに矛盾がでるみたいです。

2つ目、放送をあきらめるタイミングが早すぎる。
TranscodeWriterの中で、ある程度データ転送がない場合に、動作していないと判定して、とめてやり直す処理があります。
このソースの80行目あたりです。
このあきらめる処理、現状の動作では必要ありません。が、いれたままにしてあります。

どうして入っているかというと、ffmpegやxuggleにつないでコンバートさせるとき、コンバートするのをあきらめるタイミングがわからないため、こうなっています。

いままでは、メディアデータの書き込みが1.5秒ない場合は、動作がおかしくなったと判定して、あきらめるようにしていたのですが、Flashからのflvの出力はこの部分が少々あやしくなっていると思ったので、とりいそぎ3秒にあげてみました。

というわけで、メディアデータの取り扱いを見直すことで、また1つ動作が安定したと思います。

2013年1月28日月曜日

p2pを利用したライブストリームの動画をつくって公開してみました。

FlashMediaLiveEncoderを利用してrtmpに配信したデータをrtmfpのp2pを利用したネットワークに流すプログラムがきちんと動作するようになったので、動画を撮ってyoutubeに置いてみました。

動作のおさらいは次のような感じです。
動画は以下
構成は
データサーバーには、Sakuraのvpsを利用
FlashMediaLiveEncoder3.2で配信
Red5のpublisherのデモがrtmpの配信データ確認
PC上でslave.htmlのプログラムを開いて始めにhttpTakStreamingを実行
そのあとでrtmfpを利用したp2pのstreaming混在もためしています。

いいところ
・FlexのプログラムはFactoryクラスからデータを抜き出すだけなので、非常に簡単。
・http通信がベースになっているので、冗長化が非常に簡単。
・rtmfpのネットワーク網を構築することで、可能であればp2pによるデータ通信による補助を行う。
・ネットワーク状態がかわってもシームレスにつながって動作する。

わるいところ
・FlashPlayerにかかる負担が少々大きい。
・いまのところ、globalネットワークでのrtmfpの通信に成功していない
(たぶん、僕のルーターの設定に不備があるんだと思う)

今後やること
・任意のrtmp配信ツールで動作確認できるデモをつくって公開する。
・PC上にあるflvファイルをベースにしたtakStreamingの生成とrtmfpでの共有動作
(動画をどこにもアップロードすることなく他のユーザーと共有できるようにするつもり)
・httpではなくrtmpベースの動作
(httpのダウンロードファイルがうっとおしいのと、timerによるファイル監視動作が良くないのでなんとかする。
もちろんrtmfpの動作時の欠損補完もできるように、また、rtmpの動作補完をhttpに背負わせることも可能にする。)
・crcによる動画データの整合性確認処理の追加
(現状の動作では、rtmfpとhttpのソース動画が一致していることを前提にして動作していますので、他の動画データが混入したらたぶんおかしくなる。)

あと、できたら手伝ってくださる方、募集したいですね。

興味ある方おられましたら、このブログのコメントなり、twitterなりでコンタクトよろしくおねがいします。

2013年1月27日日曜日

segment分割のやり方について

httpTakStreamingで利用しているsegment分割ですが、ちょっと動作をかえようと思っています。

現在の動作では、設定秒数以上で、動画のキーフレームがきた時点で分割するように調整してあります。
これを、キーフレームでなくても、指定時間以上経ったら分割する方向ですすめようと思います。

現状の動作では、たまにsegmentの長さが長くなっちゃいます。
上図でも1.0程度のセグメントもあれば、2秒超えているものもあります。
実験ではカメラとの相性もありますが、ひどいときには、4秒近いセグメントになることもありました。
サーバー内部での設定では、durationを1秒にしてあるので、4秒近いセグメントができているということは、4秒間動画のkeyframeがこなかったということになります。
このままでは、追いつかなくなった場合に、動画再生がとまってしまうという不具合が発生しまうので、flmデータの生成時に、keyFrameがこなくても指定してある以上の動画フレームを取得した場合に分割するように調整してみたいとおもいます。

さて、なぜkeyFrameが先頭にくるようにしているかというと、HttpLiveStreamingがベースだからです。
httpLiveStreamingの場合、先頭にキーフレームがこない場合は、次のようになるみたいです。(デバイス依存なので、確実にそうなるわけではないといっておきます。)

1:動画データをうけとった場合、映像が再生可能になるまで音声だけ流れます。
2:その後、映像が再生できるようになったら、動画として再生されます。

という動作になることがあります。
単に映像を見せるだけのサービスならまぁいいんですが、ここにお金が絡んでくると映像がみれないのに、お金を取られたという話が入ってきちゃうんですよね。
というわけで、確実に動画として始めから再生させるために、いろいろ調べるうちに、keyFrameを強制的に先頭になるようにするというのが、デフォルトになっていました。

さて、今回のHttpTakStreamingですが、flvデータの根幹的な部分の解釈を行う、BaseStream.asの中で、動画のkeyFrameを取得するまで、すべてのデータを捨てるようにしてあります。
という動作をするようにしてあるので、問題のあるデータがあったとしても、まぁ大丈夫な動作になるであろうということが期待できます。


で、これを書いている間にFlazrのプログラムをちょっと書き換えてみました。
で、実際にためしてみたところ次のようになりました。

ほぼすべてのデータが1.055秒のセグメントになっていますね。
動作もBuffer.length=2にしたときとほぼ同じ動作になった感じになりました。

めでたしめでたしといったところ。