Create a gist now

Instantly share code, notes, and snippets.

WebRTC SFU コトハジメ

WebRTC SFU コトハジメ

日時:2016-02-17
作:時雨堂
発表:45 分
バージョン:1.2.0
url:https://shiguredo.jp

これは WebRTC Conference Japan 2016-02-16 ~ 17 の資料です。

概要

この発表は WebRTC SFU を 1 から開発した時の知見を、基本的な WebRTC の知識がある前提でお話しさせていただきます。

自己紹介

時雨堂という小さな小さな会社を経営しています。 WebRTC SFU を 1 から開発し製品として提供しています。

WebRTC SFU Sora

また、その WebRTC SFU を気軽にウェブで使えるようにしたサービスも提供しています。

WebRTC SFU as a Service Anzu

IoT 向けのラズベリーパイ2 で動作する WebRTC Gateway も公開しています。

WebRTC Gateway Momo

どんな感じで作ったのかは別件で話しているので、興味あればどうぞ。

WebRTC SFU をフルスクラッチで作ってみた

ストリーム動画配信とリアルタイム動画配信は別世界というのを知ってまとめたのを書いたりしています。

リアルタイム動画配信コトハジメ

注意

データチャネル

今回はデータチャネルの話を一切しません。 理由としては単に自分が SFU を実装した上でデータチャネルは一切実装していないため、詳細がわからないという理由です。

MCU

今回は MCU の話は一切しません。 MCU は SFU と同じように見えて全く異なるからです。

WebRTC SFU

WebRTC SFU (Selective Forwarding Unit) は全ての通信をサーバ経由で配信する一つの方法です。今までですと MCU が主力でしたが、 SFU はサーバサイドで一切映像を加工しません。転送するだけです。

簡単な SFU の図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/webrtc_sfu.png

マルチストリームの図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/multistream.png

詳細な SFU の図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/webrtc_sfu_pubsub.png

転送するだけというのがポイントで、つまりサーバの負荷は転送だけになります。転送だけというのは何かというとパケットを復号化して再度暗号化するコストが一番高くなります。

なぜ WebRTC SFU を作ったのか

  • サーバ経由型のリアルタイム配信システムを作ってみたかった
  • IoT では中継サーバを介してのリアルタイム配信が必要になると考えた
  • サーバでの録画に価値がでそうだった
  • 一定以上のユーザになると P2P では苦しそうだった

シグナリング

WebRTC SFU でもシグナリングは必要です。ただ通常のシグナリングとは異なります。

通常シグナリングは自分が Offer を生成し、相手に届けて、相手から Answer をもらいます。 SFU の場合は SFU から Offer が送られてきます。

SFU から Offer が送られてきて、 Answer をクライアントが送る図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/connect_offer_answer.png

DTLS

WebRTC は DTLS のハンドシェイクを使用し、 SRTP に使用するマスターシークレットを交換します。DTLS の Cipher Suite で定義された暗号で暗号化はされません。

そのため、 WebRTC の通信は DTLS を使いつつも、実際の暗号化は全て SRTP を使用します。

WebRTC SFU では、 DTLS-SRTP という特殊な暗号方式をしゃべれるようにする必要があります。よく誤解される TURN サーバでは暗号を解いている、というのがあります。実際は解いていませんが、サーバ経由にすると暗号をほどいているのだろうというイメージがあるのだと思います。 SFU はこれを実際に行っています。

SFU で暗号化の中身を見れているという図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/webrtc_sfu_dtls.png

TURN は暗号を解いてない図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/turn.png

SRTP

WebRTC SFU のメディアチャネルで使われている DTLS-SRTP の SRTP は映像や音声のプロトコルである RTP に Secure を付けたプロトコルです。 Secure RTP の略。そのままです。

RTP/RTCP に対して、暗号化を行います。その暗号化を行うための鍵の生成については DTLS で交換したマスターシークレットを使います。

この部分は特に SRTP の RFC から逸脱しておらず、ほぼそのまま適用されており、 WebRTC だから何か特殊なことはしていません。 SIP で使われている SRTP と同様です。ただし暗号方式は AES-CTR が採用されています。

最近になって AES-GCM の RFC がリリースされました。 AES-CTR から AES-GCM に移行するのも時間の問題でしょう。もともとこの暗号方式は DTLS の ClientHello 拡張の use_srtp にて定義可能です。クライアントから提示するため、クライアントが AES-GCM に対応さえすれば、サーバは追従するだけです。

