2012年04月25日

WebRTC事始め



今日のポストはWebRTCについて。Webでリアルタイム通信サービスを実現するためのAPIです。ブラウザで、plug-inを使わずにテレビ電話サービスを作ることが出来るようになります。

WebRTCってなーに?

WebRTCのプロジェクトページの冒頭で、WebRTCを以下のように定義しています。

WebRTC is a free, open project that enables web browsers with Real-Time Communications (RTC) capabilities via simple Javascript APIs. The WebRTC components have been optimized to best serve this purpose.
ベタに訳すると「WebRTCはオープンなプロジェクトです。簡単な複数の Javascript API を用いることで、Webブラウザでリアルタイム通信を可能とするものです。WebRTC の各コンポーネントは、この目的に対しベストとなるよう最適化されています」てな感じ。Google, Mozilla, Operaによりサポートされているプロジェクトで、このページ自体は Google Chrome チームにより運用されています。標準化については WebSocket と同様、JSのAPIはW3C, 通信プロトコル部分は IETF と分担して検討されています。(上のプロジェクトページに、それぞれのドラフトへのリンクもはられています)

上に述べたように、WebRTCは単一のAPIではなく、主に二つのAPIから構成されています。

Stream API
映像や音声などのストリームデータを扱うAPIです。カメラからのライブ映像のように、時々刻々と変わり続けるデータ(一つのまとまったファイルではなく)を取り扱うことができるようになります。例えば、getUserMedia() を使うと、カメラとマイクから映像と音声のストリームデータを操作できるようになります。
Peer-to-peer connections
ブラウザーブラウザ間でUDPを用いダイレクトにデータを送受信するためのAPIです。HTTPやWebSocketでは、必ずサーバーを介してブラウザどうしを接続しますが、このAPIではデータを送受信するときにサーバーは介しません(後述のシグナリング用に別途サーバーは必要ですが)。Stream APIで取得したストリームデータを互いのブラウザ間でダイレクトに送受信します。
軽くまとめると、Steram API で、カメラやマイクに接続し、映像・音声ストリームをPeer-to-peer connectionsで使って互いのブラウザに送る。そうすると、テレビ電話が作れるよね♪って感じ。

何はともあれデモ

