Skip to content

Commit beafcc2

Browse files
ThanhDodeurOdooseb-odoo
authored andcommitted
[IMP] add simulcast
This commit adds the support for simulcast: Simulcast is a way to let producers sent multiple version of the streams as different layers of quality. This allows the server to select the optimal layer based on the available bandwidth for each client. Two env variables are used to control this feature: `VIDEO_MAX_BITRATE`: Specifies the bitrate for the highest encoding layer per stream. It sets the `maxBitrate` property of the highest encoding of the `RTCRtpEncodingParameters` and is used to compute the bitrate attributed to the other layers. `MAX_BITRATE_OUT`: The maximum outgoing bitrate (=from the server) per session (meaning that this cap is shared between all the incoming streams for any given user). The appropriate simulcast layers used by each consumer will be selected to honor this limit. If the bitrate is still too high, packets will be dropped. Without this limit, the connections will use as much bandwidth as possible, which means that the simulcast layer will be chosen based on the client (or server) max available bandwidth. This commits also bumps the minor version as the bundle and the server need to be updated to benefit from the feature (although both the client and the server are backwards compatible).
1 parent 5e71c03 commit beafcc2

File tree

5 files changed

+62
-13
lines changed

5 files changed

+62
-13
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,9 @@ The available environment variables are:
4949
- **RTC_MAX_PORT**: Upper bound for the range of ports used by the RTC server, must be open in both TCP and UDP
5050
- **MAX_BUF_IN**: if set, limits the incoming buffer size per session (user)
5151
- **MAX_BUF_OUT**: if set, limits the outgoing buffer size per session (user)
52-
- **MAX_BITRATE_IN**: if set, limits the incoming bitrate per session (user)
53-
- **MAX_BITRATE_OUT**: if set, limits the outgoing bitrate per session (user)
52+
- **MAX_BITRATE_IN**: if set, limits the incoming bitrate per session (user), defaults to 8mbps
53+
- **MAX_BITRATE_OUT**: if set, limits the outgoing bitrate per session (user), defaults to 10mbps
54+
- **MAX_VIDEO_BITRATE**: if set, defines the `maxBitrate` of the highest encoding layer (simulcast), defaults to 4mbps
5455
- **CHANNEL_SIZE**: the maximum amount of users per channel, defaults to 100
5556
- **WORKER_LOG_LEVEL**: "none" | "error" | "warn" | "debug", will only work if `DEBUG` is properly set.
5657
- **LOG_LEVEL**: "none" | "error" | "warn" | "info" | "debug" | "verbose"

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "odoo-sfu",
33
"description": "Odoo's SFU server",
4-
"version": "1.0.0",
4+
"version": "1.1.0",
55
"author": "Odoo",
66
"license": "LGPL-3.0",
77
"type": "module",

src/client.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const RECOVERY_DELAY = 1_000; // how much time after an error should pass before
1717
const SUPPORTED_TYPES = new Set(["audio", "camera", "screen"]);
1818

