diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index f56d5342fdc..c1161f333a2 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -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; + export enum CallState { Fledgling = 'fledgling', InviteSent = 'invite_sent', @@ -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; @@ -1456,12 +1476,45 @@ export class MatrixCall extends TypedEventEmitter { - if (media.type === "audio") { - media.fmtp.forEach(fmtp => fmtp.config += ";usedtx=1"); + const payloadTypeToCodecMap = new Map(); + const codecToPayloadTypeMap = new Map(); + 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: string[] = []; + 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); @@ -1469,13 +1522,13 @@ export class MatrixCall extends TypedEventEmitter { const offer = await this.peerConn.createOffer(); - this.enableDtx(offer); + this.mungeSdp(offer, getCodecParamMods(this.isPtt)); return offer; } private async createAnswer(): Promise { const answer = await this.peerConn.createAnswer(); - this.enableDtx(answer); + this.mungeSdp(answer, getCodecParamMods(this.isPtt)); return answer; }