Skip to content

Commit f69f0d7

Browse files
authored
Add simulcast tutorial (#151)
1 parent 0bb94a4 commit f69f0d7

File tree

2 files changed

+143
-1
lines changed

2 files changed

+143
-1
lines changed

guides/advanced/simulcast.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# Simulcast
2+
3+
Simulcast is a technique where a client sends multiple encodings of the same video to the server, which is then responsible for dynamically choosing the appropraite encoding for every peer (other client).
4+
Encodings differ between each other in resolution and/or frame rate.
5+
The selection of the encoding is based on:
6+
* Receiver available bandwidth.
7+
* Receiver preferences (e.g. explicit request to receive video at HD resolution instead of FHD).
8+
* UI layout (e.g. videos displayed in smaller tiles will be sent at a lower resolution).
9+
10+
Simulcast is not utilized in direct client-client connections (no intermediate server) because in such cases,
11+
the sender can adjust its resolution or frame rate based on a feedback from the receiver.
12+
13+
Elixir WebRTC comes with:
14+
* Support for inbound simulcast - it allows to receive multiple incoming resolutions
15+
* RTP munger and keyframe detectors, which can be used for implementing encoding switching on the server side
16+
17+
Currently there is no support for:
18+
* Outbound simulcast
19+
* Bandwidth estimation
20+
* Automatic encoding switching
21+
22+
```mermaid
23+
flowchart LR
24+
WB1((Web Browser1)) -->|low| Server
25+
WB1((Web Browser1)) -->|medium| Server
26+
WB1((Web Browser1)) -->|high| Server
27+
Server -->|low| WB2((WebBrowser 2))
28+
Server -->|high| WB3((WebBrowser 3))
29+
```
30+
31+
## Turning simulcast on
32+
33+
### Elixir WebRTC
34+
35+
Elixir WebRTC automatically accepts incoming simulcast tracks so there are no extra steps required.
36+
37+
### JavaScript
38+
39+
Simulcast can be enabled when adding a new track. For example:
40+
41+
```js
42+
const pc = new RTCPeerConnection();
43+
44+
const localStream = await navigator.mediaDevices.getUserMedia({
45+
video: {
46+
width: { ideal: 1280 },
47+
height: { ideal: 720 },
48+
},
49+
});
50+
51+
pc.addTransceiver(localStream.getVideoTracks()[0], {
52+
streams: [localStream],
53+
sendEncodings: [
54+
{ rid: 'h', maxBitrate: 1500 * 1024 },
55+
{ rid: 'm', scaleResolutionDownBy: 2, maxBitrate: 600 * 1024 },
56+
{ rid: 'l', scaleResolutionDownBy: 4, maxBitrate: 300 * 1024 },
57+
],
58+
});
59+
```
60+
61+
> #### Minimal starting resolution {: .warning}
62+
> To run 3 simulcast encodings, the minimal starting resolution
63+
> must be 960x540. See more [here](https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/video/config/simulcast.cc;l=79?q=simulcast.cc).
64+
65+
66+
## Receiving simulcast packets
67+
68+
When simulcast is enabled, packets are labeled with an `rid`, which denotes simulcast
69+
encoding that a packet belongs to:
70+
71+
```elixir
72+
{:ex_webrtc, input_pc_pid, {:rtp, input_track_id, rid, packet}}
73+
```
74+
75+
## Switching between simulcast encodings
76+
77+
Switching between simulcast encodings requires some modifications to RTP packets.
78+
Every encoding starts with a random RTP sequence number and a random RTP timestamp.
79+
Because client that receives our stream is never aware of simulcast (they always receive
80+
a single encoding), we have to rewrite those sequence numbers and timestamps to be continuous and increasing.
81+
This process is known as munging.
82+
83+
1. Create munger with codec sample rate
84+
85+
```elixir
86+
alias ExWebRTC.PeerConnection
87+
alias ExWebRTC.RTP.{H264, Munger}
88+
89+
m = Munger.new(90_000)
90+
```
91+
92+
2. When a packet from an encoding that we want to forward arrives, rewrite its sequnce number and timestamp:
93+
94+
```elixir
95+
receive do
96+
{:ex_webrtc, input_pc, {:rtp, _input_track_id, "m", packet}} ->
97+
{packet, munger} = Munger.munge(munger, packet)
98+
:ok = PeerConnection.send_rtp(output_pc, output_track_id, packet)
99+
{:ex_webrtc, input_pc, {:rtp, _input_track_id, _rid, packet}} ->
100+
# ignore other packets
101+
end
102+
```
103+
104+
3. To switch to another encoding, request a keyframe for this encoding.
105+
Once the keyframe arrives, update the munger and start forwarding new packets.
106+
For example, transitioning from encoding `m` to `h`:
107+
108+
109+
```elixir
110+
# assume we have the following state
111+
state = %{
112+
current_encoding: "m",
113+
munger: munger,
114+
input_pc: input_pc,
115+
input_track_id: input_track_id,
116+
output_pc: output_pc,
117+
output_track_id: output_track_id
118+
}
119+
120+
:ok = PeerConnection.send_pli(state.input_pc, state.input_track_id, "h")
121+
122+
# ...
123+
124+
receive do
125+
{:ex_webrtc, input_pc, {:rtp, _input_track_id, rid, packet}} ->
126+
cond do
127+
rid == state.current_encoding ->
128+
{munger, packet} = Munger.munge(munger, packet)
129+
:ok = PeerConnection.send_rtp(state.output_pc, state.output_track_id, packet)
130+
%{state | munger: munger}
131+
rid == "h" and H264.keyframe?(packet) ->
132+
munger = Munger.update(munger)
133+
{munger, packet} = Munger.munge(munger, packet)
134+
:ok = PeerConnection.send_rtp(state.output_pc, state.output_track_id, packet)
135+
%{state | munger: munger, current_encoding: "h"}
136+
true ->
137+
state
138+
end
139+
end
140+
```
141+
142+
See our [Broadcaster](https://github.com/elixir-webrtc/apps/blob/master/broadcaster/lib/broadcaster/forwarder.ex) app source code for more.

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ defmodule ExWebRTC.MixProject do
7474

7575
defp docs do
7676
intro_guides = ["intro", "negotiation", "forwarding", "consuming"]
77-
advanced_guides = ["modifying", "mastering_transceivers", "debugging"]
77+
advanced_guides = ["simulcast", "modifying", "mastering_transceivers", "debugging"]
7878

7979
[
8080
main: "readme",

0 commit comments

Comments
 (0)