全てのブラウザが使用する暗号鍵は異なる

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/webrtc_sfu_pubsub.png

この暗号部分ですが、 SFU の一番のリソースボトルネックになります。画質がよくなればなるほどパケットの量は増え、復号化と暗号化の量が増えます。つまり SFU は暗号化さえ耐えきれば良いのです。

SDP

WebRTC SFU では SDP を自前で生成する必要があります。現時点で SDP は Multiplexing をどうしていくかが課題になります。 Simulcast が来たタイミングでさらに意識する必要があります。

Multistream

悲しいことですが Firefox と Chrome では Multistream 時に生成される SDP が異なります。Unified Plan や Plan B と呼ばれるものです。これは Chrome の実装が最新に追いついていないため発生している問題です。

Chrome は今年中には Unfied Plan を頑張るという話はしています。ただ相当な量のリファクタリングが必要になるようで、大変そうです。気長に待つのが良いでしょう。

Firefox は最新にしっかり追従しており、きっちりと Unified Plan をしっかりと実装しています。

Unified Plan は m= を複数定義するという仕組みです。音声や映像があれば、その分だけ m= を増やしていきます。同一の組み合わせかどうかは a=msid を使って判断します。

Plan B は m= は audio と video の二つに分けてしまい、そのなかで a=ssrc-group という仕組みを使って分割する仕組みです。Plan B は今では Chrome しか実装していません。もともと Plan B から Unified Plan への移行をするはずが、 Chrome は対応が遅れたのです。

私は Multistream は WebRTC のキラー機能だと考えています。 1 つのコネクションに動的に映像や音声を追加したり削除したりするのを気軽にできるからです。

特に Multistream は映像や音声を合成できない SFU にとっては、キモとなる技術です。ここをどのくらい自由度をつけつつも、気軽に使えるようにするかが SFU の差別化となるでしょう。

Multiplexing

WebRTC では RTP/RTCP のポートを節約するための Multiplexing に対応しています。

簡単に言うと RTP と RTCP に使用するポートを同じにするという仕組みです。もともとは RTP と RTCP のポートは別でした。 RTP のポートに +1 したものを RTCP として使うという感じです。

ただ、その場合はポート番号を使いすぎてしまうことから、一つに節約するという戦略がとられました。RTP/RTCP を一つにする、という仕組みです。さらに、音声と映像の RTP/RTCP も同じにしてしまうという仕組みも追加されました。

つまり本来は 音声で 2 つ、動画で 2 つ使っていたポートを 1 つにまとめるという仕組みです。これのおかげで複数の DTLS セッションを張る必要もなくなり、一つのセッションを使い回すことができるようになりました。判断は SSRC やペイロードタイプで十分判断できます。

現時点で Multiplexing は普通で、むしろ対応していない方が特殊な状況です。

STUN

WebRTC SFU で STUN は UDP ホールパンチング用に使用します。 WebRTC SFU 自体はグローバルにいることから、STUN がどうこうというのは基本的にはクライアントが判断するための材料になります。

SFU は送られてきた STUN パケットを送り返したり、クライアントからの Candidate に対して STUN パケットを送ったりします。判断自体は全てクライアントが行うのでサーバが判断する必要はありません。

RTCP

WebRTC SFU を作る上でコアとなるのが RTCP の生成です。 SFU では RTP をコピーして複数の宛先に転送します。そのため、受け取り先が複数います。ただもともと WebRTC では相手は 1 人しか想定されていません。そのため、動作に支障が出てきます。

RTCP は主に Sender Report と Receiver Report があります。この中に Feedback Message や Bye などが含まれてきます。Sender Report は映像や音声を配信している側が、こんな感じで送ってるよと送ったパケット数や時間などを送る仕組みになっています。 Receiver Report は Report Block というのを使って、受け取ったパケットの総数やジッターなど実際にどんな状況なのかを伝えていきます。

Sender Report を送った側はそれをみて映像の品質を動的に変更していきます。動的に映像の品質を変えることで相手の通信状況を考慮した動作が可能になるのです。が、これが SFU ではとても邪魔です。

なぜなら相手が 1 人ではなく N 人になるからです。一人でも回線品質が悪い人がいるとその人に引っ張られてしまうことになります。

