2012年3月31日土曜日

websocketについて再度やってみる。その2

データの送受信まわりの動作について調べてみました。
サンプルはあるもののどうも英語の仕様を読み解くのは苦手ですね。
今回もhybi-00とRFC6455の両方について書いておきます。

まずは単純なhybi-00の方。
こちらは単にUTF-8のバイトデータの先頭と最終に0x00と0xffをつければOK
たとえばHelloだとバイトデータが
0x48 0x65 0x6c 0x6c 0x6fになるので
0x00 0x48 0x65 0x6c 0x6c 0x6f 0xff
にして、送信すればOKです。受け取ったデータも同じ。

続いてちょっとややこしいRFC6455の方。
こちらでは、テキストデータ、バイナリデータのほかに、pingフラグ、pongフラグ、切断命令等々あります。またXORによるマスク指定もあります。

まず一番単純なデータから
送信データはHello
0x48 0x65 0x6c 0x6c 0x6f
ヘッダデータが必要なのでそのデータを追加して
0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f
このようになります。
先頭の1バイトはビットに変換すると、
1000 0001になります。
先頭1ビットは今回のパケットで命令がおわるかどうかの指定です。1がはいっている場合はおわる。0がはいっている場合は、あとから別途命令が送られてくる印になります。
次の3ビットは0に予約されています。
後ろの4ビットはデータが何であるかの指定です。
1:テキスト 2:バイナリ 3-7は将来に予約 8はコネクションを閉じる 9はping Aはpong、B-Fは将来に予約となっています。
よって0x81は今回の通信で終了するテキストデータ
0x01はあとで別のデータが追記されるテキストデータ
0x82は今回の通信で終了するバイナリデータといった具合になります。
次の1バイトはマスクフラグとメッセージの長さになります。
例では0x05になっています。ビットに変換すると
0000 0101となります。
先頭の1ビットが0になっているので、今回はマスクはなし。のこりのビットデータによるとデータが5になるので、後ろの5バイトがメッセージデータということになります。
後ろの5バイトの0x48 0x65 0x6c 0x6c 0x6fがUTFコードでHelloという文字になりますので、このデータは[今回の転送のみで完了するデータのマスクなし、5バイトのデータHello]
ということになります。

続いてあとで別のデータが追記される場合のサンプル
0x01 0x03 0x48 0x65 0x6c
0x81 0x02 0x6c 0x6f
こんな感じになります。
まず始めのデータ

0x01 0x03 0x48 0x65 0x6c
1バイト目が0000 0001になっているので、今回のメッセージで完了しないデータかつ、テキストデータという指定になります。
2バイト目、0000 0011になっているので、マスクはなし。長さは3バイト
んで、うしろの3バイトをみるとHelになっています。
2つ目のデータ
0x81 0x02 0x6c 0x6f
1バイト目が1000 0001になっているので、今回のメッセージで完了する。かつテキスト
2バイト目が0000 0010になっているので、マスクフラグはなし、長さは2バイト
んで、うしろの2バイトをみるとloになっています。
前のデータとあわせるとHelloというデータになります。

続いてマスクがある場合の動作
例Hello
0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58
こんなデータになります。
まず先頭1バイトですが、1000 0001となっているので、今回でおわりのテキストデータとなります。
次に2バイト目 1000 0101となり、マスクビットが設定されている(先頭の1ビット目)でサイズは5バイトとなります。
そこから続く4バイトはマスク用の値となります。
0x37 0xfa 0x21 0x3dが対象です。
最後の5バイトがHelloを表しています。
0x7f 0x9f 0x4d 0x51 0x58
で、復号の仕方なわけですが、XORでマスク用の値と掛け合わせてやればOKです。
0x7fは0x37と、0x9fは0xfaと・・・という順番で組み合わせていき、4つ目までおわったらまた始めに戻ります。なので5文字目の0x58は0x37と掛け合わせます。
0x37とx07fはビットにすると
0011 0111 (0x37)
0111 1111 (0x7f)
xorにかけてやると
0100 1000 (0x48)になり。これはHを表します。

どうように0xfaと0x9fでは
1111 1010 (0xfa)
1001 1111 (0x9f)

0110 0101 (0x65)になりe

0010 0001 (0x21)
0100 1101 (0x4d)
0110 1100 (0x6c) → l

