Amazon Kinesis Video Streams WebRTC で無理やり複数人のビデオチャットを作る

# はじめに

[以前、Amazon Kinesis Video Streams WebRTC（以後 KVS WebRTC）を調べてみましたが、](https://qiita.com/massie_g/items/b6d3513d06a28ba89677)1対1か、1対nを前提にしていて、複数人の双方向ビデオチャットのような仕組みは対象にしていないことが分かりました。
できないと言われるとやりたくなるのが人のサガなので、あえて多人数のビデオチャットを作ってみました。

# KVS WebRTCの通信形態

以前の記事でも書いたように、KVS WebRTCではマスター(Master)1つと、複数のビューワー(Viewer)が通信することができます。図にすると次のような形です。

<img width="609" alt="kvs_master_viewer3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19403/574d5dd5-1ce5-8a5a-8340-2b0777a97895.png">

このままではViewer同士は通信できません。そこでMasterが仲立ちをして、すべてのViewerの映像を合成して返す形にすれば、Viewer同士で全員の映像を見ることができます。

<img width="633" alt="kvs_master_mix.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19403/58665bf0-528c-9b27-13b3-1d0ffc44872a.jpeg">

# MCUをブラウザで

先ほどの合成する形態はMCU(Multi-point Control Unit)と呼ばれる方式で、ビデオ会議システムでは以前から利用されてきた仕組みです。サーバーとしてMCUを用意するのが一般的ですが、CPU負荷が重くサーバー費用が高くつくことが難点です。

このMCUをWebブラウザで手軽に作れるライブラリを作ったことを思い出して、今回KVS WebRTCと組み合わせてみました。

- Browser MCU Core ... [GitHub](https://github.com/mganeko/browser_mcu_core)
    - ブラウザの映像/音声処理を活用した、MCUライブラリです
    - WebRTC でやり取りする MediaStream を扱うために作成されています
    - シグナリングや PeerConnection の処理は、Browser MCU Core には含まれていません
- Browser MCU with KVS WebRTC example ... [GitHub](https://github.com/mganeko/browser_mcu_kvs)
    - Browser MCU Coreを使ったサンプル
    - シグナリング処理はKVS WebRTCの[Web用 SDK](https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-js)を利用
    - RTCPeerConnectionはSDKのサンプルを参考に自分で実装

## サンプルを動かす

[GitHub Pages](https://mganeko.github.io/browser_mcu_kvs/)で公開しているので、すぐに試すことができます。サンプルはChrome 80で動作確認しています。
事前準備として、KVS WebRTCのシグナリングチャネルを作成しておく必要があります。

- ブラウザでラウンチャーページ を開く
    - https://mganeko.github.io/browser_mcu_kvs/

![kvs_mcu_launcher.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19403/c4a9dcc6-b185-6cfb-073a-452c6827a451.png)

- パラメータを指定する
    - Region ... シグナリングチャネルを作成したリージョン
    - ARN ... 作成したシグナリングチャネルのARN
    - AccessKeyId ... 割り当てたIAMのアクセスキーID
    - AccessSecretKey ... 割り当てたIAMのシークレットアクセスキー
- Masterを起動
    - [open MCU master]ボタンをクリック
    - 開いたウィンドウ/タブが開き、ボタンの横に" setup KVS done."と表示されれば初期化完了
    - 開いたウィンドウ/タブで、[Connect]ボタンをクリック
    - "-- signaling client open --" と表示されれば、KVS シグナリングチャネルに接続成功
    - ※Masterのウィンドウ/タブは、完全に隠れてしまわないよう、別ウィンドウにしてどこかに表示しておく必要あり
        - 完全に隠れてしまうと、画面更新（動画の描画）がポーズしてしまうため
- 最初のViewerを起動
    - [open MCU member(viewer)]ボタンをクリック
        - ※その時のURLを別マシンで開いてもOK
    - 開いたウィンドウ/タブで、[start MCU member(offer)]ボタンをクリック
    - カメラ/マイクへのアクセス許可を求められたら、許可
    - 自分の映像と、MCU masterが合成した映像（最初は1人）が映ったら接続成功
- 2つ目以降のViewerを起動
    - 1つ目と同様、合成した映像の人数が増えていく
- Viewerの切断
    - [stop MCU member]ボタンをクリック
- Masterの切断
    - [Disconnet]ボタンをクリック

これで無事、多人数でのビデオ会議ができます。
<img width="1432" alt="kvs_mcu_mix3.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19403/e687b706-1534-64fd-c8fe-fb7b7aea7774.png">



## サンプルを編集する

サンプルをクローンして編集すれば、ARNやシークレットキーを利用者が入力しないで使えるようになります。

- レポジトリをクローン
    - git clone --recursive https://github.com/mganeko/kvs_webrtc_example.git
- kvs_keys.js を編集
    - AWS_CHANNEL_ARN ... シグナリングチャネルのARNを指定
    - AWS_ACCESS_KEY_ID ... ユーザーのアクセスキーIDを指定
    - AWS_SECRET_ACCESS_KEY ... シークレットアクセスキーを指定
- Webサーバーにhtml, jsファイルを配置
    - ※ローカルホストでない場合は、https通信が必要です


# 内部の仕組み

## Master(MCU)での映像合成

こちらのスライド([WebRTC Build MCU on browser](https://www.slideshare.net/mganeko/webrtc-build-mcu-on-browser/)にあるように、Canvasを使って映像を合成しています。

スライドの図より：
<img width="1115" alt="browser_mcu_video.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19403/37228908-0d59-5caf-d0be-bce23ec9ed00.png">

- Videoタグから、Canvasタグに描画
    - これをrequestAnimationFrame()を使って連続的に実行
- Canvasタグでは、captureStream()を使ってMediaStream（映像）を取得

## Master(MCU)での音声合成

先ほどのスライド([WebRTC Build MCU on browser](https://www.slideshare.net/mganeko/webrtc-build-mcu-on-browser/)にあるように、WebAudioを使って映像を合成しています。

実は映像よりも音声のほうが厄介です。音声は自分の声を除外した他の全員の声を合成して返します。したがって3人の参加者がいたら3通り（n人の場合はn通り）の合成を行う必要があります。

スライドの図より：
<img width="1112" alt="browser_mcu_audio2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19403/44e8e4a3-6ad5-596b-75cf-3f6944afbd37.png">

- MediaStreamから、MediaStreamAudioSourceNodeに変換
- 複数のAudioSourceNodeをMediaStreamDestinationNodeに接続
    - ここで音声合成が行われる
- MediaStreamDestinationNodeから、MediaStreamを取得

ChromeではWebAudioの処理の前にユーザージェスチャーが必要になったので、過去のMCU Browserのサンプルとは初期化のタイミングを変えて、ユーザーがボタンをクリックしてから音声合成のための処理を行っています。

## Masterでの複数PeerConnection管理

RTCPeerConnectionを、ViewerのClinetIDをキーにしたハッシュ（連想配列）に格納して管理しています。雑なコードですが、こちらのファイルにまとめています。

- [js/mcu_peer.js](https://github.com/mganeko/browser_mcu_kvs/blob/master
/js/mcu_peer.js)

またPeerConnectionで発生したイベントを処理するためのコールバック関数をあらかじめセットしておくことで、KVSのシグナリングチャネル経由でSDPやICE candidateを送信するようにしています。

## Viewer側

Viewer側はMasterとだけ双方向通信しているので、1対1の通信の場合と同様です。

- 参考：Viewerの処理([Qiita](https://qiita.com/massie_g/items/b6d3513d06a28ba89677#viewer%E3%81%AE%E5%87%A6%E7%90%86))

# おわりに

一番身近な映像/音声処理装置としてブラウザを利用することで、KVS WebRTCでも多人数でのビデオ会議ができるようになりました。実用性は？？ですが、工夫次第で制約は乗り越えらえる、ということですね。

## あらためて利用上の注意

映像合成を行っているMasterのウィンドウ/タブが完全に隠れてしまうと、requestAnimationFrame()のイベントが発火せず合成動画が止まってしまいます。絵が出ない/動かない、というケースでは念のためご確認ください。