1919
// https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerOptions
20-
const PRODUCER_OPTIONS = {
20+
const DEFAULT_PRODUCER_OPTIONS = {
2121
stopTracks: false,
2222
disableTrackOnPause: false,
2323
zeroRtpOnPause: true,
@@ -117,6 +117,11 @@ export class SfuClient extends EventTarget {
117117
camera: null,
118118
screen: null,
119119
};
120+
/** @type {Object<"audio" | "video", import("mediasoup-client").types.ProducerOptions>} */
121+
_producerOptionsByKind = {
122+
audio: DEFAULT_PRODUCER_OPTIONS,
123+
video: DEFAULT_PRODUCER_OPTIONS,
124+
};
120125
/** @type {Function[]} */
121126
_cleanups = [];
122127

@@ -284,7 +289,7 @@ export class SfuClient extends EventTarget {
284289
}
285290
try {
286291
this._producers[type] = await this._ctsTransport.produce({
287-
...PRODUCER_OPTIONS,
292+
...this._producerOptionsByKind[track.kind],
288293
track,
289294
appData: { type },
290295
});
@@ -599,7 +604,10 @@ export class SfuClient extends EventTarget {
599604
return;
600605
}
601606
case SERVER_REQUEST.INIT_TRANSPORTS: {
602-
const { capabilities, stcConfig, ctsConfig } = payload;
607+
const { capabilities, stcConfig, ctsConfig, producerOptionsByKind } = payload;
608+
if (producerOptionsByKind) {
609+
this._producerOptionsByKind = producerOptionsByKind;
610+
}
603611
if (!this._device.loaded) {
604612
await this._device.load({ routerRtpCapabilities: capabilities });
605613
}

src/config.js

+46-7
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const FALSY_INPUT = new Set(["disable", "false", "none", "no", "0"]);
1616
export const AUTH_KEY = process.env.AUTH_KEY;
1717
if (!AUTH_KEY && !process.env.JEST_WORKER_ID) {
1818
throw new Error(
19-
"AUTH_KEY env variable is required, it is not possible to authenticate requests without it",
19+
"AUTH_KEY env variable is required, it is not possible to authenticate requests without it"
2020
);
2121
}
2222
/**
@@ -29,7 +29,7 @@ if (!AUTH_KEY && !process.env.JEST_WORKER_ID) {
2929
export const PUBLIC_IP = process.env.PUBLIC_IP;
3030
if (!PUBLIC_IP && !process.env.JEST_WORKER_ID) {
3131
throw new Error(
32-
"PUBLIC_IP env variable is required, clients cannot establish webRTC connections without it",
32+
"PUBLIC_IP env variable is required, clients cannot establish webRTC connections without it"
3333
);
3434
}
3535
/**
@@ -69,7 +69,7 @@ export const PORT = Number(process.env.PORT) || 8070;
6969
*/
7070
export const NUM_WORKERS = Math.min(
7171
Number(process.env.NUM_WORKERS) || Infinity,
72-
os.availableParallelism(),
72+
os.availableParallelism()
7373
);
7474
/**
7575
* A comma separated list of the audio codecs to use, if not provided the server will support all available codecs (listed below).
@@ -111,19 +111,29 @@ export const MAX_BUF_IN = (process.env.MAX_BUF_IN && Number(process.env.MAX_BUF_
111111
*/
112112
export const MAX_BUF_OUT = (process.env.MAX_BUF_OUT && Number(process.env.MAX_BUF_OUT)) || 0;
113113
/**
114-
* The maximum incoming bitrate in bps for per session
114+
* The maximum incoming bitrate in bps per session,
115+
* This is what each user can upload.
115116
*
116117
* @type {number}
117118
*/
118119
export const MAX_BITRATE_IN =
119-
(process.env.MAX_BITRATE_IN && Number(process.env.MAX_BITRATE_IN)) || 0;
120+
(process.env.MAX_BITRATE_IN && Number(process.env.MAX_BITRATE_IN)) || 8_000_000;
120121
/**
121-
* The maximum outgoing bitrate in bps for per session
122+
* The maximum outgoing bitrate in bps per session,
123+
* this is what each user can download.
122124
*
123125
* @type {number}
124126
*/
125127
export const MAX_BITRATE_OUT =
126-
(process.env.MAX_BITRATE_OUT && Number(process.env.MAX_BITRATE_OUT)) || 0;
128+
(process.env.MAX_BITRATE_OUT && Number(process.env.MAX_BITRATE_OUT)) || 10_000_000;
129+
/**
130+
* The maximum bitrate (in bps) for the highest encoding layer (simulcast) per video producer (= per video stream).
131+
* see: `maxBitrate` @ https://www.w3.org/TR/webrtc/#dictionary-rtcrtpencodingparameters-members
132+
*
133+
* @type {number}
134+
*/
135+
export const MAX_VIDEO_BITRATE =
136+
(process.env.MAX_VIDEO_BITRATE && Number(process.env.MAX_VIDEO_BITRATE)) || 4_000_000;
127137
/**
128138
* The maximum amount of concurrent users per channel
129139
*
@@ -181,6 +191,16 @@ export const timeouts = Object.freeze({
181191
// how many errors can occur before the session is closed, recovery attempts will be made until this limit is reached
182192
export const maxSessionErrors = 6;
183193

194+
/**
195+
* @type {import("mediasoup-client").types.ProducerOptions}
196+
* https://mediasoup.org/documentation/v3/mediasoup-client/api/#ProducerOptions
197+
*/
198+
const baseProducerOptions = {
199+
stopTracks: false,
200+
disableTrackOnPause: false,
201+
zeroRtpOnPause: true,
202+
};
203+
184204
export const rtc = Object.freeze({
185205
// https://mediasoup.org/documentation/v3/mediasoup/api/#WorkerSettings
186206
workerSettings: {
@@ -208,6 +228,25 @@ export const rtc = Object.freeze({
208228
maxSctpMessageSize: MAX_BUF_IN,
209229
sctpSendBufferSize: MAX_BUF_OUT,
210230
},
231+
producerOptionsByKind: {
232+
/** @type {import("mediasoup-client").types.ProducerOptions} */
233+
audio: baseProducerOptions,
234+
/** @type {import("mediasoup-client").types.ProducerOptions} */
235+
video: {
236+
...baseProducerOptions,
237+
// for browsers using libwebrtc, values are set to allow simulcast layers to be made in that range
238+
codecOptions: {
239+
videoGoogleMinBitrate: 1_000,
240+
videoGoogleStartBitrate: 1_000_000,
241+
videoGoogleMaxBitrate: MAX_VIDEO_BITRATE * 2,
242+
},
243+
encodings: [
244+
{ scaleResolutionDownBy: 4, maxBitrate: Math.floor(MAX_VIDEO_BITRATE / 4) },
245+
{ scaleResolutionDownBy: 2, maxBitrate: Math.floor(MAX_VIDEO_BITRATE / 2) },
246+
{ scaleResolutionDownBy: 1, maxBitrate: MAX_VIDEO_BITRATE },
247+
],
248+
},
249+
},
211250
});
212251

213252
// ------------------------------------------------------------

src/models/session.js

+1
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@ export class Session extends EventEmitter {
315315
dtlsParameters: this._ctsTransport.dtlsParameters,
316316
sctpParameters: this._ctsTransport.sctpParameters,
317317
},
318+
producerOptionsByKind: config.rtc.producerOptionsByKind,
318319
},
319320
});
320321
await Promise.all([

0 commit comments

Comments
 (0)