0011 1101 (0x3d)
0101 0001 (0x51)
0110 1100 (0x6c) → l

0011 0111 (0x37)
0101 1000 (0x58)
0110 1111 (0x6f) → o
というわけで、Helloとなるわけです。

以上でwebsocketで送信されてくるデータをUTF-8の文字に変えたり元に戻したりできるようになりました。

websocketについて再度やってみる。

websocket・・・html5の目玉の1つですね。

前々から興味もっててhttps://github.com/taktod/webSocketForRed5こんなのつくったりしていたわけですが、最近jqmobiを使うようになってまた、興味がわいてきました。

で、ここ数日websocketの動作について調査しつついろいろやっていたわけです。
上記のred5用のプラグインをきちんと整備して、とりあえずrtmpとwebsocketで情報を共有するチャットをとりあえずの目標にするわけですが、今日はhandshakeについてのメモ書き書いておきます。

websocketの動作は次のようなものになっています。
1:クライアントからhandshakeの要求が送られる
2:サーバーから応答のデータを送り返す。
ここでhandshake完了つながったままになる。
3:メッセージを送りたいときにおくる。データは0x00 (データ(UTF8)) 0xFFという形で送る。(この部分はRFC6455で大きくかわってしまったようです。)

だったとおもいます。
 websocketにはいくつかバージョンがあり、ブラウザの対応もまちまちです。
そしてサーバーの対応もまちまちです。
でも、主なものは2つです。
hybi-00とRFC6455の2つ

hybi-00は昔つくったプログラムでサポートした古いバージョン。セキュリティーがどうのこうのという理由でFirefoxが見切りをつけたバージョンです。
対応ブラウザで確認したものは手持ちのiphone4SのモバイルサファリとiMacにはいっているsafariはこちらの動作をしていました。

RFC6455は現行のFirefox 11、google chromeがサポートしているあたらしいバージョン。

とりあえずこの2つサポートしておけば、たいていのブラウザで動作可能になると思われます。
今のところ理解したのは、handshakeの仕方が違う。その他は多分同じということなので、そこを書きなぐっておきます。

hybi-00のhandshake(ネタ元はこちら)
クライアントからのデータ送信

GET /demo HTTP/1.1
        Host: example.com
        Connection: Upgrade
        Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
        Sec-WebSocket-Protocol: sample
        Upgrade: WebSocket
        Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
        Origin: http://example.com

        ^n:ds[4U

サーバーからの応答

HTTP/1.1 101 WebSocket Protocol Handshake
        Upgrade: WebSocket
        Connection: Upgrade
        Sec-WebSocket-Origin: http://example.com
        Sec-WebSocket-Location: ws://example.com/demo
        Sec-WebSocket-Protocol: sample

        8jKS'y:G*Co,Wxa-

赤文字にした部分を操作して青文字にしたデータを作成します。
やり方は次のとおり。
Key1とKey2を数値データに戻します。
例としてKey2をあげると
> 12998 5 Y3 1  .P00
数字の部分を取り出して空白の数を数えます。
> 1299853100 と 5
数字の部分/空白の数の値をだします。
> 259970620
hexに直す
> 0x0F7ED63C
同じことをkey1にもやります。
> 0x316E4113
16のHexを作成します。key1 key2 と最後の赤文字のやつとなります。
> 0x31 0x6E 0x41 0x13 0x0F 0x7E 0xD6 0x3C '^' 'n' ':' 'd' 's' '[' '4' 'U'
これをMD5にかけると・・・
> 8jKS'y:G*Co,Wxa-
になるというわけです。

つづいてRFC6455の方。(ネタもとはこちら)
クライアントからの要求はこんな感じ

GET /chat HTTP/1.1
        Host: server.example.com
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
        Origin: http://example.com
        Sec-WebSocket-Protocol: chat, superchat
        Sec-WebSocket-Version: 13

サーバーの応答はこんな感じ

HTTP/1.1 101 Switching Protocols
        Upgrade: websocket
        Connection: Upgrade
        Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
        Sec-WebSocket-Protocol: chat

なぜかクライアントによってはProtocolの部分が抜け落ちていたりしていました。まぁ気にしないけど。
では計算のやり方。
必要なのは赤字のKeyの部分と、定義されているGUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)の2つです。
いきなりGUIDがでてきて、これなに?と思うとおもいますが、この値のGUIDでないとだめみたいです。
キー
> gDhllHNhbXBsZSBub25jZQ==
ここにGUIDを連結します。

> gDhllHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11
これをSHA1で変換かけてやると
> s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
になります。
あとは、サーバー応答のフォーマットにあわせてデータを送り返してやればOKとなります。

Handshake以外の部分の動作についてはまたあとで調査しますので、また記事みてね。
ではでは

2012年3月7日水曜日

HttpTakStreamingのデモを公開しておきます。

httpTakStreamingの動作デモを公開しておきます。

何かしら不具合が発生した場合に、公開デモを停止することがあることをご了承ください。

1:red5のpublisher等rtmpの配信のできるデモを準備する。
2:rtmp://49.212.39.17/htsに向かってなにかしら配信を実行する。
3:http://49.212.39.17/hts/player/player.htmlにアクセスする。
4:プレーヤーのテキストボックスの部分にhttp://49.212.39.17/hts?path=/default/hts/配信名をいれる。
5:playボタンを押す。

これで試すことができます。

例としては、rtmp://49.212.39.17/htsにむかってlivestreamという名前で放送をした場合
プレーヤーに渡すhttpTakStreamingのパスはhttp://49.212.39.17/hts?path=/default/hts/livestreamになります。

Flashプレーヤーの動作は現在のところ調整中ですが、ChromeやFirefoxのNetworkデータを確認すると、ファイルがぞくぞくとDownloadされ、内容が放送されるのがわかるとおもいます。

例として、red5のpublisherデモを使うとします。
locationは49.212.39.17/htsにあわせる。

放送する名前は適当につける。

http://49.212.39.17/hts/player/player.htmlの設定のパスをtestStreamにあわせる。
この場合はhttp://49.212.39.17/hts?path=/default/hts/testStream

放送の映像がはじまったらGoogleChromeのNetwork等で確認するとファイルの断片がどんどん落ちてくる。

という形になります。
とりあえずサンプルはこんな感じ。試してやってください。

Flex(actionScript)でSetTimeoutは、つかわない方がいいらしい。

HttpTakStreamingでは、setTimeoutを大量につかうようにプログラミングをしてあるのですが、今日こんな記事をみつけた。

連続稼動でsetTimeout関数が途中で止まってしまう
http://www.fxug.net/modules/xhnewbb/viewtopic.php?topic_id=4204

FlexのAPIによるとsetTimeoutはあまりつかわないで、Timerをつかって処理した方がのぞましいとしてるみたいですね。

http://livedocs.adobe.com/flash/9.0_jp/ActionScriptLangRefV3/flash/utils/package.html#setTimeout%28%29
このメソッドを使用する代わりに、repeatCount パラメータを 1 (タイマーを 1 回のみ実行する設定) にして、指定した間隔で Timer オブジェクトを作成することを検討してください。

とのことです。
というわけで今日はHttpTakStreamingのsetTimeoutをTimerに変えるところから作業するか・・・

2012年3月4日日曜日

FlvデータをFlashPlayerに送り届ける話。

前回の記事でNetStream.appendBytesについてちょこっとだけ書きましたが、Flvデータをガンガンおくってやれば、映像を流すことができます。

んで流す方法の話。
まず僕が興味をもったのは、Rtmfpをつかう方法
rtmfpでもFlashのストリームを流すことができますが、Flashが吐き出すデータに限られます。これだとFlash以外のソース(FMEとか)での配信がながせず、映像は残念なものになります。
そこでデータの送信を独自に作成し、appendBytesをつかってデータを再生してやれば綺麗な映像を安定して流すことができます。
やってみた方法は
1:Red5サーバーにデータを流す。
2:そのデータをバイトデータとしてrtmpメッセージとして送り返す。
3:rtmpメッセージをrtmfpのネットワークで共有する。
4:うけとったクライアントはそれぞれ映像をappendBytesで再生する。
というやりかた。

rtmfpのnetGroupで共有すればいいかなと思ったんですが、netGroupの共有ではデータの送信が間に合いませんでしたので、netStreamのデータ通信でやりとりする形に変更してやってみました。実験段階では、問題なく動作し、なんかいかGlobal経由での実験も特に問題なく成功。
問題はnetStreamのNodeの管理が煩雑になりすぎて、飽きましたw。

