webRTCでP2Pの動画チャットを頑張れば試せるデモ作ってみた。

WebRTCって、P2Pで動画チャットできるって聞いたからためしみた。ら、案の定、動作が安定していないのと、
知らないプロトコルとか出てきて泣いた。

www.webrtc.orgに紹介されている http://apprtc.appspot.com/ のデモコードだと
大きすぎて何しているのかわけわからない。
だから、シンプルにするために
 ・最低限のコードにする
 ・サーバをつかわない(本来、P2Pする前にクライアント間でメディア形式を決めるのに使います)
を目標にデモを書いてみた。
なので、手動でコピペを頑張ってもらう部分がありますw

デモを作る前提知識と参考にしたもの

サーバからのPUSHが必要

http://apprtc.appspot.com/ のデモを参考にしています。
デモコードを見るかぎり、サーバからのPUSHが必要です。
このコードは、Google App EngineのChannel APIで実現していますが、
大体は、WebSocketが前提になるんじゃないかと思います。(ハードル高いw)
今回のデモは、この通信を手動で・・・

STUNサーバ

ブラウザ同士がP2Pするには、大体NATを越える必要があるので、STUNサーバが必要です。
グローバル側から見たブラウザのIPなどの情報を教えてくれるものです。
しかし、構築するのにIPが2つ必要らしいので自前で作るのは難しいため
公開STUNサーバを利用することにしました。
http://www.voip-info.org/wiki/view/STUN
(jpはないのですか?)

コード(sample03.html)

<html>
<head>
<title>sample03</title>
</head>
<body>
<script type="text/javascript">

var local;
var remote;
var pc;
var localstream;

var init = function() {
  local = document.getElementById("local");
  remote = document.getElementById("remote");
  navigator.webkitGetUserMedia("video", gotStream, noStream);
}

var sendSignalingChannel = function(message) {
  console.log("sendSignalingChannel:" + message);
  document.form_test.sdp_out.value = message;
}

var gotStream = function(stream) {
  local.src = webkitURL.createObjectURL(stream);
  localstream = stream;
}

var createPC = function() {
  pc = new webkitPeerConnection('STUN stun.ekiga.net', sendSignalingChannel);
  pc.addStream(localstream);
  pc.onaddstream = function (event) {
    remote.src = webkitURL.createObjectURL(event.stream);
  };
}

var noStream = function(error) {
  alert("noStream");
}

var setSDP = function() {
  var text = document.form_test.sdp_in.value;
  text = text.replace(/^\n/g,"");
  pc.processSignalingMessage(text);
}

setTimeout(init,1);

</script>
<video id="local" width="320" height="240" autoplay></video>
<video id="remote" width="320" height="240" autoplay></video><br>
<form name="form_test">
<button onclick="createPC(); return false;">1 createPeerConnection</button><br>
<button onclick="createPC(); setSDP(); return false;">2 input Offer</button><br>
<button onclick="setSDP(); return false;">3 input Answer</button><br>
<button onclick="setSDP(); return false;">4 input OK</button><br>
SDP input<br>
<textarea name="sdp_in" cols="50" rows="5"></textarea><br>
SDP output<br>
<textarea name="sdp_out" cols="50" rows="10"></textarea><br>
</form>
</body>
</html>


デモ

以下にデモページを用意しています。
3月28日現在、WebRTCは、Google Chromeの開発版(DEV Channel)等でしか動作しないので、
Chromeの開発版でMediaStreamを有効にして試してください。
(味見部で試してもらった結果、Chrome Canaryだと動作しないようです)
あと、Webカメラが必要です。

ただし!手順どおり実行しないと動きませんー!
http://alumican.ddo.jp/webrtc/sample03.html
※動作確認は、Google Chrome Version 19.0.1068.1 devでしました。
※動画データなどは、サーバに送信されません。

操作手順

1.http://alumican.ddo.jp/webrtc/sample03.htmlを2つのウィンドウで開く。
 (以下、ブラウザAとブラウザBと呼称)
