ofxLibWebsockets で oF とウェブブラウザの間でお話しする
はじめに
相変わらず、ブラウザとopenFrameworks 間で共同作業させるシリーズです。
以前こんなものを書きました。
- ycapture で OpenCV での処理結果を Windows のビデオソースとしてブラウザに流し込む - 自習室
- html から node.js を介して oF の画像処理をコントロールし、ブラウザ上で閲覧する - 自習室
これらの記事では、ブラウザとoF間にNode.jsのプログラムを挟み、socket.io と OSC を相互に変換することで、データのやりとりをしていました。もともと以下の理由でそういう構成にしていました。
- ブラウザとnode.js間はsocket.ioでの通信が楽
- node.jsとoF間では、OSCでの通信が楽
- それを単につなげればよいので楽
このときの構成は下図のようになります
しかしこの結果
- 機能上はほぼ意味の無い node.js の変換コードを書かなければいけない。実装コスト増大
という問題も発生しました。
- oFでやろうとしていることを別のプログラムでシミュレートする
- 複数のブラウザやoFのプログラムとやりとりをする
などのケースを想定すると多段構成にする意味もありますが、一対一で通信をするだけなら、ほぼ意味はありません。従って、ブラウザとoFで直接お話しをさせたいです。
いろいろ試した結果、以下の構成が可能であることがわかりました。今回はその方法についてまとめます。
非バイナリデータのみ
WebSocketでバイナリのやりとりも出来る筈ですが、本記事では非バイナリデータ…つまり、数値、文字、bool とそれらをオブジェクト化した物などをやりとりします。いずれバイナリも調査します。
この記事を追うとできるもの
oFの画面でマウスを動かすと、その座標をJSONとしてラップし、WebSocketを介してブラウザに送ります。ブラウザではJSONを復元して、オブジェクトとしてコンソールに出力します。
完成品はこちらにアップしています
ofxLibWebsockets の基本
addon単体としてのドキュメントがほとんど無いため、使いこなしに少々苦労しました。サンプルを読んで探していくスタイル。
プロジェクトへの追加
oFプロジェクトへの追加の方法は、ofxLibWebsockets のgithubにまとめられています。こちらを参考に追加もしくはoFプロジェクトの作成を行ってください。
自分はWindows(VS2012) で試しているのですが、配布されている ofxLibWebsockets のオプションに間違いを見つけました。以下修正してください。
プロジェクトプロパティ > リンカー > 全般 > 追加のライブラリディレクトリ で、構成が Debug の際に、 ..\..\..\addons\ofxLibwebsockets\libs\libwebsockets\lib\win32\Release` となっているものを ..\..\..\addons\ofxLibwebsockets\libs\libwebsockets\lib\win32\Debug に修正。
oF側をサーバとしてセットアップ
ofxLibWebsockets のサンプル "example_server_echo" を参考に。最低限の構成は以下、だと思います
void ofApp::setup(){ ofxLibwebsockets::ServerOptions options = ofxLibwebsockets::defaultServerOptions(); options.port = 9092; options.bUseSSL = false; // you'll have to manually accept this self-signed cert if 'true'! bSetup = server.setup( options ); // this adds your app as a listener for the server server.addListener(this); }
メッセージの受け取り
こちらも、ofxLibWebsockets のサンプル "example_server_echo" を参考に。ただ受け取ったメッセージを標準出力します。
void ofApp::onMessage( ofxLibwebsockets::Event& args ){ cout << "got message " << args.message << endl; }
試しにブラウザから送ってみる
先に "example_server_echo" を起動してから、Chromeで適当なページを開き、developer tool のコンソール上で以下の様に順に打ち込みます
ws = new WebSocket('ws://localhost:9092'); // 接続 ws.send('hoge'); // 文字列を送る
一行目をenterした時点で、oFのコンソールには "new connection open" と表示されます。
更に二行目をenterすると、oFのコンソールに "got message hoge" を表示されます。
JSON形式でやりとりしてみる
ブラウザ側から送ってくるデータは、それが元々オブジェクトであった場合も、message = JSON.stringify(data)
したのちに送り出し、ofxLibWebsockets で受け取ると、jsonとして復元されます。
逆に、ブラウザ側でJSON形式で受け取りたい場合は、oF側でJSON形式でまとめ上げたものを文字列として送り出し、それをdata = JSON.parse(message)
することで、jsonとして復元されます。
図にまとめると下のようになります。
ofxLibWebsockets は内部でjsoncpp というライブラリを利用しているので、json <-> 文字列 の変換が出来ます。新しいjson形式のデータ作成も可能です。
ブラウザからobjectデータを送り出し、oFで受け取るケース
// 送出側 (js) var ws = new WebSocket('ws://localhost:9092/'); var pos = {'x':0, 'y':100}; var message = JSON.stringify(pos); ws.send(message);
// 受け取り側 (cpp) void ofApp::onMessage( ofxLibwebsockets::Event& args ){ // args.messageには、 js側で message = JSON.stringify(pos) として作られた文字列"message" が入っている cout<<"got message "<<args.message<<endl; // 送られてきた args.message がjsonにパースできる形式である場合、 // 自動的にパースされ args.json に Json::Value 形式で収められる if ( !args.json.isNull() ){ cout << "New message: " + args.json.toStyledString() + " from " + args.conn.getClientName(); } }
oFでJSON形式にラップしたものをブラウザで受け取るケース
マウスを動かすと、最新の座標をWebSocketで送り出す、というもの
// 送出側 (cpp) void ofApp::mouseMoved(int x, int y ){ Json::FastWriter writer; Json::Value value; value["x"] = x; value["y"] = y; std::string message = writer.write(value); server.send(message); cout << "mouseMoved : " << message << endl; }
// 受け取り側 (js) var ws = new WebSocket('ws://localhost:9092/'); ws.onmessage = function(event) { var data = JSON.parse(event.data); //event.dataは文字列。これをJSON形式にパース console.log(data); }
JSONCPP の使い方
以下のページなどを参考にしました
- jsoncpp を試す - てっく煮ブログ
- c++ - JSONCPP Writing to files - Stack Overflow
- Home · open-source-parsers/jsoncpp Wiki · GitHub
スピードを上げる
ofxLibWebsocket (libwebsocket)では、WebSocketの送り出し受け取りを、専用のスレッドを立てて行っています。一つの処理をコンテキストとしてまとめ、それをキューに積んでいって、前から順に処理していきます。その辺りはこちらのページで勉強させて頂きました。
ofxLibWebsocketでは、そのスレッド処理に、何故か標準で 50ms の待機時間が挿入されているため、毎秒 20 回までしか通信の処理が出来ません。マウスの動きを伝えるには遅すぎます。ここを変更します。
// addons/ofxLibwebsockets/ofxLibWebsockets/Server.cpp 19行目 Server::Server(){ context = NULL; waitMillis = 10; //★ここ 50 -> 10 など。 reactors.push_back(this); defaultOptions = defaultServerOptions(); }
これで、最大100回/秒 通信できるようになります。
waitMillis を変更するための public な関数をServer.cpp か 親クラスの Reactor.cpp に追加しても良いかもしれません。
さいごに
oFで画像を作ってブラウザ内で利用する、と言ったことも出来るかもしれないので、調査してみます。