つづいて興味をもったのはhttpを使う方法。
URLLoaderをつかってapacheサーバーからデータをガンガンもらって動作させるというやり方。
上記のp2pと動作は基本的に同じ
1:Red5サーバーにデータを流す。
2:そのデータをfthファイルとftmファイルに分解してhttpでダウンロードできるように変更。
3:プレーヤーは各自ダウンロードをし、appendBytesで再生する。
というやり方。

こちらも実験では問題なく動作し、Global経由での実験も特に問題なし、まぁ、つくったFlashプログラムのダウンロードタイミングにちと問題があって、再生が微妙につまったりしましたが、そこもなんとかしました。
これが今回のHttpTakStreamingでのやり方です。
ただ、Flash側からアクセスしなければいけなく、メッセージができ次第pushするわけではないのでどうしても遅延が大きくなる傾向があります。

netStream.appendBytesの話

さて、githubにhttpTakStreamingを公開したわけですが、ここでつかわれている、netStream.appendBytesについて、ちょっと紹介しておきたいと思います。

ActionScriptのnetStreamにはFlashPlayer10.1から(だったと思いますが)、appendBytesというメソッドが追加されています。
どういう動作かというと、netConnectionのサーバー指定しないものをベースにした、netStreamでは、追記されたFLVバイトデータを再生することができるというものです。

これをつかえばPC内にあるFLVデータを再生するプレーヤーとかつくることができます。
http://help.adobe.com/ja_JP/FlashPlatform/reference/actionscript/3/flash/net/NetStream.html#appendBytes()
さて、アドベの説明によると[バイトパーサーは、ヘッダー付きの FLV ファイルを認識します。ヘッダーが解析された後、appendBytes() は、今後のすべての呼び出しが同じ実際のファイルまたは仮想ファイルの連続であると予期します。appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN) が呼び出さるまでは、別のヘッダーは予期されません。]とあります。

要するに追記されたデータはすべて同じファイルの続きであると解釈して動作しますよ。ということです。

いいかえると、後から追記するデータでも、同じファイルとして成立するならば、再生データとして受け入れるということです。
もちろんデータの供給がおいつかなくなるとNetStream.Buffer.Emptyとかがでてとまっちゃいますが、そうでなければ大丈夫です。

というわけで、HttpTakStreamingでは、FLVのファイルとして成立させるために、Fthファイル(FlvTakHeaderファイル)をまず読み込ませFLVデータの土台を送りつけたあとに、Ftmファイル(FlvTakMediaファイル)を動画の本体として連続でどんどん読み込ませるという方法で、ライブ放送を成立させています。

2012年3月3日土曜日

自前でhttpのstreamingをつくってみました。その3

HttpTakStreamingのプログラムをgithubに公開しました。

https://github.com/taktod/HttpTakStreaming

http経由のストリーミングなので、apacheやnginxでスケーリングすればOK
しかもダウンロード先を中途で変更しても、ダウンロードファイルに矛盾がでなければOK
さらに、遅延も一応最高で5秒を達成してあります。

というなかなかいけるんじゃない?というものができあがりました。

いまのところ、flexのダウンロードタイミングにちと難があるみたいですが、そこらへんをぼちぼち修正していきたいところ。

2012年3月2日金曜日

red5 ivyの依存関係でつまったら・・・

red5の最新の開発版が利用したかったので、subversionでダウンロードしました。

antでコンパイルしてみたところ

[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] ::          UNRESOLVED DEPENDENCIES         ::
[ivy:resolve] ::::::::::::::::::::::::::::::::::::::::::::::
[ivy:resolve] :: org.slf4j#com.springsource.slf4j.api;1.6.1: ・・・
というエラーがでて、コンパイルできませんでした。

原因はivyの設定がまちがっているとか、red5のリポジトリがこわれているとかではなく、
「以前のコンパイル時のivyのcacheがシステムにのこっているために、バージョンがあわない」
というもののようです。

そこでおまじない
$ ant ivyclear
これを実行するとivyのcacheがクリアされます。
$ ant
んで、antを実行。

これでコンパイルが無事おわりred5が生成されました。