2011年6月29日水曜日

コネクト時の情報をしらべる。@red5

接続時にSWFプレーヤーから送信される情報について・・・です。

データはMapの形式でIConnectionのオブジェクトからgetConnectParamsのメソッドで取得できます。
{objectEncoding=0, app=liveHelper, fpad=false, flashVer=MAC 10,3,181,34, tcUrl=rtmp://localhost/liveHelper, audioCodecs=3191, videoFunction=1, pageUrl=http://localhost:5080/demos/publisher.html, path=liveHelper, capabilities=239, swfUrl=http://localhost:5080/demos/publisher.swf, videoCodecs=252} 
System.out.printlnで中身を取得してやるとこんな情報が・・・
特筆すべきところはrtmpのアドレスにqueryを追加してやると
rtmp://localhost/liveHelper?testといれるとtcUrl=rtmp://localhost/liveHelper?testとなる上にあらたな要素queryString=?testというのが追加されますね。(app指定がliveHelper?testにかわりました。)

仮にFlashMediaLiveEncoderで接続したらこんな感じ

{app=liveHelper, flashVer=FMLE/3.0 (compatible; FMSc/1.0), tcUrl=rtmp://localhost/liveHelper, path=liveHelper, type=nonprivate, swfUrl=rtmp://localhost/liveHelper}
flashVerのところにFMLE/3.0なんていう特殊なものいれることができるんですね。
compatible; FMSc/1.0と書いてあるところをみると、バージョンがあがるとRed5ではFMLEで放送できなくなるかもしれませんね。


とここでおわったら面白くないのでさらに一歩踏み込んでみたいとおもいます。
僕はRtmpClient信者でもあるわけですが、これでこのパラメーターを偽装できないか?と思った次第です。結論からいうと可能です。
Red5のソースコードorg.red5.server.net.rtmp/BaseRTMPClientHandler.javaの180行目あたりmakeDefaultConnectionParamsの中身を見てもらえればわかりますが
RtmpClient.connectの引数としてMap<String, Object>に適当なデータをいれて渡せばOKみたいですね。
DefaultConnectionParamsをみてみたらわかりますが、そのままですね。swfUrlとかも適当な文字に書き換えてしまえば自由自在のようです。

というわけで思い立ったら即実行してみたいと思います。
利用するのはおなじみ、red5_phpのソースコードより、com.ttProject.TestClass.javaを改変してテストしてみます。
   RtmpClientEx rcex = new RtmpClientEx();
   rcex.setListener(new RcexListener(rcex));
   Map params = rcex.makeDefaultConnectionParams("localhost", 1935, "liveHelper");
   params.put("test", "hogehoge");
   rcex.connect("localhost", 1935, params);



実行結果はこんな感じ

{app=liveHelper, objectEncoding=0, fpad=false, flashVer=WIN 9,0,124,2, tcUrl=rtmp://localhost:1935/liveHelper, audioCodecs=1639, test=hogehoge, pageUrl=null, videoFunction=1, path=liveHelper, capabilities=15, swfUrl=null, videoCodecs=252}
ちゃんとくわわってますね。

2011年6月27日月曜日

Red5のマルチスレッドが詰まる?対処その1

Red5というか、ApacheMinaのNioSocketAcceptorをつかったサーバーが、だいたい詰まるっぽい。

というわけで、問題点と対処方法その1(シリーズ化するかはまだわからないけど)
例としてxmlsocketサーバーのプログラムをあげてみます。

このプログラムはRed5のorg.red5.server.net.rtmpのTransportの部分を参考にして、書いてみました。
こちらのjavaファイルをみてもらえればわかりますが、Executorを2つ準備して、コネクト用とIO処理用にスレッドのpoolを割り当ててます。

Executor connectionExecutor = Executors.newFixedThreadPool(connectionThreads);
Executor ioExecutor = Executors.newFixedThreadPool(ioThreads);
acceptor = new NioSocketAcceptor(connectionExecutor, new NioProcessor(ioExecutor));
この状態で、IoHandler側にどのスレッドで動作しているか確認するために
そうですね・・・messageReceivedあたりにThread.currentThread().toString()でもいれて動作スレッド情報をぬきとると、だいたいすべて同じThreadをつかって動作しているようになります。
というわけでうまくマルチスレッド化していません。

どうやら、ApacheMinaのライブラリの内部でプロセスをわりあてる部分がうまく動作していないようです。
ではどうすればいいかというと、

acceptor = new NioSocketAcceptor(Runtime.getRuntime().availableProcessors());
このように、存在プロセス数をわたしてやると、接続した順に処理スレッドが割り当てられるようになります。
たとえばCPUが4つのサーバーなら・・・
1→2→3→4→1と順繰りに使われるようになります。
一応apacheMinaのソースコードをひもといてみると、どうやら数字をいれてやるとapache.minaのライブラリにある、SimpleIoProcessorPoolというものが内部で自動生成されているようです。
acceptor = new NioSocketAcceptor(new SimpleIoProcessorPool<NioSession>(NioProcessor.class, 4));
自分で書くとこんな感じになるらしい。(自分で書くのはおすすめしません、なぜならpoolを自分で破棄しないとだめだからです。数字をわたすだけなら、NioSocketAcceptorが自分で管理してくれます。)
Executorsから引き出せるデフォルトのスレッドプールじゃないところがにくいですね。

これで一応、接続ごとに利用するスレッドはばらばらにできるわけですが、贅沢をいうなら接続ごとではなく、処理ごとにThreadがばらばらになれば、一番効率がいいはずなんで、その2をかくとすればそこの対処を書きたいところです。

なお、Red5のorg.red5.server.net.rtmp.RTMPMinaTransport.javaの中の書き方はExecutorを指定するようになっていますので、どうしてもRed5の動作がかく付くなんとかしたいという人は、NioSocketAcceptorの使い方の部分を書き換えちゃってコンパイルしちゃうのもありかもしれません。
え、いや、保証はしませんよ?

2011年6月24日金曜日

UDPホールパンチング、その2

UDPホールパンチングをつかえばP2Pができる、ただしUDPのパケットはすべて確実に送信されるわけではない・・・
red5_phpのBroadcastStreamを改善するときに、H.264やVP6の動画データを扱う場合はヘッダ情報をRed5の内部データに登録しておかないと、再生がうまくできないという問題があったので、UDPでH.264やVP6の映像のやりとりをするときにヘッダ情報がきちんとおくれないと動作しないのでは?という懸念があります。
というわけでUDPホールパンチングではないけど、パケットロスを適当におりまぜてやった場合に動画がどうなるか・・・やってみました。

まずはソースコード
public class Application extends ApplicationAdapter{
   private BroadcastStream gstream;
   @Override
   public boolean appStart(IScope scope) {
      gstream = new BroadcastStream("error");
      gstream.setScope(scope);

      IProviderService providerService = (IProviderService)this.getContext().getBean(IProviderService.BEAN_NAME);
      if(providerService.registerBroadcastStream(scope, "error", gstream)) {
         IBroadcastScope bsScope = (BroadcastScope)providerService.getLiveProviderInput(scope, "error", true);
         bsScope.setAttribute(IBroadcastScope.STREAM_ATTRIBUTE, gstream);
      }
      gstream.start();
      return super.appStart(scope);
   }
   @Override
   public void streamBroadcastStart(IBroadcastStream stream) {
   stream.addStreamListener(new IStreamListener() {
      private boolean first = false;
      @Override
      public void packetReceived(IBroadcastStream stream, IStreamPacket packet) {
         if(packet instanceof IRTMPEvent) {
            if(!first) {
               first = true;
               return;
            }
            else {
               first = false;
            }
            gstream.dispatchEvent((IRTMPEvent)packet);
         }
      });
      super.streamBroadcastStart(stream);
   }
}
これとは別にred5_phpのリポジトリにいれてあるBroadcastStreamが必須。

やっていることはアプリが生成されたときに、errorという名前のストリームを作成する。
なんらかの放送がはじまったらストリームパケットをerrorのストリームに受け流す。
(ただしfirstのフラグで適度に邪魔してやる。)

結果は実際にやってみてみてもらえればいいと思いますが
FLV1の場合。普通にみれる動画になった。(音声は不明)
VP6の場合。ピンクや青の四角いのが大量にわいてきた。ffmpegのエンコードミスしたときみたいなやつ。静止画にするとそれなりの絵になる。
H.264の場合。よくわからんもやもやがついてきた。これもエンコ失敗したときにでてくるやつっぽい感じ。
VP6にしろH.264にしろ画像がでてこないという現象はとりあえずなかった。
前のプログラムを組んだときの手応えからすると、画像がうつらないという結果を期待したのだが・・・
ちょっと組んでためしただけなので、確定ではないですが、仮にUDPでP2Pの生放送をつくった場合高画質を狙うと回線の状態とかによると、めちゃくちゃな映像のやりとりになる可能性あり・・・みたいですね。

2011年6月11日土曜日

rtmfpをちょっといじってみました。

rtmfpというFlashのP2P実装をちょっとだけいじってみました。
まぁ結局断念したわけですが・・・

やったことは以下のとおり
・UDPサーバーを書く1935ポートをlistenする。
・接続されたら、アドベのラボのp2p.rtmfp.netに接続する。
・自作のサーバーが中継して、内部パケットをのぞき見る。
というプログラムを書きました。まぁぶっちゃけると専用ソフトを使えばパケットの中身が見れるのは知っています。

で、実際に実行するとどうなったかというと、
1:サーバー起動
2:Flexプログラムでつくったサーバーにrtmfp://(中継サーバー)/(devkey)に接続
ここでFlashからp2p.rtmfp.netに何らかのデータがおくられる。
3:p2p.rtmfp.netから応答がかえってくる。
でおわりでした。

以下今回の実験でわかったこと。
1:Flashはrtmfpの接続に失敗した場合、一定時間置きに再チャレンジを行う。
2:Flashからp2p.rtmfp.netに送っているデータにはdevキー情報がはいっているが、接続対象サーバー情報ははいっていない。(中継サーバーを通しても問題なく接続できるため。)
3:サーバーは始めの応答データとして、接続するべき別のUDPサーバーを返しているっぽい。(ここで実動作のために別のサーバーを利用しているようなので、始めにつくった中継サーバーはまったく使われないため、動作テストを断念しました。)

始めのサーバーからの応答でどのポートをつかったどのサーバーに接続する動作をするのかがわかるなら、どうとでもできそうですが、いまの状態ではそこはまったくわからない・・・
来週はこのあたりなんとかする方法を考えてなにか思いついたら再度挑戦したいところです。

RtmpClientの使い方その5 H.264やVP6のデータパケットも対応できるようにする。

今回はRtmpClientというよりBroadcastStreamの方の話ですが、いままでできなかったH.264やVP6のストリームデータ対応についてです。

https://github.com/taktod/red5_php
ここのgitのリポジトリのcom.ttProject.red5.server.adapter.library.edge.BroadcastStreamが今回の変更した主な対象物となります。

実際やったことはdispatchEventというデータを受け流す部分にビデオデータやオーディオデータの解析する部分をいれて、Red5のClientBroadcastStreamと同様の処理にまわすようにプログラムを合わせたという形になっています。
ClientBroadcastStreamは放送するプレーヤーがあって、そちらをイベントの始まりとするような処理が書いてあり、僕のつくったBroadcastStreamは外部からもってきたパケットデータをイベントの始まりとするように書いています。
start() stop() close()あたりにまだ若干バグがありそうなので、今後少々保守していこうかなと思います。

試してはいませんが、xuggleのxuggle-xuggler-red5のcom.xuggle.red5.io.BroadcastStream.javaに上書きしてやると、Xuggleの変換後のパケットデータがうまくまわるようになるかもしれませんね。

2011年6月10日金曜日

UDPホールパンチング、その1

P2Pを行おうとすると、ポートの解放等手間がかかります。
だいたいはルーターの設定をいじってやれば済むのですが、やはり素人ではなかなか手がだせません。
設定用のHtmlをプログラムからリクエストとばしてやって、特定の機器では自動的にポート解放をするということもやろうと思えばできますが、さすがに勝手にプログラムがポートをあけるなんて恐ろしい話です。(Basic認証くらいなら、ダイアログを開いて入力してもらって・・・みたいな感じですかね。)

というわけでFlashのP2P実装RtmfpでもつかっているUDPホールパンチングという技術をちょっと触ってみました。
今回はその初回です。
やることは結構単純で以下の手順です。
まず適当なPCでサーバーにUDPによる接続を実行する。するとそのPCが接続につかったIPアドレスとポートがわかる。
別のPCが先のIPアドレスとポート宛にメッセージをおくると、メッセージがとどく。
この手順で互いにIPアドレスとポートを交換しあうとP2Pで通信ができるというもの。
まぁ、かなり乱暴な説明です。

ともあれJavaでちょこっと作ってみました。
https://github.com/taktod/udptest
コメントもかかずにパパッとつくっただけなので、わかりにくいと思いますが、次の動作をします。
実行可能jarファイルをserver.jarとclient.jarつくって
1:サーバーを立ち上げる。
2:クライアントを実行するとサーバーに接続し、testという文字列を送信する。(ダミーデータ)
3:サーバーはtestを受け取るとそこから接続してきたユーザーのIPアドレスと利用中のポートを取得してMapにいれて保持しておく。
4:サーバーから接続中のクライアントには、新しいクライアントの情報を送る。
5:サーバーから新しいクライアントには、接続中のクライアントの情報を送る。
6:クライアント情報を受け取ったクライアントはinitという文字列を情報の宛先に送信する。(ダミー)
6の時点で全クライアントが互いにinitという文字列データ送信を相互におこなったことになる。
7:標準入力のデータを受け取ったクライアントは他のクライアントにそのデータを送る。(P2Pの動作実行)

という次第です。
注意点は、しばらく放置するとタイムアウトになるのか接続できなくなるみたいです。
この部分は、サーバーやクライアント同士で適当にPingのやりとりをするように実装すればOKかと思います。

Javaでかけちゃうのが面白い。
中央サーバーをGAEにでもおいておき、P2PのクライアントはJavaAppletにとかできたら、かなりおもしろいことできるんじゃない?

2011年6月1日水曜日

twitterにBookmarkletでちょっかいをだす。

もともと計画していた、どこのウェブサイトでもアバターチャットができるようにするというブックマークレット
サーバーを大量に準備する予定もコネもないのでとりあえずはtwitter上でうごかして1部屋でやろうと考えています。

ところが、twitter上でコマンドを実行するとtwitterの動作がこわれるようです。
しらべてみるとjQueryを注入すると無名関数が干渉でもするのでしょうか・・・

プログラムを呼び出した時点で右クリックは移動用として禁止にするから、そのあたりのおおもとのtwitterに対する影響を調査して、違和感なくあそべるようにしたいですね。

あとは余力があればtwitterとの連携もできたら楽しそう。発言したらそのままtweetになるとか。