WebRTC GetUserMediaとPeerConnectionについて

(火)

WebRTCとは

Web Real-Time Communications の略称でWebブラウザの上でカメラやマイクの利用や、ブラウザ同士の双方向通信(P2P)を実現する技術。

  1. 対戦ゲームでのブラウザ同士の通信
  2.  
  3. ビデオ会議、音声会議などに利用
詳しくは、こちら

使用した技術

WebRTC

GetUserMedia
ブラウザからカメラやマイクを使用して動画音声を取得する
peerConnection
ブラウザとブラウザのP2P通信を実現する技術

サーバ関係

Node.js
WebSocketによる接続者同士の情報を管理するために必要
node.js
バージョン0.4.12
nvm
バージョン 不明
npm
バージョン1.0.106
express
バージョン2.5.9
socket.io
バージョン0.9.6

開発環境

OS
Mac OS X 10.7.4

使用ブラウザ

2012年9月13日現在で最新版のGoogleChrome バージョン 21.0.1180.89

ブラウザの設定

Googel Chromeの設定

 まず,ブラウザ(Chrome)からpeerConnectionとGetUserMediaを使えるようにするための設定を行う。アドレスバーにて,「chrome:flags」と入力するとChromeの設定画面が開く.

そこで,peerConnectionとGetUserMediaを有効にする

ローカルサーバの構築

 GetUserMediaによるカメラからの動画取得などは、サーバ環境が無いと実行できない。 そのため,今回はサーバにNode.jsを使用する(peerConnectionを行うときにWebSocketが必要になるため) GetUserMediaだけならMANPを使用すると便利だと思う。

GetUserMediaによるカメラの使用について

 簡単に説明すると.JavaScriptで

        navigator.webkitGetUserMedia({ video: true, audio: true }, success, error);
 を記述するとカメラや音声をブラウザで取得することが出来る。成功した場合には,successが呼ばれ何かエラーが起きればerrorが呼ばれる.

 以下参考までに...

    <video id="myVideo" width="300" height="300"></video>
    <script>
    navigator.webkitGetUserMedia({ video: true, audio: true }, success, error);
    //成功した時に呼ばれる 
    function success(stream) {
        document.querySelector('#myVide').src = window.webkitURL.createObjectURL(stream);
    }
    function error (message) {
        console.log(message);
    }
    <script>

peerConnectionによるP2Pの接続について

 peerConnectionを用いるとブラウザ同士のP2P通信を実現することが出来る。 P2Pを行う上で問題になるのは、NATの問題があるため、STUNサーバを使用する。 詳しくは、以下ののサイトが参考になった。P2P通信の最大のハードル「NAT」を越える新技術

STUNサーバについて

P2P接続までの通信の流れ

  1. STUNサーバにアクセスして自分のブラウザに接続出来る候補の情報を要求する
    ※正確には、アンサーやオファーを作成した時にSTUNサーバへアクセスされる
  2. STUNサーバから接続候補を取得するたびにブラウザBに受信した接続候補情報を送信する
    ※接続候補は複数送られてくるので受信の度に送る必要がある
  3. 接続相手に自分の接続情報と「今から通信しましょう」というオファーを作成し、接続先相手に送信する
    3': STUNサーバから受信した接続候補情報を受信の度にブラウザBに送信する又は相手から受信する
  4. ブラウザBでオファーを受信する
  5. 1と同様に、ブラウザBもSTUNサーバにアクセスして自分のブラウザに接続出来る候補の情報を要求する
  6. STUNサーバから接続候補を取得するたびにブラウザAに受信した接続候補情報を送信する
    ※接続候補は複数送られてくるので受信の度に送る必要がある
  7. ブラウザAから受信したオファーを元に「通信しても良いですよ」というアンサーを作成し、相手に送信する
    7': STUNサーバから受信した接続候補情報を受信の度にブラウザAに送信する又は相手から受信する
  8. ブラウザAでアンサーを受信する

P2P接続までにブラウザ同士で交換が必要な情報

  1. 両ブラウザがSTUNサーバから受信した接続先候補情報(candidte)
  2. オファー
  3. アンサー

これらの情報をブラウザ同士でスムーズに交換するために、今回はWebSocketを使用する。

1:STUNサーバにアクセスして自分のブラウザに接続出来る候補の情報を要求する

