I wrote a WebRTC SFU from scratch
Author: | @voluntas |
---|---|
Translator: | @alanmshelly |
Version: | 0.0.0 |
url: | https://voluntas.github.io/ |
2015 年 11 月 04 日に行われる WebRTC Meetup Japan 向けの発表資料です。
Presentation materials for WebRTC Meetup Japan on 2015/11/04.
時雨堂 という零細企業を経営しています。
twitter は @voluntas
I run a small company named Shiguredo
twitter account is @voluntas
- 日本だと誰も作ってなさそうだったし、面白そうだと思ったから。
- テレビ会議システムにはあまり興味なく配信システムに興味があった。
- 音声や映像のリアルタイムな共有に興味があった
- Erlang による DTLS の実装のめどがついたから。
- No one in Japan had done it and it seemed fun.
- I didn't have any interest in conference systems but I was interested in broadcasting systems.
- I was interested in realtime voice and video sharing.
- I had figured out how to implement DTLS in Erlang.
Erlang には DTLS の実装がまだ公式にないこと、TLS は実装経験があるのでなんとかなると思ってた。前職で UDP 上で TLS を実現する EAP-TLS とかを書いていた。
Chrome がまだ DTLS 1.0 なのでまずは 1.0 から。
UDP なのでシーケンス番号が入ってきたり少しずつ違う。DTLS 1.0 は TLS 1.1 ベース。
CipherSuites は決め打ちした。ECDHE で RSA そして AES-CBC で実装。
実装終わって openssl でテストして動作した。
Erlang doesn't have an official DTLS implementation but I had experience implementing TLS. I'd written EAP-TLS to implement TLS over UDP at my previous job.
Chrome still uses DTLS 1.0 so start with 1.0.
It's UDP so there are some small differences such as sequence numbers being used. DTLS 1.0 is based on TLS 1.1.
CiperSuites is hardcoded. RSA and AES-CBC are implemented using ECDHE.
After finishing implementation, I tested with openssl.
今後考えるとやっておきたい、ということで実装。
ついでに AEAD 認証に対応しておきたい。AES-GCM と CHACHA20 に定める。
実装終わってテスト通った。ただ DTLS 1.0 邪魔だなと思い始める。
全消しして 1.2 のみのコードにする。Chrome は追従するだろうと読む。 Chrome M47 から徐々に DTLS 1.2 になる模様。Firefox はすでに DTLS 1.2 。
SRTP を組み込んだので、ライブラリ的には DTLS-SRTP ライブラリ。
I implemented it as it will be useful in the future.
I also added support for AEAD while I was at it. Decided on AES-GCM and CHACHA20.
Tests passed after implementing... but I started to think the DTLS 1.0 code was unnecessary.
I deleted all of it and left only the DTLS 1.2 code, predicting Chrome would catch up. It looks like Chrome will start including DTLS 1.2 from M47. Firefox also has DTLS 1.2.
It is bundled with SRTP so the library is a DTLS-SRTP library.
投げるにしても受け取るにしても必要ということで作る。文字列はだるい。 いろいろな SDP をパーサに食べさせてテスト、問題なさそうということで完成。
Needed for both sending and receiving. Handling text is annoying. Tried a bunch of SDP parsers. I found no problems so done.
## STUN ライブラリ
すでに実装してたので ICE に対応させて終わり。
すでに持っていたがいけてなかったのでほぼ書き直し。かなりの頻度で書き直したり機能追加したりした。
SRTCP 系の対応が死にそうになる。独自すぎる。一通り退治して実際に WebRTC の SRTP パケットを受け取って無事動く。確認は明らかに変な値じゃないかを確認する。暗号系の怖いところはとりあえず復号はできてしまうところを。
間違ってるかどうかはアプリでしか判断できない。
とりあえず作ったライブラリを組み上げてみる。シグナリングサーバーがいないとダメなので組み上げたらシグナリングサーバーの開発へ。
SkyWay で経験していたが方向が逆向きなので一から設計。WebRTC SFU では SFU から Offer を出す。
さらに接続の認証方式などをあとで考えられるようにしてざっくり実装。
まずは WebSocket だけで対応。XHR へのダウングレードは後回し。
認証は後回し。動くところまで持っていく。
まずは配信者だけで確認。なんとなくデコードできていそうというところまで持っていく。
次に視聴者を追加。ここで最初に視聴者をつながないと画面が映らないことに気づく。FIR を投げないとダメとアドバイスをいただく。
視聴者の DTLS が完了したタイミングで SFU が FIR を配信者に送る仕組みを入れる。大量接続を踏まえ 500 ms単位で接続を間引きして FIR を送らないようにする仕組みを追加。
無事動作するようになる。
RTP/RTCP が生で見られるようになったのでパースできてないのを実装したりする。
いつも通り WS デバッガーをくっつけて流れてるパケットを WS で気軽に閲覧できるようにする。
Firefox と Chrome の Application layer な RTCP に対応数。
Erlang doesn't have an official DTLS implementation but I had experience implementing TLS. I'd written EAP-TLS to implement TLS over UDP at my previous job.
Chrome still uses DTLS 1.0 so start with 1.0.
It's UDP so there are some small differences such as sequence numbers being used. DTLS 1.0 is based on TLS 1.1.
CiperSuites is hardcoded. RSA and AES-CBC are implemented using ECDHE.
After finishing implementation, I tested with openssl.
I implemented it as it will be useful in the future.
I also added support for AEAD while I was at it. Decided on AES-GCM and CHACHA20.
Tests passed after implementing... but I started to think the DTLS 1.0 code was unnecessary.
I deleted all of it and left only the DTLS 1.2 code, predicting Chrome would catch up. It looks like Chrome will start including DTLS 1.2 from M47. Firefox also has DTLS 1.2.
It is bundled with SRTP so the library is a DTLS-SRTP library.
Needed for both sending and receiving. Handling text is annoying. Tried a bunch of SDP parsers. I found no problems so done.
## STUN library
I'd already written one so I made it support ICE and I was done.
The one I had wasn't very good so I rewrote it almost completely. I rewrote it/added new features quite often.
Almost died trying to support SRTCP. It's too unique. I eventually got it to accept WebRTC SRTP packets. Confirmed by reading the values and making sure they weren't obviously wrong. The scary part about encryption is you can decrypt anything but not know if its right.
You can only test if its working or not using the application.
Started putting together the libraries I'd developed. I needed a signalling server so I went on to develop the signalling server.
The implementation is the opposite of the one for SkyWay so I had to design from scratch. The WebRTC SFU creates an offer at the SFU.
Also quickly implemented an scheme that let me decide on authentication later.
First support just WebSockets. Will implement downgrade to XHR later.
Authentication comes later. Just get it working.
Try with just the broadcaster first. Work on it until it looks like the SFU is decoding properly.
Next add the receivers. I realized here that if a receiver wasn't connected first, then the stream wouldn't show. I got advice that I need to send FIR.
Add logic to send FIR from the SFU to the broadcaster when a receiver's DTLS finishes. In order to handle large numbers of simultaneous connections, I limited the FIRs to send a maximum of once every 500ms.
Works properly.
I could now see raw RTP/RTCP packets so I decided to parse them.
Make it so that I could connect with a WS debugger and see the packets.
Supports Firefox and Chrome Application layer RTCP packets.
ここで WebRTC SFU のローカルでの起動デモを見せる。
WS デバッガーも見せる。
Show a demo running the WebRTC SFU locally.
Show the WS debugger as well.
それといった課題はあまりないが、明示的に解決しなければいけないのは RTCP の代理返信。
今は RR を全部送ってるので間引きする。間引き後は SR->RR は配信者と SFU で閉じる。視聴者からの RR は SFU で集約して SFU の RR 生成時に全体の総意を汲み取りつつ反映する。
No major problems but one part I have to explicitly fix is the RTCP proxy responses.
Currently all RRs are being sent and being culled. The broadcaster and SFU close SR->RR after culling. The RRs from the receivers are gathered at the SFU, and when the SFU creates an RR it is based on all of the RRs from the receivers.
自社製品の宣伝です
アプリとの連携がキモなので連携部分を重要視した。
サーバ自体には一切情報を追加したりせず、起動したらすぐ使える。
接続の認証がしたい、チャネル単位で接続数制限がしたい、時間制限がしたい。 全て WebRTC SFU がデリゲーションしてくるので、アプリ側で実装可能。
データを持たないため、台数さえ増やせばいいのでスケールさせやすい。
WebRTC SFU に WS で接続することで、クライアントの接続や、一定間隔での接続時間の通知などステートが変化したときにストリームでデータを送ってくる。
リアルタイムにアプリ側で判断が可能。
SFU に対して叩く API でチャネル単位での全員切断や、指定した接続の切断。さらにチャネルの接続一覧などがとれる。
アプリはストリーム API でリアルタイムに受け取るステート変化に対してアクションを起こすことが可能になる。
認証などの同期的に判断が必要なのは指定した HTTP API を叩いてくれ、200 だったら OK という動作をする。
全てアプリ側で判断できるため、データベースに悩む必要が無い。
This is advertising for our product
Connecting with apps is fundamental so we focused especially on the API.
The server itself doesn't add any data and can be used as soon as it starts.
I want to authenticate connections. I want to limit connections per channel. I want to limit connection time. The WebRTC SFU delegates all of this so it can be implemented in the application.
Since it doesn't hold any data, you can easily scale out by adding servers.
It is possible to get state change information such as clients connecting or connection time by connecting sent to you by connecting via WS.
It is possible to make decisions in real time in the application.
It is possible to call an API to disconnect a single, multiple or all clients connected to each channel. You can also get a list of clients connected to a channel.
The application can use the information received from the stream API to detect a change in state and take action.
If you need something synchronous such as authentication, call an HTTP API and if the response is 200 then it will proceed as OK.
It's all handled in the application so you don't have to worry about a database.
VP8 であれば WebM 形式での保存を目指す。ただ H.264 がメインになる可能性があるのでまだ様子見。
おもしろい機能ではあるので、対応はしていきたい。
Chrome が H.264 になると強い。HW が H.264 なのが多いため。
SDP 部分しか影響ないのでそこを接続時に切り替える。
配信者がフォーマットを選べるようにする。
閲覧者が複数の配信を Multistream で同時に見れるようにする。
これは複数カメラの配置に対して需要がありそうなので前倒しで実装していく予定。
年末までには実装。別に難しくない。
Firefox に来たらすぐに対応。
自前開発があるので、SFU に組み込む。 主に TCP -> UDP への変換が目的になるが、 SFU に組み込むことで TCP -> TURN -> SFU -> UDP みたいにできる。 わざわざ一回ネットワークを経由しなくて良くなる。
If it's VP8, I want to write to a WebM file. That being said, H.264 might become the main so I'm still waiting.
It's an interesting features so I want to implement it for sure.
It will be good if Chrome supports H.264 since H.264 has a lot of hardware support.
It only affects the SDP so I can change it there when connecting.
I'll make it so that the broadcaster can choose.
Make it so that the viewers can receive multiple streams simultaneously using multistream.
It looks like it there is a large need to use this with multiple cameras so I will increase priority.
By the end of the year. It's not that difficult.
I will implement as soon as it comes to Firefox.
I have an implementation so I will integrate into the SFU. Mostly for converting TCP->UDP but by integrating it into the SFU, you can do TCP->TURN->SFU->UDP. You don't have to add another hop through a network.