RTCP を動的に生成する図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/rtcp.png

Firefox

2016-02 の時点で、 WebRTC の最先端を突き進んでいるのは実は Firefox です。 Firefox は Multistream や Simulcast といった部分で全力実装です。さらに、 VP9 への対応もすでに始まっています。

Chrome に後れを取っている部分と言えば解析や統計機能の部分でしょうか。基本的に WebRTC を見る場合は Firefox に合わせていくのが正しい状況です。

WebRTC は Google が最先端となりそうなものなのですが、意外にもそうではありません。

WebRTC をやるなら Firefox を追いかけろ

パケットが欠けたときに FIR を送ってしまうのは早めに辞めて PLI を送る仕組みに切り替えて欲しいとは思っています。

Chrome

Chrome は Firefox に比べていろいろな実装が後手後手に回っています。というか中途半端な実装が多いというのが正直なところです。

これはハングアウトに引っ張られているのかどうかはわかりません。ただ、去年色々前に進めると言ってから ECDSA の採用、MediaStream の録画機能と少しずつ進んでいるように思えます。

ちなみにネットワーク部分に関しては Firefox より Chrome の方が先に進んでいる印象があります。 ICE-TCP もそうですが、デバッグ機能など変なところで突出しているイメージです。

2016-02 の時点で Multistream に関してはまだ Plan B から抜け出せていません。これは正直かなり足を引っ張っているのでなんとかしてほしいですが、コストが高いのもなんとなくわかります。ゆっくり待ちましょう。

Multistream

SDP の部分でも説明しましたがここでもう一度説明します。

Multistream は全てのやりとりを一つのポート、つまり一つの DTLS でやりとりを行う技術です。映像や音声を後から追加したり、流れている映像や通信を削除したりすることもできます。

参加者を動的に追加したり、削除したりを新しいコネクションを張らずに実現できるわけです。

マルチストリームの図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/multistream.png

複数人数でのマルチストリームの図

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/multistream3.png

視聴者との直接やりとり

Multistream を上手く活用することで、接続を増やすことなく、切断することなく、今視聴者として参加している人を配信者として動的に参加させることが可能になります。

Multistream は配信者と視聴者を動的に切り替えたりすることができるようになるのです。

最初は一人だけが配信してそれ以外は視聴者

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/multistream_1ton.png

その後、誰かが質問をしてきたので、見ている人全員にも見えるようになる

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/multistream_2ton.png

質問が終わったので質問した人は視聴している一人になる

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/multistream_1ton.png

このように動的に配信者を参加させることが可能になります。

Simulcast

Simulcast は複数ビットレートの映像を配信側が送りつける方式です。この方式を使う事で SFU では回線速度が遅い場合は低ビットレート、回線速度が速い場合は高ビットレートの映像を配信することができます。

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/simulcast.png

Firefox 47 にて Simulcast の最小限な実装が入りました。簡単なサンプルと Simulcast を指定したときの SDP を張っておきます。

Simulcast を指定するサンプルコード:

var pc = new RTCPeerConnection({});

navigator.mediaDevices.enumerateDevices()
.then( devices => {
  return navigator.mediaDevices.getUserMedia({audio: false, video: true})
})
.then( stream => {
  pc.addTrack(stream.getVideoTracks()[0], stream);
  var sender = pc.getSenders()[0];
  // active と priority は 2016-02-03 の時点で Firefox では未実装のようです
  sender.setParameters({encodings: [{ rid: "spam", active: true, priority: "high", maxBitrate: 4000 },
                                    { rid: "egg", active: true, priority: "medium", maxBitrate: 1000 }]})

  pc.createOffer()
  .then( offer => {
      console.log(offer.sdp);
  })
})
.catch( error => {
    // エラー
});

Simulcast 有効時の SDP:

