Skip to content

Commit 1883642

Browse files
committed
Add simulcast tutorial
1 parent 783f25a commit 1883642

File tree

2 files changed

+114
-1
lines changed

2 files changed

+114
-1
lines changed

guides/advanced/simulcast.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
## Turning simulcast on
23+
24+
### Elixir WebRTC
25+
26+
Elixir WebRTC automatically accepts incoming simulcast tracks so there are no extra steps required.
27+
28+
### JavaScript
29+
30+
Simulcast can be enabled when adding a new track. For example:
31+
32+
```js
33+
const pc = new RTCPeerConnection();
34+
35+
const localStream = await navigator.mediaDevices.getUserMedia({
36+
video: {
37+
width: { ideal: 1280 },
38+
height: { ideal: 720 },
39+
},
40+
});
41+
42+
pc.addTransceiver(localStream.getVideoTracks()[0], {
43+
streams: [localStream],
44+
sendEncodings: [
45+
{ rid: 'h', maxBitrate: 1500 * 1024 },
46+
{ rid: 'm', scaleResolutionDownBy: 2, maxBitrate: 600 * 1024 },
47+
{ rid: 'l', scaleResolutionDownBy: 4, maxBitrate: 300 * 1024 },
48+
],
49+
});
50+
```
51+
52+
> #### Minimal starting resolution {: .warning}
53+
> To run 3 simulcast encodings, the minimal starting resolution
54+
> 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)
55+
56+
57+
## Receiving simulcast packets
58+
59+
When simulcast is enabled, packets are labeled with an `rid`, which denotes simulcast
60+
encoding that a packet belongs to:
61+
62+
```elixir
63+
{:ex_webrtc, pc_pid, {:rtp, track_id, rid, packet}}
64+
```
65+
66+
## Switching between simulcast encodings
67+
68+
Switching between simulcast encodings requires some modifications to RTP packets.
69+
Every encoding starts with a random RTP sequence number and a random RTP timestamp.
70+
Because client that receives our stream is never aware of simulcast (they always receive
71+
a single encoding), we have to rewrite those sequence numbers and timestamps to be continuous and increasing.
72+
This process is known as munging.
73+
74+
1. Create munger with codec sample rate
75+
76+
```elixir
77+
alias ExWebRTC.PeerConnection
78+
alias ExWebRTC.RTP.{H264, Munger}
79+
80+
m = Munger.new(90_000)
81+
```
82+
83+
2. When a packet arrives, rewrite its sequnce number and timestamp:
84+
85+
```elixir
86+
{packet, munger} = Munger.munge(munger, packet)
87+
```
88+
89+
3. To switch to another encoding, request a keyframe for this encoding.
90+
Once the keyframe arrives, update the munger and start forwarding new packets.
91+
For example, transitioning from encoding `m` to `h`:
92+
93+
94+
```elixir
95+
:ok = PeerConnection.send_pli(input_pc, track_id, "h")
96+
97+
# ...
98+
99+
def handle_info({:ex_webrtc, input_pc, {:rtp, _track_id, "h", packet}}, state) do
100+
if H264.keyframe?(packet) do
101+
munger = Munger.update(munger)
102+
{munger, packet} = Munger.munge(munger, packet)
103+
PeerConnection.send_rtp(state.output_pc, state.output_track_id, packet)
104+
state = %{state | munger: munger}
105+
{:noreply, state}
106+
else
107+
# Ignore packets from 'h' until we receive a keyframe.
108+
{:noreply, state}
109+
end
110+
end
111+
```
112+
113+
See our [Broadcaster](https://github.com/elixir-webrtc/apps/tree/master/broadcaster) 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)