WebRTCで開発者が利用できるオーディオ関連の設定

この記事は NTTコミュニケーションズ Advent Calendar 2018 の11日目です。

普段は[WebRTCプラットフォーム SkyWay](https://webrtc.ecl.ntt.com/)の中の人をやっています。ですので、本日はWebRTCに関連したネタを投稿します。

WebRTCでは、コーデックの相性問題や端末・通信への影響が大きいため、映像（Video）がフォーカスされやすくネットにも情報が豊富です。反面、オーディオについてはそんなに情報がない気がします（特に日本語の情報は）。普段必要とされいないというのもあるかもしれませんが、WebRTCを使ってアプリ開発を行う上で、知っておいたほうが良いこともあるため、今回はまとめてみることにします。

*お断り: この記事は以前私が勉強会で発表した資料をベースに、加筆しQiita記事として書き起こしたものです*

## WebRTCで用いられるオーディオコーデック

WebRTCで一般的に用いられているコーデックはOpusというコーデックです。
他に歴史があるG.711等も利用可能ですが、敢えて別のコーデックを指定するという機会は少ないと思われるので、Opusが主流だと思います。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/b41a3cce-fccc-1350-8419-8023c65792a2.png)
引用元:Wikipediaより

## Opusの特徴

Opusはナローバンドからフルバンドまで幅広くカバーします。先に取り上げた、G.711はナローバンドを対象にしたコーデックとなっています。
バンドの関係については以下の図がわかりやすので貼っております。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/7171b7b3-a3f6-9769-f8e5-eee9ddc64c04.png)
引用元: https://bloggeek.me/single-voice-codec-webrtc/


Opusは複数のコーデックを内部で使い分ける仕様になっており、ナローバンド・ワイドバンドについてはSILK、スーパーワイドバンド・フルバンドについてはCELTという2種類のコーデックをハイブリッドで利用します。

その他のOpusの特徴としては…

- 前方誤り訂正（FEC）＆フロー制御が組み込まれている
- FECはパケットロスをカバーし、帯域節約と処理負荷軽減のためSILKのみで動作
- 独自のフロー制御が実装されている
    - WebRTCは独自にフロー制御のメカニズムを持っているため、これが実際にどう動作しているのかは不明（ちゃんと調べていないです
    - WebRTCのフロー制御についてこの記事がわかりやすいです
        - https://qiita.com/komasshu/items/1cb5d4469595a635c689

## Opusが利用する通信帯域

Opus自体は可変ビットレート（VBR）で動作し、仕様書上は6Kbps〜510Kbpsで動作することになっていますが、WebRTCについては実装に依存しています。Chromeの場合は[ソースコード](https://chromium.googlesource.com/external/webrtc/+/HEAD/api/audio_codecs/opus/audio_encoder_opus_config.cc#38)から、32Kbpsで固定されているように見えます。

実際にどのくらいの帯域を利用するかは`chrome://webrtc-internals`を見ると確認ができますが、だいたい35Kbps前後に収まっているようです。

<img width="323" alt="webrtc-internals.png" src="https://qiita-image-store.s3.amazonaws.com/0/6651/7acea9ba-6d7e-4631-a127-968f92504262.png">

chrome://webrtc-internalsより


## WebRTCで利用できるオーディオ関連の設定

ここからが本題です。

WebアプリケーションとしてWebRTCを利用しようとすると、getUserMediaというAPIを利用することになると思いますが、getUserMediaでオーディオに関して制約条件を設定できる項目がいくつかあります。

```javascript
// getUserMediaでカメラ、マイクにアクセス 
function startVideo() { 
  navigator.mediaDevices.getUserMedia({video: true, audio: {
    sampleRate: {ideal: 48000},
    channelCount: {ideal: 2, min: 1}
    }}).then(function (stream) { // success 
      playVideo(localVideo,stream); 
      localStream = stream; })
    .catch(function (error) { // error              
      console.error('mediaDevice.getUserMedia() error:', error);
      return; 
    }); 
} 
```

実際に指定できる項目は以下のとおりです。WebRTCあるあるですが、ブラウザごとに設定できる項目は異なります。

|constraints|説明|Chrome M71|Firefox 63|Safari 12|
|:--|:--|:--|:--|:--|
|autoGainControl|入力音量の自動調整機能の有効・無効|サポート<BR>エコーキャンセラーが無効の場合に動作|サポート|未サポート|
|channelCount|チャンネル数（2:ステレオ、1:モノラル）|固定値:1|サポート|未サポート|
|echoCancellation|エコーキャンセラーの有効・無効|サポート|サポート|サポート|
|echoCancellationType|エコーキャンセラーの種類（browser / system)を選択|サポート|未サポート|未サポート|
|noiseSuppression|ノイズ抑制の有効・無効|サポート|サポート|未サポート|
|sampleRate|サンプリングレート|固定値:48,000Hz|不明|固定値:44,100Hz|
|sampleSize|サンプリングサイズ|固定値:16bit|不明|不明|
|volume|ボリューム:0.0〜1.0|固定値:1|不明|サポート|

