Skip to content

Commit

Permalink
fix: max simulcast layers preference (#1560)
Browse files Browse the repository at this point in the history
Adds another experimental `maxSimulcastLayers` publish option that would
limit the number of layers published when a non-SVC codec is used.
  • Loading branch information
oliverlaz authored Nov 7, 2024
1 parent 2132b6e commit 2b0bf28
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 5 deletions.
3 changes: 2 additions & 1 deletion packages/client/src/rtc/Publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,8 @@ export class Publisher {
? // for SVC, we only have one layer (q) and often rid is omitted
enabledLayers[0]
: // for non-SVC, we need to find the layer by rid (simulcast)
enabledLayers.find((l) => l.name === encoder.rid);
enabledLayers.find((l) => l.name === encoder.rid) ??
(params.encodings.length === 1 ? enabledLayers[0] : undefined);

// flip 'active' flag only when necessary
const shouldActivate = !!layer?.active;
Expand Down
39 changes: 39 additions & 0 deletions packages/client/src/rtc/__tests__/Publisher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,45 @@ describe('Publisher', () => {
]);
});

it('can dynamically activate/deactivate simulcast layers when rid is missing', async () => {
const transceiver = new RTCRtpTransceiver();
const setParametersSpy = vi
.spyOn(transceiver.sender, 'setParameters')
.mockResolvedValue();
const getParametersSpy = vi
.spyOn(transceiver.sender, 'getParameters')
.mockReturnValue({
// @ts-expect-error incomplete data
codecs: [{ mimeType: 'video/VP8' }],
encodings: [{ active: false }],
});

// inject the transceiver
publisher['transceiverCache'].set(TrackType.VIDEO, transceiver);

await publisher['changePublishQuality']([
{
name: 'q',
active: true,
maxBitrate: 100,
scaleResolutionDownBy: 4,
maxFramerate: 30,
scalabilityMode: '',
},
]);

expect(getParametersSpy).toHaveBeenCalled();
expect(setParametersSpy).toHaveBeenCalled();
expect(setParametersSpy.mock.calls[0][0].encodings).toEqual([
{
active: true,
maxBitrate: 100,
scaleResolutionDownBy: 4,
maxFramerate: 30,
},
]);
});

it('can dynamically update scalability mode in SVC', async () => {
const transceiver = new RTCRtpTransceiver();
const setParametersSpy = vi
Expand Down
4 changes: 2 additions & 2 deletions packages/client/src/rtc/bitrateLookup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const bitrateLookupTable: Record<
> = {
h264: {
2160: 5_000_000,
1440: 3_500_000,
1080: 2_750_000,
1440: 3_000_000,
1080: 2_000_000,
720: 1_250_000,
540: 750_000,
360: 400_000,
Expand Down
8 changes: 6 additions & 2 deletions packages/client/src/rtc/videoLayers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ export const findOptimalVideoLayers = (
const optimalVideoLayers: OptimalVideoLayer[] = [];
const settings = videoTrack.getSettings();
const { width = 0, height = 0 } = settings;
const { scalabilityMode, bitrateDownscaleFactor = 2 } = publishOptions || {};
const {
scalabilityMode,
bitrateDownscaleFactor = 2,
maxSimulcastLayers = 3,
} = publishOptions || {};
const maxBitrate = getComputedMaxBitrate(
targetResolution,
width,
Expand All @@ -76,7 +80,7 @@ export const findOptimalVideoLayers = (
let downscaleFactor = 1;
let bitrateFactor = 1;
const svcCodec = isSvcCodec(codecInUse);
for (const rid of ['f', 'h', 'q']) {
for (const rid of ['f', 'h', 'q'].slice(0, Math.min(3, maxSimulcastLayers))) {
const layer: OptimalVideoLayer = {
active: true,
rid,
Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@ export type PublishOptions = {
* in simulcast mode (non-SVC).
*/
bitrateDownscaleFactor?: number;
/**
* The maximum number of simulcast layers to use when publishing the video stream.
*/
maxSimulcastLayers?: number;
/**
* Screen share settings.
*/
Expand Down
6 changes: 6 additions & 0 deletions sample-apps/react/react-dogfood/components/MeetingUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export const MeetingUI = ({ chatClient, mode }: MeetingUIProps) => {
const scalabilityMode = router.query['scalability_mode'] as
| string
| undefined;
const maxSimulcastLayers = router.query['max_simulcast_layers'] as
| string
| undefined;

const onJoin = useCallback(
async ({ fastJoin = false } = {}) => {
Expand All @@ -74,6 +77,9 @@ export const MeetingUI = ({ chatClient, mode }: MeetingUIProps) => {
bitrateDownscaleFactor: bitrateFactorOverride
? parseInt(bitrateFactorOverride, 10)
: 2, // default to 2
maxSimulcastLayers: maxSimulcastLayers
? parseInt(maxSimulcastLayers, 10)
: 3, // default to 3
});

await call.join({ create: true });
Expand Down

0 comments on commit 2b0bf28

Please sign in to comment.