WebRTCデバッグ入門

# はじめに

- この資料は、[WebRTC入門者の会(2016.11.01)](http://connpass.com/event/39679/)用の資料です。
- 資料の全体はこちらのINDEXを参照してください。
  - [WebRTCハンズオン資料 INDEX - Qiita](http://qiita.com/yusuke84/items/de9f0f6d221acec6fc07)

---

# 自己紹介

- 名前: なかゆうすけ（[@Tukimikage](https://twitter.com/Tukimikage)）
- 仕事:
  - [SkyWay](https://skyway.io)のDevrel
  - [HTML5 Experts.jp](https://html5experts.jp)の運営
- コミュニティ：
  - [WebRTC Meetup Tokyo/Osaka/Japan](https://atnd.org/groups/webrtc)主催


---

# 今日のスライドの目的

- WebRTCサービスを開発する際に必要となる、各種デバッグ技術をお伝えします

---

# 前提条件

- Webブラウザでの利用を前提とします
- [WebRTCハンズオン](http://qiita.com/yusuke84/items/de9f0f6d221acec6fc07) の知識が最低限必要となりますので、まだ読んでいない方は先にご一読ください。
- MediaChannelのみしか扱いません
- デバッグ方法やそのためのノウハウをご紹介するだけなので、事象解決には別の努力がきっと必要です

---

# 目次

- よくあるトラブルユースケース
- その他知っておくと便利なトピックする

---

# よくあるトラブルユースケース
  -  うまくつながらない時がある

---

## 「つながらない」場合の切り分け王道パターン
  - 切り分け１：ネットワーク的に接続されているか
  - 切り分け２：音声、映像ストリームが流れているか

---

### 切り分け1

- ネットワーク的に接続されているかどうかを確認する
  - Chromeの場合
      - アドレスバーで `chrome://webrtc-internals`
      - 一つだけ **太字** の **Conn-xxxx-x-x** という項目がある（有効なTransport/輸送路）
          - **存在しなければネットワーク的に接続されているか**
          - ある場合でもタイムスタンプが更新されていない場合は**その時点で利用されていない**

---

- サンプル

![no1.png](https://qiita-image-store.s3.amazonaws.com/0/6651/233500f8-b15a-7157-139c-8325f671a0a8.png)

- 表示されている項目と意味（重要なものだけ抜粋）

|項目|意味|
|:--|:--|
|timestamp|タイムスタンプ<BR>現在利用しているTransportかどうかはこのタイムスタンプが更新されているかどうかで判断|
|googxxxxAddress|ローカル・リモートそれぞれのIPアドレスとポート番号|
|xxxxCandidateId|ローカル・リモートそれぞれで採択されたCandidate情報のID※1（通信するためのIPアドレス、ポート番号等の候補情報）<BR>このIDでサイト内を検索すると詳細が確認できる|
|googxxxxCandidateType|ローカル・リモートそれぞれのCandidateタイプ<BR>`relay` : TURN経由の接続<BR>`それ以外` : TURNを使用せずにダイレクト接続|

---

- ※1 採択されたCandidate情報
  - これはFirefoxの方が見やすい
      - アドレスバーで `about:webrtc`

![no2.png](https://qiita-image-store.s3.amazonaws.com/0/6651/8d2c4f23-00d7-3d22-47b0-c2b6a13b0ef1.png)


---

### 切り分け2

- 音声、映像ストリームが流れているかを確認する
  - Chromeの場合
      - アドレスバーで `chrome://webrtc-internals`
          - AudioTrackとVideoTrack情報を確認する
              - `ssrc_xxxxxxx_send` : 送信側でAudioとVideoで２つ（Track単位）
              - `ssrc_xxxxxxx_recv` : 受信側で同じく２つ
          - **送受信バイト数がカウントアップしていればストリームは流れている**
              
---

- サンプル

![no3.png](https://qiita-image-store.s3.amazonaws.com/0/6651/0f1b22ed-f40c-547b-95eb-21e1d3915312.png)

- 表示されている項目と意味（重要なものだけ抜粋）

|項目|意味|
|:--|:--|
|msid|MediaStreamIDでAudioTrackとVideoTrackを束ねた１つのStreamを指す<BR>JavaScriptから識別することができる|
|timestamp|タイムスタンプ<BR>有効なTrackかどうかはこのタイムスタンプが更新されているかどうかで判断|
|bytesSent|送受信バイト数|
|mediaType|audio/video|
|packetsLost|パケットがロストした場合に表示される|
|ssrc|Synchronisation Sourceという。ここではRTP（リアルタイムプロトコル）で利用する識別子|
|transportId|先程見た `Conn-xxxx-x-x` に該当<BR>このTrackがどのTransport（輸送路）を利用しているかわかる|
|googCodecName|仕様しているコーデック<BR>Audioであれば、`Opus`が主流<BR>Videoであれば、`VP8`や`H264`が主流|
|googTrackId|TrackIDでJavaScriptから識別することもができる|

---

- 参考：Transport、MediaStream、Trackの関係

![no4.png](https://qiita-image-store.s3.amazonaws.com/0/6651/c39a6826-d35b-5e61-3ebc-ad7ff2549bf1.png)

---

# よくあるトラブルユースケース
  -  カメラの映像やマイクの音声が取得できない

---

## ブラウザのデバイス認識状態を確認

- プルダウンで選択しブラウザをリロードすることで有効になる
  - Chrome
![no5.png](https://qiita-image-store.s3.amazonaws.com/0/6651/1b2844fc-e714-86d3-9370-0a4295c91dc6.png)
  - Firefox
![no6.png](https://qiita-image-store.s3.amazonaws.com/0/6651/60083c10-895b-2db2-547b-2b92f1671550.png)

- PCに利用できるマイク、カメラが接続されていない場合、 `navigator.mediaDevices.getUserMedia` のAPIでエラーが返ってくるので注意
- Chromeは一度デバイス選択するとその状態が記録されるので、うっかり**アクセスをブロック**すると、以後ブロックを明示的に解除するまで利用できないので注意

---

- ブラウザで認識しているのに映像が出ない、音が聞こえない場合に考えられること
  - Video
    - `navigator.mediaDevices.getUserMedia`で指定するキャプチャサイズ（`width`と`height`）の映像をWebカメラから取得できない
  - Audio
    - PC側のマスターボリュームが`0`になっているなど

---

# よくあるトラブルユースケース
  - PCやブラウザの動作が重い、途中で映像が止まる、音が途切れる

---

## P2P接続によるマシン負荷が高い

- P2Pによるビデオチャットを複数本同時に行こない（フルメッシュ接続）多人数ビデオチャットを実現する場合、マシンへの負荷が増大する
  - ![no7.jpeg](https://qiita-image-store.s3.amazonaws.com/0/6651/b1bc3cec-c513-2a20-298a-e812776723de.jpeg)
  - PCなどでは主にエンコード処理の負荷が原因
  - モバイル端末ではデコード処理も負荷要因になる可能性も

---

## P2P接続によるマシン負荷が高い

- 対策1
  - `navigator.mediaDevices.getUserMedia` で指定するキャプチャサイズを小さくする
  - 画面共有等はフレームレートを下げる（Firefoxでは効かない）

```js
function startVideo() {
    navigator.mediaDevices.getUserMedia({
        video: {
            width: {
                min: 320,
                max: 1280
            },height: {
                min: 240,
                max: 720
            },frameRate: {
                min: 1,
                max: 10
            }
        },
        audio: true})
        .then(function (stream) { // success
            playVideo(localVideo,stream);
            localStream = stream;
        }).catch(function (error) { // error
        console.error('mediaDevice.getUserMedia() error:', error);
        return;
    });
}
```

---

## ネットワークの品質（速度等）が良くない

- WebRTCにはネットワーク帯域を推定し動的に転送レートを変動させるが、限度がある
- 【注意】プロバイダが提供する速度測定サイトの結果はWebRTCではあまりあてにならない
  - 速度測定サイトはプロバイダが提供するサーバとの転送速度を測るものだから
  - WebRTCではEnd-to-Endでの速度を測定する必要がある   

---

### 参考：WebRTCはどのくらいの通信帯域を利用するのか
  - 送信する映像のサイズ、フレームレート数によってい左右される

- 測定条件
  - カメラ前の画面の動きはほぼ無い
  - ローカルで計測（ネットワーク遅延は無視）
  - ブラウザは Chrome M54
  - コーデック：VP8

- 測定結果

|設定|ビットレート|
|:--|:--|
|640x480/30fps|![no10.png](https://qiita-image-store.s3.amazonaws.com/0/6651/fab226b1-36e1-242a-c9fc-ead4dc1f3430.png)<BR>1.5Mbps前後|
|320x240/30fps|![no11.png](https://qiita-image-store.s3.amazonaws.com/0/6651/618d2591-cd8f-91b7-1741-f647360d6331.png)<BR>600kbps前後|
|320x240/10fps|![no12.png](https://qiita-image-store.s3.amazonaws.com/0/6651/957284f3-8a84-8f45-ac36-62e1a7a783db.png)<BR>400kbps前後|
|1920x1080/30fps|![no13.png](https://qiita-image-store.s3.amazonaws.com/0/6651/50d7203b-5ba1-738e-0308-f4e8302a7b77.png)<BR>2Mbps前後|

---

- WebRTCによるアプリケーションを実業務で開発導入する場合は、ネットワーク設計まで含めて考慮憂する必要がある

---

## P2P接続によるマシン負荷が高い

- 対策2
  - WebRTCプラットフォームサービスが提供するMCUやSFUを利用する
  - MCUやSFUを自分で建てて運用する（上級）

---

### MCU(Multipoint Control Unit)
  - メディアサーバを用意して映像音声をミキシングすることで、人数が増えた場合でも、送受信するメディアの数が変わらない
  - トランスコード、オーバーレイ、録画、録音、何でもできるが、サーバ側の処理で画面レイアウトが決まってしまう等、クライアント側での自由度がない
  - 代表例：Skype
    
![no8.jpeg](https://qiita-image-store.s3.amazonaws.com/0/6651/ff5026b9-94e2-a24f-cf5d-94a74314956d.jpeg)

---

### SFU(Selective Forwarding Unit)
  - 送信側のMediaをサーバで受信者数分スプリットして配信することで、送信側の負荷（エンコード負荷）を押さえることができる
  - 受信者の処理能力にバラツキがある場合には、RTCPを利用し品質をコントロールする機能が、SFU側に必要となる
    - Simulcast+SVCという技術を組み合わせて解決する方法もある（この場合は、受信側の対応が必須）

  - 代表例：Google Hangout

![no9.jpeg](https://qiita-image-store.s3.amazonaws.com/0/6651/c589e61c-2c71-7474-668a-f3f643f11e28.jpeg)


---

# その他知っておくと便利なトピックする
  - ICEによる自動再接続

---

## ICEとは？

- ICE（Interactive Connectivity Establishment）はNAT超えするためのユーティリティ
- WebRTCではICEを利用してP2P通信を確立する
  - ICEの手順
    - １.通信候補の収集
      - IceCandaidateという情報をブラウザが収集する
    - 2.通信候補の交換
      - シグナリングサーバを介して相手と交換
    - 3.通信候補の整理
      - P2PなのかRelayなのか（つまりTURN経由）等を考慮して優先度が高い順に候補を並べる
      - TrickleICEという手法の場合は省略される
    - 4.通信の試行
      - 一般的にUDPホールパンチングと呼ばれる手法を利用して通信を試みる
      - 新しく候補が見つかることもある
    - 5.通信手段を確定
      - 通信に成功した候補の中からどのルートを使うかを確定する（制御権を持つPeerが決める）

---

### 実は繋がったら終わりではない

- ICEは通信状態をモニタリングしており、一時的な通信断については自動で復旧する

<img width="625" alt="no15.png" src="https://qiita-image-store.s3.amazonaws.com/0/6651/a99f9f56-daf2-2a28-84e5-367b62b0546b.png">

- 例:前回ハンズオンで開発したアプリの場合

```js
    // ICEのステータスが変更になったときの処理
    peer.oniceconnectionstatechange = function() {
        console.log('ICE connection Status has changed to ' + peer.iceConnectionState);
        switch (peer.iceConnectionState) {
            case 'closed':
            case 'failed':
                // ICEのステートが切断状態または異常状態になったら切断処理を実行する
                if (peerConnection) {
                    hangUp();
                }
                break;
            case 'dissconnected':
                break;
        }
    };
```

- ICEステータスの変化（コンソール抜粋）
  - 数秒程度の通信断であれば復旧できる可能性あり

```
ICE connection Status has changed to connected ←接続候補をチェック中
ICE connection Status has changed to completed ←全ての候補のチェックが完了
ICE connection Status has changed to disconnected ←故意に通信を切断
ICE connection Status has changed to connected ←通信断を解除
ICE connection Status has changed to completed ←再び接続
ICE connection Status has changed to disconnected ←故意に通信を切断
ICE connection Status has changed to failed　←そのまま放置すると修復不可状態に
sending close message
ICE connection Status has changed to closed ←ICEのプロセスが全て終了
```

---

# その他知っておくと便利なトピックする
  - どのようなシーンでTURNサーバは有効なのか？

---

## TURNサーバはどのように利用されるのか？

- TURNサーバとは？
  - P2Pによる通信ができない場合にリレーするためのサーバ

- Q. こうやってプログラムすれば全ての通信がTURN経由になると思いますか？

```js
    let pc_config = {"iceServers":[
        {"urls":"stun:stun.xxxx.xxx:3478"},
        {"urls": "turn:turn.xxxx.xxx:3478?transport=tcp",
            "username:": "username",
            "credential": "password"
        }]
    };
    let peer = new RTCPeerConnection(pc_config);
```

---

- A. NO
  - TURNサーバはあくまでCandidate候補（接続候補）の１つとして扱われ、実際にTURNサーバを利用するかどうかはネットワークの状況次第でブラウザが判断します
  - STUNのみでつながるか、TURNが必要かの見極めポイント
      - NATタイプによる制限
        - ![no14.png](https://qiita-image-store.s3.amazonaws.com/0/6651/d82b9e05-5a3e-1c28-a54c-9fcdeefc0293.png)
      - 公衆wifi等で導入されるプライバシーセパレータ（端末同士の通信をセキュリティ上規制）
      - UDP通信がブロックされており、TCP通信しか利用できない
          - TURNサーバを利用すると`クライアント→TURNサーバ`の通信をTCP（ポート番号も自由に指定可能）で行うことができる


---

# まとめ

- よくあるトラブルユースケース
  - うまくつながらない時がある
  - カメラの映像やマイクの音声が取得できない
  - PCやブラウザの動作が重い、途中で映像が止まる、音が途切れる
- その他知っておくと便利なトピックする
  - ICEによる自動再接続
  - どんなシーンでTURNサーバは有効なのか？

---

# 参考文献（もっと詳しく知りたい方向け）

- [WebRTCのICEについて知る - SlideShare](www.slideshare.net/iwashi86/webrtcice)
- [SDP for WebRTC - From Basics to Maniacs - SlideShare](www.slideshare.net/iwashi86/20150311-web-rtcmeetup7sdp)
- [SFUの話](http://www.slideshare.net/tnoho/sfu)

---

# ご清聴ありがとうございました！