インスタンスの生成

    var peerCon = new webkitPeerConnection00('STUN stun.l.google.com:19302', onIceCandidate);

onIceCandidateは、コールバック関数でSTUNサーバから自分の接続情報(Candidate)が送られてくるたびに呼ばれる関数。 受信した自分の情報を接続する相手に送る必要があるので、コールバック関数の中で相手に送信する。 ※コールバックについては、2を参照

GetUserMediaを使用してビデオチャットを実装する場合

peerConに送信するデータや受信したデータをどのように扱うかを記述する必要がある。 それらの処理もインスタンスを生成した際に記述する。

送信するデータ

        peerCon.addStream(localStream);

localStreamは、グローバル変数でnavigator.webkitGetUserMedia({ video: true, audio: true }, success, error);のsuccess時の引数stream

        //成功した時に呼ばれる引数をグローバル変数で保持して代入している
        function success(stream) { 
            document.querySelector('#myVide').src = window.webkitURL.createObjectURL(stream);
            localstream = stream;
        }

相手から受信した時に呼ばれるメソッド

メソッド名は自分で命名可能、引数に相手先で設定した送信データが渡される

        peerCon.onaddstream = startShowingStream;
        function startShowingStream (streamEvent) {
            document.querySelector('#yourVide').src = webkitURL.createObjectURL(streamEvent.stream);
        }

コネクションが開始されるときに呼ばれるメソッド(必要に応じて実装)

        peerCon.onconnecting = function() {
            console.log('コネクト開始');
        }
   

コネクションが開いた時に呼び出されるメソッド(必要に応じて実装)

        peerCon.onopen = function () {
            console.log('オープンしたぜ!');
        }

接続相手が送信データをなんらかの形で削除した時に呼ばれるメソッド(必要に応じて実装)

        peerCon.onremovestream = function () {
            console.log('消したで!');
        }

2:STUNサーバから接続候補を取得するたびにブラウザBに受信した接続候補情報を送信する

※接続候補は複数送られてくるので受信の度に送る必要がある

Candidate(候補者について)の送信について

例えばwebkitPeerConnection00のインスタンスを下記の用に生成した場合

        var peerCon = new webkitPeerConnection00('STUN stun.l.google.com:19302', onIceCandidate);
コールバック関数の書き方は、
        function onIceCandidate (candidate) {
            //ここにWebSocketを使って以下のデータを送信する 
            //送信するデータ:{ label:candidate.label, candidateSdp: candidate.toSdp() }
        }
※Candidateは、引数を2つとっておりデータがなくなれば第二引数にfalseが入って返ってくるそうだ

3':STUNサーバから受信した接続候補情報を受信の度にブラウザBに送信する又は相手から受信する

7':STUNサーバから受信した接続候補情報を受信の度にブラウザAに送信する又は相手から受信する

        //WebSocketを用いてCandidateを受信したときの関数を作成する。
        //その関数の中に以下の処理を記述する
        var message = 受信したデータ
        var candidate = new IceCandidate(message.label, message.candidateSdp);
        peerCon.processIceMessage(candidate);

3:接続相手に自分の接続情報と「今から通信しましょう」というオファーを作成し、接続先相手に送信する

オファーとアンサーについて

 peerconnectionによるP2Pを実装するためには、接続を要求する側(A)がオファーを作成し、接続を要求される側(B)に送信する。 その後、Bがオファーを受信後アンサーを作成し、Aにアンサーを送信。 Aがアンサーを受信後、P2Pによる接続が出来るようになる。

大事な2つのメソッドとインスタンスの生成

setLocalDescription();
自分が送るデータの説明を付け加えているイメージ
setRemoteDescrioption();
相手から送られてきたデータを解析するイメージ
new SessionDescription();
送られてきた情報を元にインスタンスを生成する

ブラウザAでオファーの作成

    var offer = peerCon.createOffer({video:true, audio:true});
    peerCon.setLocalDescription(peerCon.SDP_OFFER, offer );
    //ここでWebSocketを用いてブラウザBにoffer.toSdp()を送信する
    peerCon.startIce(); //アクセス開始の合図

4:ブラウザBでオファーを受信する

5:1と同様に、ブラウザBもSTUNサーバにアクセスして自分のブラウザに接続出来る候補の情報を要求する

※4と5が同時に記述されているのは、1の※の理由と同じ

