WebRTCのシグナリング
WebRTCでは、Peer-to-Peer通信を始める前に、お互いの情報を交換するためのシグナリングと呼ばれる処理があります。その過程で2種類の情報をやり取りします。
- SDP: Peerの情報
- ICE Candidate: 通信経路の情報
通常はこの2つは異なるイベントをトリガーにしてやり取りします。
- SDPの送信: PeerConnection.createOffer() / PeerConnection.createAnswer() のコールバック (1回ずつ)
- ICE Candidateの送信: PeerConnection.onicecandidate() イベントハンドラ (複数回)
そのため最低2往復、場合によっては4往復以上のやり取りがあります。
とこが、これを簡略化して1往復で済ませてしまう方法があるようです。
簡易シグナリングの仕組み
通常のシグナリングの流れは、次のようにSDPと、ICE Candidateを別々にやり取りします。
- Peer AからcreateOffer()で生成された Offer SDP をPeer Bに送信
- Peer Bでは、createAnswer()で Answer SDP を生成し、Peer Aに返信
- それぞれのPeerで、onicecandidate()イベントハンドラに渡されたICE Candidateを、相手に送信(複数回)
createOffer()/createAnswer()で生成されるSDPは、次のような内容になっています。
sdp: "v=0 o=- 3336702409326257609 2 IN IP4 127.0.0.1 ...以下略... "
type: "offer" // <-- または "answer"
この sdp の部分の文字列が、実際のPeerの情報です。これを相手に送るのですが、同時にPeerConnection.setLocalDescription()に渡して自分でも覚えます。覚えたsdpの値は次の属性値として取得することができます。
PeerConnection.localDescription.sdp
その後、複数回 onicecandidate()イベントが発生しますが、発生するたびに PeerConnection.localDescription.sdpの値が変化していきます。 最初と最後の差分を取ってみると、次の行が増えています。
< m=audio 1 RTP/SAVPF 111 103 104 0 8 106 105 13 126
---
> m=audio 64980 RTP/SAVPF 111 103 104 0 8 106 105 13 126
> a=candidate:2999745851 1 udp 2122260223 192.xxx.xxx.xxx 64980 typ host generation 0
> a=candidate:2999745851 2 udp 2122260223 192.xxx.xxx.xxx 64980 typ host generation 0
> a=candidate:2747735740 1 udp 2122194687 192.xxx.xxx.xxx 64981 typ host generation 0
> a=candidate:2747735740 2 udp 2122194687 192.xxx.xxx.xxx 64981 typ host generation 0
> a=candidate:1606961068 1 udp 2122129151 10.xxx.xxx.xxx 64982 typ host generation 0
> a=candidate:1606961068 2 udp 2122129151 10.xxx.xxx.xxx 64982 typ host generation 0
> a=candidate:1435463253 1 udp 2122063615 192.xxx.xxx.xxx 64983 typ host generation 0
> a=candidate:1435463253 2 udp 2122063615 192.xxx.xxx.xxx 64983 typ host generation 0
> a=candidate:4233069003 1 tcp 1518280447 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:4233069003 2 tcp 1518280447 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:3980714572 1 tcp 1518214911 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:3980714572 2 tcp 1518214911 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:290175836 1 tcp 1518149375 10.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:290175836 2 tcp 1518149375 10.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:453808805 1 tcp 1518083839 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:453808805 2 tcp 1518083839 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
< m=video 1 RTP/SAVPF 100 116 117 96
---
> m=video 64980 RTP/SAVPF 100 116 117 96
> a=candidate:2999745851 1 udp 2122260223 192.xxx.xxx.xxx 64980 typ host generation 0
> a=candidate:2999745851 2 udp 2122260223 192.xxx.xxx.xxx 64980 typ host generation 0
> a=candidate:2747735740 1 udp 2122194687 192.xxx.xxx.xxx 64981 typ host generation 0
> a=candidate:2747735740 2 udp 2122194687 192.xxx.xxx.xxx 64981 typ host generation 0
> a=candidate:1606961068 1 udp 2122129151 10.xxx.xxx.xxx 64982 typ host generation 0
> a=candidate:1606961068 2 udp 2122129151 10.xxx.xxx.xxx 64982 typ host generation 0
> a=candidate:1435463253 1 udp 2122063615 192.xxx.xxx.xxx 64983 typ host generation 0
> a=candidate:1435463253 2 udp 2122063615 192.xxx.xxx.xxx 64983 typ host generation 0
> a=candidate:4233069003 1 tcp 1518280447 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:4233069003 2 tcp 1518280447 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:3980714572 1 tcp 1518214911 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:3980714572 2 tcp 1518214911 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:290175836 1 tcp 1518149375 10.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:290175836 2 tcp 1518149375 10.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:453808805 1 tcp 1518083839 192.xxx.xxx.xxx 0 typ host tcptype active generation 0
> a=candidate:453808805 2 tcp 1518083839 19.xxx.xxx.xxx 0 typ host tcptype active generation 0
増えているのは全て a=candidate の行です。つまり、ICE Candidateの情報がSDPの中に追加されて保持されています。 ここまで出揃ったら、初めてSDPを相手に渡します。戻るAnswer SDPも、ICE candidateが出揃ってから送り返します。 すると、1往復ですべての情報が揃い、Peer-to-Peer通信が始まります。
※ICE Candidateが出そろったという判断は、onicecandidate()イベントハンドラに渡される引数で判定しています。
簡易シグナリングの特徴
この方式の特徴は次の通りです。
- 利点:やりとりがシンプルになる
- 欠点:すべてのICE candidateが出そろうまでは、Peer-to-Peer通信が始まらない。(※通常の手順なら、やり取りの途中で通信可能な経路が発見された時点で、Peer-to-Peer通信が開始される)
この裏ワザ(?)は、Kurentoの調査を行う過程でソースを読んで発見しました。やはりソースを読むのが一番の勉強になりますね。
※前日の記事 歴史で振り返るWebRTC を読んだら、もともとはSDPの中にcandidateも含んでいたことを知りました。今回の簡易シグナリングは先祖がえりなのですね。
サンプル
サンプルを使って、実際に手動シグナリングで試すことができます。
- 2017年の新サンプル ... Chrome, Firefox, Safari 11 (Max OS X) 対応
- 2014年の旧サンプル ... Chrome のみ
カメラをお持ちの人は、Chromeで2つのウィンドウで開いて試してみてください。手順はこの図の通りです。