Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set max average bitrate on PTT calls #2499

Merged
merged 3 commits into from
Jul 6, 2022
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 58 additions & 5 deletions src/webrtc/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@ interface AssertedIdentity {
displayName: string;
}

// Used internally to specify modifications to codec parameters in SDP
interface CodecParams {
enableDtx?: boolean; // true to enable discontinuous transmission, false to disable, undefined to leave as-is
maxAverageBitrate?: number; // sets the max average bitrate, or undefined to leave as-is
}

type CodecParamMods = Record<string, CodecParams>;

export enum CallState {
Fledgling = 'fledgling',
InviteSent = 'invite_sent',
Expand Down Expand Up @@ -261,6 +269,18 @@ export function genCallID(): string {
return Date.now().toString() + randomString(16);
}

function getCodecParamMods(isPtt: boolean): CodecParamMods {
const mods = {
'opus': {
enableDtx: true,
},
} as CodecParamMods;

if (isPtt) mods.opus.maxAverageBitrate = 12000;

return mods;
}

export type CallEventHandlerMap = {
[CallEvent.DataChannel]: (channel: RTCDataChannel) => void;
[CallEvent.FeedsChanged]: (feeds: CallFeed[]) => void;
Expand Down Expand Up @@ -1456,26 +1476,59 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap

// Enables DTX (discontinuous transmission) on the given session to reduce
// bandwidth when transmitting silence
private enableDtx(description: RTCSessionDescriptionInit): void {
private mungeSdp(description: RTCSessionDescriptionInit, mods: CodecParamMods): void {
// The only way to enable DTX at this time is through SDP munging
const sdp = parseSdp(description.sdp);

sdp.media.forEach(media => {
if (media.type === "audio") {
media.fmtp.forEach(fmtp => fmtp.config += ";usedtx=1");
const payloadTypeToCodecMap = new Map<number, string>();
const codecToPayloadTypeMap = new Map<string, number>();
for (const rtp of media.rtp) {
payloadTypeToCodecMap.set(rtp.payload, rtp.codec);
codecToPayloadTypeMap.set(rtp.codec, rtp.payload);
}

for (const [codec, params] of Object.entries(mods)) {
if (!codecToPayloadTypeMap.has(codec)) {
logger.info(`Ignoring SDP modifications for ${codec} as it's not present.`);
continue;
}

const extraconfig = [] as string[];
dbkr marked this conversation as resolved.
Show resolved Hide resolved
if (params.enableDtx !== undefined) {
extraconfig.push(`usedtx=${params.enableDtx ? '1' : '0'}`);
}
if (params.maxAverageBitrate !== undefined) {
extraconfig.push(`maxaveragebitrate=${params.maxAverageBitrate}`);
}

let found = false;
for (const fmtp of media.fmtp) {
if (payloadTypeToCodecMap.get(fmtp.payload) === codec) {
found = true;
fmtp.config += ";" + extraconfig.join(";");
}
}
if (!found) {
media.fmtp.push({
payload: codecToPayloadTypeMap.get(codec),
config: extraconfig.join(";"),
});
}
}
});
description.sdp = writeSdp(sdp);
}

private async createOffer(): Promise<RTCSessionDescriptionInit> {
const offer = await this.peerConn.createOffer();
this.enableDtx(offer);
this.mungeSdp(offer, getCodecParamMods(this.isPtt));
return offer;
}

private async createAnswer(): Promise<RTCSessionDescriptionInit> {
const answer = await this.peerConn.createAnswer();
this.enableDtx(answer);
this.mungeSdp(answer, getCodecParamMods(this.isPtt));
return answer;
}

Expand Down