2.おもむろにブラウザAの「1 createPeerConnection」ボタンを押す。
3.ブラウザAの「SDP output」に表示されたテキストをコピー
4.ブラウザBの「SDP input」にペースト
5.ブラウザBの「2 input Offer」ボタンを押す。
6.ブラウザBの「SDP output」に表示されたテキストをコピー
7.ブラウザAの「SDP input」にペースト
8.ブラウザAの「3 input Answer」ボタンを押す。
9.ブラウザAの「SDP output」に表示されたテキストをコピー
10.ブラウザBの「SDP input」にペースト(※前に入ってたデータは消す)
11.ブラウザBの「4 input OK」ボタンを押す。

この手順を素早く!(時間制限があるみたいだから)
うまくいけば、これでブラウザAもBもカメラの動画がローカルとリモートの2つ表示されます。

f:id:alumican:20120327183354p:plain


基本的に、手順操作に失敗したら、状態依存があるらしく2度と同じ結果が得られないことが多いので
開発版のChromeを再起動するのがいいかなと思います。
他でコーディング試している人はどうしてるんだろう。教えて偉い人m(_ _)m

最期までやってくれた方、お疲れさまでした。
次は、ちゃんとサーバ立ててWebSocket込みのデモを作ろうとおもいます。

 ローカルでしか試していないので不具合とかあったらコメントください。

 

webRTCで顔認識(face.js)を試してみた。

face.jsって、なんか、顔認識できるらしいぞ。凄い!
そんなわけで、試してみる。

ccv.jsとface.jsを使うと実現できるぽい。
ccv.jsは、コンピュータビジョンのライブラリ。
face.jsは、顔認識するためのパターンデータみたい。

2つのソースは、以下から取得してきた。(どこがオリジナルかわからず。。。)
https://github.com/wesbos/HTML5-Face-Detection

顔を認識っていったら、誰もが一度はやってみたいと思ったはず。
顔を笑い男に上書きするやつを書いてみた。

ソース(sample02.html)

<html>
<head>
<title>sample02</title>
<script type="text/javascript" src="./js/ccv.js"></script>
<script type="text/javascript" src="./js/face.js"></script>
</head>
<body>
<h1>webRTC with face.js</h1>
<script type="text/javascript">
  var src;
  var dest;
  var width;
  var height;

  var image = new Image();
  image.src = "./laughingman.gif";
  
  initialize = function() {
    src = document.getElementById("src");
    dest = document.getElementById("dest");
    navigator.webkitGetUserMedia("video,audio",onGotStream,onFailedStream);
  }
  
  onGotStream = function (stream) {
    var url = webkitURL.createObjectURL(stream);
    src.src = url;
    setInterval(draw,500);
  }
  
  onFailedStream = function(error) {
    alert("onFailedStream");
  }
  
  var draw = function () {
    width = src.videoWidth;
    height = src.videoHeight;
    
    var canvas = document.createElement('canvas');
    
    canvas.width = width;
    canvas.height = height;
    var context = canvas.getContext('2d');
    context.drawImage(src,0,0,width,height);
    
    dest.width = width;
    dest.height = height;
    var context2 = dest.getContext('2d');
    context2.drawImage(src,0,0,width,height);
    
    var comp = ccv.detect_objects({"canvas" :ccv.grayscale(canvas),
      "cascade" : cascade,
      "interval" : 5,
      "min_neighbors" : 1 });
  
    for(var i=0;i < comp.length;i++) {
      context2.drawImage(image,comp[i].x-30,comp[i].y-30,comp[i].width+60,comp[i].height+60);
    }

  }
  
  setTimeout(initialize,1);
  
</script>
<video id="src" autoplay></video>
<canvas id="dest"></canvas>
</body>
</html> 

デモ

動かしてみたいけど面倒くさい人のためにデモページを用意しました。
WebRTCは、Google Chromeの開発版でしか動作しないので、
Chromeの開発版でMediaStreamを有効にして試してください。
あと、Webカメラが必要です。

http://alumican.ddo.jp/webrtc/sample02.html
※動画データは、サーバに送信されませんよ。

動作画面

ちょっとface.jsの認識が微妙だけど、こんな感じです!
なんか1ネタできそう。

f:id:alumican:20120322163301j:plain

WebRTCをシンプルに試す

WebRTCって、なんなの?使うのめんどくさいの?
って、日頃から気になっていたので、簡単に動くことを試してみた。
RTCは、Real-Time Communicationsの略で、ブラウザでカメラとか音とか扱える夢いっぱいの機能です。
前提として、 Google Chromeだと、まだ開発版(Dev channel)しか対応していないらしいので、
開発版をダウンロードして試します。
あと、残念ながらカメラがないと試せません。。。

