End to End Encryption (E2EE) なメッセージングツールである Signal が自分たちで利用している WebRTC SFU を OSS として公開しました。

既存の OSS を採用せず、Rust で 1 から Signal 専用の SFU として開発されています。モダンな WebRTC SFU という感じなので、ブログ記事とソースコードをざっくり読んだ感想を書いていこうと思います。

要約

  • 負荷を下げるならパケット量を減らす
  • Signal 専用の SFU で他に転用はできなさそう

課題は 1 ルームで 40 人

SFU が一番最初にぶつかる課題の一つです。MCU と違い合成を行わないため、1 ルームに参加する人が増えれば増えるほどクライアント側の受信負荷がとても高くなります。

クライアント負荷が高くなるのはパケットを受け取る量が増えるからです。つまり負荷を下げるには人が増えてもパケットを受け取る量がほとんど変わらないようにするというのが重要になります。

そのためやるべき事は一つで、アクティブでは無い=つまり今話をしていないユーザのパケットを送らないことです。

不要なパケットをできるだけSFU からクライアントへ送らなければいいわけです。そのためには「音声判断」が必要になります。WebRTC には音量レベルというのが入っており、現時点での音量情報がパケットに乗ってきます。それをみてサーバ側はリアルタイムで「パケットを送るか送らないか」を判断する仕組みを入れます。

コードはこちら。ライセンスは AGPLv3 です。

この仕組みを利用すればアクティブでは無い人のパケットを送らなくなるため、アクティブな人数分のパケットだけ受信する=つまりアクティブ数が多くならなければどれだけ多くのルーム参加者でもクライアントの負荷は増えなくなります。

輻輳制御とサイマルキャスト

SFU が二番目にぶつかる問題は「受信者の回線やマシンが弱くアクティブなユーザだとしても高解像度を受信できない」という問題です。

SFU は音声や映像を転送する仕組みです。そのため、配信者がもし高画質の映像しか出していない場合、受信者はかならず「高画質」の映像を受信する必要があります。

この問題を回避するためにサイマルキャストという、配信者側が複数の画質を配信し、SFU 側で帯域などを見てクライアントに最適な画質を配信するという仕組みが提供できるようになります。

配信者はサイマルキャストを使い低画質と高画質の映像を配信する。SFU は受信者の帯域を推定し、高画質を受信する余裕があれば、アクティブな場合は高画質の映像を送る。余裕がなければアクティブになったとしても低画質のままにする、といったような仕組みです。

Signal の SFU 開発でもこの切り替えの仕組みがとても大変だったと書いてあります。輻輳制御は本当に難しい技術で正解があるわけではありません。

Signal のメッセージングの仕組みに乗っかる

Signal はもともとE2EE メッセージングの仕組みを持っているため、鍵交換や鍵配布があるため、それを有効活用している仕組みのようです。

コードを見ると DTLS を使わないで鍵のやりとりをする仕組みなども入っていました。実際 Signal はモバイルとデスクトップアプリしか提供しておらずブラウザ上ではサービスを提供していません。

そのため、WebRTC に準拠している必要は一切無いのです。libwebrtc ベースの仕組みを利用した SFU というのが今回の SFU です。Signal に特化させたことでとても小さな実装になっている印象です。

アクティブユーザが数千万規模なサービスで E2EE に特化しており OSS としてクライアントやサーバを公開する事が重要な Signal にとっては、今回のように Rust で 1 から開発することは最適解のように思えます。

音声や映像の E2EE 鍵更新

少しマニアックな話になりますが、E2EE を音声や映像で使う場合は参加/離脱時に音声や映像を暗号化する鍵の更新が入ります。

この更新された新しい鍵の利用はなんと参加または離脱後から 3 秒ととても短いです。ちなみに Google Duo は 5 秒でした。自社製品も Google Duo にあわせて 5 秒にしてあります。

--

--