blob: cce2c1661f7d7761da12d2ee4ba7be1446fcfe9b [file] [log] [blame]
Florent Castelli9f783ba2023-05-16 13:09:151<!DOCTYPE html>
2<meta charset="utf-8">
3<title>RTCRtpEncodingParameters codec property</title>
4<script src="/resources/testharness.js"></script>
5<script src="/resources/testharnessreport.js"></script>
6<script src="../webrtc/RTCPeerConnection-helper.js"></script>
7<script src="../webrtc/third_party/sdp/sdp.js"></script>
8<script src="../webrtc/simulcast/simulcast.js"></script>
9<script>
10 'use strict';
11
Blink WPT Bot396bc8e2023-09-01 20:33:4612 function arrayEquals(a, b) {
13 return Array.isArray(a) && Array.isArray(b) &&
14 a.length === b.length &&
15 a.every((val, i) => val === b[i]);
16 }
17
18 async function sleep(timeout) {
19 return new Promise(resolve => {
20 step_timeout(() => {
21 resolve();
22 }, timeout);
23 });
24 }
25
Florent Castelli9f783ba2023-05-16 13:09:1526 function findFirstCodec(name) {
27 return RTCRtpSender.getCapabilities(name.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(name, undefined, { sensitivity: 'base' }) === 0)[0];
28 }
29
30 function codecsNotMatching(mimeType) {
31 return RTCRtpSender.getCapabilities(mimeType.split('/')[0]).codecs.filter(c => c.mimeType.localeCompare(mimeType, undefined, {sensitivity: 'base'}) !== 0);
32 }
33
34 function assertCodecEquals(a, b) {
35 assert_equals(a.mimeType, b.mimeType);
36 assert_equals(a.clockRate, b.clockRate);
37 assert_equals(a.channels, b.channels);
38 assert_equals(a.sdpFmtpLine, b.sdpFmtpLine);
39 }
40
41 async function codecsForSender(sender) {
42 const rids = sender.getParameters().encodings.map(e => e.rid);
43 const stats = await sender.getStats();
44 const codecs = [...stats]
45 .filter(([k, v]) => v.type === 'outbound-rtp')
46 .sort(([k, v], [k2, v2]) => rids.indexOf(v.rid) - rids.indexOf(v2.rid))
47 .map(([k, v]) => stats.get(v.codecId).mimeType);
48 return codecs;
49 }
50
Blink WPT Bot396bc8e2023-09-01 20:33:4651 async function waitForAllLayers(t, sender) {
52 const encodings_count = sender.getParameters().encodings.length;
53 return step_wait_async(t, async () => {
54 const stats = await sender.getStats();
55 return [...stats]
56 .filter(([k, v]) => v.type === 'outbound-rtp').length == encodings_count;
57 }, `Wait for ${encodings_count} layers to start`);
58 }
59
60 function step_wait_async(t, cond, description, timeout=3000, interval=100) {
61 return new Promise(resolve => {
62 var timeout_full = timeout * t.timeout_multiplier;
63 var remaining = Math.ceil(timeout_full / interval);
64
65 var wait_for_inner = t.step_func(async () => {
66 if (await cond()) {
67 resolve();
68 } else {
69 if(remaining === 0) {
70 assert(false, "step_wait_async", description,
71 "Timed out waiting on condition");
72 }
73 remaining--;
74 await sleep(interval);
75 wait_for_inner();
76 }
77 });
78
79 wait_for_inner();
80 });
81 }
82
Florent Castelli9f783ba2023-05-16 13:09:1583 promise_test(async t => {
84 const pc = new RTCPeerConnection();
85 t.add_cleanup(() => pc.close());
86
87 const { sender } = pc.addTransceiver('audio');
88
89 let param = sender.getParameters();
90 let encoding = param.encodings[0];
91
92 assert_equals(encoding.codec, undefined);
93 }, `Codec should be undefined by default on audio encodings`);
94
95 promise_test(async t => {
96 const pc = new RTCPeerConnection();
97 t.add_cleanup(() => pc.close());
98
99 const { sender } = pc.addTransceiver('video');
100
101 let param = sender.getParameters();
102 let encoding = param.encodings[0];
103
104 assert_equals(encoding.codec, undefined);
105 }, `Codec should be undefined by default on video encodings`);
106
107 promise_test(async t => {
108 const pc = new RTCPeerConnection();
109 t.add_cleanup(() => pc.close());
110
111 const opus = findFirstCodec('audio/opus');
112
113 const { sender } = pc.addTransceiver('audio', {
114 sendEncodings: [{codec: opus}],
115 });
116
117 let param = sender.getParameters();
118 let encoding = param.encodings[0];
119
120 assertCodecEquals(opus, encoding.codec);
121 }, `Creating an audio sender with addTransceiver and codec should work`);
122
123 promise_test(async t => {
124 const pc = new RTCPeerConnection();
125 t.add_cleanup(() => pc.close());
126
127 const vp8 = findFirstCodec('video/VP8');
128
129 const { sender } = pc.addTransceiver('video', {
130 sendEncodings: [{codec: vp8}],
131 });
132
133 let param = sender.getParameters();
134 let encoding = param.encodings[0];
135
136 assertCodecEquals(vp8, encoding.codec);
137 }, `Creating a video sender with addTransceiver and codec should work`);
138
139 promise_test(async t => {
140 const pc = new RTCPeerConnection();
141 t.add_cleanup(() => pc.close());
142
143 const opus = findFirstCodec('audio/opus');
144
145 const { sender } = pc.addTransceiver('audio');
146
147 let param = sender.getParameters();
148 let encoding = param.encodings[0];
149
150 encoding.codec = opus;
151 await sender.setParameters(param);
152 param = sender.getParameters();
153 encoding = param.encodings[0];
154
155 assertCodecEquals(opus, encoding.codec);
156
157 delete encoding.codec;
158 await sender.setParameters(param);
159 param = sender.getParameters();
160 encoding = param.encodings[0];
161
162 assert_equals(encoding.codec, undefined);
163 }, `Setting codec on an audio sender with setParameters should work`);
164
165 promise_test(async t => {
166 const pc = new RTCPeerConnection();
167 t.add_cleanup(() => pc.close());
168
169 const vp8 = findFirstCodec('video/VP8');
170
171 const { sender } = pc.addTransceiver('video');
172
173 let param = sender.getParameters();
174 let encoding = param.encodings[0];
175
176 encoding.codec = vp8;
177 await sender.setParameters(param);
178 param = sender.getParameters();
179 encoding = param.encodings[0];
180
181 assertCodecEquals(vp8, encoding.codec);
182
183 delete encoding.codec;
184 await sender.setParameters(param);
185 param = sender.getParameters();
186 encoding = param.encodings[0];
187
188 assert_equals(encoding.codec, undefined);
189 }, `Setting codec on a video sender with setParameters should work`);
190
191 promise_test(async t => {
192 const pc = new RTCPeerConnection();
193 t.add_cleanup(() => pc.close());
194
195 const newCodec = {
196 mimeType: "audio/newCodec",
197 clockRate: 90000,
198 channel: 2,
199 };
200
Blink WPT Bot396bc8e2023-09-01 20:33:46201 assert_throws_dom('OperationError', () => pc.addTransceiver('audio', {
Florent Castelli9f783ba2023-05-16 13:09:15202 sendEncodings: [{codec: newCodec}],
203 }));
204 }, `Creating an audio sender with addTransceiver and non-existing codec should throw OperationError`);
205
206 promise_test(async t => {
207 const pc = new RTCPeerConnection();
208 t.add_cleanup(() => pc.close());
209
210 const newCodec = {
Blink WPT Bot396bc8e2023-09-01 20:33:46211 mimeType: "dummy/newCodec",
212 clockRate: 90000,
213 channel: 2,
214 };
215
216 assert_throws_dom('OperationError', () => pc.addTransceiver('audio', {
217 sendEncodings: [{codec: newCodec}],
218 }));
219 }, `Creating an audio sender with addTransceiver and non-existing codec type should throw OperationError`);
220
221 promise_test(async t => {
222 const pc = new RTCPeerConnection();
223 t.add_cleanup(() => pc.close());
224
225 const newCodec = {
Florent Castelli9f783ba2023-05-16 13:09:15226 mimeType: "video/newCodec",
227 clockRate: 90000,
228 };
229
230 assert_throws_dom('OperationError', () => pc.addTransceiver('video', {
231 sendEncodings: [{codec: newCodec}],
232 }));
233 }, `Creating a video sender with addTransceiver and non-existing codec should throw OperationError`);
234
235 promise_test(async t => {
236 const pc = new RTCPeerConnection();
237 t.add_cleanup(() => pc.close());
238
239 const newCodec = {
Blink WPT Bot396bc8e2023-09-01 20:33:46240 mimeType: "dummy/newCodec",
241 clockRate: 90000,
242 };
243
244 assert_throws_dom('OperationError', () => pc.addTransceiver('video', {
245 sendEncodings: [{codec: newCodec}],
246 }));
247 }, `Creating a video sender with addTransceiver and non-existing codec type should throw OperationError`);
248
249 promise_test(async t => {
250 const pc = new RTCPeerConnection();
251 t.add_cleanup(() => pc.close());
252
253 const newCodec = {
Florent Castelli9f783ba2023-05-16 13:09:15254 mimeType: "audio/newCodec",
255 clockRate: 90000,
256 channel: 2,
257 };
258
259 const { sender } = pc.addTransceiver('audio');
260
261 let param = sender.getParameters();
262 let encoding = param.encodings[0];
263
264 encoding.codec = newCodec;
265 await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
266 }, `Setting a non-existing codec on an audio sender with setParameters should throw InvalidModificationError`);
267
268 promise_test(async t => {
269 const pc = new RTCPeerConnection();
270 t.add_cleanup(() => pc.close());
271
272 const newCodec = {
273 mimeType: "video/newCodec",
274 clockRate: 90000,
275 };
276
277 const { sender } = pc.addTransceiver('video');
278
279 let param = sender.getParameters();
280 let encoding = param.encodings[0];
281
282 encoding.codec = newCodec;
283 await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
284 }, `Setting a non-existing codec on a video sender with setParameters should throw InvalidModificationError`);
285
286 promise_test(async t => {
287 const pc = new RTCPeerConnection();
288 t.add_cleanup(() => pc.close());
289
290 const opus = findFirstCodec('audio/opus');
291 const nonOpus = codecsNotMatching(opus.mimeType);
292
293 const transceiver = pc.addTransceiver('audio');
294 const sender = transceiver.sender;
295
296 transceiver.setCodecPreferences(nonOpus);
297
298 let param = sender.getParameters();
299 let encoding = param.encodings[0];
300
301 encoding.codec = opus;
302 await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
303 }, `Setting a non-preferred codec on an audio sender with setParameters should throw InvalidModificationError`);
304
305 promise_test(async t => {
306 const pc = new RTCPeerConnection();
307 t.add_cleanup(() => pc.close());
308
309 const vp8 = findFirstCodec('video/VP8');
310 const nonVP8 = codecsNotMatching(vp8.mimeType);
311
312 const transceiver = pc.addTransceiver('video');
313 const sender = transceiver.sender;
314
315 transceiver.setCodecPreferences(nonVP8);
316
317 let param = sender.getParameters();
318 let encoding = param.encodings[0];
319
320 encoding.codec = vp8;
321 await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
322 }, `Setting a non-preferred codec on a video sender with setParameters should throw InvalidModificationError`);
323
324 promise_test(async (t) => {
325 const pc1 = new RTCPeerConnection();
326 const pc2 = new RTCPeerConnection();
327 t.add_cleanup(() => pc1.close());
328 t.add_cleanup(() => pc2.close());
329
330 const opus = findFirstCodec('audio/opus');
331 const nonOpus = codecsNotMatching(opus.mimeType);
332
333 const transceiver = pc1.addTransceiver('audio');
334 const sender = transceiver.sender;
335
336 transceiver.setCodecPreferences(nonOpus);
337
338 exchangeIceCandidates(pc1, pc2);
339 await exchangeOfferAnswer(pc1, pc2);
340
341 let param = sender.getParameters();
342 let encoding = param.encodings[0];
343
344 encoding.codec = opus;
345 await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
346 }, `Setting a non-negotiated codec on an audio sender with setParameters should throw InvalidModificationError`);
347
348 promise_test(async (t) => {
349 const pc1 = new RTCPeerConnection();
350 const pc2 = new RTCPeerConnection();
351 t.add_cleanup(() => pc1.close());
352 t.add_cleanup(() => pc2.close());
353
354 const vp8 = findFirstCodec('video/VP8');
355 const nonVP8 = codecsNotMatching(vp8.mimeType);
356
357 const transceiver = pc1.addTransceiver('video');
358 const sender = transceiver.sender;
359
360 transceiver.setCodecPreferences(nonVP8);
361
362 exchangeIceCandidates(pc1, pc2);
363 await exchangeOfferAnswer(pc1, pc2);
364
365 let param = sender.getParameters();
366 let encoding = param.encodings[0];
367
368 encoding.codec = vp8;
369 await promise_rejects_dom(t, "InvalidModificationError", sender.setParameters(param));
370 }, `Setting a non-negotiated codec on a video sender with setParameters should throw InvalidModificationError`);
371
372 promise_test(async (t) => {
373 const pc1 = new RTCPeerConnection();
374 const pc2 = new RTCPeerConnection();
375 t.add_cleanup(() => pc1.close());
376 t.add_cleanup(() => pc2.close());
377
378 const opus = findFirstCodec('audio/opus');
379 const nonOpus = codecsNotMatching(opus.mimeType);
380
381 const transceiver = pc1.addTransceiver('audio', {
382 sendEncodings: [{codec: opus}],
383 });
384 const sender = transceiver.sender;
385
386 exchangeIceCandidates(pc1, pc2);
387 await exchangeOfferAnswer(pc1, pc2);
388
389 let param = sender.getParameters();
390 let encoding = param.encodings[0];
391
392 assertCodecEquals(opus, encoding.codec);
393
394 transceiver.setCodecPreferences(nonOpus);
395 await exchangeOfferAnswer(pc1, pc2);
396
397 param = sender.getParameters();
398 encoding = param.encodings[0];
399
400 assert_equals(encoding.codec, undefined);
401 }, `Codec should be undefined after negotiating away the currently set codec on an audio sender`);
402
403 promise_test(async (t) => {
404 const pc1 = new RTCPeerConnection();
405 const pc2 = new RTCPeerConnection();
406 t.add_cleanup(() => pc1.close());
407 t.add_cleanup(() => pc2.close());
408
409 const vp8 = findFirstCodec('video/VP8');
410 const nonVP8 = codecsNotMatching(vp8.mimeType);
411
412 const transceiver = pc1.addTransceiver('video', {
413 sendEncodings: [{codec: vp8}],
414 });
415 const sender = transceiver.sender;
416
417 exchangeIceCandidates(pc1, pc2);
418 await exchangeOfferAnswer(pc1, pc2);
419
420 let param = sender.getParameters();
421 let encoding = param.encodings[0];
422
423 assertCodecEquals(vp8, encoding.codec);
424
425 transceiver.setCodecPreferences(nonVP8);
426 await exchangeOfferAnswer(pc1, pc2);
427
428 param = sender.getParameters();
429 encoding = param.encodings[0];
430
431 assert_equals(encoding.codec, undefined);
432 }, `Codec should be undefined after negotiating away the currently set codec on a video sender`);
433
434 promise_test(async (t) => {
435 const pc1 = new RTCPeerConnection();
436 const pc2 = new RTCPeerConnection();
437 t.add_cleanup(() => pc1.close());
438 t.add_cleanup(() => pc2.close());
439 const stream = await getNoiseStream({audio:true});
440 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
441
442 const opus = findFirstCodec('audio/opus');
443 const nonOpus = codecsNotMatching(opus.mimeType);
444
445 const transceiver = pc1.addTransceiver(stream.getTracks()[0]);
446 const sender = transceiver.sender;
447
448 transceiver.setCodecPreferences(nonOpus.concat([opus]));
449
450 exchangeIceCandidates(pc1, pc2);
451 await exchangeOfferAnswer(pc1, pc2);
452
453 let codecs = await codecsForSender(sender);
454 assert_not_equals(codecs[0], opus.mimeType);
455
456 let param = sender.getParameters();
457 let encoding = param.encodings[0];
458 encoding.codec = opus;
459
460 await sender.setParameters(param);
461
Blink WPT Bot396bc8e2023-09-01 20:33:46462 await step_wait_async(t, async () => {
463 let old_codecs = codecs;
464 codecs = await codecsForSender(sender);
465 return !arrayEquals(codecs, old_codecs);
466 }, 'Waiting for current codecs to change', 5000, 200);
467
Florent Castelli9f783ba2023-05-16 13:09:15468 assert_array_equals(codecs, [opus.mimeType]);
469 }, `Stats output-rtp should match the selected codec in non-simulcast usecase on an audio sender`);
470
471 promise_test(async (t) => {
472 const pc1 = new RTCPeerConnection();
473 const pc2 = new RTCPeerConnection();
474 t.add_cleanup(() => pc1.close());
475 t.add_cleanup(() => pc2.close());
476 const stream = await getNoiseStream({video:true});
477 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
478
479 const vp8 = findFirstCodec('video/VP8');
480 const nonVP8 = codecsNotMatching(vp8.mimeType);
481
482 const transceiver = pc1.addTransceiver(stream.getTracks()[0]);
483 const sender = transceiver.sender;
484
485 transceiver.setCodecPreferences(nonVP8.concat([vp8]));
486
487 exchangeIceCandidates(pc1, pc2);
488 await exchangeOfferAnswer(pc1, pc2);
489
490 let codecs = await codecsForSender(sender);
491 assert_not_equals(codecs[0], vp8.mimeType);
492
493 let param = sender.getParameters();
494 let encoding = param.encodings[0];
495 encoding.codec = vp8;
496
497 await sender.setParameters(param);
498
Blink WPT Bot396bc8e2023-09-01 20:33:46499 await step_wait_async(t, async () => {
500 let old_codecs = codecs;
501 codecs = await codecsForSender(sender);
502 return !arrayEquals(codecs, old_codecs);
503 }, 'Waiting for current codecs to change', 5000, 200);
504
Florent Castelli9f783ba2023-05-16 13:09:15505 assert_array_equals(codecs, [vp8.mimeType]);
506 }, `Stats output-rtp should match the selected codec in non-simulcast usecase on a video sender`);
Blink WPT Bot396bc8e2023-09-01 20:33:46507
508 promise_test(async (t) => {
509 const pc1 = new RTCPeerConnection();
510 const pc2 = new RTCPeerConnection();
511 t.add_cleanup(() => pc1.close());
512 t.add_cleanup(() => pc2.close());
513 const stream = await getNoiseStream({video:true});
514 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
515
516 const vp8 = findFirstCodec('video/VP8');
517 const nonVP8 = codecsNotMatching(vp8.mimeType);
518
519 const transceiver = pc1.addTransceiver(stream.getTracks()[0], {
520 sendEncodings: [{rid: '0'}, {rid: '1'}, {rid: '2'}],
521 });
522 const sender = transceiver.sender;
523
524 transceiver.setCodecPreferences(nonVP8.concat([vp8]));
525
526 exchangeIceCandidates(pc1, pc2);
527 await doOfferToSendSimulcastAndAnswer(pc1, pc2, ['0', '1', '2']);
528
529 await waitForAllLayers(t, sender);
530
531 let codecs = await codecsForSender(sender);
532 assert_not_equals(codecs[0], vp8.mimeType);
533 assert_not_equals(codecs[1], vp8.mimeType);
534 assert_not_equals(codecs[2], vp8.mimeType);
535
536 let param = sender.getParameters();
537 param.encodings[0].codec = vp8;
538 param.encodings[1].codec = vp8;
539 param.encodings[2].codec = vp8;
540
541 await sender.setParameters(param);
542
543 // Waiting for 10s as ramp-up time can be slow in the runners.
544 await step_wait_async(t, async () => {
545 let old_codecs = codecs;
546 codecs = await codecsForSender(sender);
547 return !arrayEquals(codecs, old_codecs);
548 }, 'Waiting for current codecs to change', 10000, 200);
549
550 assert_array_equals(codecs, [vp8.mimeType, vp8.mimeType, vp8.mimeType]);
551 }, `Stats output-rtp should match the selected codec in simulcast usecase on a video sender`);
552
553 promise_test(async (t) => {
554 const pc1 = new RTCPeerConnection();
555 const pc2 = new RTCPeerConnection();
556 t.add_cleanup(() => pc1.close());
557 t.add_cleanup(() => pc2.close());
558 const stream = await getNoiseStream({video:true});
559 t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
560
561 const vp8 = findFirstCodec('video/VP8');
562 const nonVP8 = codecsNotMatching(vp8.mimeType);
563
564 const transceiver = pc1.addTransceiver(stream.getTracks()[0], {
565 sendEncodings: [{rid: '0'}, {rid: '1'}, {rid: '2'}],
566 });
567 const sender = transceiver.sender;
568
569 transceiver.setCodecPreferences(nonVP8.concat([vp8]));
570
571 exchangeIceCandidates(pc1, pc2);
572 await doOfferToSendSimulcastAndAnswer(pc1, pc2, ['0', '1', '2']);
573
574 await waitForAllLayers(t, sender);
575
576 let codecs = await codecsForSender(sender);
577 assert_not_equals(codecs[0], vp8.mimeType);
578 assert_not_equals(codecs[1], vp8.mimeType);
579 assert_not_equals(codecs[2], vp8.mimeType);
580
581 let param = sender.getParameters();
582 param.encodings[1].codec = vp8;
583
584 await sender.setParameters(param);
585
586 await step_wait_async(t, async () => {
587 let old_codecs = codecs;
588 codecs = await codecsForSender(sender);
589 return !arrayEquals(codecs, old_codecs);
590 }, 'Waiting for current codecs to change', 5000, 200);
591
592 assert_not_equals(codecs[0], vp8.mimeType);
593 assert_equals(codecs[1], vp8.mimeType);
594 assert_not_equals(codecs[2], vp8.mimeType);
595 }, `Stats output-rtp should match the selected mixed codecs in simulcast usecase on a video sender`);
596
Florent Castelli9f783ba2023-05-16 13:09:15597</script>