Chrome開発版ダウンロード と WebRTCの有効化

Chrome開発版をビルドすると大変時間がかかるので、
以下のサイトから環境にあったChrome開発版をダウンロードします。
私が使ったのは、Dev channel for 64-bit Debian/Ubuntuです。
http://dev.chromium.org/getting-involved/dev-channel
ダウンロード後に、Chromeをインストールし、起動します。

URLを入力するアドレス欄に
chrome://flags
を入力すると、試験運用機能の設定画面が表示されるので
「MediaStream を有効にする」をオンにしてください。

その他の準備

Webカメラとapacheか何かWebサーバが必要です。
これで、環境は出来上がりです。

HTMLを書く

以下のカメラ映像を表示するだけのHTMLを書きます。

<html>
<head>
<title>sample01</title>
</head>
<body>
<script type="text/javascript">
var localvideo;
initialize = function() {
  localvideo = document.getElementById("localvideo");
  navigator.webkitGetUserMedia("video,audio",onGotStream,onFailedStream);
}

onGotStream = function(stream) {
  console.log("onGotStream");
  var url = webkitURL.createObjectURL(stream);
  localvideo.src = url;
}

onFailedStream = function(error) {
  alert("onFailedStream");
}

setTimeout(initialize,1000);
</script>
<video id="localvideo" autoplay></video>
</body>
</html>

ChromeでWebサーバにアクセスする

つくったHTMLをそのままChromeにドラッグ&ドロップしても動作しないので、
HTMLをWebサーバで公開し、アクセスします。

f:id:alumican:20120321135604p:plain

Chromeから許可を求められるので、「許可」を押します。
以下のようなカメラ映像が得られれば、成功です!

f:id:alumican:20120321135608p:plain

コンパイラ実装会(3)に参加してきた。その3

前回に引き続き、今回は、brainfuckのコード から アセンブラのコード を生成して出力する node.jsのコードを作成しました。
想定している環境は、Linuxx86_64です。

ネット上のアセンブラの情報がNASMとGASとIntel記法とAT&T記法でごちゃってて、ちょっと一苦労。。。
アセンブラにもNASMとGAS等があり、今回は、GASを。
記法はIntel記法とAT&T記法がありますが、Intel記法です。

bf2asm.js

var fs = require('fs');

var cmds = "";
var indent = 0;
var code = fs.readFileSync('data','utf-8');

var trans = function(code) {
  var arr = code.split('');
  while(arr.length != 0){
    var c = arr.shift();
    switch (c) {
    case '+' : 
      cmds += 'inc byte ptr[esi]\n';
      break;
    case '-' :
      cmds += 'dec byte ptr[esi]\n';
      break;
    case '>' :
      cmds += 'inc esi\n';
      break;
    case '<' :
      cmds += 'dec esi\n';
      break;
    case '.' :
      cmds += 'mov edx, 0x1\n';
      cmds += 'mov ecx, esi\n';
      cmds += 'mov ebx, 0x1\n';
      cmds += 'mov eax, 0x4\n';
      cmds += 'int 0x80\n';
      break;
    //case ',' :
      //cmds += 'ptr = getchar();\n';  
      //break;
    case '[' :
      cmds += indent*2 + ':\n';
      cmds += 'cmp byte ptr [esi],0\n';
      cmds += 'jz ' + (indent*2+1) + 'f\n';
      indent += 1;
      break;
    case ']' :
      indent -= 1;
      cmds += 'jmp ' + indent*2 + "b\n";
      cmds += (indent*2+1) + ':\n';
      break;
    default:
    }
  }
  return cmds;
}

var out = '';
out += '.intel_syntax noprefix\n';
out += '.comm mem, 30000\n';
out += '.code64\n';
out += '.globl _start\n';
out += '_start:\n';
out += 'lea esi, mem\n';
out += trans(code);
out += 'xor ebx,ebx\n';
out += 'mov eax,0x1\n';
out += 'int 0x80\n';

fs.writeFileSync('./bf.s',out,'utf-8');

data (brainfuckのHelloコード)

>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++
++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>
++++++++[<++++>-]<+.[-]++++++++++.

実行方法

