@@ -8,11 +8,20 @@ import { ddExtensionURI, isChromiumBased, isSVCCodec } from './utils';
88
99/** @internal */
1010interface TrackBitrateInfo {
11- sid : string ;
11+ cid ?: string ;
12+ transceiver ?: RTCRtpTransceiver ;
1213 codec : string ;
1314 maxbr : number ;
1415}
1516
17+ /* The svc codec (av1/vp9) would use a very low bitrate at the begining and
18+ increase slowly by the bandwidth estimator until it reach the target bitrate. The
19+ process commonly cost more than 10 seconds cause subscriber will get blur video at
20+ the first few seconds. So we use a 70% of target bitrate here as the start bitrate to
21+ eliminate this issue.
22+ */
23+ const startBitrateForSVC = 0.7 ;
24+
1625export const PCEvents = {
1726 NegotiationStarted : 'negotiationStarted' ,
1827 NegotiationComplete : 'negotiationComplete' ,
@@ -56,12 +65,65 @@ export default class PCTransport extends EventEmitter {
5665 }
5766
5867 async setRemoteDescription ( sd : RTCSessionDescriptionInit ) : Promise < void > {
68+ let mungedSDP : string | undefined = undefined ;
5969 if ( sd . type === 'offer' ) {
6070 let { stereoMids, nackMids } = extractStereoAndNackAudioFromOffer ( sd ) ;
6171 this . remoteStereoMids = stereoMids ;
6272 this . remoteNackMids = nackMids ;
73+ } else if ( sd . type === 'answer' ) {
74+ const sdpParsed = parse ( sd . sdp ?? '' ) ;
75+ sdpParsed . media . forEach ( ( media ) => {
76+ if ( media . type === 'audio' ) {
77+ // mung sdp for opus bitrate settings
78+ this . trackBitrates . some ( ( trackbr ) : boolean => {
79+ if ( ! trackbr . transceiver || media . mid != trackbr . transceiver . mid ) {
80+ return false ;
81+ }
82+
83+ let codecPayload = 0 ;
84+ media . rtp . some ( ( rtp ) : boolean => {
85+ if ( rtp . codec . toUpperCase ( ) === trackbr . codec . toUpperCase ( ) ) {
86+ codecPayload = rtp . payload ;
87+ return true ;
88+ }
89+ return false ;
90+ } ) ;
91+
92+ if ( codecPayload === 0 ) {
93+ return true ;
94+ }
95+
96+ let fmtpFound = false ;
97+ for ( const fmtp of media . fmtp ) {
98+ if ( fmtp . payload === codecPayload ) {
99+ fmtp . config = fmtp . config
100+ . split ( ';' )
101+ . filter ( ( attr ) => ! attr . includes ( 'maxaveragebitrate' ) )
102+ . join ( ';' ) ;
103+ if ( trackbr . maxbr > 0 ) {
104+ fmtp . config += `;maxaveragebitrate=${ trackbr . maxbr * 1000 } ` ;
105+ }
106+ fmtpFound = true ;
107+ break ;
108+ }
109+ }
110+
111+ if ( ! fmtpFound ) {
112+ if ( trackbr . maxbr > 0 ) {
113+ media . fmtp . push ( {
114+ payload : codecPayload ,
115+ config : `maxaveragebitrate=${ trackbr . maxbr * 1000 } ` ,
116+ } ) ;
117+ }
118+ }
119+
120+ return true ;
121+ } ) ;
122+ }
123+ } ) ;
124+ mungedSDP = write ( sdpParsed ) ;
63125 }
64- await this . pc . setRemoteDescription ( sd ) ;
126+ await this . setMungedSDP ( sd , mungedSDP , true ) ;
65127
66128 this . pendingCandidates . forEach ( ( candidate ) => {
67129 this . pc . addIceCandidate ( candidate ) ;
@@ -130,7 +192,7 @@ export default class PCTransport extends EventEmitter {
130192 ensureVideoDDExtensionForSVC ( media ) ;
131193 // mung sdp for codec bitrate setting that can't apply by sendEncoding
132194 this . trackBitrates . some ( ( trackbr ) : boolean => {
133- if ( ! media . msid || ! media . msid . includes ( trackbr . sid ) ) {
195+ if ( ! media . msid || ! trackbr . cid || ! media . msid . includes ( trackbr . cid ) ) {
134196 return false ;
135197 }
136198
@@ -143,39 +205,39 @@ export default class PCTransport extends EventEmitter {
143205 return false ;
144206 } ) ;
145207
146- // add x-google-max-bitrate to fmtp line if not exist
147- if ( codecPayload > 0 ) {
148- if (
149- ! media . fmtp . some ( ( fmtp ) : boolean => {
150- if ( fmtp . payload === codecPayload ) {
151- if ( ! fmtp . config . includes ( 'x-google-start-bitrate' ) ) {
152- fmtp . config += `;x-google-start-bitrate=${ trackbr . maxbr * 0.7 } ` ;
153- }
154- if ( ! fmtp . config . includes ( 'x-google-max-bitrate' ) ) {
155- fmtp . config += `;x-google-max-bitrate=${ trackbr . maxbr } ` ;
156- }
157- return true ;
158- }
159- return false ;
160- } )
161- ) {
162- media . fmtp . push ( {
163- payload : codecPayload ,
164- config : `x-google-start-bitrate=${ trackbr . maxbr * 0.7 } ;x-google-max-bitrate=${
165- trackbr . maxbr
166- } `,
167- } ) ;
208+ if ( codecPayload === 0 ) {
209+ return true ;
210+ }
211+
212+ let fmtpFound = false ;
213+ for ( const fmtp of media . fmtp ) {
214+ if ( fmtp . payload === codecPayload ) {
215+ if ( ! fmtp . config . includes ( 'x-google-start-bitrate' ) ) {
216+ fmtp . config += `;x-google-start-bitrate=${ trackbr . maxbr * startBitrateForSVC } ` ;
217+ }
218+ if ( ! fmtp . config . includes ( 'x-google-max-bitrate' ) ) {
219+ fmtp . config += `;x-google-max-bitrate=${ trackbr . maxbr } ` ;
220+ }
221+ fmtpFound = true ;
222+ break ;
168223 }
169224 }
170225
226+ if ( ! fmtpFound ) {
227+ media . fmtp . push ( {
228+ payload : codecPayload ,
229+ config : `x-google-start-bitrate=${
230+ trackbr . maxbr * startBitrateForSVC
231+ } ;x-google-max-bitrate=${ trackbr . maxbr } `,
232+ } ) ;
233+ }
234+
171235 return true ;
172236 } ) ;
173237 }
174238 } ) ;
175239
176- this . trackBitrates = [ ] ;
177-
178- await this . setMungedLocalDescription ( offer , write ( sdpParsed ) ) ;
240+ await this . setMungedSDP ( offer , write ( sdpParsed ) ) ;
179241 this . onOffer ( offer ) ;
180242 }
181243
@@ -187,16 +249,12 @@ export default class PCTransport extends EventEmitter {
187249 ensureAudioNackAndStereo ( media , this . remoteStereoMids , this . remoteNackMids ) ;
188250 }
189251 } ) ;
190- await this . setMungedLocalDescription ( answer , write ( sdpParsed ) ) ;
252+ await this . setMungedSDP ( answer , write ( sdpParsed ) ) ;
191253 return answer ;
192254 }
193255
194- setTrackCodecBitrate ( sid : string , codec : string , maxbr : number ) {
195- this . trackBitrates . push ( {
196- sid,
197- codec,
198- maxbr,
199- } ) ;
256+ setTrackCodecBitrate ( info : TrackBitrateInfo ) {
257+ this . trackBitrates . push ( info ) ;
200258 }
201259
202260 close ( ) {
@@ -205,22 +263,32 @@ export default class PCTransport extends EventEmitter {
205263 this . pc . close ( ) ;
206264 }
207265
208- private async setMungedLocalDescription ( sd : RTCSessionDescriptionInit , munged : string ) {
209- const originalSdp = sd . sdp ;
210- sd . sdp = munged ;
211- try {
212- log . debug ( 'setting munged local description' ) ;
213- await this . pc . setLocalDescription ( sd ) ;
214- return ;
215- } catch ( e ) {
216- log . warn ( `not able to set ${ sd . type } , falling back to unmodified sdp` , {
217- error : e ,
218- } ) ;
219- sd . sdp = originalSdp ;
266+ private async setMungedSDP ( sd : RTCSessionDescriptionInit , munged ?: string , remote ?: boolean ) {
267+ if ( munged ) {
268+ const originalSdp = sd . sdp ;
269+ sd . sdp = munged ;
270+ try {
271+ log . debug ( `setting munged ${ remote ? 'remote' : 'local' } description` ) ;
272+ if ( remote ) {
273+ await this . pc . setRemoteDescription ( sd ) ;
274+ } else {
275+ await this . pc . setLocalDescription ( sd ) ;
276+ }
277+ return ;
278+ } catch ( e ) {
279+ log . warn ( `not able to set ${ sd . type } , falling back to unmodified sdp` , {
280+ error : e ,
281+ } ) ;
282+ sd . sdp = originalSdp ;
283+ }
220284 }
221285
222286 try {
223- await this . pc . setLocalDescription ( sd ) ;
287+ if ( remote ) {
288+ await this . pc . setRemoteDescription ( sd ) ;
289+ } else {
290+ await this . pc . setLocalDescription ( sd ) ;
291+ }
224292 } catch ( e ) {
225293 // this error cannot always be caught.
226294 // If the local description has a setCodecPreferences error, this error will be uncaught
0 commit comments