RTCRtpParameters-encodings.html (22864B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTCRtpParameters encodings</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="dictionary-helper.js"></script> 7 <script src="RTCRtpParameters-helper.js"></script> 8 <script> 9 'use strict'; 10 11 // Test is based on the following editor draft: 12 // https://w3c.github.io/webrtc-pc/archives/20170605/webrtc.html 13 14 // The following helper functions are called from RTCRtpParameters-helper.js: 15 // validateSenderRtpParameters 16 17 /* 18 5.1. RTCPeerConnection Interface Extensions 19 partial interface RTCPeerConnection { 20 RTCRtpTransceiver addTransceiver((MediaStreamTrack or DOMString) trackOrKind, 21 optional RTCRtpTransceiverInit init); 22 ... 23 }; 24 25 dictionary RTCRtpTransceiverInit { 26 RTCRtpTransceiverDirection direction = "sendrecv"; 27 sequence<MediaStream> streams; 28 sequence<RTCRtpEncodingParameters> sendEncodings; 29 }; 30 31 5.2. RTCRtpSender Interface 32 interface RTCRtpSender { 33 Promise<void> setParameters(optional RTCRtpParameters parameters); 34 RTCRtpParameters getParameters(); 35 }; 36 37 dictionary RTCRtpParameters { 38 DOMString transactionId; 39 sequence<RTCRtpEncodingParameters> encodings; 40 sequence<RTCRtpHeaderExtensionParameters> headerExtensions; 41 RTCRtcpParameters rtcp; 42 sequence<RTCRtpCodecParameters> codecs; 43 }; 44 45 dictionary RTCRtpEncodingParameters { 46 boolean active; 47 unsigned long maxBitrate; 48 49 [readonly] 50 DOMString rid; 51 52 double scaleResolutionDownBy; 53 }; 54 55 getParameters 56 - encodings is set to the value of the [[send encodings]] internal slot. 57 */ 58 59 promise_test(async t => { 60 const pc = new RTCPeerConnection(); 61 t.add_cleanup(() => pc.close()); 62 const transceiver = pc.addTransceiver('video'); 63 64 const param = transceiver.sender.getParameters(); 65 assert_equals(param.encodings.length, 1); 66 // Do not call this in every test; it does not make sense to disable all of 67 // the tests below for an implementation that is missing support for 68 // fields that are not related to the test. 69 validateSenderRtpParameters(param); 70 }, `getParameters should return RTCRtpEncodingParameters with all required fields`); 71 72 /* 73 5.1. addTransceiver 74 7. Create an RTCRtpSender with track, streams and sendEncodings and let sender 75 be the result. 76 77 5.2. create an RTCRtpSender 78 5. Let sender have a [[send encodings]] internal slot, representing a list 79 of RTCRtpEncodingParameters dictionaries. 80 6. If sendEncodings is given as input to this algorithm, and is non-empty, 81 set the [[send encodings]] slot to sendEncodings. 82 83 Otherwise, set it to a list containing a single RTCRtpEncodingParameters 84 with active set to true. 85 */ 86 promise_test(async t => { 87 const pc = new RTCPeerConnection(); 88 t.add_cleanup(() => pc.close()); 89 const transceiver = pc.addTransceiver('audio'); 90 91 const param = transceiver.sender.getParameters(); 92 const { encodings } = param; 93 assert_equals(encodings.length, 1); 94 const encoding = param.encodings[0]; 95 96 assert_equals(encoding.active, true); 97 assert_not_own_property(encoding, "maxBitrate"); 98 assert_not_own_property(encoding, "rid"); 99 assert_not_own_property(encoding, "scaleResolutionDownBy"); 100 // We do not check props from extension specifications here; those checks 101 // need to go in a test-case for that extension specification. 102 }, 'addTransceiver(audio) with undefined sendEncodings should have default encoding parameter with active set to true'); 103 104 promise_test(async t => { 105 const pc = new RTCPeerConnection(); 106 t.add_cleanup(() => pc.close()); 107 const transceiver = pc.addTransceiver('video'); 108 109 const param = transceiver.sender.getParameters(); 110 const { encodings } = param; 111 assert_equals(encodings.length, 1); 112 const encoding = param.encodings[0]; 113 114 assert_equals(encoding.active, true); 115 // spec says to return an encoding without a scaleResolutionDownBy value 116 // when addTransceiver does not pass any encodings, however spec also says 117 // to throw if setParameters is missing a scaleResolutionDownBy. One of 118 // these two requirements needs to be removed, but it is unclear right now 119 // which will be removed. For now, allow scaleResolutionDownBy, but don't 120 // require it. 121 // https://github.com/w3c/webrtc-pc/issues/2730 122 assert_not_own_property(encoding, "maxBitrate"); 123 assert_not_own_property(encoding, "rid"); 124 assert_equals(encoding.scaleResolutionDownBy, 1.0); 125 // We do not check props from extension specifications here; those checks 126 // need to go in a test-case for that extension specification. 127 }, 'addTransceiver(video) with undefined sendEncodings should have default encoding parameter with active set to true and scaleResolutionDownBy set to 1'); 128 129 promise_test(async t => { 130 const pc = new RTCPeerConnection(); 131 t.add_cleanup(() => pc.close()); 132 const transceiver = pc.addTransceiver('audio', { sendEncodings: [] }); 133 134 const param = transceiver.sender.getParameters(); 135 const { encodings } = param; 136 assert_equals(encodings.length, 1); 137 const encoding = param.encodings[0]; 138 139 assert_equals(encoding.active, true); 140 assert_not_own_property(encoding, "maxBitrate"); 141 assert_not_own_property(encoding, "rid"); 142 assert_not_own_property(encoding, "scaleResolutionDownBy"); 143 // We do not check props from extension specifications here; those checks 144 // need to go in a test-case for that extension specification. 145 }, 'addTransceiver(audio) with empty list sendEncodings should have default encoding parameter with active set to true'); 146 147 promise_test(async t => { 148 const pc = new RTCPeerConnection(); 149 t.add_cleanup(() => pc.close()); 150 const transceiver = pc.addTransceiver('video', { sendEncodings: [] }); 151 152 const param = transceiver.sender.getParameters(); 153 const { encodings } = param; 154 assert_equals(encodings.length, 1); 155 const encoding = param.encodings[0]; 156 157 assert_equals(encoding.active, true); 158 assert_not_own_property(encoding, "maxBitrate"); 159 assert_not_own_property(encoding, "rid"); 160 assert_equals(encoding.scaleResolutionDownBy, 1.0); 161 // We do not check props from extension specifications here; those checks 162 // need to go in a test-case for that extension specification. 163 }, 'addTransceiver(video) with empty list sendEncodings should have default encoding parameter with active set to true and scaleResolutionDownBy set to 1'); 164 165 promise_test(async t => { 166 const pc = new RTCPeerConnection(); 167 t.add_cleanup(() => pc.close()); 168 const transceiver = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar", scaleResolutionDownBy: 3.0}]}); 169 170 const param = transceiver.sender.getParameters(); 171 const { encodings } = param; 172 assert_equals(encodings.length, 2); 173 assert_equals(encodings[0].scaleResolutionDownBy, 1.0); 174 assert_equals(encodings[1].scaleResolutionDownBy, 3.0); 175 }, `addTransceiver(video) should auto-set scaleResolutionDownBy to 1 when some encodings have it, but not all`); 176 177 promise_test(async t => { 178 const pc = new RTCPeerConnection(); 179 t.add_cleanup(() => pc.close()); 180 const transceiver = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]}); 181 182 const param = transceiver.sender.getParameters(); 183 const { encodings } = param; 184 assert_equals(encodings.length, 2); 185 assert_equals(encodings[0].scaleResolutionDownBy, 2.0); 186 assert_equals(encodings[1].scaleResolutionDownBy, 1.0); 187 }, `addTransceiver should auto-set scaleResolutionDownBy to powers of 2 (descending) when absent`); 188 189 promise_test(async t => { 190 const pc = new RTCPeerConnection(); 191 t.add_cleanup(() => pc.close()); 192 const sendEncodings = []; 193 for (let i = 0; i < 1000; i++) { 194 sendEncodings.push({rid: i}); 195 } 196 const transceiver = pc.addTransceiver('video', {sendEncodings}); 197 198 const param = transceiver.sender.getParameters(); 199 const { encodings } = param; 200 assert_less_than(encodings.length, 1000, `1000 encodings is clearly too many`); 201 }, `addTransceiver with a ridiculous number of encodings should truncate the list`); 202 203 promise_test(async t => { 204 const pc = new RTCPeerConnection(); 205 t.add_cleanup(() => pc.close()); 206 const transceiver = pc.addTransceiver('audio', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]}); 207 208 const param = transceiver.sender.getParameters(); 209 const { encodings } = param; 210 assert_equals(encodings.length, 1); 211 assert_not_own_property(encodings[0], "maxBitrate"); 212 assert_not_own_property(encodings[0], "rid"); 213 assert_not_own_property(encodings[0], "scaleResolutionDownBy"); 214 // We do not check props from extension specifications here; those checks 215 // need to go in a test-case for that extension specification. 216 }, `addTransceiver(audio) with multiple encodings should result in one encoding with no properties other than active`); 217 218 promise_test(async t => { 219 const pc = new RTCPeerConnection(); 220 t.add_cleanup(() => pc.close()); 221 const {sender} = pc.addTransceiver('audio', {sendEncodings: [{rid: "foo", scaleResolutionDownBy: 2.0}]}); 222 const {encodings} = sender.getParameters(); 223 assert_equals(encodings.length, 1); 224 assert_not_own_property(encodings[0], "scaleResolutionDownBy"); 225 }, `addTransceiver(audio) should remove valid scaleResolutionDownBy`); 226 227 promise_test(async t => { 228 const pc = new RTCPeerConnection(); 229 t.add_cleanup(() => pc.close()); 230 const {sender} = pc.addTransceiver('audio', {sendEncodings: [{rid: "foo", scaleResolutionDownBy: -1.0}]}); 231 const {encodings} = sender.getParameters(); 232 assert_equals(encodings.length, 1); 233 assert_not_own_property(encodings[0], "scaleResolutionDownBy"); 234 }, `addTransceiver(audio) should remove invalid scaleResolutionDownBy`); 235 236 promise_test(async t => { 237 const pc = new RTCPeerConnection(); 238 t.add_cleanup(() => pc.close()); 239 const {sender} = pc.addTransceiver('audio'); 240 let params = sender.getParameters(); 241 assert_equals(params.encodings.length, 1); 242 params.encodings[0].scaleResolutionDownBy = 2; 243 await sender.setParameters(params); 244 const {encodings} = sender.getParameters(); 245 assert_equals(encodings.length, 1); 246 assert_not_own_property(encodings[0], "scaleResolutionDownBy"); 247 }, `setParameters with scaleResolutionDownBy on an audio sender should succeed, but remove the scaleResolutionDownBy`); 248 249 promise_test(async t => { 250 const pc = new RTCPeerConnection(); 251 t.add_cleanup(() => pc.close()); 252 const {sender} = pc.addTransceiver('audio'); 253 let params = sender.getParameters(); 254 assert_equals(params.encodings.length, 1); 255 params.encodings[0].scaleResolutionDownBy = -1; 256 await sender.setParameters(params); 257 const {encodings} = sender.getParameters(); 258 assert_equals(encodings.length, 1); 259 assert_not_own_property(encodings[0], "scaleResolutionDownBy"); 260 }, `setParameters with an invalid scaleResolutionDownBy on an audio sender should succeed, but remove the scaleResolutionDownBy`); 261 262 promise_test(async t => { 263 const pc = new RTCPeerConnection(); 264 t.add_cleanup(() => pc.close()); 265 266 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo"}, {rid: "foo"}] })); 267 }, 'addTransceiver with duplicate rid and multiple encodings throws TypeError'); 268 269 promise_test(async t => { 270 const pc = new RTCPeerConnection(); 271 t.add_cleanup(() => pc.close()); 272 273 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo"}, {}] })); 274 }, 'addTransceiver with missing rid and multiple encodings throws TypeError'); 275 276 promise_test(async t => { 277 const pc = new RTCPeerConnection(); 278 t.add_cleanup(() => pc.close()); 279 280 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: ""}] })); 281 }, 'addTransceiver with empty rid throws TypeError'); 282 283 promise_test(async t => { 284 const pc = new RTCPeerConnection(); 285 t.add_cleanup(() => pc.close()); 286 287 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "!?"}] })); 288 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "(╯°□°)╯︵ ┻━┻"}] })); 289 // RFC 8851 says '-' and '_' are allowed, but RFC 8852 says they are not. 290 // RFC 8852 needs to be adhered to, otherwise we can't put the rid in RTP 291 // https://github.com/w3c/webrtc-pc/issues/2732 292 // https://www.rfc-editor.org/errata/eid7132 293 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo-bar"}] })); 294 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: "foo_bar"}] })); 295 }, 'addTransceiver with invalid rid characters throws TypeError'); 296 297 promise_test(async t => { 298 const pc = new RTCPeerConnection(); 299 t.add_cleanup(() => pc.close()); 300 301 // https://github.com/w3c/webrtc-pc/issues/2732 302 assert_throws_js(TypeError, () => pc.addTransceiver('video', { sendEncodings: [{rid: 'a'.repeat(256)}] })); 303 }, 'addTransceiver with rid longer than 255 characters throws TypeError'); 304 305 promise_test(async t => { 306 const pc = new RTCPeerConnection(); 307 t.add_cleanup(() => pc.close()); 308 309 assert_throws_js(RangeError, () => pc.addTransceiver('video', { sendEncodings: [{scaleResolutionDownBy: -1}] })); 310 assert_throws_js(RangeError, () => pc.addTransceiver('video', { sendEncodings: [{scaleResolutionDownBy: 0}] })); 311 assert_throws_js(RangeError, () => pc.addTransceiver('video', { sendEncodings: [{scaleResolutionDownBy: 0.5}] })); 312 }, `addTransceiver with scaleResolutionDownBy < 1 throws RangeError`); 313 314 /* 315 5.2. create an RTCRtpSender 316 To create an RTCRtpSender with a MediaStreamTrack , track, a list of MediaStream 317 objects, streams, and optionally a list of RTCRtpEncodingParameters objects, 318 sendEncodings, run the following steps: 319 5. Let sender have a [[send encodings]] internal slot, representing a list 320 of RTCRtpEncodingParameters dictionaries. 321 322 6. If sendEncodings is given as input to this algorithm, and is non-empty, 323 set the [[send encodings]] slot to sendEncodings. 324 325 5.2. getParameters 326 - encodings is set to the value of the [[send encodings]] internal slot. 327 */ 328 promise_test(async t => { 329 const pc = new RTCPeerConnection(); 330 t.add_cleanup(() => pc.close()); 331 const { sender } = pc.addTransceiver('video', { 332 sendEncodings: [{ 333 active: false, 334 maxBitrate: 8, 335 rid: 'foo' 336 }] 337 }); 338 339 const param = sender.getParameters(); 340 const encoding = param.encodings[0]; 341 342 assert_equals(encoding.active, false); 343 assert_equals(encoding.maxBitrate, 8); 344 assert_not_own_property(encoding, "rid", "rid should be removed with a single encoding"); 345 346 }, `sender.getParameters() should return sendEncodings set by addTransceiver()`); 347 348 /* 349 5.2. setParameters 350 3. Let N be the number of RTCRtpEncodingParameters stored in sender's internal 351 [[send encodings]] slot. 352 7. If parameters.encodings.length is different from N, or if any parameter 353 in the parameters argument, marked as a Read-only parameter, has a value 354 that is different from the corresponding parameter value returned from 355 sender.getParameters(), abort these steps and return a promise rejected 356 with a newly created InvalidModificationError. Note that this also applies 357 to transactionId. 358 */ 359 promise_test(async t => { 360 const pc = new RTCPeerConnection(); 361 t.add_cleanup(() => pc.close()); 362 const { sender } = pc.addTransceiver('video'); 363 364 const param = sender.getParameters(); 365 366 const { encodings } = param; 367 assert_equals(encodings.length, 1); 368 369 // While {} is valid RTCRtpEncodingParameters because all fields are 370 // optional, it is still invalid to be missing a rid when there are multiple 371 // encodings. Only trigger one kind of error here. 372 encodings.push({ rid: "foo" }); 373 assert_equals(param.encodings.length, 2); 374 375 return promise_rejects_dom(t, 'InvalidModificationError', 376 sender.setParameters(param)); 377 }, `sender.setParameters() with added encodings should reject with InvalidModificationError`); 378 379 promise_test(async t => { 380 const pc = new RTCPeerConnection(); 381 t.add_cleanup(() => pc.close()); 382 const { sender } = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]}); 383 384 const param = sender.getParameters(); 385 386 const { encodings } = param; 387 assert_equals(encodings.length, 2); 388 389 encodings.pop(); 390 assert_equals(param.encodings.length, 1); 391 392 return promise_rejects_dom(t, 'InvalidModificationError', 393 sender.setParameters(param)); 394 }, `sender.setParameters() with removed encodings should reject with InvalidModificationError`); 395 396 promise_test(async t => { 397 const pc = new RTCPeerConnection(); 398 t.add_cleanup(() => pc.close()); 399 const { sender } = pc.addTransceiver('video', {sendEncodings: [{rid: "foo"}, {rid: "bar"}]}); 400 401 const param = sender.getParameters(); 402 403 const { encodings } = param; 404 assert_equals(encodings.length, 2); 405 encodings.push(encodings.shift()); 406 assert_equals(param.encodings.length, 2); 407 408 return promise_rejects_dom(t, 'InvalidModificationError', 409 sender.setParameters(param)); 410 }, `sender.setParameters() with reordered encodings should reject with InvalidModificationError`); 411 412 promise_test(async t => { 413 const pc = new RTCPeerConnection(); 414 t.add_cleanup(() => pc.close()); 415 const { sender } = pc.addTransceiver('video'); 416 417 const param = sender.getParameters(); 418 419 delete param.encodings; 420 421 return promise_rejects_js(t, TypeError, 422 sender.setParameters(param)); 423 }, `sender.setParameters() with encodings unset should reject with TypeError`); 424 425 promise_test(async t => { 426 const pc = new RTCPeerConnection(); 427 t.add_cleanup(() => pc.close()); 428 const { sender } = pc.addTransceiver('video'); 429 430 const param = sender.getParameters(); 431 432 param.encodings = []; 433 434 return promise_rejects_dom(t, 'InvalidModificationError', 435 sender.setParameters(param)); 436 }, `sender.setParameters() with empty encodings should reject with InvalidModificationError (video)`); 437 438 promise_test(async t => { 439 const pc = new RTCPeerConnection(); 440 t.add_cleanup(() => pc.close()); 441 const { sender } = pc.addTransceiver('audio'); 442 443 const param = sender.getParameters(); 444 445 param.encodings = []; 446 447 return promise_rejects_dom(t, 'InvalidModificationError', 448 sender.setParameters(param)); 449 }, `sender.setParameters() with empty encodings should reject with InvalidModificationError (audio)`); 450 451 promise_test(async t => { 452 const pc = new RTCPeerConnection(); 453 t.add_cleanup(() => pc.close()); 454 const { sender } = pc.addTransceiver('video', { 455 sendEncodings: [{ rid: 'foo' }, { rid: 'baz' }], 456 }); 457 458 const param = sender.getParameters(); 459 const encoding = param.encodings[0]; 460 461 assert_equals(encoding.rid, 'foo'); 462 463 encoding.rid = 'bar'; 464 return promise_rejects_dom(t, 'InvalidModificationError', 465 sender.setParameters(param)); 466 }, `setParameters() with modified encoding.rid field should reject with InvalidModificationError`); 467 468 /* 469 5.2. setParameters 470 8. If the scaleResolutionDownBy parameter in the parameters argument has a 471 value less than 1.0, abort these steps and return a promise rejected with 472 a newly created RangeError. 473 */ 474 promise_test(async t => { 475 const pc = new RTCPeerConnection(); 476 t.add_cleanup(() => pc.close()); 477 const { sender } = pc.addTransceiver('video'); 478 479 const param = sender.getParameters(); 480 const encoding = param.encodings[0]; 481 482 encoding.scaleResolutionDownBy = 0.5; 483 await promise_rejects_js(t, RangeError, sender.setParameters(param)); 484 encoding.scaleResolutionDownBy = 0; 485 await promise_rejects_js(t, RangeError, sender.setParameters(param)); 486 encoding.scaleResolutionDownBy = -1; 487 await promise_rejects_js(t, RangeError, sender.setParameters(param)); 488 }, `setParameters() with encoding.scaleResolutionDownBy field set to less than 1.0 should reject with RangeError`); 489 490 promise_test(async t => { 491 const pc = new RTCPeerConnection(); 492 t.add_cleanup(() => pc.close()); 493 const { sender } = pc.addTransceiver('video'); 494 495 let param = sender.getParameters(); 496 const encoding = param.encodings[0]; 497 498 delete encoding.scaleResolutionDownBy; 499 await sender.setParameters(param); 500 param = sender.getParameters(); 501 assert_equals(param.encodings[0].scaleResolutionDownBy, 1.0); 502 }, `setParameters() with missing encoding.scaleResolutionDownBy field should succeed, and set the value back to 1`); 503 504 promise_test(async t => { 505 const pc = new RTCPeerConnection(); 506 t.add_cleanup(() => pc.close()); 507 const { sender } = pc.addTransceiver('video'); 508 509 const param = sender.getParameters(); 510 const encoding = param.encodings[0]; 511 512 encoding.scaleResolutionDownBy = 1.5; 513 return sender.setParameters(param) 514 .then(() => { 515 const param = sender.getParameters(); 516 const encoding = param.encodings[0]; 517 518 assert_approx_equals(encoding.scaleResolutionDownBy, 1.5, 0.01); 519 }); 520 }, `setParameters() with encoding.scaleResolutionDownBy field set to greater than 1.0 should succeed`); 521 522 test_modified_encoding('video', 'active', false, true, 523 'setParameters() with encoding.active false->true should succeed (video)'); 524 525 test_modified_encoding('video', 'active', true, false, 526 'setParameters() with encoding.active true->false should succeed (video)'); 527 528 test_modified_encoding('video', 'maxBitrate', 10000, 20000, 529 'setParameters() with modified encoding.maxBitrate should succeed (video)'); 530 531 test_modified_encoding('audio', 'active', false, true, 532 'setParameters() with encoding.active false->true should succeed (audio)'); 533 534 test_modified_encoding('audio', 'active', true, false, 535 'setParameters() with encoding.active true->false should succeed (audio)'); 536 537 test_modified_encoding('audio', 'maxBitrate', 10000, 20000, 538 'setParameters() with modified encoding.maxBitrate should succeed (audio)'); 539 540 test_modified_encoding('video', 'scaleResolutionDownBy', 2, 4, 541 'setParameters() with modified encoding.scaleResolutionDownBy should succeed'); 542 543 </script>