先に紹介したプロジェクトページで、テレビ電話のサンプルデモが公開されています。Chromeのdevチャネル or canaryでこのデモを試せます(chrome://flags「Media Streamを有効にする」のを忘れずに)。https://apprtc.appspot.com/にアクセスすると、画面の下部にテレビ電話ルーム用のURLが表示されます。このURLを別のブラウザで開き、しばらく待つとテレビ電話が始まります。ただし、ネットワーク環境によっては動かない(後述)ことがありますので、その時は同一マシン内で「ほほぉー」とほくそ笑んで我慢してください。

rtc_screenshot

WebSocketとの違いは?

さて、このWebRTCは先にもあげたように「Webでリアルタイム通信を可能にする」APIです。。。と聞くと、一つ疑問が。それは WebSocket との違いです。WebSocket が出てきたことで、双方向通信が出来るようになって「リアルタイムWeb」なんて言葉がメジャーになりました。そう、「Webでリアルタイム通信を可能にする」のはWebSocketが既に実現しているはずです。だとすると、WebRTCって何なの?なんで必要なの?という疑問が湧いてくるわけです。

送受信するデータの特性によって使い分けるのが吉

。。。と、答えを書いてみました。でもなんか難しいですね。きちんと解説しましょう。データには大きく二つのタイプがあります。一つは完全性が要求されるデータ例えばテキストデータなんかが該当します。

例えば、チャットで「はろー」というメッセージを送ったとして、これが一文字づつ送信されるとします。「は」「ろ」「ー」って一文字づつ。ここで、例えば「ろ」というデータがインターネットのどこかで無くなっちゃった場合を考えてみましょう。無くなったままで扱われてしまうと、相手には
はー って表示されてしまいます。「大丈夫か?落ち込んでるの?」とかいらぬ心配をかけちゃいますね。大変です。
なので、こういうデータでは普通データが無くなった場合に、そのデータを再送するということをします。それによって完全性を保証するわけです。そうすれば、相手には はろー ってきちんと表示されますよね。安心です。

もう一つはある程度不完全でもいいデータです。映像なんかが典型的です。
不正確なのを承知でざっくり言うと、映像というのは1秒間に30コマの画像が送られてきて、それをぱらぱら漫画で見ているという感じでサービスが提供されています。ここで例えば、先程の「ろ」のように画像が一コマ落ちたとします。そうすると、受け手側では何がおきるでしょう?答えは「落ちたことに気づかない」んです。一こま落ちるというのは、ぱらぱら漫画で言えば、1枚1枚きれいにぱらぱらしていたのが、ある瞬間だけ 2 枚ぱらっとしてしまうことに相当します。そんなの気づかないですよね。つまり、映像データはある程度だったら落ちちゃっても構わないってことなんです。

で、WebSocketは完全性が要求されるデータに適しています。TCPという落ちたときに再送するプロトコルの上で動くように仕様化されていますので、テキストデータのように落ちちゃ困るデータを扱うときはこちらを使います。
一方、WebRTCはある程度不完全でもいいデータに適しています。UDPという落ちてもほっとくプロトコルの上で動きます。下手に再送とかがかかると、それが完了するまで受け手側では待たされちゃうんで、それよりだったら、落ちたら落ちたでいいから、さっさと次に行ってよという時に最適って訳です。

上に書いたのはあくまで分かりやすい単純なケースで突き詰めると他にも色々あります。こういうことを書くと訳が分からなくなるかもしれませんが、実際テキストメッセージを交換するのにUDPを使うこともあります。この辺を突っ込むと話が難しくなっちゃうので割愛しますが、要は提供するデータ・サービスに応じてWebSocketとWebRTCを上手く使い分けるのが吉ってことになります。

最大の敵。それはNAT

で、WebRTCの Peer-to-peer connectionsでは、UDPという落ちちゃってもいいプロトコルを使って、相手のブラウザにダイレクトに映像データなんかを送ります。この相手のブラウザにダイレクトに送るというのが一般的には問題になります。

通常、家からネットに接続するときに、各端末にはプライベートアドレスがふられます。で、プライベートアドレスのままだと、インターネット上のサーバーには接続できないので、おうちのブロードバンドルーターくんがインターネットにでていくときにプライベートアドレスをグローバルアドレスに変換したり、他にもごにょごにょしてからサーバーに接続しています。こういうアドレス変換動作をNATと呼びます。
で、通信する相手がサーバーの時はそれでいいのですが、Aさんが友達のBくんにWebRTCを使って接続する場合に困ったことがおこります。なぜならBくんのブラウザのIPアドレスはプライベートアドレスになってしまうため、インターネットにそのアドレスあてに送っても届かないってことになってしまうからです(ってゆーか送っちゃいけないんですが)。なので、AさんはBくんのブロードバンドルーターにつけられているグローバルアドレスあてにデータを送る必要があります。

スライド1

問題はそれだけではありません。NATではグローバルアドレスからプライベートアドレスに変換するときに、ポート番号という識別子を見て、家の中のどの端末(もっと言うとアプリケーション)にデータを送るかを決めます。このポート番号は、通常インターネットへ通信が起こるたびに動的に設定されます。なので、Aさんは「ころころ変わるポート番号とグローバルIPアドレスを知って、それに対してデータを送る」ということをやらなければならなくなります。本当にやっかいです。となると、この「ポート番号とグローバルアドレスのペア」をどうやってBさんに伝える?というのが問題になります。

STUNを使おう

これを解決するために、"STUNサーバー"というのを使います。まず、AさんはSTUNサーバーに対してUDPでパケット(データ)を送ります。そうすると、STUNサーバーにはNATで動的に割り当てられたグローバルアドレスとポート番号に変換されたパケットが届き、この情報を知ることができます。サーバーはその情報をAさんに返してやります。こうすることで、Aさんは自分に割り当てられたグローバルアドレスとポート番号のペアを知ることができます。(STUN以外にもTURNとか色々ありますが、あまり明るくないので割愛)

シグナリング

次にAさんはSTUNサーバーから貰ったアドレスとポート番号のペアをBさんに教えてあげる必要があります。このため、シグナリングサーバーというものを使います(SIPサーバーとかXMPPサーバーとかを使うのが普通ですが、単純につなぐだけならこれらのサーバーを使わなくてもOK)。Aさんは、このシグナリングサーバーに対して前述のアドレスとポート番号情報を送ると、シグナリングサーバーはその情報をBさんにPushします(なので、Bさんはシグナリングサーバーと事前に繋がっている必要があります。あと、データのPushが必要になりますので、CometよりもWebSocketを使いたくなってしまうところです)。ここまで来ればしめたもので、貰ったアドレスとポート番号のペアに対して、BさんはAさんに映像/音声データを送ることができるようになります。

スライド2

また、BさんからAさんに対しても、「STUNに繋いで・・・シグナリングメッセージを送って・・・」といった先に示したフローと同様のことをやれば、双方で映像/音声データを送ることが可能。てことで、目出度くテレビ電話の出来上がりです。テレビ電話を実現するのって色々とめんどくさいですね。で、これを簡単に使えるようにしたいというのが冒頭に上げた WebRTC プロジェクトのスローガンということになるんだろうなと僕は思っています。(現状WebRTCをコーディングするとちと面倒な感じがするのですが、これを更に簡単にしていこうという意向は見受けられます)

STUNが使えるかは環境依存

一見矛盾なく動く一連の流れですが、実は大きな課題をはらんでいます。グローバルアドレスとポート番号を知るためにAさんはSTUNサーバーを使ったわけですが、実際のデータはBさんが送信元となって、このアドレスとポート番号あてに送信します。従って、NATが「なんだ、このデータ、最初アクセスした(STUNサーバー)のアドレスと違うところから送られてるじゃねーか、落としちまえ」と判断してしまうとこのシナリオは全て破綻してしまうのです。実際、上にあげたシナリオはどのIPアドレスから送信されようが、所定のアドレス/ポートペア宛のパケットであれば、該当の端末に届ける類のNATでないと動きません(こーゆーNATをフルコーンNATといいます。まぁ、他にもあるのですが、この辺はホント難しいので、ここでは割愛します。より詳しく知りたい方は、こちらを確認されるといいかと)。

スライド3

てことで 「WebRTC は動く場合と動かない場合がある。それは双方のユーザーがどーゆーNATを使っているか次第なのさ」ということになります。うーーーん、やっかい。プライベートアドレス & NATが絡むと、物事はほんとに難しくなります。(なので、グローバルIPアドレスが各ユーザーにふられるIPv6のほうがやりやすいよね!!という話になってきます。IPv4アドレスの枯渇問題が発生し IPv6 の利用が現実的となってきたタイミングと、WebRTCの本格実装が始まったタイミングが同調したのは、偶然ではないように感じられます)

WebRTCをコーディングしてみる!!

という訳で、WebRTCを用いたコーディングを解説したいと思います。今回参考としたのは、先に紹介したWebRTCデモサイトのrepositry (http://code.google.com/p/webrtc-samples/)で公開されている http://code.google.com/p/webrtc-samples/source/browse/trunk/apprtc/index.htmlです。これを基に https://github.com/KensakuKOMATSU/webrtc_test/blob/master/views/peerconnection.ejsを自分なりに書いてみたので、そこからコーディングのポイントを解説します。

全体の流れ

WebRTCの全体の流れは、先に上げたとおりですが、もう一度整理してみます。
  1. カメラとマイクに接続する
  2. STUNサーバーに接続する
  3. Peerの接続先に自分の接続情報を伝える
  4. Peerからの映像/音声ストリームを受信する
ちなみに、以下で示すAPIは W3C ドラフトに記述されている最新仕様とは異なりますのでご注意ください(僕が試した時点で動くコードです)。

カメラとマイクに接続する

まず、カメラとマイクから映像と音声ストリームを取得します。これを実行するAPIがgetUserMedia()です。
navigator.webkitGetUserMedia("video,audio", onGUMSuccess, onGUMError);
第一引数で、映像と音声デバイス(それぞれカメラとマイクから)にアクセスすることを、第二引数と第三引数ではそれぞれデバイスアクセスに成功/失敗したときのコールバック関数を指定しています。デバイスアクセスに成功すると、映像・音声ストリームに対するオブジェクトが返されます。
function onGUMSuccess(stream){
  var url = webkitURL.createObjectURL(stream);
  $("video#local")[0].src = url;
  localStream = stream;

  $("button.start").one('click', function(e){
    $(this).attr("disabled", "true");
    createPeerConnection();
  });
}
コールバック関数の第一引数として、streamオブジェクトが与えられますので、これに対し各種操作を行います。前半部分は、ローカルの画面上に自分の映像を表示する部分。webkitURL.createObjectURL()でストリームにアクセスするBlobURLを取得し、それをvideoタグのsrc属性としてセットするだけで画面上にカメラ映像が表示されます。また、後々で使うために、グローバル変数 localStream にこのstreamを代入しています。
後半は、相手先(Peer)への接続を開始する部分。今回作成したサンプルでは、ローカルとリモートで二つのブラウザが開いた状態でのみ動く非常に粗い作りになっていますので、その辺りを手動制御できるようボタンをクリックしたときに接続開始するようにしました。

STUNサーバーに接続する

ではピアへの接続に移ります。createPeerConnection()では、以下のように Peer-to-peer connections APIを用いています(STUNサーバーとして、googleが公開しているサーバーを利用しています)。
function createPeerConnection() {
  pc = new webkitDeprecatedPeerConnection("STUN stun.l.google.com:19302", onSignalingMessage);

  pc.addStream(localStream);

  // set handlers for peerconnection events
  pc.onconnecting = onSessionConnecting;
  pc.onopen = onSessionOpened;
  pc.onaddstream = onRemoteStreamAdded;
  pc.onremovestream = onRemoteStreamRemoved;
}
1行目のwebkitDeprecatedPeerConnection()で、STUNサーバーに接続し、自分のグローバルアドレスとポート番号を取得しています。これが成功すると、コールバック関数 onSignalingMessageが呼ばれます(後述)。ちなみに、メソッド名に Deprecated とあるように、このメソッドはいずれ使えなくなります。ご注意を
その後の pc.addStream() で先ほど取得した stream オブジェクトをピアに送信するストリームとして指定しています。また、ピアとの接続中などなどに各種イベントが発生しますので、そのハンドラもここで指定しています。

Peerの接続先に自分の接続情報を伝える

webkitDeprecatedPeerConnection()で、STUNサーバーからアドレス&ポート番号情報取得に成功すると、指定したコールバック関数が呼び出されます。ここで第一引数に接続用のSDPメッセージ(STUNから取得したアドレス&ポート番号のペアや、映像情報などが記述されている)が格納されていますので、これを何らかの手段でピアのブラウザに渡します(僕が作成したサンプルでは、シンプルにWebSocketを使って、ピアの接続先にこのメッセージを渡しました。WebSocketのコーディングはchatを作る場合と殆ど同じで大丈夫です)。イメージとしては、以下のような感じ。
function onSignalingMessage(mesg) {
  // WebSocketを使って、ピアにSDPメッセージを転送する
  ws.send(mesg);
}
ピアでSDPメッセージを受信したら、Peer-to-peer connections APIのprocessSignalingMessage()メソッドを使って、SDPメッセージを基に接続処理を行います(受け側で、Peer-to-peer connections のインスタンスがない場合は、コンストラクタを呼んでおくことをお忘れなく!!)。
ws.onmessage = function(e){
  // Peer-to-peer connections のインスタンスがない場合は、まず最初に
  // コンストラクタを呼ぶ
  if(!!pc === false) {
    createPeerConnection();
  }

  // SDPメッセージより、接続処理を行う。
  pc.processSignalingMessage(e.data);
}

Peerからの映像/音声ストリームを受信する

接続処理中に、リモートピアからの映像・音声ストリーム受信が可能になるとピアコネクションのインスタンスに対して addstream イベントが発生しますので、これに対するコールバックとして受信ストリームを画面に表示する処理を記述します。
function onRemoteStreamAdded(e) {
  var url = webkitURL.createObjectURL(event.stream);
  $("video#remote")[0].src = url;
}
引数で与えられるイベントオブジェクトの stream プロパティに、リモートから受信する映像・音声ストリームのオブジェクトが格納されていますので、localStreamの場合と同様 webkitURL.createObjectURL() でBlobURLに変換後、所定の videoタグのsrc属性にセットします。簡単ですね。

簡単ではありますが、ただ単にビデオ会議をするだけであれば、これぐらいでコーディングできます。なお、https://github.com/KensakuKOMATSU/webrtc_testにサーバーコード含め公開していますので、興味のある方は確認してみてください。

今回紹介したのは、WebRTCの一部です。まだ僕自身キチンと理解していないので、ここで紹介はさけますが、W3Cのドラフトを見ると、なかなか奥は深そうです。これまでのWebに対する考え方を一変するのは間違いないAPI。今後の進展が楽しみですね!!



人気ブログランキングへ
kotesaki at 21:52│Comments(15)TrackBack(0)clip!html5 | webrtc

トラックバックURL

この記事へのコメント

1. Posted by шунгит   2012年08月10日 09:34
興味深いことに
2. Posted by tattoo needles   2012年09月04日 00:11
簡単ですね。
3. Posted by Chest Tattoos   2013年01月08日 00:42
興味深いことに
4. Posted by hoger   2013年01月22日 17:30
簡単ですね。
5. Posted by vanessa bruno   2013年01月29日 17:48
また、ピアとの接続中などなどに各種イベントが発生しますので、そのハンドラもここで指定しています
6. Posted by RS Gold   2013年02月26日 15:10
簡単ですね。
7. Posted by http://lifestyle.alrazaak.com/Bridal_Mehndi_Designs.html   2013年03月12日 02:49
、ピアとの接続中などなどに各種イベントが発生しますので、そのハンドラもここで指定しています
8. Posted by Henna Designs   2013年03月19日 00:59
ピアとの接続中などなどに各種イベントが発生しますので、そのハンドラもここで指定しています
9. Posted by Diablo 3 Gold kaufen   2013年03月22日 22:20
にビデオ会議をするだけであれば
10. Posted by mehndi designs   2013年04月13日 02:43
デオ会議をするだけであれば
11. Posted by sir kay bal lambay karnay ka wazifa   2013年05月01日 03:25
ビデオ会議をするだけであれば
12. Posted by guild wars 2 gold   2013年05月14日 12:30
5 接続処理中に、リモートピアからの映像・音声ストリーム受信が可能になるとピアコネクションのインスタンスに対して addstream イベントが発生しますので、これに対するコールバックとして受信ストリームを画面に表示する処理を記述します。
13. Posted by Nokia Asha 303 price in Pakistan   2013年06月08日 19:21
ビデオ会議をするだけであれば
14. Posted by クリスチャンルブタン パンプス   2014年02月17日 08:48
クリスチャンルブタン Christian Louboutin 靴,アウトソールや、ヒール部分に若干の擦れがあり、左の真ん中のステッチに軽微なほつれが見られます。インソール踵部分に僅かな押し跡がございますが、いずれも目立つものではなくきれいな状態です。
15. Posted by adidas Women Shoes sale   2016年05月13日 17:41
This piece of writing こてさきAjax:WebRTC事始め - livedoor Blog(ブログ) concerning how to embed a YouTube video code is genuinely valuable in favor of new internet users. Pleasant job, keep it up.
adidas Women Shoes sale http://wcbrand.com/adidas/adidas-women-shoes_c4

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