SkyWayトラブルシューティング入門

**[2020/12/7 追記] [JavaScript SDK v4.1.0](https://github.com/skyway/skyway-js-sdk/releases/tag/v4.1.0)のリリースに伴い、Room形式であってもRTCPeerConnectionを取得することができる様になったため、該当箇所を修正しました。**

# はじめに
初めまして。
WebRTC Platform SkyWayのプリセールスエンジニア、サポートエンジニアのBBこと馬場です。

現在のwith コロナの世界では、多くのものがリモート化され、リモートでのリアルタイムコミュニケーションを実現する技術である、WebRTCが注目されつつあります。
WebRTCは、ブラウザさえあればリモートでのリアルタイムコミュニケーションが実現できるというとても便利な技術ですが、

- つながらない
- 品質が安定しない

と言ったトラブルに見舞われ、そのトラブルシューティングに苦慮されている方も見受けられます。WebRTCのトラブルシューティングにはコツがあります。
SkyWayのエンジニアとして多くのお客様が抱える問題をトラブルシュートしてきたノウハウから、WebRTC(特にSkyWay)をトラブルシュートする上での勘所をご紹介します。

# 想定読者
- SkyWayを使ったWebアプリ開発者
- SkyWayを使ったWebサービスの運用者

※ WebRTC一般的な話も多いので、SkyWay利用者に限らず参考になる点はあると思います。
※ あくまで、SkyWay利用者向けとなるため、Signaling/TURN/SFUなどの運用者向けの情報ではありません。

# SkyWay(WebRTC Platform)のトラブルシューティングの勘所
## トラブルシューティングの躓きポイント
SkyWayのトラブルシューティングをする際の躓きポイントは主に2つあります。

1. クライアントサイドで動作するのでログが取りづらい点
2. 端末スペックやNW環境などコントロールしづらいものがボトルネックになりやすい点

### 1. クライアントサイドで動作するのでログが取りづらい点
WebRTCは主にクライアントサイドで動作します。そのため、ログの取得がクライアントサイドに依存し、取得しづらいという点があります。
WebRTC統計情報を取得するためのAPIが準備されているため、基本的にはそれに頼ることとなります。ブラウザ標準の`chrome://webrtc-internals/`(Chromeの場合)を使う方法や、アプリに組み込み可能なブラウザAPIとして準備されている `getStats()` を使う方法があります。

### 2. 端末スペックやNW環境などコントロールしづらいものがボトルネックになりやすい点
WebRTCで、通話品質が安定しない、端末動作が重いといった事象の原因の多くは、端末スペックやNW環境など、サービス側でコントロールしづらいものです。
特に、コンシューマー向けのサービスの場合には、利用環境が多岐に渡り、低スペックな端末・回線が使われる可能性もあるため、対応が難しい面もありますが、原因を特定し利用者層や利用用途にあわせてパラメータをチューニングしたり、エンドユーザに改善をうながしたりすることで対応可能です。

## トラブルシューティング
以降、下記の想定ケース毎にトラブルシューティング方法を説明します。
- ケース1: ローカルの映像・音声が取得できない
- ケース2: 接続できない
- ケース3: 映像・音声が途切れる、遅延する
- ケース4: 映像・音声品質が悪い
- ケース5: 映像・音声が出力されない
- ケース6: 端末・ブラウザの動作が重い

### ケース1: ローカルの映像・音声が取得できない
#### デバイスからの入力が許可されていない
`getUserMedia()` でローカルの映像・音声が取得できない場合、デバイスからの入力が許可されていない可能性があります。
基本的には、サイトアクセス時に許可を求められるため、エンドユーザが許可すれば問題ないのですが、予め拒否する設定になっており、許可が求められない場合があるため要注意です。

#### 確認方法
必要に応じて、カメラ、マイク、画面共有の使用を許可してください。
`getUserMedia()` や `getDisplayMedia()` でこれらのデバイスが取得できない場合、`rejected` の `Promise` を返します。
確認手順については各ブラウザのマニュアルやヘルプを参照してください。

- [Chrome](https://support.google.com/chrome/answer/2693767?co=GENIE.Platform%3DDesktop&hl=ja)
- [Firefox](https://support.mozilla.org/ja/kb/how-manage-your-camera-and-microphone-permissions)
- [Edge](https://support.microsoft.com/ja-jp/windows/windows-10-%E3%81%A7%E3%81%AE%E3%82%AB%E3%83%A1%E3%83%A9%E3%81%A8%E3%83%9E%E3%82%A4%E3%82%AF-%E3%81%8A%E3%82%88%E3%81%B3%E3%83%97%E3%83%A9%E3%82%A4%E3%83%90%E3%82%B7%E3%83%BC-a83257bc-e990-d54a-d212-b5e41beba857)
- [Safari for Mac](https://support.apple.com/ja-jp/guide/safari/ibrw7f78f7fe/mac)

なお、Mac PCの場合、上記ブラウザ毎の許可設定以外に、OSでの各アプリケーションに対する許可設定が必要となります。

- [マイク](https://support.apple.com/ja-jp/guide/mac-help/mchla1b1e1fe/mac)
- [カメラ](https://support.apple.com/ja-jp/guide/mac-help/mchlf6d108da/mac)
- [画面共有](https://support.apple.com/ja-jp/guide/mac-help/mchld6aa7d23/mac)

### ケース2: 接続できない
そもそも、ネットワーク的に接続されていないケースです。
このケースでは様々な要因が考えられます。

主な要因
- Signalingサーバと接続できない
- 端末とメディア通信の接続ができていない

#### 確認方法( `chrome://webrtc-internals/` の見方詳細は後述)
`webrtc-internals` を利用することで、WebRTCの通信が行われているかを確認することができます。

まず、Signalingサーバと接続できていない場合は、そもそもWebRTCセッションの統計情報のタブが表示されません。

`Stats Tables` セクションにて `RTCIceCandidatePair` を確認してください。
いくつか存在すると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609551/c2cce0d9-e23d-7b91-5648-5547291d3244.png)


今回のケースだと2つありますね。
WebRTCの通信経路は、ICEという仕組みを利用して確立します。正常に通信できている場合には、`nominated: true` となっていて `bytesSent` などがどんどん加算されていっているものがあるはずです。これがない場合は、通信できているWebRTCの経路が存在しないことになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609551/b97c88b6-82db-6120-13dd-64e70b2714eb.png)
使われているPair

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609551/77ebcfca-bbdc-d3b7-6fac-67d740566b18.png)
使われていないPair

Firefoxでも同じように、`about:webrtc`で `RTCIceCandidatePairStats` の情報がみれます。
Firefoxの場合は、`Non-standard properties` の `selected` が採用されていて、これが `true` になっていたら選択されたpairだというのがわかるみたいです。こっちの方がわかりやすいな。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609551/cf0f2b8f-7cf2-e065-8410-e66aa8d37c20.png)


ネットワーク的に接続されていない場合には、端末のNW側に問題があることがほとんどですので、NW環境の確認をお勧めします。

### ケース3: 映像・音声が途切れる、遅延する
これが多分一番多くトラブルとして挙げられるケースだと思われます。低遅延でリアルタイムコミュニケーションをしたいにもかかわらず、これでは困ってしまいますね。
主な要因は、端末側のNWや回線、端末の負荷にあります。

#### 端末側のNWや回線に関する原因確認方法( `chrome://webrtc-internals/` の見方詳細は後述)
端末側のNWや回線側の問題の場合には、これも`webrtc-internals` を利用することで確認できます。

- WebRTC通信全体の送信(受信)ビットレート(`RTCTransport`>`[bytesSent(Received)_in_bits/s]`)
    - WebRTC通信全体の送信(受信)ビットレートが確認できます。
    - 「ビットレートが低い」 = 「回線が細い可能性がある」
    - `RTCInboundRTPAudio(Video)Stream` でSteam毎のビットレートも確認可能。
- MediaStreamのジッター(`RTCInboundRTPAudio(Video)Stream` > `[jitterBufferDelay/jitterBufferEmittedCount_in_ms]`
    - 各MediaStreamのジッターが確認できます。
    - 「ジッターが大きい」 = 「ネットワークの輻輳などによるジッターが原因で遅延や通話品質の低下が発生している可能性がある」
- MediaStreamのパケロス(`RTCInboundRTPAudio(Video)Stream` > `packetsLost`)
    - 各MediaStreamのパケロスが確認できます。
    - 「パケロスが多い」 = 「パケロスが原因で通話品質の低下が発生している可能性がある」

WebRTCのようなリアルタイム通信では、特にジッターが品質に影響することが多いです。回線速度テストツールで十分な速度が確保されているにもかかわらず、通話品質が悪い場合には、このジッターを確認してみてください。
ジッターが大きい場合には、間のネットワークのルーターに原因がある場合が多いです。

#### 端末に関する原因確認方法
端末に原因がある場合の確認方法としては、後述の「ケース6: 端末・ブラウザの動作が重い」を参照してください。

### ケース4: 映像・音声品質が悪い
このケースは、(特にSkyWayでは)SFU Roomを利用している場合がほとんどです。
一般的なSFUは送られてきたメディアをそのまま端末に転送するため、どうしても回線が細い人に合わせる必要が出てきます。
同じ映像を閲覧している人に一人でも回線が細い人がいると、これに引っ張られて(特に)映像品質が劣化してしまうということがあります。
回避策としては Simulcast に対応したSFUを使うことですが、SkyWayのSFUはSimulcastに対応していません。
次期SkyWayではSimulcastに対応したSFUを提供できるよう開発を進めています。

#### 確認方法
これも、`webrtc-internals` で確認していきましょう。

- ビデオ解像度(`RTCInboundRTPVideoStream` > `frameWidth/frameHeight`)
    - 受信したビデオの解像度が確認できます。
    - ビデオの解像度が低いと映像の精細さがなくなり、のぺっとしたような画質になります。
- 受信ビデオフレームレート(`RTCInboundRTPVideoStream` > `framesPerSecond`)
    - 受信したビデオのフレームレートが確認できます。
    - ビデオのフレームレートが低いとカクカクとした映像となります。
- ビデオ解像度/フレームレートの低下理由(`RTCInboundRTPVideoStream` > [`qualityLimitationReason`](https://w3c.github.io/webrtc-stats/#rtcqualitylimitationreason-enum))
    - WebRTCのフロー制御機能(※)により、解像度やフレームレートが制限された理由が確認できます。
        - none: 解像度やフレームレートの制限をしていない
        - cpu: 主にCPU負荷のために制限
        - bandwidth: 主にジッターやパケロスベースでの送信可能帯域推定値(※)のために制限
        - other: それ以外の理由で制限

※ フロー制御機能や送信可能帯域推定については「[WebRTC 映像フロー制御の仕組み - Google Congestion Control](https://qiita.com/komasshu/items/1cb5d4469595a635c689)」でわかりやすく解説されています。

#### その他の要因
映像・音声品質が悪いその他の要因としては、特に音声品質についてはマイク性能が悪いことが考えられます。
WebRTCの画質・音質は入出力デバイスに大きく依存します。
品質を求める場合には、TV会議用のスピーカーや、ヘッドセットのご利用をお勧めします。びっくりするほど変わりますよ。

また、ヘッドセットを使っているつもりでも、意図せずPC内臓のマイクを使ってしまっているなんてこともあります。
PC内臓のマイクは打鍵音が入ったり、電気的なノイズも乗りやすいので、通話品質はかなり厳しいものが多いのが実情です。
Firefoxでは、デバイスの許可の際にデバイスを選択することができるのですが、Chromeなどではデバイスの選択画面がないため、アプリ側で実装してあげることで、これらのトラブルを避けることができるかもしれません。
例えば `JavaScript SDK` の場合には、[SkyWay公式Webサイト > APIリファレンス > 使用するデバイスの選択](https://webrtc.ecl.ntt.com/api-reference/javascript.html#%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E3%83%87%E3%83%90%E3%82%A4%E3%82%B9%E3%81%AE%E9%81%B8%E6%8A%9E) でアプリの実装例を紹介しているので参考にしてください。

### ケース5: 映像・音声が出力されない
映像・音声は取得できていて、WebRTCのメディア通信も発生しているにもかかわらず、受信側で出力されないケースです。
このケースは様々な原因が考えられますが、原因の一つとしてメディアのデコードに問題があるケースが考えられます。

#### ブラウザ・端末がCodecに対応していない
通常、Codecを明示的に指定しない場合には、IceCandidateの際に双方が利用可能なCodecが選択されますが、`MediaConnection` の `call options` や `MeshRoom` の `room options` で `videoCodec`/`audioCodec` を指定した場合に、ブラウザや端末がCodecに対応していないと、映像・音声が出力されないということがあります。
※ SkyWayの `SFU Room` をご利用の場合は、このオプションは指定できず、映像: `VP8`, 音声: `opus` 固定です。

#### ブラウザ・端末のバグなどにより、正常にデコードできない。
最近、libwebrtc公式プロジェクトにて、ハードウェアエンコーダを利用し16の倍数でない解像度を指定した際に、Androidデバイスの映像が正しく表示されない事象が報告されて、話題となっていました。

[bugs.chromium.org: Issue 11337: Android HW Encoder produces corrupted images (also in Google Duo)](https://bugs.chromium.org/p/webrtc/issues/detail?id=11337)

SkyWayのFAQでも本件に関する解説と、ワークアラウンドを紹介しています。
[SkyWay FAQ: Android端末で映像が崩れて表示されます](https://support.skyway.io/hc/ja/articles/360058858573)

### ケース6: 端末・ブラウザの動作が重い
端末の負荷は、OSのタスクマネージャーなどで確認できますが、負荷が大きくなる要因としては、

- 同時接続されているmediaStreamの数が多く、エンコード負荷が大きくなっている
- ソフトウェアエンコーダが利用されていて、エンコード負荷が大きくなっている

などが考えられます。

前者の場合には、SFUなど(SkyWayではSFU Room)を利用することで改善されます。
SFUを利用した場合であっても、同時に接続できる数には限界があるのでご注意ください。
SkyWayのSFU Roomを利用した場合の実績は下記の通りです。
参考) [SkyWay FAQ: 1ルームあたりに同時に参加できる最大人数は何人ですか？](https://support.skyway.io/hc/ja/articles/360048479614)

後者の場合には、広くハードウェアエンコーダに対応したコーデックを利用するようにすることで改善される場合があります。

## その他のトラブルシューティングで役立つ情報
### ChromeでのWebRTC統計情報確認ツール`chrome://webrtc-internal/` の見方
#### アクセス方法
アドレスバーで`chrome://webrtc-internals` へアクセスしてください。
このような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609551/9a4e94b9-cbca-409a-a67f-e4eadb1217f3.png)


なお、Firefox でも、`about:webrtc` にアクセスすることで、同じような情報を確認することができます。

#### タブについて
タブがいくつかありますが各タブについては下記の通りです。

- GetUserMedia Requests
    - 同じChromeセッションで実行されている、`getUserMedia()` の情報
- URLが記載されているもの
    - WebRTC Peer Connectionの統計情報

#### ログのダウンロード
`▼Create Dump` をクリックすると、下記のような表示になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609551/f5175a71-cbf8-1f3e-2d79-f59e53d8569b.png)


`Download the PeerConnection updates and stats data` をクリックすると、表示中のタブの統計情報をダウンロードすることができます。
SkyWayのテクニカルサポート(Enterprise Editionご利用の方向けのサービス)でトラブルシューティングを依頼したい場合には、こちらの情報を送付してくださると、原因分析が早くなるかもしれません。

また、`Enable diagnostic audio recordings` や `Enable diagnostic packet and event recording` にチェックを入れると、音声の録音やパケットやEventの取得ができます。

####  WebRTC Peer Connectionの統計情報
WebRTC Peer Connectionの統計情報は、https://w3c.github.io/webrtc-stats/ で規定されている、WebRTC Stats のAPIから取得しているようです。

「[Chrome M75から chrome://webrtc-internals が新しくなります@yusuke84](https://qiita.com/yusuke84/items/428affe75affa16f104d)」で詳しく解説されていますので、そちらも参考にしてください。
ここでは、トラブルシューティング中に私がよくこんがらがる各項目の関係性と、対象の項目の特定の仕方についてまとめます。

##### 各項目の関係性
- IceCandidate
    - 通信経路の候補。
    - 項目の `_` 以降は `CandidateId`
- IceCandidatePair
    - LocalとRemoteの `IceCandidate` の組み合わせ
    - 項目の `_` 以降は、`CandidateId(local)_CandidateId(remote)`
- Transport
    - `IceCandidatePair` から選択された通信経路
        - 選択されたPairは項目内の `selectedCandidatePairId` で規定
    - この経路に、MediaConnectionが張られる
- MediaStream
    - `Transport` に張られたメディアストリーム
    - 複数のMediaSteamTrackからなる
        - `trackIds` でMediaStreamTrackを確認可能
- Outbound(Inbound)RTPAudio(Video)Stream
    - `Transport` に張られた実際のRTPの通信ストリーム
        - `TransportId` でどの通信経路上に張られたストリームかを確認可能
    - MediaStreamTrackと1対1で紐づく
        - `trackId` で `MediaStreamTrack` との紐付けが可能
- MediaStreamTrack
    - `MediaStream` に含まれる、オーディオやビデオのトラック

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609551/cb65a81c-ffb8-2136-9ebd-29cdeee8522a.png)
`IceCandidate`, `IceCandidatePair`, `Transport`, `MediaStream`, `Outbound(Inbound)RTPAudio(Video)Stream`, `MediaStreamTrack`の関係性イメージ

### getStats()
WebRTCの統計情報は、ブラウザAPIの`RTCPeerConnection.getStats()` により、実装されているアプリ内で取得して利用することができます。
SkyWayのSDKでも、MediaConnection および DataConnection には`RTCPeerConnection` を取得するAPIが実装されているため、`getStats()` にアクセスすることができます。

**注意点**
~~現在のところ、RTCPeerConnectionのAPIはRoom APIには実装されていません。~~
**[2020/12/07 追記] JavaScript SDK v4.1.0 にてRTCPeerConnectionを取得するAPIが実装されました。v4.1.0を利用すれば、Room形式であっても、`getStats()` にてWebRTCの統計情報が取得できる様になります。**

例えば、JavaScriptSDKにて `getStats()` を取得するコードは以下の通りです。

```javascript:Sample
const peer = new Peer('some-peer-name', {
  key:   "<YOUR-API-KEY>"
});

const mediaConnection = peer.call("peerId", localStream);
let pcstats;

if (mediaConnection.open) {
  const pc = mediaConnection.getPeerConnection();
  pcstats = pc.getstats();
}
```
なお、`getStats()` はブラウザによって実装が異なるり、得られる統計情報が異なるという問題があります。
この壁にぶつかった場合には、弊社エンジニアがRTCStatsをラッピングして、一様に扱えるようにする、`rtcstats-wrapper` を公開していますので是非ご活用ください。
※ ただし、`rtcstats-wrapper` は2019年9月の仕様を基に作成されています。今後、ブラウザ実装の変更により使えなくなる可能性もございますのでご了承ください。

[WebRTCの通信状況をプログラマブルに判別するライブラリを作ってみた@monmee](https://qiita.com/monmee/items/77310482b0a6a034a887)

### SkyWayからの情報発信
SkyWayではユーザの皆様の問題解決に役立つ情報の発信を強化しています。
ご不明点があれば、まずは、FAQやエンジニア向け資料のページをご確認ください。

- [FAQ](https://support.skyway.io/hc/ja/categories/204565748-FAQ)
- [公式サイトのエンジニア向け資料](https://webrtc.ecl.ntt.com/skyway/function.html)

今後もどんどん情報発信を強化していく予定ですのでお楽しみに。

# まとめ
SkyWayご利用の皆様向けに、SkyWayのトラブルシューティング方法について、6つのトラブルケースにそって説明しました。
WebRTCを活用した、イノベーティブなサービス開発、サービス運用の一助になれば幸いです。
SkyWayは今後も皆様のイノベーティブな活動を支援いたします。