受信側でwebkitPeerConnection00のインスタンスpeerConが無い場合インスタンスの生成を行う必要がある ※1と同じ処理を行う

オファーの受信時の処理

    //WebSocketを用いてofferを受信したときの関数を作成し以下の処理を記述する
    var offerSdp = 受信したoffer
    peerCon.setRemoteDescription(peerCon.SDP_OFFER, new SessionDescription(offerSdp));

6:STUNサーバから接続候補を取得するたびにブラウザBに受信した接続候補情報を送信する

※接続候補は複数送られてくるので受信の度に送る必要がある

※2と同じ処理を行う

7:ブラウザAから受信したオファーを元に「通信しても良いですよ」というアンサーを作成し、相手に送信する

※4の処理の続きに記入する

ブラウザBでアンサーの作成

    var answer = peerCon.createAnswer(offerSdp, {video:true, audio:true});
    peerCon.setLocalDescription(peerCon.SDP_ANSWER, answer);     
    //ここでWebSocketを用いてブラウザAにanswer.toSdp()を送信する
    peerCon.startIce(); //アクセス開始の合図

7:ブラウザAから受信したオファーを元に「通信しても良いですよ」というアンサーを作成し、相手に送信する

※4の処理の続きに記入する

ブラウザBでアンサーの作成

    var answer = peerCon.createAnswer(offerSdp, {video:true, audio:true});
    peerCon.setLocalDescription(peerCon.SDP_ANSWER, answer);     
    //ここでWebSocketを用いてブラウザAにanswer.toSdp()を送信する
    peerCon.startIce(); //アクセス開始の合図

8:ブラウザAでアンサーを受信する

アンサーを受信時の処理

    //WebSocketを用いてanswerを受信したときの関数を作成し以下の処理を記述する
    var anserSdp = 受信したanswer
    peerCon.setRemoteDescription(peerCon.SDP_ANSWER, new SessionDescription(answerSdp));

以上の処理を行うことで、PeerConnectionを用いたP2P通信を実装することが可能でさる。

デモ

WebRTCのデモコード ダウンロード 只今準備中です

実行するには

$ node server.js
のコマンドでサーバを立ち上げる。

※Node.jsの環境とWebカメラ必須

ブラウザで [ localhost:8124 ] と入力すれば、ページが表示される。

アクセスするとWebカメラに接続しても良いか聞かれるので許可を押す。

Webカメラが起動するので、その後もう1つブラウザを立ち上げてカメラを起動してから接続ボタンを押す。

すると2つのブラウザ間でP2Pの通信を行える。

尚、付随機能として「自分の名前」とチャットをイメージした「コメント」をリアルタイムに相手に送信する機能を追加した。

チャット機能は、相手の画面上のランダムな場所にコメントが表示され、5秒ほどしたら消える仕様になっている。

また、同じローカルネットワークに属しているのであれば自分のIPアドレスを教えて上げると他のPCのブラウザとP2Pで接続することが可能である。 その際にはindex.htmlの下記の場所を書き換える必要がある。

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>WebRTCのデモ</title>
        <link type="text/css" href="css/style.css" rel="stylesheet">
        <link type="text/css" href="css/indexStyle.css" rel="stylesheet">
        <script src = "http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
        <!-- ローカルネットワークで共有する場合読み込み先を毎回変更する必要あり-->
        //<script src = "localhost:8124/socket.io/socket.io.js" > </script>
        <script src = "http://IPアドレス:8124/socket.io/socket.io.js"></script>

        <script>
            //var socket = io.connect('localhost:8124');
            var socket = io.connect('http://IPアドレス:8124');
    </script>
    </head>

多人数ビデオチャットのデモについてはこちら

参考資料

WebRTC
http://www.webrtc.org/
WebRTCとか勉強会 株式会社ミクシィ あんどうさん
https://docs.google.com/presentation/d/1MkvmX6Gvb1cRATdwF4RFBtol8NgdnblwlwikDnFhT10/present#slide=id.p
WebRTCについて ERICSSON
https://labs.ericsson.com/apis/web-real-time-communication/documentation
WebRTC本家のデモ
https://webrtc-demos.appspot.com/html/pc2.html

このページを作成するにあたり、お世話になったソースコード貼り付け時のハイライトを自動で行ってくれるサイト

http://kujirahand.com/tools/tougarasi/