- 参考/引用元: https://addpipe.com/blog/audio-constraints-getusermedia/
- constraintsで指定できるかどうか（エラーにならないかどうか）を調べたものです
- 値を設定する場合は min, max, exact, ideal が利用可能です

## デモ

これらの項目が機能している様子を確認してみます。とはいえ、音声なので目には見えません。比較的わかりやすいエコーキャンセラーで試してみます。

Chrome M71で`echoCancellation`のtrue/falseでどれだけ違いがあるか確認してみました。テストは同一PC内でタブを2つ開きビデオチャットをつなげてハウリングを発生させるものです。

- エコーキャンセラー無効
    - https://drive.google.com/open?id=1yyKkGzX78-QkzIA08GbLQYdun_Vrqqjg

```javascript
echoCancellation : false,
noiseSuppression : false
```

- エコーキャンセラー(ブラウザ提供機能)有効
    - https://drive.google.com/open?id=1GZyYP30BUU4eWY_ouVMEiqi7BKoL5bOJ

```javascript
echoCancellation : true,
noiseSuppression : false
```

**注意:両m4aファイル共、10秒ほど不快な音が発生するのでご注意ください**

尚、`echoCancellation:false`、`audioGainControl:true`にした場合、ハウリングが始まって入力音量の増幅（ゲインの増幅）を検知したら、自動で入力音量を調整し結果的にハウリングが抑止されますが、恐らく、入力音量自体が小さくなるのでまともに会話はできなくなると思われます。

## エコー・ハウリング対策

WebRTCでオーディを利用したアプリを開発・提供・利用する上で考慮が必要なポイントで、真っ先に思いつくのが、デモでも触れたエコー・ハウリング対策です。

### エコー・ハウリングの仕組み

- エコーとは自分が喋った音声が相手のスピーカー・マイク経由で返ってくる事象で、とても喋りづらくなる

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/1071aa1d-87ad-e13c-4067-bf0dfc4834e3.png)

- ハウリングはエコーが更に循環することで発生し、キーンという音が発生し会話どころではなくなる

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/2ac2cc3a-6d1e-9bd1-9a83-776aba3e89af.png)

### WebRTCにおけるエコー対策

WebRTCにはソフトウェア制御のエコーキャンセラーが組み込まれています。WebRTCのエコーキャンセラーはスピーカーから再生されている音を分析し、マイクでキャプチャした音から、前者の成分を取り除くことでハウリングを解消するアプローチを取ります。

AEC(アコースティック・エコー・キャンセレーション)と呼ばれるそれは、Chrome M59でVersion3にメジャーアップデートし以下のような機能強化がなされました。

- エコー除去機能の強化
- マイクゲインの変化を検知し活用する
- SSE2/SIMD命令への最適化によるパフォーマンス改善

詳しくは: https://bugs.chromium.org/p/webrtc/issues/detail?id=6018

![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/db9b3256-8e7c-69d6-8367-6fe0c69b1774.png)

実際の効果の程は、上記のデモのとおりです。

### AECの選択

今までAECはWebRTCが提供するもの一択でしたが、Chrome M68+で実験的にAECを選択できる機能の実装が始まっています。getUserMediaのConstraintsとしてご紹介した`echoCancellationType`がそれです。`browser`と`system`を選択可能です。