$ node bf2asm.js
$ as bf.s -o bf.o
$ ld bf.o
$ ./a.out 
Hello World!

 

コンパイラ実装会(3)に参加してきた。その2

コンパイラ実装会(3)に参加したら、
「では、@alumicanさん発表お願いします。」(な、なんだってー!)
と、@7shiさんにふられたので発表したネタを公開します。


ELF形式の実行ファイルの中身を解析するツールです。はい。ブラウザで。
コンパイラ実装するにあたり、全然真面目に勉強していなかったので、
「もうELF形式のファイルを直接書き換えちゃえばいいよねw!」
というのが動機ですが、読み込むところまでしかできてません。
以下で公開しています。

http://alumican.ddo.jp/elfreader/

使い方

ELF形式の実行ファイルを「緑の枠」にドラッグアンドドロップします。
うまくいけば、解析されて、
ELF Header
Program Header Table
Segment and Section
Section Header Table
の項目が表示されます。クリックすると中身を解析できます。

うまくいかないと、大体の場合、ブラウザが応答しなくなりますw

解析された内容を下記のサイトを参考にして調べると、
何が入っているのかわかりやすいかなと思います。
http://caspar.hazymoon.jp/OpenBSD/annex/elf.html

※ELF形式のファイルは32bitのみ対応です
ドラッグアンドドロップしたファイルは、ローカルで処理されるのでサーバに送信されたりしません。

コンパイラ実装会(3)に参加してきた。

基本的にハッカソンのように個々人がもくもくと実装する会です。
運がよければ、すごい人の自作VMとか自作JITとか自作言語の発表が聞けちゃいます。
今回は、brainfuckのコード から Cのコード を生成して出力する node.jsのコードを作成しました。
brainfuckは、命令が8つしかないので、処理は簡単です。
以下は、コードと実行方法です。

brainfuck-ctrans.js

var fs = require('fs');

var out = "";
var code = fs.readFileSync('./data','utf-8');
out += '#include \n';
out += 'void main() {\n';
out += 'char m[30000];\n';
out += 'char* ptr = m;\n';

var arr = code.split('');
while(arr.length != 0){
    var c = arr.shift();
    switch (c) {
    case '>' : 
        out += '++ptr;\n';
        break;
    case '<' :
        out += '--ptr;\n';
        break;
    case '+' :
        out += '++(*ptr);\n';
        break;
    case '-' :
        out += '--(*ptr);\n';
        break;
    case '.' :
        out += 'putchar(*ptr);\n';
        break;
    case ',' :
        out += '*ptr = getchar();\n';
        break;
    case '[' :
        out += 'while(*ptr) {\n';
        break;
    case ']' :
        out += '}\n';
        break;
    default:
    }
}
out += '}\n';
fs.writeFileSync('./out.c',out,'utf-8');

data (brainfuckのHelloコード)

>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.+++++++..+++.[-]>++++++++[<++
++>-]<.>+++++++++++[<+++++>-]<.>++++++++[<+++>-]<.+++.------.--------.[-]>
++++++++[<++++>-]<+.[-]++++++++++.

実行方法

  1. node brainfuck-ctrans.js
  2. gcc out.c 
  3. a.out

Hello World!
と、表示されます。(たぶん)

次は

次は、brainfuckのコード から アセンブラのコード を生成して出力する node.jsのコードを
作成します。

Androidデバイス間でP2P通信できるAllJoynをためしてみた。

横浜Androidプラットフォーム部第18回勉強会でAndroid Builders Summitの紹介があったときに はじめてAllJoynのプロジェクトを知ったので、簡単にサンプルを試してみました。
まだ、Androidデバイス間のP2P通信ができるおもしろいところまでいっていないですが、
次回ということで。ひとつ。

AllJoynとは

Qualcommが作ってるDbusをデバイス間にも拡張したP2Pシステムらしい。
オープンソースなのでGitHubにて公開されています。
froyoとgingerbreadの他、ICSでも動作します。

プロジェクトのサイトは以下
https://www.alljoyn.org/

前提要件

Android SDK 
Android NDK
Eclipse 
Eclipse ADT plug-in

その他の必要なパッケージ

aptitude install scons

Android SDKをインストール

下記のサイトを参考にしてください。
http://developer.android.com/intl/ja/sdk/index.html
Eclipse と ADT plug-in についても記述されているとおもうので省略します。

