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