- browser
    - WebRTCエンジン(libwebrtc)に実装されているAECを利用する
- system
    - OSが提供するAECを利用
        - WindowsOSでは`Voice Capture DSP`というコンポーネントを利用してAECを実現しているが、まだ性能は不十分
        - macOS Xでは、バージョン10.12（Sierra）以降、OSとしてエコーキャンセラーが組み込まれている

詳細は[Google Developers - More native echo cancellation!](https://developers.google.com/web/updates/2018/06/more-native-echo-cancellation)

### echoCancellationTypeの動作確認

以前確認したChrome M69では起動にオプション引数（以下の通り）が必要でしたが、M71では普通に起動するだけで有効になっているようです。

```
open -a "Google Chrome" -n --args --enable-blink-features=ExperimentalHardwareEchoCancellation
```

AECのタイプは、getUserMediaで取得したMediaStreamのプロパティから確認できます。

```
stream.getAudioTracks()[0].getSettings()
```

```JavaScript
{
  autoGainControl: true
  channelCount: 1
  deviceId: "default"
  echoCancellation: true
  echoCancellationType: "system" // <-ここ
  groupId: "f2e1f0301ef6faafc0b9d85a6d9df1cad6fa9bb0db1be694c50404b7745d50de"
  latency: 0.01
  noiseSuppression: false
  sampleRate: 48000
  sampleSize: 16
  volume: 0.5411764705882353
}
```

私はmaxOS X(Sierra)を利用しているので、OSのエコーキャンセラーを実際に試してみました。

- エコーキャンセラー（maxOS X提供機能）:有効
    - https://drive.google.com/open?id=1jICZ75aQwwylwWVSaPHdQxldQ_HZrOWi

```javascript
 echoCancellation : true,
 echoCancellationType : 'system',
 noiseSuppression : false
```

先に試した2パターンのデモと同じ環境ですが、控えめに言って最強ですね。ハウリングさせること自体が難しそうです。現状利用できる環境が限られますが、開発者にとって知っていて損はないと思われます。

### とはいえ、最後は物理…

AECの性能があがってきているとはいえ、最後は物理です。開発者はアプリの作りに配慮し、利用者は利用する機材に配慮する必要があります。特に会議系のアプリをWebRTCで実現する場合は利用者の環境一つでユーザー体験は劇的に向上します。

一人で参加する場合や、同じ場所で複数のPCで参加する場合はヘッドセットを利用するようにしましょう。安いもので十分です。USB接続のものはノイズが入りにくくておすすめです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/3e63feb0-2018-9a3f-45f8-6d279aa00231.png)

また、多人数で参加する場合はエコーキャンセラー内臓のカンファレンスマイク・スピーカーも有効です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/6651/03f92504-4c87-b61b-11c5-3781d0b3fe69.png)

私は仕事柄、この2つは常に持ち運んでいます。どこでも最高の環境でWeb会議が可能ですw

## 最後に

この記事では以下の事柄についてご紹介しました。

- WebRTCはオーディオコーデックとしてOpusを利用する
- getUserMediaのConstraintsを利用して、オーディオ周りの設定が出来るようになっている
- WebRTCエンジン(libwebrtc)にはエコー・ハウリング対策としてAECが搭載されている

みなさん、良いWebRTCライフを！
最後に宣伝させてください…SkyWayも今年はアドベント・カレンダーをやっています。

[SkyWay Advent Calendar 2018](https://qiita.com/advent-calendar/2018/skyway)

参加してもらえると、私が死ぬほど喜びます！！！！

## 参考文献

- https://webrtc.org/blog/2011/07/11/webrtc-improvement-optimized-aec-acoustic-echo-cancellation.html
- https://developers.google.com/web/updates/2018/06/more-native-echo-cancellation
- https://bugs.chromium.org/p/webrtc/issues/detail?id=6018
- https://addpipe.com/blog/audio-constraints-getusermedia/
- https://bloggeek.me/single-voice-codec-webrtc/
- http://hudson.doorblog.jp/archives/41822410.html
- http://www.kyastem.co.jp/technical/echo-cancellation/