twitterでRTMPEをやっているという方からtweetが・・
そういえばやってなかったので実験してみました。
■Handshakeの動作がどうなっているか確認
conf/logback.xmlの設定のorg.red5.server.net.rtmpの指定をDEBUGに変更したらログが表示されるようになります。
FlashPlayer10(RTMPE)
Scheme1でHandshakeするようです。
その後でPublicキーの相互やりとりを実施となるようです。
RtmpClientの場合(RTMP(暗号認証指定時))
Scheme0でHandshakeをおこなってから
同じようにPublicキーのやりとりを実施しています。
FlashPlayer10(RTMPT)
Scheme1でHandshakeするようです。
・感想
全部Scheme1で認証するようにFlashPlayer側がかわってしまったのでしょうか・・・
内部にもいろいろ隠し仕様が追加されてるような気がします。(確定ではないです。)
■動作的にどうなるか?(demos/publisher.htmlにて確認)
FlashMediaServerの場合
・rtmpe://サーバー/liveで接続して
放送:ok
視聴:ok
まぁあたりまえです。
Red5の場合
・rtmpe://サーバー/liveで接続して
放送:ok
視聴:ng(パケットが送られてこなくてフリーズする?)
となっているようです。
根拠は
rtmp:視聴 rtmpe:放送では問題なく動作
rtmp:放送 rtmpe:視聴ではやはり固まります。
・rtmpt://サーバー:5080/liveで実行した場合
放送:ok
視聴:ok
だが、しばらくたつとフリーズしてブラウザが落ちるようです。(Chrome OSX)
内部でFlashPlayer動作用のプロセスがかたまったまま、ブラウザを閉じても終了しないそんな感じになっておりました。
・感想
かろうじてrtmpeで放送するのは使えますかね。ほかはちょっと・・・
というかrtmpeの放送処理もちょっと・・・
■まとめ
簡単におこなった実験ですので、確定ではありませんが、rtmp以外のプロトコルに関しては、今後のred5のバージョンアップを待つか、Red5の大本のソースコードに切り込んでいしか方法はないような感じかと思います。
しかし、Flashのバージョンアップしてから機能のメンテナンスをきちんと実行しないのは、面倒だからやっていないのか、はたまた問題がでてるのはわかっているけど、Flashの動作の解析がうまくいかなかったのか、どうなんでしょう。
2011年5月29日日曜日
2011年5月27日金曜日
RtmpClientの使い方その4 scheme1のValidation通してみる。
Red5の内部についている。RTMPClientをつかって別のRed5のサーバーにクライアントとして接続させると動作的に不正なクライアントと判定されます。
これはscheme1のValidationが有効になるようにRTMPClientがHandshakeしないためです。
Red5のサーバー動作側がscheme1でValidationの確認してるんだから、Red5チームがメンテしわすれたんでしょうね。
さて、この問題をなんとかしてみます。
ちなみに、現状のバージョンのRed5では別にValidation通さなくてもなんの問題もありません。判定だけしてそのまま処理続行してますから。ただの自己満足の色合いが強いです。
ソースコードの全体図はgithubにあげたred5_phpのリポジトリを確認してください。
ここのcom.ttProject.TestClassを実行するとlocalhostの1935ポートのRtmpサーバーのliveアプリケーションに接続しにいくようにしてあります。
まずは変更点の解説
■RtmpClientEx.javaの中
1:コンストラクタ上でioHandlerの初期化を実行
2:startConnectorのメソッドをRTMPClientよりオーバーライド
元ネタのRTMPClientではioHandlerをRTMPMinaIoHandlerとして初期化しています。
このHandlerの内部でつかっているOutboundHandshakeというクラスの中身を必要に応じて変更したいので、Handlerに細工する→Sessionに独自handshakeクラスを注入するという手を使います。
2の方のstartConnectorのメソッドのオーバーライドはioHandlerがprivateではなくprotectedだったら必要なかったのですが、まぁしかたないです。
■RTMPMinaIoHandlerEx
1:sessionCreatedをオーバーライド
このイベントの時に、オリジナルのRTMPMinaIoHandlerの処理を実施したあとに、sessionの設定属性を独自のものに書き換え。
テスト等でオリジナルのRTMPClientのHandshakeを実行したい場合は設定しているOutboundHandshakeExの部分をコメントアウトすれば大丈夫です。
■OutboundHandshakeEx
1:generateClientRequest1をオーバーライド
2:addBytesメソッド
3:calculateOffsetメソッド
2と3は内部で使う補助メソッドです。OutboundHandshakeの中でも定義されているんですが、privateになっているため、コピーしました。
問題は1の部分
大本のOutboundHandshakeクラスの中では次のようにHandshakeが進行します。
はじめにcreateHandshakeBytesメソッドで、timestamp(4バイト) + プレーヤーバージョン指定(4バイト*1) + 1528バイトのランダムバイトをひな形として作成します。
続いてgenerateClientRequest1メソッドで、送信バイトデータとして、Encrypted識別子(0x03)と先ほどつくったひな形をHandshakeのエントリーとして送信しています。(*2)
*1:ここでいうプレーヤーのバージョンは、adobeのFlashプレーヤーのバージョン確認ででるバージョンとは別のデータになります。内部管理用のバージョン番号が別にあるのでしょう。
*2:adobeのサイトのrtmpの仕様書によるとHandshakeの冒頭でc0 c1 c2 s0 s1 s2とクライアントが3回、サーバーが3回データを送信するように設計されていると記述されています。このc0c1に相当するデータが一度に送信されています。(ってか分けてる必要あんのこれ?)
OutboundHandshakeExではひな形はcreateHandshakeBytesでつくってもらってからgenerateClientRequest1上で次のようなことを行っています。
まず、ひな形をローカル変数に保持。
続いて772から4バイトがvalidation用のキーの埋め込み場所を指定するデータとなるため、その4バイトを取得する。
776から728バイトのデータがどうなっているかをキーにSHA256で暗号化するのでその計算を実行。
できたキーを先ほど計算した埋め込み場所に上書き。
でHandshakeのひな形できあがりとなります。
では最後に、conf/logback.xmlでorg.red5.server.net.rtmpをDEBUGにした状態でRed5 1.0.0 RC2 Rev 4222に接続させてみます。
まずは、エラーにRTMPClientでつないだ場合
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player encryption byte: 3
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Detecting flash player version 9,0,124,2
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player version byte: 9
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 0 client digest offset: 342
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: 1f52f06ea4da019863e8d35badd242cb29be6224613996df3bf3becd2da28676
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 1 client digest offset: 1284
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: 32ba98dd706a7c8ecb089ea49c44c31aafe2bbcc5ad0b44e489bbf13e1d0761b
[ERROR] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Unable to validate client
[INFO] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Invalid RTMP connection data detected, you may experience errors
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Using new style handshake
これはscheme1のValidationが有効になるようにRTMPClientがHandshakeしないためです。
Red5のサーバー動作側がscheme1でValidationの確認してるんだから、Red5チームがメンテしわすれたんでしょうね。
さて、この問題をなんとかしてみます。
ちなみに、現状のバージョンのRed5では別にValidation通さなくてもなんの問題もありません。判定だけしてそのまま処理続行してますから。ただの自己満足の色合いが強いです。
ソースコードの全体図はgithubにあげたred5_phpのリポジトリを確認してください。
ここのcom.ttProject.TestClassを実行するとlocalhostの1935ポートのRtmpサーバーのliveアプリケーションに接続しにいくようにしてあります。
まずは変更点の解説
■RtmpClientEx.javaの中
1:コンストラクタ上でioHandlerの初期化を実行
2:startConnectorのメソッドをRTMPClientよりオーバーライド
元ネタのRTMPClientではioHandlerをRTMPMinaIoHandlerとして初期化しています。
このHandlerの内部でつかっているOutboundHandshakeというクラスの中身を必要に応じて変更したいので、Handlerに細工する→Sessionに独自handshakeクラスを注入するという手を使います。
2の方のstartConnectorのメソッドのオーバーライドはioHandlerがprivateではなくprotectedだったら必要なかったのですが、まぁしかたないです。
■RTMPMinaIoHandlerEx
1:sessionCreatedをオーバーライド
このイベントの時に、オリジナルのRTMPMinaIoHandlerの処理を実施したあとに、sessionの設定属性を独自のものに書き換え。
テスト等でオリジナルのRTMPClientのHandshakeを実行したい場合は設定しているOutboundHandshakeExの部分をコメントアウトすれば大丈夫です。
■OutboundHandshakeEx
1:generateClientRequest1をオーバーライド
2:addBytesメソッド
3:calculateOffsetメソッド
2と3は内部で使う補助メソッドです。OutboundHandshakeの中でも定義されているんですが、privateになっているため、コピーしました。
問題は1の部分
大本のOutboundHandshakeクラスの中では次のようにHandshakeが進行します。
はじめにcreateHandshakeBytesメソッドで、timestamp(4バイト) + プレーヤーバージョン指定(4バイト*1) + 1528バイトのランダムバイトをひな形として作成します。
続いてgenerateClientRequest1メソッドで、送信バイトデータとして、Encrypted識別子(0x03)と先ほどつくったひな形をHandshakeのエントリーとして送信しています。(*2)
*1:ここでいうプレーヤーのバージョンは、adobeのFlashプレーヤーのバージョン確認ででるバージョンとは別のデータになります。内部管理用のバージョン番号が別にあるのでしょう。
*2:adobeのサイトのrtmpの仕様書によるとHandshakeの冒頭でc0 c1 c2 s0 s1 s2とクライアントが3回、サーバーが3回データを送信するように設計されていると記述されています。このc0c1に相当するデータが一度に送信されています。(ってか分けてる必要あんのこれ?)
OutboundHandshakeExではひな形はcreateHandshakeBytesでつくってもらってからgenerateClientRequest1上で次のようなことを行っています。
まず、ひな形をローカル変数に保持。
続いて772から4バイトがvalidation用のキーの埋め込み場所を指定するデータとなるため、その4バイトを取得する。
776から728バイトのデータがどうなっているかをキーにSHA256で暗号化するのでその計算を実行。
できたキーを先ほど計算した埋め込み場所に上書き。
でHandshakeのひな形できあがりとなります。
では最後に、conf/logback.xmlでorg.red5.server.net.rtmpをDEBUGにした状態でRed5 1.0.0 RC2 Rev 4222に接続させてみます。
まずは、エラーにRTMPClientでつないだ場合
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player encryption byte: 3
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Detecting flash player version 9,0,124,2
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player version byte: 9
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 0 client digest offset: 342
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: 1f52f06ea4da019863e8d35badd242cb29be6224613996df3bf3becd2da28676
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 1 client digest offset: 1284
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: 32ba98dd706a7c8ecb089ea49c44c31aafe2bbcc5ad0b44e489bbf13e1d0761b
[ERROR] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Unable to validate client
[INFO] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Invalid RTMP connection data detected, you may experience errors
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Using new style handshake
ちゃんと無効なクライアントと判定されてしまいました。Scheme 0もScheme 1も認証がとおっていません。
続いて修正版で接続した場合
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player encryption byte: 3
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Detecting flash player version 9,0,124,2
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player version byte: 9
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 0 client digest offset: 691
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: 4f19642c794f70f130ac7b159cda40f71a754abbcea4fa40d8bc328923bf0504
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 1 client digest offset: 1217
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: 55671918c7a41de1e20d29b1f9b11e3c70951ce34e13f5c4b32b51054bbef963
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Selected scheme: 1
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Valid RTMP client detected
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Using new style handshake
きちんとScheme1を検出して有効と判定してくれました。
まぁやってることは同じですけどw
最後にFlashPlayerでつないだ場合
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player encryption byte: 3
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Detecting flash player version 128,0,7,2
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Player version byte: 128
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 0 client digest offset: 374
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: e038bc995719caece95d042eb4e71a1ffbfb140d4a6ac3aa52e296c742d3082b
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Scheme: 1 client digest offset: 1310
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Temp: 8753c820eb1225042a7c13ce15bc5f99af692caa07986d0125b9567eec7f4bc4
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Selected scheme: 1
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Valid RTMP client detected
[DEBUG] [NioProcessor-1] org.red5.server.net.rtmp.RTMPHandshake - Using new style handshake
これで似た感じの動作になってきました。
2011年5月25日水曜日
xuggleを再度やってみる。
@wikiの方の記事には昔書きましたが、xuggleにそろそろまた手をだしたいと思います。
Red5のRTMPClientのpublish処理がまともに動きますからね。
いろいろとできるはず。
ただし、xuggle....いろんな問題をはらんでます。
コンパイルがとおらなかったり、以前ためしたときにはh264のエンコードが微妙だったり、faacの扱いがあるのかないのかわからなかったり、動作させるときに設定パラメータ大杉だったりと・・・
ま、とりあえずインストールですね。
まずは、yasmを入手します。一部のコンパイルで必要ですから。
http://www.tortall.net/projects/yasm/wiki/Download
こちらからtarballを入手して、configure make make installの3点セット
xuggleのbuild.xmlの設定を解析していないので、インストールせずにコンパイルを通す方法はわかりません。
なお、tarballのソースコードを入手すれば、configureがちゃんと入ってますが開発版のyasmを入手したところ、configureがなく動作させることができませんでした。
次にxuggle本体です。
http://xuggle.googlecode.com
こちらにいって、ソースからsvnをつかってダウンロードしました。
今回インストールしたいのは、xuggle-xugglerなので、
svnで落としたディレクトリ/java/xuggle-xugglerに移動して、antを実行しコンパイルを進めました。
一応動作確認も実行するant run-testsというコマンドもあるのですが、今回はただのantにしておきました。
ant run-testsやってみましたがやっぱりfaacでこけますね。
xuggleの設定してるテストが現状のffmpegに適合してないっぽい。
faacそのものが動作するかはあとで調べないとだめですね。
あと、前はコンパイルするとxuggle/java/xuggle-xuggler/dist/stage/usr/local/xuggle/....
という形でディレクトリが生成されていたのですがxuggleというディレクトリがなくなっているようです。
ant installを実行すると/usr以下の部分がPCの/usr以下にコピーするように動作するので、この部分書き直したほうがいいと思う。
このままインストールするとどこまでがxuggleかわからないので、現状のままほっといて以下のパスを環境変数に設定しておきました。
export XUGGLE_HOME=/Users/xxxxx/xuggletest/xuggle/java/xuggler-xuggler/dist/stage/usr/local
export PATH=$XUGGLE_HOME/bin:$PATH
export DYLD_LIBRARY_PATH=$XUGGLE_HOME/lib:$LD_LIBRARY_PATH
(DYLD_LIBRARY_PATHがまちがってたっぽいので修正Macの場合はこうするべき。)
とりあえずこれで目標達成。
あとで適当にflvファイルをコピーするプログラムでも書こうと思います。
Red5のRTMPClientのpublish処理がまともに動きますからね。
いろいろとできるはず。
ただし、xuggle....いろんな問題をはらんでます。
コンパイルがとおらなかったり、以前ためしたときにはh264のエンコードが微妙だったり、faacの扱いがあるのかないのかわからなかったり、動作させるときに設定パラメータ大杉だったりと・・・
ま、とりあえずインストールですね。
まずは、yasmを入手します。一部のコンパイルで必要ですから。
http://www.tortall.net/projects/yasm/wiki/Download
こちらからtarballを入手して、configure make make installの3点セット
xuggleのbuild.xmlの設定を解析していないので、インストールせずにコンパイルを通す方法はわかりません。
なお、tarballのソースコードを入手すれば、configureがちゃんと入ってますが開発版のyasmを入手したところ、configureがなく動作させることができませんでした。
次にxuggle本体です。
http://xuggle.googlecode.com
こちらにいって、ソースからsvnをつかってダウンロードしました。
今回インストールしたいのは、xuggle-xugglerなので、
svnで落としたディレクトリ/java/xuggle-xugglerに移動して、antを実行しコンパイルを進めました。
一応動作確認も実行するant run-testsというコマンドもあるのですが、今回はただのantにしておきました。
ant run-testsやってみましたがやっぱりfaacでこけますね。
xuggleの設定してるテストが現状のffmpegに適合してないっぽい。
faacそのものが動作するかはあとで調べないとだめですね。
あと、前はコンパイルするとxuggle/java/xuggle-xuggler/dist/stage/usr/local/xuggle/....
という形でディレクトリが生成されていたのですがxuggleというディレクトリがなくなっているようです。
ant installを実行すると/usr以下の部分がPCの/usr以下にコピーするように動作するので、この部分書き直したほうがいいと思う。
このままインストールするとどこまでがxuggleかわからないので、現状のままほっといて以下のパスを環境変数に設定しておきました。
export XUGGLE_HOME=/Users/xxxxx/xuggletest/xuggle/java/xuggler-xuggler/dist/stage/usr/local
export PATH=$XUGGLE_HOME/bin:$PATH
export DYLD_LIBRARY_PATH=$XUGGLE_HOME/lib:$LD_LIBRARY_PATH
(DYLD_LIBRARY_PATHがまちがってたっぽいので修正Macの場合はこうするべき。)
とりあえずこれで目標達成。
あとで適当にflvファイルをコピーするプログラムでも書こうと思います。
2011年5月24日火曜日
放送を別のスコープに受け流す。
Red5のフォーラムにChange the scope/room of a streamというのがあったので放送を別のスコープに受け流すプログラム書いてみた。
どこかのコネクションがplayを始めるとそのコネクション用のBroadcastStreamをつくる。
どこかのコネクションがpublishをはじめるとそのstream内でながれているパケットデータをつくったBroadcastStreamに流していく。
FlashによるFlv1のストリームの場合は、放送を先にはじめてもOKですが、FMEによる放送の場合は、先に視聴を開始→放送を開始としないと視聴できないです。
これはたぶんH.264の場合先頭にメディアのヘッダ情報がないと放送が開始できないとかいった理由でしょう。
あと、プログラムをみればわかりますが、2つ以上の放送を開始してしまうと、両方のデータをパケットに流すことになってしまうので、壊れると思います。試していませんが。
終了まわり等きちんと書いていないので運用等で使いたい場合は、そのあたりの修正が必須になります。
ここんところRed5ネタばかりだ。
どこかのコネクションがplayを始めるとそのコネクション用のBroadcastStreamをつくる。
どこかのコネクションがpublishをはじめるとそのstream内でながれているパケットデータをつくったBroadcastStreamに流していく。
FlashによるFlv1のストリームの場合は、放送を先にはじめてもOKですが、FMEによる放送の場合は、先に視聴を開始→放送を開始としないと視聴できないです。
これはたぶんH.264の場合先頭にメディアのヘッダ情報がないと放送が開始できないとかいった理由でしょう。
あと、プログラムをみればわかりますが、2つ以上の放送を開始してしまうと、両方のデータをパケットに流すことになってしまうので、壊れると思います。試していませんが。
終了まわり等きちんと書いていないので運用等で使いたい場合は、そのあたりの修正が必須になります。
ここんところRed5ネタばかりだ。
2011年5月22日日曜日
RtmpClientの使い方その3.2 FlashMediaServerに対応させるのは、やめときます。
さっそくですが、RtmpClientでFlashMediaServerに接続させるのは、やめようと思います。
どうも調べてる限りでは、Handshake以外の何かが抜け落ちているようです。
rtmpeの状態では接続が成立するので(動画データは送れないけど)、そっちから攻めればなにかわかるかと思いますが、これ以上は公開せずに会社で実験したいと思います。
そもそもやりたかったことは、FlashMediaLiveEncoderでFMSにデータを流したやつを、Red5経由でやりとりしてやれば、FMS1つ分のライセンスで放送し放題じゃないの?ということでした。
まぁ、FMS自身に放送データを別のサーバーにpublishする方法があるので、そっちからせめてRed5サーバーにpublishさせてやれば要件は満たせるかなとおもいます。
では、一応いままでにやったこと。
■Flashのバージョンを落として各サーバーに接続させてみた。
・ver 7,8ではRed5のpublisherが起動できず。
・ver 9で接続ができ、FlashMediaServerにも接続させることができた。
■Red5のRtmpClientでRTMPEで本当に接続できるか再度確認
・きちんとつながりFlashMediaServerのログ上にも接続したという記録がつきました。
■ver 9のプレーヤーが吐いたデータをそのまま投げてHandshakeを実行させてみる。
・ver 9のプレーヤーがRed5サーバーにつねげたときに送られてきたパケットデータをそのまま投げるようにして、FlashMediaServerに接続させてみた。→動作せず。
ちなみにver 9のプレーヤーでも接続元のページの情報等をFlashMediaServerはログに残していたので、なにかがあるのかもしれませんね。
9/23追記
なお、JavaのRtmp実装で接続したい場合はFlazrというライブラリを利用すれば可能です。
どうも調べてる限りでは、Handshake以外の何かが抜け落ちているようです。
rtmpeの状態では接続が成立するので(動画データは送れないけど)、そっちから攻めればなにかわかるかと思いますが、これ以上は公開せずに会社で実験したいと思います。
そもそもやりたかったことは、FlashMediaLiveEncoderでFMSにデータを流したやつを、Red5経由でやりとりしてやれば、FMS1つ分のライセンスで放送し放題じゃないの?ということでした。
まぁ、FMS自身に放送データを別のサーバーにpublishする方法があるので、そっちからせめてRed5サーバーにpublishさせてやれば要件は満たせるかなとおもいます。
では、一応いままでにやったこと。
■Flashのバージョンを落として各サーバーに接続させてみた。
・ver 7,8ではRed5のpublisherが起動できず。
・ver 9で接続ができ、FlashMediaServerにも接続させることができた。
■Red5のRtmpClientでRTMPEで本当に接続できるか再度確認
・きちんとつながりFlashMediaServerのログ上にも接続したという記録がつきました。
■ver 9のプレーヤーが吐いたデータをそのまま投げてHandshakeを実行させてみる。
・ver 9のプレーヤーがRed5サーバーにつねげたときに送られてきたパケットデータをそのまま投げるようにして、FlashMediaServerに接続させてみた。→動作せず。
ちなみにver 9のプレーヤーでも接続元のページの情報等をFlashMediaServerはログに残していたので、なにかがあるのかもしれませんね。
9/23追記
なお、JavaのRtmp実装で接続したい場合はFlazrというライブラリを利用すれば可能です。
RtmpClientの使い方その3.1 うまく動かないのでHandshakeについて調べてみた。
先の記事での動作を実際にliverepeaterに適応してみたら、動画のデータへの対応がうまくいかず、動作不良がおこる模様でした。
やっぱりHandshakeがおかしいみたいです。(というかrtmpeの接続と認識されるような構成になってるっぽい。)
そこでorg.red5.server.net.rtmpをDEBUGに変更してサーバー、クライアントを動作させHandShakeがどうなっているか確認してみました。
以下サーバー側のログから動作概要抜粋
■RtmpClientでつないだ場合
■RtmpClientをEncrypt有効にしてつないだ場合
■本物のFlashPlayerでつないだ場合
やっぱりHandshakeがおかしいみたいです。(というかrtmpeの接続と認識されるような構成になってるっぽい。)
そこでorg.red5.server.net.rtmpをDEBUGに変更してサーバー、クライアントを動作させHandShakeがどうなっているか確認してみました。
以下サーバー側のログから動作概要抜粋
■RtmpClientでつないだ場合
- RTMPMinaConnection - RTMPMinaConnection created
- RTMPConnManager - Connection created, id: 5
- RTMPHandshake - Handshake ctor
- - Player encryption byte: 3
- - Detecting flash player version 9,0,124,2
- - Player version byte: 9
- - Scheme: 0 client digest offset: 310
- - Temp: バイトデータ
- - Scheme: 1 client digest offset:1168
- - Temp: バイトデータ
- - Unable to validate client
- - Invalid RTMP connection data detected.
- - Using new style handshake
- - Public key: 数字の羅列
- - Public key as bytes - length [128]: バイトデータ
■RtmpClientをEncrypt有効にしてつないだ場合
- RTMPMinaConnection - RTMPMinaConnection created
- RTMPConnManager - Connection created, id: 5
- RTMPHandshake - Handshake ctor
- - Player encryption byte: 6
- - Detecting flash player version 9,0,124,2
- - Player version byte: 9
- - Scheme: 0 client digest offset: 497
- - Temp: バイトデータ
- - Valid RTMP client detected.
- - Using new style handshake
- - Public key: 数字の羅列
- - Public key as bytes - length [129]: バイトデータ
- - Truncated public key length to 128
- - Incoming public key [128]: バイトデータ
- - Outgoing public key [128]: バイトデータ
- - Shared secret [128]: バイトデータ
■本物のFlashPlayerでつないだ場合
- RTMPMinaConnection - RTMPMinaConnection created
- RTMPConnManager - Connection created, id: 5
- RTMPHandshake - Handshake ctor
- - Player encryption byte: 3
- - Detecting flash player version 128,0,7,2
- - Player version byte: 128
- - Scheme: 0 client digest offset: 526
- - Temp: バイトデータ
- - Scheme: 1 client digect offset: 940
- - Temp: バイトデータ
- - Selected scheme: 1
- - Valid RTMP client detected.
- - Using new style handshake
- - Public key: 数字の羅列
- - Public key as bytes - length [129]: バイトデータ
- - Truncated public key length to 128
versionは固有の値だからおいといて。
Scheme 1の認証で有効なクライアントと判定されています。
Publicキーでは先頭に0x00が追加された129バイトのデータになっています。
そして、Cryptを有効にしたときの接続で見られた3つのキーのやりとりはありません。
というわけでCryptを有効にした場合ともともとの状態との中間になる方法で接続しようとしないとだめっぽいです。
■RtmpClientのデフォルトで接続するが、Scheme1の認証を強制的に受け入れる場合
- RTMPMinaConnection - RTMPMinaConnection created
- RTMPConnManager - Connection created, id: 5
- RTMPHandshake - Handshake ctor
- - Player encryption byte: 3
- - Detecting flash player version 9,0,124,2
- - Player version byte: 9
- - Scheme: 0 client digest offset: 674
- - Temp: バイトデータ
- - Selected scheme: 1
- - Valid RTMP client detected.
- - Using new style handshake
- - Public key: 数字の羅列
- - Public key as bytes - length [128]: バイトデータ
- - Truncated public key length to 128
ちなみにこのバージョンでは、サーバー側のプログラムを書き換え認証を強制受け入れとしてあります。
■今後の攻め方
- Cryptを有効にした場合をベースにいらないものを省くという方向でせめればいいと思われる。
- Scheme1で認証されるようにすること。
- Public keyのバイト数は129バイトになるように調整する。
とすれば、うまくいきそうな気がします。
なお、暗号化のHandshakeをクライアントで実行させかつ、Player encryption byteを3(暗号化しないと宣言)として動作させ、Flash Media Serverに接続させようとしたところつながった瞬間に切断されました。
無効なクライアントと認証されてるっぽいです。Scheme0で有効性を検証してるのがだめなんだろうねぇ・・・
2011年5月21日土曜日
RtmpClientの使い方その3 Red5側のプログラムを修正してみる場合
Red5に搭載されているRTMPClient(とそれに付随する継承クラス)では2つの問題をはらんでいます。
1つ目は、サーバー側からのクライアント上の関数実行命令に対する返答のしかたがおかしいこと。
2つ目は、HandShakeの仕方が不正であること。
1つ目の問題では、Red5サーバーに接続しにいった状態で、放送を受信しつつサーバーからクライアント関数の実行を実施されると、パケットが混乱し、サーバーから切断されるという不具合があります。
(相手がRed5ならNG、Wowzaなら発生せず、FMSの場合は不明)
2つ目の問題では、Red5サーバーに接続しにいったときにサーバー側で不正なクライアントによる接続と判定されます。また、FMSには接続したあとにすぐ切断させられます。
(相手がRed5なら警告でてそのままつながる、Wowzaつながる、FMS接続後即切断)
今回はRed5側のプログラムを修正して再コンパイルして直す方法です。
まず、Red5のプログラムをSubversionで取得、ant(1.7以降)を準備しておきます。
まずは問題その1、クライアント関数の実行応答が不正な件
src/org/red5/server/net/rtmp/BaseRTMPClientHandler.javaのonInvoke関数の一番最後の部分に手を加えます。
} else if (!onStatus) {
Invoke reply = new Invoke();
reply.setCall(call);
reply.setInvokeId(invoke.getInvokeId());
log.debug("Sending empty call reply: {}", reply);
channel.write(reply);
}
となっている部分を
1つ目は、サーバー側からのクライアント上の関数実行命令に対する返答のしかたがおかしいこと。
2つ目は、HandShakeの仕方が不正であること。
1つ目の問題では、Red5サーバーに接続しにいった状態で、放送を受信しつつサーバーからクライアント関数の実行を実施されると、パケットが混乱し、サーバーから切断されるという不具合があります。
(相手がRed5ならNG、Wowzaなら発生せず、FMSの場合は不明)
2つ目の問題では、Red5サーバーに接続しにいったときにサーバー側で不正なクライアントによる接続と判定されます。また、FMSには接続したあとにすぐ切断させられます。
(相手がRed5なら警告でてそのままつながる、Wowzaつながる、FMS接続後即切断)
今回はRed5側のプログラムを修正して再コンパイルして直す方法です。
まず、Red5のプログラムをSubversionで取得、ant(1.7以降)を準備しておきます。
まずは問題その1、クライアント関数の実行応答が不正な件
src/org/red5/server/net/rtmp/BaseRTMPClientHandler.javaのonInvoke関数の一番最後の部分に手を加えます。
} else if (!onStatus) {
Invoke reply = new Invoke();
reply.setCall(call);
reply.setInvokeId(invoke.getInvokeId());
log.debug("Sending empty call reply: {}", reply);
channel.write(reply);
}
となっている部分を
} else if (!onStatus) {
Invoke reply = new Invoke();
call.setStatus(Call.STATUS_METHOD_NOT_FOUND);
reply.setHeader(source);
reply.setCall(call);
reply.setInvokeId(invoke.getInvokeId());
log.debug("Sending empty call reply: {}", reply);
channel.write(reply);
}
}
強制的に実行しようとした関数はありませんでした。と応答させてみました。
実行結果を返したい場合はRed5_phpのリポジトリのRtmpClientExのonInvokeの中に記述書いてあるので興味ある人はのぞいてみてください。
つぎに問題その2、Handshakeがこける問題。(FMSにはつながりにいくようになりますが、放送の受信ができなくなる。どうやらこの方法だとRTMPEの動作と認識されるっぽい。[つまり本物のFlashのHandshakeのやり方とは違うということ。])
src/org/red5/server/net/rtmp/RTMPMinaIoHandler.javaのsessionCreated関数をいじります。中盤あたり
if(rtmp.getMode() == RTMP.MODE_CLIENT) {
// create an outbound handshake
OutboundHandshake outgoingHandshake = new OutboundHandshake();
// if handler is rtmpe client set encryption on the protocol state
// if (handler instanceof RTMPEClient) {
// rtmp.setEncrypted(true);
// rtmp.setEncrypted(true);
// set the handshake type to encrypted as well
// outgoingHandshake.setHandshakeType(RTMPConnection.RTMPENCRYPTED);
//}
// add the handshake
session.setAttribute(RTMPConnection.RTMP_HANDSHAKE, outgoingHandshake);
}
となっている部分を・・・
if(rtmp.getMode() == RTMP.MODE_CLIENT) {
// create an outbound handshake
OutboundHandshake outgoingHandshake = new OutboundHandshake();
// if handler is rtmpe client set encryption on the protocol state
// if (handler instanceof RTMPEClient) {
rtmp.setEncrypted(true);
rtmp.setEncrypted(true);
// set the handshake type to encrypted as well
outgoingHandshake.setHandshakeType(RTMPConnection.RTMPENCRYPTED);
//}
// add the handshake
session.setAttribute(RTMPConnection.RTMP_HANDSHAKE, outgoingHandshake);
}
この2カ所のコメントをはずせば暗号化されたプレーヤーのバージョン情報が記録されるようになり、ただしくプレーヤーからの接続のHandshakeが完了するようになります。
--追記--
すみません、この記述うそです。
Rtmpeとして接続しにいこうとして、Handshakeは成立しますが動画データのやりとりでこけます。Red5やWowzaとデータのやりとりをする場合は元のRTMPMinaIoHandlerつかって処理してもらって、内部判定はInvalidClientとして操作させるかScheme1でValidationを通るように修正するかするといいかと思います。
別の記事であきらめていますが、Scheme1でValidと判定されるようにしてもFlashMediaServerへの接続は失敗します。
--追記ここまで--
--追記--
すみません、この記述うそです。
Rtmpeとして接続しにいこうとして、Handshakeは成立しますが動画データのやりとりでこけます。Red5やWowzaとデータのやりとりをする場合は元のRTMPMinaIoHandlerつかって処理してもらって、内部判定はInvalidClientとして操作させるかScheme1でValidationを通るように修正するかするといいかと思います。
別の記事であきらめていますが、Scheme1でValidと判定されるようにしてもFlashMediaServerへの接続は失敗します。
--追記ここまで--
あとは、大本のディレクトリにもどって、antを実行すればコンパイルがおわり修正されたRed5.jarが生成されました。
一応これで動作するようにはなるのですが、Red5そのものの修正するのはバージョンがかわるたびにパッチを当てないといけなくなって面倒なので、ちょっと拡張クラス側で対応できないか模索したいと思っています。
僕のつくったRtmpClientExで対応完了するなら、その方が使いやすいですからw
2011年5月20日金曜日
RtmpClientの使い方その2.5 HandShakeがこける。
やっと週末です。
今週の情報
liverepeaterのプログラムでつかったRTMPClientの拡張ですが、FlashMediaServerにつなぐとHandShakeこけるっぽい。
理由は「Red5のOutboundHandshakeの実装が適当」だからです。
すみません。適当ではなかったです。
詳細は文末
HandShakeを動作させるときに、Clientから3回、Serverから3回メッセージを送信するわけですが、Clientからの2回目のメッセージの中にプレーヤーが正常であるか確認するための暗号化されたキーをいれてないといけないっぽいです。
主にメンテナンスされているInboundHandshake(サーバー側実装)では、しっかり確認するようにつくられているのに、OutboundHandshake(クライアント側実装)では処理が抜け落ちてるようです。
実際にRed5のサーバーにRtmpClientをつかった実装の接続を実施すると、コンソール上に不正なクライアントがどうのこうのというメッセージがでてきます。
ぱぱっと直してRed5サーバーに有効なクライアントと判定させて、FlashMediaServerにつないで動作確認してみたいところです。
接続できない理由(追記)
Rtmpe用の接続としてちゃんとバージョン9以降用の接続が準備されていました。この方法で接続させないと、SHA128による暗号化したデータの追加がない状態でHandshakeを実行しようとするので、不正なプレーヤーと判定されるようです。
詳細は明日にでも書きます。
今週の情報
liverepeaterのプログラムでつかったRTMPClientの拡張ですが、FlashMediaServerにつなぐとHandShakeこけるっぽい。
理由は「Red5のOutboundHandshakeの実装が適当」だからです。
すみません。適当ではなかったです。
詳細は文末
HandShakeを動作させるときに、Clientから3回、Serverから3回メッセージを送信するわけですが、Clientからの2回目のメッセージの中にプレーヤーが正常であるか確認するための暗号化されたキーをいれてないといけないっぽいです。
主にメンテナンスされているInboundHandshake(サーバー側実装)では、しっかり確認するようにつくられているのに、OutboundHandshake(クライアント側実装)では処理が抜け落ちてるようです。
実際にRed5のサーバーにRtmpClientをつかった実装の接続を実施すると、コンソール上に不正なクライアントがどうのこうのというメッセージがでてきます。
ぱぱっと直してRed5サーバーに有効なクライアントと判定させて、FlashMediaServerにつないで動作確認してみたいところです。
接続できない理由(追記)
Rtmpe用の接続としてちゃんとバージョン9以降用の接続が準備されていました。この方法で接続させないと、SHA128による暗号化したデータの追加がない状態でHandshakeを実行しようとするので、不正なプレーヤーと判定されるようです。
詳細は明日にでも書きます。
2011年5月14日土曜日
RtmpClientの使い方その2
おつぎは、RtmpClientExの話です。
このクラスはRed5のRTMPClientを継承してつくってあります。
もともとはネット上でRed5のRtmpClientをつかって録画させる等の記事を参考につくっていました。
元ネタ
やってることはActionScriptでNetConnectionとNetStreamを使うのと同じ感じで
RtmpClientとNetStream(BaseRTMPClientHandlerの内部クラスになっています。)を扱っている感じです。ActionScriptではNetConnectionにaddEventListenerで接続イベントをとったりします。RtmpClientではIPendingServiceCallbackをセットしてやってそこで返答をみて、接続イベントを取得したりします。
元ネタの記事を参考にしつつ、RTMPClientのソースコードをみながらプログラムを組んできたんですが、GW中にふとRTMPClientの元になるBaseRTMPClientHandlerみてやったところやってることがほぼ90%くらい一致しちゃったので、RtmpCLientExはBaseRTMPClientHandlerの機能を補助する程度のプログラムしか書いてありません。
一致してるのに気づいたときは、思わず吹いてしまいました。いきつく先は同じなんですねw。
では、使い方
1:RtmpClientExのインスタンスをつくって、接続先等を指定する。
2:connect実行(オプション付きの方法がいくつかあるので、好きなのを利用すればよい)
3:コネクトが完了したら、playして、つながっているサーバーからのメディアデータをBroadcastStreamでミラーするなり、publishしてほかのサーバーに投げ返すなり好きにすればOKです。
4:IRtmpClientEx.onInvokeやRtmpClientEx.invokeを利用して他のサーバーの関数呼び出しを実行するのもいいと思う。
5:SharedObjectの実装に関しては、昔しらべたときには受け取りは問題ありませんでしたが、送信に難ありでした。(最新のRed5 1.0.0になったときに修正されてるかもしれません。後日しらべる予定)
rtmpClient = new RtmpClientEx();
rtmpClient.setScope(scope);
rtmpClient.setServer(server);
rtmpClient.setPort(port);
rtmpClient.setApplication(application);
rtmpClient.setListener(new RepeatListener(rtmpClient, new StreamListener(outputStream)));
rtmpClient.connect();
listenerをつけることで接続イベントや関数の実行イベント等やりとりしてます。
たぶんこのlistenerにはSharedObjectの部分将来的に追加するかも。
現行のRed5 RC2(僕が利用しているのはRev.4219)では、SharedObject以外はだいたい満足のいく動作します。
少々不満があるのはonInvokeの返答まわりです。BaseRTMPClientHandlerの実装がたぶん間違っていて、場合によってはエラーになってしまうのでそこはRtmpClientEx側で修正してあります。
onInvokeの返答動作に関してですが、暫定的にIServiceCallを引数として渡してあります。
サーバーから呼び出された関数:call.getServiceMethodName();
サーバーから渡される引数:call.getArguments();
サーバーに応答する返答:call.setStatus(Call.STATUS_SUCCESS_RESULT);を設置してから、返答としてonInvokeがreturnすればOK。
となります。
無理に使わずにRtmpClientEx.invokeを利用して、呼び出し元サーバー側の関数を実行して返答して返した方が無難だと思いますけど。
安定性の件で懸念があり、過去のバージョンのRed5を使っている方もいるかと思いますが、過去のバージョンでRtmpClientExを使うのはお勧めできません。
0.8.0のころはBroadcastStreamに流すとタイムスタンプがおかしくなるのか、なぜか高速再生され、パケットがながれてくるたびに、再生→しばらく停止→再生を繰り返していました。
0.9.1のころに修正されましたが、publishする方がまだきちんと動作していませんでした。
んで、1.0.0のときにpublish側の実装も見直されたようできちんと動作するようになったという・・・
このクラスはRed5のRTMPClientを継承してつくってあります。
もともとはネット上でRed5のRtmpClientをつかって録画させる等の記事を参考につくっていました。
元ネタ
やってることはActionScriptでNetConnectionとNetStreamを使うのと同じ感じで
RtmpClientとNetStream(BaseRTMPClientHandlerの内部クラスになっています。)を扱っている感じです。ActionScriptではNetConnectionにaddEventListenerで接続イベントをとったりします。RtmpClientではIPendingServiceCallbackをセットしてやってそこで返答をみて、接続イベントを取得したりします。
元ネタの記事を参考にしつつ、RTMPClientのソースコードをみながらプログラムを組んできたんですが、GW中にふとRTMPClientの元になるBaseRTMPClientHandlerみてやったところやってることがほぼ90%くらい一致しちゃったので、RtmpCLientExはBaseRTMPClientHandlerの機能を補助する程度のプログラムしか書いてありません。
一致してるのに気づいたときは、思わず吹いてしまいました。いきつく先は同じなんですねw。
では、使い方
1:RtmpClientExのインスタンスをつくって、接続先等を指定する。
2:connect実行(オプション付きの方法がいくつかあるので、好きなのを利用すればよい)
3:コネクトが完了したら、playして、つながっているサーバーからのメディアデータをBroadcastStreamでミラーするなり、publishしてほかのサーバーに投げ返すなり好きにすればOKです。
4:IRtmpClientEx.onInvokeやRtmpClientEx.invokeを利用して他のサーバーの関数呼び出しを実行するのもいいと思う。
5:SharedObjectの実装に関しては、昔しらべたときには受け取りは問題ありませんでしたが、送信に難ありでした。(最新のRed5 1.0.0になったときに修正されてるかもしれません。後日しらべる予定)
rtmpClient = new RtmpClientEx();
rtmpClient.setScope(scope);
rtmpClient.setServer(server);
rtmpClient.setPort(port);
rtmpClient.setApplication(application);
rtmpClient.setListener(new RepeatListener(rtmpClient, new StreamListener(outputStream)));
rtmpClient.connect();
listenerをつけることで接続イベントや関数の実行イベント等やりとりしてます。
たぶんこのlistenerにはSharedObjectの部分将来的に追加するかも。
現行のRed5 RC2(僕が利用しているのはRev.4219)では、SharedObject以外はだいたい満足のいく動作します。
少々不満があるのはonInvokeの返答まわりです。BaseRTMPClientHandlerの実装がたぶん間違っていて、場合によってはエラーになってしまうのでそこはRtmpClientEx側で修正してあります。
onInvokeの返答動作に関してですが、暫定的にIServiceCallを引数として渡してあります。
サーバーから呼び出された関数:call.getServiceMethodName();
サーバーから渡される引数:call.getArguments();
サーバーに応答する返答:call.setStatus(Call.STATUS_SUCCESS_RESULT);を設置してから、返答としてonInvokeがreturnすればOK。
となります。
無理に使わずにRtmpClientEx.invokeを利用して、呼び出し元サーバー側の関数を実行して返答して返した方が無難だと思いますけど。
安定性の件で懸念があり、過去のバージョンのRed5を使っている方もいるかと思いますが、過去のバージョンでRtmpClientExを使うのはお勧めできません。
0.8.0のころはBroadcastStreamに流すとタイムスタンプがおかしくなるのか、なぜか高速再生され、パケットがながれてくるたびに、再生→しばらく停止→再生を繰り返していました。
0.9.1のころに修正されましたが、publishする方がまだきちんと動作していませんでした。
んで、1.0.0のときにpublish側の実装も見直されたようできちんと動作するようになったという・・・
RtmpClientの使い方その1
liverepeaterでも利用したRed5のRtmpClient実装についてのメモ書き
Red5_phpのリポジトリにも登録してありますが、この実装はBroadcastStreamとIRtmpClientEx、RtmpClientExの3つがセットでできてます。
BroadcastStreamは別のサーバーから流されてきたメディアデータをサーバーにつながっているユーザーに視聴させるためのIBroadcastStreamのインターフェイスに乗っ取ったもの
RtmpClientExはRTMPClientを拡張したクラス、まぁどちらかというとBaseRTMPClientHandlerを元にしてあります。
IRtmpClientExはRtmpClientExで利用するイベントリスナー用のインターフェイスです。
今回はBroadcastStreamについてちょっとメモ書き書いておきます。
このクラスの元ネタはXuggleというJava上でffmpeg(動画変換ソフト)を利用するためのライブラリの一部です。XuggleではJNIというJavaからOS依存コードを呼び出す方法でffmpegのコマンドを実行するみたいなことをやっているようです。
Xuggleの一部にXuggle-Xuggler-Red5というRed5用の拡張が存在し、そこにRed5に流している映像を変換して別の映像にして、視聴できるようにするというものがあります。
[放送者]→[サーバー]→[ffmpeg]→[サーバー]→[視聴者]
こんな流れです。
単に変換するだけでなく、サーバーからffmpegに渡すとき、もしくは、ffmpegからサーバーにデータを戻すときの2カ所でフレームに手を加えることができるので、画面を上限左右反転したり、ピクセルの色をいじって白黒画像に変換したり、特定のものの認識をしたりということができます。
内部でffmpegによる変換を加えることになるので、サーバーにはかなり負荷がかかることになりますが、H.264変換や画像のサイズ変換等も可能です。
最近のffmpegはlibrtmpに対応していて(これもxuggleの開発者の仕事みたいですが)、ffmpegのプロセスに直接rtmpサーバーに接続させたり、rtmpサーバーにデータ流したりできるので、変換そのものはあまり恩恵がないかもしれませんね。
そういう意味では、liverepeaterの処理は別になくてもffmpegで事足ります。(ただしroomを使ったりクライアントの関数実行をしたり等するときちんと動作しなかったりしますから、その点はliverepeaterの実装の方が優れてますね。)
さて、話は戻りますが、このxuggle-xuggler-red5の中で、サーバー内では自分がうけとったメディア情報を別の名前のストリームとして視聴者がみることができるようにする実装があります。これがBroadcastStreamのクラスというわけです。
使い方は以下のとおりです。
1:BroadcastStreamのオブジェクトをつくる。
2:名前やスコープ情報をセットする。
3:ProviderServiceをbeanから取得してそこにBroadcastStreamを登録
4:BroadcastScoepをProviderServiceから取得してBroadcastStreamを登録
あとは、IEventの形のメディアデータをファイルからとったり、別のサーバーからRtmpClientExでもってきたり、自分のサーバーのstreamからとったりしたものをdispatchEventになげてやればOKという形になります。
BroadcastStream outputStream;
outputStream = new BroadcastStream(name);
outputStream.setScope(scope);
IProviderService service = (IProviderService)getContext().getBean(IProviderService.BEAN_NAME);
if(service.registerBroadcastStream(scope, name, outputStream)) {
IBroadcastScope bsScope = (BroadcastScope) service.getLiveProviderInput(scope, name, true);
bsScope.setAttribute(IBroadcastScope.STREAM_ATTRIBUTE, outputStream);
}
else {
throw new RuntimeException("Failed to make mirroring stream");
}
このBroadcastStreamは、ほぼ問題ないのですが、Xuggle-xuggler-red5や僕のつくったプログラム上での扱い方が完璧ではないようです。
まず、ストリームをサービスに登録する上記の部分はいいのですが、逆に解除する方法はなにかがたりないようです。
BroadcastStreamの中に独自実装としてterminateGhostConnectionをいれてありますが、そこにのせているとおり登録したBroadcastStreamを解除しなければいけないです。
ただし、誰かがそのストリームにつながっている状態で(放送していなくてもつながっているならやってはいけないです。)登録解除を実行しようとすると内部のメモリー上に、放送中であるというデータが変な風にのこるらしく、Flashプレーヤーで放送を開始しようとしてもBadNameがでるようになり、もう放送できなくなってしまうというバグがあります。
また、start() stop()の関数に独自実装を追加してありますが、statusの放送はじめました、終了しましたというメッセージをクライアントにおくることは可能なんですが、内部動作的にストリームを停止することはできません。
ニュアンスが伝わりにくいと思いますが、普通につながっているサーバーで放送を停止するとサーバーが停止したことを理解して、あたらしく別のクライアントが接続してplayを開始してもパケットデータがいっさい送られてきません。
ところが、BroadcastStreamをベースにしているチャンネルの場合playを開始すると最後に流れたパケットデータが送られてきてしまい、静止画が表示されてしまいます。
一応mLivePipeのunsubscribeを送ると止めることができるのですが、今後は再開することができないという・・・
まぁ、バグではありますが、あるからといって運用に問題がでるところではないですけど。
このBroadcastStreamの元ネタはたぶんRed5のClientBroadcastStreamだと思います。
ClientBroadcastStreamはFlashからRed5に放送をしたときに内部で利用しているクラスでRed5チームによりメンテナンスされてるようです。こっちはしっかり動作しますが、publishするクライアントとの癒着がひどくて、そのまま切り離しで一部機能を使うということはできないようでした。
BroadcastStreamに関してはこんなところですね。
追記:
そうそう大切なこと書き忘れていました。
BroadcastStreamのdispatchEvent関数のRTMPMessageをつくる部分ですが、Red5 1.0.0の開発中にコンストラクタがprivate化されて、かわりにbuildメソッドが追加されています。よって、古いバージョンのRed5 1.0.0を使う場合は一部変更して、再コンパイルする必要がありますので、ご注意ください。
Red5_phpのリポジトリにも登録してありますが、この実装はBroadcastStreamとIRtmpClientEx、RtmpClientExの3つがセットでできてます。
BroadcastStreamは別のサーバーから流されてきたメディアデータをサーバーにつながっているユーザーに視聴させるためのIBroadcastStreamのインターフェイスに乗っ取ったもの
RtmpClientExはRTMPClientを拡張したクラス、まぁどちらかというとBaseRTMPClientHandlerを元にしてあります。
IRtmpClientExはRtmpClientExで利用するイベントリスナー用のインターフェイスです。
今回はBroadcastStreamについてちょっとメモ書き書いておきます。
このクラスの元ネタはXuggleというJava上でffmpeg(動画変換ソフト)を利用するためのライブラリの一部です。XuggleではJNIというJavaからOS依存コードを呼び出す方法でffmpegのコマンドを実行するみたいなことをやっているようです。
Xuggleの一部にXuggle-Xuggler-Red5というRed5用の拡張が存在し、そこにRed5に流している映像を変換して別の映像にして、視聴できるようにするというものがあります。
[放送者]→[サーバー]→[ffmpeg]→[サーバー]→[視聴者]
こんな流れです。
単に変換するだけでなく、サーバーからffmpegに渡すとき、もしくは、ffmpegからサーバーにデータを戻すときの2カ所でフレームに手を加えることができるので、画面を上限左右反転したり、ピクセルの色をいじって白黒画像に変換したり、特定のものの認識をしたりということができます。
内部でffmpegによる変換を加えることになるので、サーバーにはかなり負荷がかかることになりますが、H.264変換や画像のサイズ変換等も可能です。
最近のffmpegはlibrtmpに対応していて(これもxuggleの開発者の仕事みたいですが)、ffmpegのプロセスに直接rtmpサーバーに接続させたり、rtmpサーバーにデータ流したりできるので、変換そのものはあまり恩恵がないかもしれませんね。
そういう意味では、liverepeaterの処理は別になくてもffmpegで事足ります。(ただしroomを使ったりクライアントの関数実行をしたり等するときちんと動作しなかったりしますから、その点はliverepeaterの実装の方が優れてますね。)
さて、話は戻りますが、このxuggle-xuggler-red5の中で、サーバー内では自分がうけとったメディア情報を別の名前のストリームとして視聴者がみることができるようにする実装があります。これがBroadcastStreamのクラスというわけです。
使い方は以下のとおりです。
1:BroadcastStreamのオブジェクトをつくる。
2:名前やスコープ情報をセットする。
3:ProviderServiceをbeanから取得してそこにBroadcastStreamを登録
4:BroadcastScoepをProviderServiceから取得してBroadcastStreamを登録
あとは、IEventの形のメディアデータをファイルからとったり、別のサーバーからRtmpClientExでもってきたり、自分のサーバーのstreamからとったりしたものをdispatchEventになげてやればOKという形になります。
BroadcastStream outputStream;
outputStream = new BroadcastStream(name);
outputStream.setScope(scope);
IProviderService service = (IProviderService)getContext().getBean(IProviderService.BEAN_NAME);
if(service.registerBroadcastStream(scope, name, outputStream)) {
IBroadcastScope bsScope = (BroadcastScope) service.getLiveProviderInput(scope, name, true);
bsScope.setAttribute(IBroadcastScope.STREAM_ATTRIBUTE, outputStream);
}
else {
throw new RuntimeException("Failed to make mirroring stream");
}
このBroadcastStreamは、ほぼ問題ないのですが、Xuggle-xuggler-red5や僕のつくったプログラム上での扱い方が完璧ではないようです。
まず、ストリームをサービスに登録する上記の部分はいいのですが、逆に解除する方法はなにかがたりないようです。
BroadcastStreamの中に独自実装としてterminateGhostConnectionをいれてありますが、そこにのせているとおり登録したBroadcastStreamを解除しなければいけないです。
ただし、誰かがそのストリームにつながっている状態で(放送していなくてもつながっているならやってはいけないです。)登録解除を実行しようとすると内部のメモリー上に、放送中であるというデータが変な風にのこるらしく、Flashプレーヤーで放送を開始しようとしてもBadNameがでるようになり、もう放送できなくなってしまうというバグがあります。
また、start() stop()の関数に独自実装を追加してありますが、statusの放送はじめました、終了しましたというメッセージをクライアントにおくることは可能なんですが、内部動作的にストリームを停止することはできません。
ニュアンスが伝わりにくいと思いますが、普通につながっているサーバーで放送を停止するとサーバーが停止したことを理解して、あたらしく別のクライアントが接続してplayを開始してもパケットデータがいっさい送られてきません。
ところが、BroadcastStreamをベースにしているチャンネルの場合playを開始すると最後に流れたパケットデータが送られてきてしまい、静止画が表示されてしまいます。
一応mLivePipeのunsubscribeを送ると止めることができるのですが、今後は再開することができないという・・・
まぁ、バグではありますが、あるからといって運用に問題がでるところではないですけど。
このBroadcastStreamの元ネタはたぶんRed5のClientBroadcastStreamだと思います。
ClientBroadcastStreamはFlashからRed5に放送をしたときに内部で利用しているクラスでRed5チームによりメンテナンスされてるようです。こっちはしっかり動作しますが、publishするクライアントとの癒着がひどくて、そのまま切り離しで一部機能を使うということはできないようでした。
BroadcastStreamに関してはこんなところですね。
追記:
そうそう大切なこと書き忘れていました。
BroadcastStreamのdispatchEvent関数のRTMPMessageをつくる部分ですが、Red5 1.0.0の開発中にコンストラクタがprivate化されて、かわりにbuildメソッドが追加されています。よって、古いバージョンのRed5 1.0.0を使う場合は一部変更して、再コンパイルする必要がありますので、ご注意ください。
liverepeaterつくってみた。(RTMPClientの使い方その0)
Wowzaにはクラスタリング用としてliverepeaterというものがあります。
サーバーAでやっているメディアデータをサーバーBにつないでも見ることができるというものです。
これでサーバーの接続を複数のサーバーにわけることができるので1つの配信を多人数に提供することができる。というものです。
これを今回実装してみました。
まぁRed5にもclusterの方式はあって、こちらはmrtmpという独自の通信プロトコルをつかって映像音声だけでなく内部の命令もミラーしてしまうので、正直こちらの方が使い勝手は上なのですが、Red5 0.9.0以降ではメンテナンスがされていなかったらしく、動作させるにはconfの中の設定をいじったり、場合によっては大本のプログラムの修正が必要になります。
以前1.0.0 RC1で実験しようとしたときにはうまく動作させられませんでした。
またこの方式ではシステムをすべてoriginに頼ることになるので、メディアを利用しないサーバーではあまり意味がありませんでした。
幸いRed5にはRTMPClientというFlashプレーヤーと同じように他のRtmpサーバーに接続しにいってデータのやりとりをするというクラス群が存在するので、これを利用してなんとかすることにしました。
僕のつくったliverepeaterの強みは
・純粋なRtmpクライアントとして接続させているので、接続する先のサーバーはRed5でもWowzaでも(FlashMediaServerでも:これは未検証)接続して動作すること。
・子世代だけでなく、何世代でも再接続させることが可能なこと
(A←B←C←D←Eの接続はテスト済み)
の2つですね。
サーバーAでやっているメディアデータをサーバーBにつないでも見ることができるというものです。
これでサーバーの接続を複数のサーバーにわけることができるので1つの配信を多人数に提供することができる。というものです。
これを今回実装してみました。
まぁRed5にもclusterの方式はあって、こちらはmrtmpという独自の通信プロトコルをつかって映像音声だけでなく内部の命令もミラーしてしまうので、正直こちらの方が使い勝手は上なのですが、Red5 0.9.0以降ではメンテナンスがされていなかったらしく、動作させるにはconfの中の設定をいじったり、場合によっては大本のプログラムの修正が必要になります。
以前1.0.0 RC1で実験しようとしたときにはうまく動作させられませんでした。
またこの方式ではシステムをすべてoriginに頼ることになるので、メディアを利用しないサーバーではあまり意味がありませんでした。
幸いRed5にはRTMPClientというFlashプレーヤーと同じように他のRtmpサーバーに接続しにいってデータのやりとりをするというクラス群が存在するので、これを利用してなんとかすることにしました。
僕のつくったliverepeaterの強みは
・純粋なRtmpクライアントとして接続させているので、接続する先のサーバーはRed5でもWowzaでも(FlashMediaServerでも:これは未検証)接続して動作すること。
・子世代だけでなく、何世代でも再接続させることが可能なこと
(A←B←C←D←Eの接続はテスト済み)
の2つですね。
基本的にはred5-web.xmlのweb.handlerのところにpropertyとして接続しにいくサーバー、ポート、アプリケーションを設定すれば、データの通信を行います。
関数のデータのやりとりまわり。xuggleから流用したBroadcastStreamまわりの実装。
について数回にわけてブログにこんなことで苦労したと書ければと思ってます。
2011年5月7日土曜日
他のサーバーに接続しにいって、放送を奪う。
他のRtmpサーバーに接続しにいって放送を奪うやり方。
必要なもの
・Red5 1.0.0の開発版(僕が利用しているのは、Rev 4219)
・Red5_phpのリポジトリにあげてるプラグインデータ
https://github.com/taktod/red5_php
・以下のプログラム
https://github.com/taktod/Red5MediaMirror
やり方
1:Red5のpluginsのところにred5_phpでつくったライブラリデータ(リポジトリにつけてあるphpPlugin.jarで多分OK)を設置する。
2:Red5MediaMirrorのプログラムの接続先を適当に変更する。
3:コンパイルしてつくったjarライブラリをwebapps/[適当なアプリ]/WEB-INF/libに設置
red5-web.xml等適宜書き換える。
4:大本のサーバーで放送開始し、Mirrorしてる方のサーバーで視聴する。
必要なもの
・Red5 1.0.0の開発版(僕が利用しているのは、Rev 4219)
・Red5_phpのリポジトリにあげてるプラグインデータ
https://github.com/taktod/red5_php
・以下のプログラム
https://github.com/taktod/Red5MediaMirror
やり方
1:Red5のpluginsのところにred5_phpでつくったライブラリデータ(リポジトリにつけてあるphpPlugin.jarで多分OK)を設置する。
2:Red5MediaMirrorのプログラムの接続先を適当に変更する。
3:コンパイルしてつくったjarライブラリをwebapps/[適当なアプリ]/WEB-INF/libに設置
red5-web.xml等適宜書き換える。
4:大本のサーバーで放送開始し、Mirrorしてる方のサーバーで視聴する。
RtmpClientやApacheMinaの例外を補足する。
問題が発生したときにはtry-catchを利用して例外を補足しますが、フレームワークの内部でおこってしまうと補足できないことがあります。
そのときの対処の仕方:
RtmpClientの場合
org.red5.server.net.rtmp.ClientExceptionHandlerをimplementsしたクラスをつくって補足すればOK
ApacheMinaの場合
org.apache.mina.util.ExceptionMonitorを継承したクラスをつくって補足すればOK
というわけで両方補足するならこんなクラスになります。
public class ExceptionTest extends ExceptionMonitor implements ClientExceptionHandler {
public void exceptionCaught(Throwable error) {
error.printStackTrace(); // mina
}
public void handleException(Throwable error) {
error.printStackTrace(); // rtmpClient
}
}
これをつくってからセットします。
ExceptionTest et = new ExceptionTest();
ExceptionMonitor.setInstance(et); // mina
rtmpClient.setExceptionHandler(et); // rtmpClient
これでエラーの補足をして、いろいろ処理をすればOK
ちなみに問題を引き起こした部分の例外をとりたい場合は
error.getCause();
で取得すれば調べること可能です。
んで、これをなにに使うかといいますと
RtmpClientは接続ミス(接続先のサーバーがみつからない等)の時に例外を発行し、try-catchで拾えないというわけです。
そのときの対処の仕方:
RtmpClientの場合
org.red5.server.net.rtmp.ClientExceptionHandlerをimplementsしたクラスをつくって補足すればOK
ApacheMinaの場合
org.apache.mina.util.ExceptionMonitorを継承したクラスをつくって補足すればOK
というわけで両方補足するならこんなクラスになります。
public class ExceptionTest extends ExceptionMonitor implements ClientExceptionHandler {
public void exceptionCaught(Throwable error) {
error.printStackTrace(); // mina
}
public void handleException(Throwable error) {
error.printStackTrace(); // rtmpClient
}
}
これをつくってからセットします。
ExceptionTest et = new ExceptionTest();
ExceptionMonitor.setInstance(et); // mina
rtmpClient.setExceptionHandler(et); // rtmpClient
これでエラーの補足をして、いろいろ処理をすればOK
ちなみに問題を引き起こした部分の例外をとりたい場合は
error.getCause();
で取得すれば調べること可能です。
んで、これをなにに使うかといいますと
RtmpClientは接続ミス(接続先のサーバーがみつからない等)の時に例外を発行し、try-catchで拾えないというわけです。
オブジェクトの中身を無理矢理Dumpする。
PHPにはvar_dumpだのprint_rだの
オブジェクトの中を確認する便利な関数がありますが、Javaにはないっぽいです。
System.out.println(target.toString());
こいつで吐き出してくれればわかりやすいのですが・・・そういうわけにもいきません。
というわけで、Reflectをつかって無理矢理調査してみた。
System.out.println("dump start....");
try {
Class clazz = target.getClass();
System.out.println(clazz);
for(Field f : clazz.getFields()) {
System.out.print(f.getName() + ":");
try {
f.setAccessible(true);
Object fieldval = f.get(target);
System.out.println(fieldval.toString());
}
catch(Exception e) {
System.out.println();
}
}
for(Field f : clazz.getDeclaredFields()) {
System.out.print(f.getName() + ":");
try {
f.setAccessible(true);
Object fieldval = f.get(target);
System.out.println(fieldval.toString());
}
catch(Exception e) {
System.out.println();
}
}
}
catch(Exception e) {
e.printStackTrace();
}
System.out.println("dump end....");
こんな感じ。
でも継承元のクラスのデータまでは取得できないっぽい。改良の余地ありですね。
オブジェクトの中を確認する便利な関数がありますが、Javaにはないっぽいです。
System.out.println(target.toString());
こいつで吐き出してくれればわかりやすいのですが・・・そういうわけにもいきません。
というわけで、Reflectをつかって無理矢理調査してみた。
System.out.println("dump start....");
try {
Class clazz = target.getClass();
System.out.println(clazz);
for(Field f : clazz.getFields()) {
System.out.print(f.getName() + ":");
try {
f.setAccessible(true);
Object fieldval = f.get(target);
System.out.println(fieldval.toString());
}
catch(Exception e) {
System.out.println();
}
}
for(Field f : clazz.getDeclaredFields()) {
System.out.print(f.getName() + ":");
try {
f.setAccessible(true);
Object fieldval = f.get(target);
System.out.println(fieldval.toString());
}
catch(Exception e) {
System.out.println();
}
}
}
catch(Exception e) {
e.printStackTrace();
}
System.out.println("dump end....");
こんな感じ。
でも継承元のクラスのデータまでは取得できないっぽい。改良の余地ありですね。
java.io.tmpdirってどこ?
Red5のehcacheのデータがロックされたっぽくてうまく動作しなくなってしまったので
修正しようとehcacheのデータをディスクから強制削除しようと思います。
んで、conf/ehcache.xmlの中身をみたところ...
java.io.tmpdir/red5
としか書いてない。
java.io.tmpdirってどこやねんということでしらべてみました。
envコマンドでさがしても見つからなかったのですがJavaのSystemプロパティーをみないとだめっぽいです。
Properties prop = System.getProperties();
prop.list(System.out);
これを実行するとほかのリストと同じように、つらつらとでてきます。
iMacの場合は/var/folders/nB/nBAoJZS.......
わかるかいorz
/tmp/とかのディレクトリ以下においてくれればわかりやすいものを・・・
修正しようとehcacheのデータをディスクから強制削除しようと思います。
んで、conf/ehcache.xmlの中身をみたところ...
java.io.tmpdir/red5
としか書いてない。
java.io.tmpdirってどこやねんということでしらべてみました。
envコマンドでさがしても見つからなかったのですがJavaのSystemプロパティーをみないとだめっぽいです。
Properties prop = System.getProperties();
prop.list(System.out);
これを実行するとほかのリストと同じように、つらつらとでてきます。
iMacの場合は/var/folders/nB/nBAoJZS.......
わかるかいorz
/tmp/とかのディレクトリ以下においてくれればわかりやすいものを・・・
2011年5月6日金曜日
SoftReferenceに関して調べてみた。
xmlsocketのサーバーアプリでメモリー上にデータを残すために、属性を実装しようと思う。
基本的にMapにデータをいれておくわけだが、普通にHashMapとかにしておくと、そのうちOut of Memoryが発生してプログラムがこわれてしまう。
そこでReferenceを使おうと思うのですが、WeakHashMapだとGCがあるたびにデータが消し飛びます。
そこで目をつけたのがSoftReferenceの実装
というわけでメモリーの仕様状況をさっくり調べてみた。
まず普通のHashMapの場合 Map<Object, Object>
基本的にMapにデータをいれておくわけだが、普通にHashMapとかにしておくと、そのうちOut of Memoryが発生してプログラムがこわれてしまう。
そこでReferenceを使おうと思うのですが、WeakHashMapだとGCがあるたびにデータが消し飛びます。
そこで目をつけたのがSoftReferenceの実装
というわけでメモリーの仕様状況をさっくり調べてみた。
まず普通のHashMapの場合 Map<Object, Object>
こんな感じで120Mまで使い切ってしまいました。
ためるだけためてout of memoryの例外がでて、それまで。
xmlsocketのPHP実装でつくっていたので、イベントでさらにデータのセットを行うタイマー処理をサイドまわしたら即out of memoryが再発。あたりまえですね。もうメモリーの残量はないのでうごくわけない。
続いてSoftHashMapをいれた場合 Map<Object, SoftReference<Object>>
120M近くまであがったらメモリーが解放されているのがわかります。
ただし、かならず120M近くまであがったら解放されるわけではないことがわかります。
また、一定のオブジェクトのみ参照を繰り返した場合(connect.phpで接続あるたびに一番はじめに設置した属性データを参照するようにしてました。)そのオブジェクトはいつまでたっても削除されませんでした。
どうやら頻度の多いデータは優先的にのこるようになっているようです。
また、connect.phpを途中で書き換えてとなりのマップデータを参照してみたところnullになっていました。
さらにキーの数は初期化されずにどんどん増えていきました。
というわけで
・SoftReferenceはメモリーの使用量が多くなってきたら自動で解放される。
・アクセスの少ないデータが優先で消える、それから古いデータが優先で消える。
・Out of Memoryエラーが出る前に消える
・Map<Object, SoftReference<Object>>だとキーの方のデータがたまることでOut of Memoryエラーになりかねない。
ということがわかりました。
属性のプログラムの基本方針はSoftReferenceでよさそうですね。
2011年5月5日木曜日
Quercus上での値の取り扱いについて
Quercus上の値をJava側に渡したときにどうなるか・・・
まとめておきます。
まずは基礎、どんな対応になっているか
http://caucho.com/resin-3.1/doc/quercus.xtp
基本的なデータはこちらにあります。
ここに掲載されていないもの
"あいうえお"等のConstString on PHP→ConstStringValue
class定義されたものon PHP→ObjectExtValue
配列→ArrayValueImpl
このあたり注意
Java側のメソッドを呼び出したときに自動的に型変換されるもの。
数字は基本的に戻る。ただしlongやdoubleになるようです。(例floatの範囲内でもdouble扱いになります。)
文字列もStringに戻る。
その他のものは元に戻らず。com.caucho.quercus.env.****Valueの型になったままになるらしい。
クラス定義をPHP側で実行したものはObjectExtValueのまま
クラス定義をJava側で実行し、PHP側でInstanceを生成したものは元のJavaオブジェクトに戻る。
はい、ここ注意です。ちゃんとJavaのクラスオブジェクトに変換されます。
ちなみに、クラス定義をJava側で実施し、PHP側で継承したクラスをつくり、継承したクラスのインスタンスを作った場合はObjectExtValue扱いです。
注意事項その1
そして注意が必要なのは、配列もArrayValueImplのままであり、配列の中身のデータは整数や文字列であってもJavaのオブジェクトにもどっていない。
ここ、絶対に注意です。
さらに、配列の添字として文字列をおくるとき、ArrayValueImpl相手に送るにはStringValue互換の型に変換しないと動作しません。
JavaのStringをポンといれても動作しねぇ・・・orz
注意事項その2
PHP側でクラス定義を実行し、インスタンスをつくったものをJava側に渡すとObjectExtValueになりますが、こいつを実行するにはcallMethodを呼び出してやればよい。
ただし内部の引数はすべてcom.caucho.quercus.env.Valueの型にする必要あり。
このPHP側のデータの呼び出しの型変換自動で実行してくれれば素敵なんですが、そのあたりどうなんですかね?
Servletで呼び出して実行するだけなら、基本Java側のデータを読みこんでなんかすることはないと思うから省いたのだろうか・・・
まとめておきます。
まずは基礎、どんな対応になっているか
http://caucho.com/resin-3.1/doc/quercus.xtp
基本的なデータはこちらにあります。
ここに掲載されていないもの
"あいうえお"等のConstString on PHP→ConstStringValue
class定義されたものon PHP→ObjectExtValue
配列→ArrayValueImpl
このあたり注意
Java側のメソッドを呼び出したときに自動的に型変換されるもの。
数字は基本的に戻る。ただしlongやdoubleになるようです。(例floatの範囲内でもdouble扱いになります。)
文字列もStringに戻る。
その他のものは元に戻らず。com.caucho.quercus.env.****Valueの型になったままになるらしい。
クラス定義をPHP側で実行したものはObjectExtValueのまま
クラス定義をJava側で実行し、PHP側でInstanceを生成したものは元のJavaオブジェクトに戻る。
はい、ここ注意です。ちゃんとJavaのクラスオブジェクトに変換されます。
ちなみに、クラス定義をJava側で実施し、PHP側で継承したクラスをつくり、継承したクラスのインスタンスを作った場合はObjectExtValue扱いです。
注意事項その1
そして注意が必要なのは、配列もArrayValueImplのままであり、配列の中身のデータは整数や文字列であってもJavaのオブジェクトにもどっていない。
ここ、絶対に注意です。
さらに、配列の添字として文字列をおくるとき、ArrayValueImpl相手に送るにはStringValue互換の型に変換しないと動作しません。
JavaのStringをポンといれても動作しねぇ・・・orz
注意事項その2
PHP側でクラス定義を実行し、インスタンスをつくったものをJava側に渡すとObjectExtValueになりますが、こいつを実行するにはcallMethodを呼び出してやればよい。
ただし内部の引数はすべてcom.caucho.quercus.env.Valueの型にする必要あり。
このPHP側のデータの呼び出しの型変換自動で実行してくれれば素敵なんですが、そのあたりどうなんですかね?
Servletで呼び出して実行するだけなら、基本Java側のデータを読みこんでなんかすることはないと思うから省いたのだろうか・・・
2011年5月4日水曜日
UTF8を扱いたかった。
日本語がうまく扱えないので調べてみた。
ことの発端はローカルで開発しているときには問題がでなかった文字コード問題がサーバー側で起動したらぽろぽろ問題が発生したことにある。
byteコードに直したデータを再変換かけてやってもなぜか??????等こわれた文字列になって動作できなかったです。
システムエンコードとデフォルトエンコードをしらべてみると
System.out.println(new InputStreamReader(System.in).getEncoding());
System.out.println(System.getProperty("file.encoding"));
ASCII
ANSI_X3.4-1968
という文字コードになっている模様。
System.setProperty("file.encoding", "UTF8");等やってみたものの効果なし。
仕方ないのでRed5の起動オプションに
-Dfile.encoding=UTF8
を追加しました。これがないとQuercus上でUTF8の文字列のJava PHP間での共有ができないっぽいです。
この状態でエンコードを調べると
UTF8
UTF8
となりました。
なお、先に問題になっていた、Quercus上での2重エンコードしてしまってUTF8が化ける問題はこの件とは別ものです。やはりbin2hexで16進数文字列に変更してやってから変換かけないとだめっぽいです。
ことの発端はローカルで開発しているときには問題がでなかった文字コード問題がサーバー側で起動したらぽろぽろ問題が発生したことにある。
byteコードに直したデータを再変換かけてやってもなぜか??????等こわれた文字列になって動作できなかったです。
システムエンコードとデフォルトエンコードをしらべてみると
System.out.println(new InputStreamReader(System.in).getEncoding());
System.out.println(System.getProperty("file.encoding"));
ASCII
ANSI_X3.4-1968
という文字コードになっている模様。
System.setProperty("file.encoding", "UTF8");等やってみたものの効果なし。
仕方ないのでRed5の起動オプションに
-Dfile.encoding=UTF8
を追加しました。これがないとQuercus上でUTF8の文字列のJava PHP間での共有ができないっぽいです。
この状態でエンコードを調べると
UTF8
UTF8
となりました。
なお、先に問題になっていた、Quercus上での2重エンコードしてしまってUTF8が化ける問題はこの件とは別ものです。やはりbin2hexで16進数文字列に変更してやってから変換かけないとだめっぽいです。
2011年5月3日火曜日
QuercusでJavaからの日本語入力を取得して動作させる。(失敗事例)
Quercusでうまく日本語を使うためにいろいろやったのですが、もちろん失敗したこともあります。
というわけで失敗事例:Base64エンコードを実施する。
失敗理由は、PHP側とJava側で変換方法が一致しなかった。
・元の文字列[あいうえお]
・org.boouncycastle.util.encoders.Base64を利用した場合
Base64.encode("あいうえお".getBytes());
結果:[B@443ecfff
・org.caucho.util.Base64を利用した場合
Base64.encode("あいうえお");
結果:Pz8/Pz8=
・PHP上base64_encode
base64_encode("あいうえお");
結果:44GC44GE44GG44GI44GK
通常のPHPでコンソール実行しても同じようになりました。
decodeかけてやると元に戻せました。
というわけで失敗事例:Base64エンコードを実施する。
失敗理由は、PHP側とJava側で変換方法が一致しなかった。
・元の文字列[あいうえお]
・org.boouncycastle.util.encoders.Base64を利用した場合
Base64.encode("あいうえお".getBytes());
結果:[B@443ecfff
・org.caucho.util.Base64を利用した場合
Base64.encode("あいうえお");
結果:Pz8/Pz8=
・PHP上base64_encode
base64_encode("あいうえお");
結果:44GC44GE44GG44GI44GK
通常のPHPでコンソール実行しても同じようになりました。
decodeかけてやると元に戻せました。
QuercusでJavaからの日本語入力を取得して動作させる。
Quercusの内部設定をUTF-8で動作させ、ほかの環境がすべてUTF8の状態にしても、JavaのStringの状態で日本語を受け取ると化けまくります。
そこでなんとかしましょう。
まず成功事例
1:Java側のオブジェクトをbyteベースの文字列に変換
2:PHP側の文字列として取得
3:packで文字列に戻す。
これでうまくいきました。
また、PHP側で取得済みのStringオブジェクトを変換用のクラスに渡せばきちんと変換されました。
以下コード
0:変換対象のStringオブジェクトを応答する関数
public static String TestString() {
return "あいうえお";
}
1:byte文字列に変換
public static String toByteString(Object o) {
byte[] b = o.toString().getBytes();
StringBuilder bs = new StringBuilder();
String hex;
for(int i = 0;i < b.length; i ++) {
hex = Integer.toHexString(b[i]);
bs.append(hex.substring(hex.length() - 2));
}
return bs.toString();
}
2,3PHP側
// 文字列取得
$testStr = TestClass::TestString();
echo $testStr; // 表示させてみる。
echo " ";
// byteストリングに変換
$val = TestClass::toByteString($testStr);
echo $val;
echo " ";
// packして文字列に戻す。
echo pack("H*", $val);
echo " ";
出力はこうなります。
BDFHJ e38182e38184e38186e38188e3818a あいうえお
Javaから文字列を受け取った文字列はひたすらtoByteStringに送れば大丈夫っぽいです。
そこでなんとかしましょう。
まず成功事例
1:Java側のオブジェクトをbyteベースの文字列に変換
2:PHP側の文字列として取得
3:packで文字列に戻す。
これでうまくいきました。
また、PHP側で取得済みのStringオブジェクトを変換用のクラスに渡せばきちんと変換されました。
以下コード
0:変換対象のStringオブジェクトを応答する関数
public static String TestString() {
return "あいうえお";
}
1:byte文字列に変換
public static String toByteString(Object o) {
byte[] b = o.toString().getBytes();
StringBuilder bs = new StringBuilder();
String hex;
for(int i = 0;i < b.length; i ++) {
hex = Integer.toHexString(b[i]);
bs.append(hex.substring(hex.length() - 2));
}
return bs.toString();
}
2,3PHP側
// 文字列取得
$testStr = TestClass::TestString();
echo $testStr; // 表示させてみる。
echo " ";
// byteストリングに変換
$val = TestClass::toByteString($testStr);
echo $val;
echo " ";
// packして文字列に戻す。
echo pack("H*", $val);
echo " ";
出力はこうなります。
BDFHJ e38182e38184e38186e38188e3818a あいうえお
Javaから文字列を受け取った文字列はひたすらtoByteStringに送れば大丈夫っぽいです。
screenを使う。
screenをつかってサーバーをもっと便利に使おうと思います。
screenでできること。
・ターミナルをつけっぱなしにしておける。
・別の人とターミナルの表示を共有できる。
・1つのターミナルで複数のコンソールの管理ができる。
等々
今回はRed5プロセスを起動するわけですが以下の問題点があります。
・デーモン用のスクリプトを書くのは面倒。
・nohupで起動しておいてもいいが、標準出力のデータは確認したい。
・かといって標準出力のデータがたまりすぎると困る。
というわけでscreenを使います。
CentOSのサーバーを利用しているので、yumでscreenを導入します。
super userになってから
# yum install screen
これでOK
続いてscreenの設定をカスタマイズしておきます。
エンコードはutf8
エスケープはCtrl + zに変更
.screenrc
defencoding utf8
escape ^z^z
^zの部分はCtrl + zを入力するのではなく普通に^zとキーボードで入力します。
では早速スクリーンの実行
$ screen
見た目かわりませんが、そのままbackspaceを入力すると下の方にWuffとかでてくると思います。
スクリーンを止める。
$ exit
screenの使い方メモ(Ctrl + zに変更したのでそれをベースにしたコマンドになってます。)
Ctrl+z w 下部にリスト表示
Ctrl+z A スクリーンの名前変更(大文字のAであることに注意)
Ctrl+z space 次のコンソールへ移動
Ctrl+z d screenを抜けて、通常のコンソールに移動する。
screen -x スクリーンに復帰する。(マルチ)
screen -r スクリーンに復帰する。(別のユーザーがアクセスしていたらエラー)
この位網羅しておけば、screenはばっちり。
追記として以下もしってれば、なおよし
Ctrl+z S スクリーンを上下分割(大文字)
Ctrl+z tab スクリーン移動
Ctrl+z Q 現在領域以外スクリーン破棄(大文字)
Ctrl+z X 現在領域のスクリーン破棄(大文字)*パスワード入力を要求される。
screenでできること。
・ターミナルをつけっぱなしにしておける。
・別の人とターミナルの表示を共有できる。
・1つのターミナルで複数のコンソールの管理ができる。
等々
今回はRed5プロセスを起動するわけですが以下の問題点があります。
・デーモン用のスクリプトを書くのは面倒。
・nohupで起動しておいてもいいが、標準出力のデータは確認したい。
・かといって標準出力のデータがたまりすぎると困る。
というわけでscreenを使います。
CentOSのサーバーを利用しているので、yumでscreenを導入します。
super userになってから
# yum install screen
これでOK
続いてscreenの設定をカスタマイズしておきます。
エンコードはutf8
エスケープはCtrl + zに変更
.screenrc
defencoding utf8
escape ^z^z
^zの部分はCtrl + zを入力するのではなく普通に^zとキーボードで入力します。
では早速スクリーンの実行
$ screen
見た目かわりませんが、そのままbackspaceを入力すると下の方にWuffとかでてくると思います。
スクリーンを止める。
$ exit
screenの使い方メモ(Ctrl + zに変更したのでそれをベースにしたコマンドになってます。)
Ctrl+z w 下部にリスト表示
Ctrl+z A スクリーンの名前変更(大文字のAであることに注意)
Ctrl+z space 次のコンソールへ移動
Ctrl+z d screenを抜けて、通常のコンソールに移動する。
screen -x スクリーンに復帰する。(マルチ)
screen -r スクリーンに復帰する。(別のユーザーがアクセスしていたらエラー)
この位網羅しておけば、screenはばっちり。
追記として以下もしってれば、なおよし
Ctrl+z S スクリーンを上下分割(大文字)
Ctrl+z tab スクリーン移動
Ctrl+z Q 現在領域以外スクリーン破棄(大文字)
Ctrl+z X 現在領域のスクリーン破棄(大文字)*パスワード入力を要求される。
2011年5月2日月曜日
Red5_PHPの拡張命令について
先の投稿で書いた
https://github.com/taktod/red5_php
このリポジトリですが、拡張としてRed5サーバーが別のRed5サーバーにクライアントとしてつなぎにいって、処理をするためのプログラムをいれようと思っています。
対象ファイルは
com.ttProject.red5.server.adapterパッケージ
ApplicationAdapterEx.java(冗長化用の拡張命令付きのApplicationAdapter)
ApplicationAdapterPhpEx.java(冗長化用の拡張命令付きのPHP用ApplicationAdapter)
com.ttProject.red5.server.adapter.libraryパッケージ
BroadcastStream.java(パケットデータをサーバーに接続しているユーザーに送るためのIBroadcastStream派生クラス(xugglerを参考にしてます。))
IRtmpClientEx.java(他のサーバーとのやり取りイベント用のインターフェイス)
RtmpClientEx.java(他のサーバーに接続しにいくためのプログラム)
の5つのファイルです。
基本的にRtmpClientExで他のサーバーからデータを取得して、BroadcastStreamで自分のサーバーにつながっているユーザーに情報を受け流す。
という処理が目標です。
注意点は
・BroadcastStream.javaの一部ですが、Red5側のRTMPMessageのクラスの動作が最近かわってしまったために、古いプログラムを使っている人は修正する必要がある。
・BroadcastStreamとRed5の本体、Clientのフラッシュプレーヤーとの情報やりとりの放送開始、停止等の命令が若干うまく動作しない。(Red5の解析がもっと必要。)
・映像をやりとりしているRtmpClientExに向かってサーバーからクライアントの関数呼び出しを実行すると映像やりとりが壊れる。(映像用と命令用と2つ接続して使えば問題なく動作する。:これはたぶんRed5のバグ)
このあたり注意が必要です。
まだ、完成してはいないので、今後の更新にご期待ください。ゴールデンウィーク中にはとりあえず完成させたいな。
何か質問等ありましたら、コメント等でどうぞ。
https://github.com/taktod/red5_php
このリポジトリですが、拡張としてRed5サーバーが別のRed5サーバーにクライアントとしてつなぎにいって、処理をするためのプログラムをいれようと思っています。
対象ファイルは
com.ttProject.red5.server.adapterパッケージ
ApplicationAdapterEx.java(冗長化用の拡張命令付きのApplicationAdapter)
ApplicationAdapterPhpEx.java(冗長化用の拡張命令付きのPHP用ApplicationAdapter)
com.ttProject.red5.server.adapter.libraryパッケージ
BroadcastStream.java(パケットデータをサーバーに接続しているユーザーに送るためのIBroadcastStream派生クラス(xugglerを参考にしてます。))
IRtmpClientEx.java(他のサーバーとのやり取りイベント用のインターフェイス)
RtmpClientEx.java(他のサーバーに接続しにいくためのプログラム)
の5つのファイルです。
基本的にRtmpClientExで他のサーバーからデータを取得して、BroadcastStreamで自分のサーバーにつながっているユーザーに情報を受け流す。
という処理が目標です。
注意点は
・BroadcastStream.javaの一部ですが、Red5側のRTMPMessageのクラスの動作が最近かわってしまったために、古いプログラムを使っている人は修正する必要がある。
・BroadcastStreamとRed5の本体、Clientのフラッシュプレーヤーとの情報やりとりの放送開始、停止等の命令が若干うまく動作しない。(Red5の解析がもっと必要。)
・映像をやりとりしているRtmpClientExに向かってサーバーからクライアントの関数呼び出しを実行すると映像やりとりが壊れる。(映像用と命令用と2つ接続して使えば問題なく動作する。:これはたぶんRed5のバグ)
このあたり注意が必要です。
まだ、完成してはいないので、今後の更新にご期待ください。ゴールデンウィーク中にはとりあえず完成させたいな。
何か質問等ありましたら、コメント等でどうぞ。
Red5上でPHP使えるようにする。
Red5上でもResinを使えば普通にServletでPHPを使うことができるように成ります。
WEB-INF/web.xmlの内部でQuercusServletを適宜設定してやればOK
ただしこれではRed5のRtmp動作の方でQuercusを利用することができません。
そこでXmlsocketサーバーと同じようにRed5でQuercusをつかったプログラムが書けるようにしてみました。
githubのリポジトリはこちら。
https://github.com/taktod/red5_php
通常ApplicationAdapter指定する、もしくはApplicationAdapterを継承したオリジナルクラスをWEB-INF/red5-web.xmlで指定しますが、かわりにApplicationAdapterPhpを指定して、WEB-INF/php/以下にPHPのディレクトリ内のプログラムを設置すれば、phpディレクトリの内部のプログラムを実行するようになります。
PHPのプログラムを変更した場合は変更したファイルの再コンパイルに関する情報が標準出力上にでます。
こんな感じ
2011/05/02 14:17:55 com.caucho.vfs.Depend logModified
情報: /Users/xxxxxxx/Documents/red5/dist/webapps/myapp2/WEB-INF/php/appConnect.php length is modified (535 -> 523)
WEB-INF/web.xmlの内部でQuercusServletを適宜設定してやればOK
ただしこれではRed5のRtmp動作の方でQuercusを利用することができません。
そこでXmlsocketサーバーと同じようにRed5でQuercusをつかったプログラムが書けるようにしてみました。
githubのリポジトリはこちら。
https://github.com/taktod/red5_php
通常ApplicationAdapter指定する、もしくはApplicationAdapterを継承したオリジナルクラスをWEB-INF/red5-web.xmlで指定しますが、かわりにApplicationAdapterPhpを指定して、WEB-INF/php/以下にPHPのディレクトリ内のプログラムを設置すれば、phpディレクトリの内部のプログラムを実行するようになります。
PHPのプログラムを変更した場合は変更したファイルの再コンパイルに関する情報が標準出力上にでます。
こんな感じ
2011/05/02 14:17:55 com.caucho.vfs.Depend logModified
情報: /Users/xxxxxxx/Documents/red5/dist/webapps/myapp2/WEB-INF/php/appConnect.php length is modified (535 -> 523)
Java側のオブジェクト等も自由に利用できるので、興味ある方はどうぞ。
2011年5月1日日曜日
xmlsocketのサーバーを書きました
https://github.com/taktod/xmlsocket-server
動作確認用として使えるswfプレーヤーは以下
http://poepoemix.appspot.com/swf/XmlSocket.swf
肝は、サーバー側処理のメインとなる部分をQuercusというJava上で動くPHPエンジンをつかうことで
・手軽なPHPで書き込めるという点
・サーバーの停止をしなくてもPHPの書き換えを実行するとその場で更新される点
・JITコンパイルにより高速な動作をする点(PHP上の処理もコンパイルされます。)
このあたりでしょうか。
Red5のアプリケーション拡張にも使いたいです。特にサーバーをとめなくてもコードを書き換えできる点がかなりgood.
https://github.com/taktod/xmlsocket-server
動作確認用として使えるswfプレーヤーは以下
http://poepoemix.appspot.com/swf/XmlSocket.swf
肝は、サーバー側処理のメインとなる部分をQuercusというJava上で動くPHPエンジンをつかうことで
・手軽なPHPで書き込めるという点
・サーバーの停止をしなくてもPHPの書き換えを実行するとその場で更新される点
・JITコンパイルにより高速な動作をする点(PHP上の処理もコンパイルされます。)
このあたりでしょうか。
Red5のアプリケーション拡張にも使いたいです。特にサーバーをとめなくてもコードを書き換えできる点がかなりgood.
登録:
投稿 (Atom)