Android NDKをインストール

# mkdir -p $HOME/alljoyn
# cd $HOME/alljoyn
# wget http://dl.google.com/android/ndk/android-ndk-r7b-linux-x86.tar.bz2
# tar jxvf android-ndk-r7b-linux-x86.tar.bz2

詳細は下記のサイトを参考にしてください。
http://developer.android.com/intl/ja/sdk/ndk/index.html

Androidのソース取得とビルド

# mkdir -p $HOME/alljoyn/android-src
# cd $HOME/alljoyn/android-src

$HOME/alljoyn/android-srcの中にソースを取得して、ビルドしてください。
詳細は下記のサイトを参考にしてください。(ビルドにすごい時間かかります・・・)
http://source.android.com/source/downloading.html

alljoynのソース取得とビルド

$ mkdir -p $HOME/alljoyn/repo
$ cd $HOME/alljoyn/repo
$ repo init -u git://github.com/alljoyn/manifest.git
$ repo sync
$ repo start master --all

$ mkdir -p $HOME/alljoyn/lib
$ cd $HOME/alljoyn/lib
$ wget https://github.com/downloads/KentBeck/junit/junit-4.10.jar
$ jar xvf junit-4.10.jar
$ export JAVA_HOME=/opt/jdk1.6.0_30/ <-環境によって変更してください。
$ export CLASSPATH=$HOME/alljoyn/lib
$ cd $HOME/alljoyn/repo
$ scons OS=android CPU=arm ANDROID_NDK=$HOME/alljoyn/android-ndk-r7b ANDROID_SRC=$HOME/alljoyn/android-src WS=off

libcrypto.soを取得

Androidエミュレータを起動する。(※avdの設定hogehogeは作成済みという前提)
$ emulator -avd hogehoge
$ cd $HOME/alljoyn/repo/build/android/arm/debug/dist/lib
$ adb pull /system/lib/libcrypto.so libcrypto.so

なぜか、ターゲットになるAndroidのlibcrypto.soを取得するという手順。
それって、どうなの?

AllJoynデーモンの作成とインストール

$ cd $HOME/alljoyn/repo/build/android/arm/debug/dist/alljoyn_android/alljoyn/
$ $HOME/alljoyn/android-ndk-r7b/ndk-build

eclipseを起動

新しいプロジェクトを作成
File -> New -> project -> Android Project
[Create project from existing source]を選択
location に [$HOME/alljoyn/repo/build/android/arm/debug/dist/alljoyn_android/alljoyn]を指定
finishを選択

ビルド
Project -> Build Project

エクスポート
File -> Export... -> Export Android Application から alljoyn.apk を作成

AllJoynデーモンプログラムをインストールする。
$ adb install alljoyn.apk

AllJoynデーモンプログラムを実行すると以下のようにバックグラウンドで常駐します。
f:id:alumican:20120229093140p:plain

サンプルの作成とインストール

alljoynのサンプルで動作検証する。

Simple Clientアプリ

eclipseを起動

新しいプロジェクトを作成
File -> New -> project -> Android Project
[Create project from existing source]を選択
location に [$HOME/alljoyn/repo/build/android/arm/debug/dist/java/samples/simple/client]を指定
finishを選択

ビルド
Project -> Build Project

エクスポート
File -> Export... -> Export Android Application から Client.apk を作成

Simple Serviceアプリ

eclipseを起動

新しいプロジェクトを作成
File -> New -> project -> Android Project
[Create project from existing source]を選択
location に [$HOME/alljoyn/repo/build/android/arm/debug/dist/java/samples/simple/service]を指定
finishを選択

ビルド
Project -> Build Project

エクスポート
File -> Export... -> Export Android Application から Service.apk を作成

サンプルアプリをインストールする。

$ adb install Client.apk
$ adb install Service.apk

検証方法

以下の順番で実行する。
AllJoynデーモン(alljoyn.apk)
Simple Service(Service.apk) ※必ずHOMEボタンでぬけること。BACKボタンで抜けるとうまく動作しません。
Simple Client(Client.apk)

クライアントアプリ(Client.apk)のアクティビティの「Enter Message」に文字を入力し、
Enterキーを押すと、Simple Serviceが応答し、検証できます。

以下が成功した画面です。

f:id:alumican:20120301120400p:plain