v=0
o=mozilla...THIS_IS_SDPARTA-47.0a1 1807443757353422678 0 IN IP4 0.0.0.0
s=-
t=0 0
a=fingerprint:sha-256 9E:DC:41:7B:95:B1:71:5F:CE:1E:70:D6:7E:4D:D9:C5:EA:DB:63:03:8D:A9:B2:DA:4B:98:D4:FE:DF:23:B9:B6
a=ice-options:trickle
a=msid-semantic:WMS *
m=video 9 UDP/TLS/RTP/SAVPF 120 126 97
c=IN IP4 0.0.0.0
a=sendrecv
a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1
a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1
a=fmtp:120 max-fs=12288;max-fr=60
a=ice-pwd:c11d5e4cdcf0f78dd61652efa93fceaa
a=ice-ufrag:ea0d08fc
a=mid:sdparta_0
a=msid:{1a0c032c-5b9b-7a41-8fa2-a3634ff5ed1f} {873e039b-dc7e-fe48-bb07-c45356e4a7c6}
a=rid:spam send
a=rid:egg send
a=rtcp-fb:120 nack
a=rtcp-fb:120 nack pli
a=rtcp-fb:120 ccm fir
a=rtcp-fb:126 nack
a=rtcp-fb:126 nack pli
a=rtcp-fb:126 ccm fir
a=rtcp-fb:97 nack
a=rtcp-fb:97 nack pli
a=rtcp-fb:97 ccm fir
a=rtcp-mux
a=rtpmap:120 VP8/90000
a=rtpmap:126 H264/90000
a=rtpmap:97 H264/90000
a=setup:actpass
a=simulcast: send rid=spam;egg
a=ssrc:2394790163 cname:{2fd69058-6496-984a-abdd-0d67f5d1701e}

本来は rid の名前の後ろにビットレートの細かい profile みたいなのが入ります。まだ出力されていないようです。 また、 RTP の拡張も入るはずなのですが、この段階では有効になっていないようです。

Simulcast は SFU にとってとても重要な技術です。この技術を使う事で再変換なしで帯域による映像の画質を変更できるわけです。

思っているよりも早く仕様が入ったという印象です。 WebRTC 1.0 には Simulcast は含まれてリリースされていくようです。

録画

WebRTC をサーバ経由で扱うメリットの一つとしてあげられるのが録画機能です。クライアントで録画する機能はそもそも WebRTC が API の一つとして提供しています。

録画はその処理をサーバ側で行う機能です。サーバはパケットを受信したら暗号を復号して、暗号化してから送信しています。そのためサーバには暗号化されていないパケットが存在できます。

よく誤解されますが TURN サーバの場合は暗号化されたパケットをそのまま流れます、そのため TURN サーバは何が流れているかを把握できません。

録画は WebRTC で配信をするにあたり、様々可能性を持っています。リアルタイムという遅延の少ない世界で見る映像を後出しで見ることができるようになるためです。

遅延が少ない世界と、遅延を気にしない世界の両方に存在できるようになります。VP8/Opus で配信されている映像であれば WebM 形式として保存が可能です。それを MPEG-DASH 形式に変換しておけば、後は HLS のような配信することもできます。

ただし、録画はコーデックに依存するため、なかなかしんどいと言われています。今後コーデックが VP9 や H.264 が標準になってくる場合色々と課題が出てきそうです。

TURN

グローバルに存在する WebRTC SFU といえども、 TURN は必要になる場合があります。それは TCP しか使えない世界だったりする場合です。 もちろん UDP の場合でも TURN が必要になる場合もありますが、ほとんどの場合は TCP での配信が求められるために必要となるでしょう。

ただ、今後はわざわざ TURN サーバを立てる必要もなくなるかもしれません。

ICE-TCP

ICE というのは STUN と TURN を合わせた仕組みを指しますが、 ICE-TCP という技術があります。これは Chrome では有効になっています。 Firefox では実装はされていますがデフォルトでは無効になっています。

実は WebRTC SFU は ICE-TCP との相性がとても良いです。

TURN-TCP を使うにはまず STUN を TCP で繋げられるようにする必要があります。WebRTC SFU として ICE-TCP を採用すると TURN サーバが不要になります。ただし、実装としては TCP というワンクッションが一度入ります。

TCP で SFU がパケットを受け取ってその中のパケットをは DTLS-SRTP なのです。つまりただの TCP のトンネルを SFU にはるだけです。その中を普通に DTLS-SRTP パケットを投げつけてきます。

これで UDP が通れない場合や、TCP で 80 番しか空いていない場合とかも解決です。ちなみに ICE-TCP は TLS も含みますので DTLS-SRTP over TLS も可能になります。

https://dl.dropboxusercontent.com/u/89936/webrtc_sfu/ice_tcp_dtls.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment