@@ -93,6 +93,14 @@ interface AssertedIdentity {
93
93
displayName : string ;
94
94
}
95
95
96
+ // Used internally to specify modifications to codec parameters in SDP
97
+ interface CodecParams {
98
+ enableDtx ?: boolean ; // true to enable discontinuous transmission, false to disable, undefined to leave as-is
99
+ maxAverageBitrate ?: number ; // sets the max average bitrate, or undefined to leave as-is
100
+ }
101
+
102
+ type CodecParamMods = Record < string , CodecParams > ;
103
+
96
104
export enum CallState {
97
105
Fledgling = 'fledgling' ,
98
106
InviteSent = 'invite_sent' ,
@@ -261,6 +269,18 @@ export function genCallID(): string {
261
269
return Date . now ( ) . toString ( ) + randomString ( 16 ) ;
262
270
}
263
271
272
+ function getCodecParamMods ( isPtt : boolean ) : CodecParamMods {
273
+ const mods = {
274
+ 'opus' : {
275
+ enableDtx : true ,
276
+ } ,
277
+ } as CodecParamMods ;
278
+
279
+ if ( isPtt ) mods . opus . maxAverageBitrate = 12000 ;
280
+
281
+ return mods ;
282
+ }
283
+
264
284
export type CallEventHandlerMap = {
265
285
[ CallEvent . DataChannel ] : ( channel : RTCDataChannel ) => void ;
266
286
[ CallEvent . FeedsChanged ] : ( feeds : CallFeed [ ] ) => void ;
@@ -1456,26 +1476,59 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
1456
1476
1457
1477
// Enables DTX (discontinuous transmission) on the given session to reduce
1458
1478
// bandwidth when transmitting silence
1459
- private enableDtx ( description : RTCSessionDescriptionInit ) : void {
1479
+ private mungeSdp ( description : RTCSessionDescriptionInit , mods : CodecParamMods ) : void {
1460
1480
// The only way to enable DTX at this time is through SDP munging
1461
1481
const sdp = parseSdp ( description . sdp ) ;
1482
+
1462
1483
sdp . media . forEach ( media => {
1463
- if ( media . type === "audio" ) {
1464
- media . fmtp . forEach ( fmtp => fmtp . config += ";usedtx=1" ) ;
1484
+ const payloadTypeToCodecMap = new Map < number , string > ( ) ;
1485
+ const codecToPayloadTypeMap = new Map < string , number > ( ) ;
1486
+ for ( const rtp of media . rtp ) {
1487
+ payloadTypeToCodecMap . set ( rtp . payload , rtp . codec ) ;
1488
+ codecToPayloadTypeMap . set ( rtp . codec , rtp . payload ) ;
1489
+ }
1490
+
1491
+ for ( const [ codec , params ] of Object . entries ( mods ) ) {
1492
+ if ( ! codecToPayloadTypeMap . has ( codec ) ) {
1493
+ logger . info ( `Ignoring SDP modifications for ${ codec } as it's not present.` ) ;
1494
+ continue ;
1495
+ }
1496
+
1497
+ const extraconfig : string [ ] = [ ] ;
1498
+ if ( params . enableDtx !== undefined ) {
1499
+ extraconfig . push ( `usedtx=${ params . enableDtx ? '1' : '0' } ` ) ;
1500
+ }
1501
+ if ( params . maxAverageBitrate !== undefined ) {
1502
+ extraconfig . push ( `maxaveragebitrate=${ params . maxAverageBitrate } ` ) ;
1503
+ }
1504
+
1505
+ let found = false ;
1506
+ for ( const fmtp of media . fmtp ) {
1507
+ if ( payloadTypeToCodecMap . get ( fmtp . payload ) === codec ) {
1508
+ found = true ;
1509
+ fmtp . config += ";" + extraconfig . join ( ";" ) ;
1510
+ }
1511
+ }
1512
+ if ( ! found ) {
1513
+ media . fmtp . push ( {
1514
+ payload : codecToPayloadTypeMap . get ( codec ) ,
1515
+ config : extraconfig . join ( ";" ) ,
1516
+ } ) ;
1517
+ }
1465
1518
}
1466
1519
} ) ;
1467
1520
description . sdp = writeSdp ( sdp ) ;
1468
1521
}
1469
1522
1470
1523
private async createOffer ( ) : Promise < RTCSessionDescriptionInit > {
1471
1524
const offer = await this . peerConn . createOffer ( ) ;
1472
- this . enableDtx ( offer ) ;
1525
+ this . mungeSdp ( offer , getCodecParamMods ( this . isPtt ) ) ;
1473
1526
return offer ;
1474
1527
}
1475
1528
1476
1529
private async createAnswer ( ) : Promise < RTCSessionDescriptionInit > {
1477
1530
const answer = await this . peerConn . createAnswer ( ) ;
1478
- this . enableDtx ( answer ) ;
1531
+ this . mungeSdp ( answer , getCodecParamMods ( this . isPtt ) ) ;
1479
1532
return answer ;
1480
1533
}
1481
1534
0 commit comments