RTCRtpTransceiver-setCodecPreferences.html (13679B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTCRtpTransceiver.prototype.setCodecPreferences</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="./third_party/sdp/sdp.js"></script> 7 <script> 8 'use strict'; 9 10 test((t) => { 11 const pc = new RTCPeerConnection(); 12 t.add_cleanup(() => pc.close()); 13 const transceiver = pc.addTransceiver('audio'); 14 const capabilities = RTCRtpReceiver.getCapabilities('audio'); 15 transceiver.setCodecPreferences(capabilities.codecs); 16 }, `setCodecPreferences() on audio transceiver with codecs returned from RTCRtpReceiver.getCapabilities('audio') should succeed`); 17 18 test((t) => { 19 const pc = new RTCPeerConnection(); 20 t.add_cleanup(() => pc.close()); 21 const transceiver = pc.addTransceiver('video'); 22 const capabilities = RTCRtpReceiver.getCapabilities('video'); 23 transceiver.setCodecPreferences(capabilities.codecs); 24 }, `setCodecPreferences() on video transceiver with codecs returned from RTCRtpReceiver.getCapabilities('video') should succeed`); 25 26 test((t) => { 27 const pc = new RTCPeerConnection(); 28 t.add_cleanup(() => pc.close()); 29 const transceiver = pc.addTransceiver('audio'); 30 transceiver.setCodecPreferences([]); 31 }, `setCodecPreferences([]) should succeed`); 32 33 test((t) => { 34 const pc = new RTCPeerConnection(); 35 t.add_cleanup(() => pc.close()); 36 const transceiver = pc.addTransceiver('audio'); 37 const capabilities = RTCRtpReceiver.getCapabilities('audio'); 38 const { codecs } = capabilities; 39 40 if(codecs.length >= 2) { 41 const tmp = codecs[0]; 42 codecs[0] = codecs[1]; 43 codecs[1] = tmp; 44 } 45 46 transceiver.setCodecPreferences(codecs); 47 }, `setCodecPreferences() with reordered codecs should succeed`); 48 49 test((t) => { 50 const pc = new RTCPeerConnection(); 51 t.add_cleanup(() => pc.close()); 52 const transceiver = pc.addTransceiver('video'); 53 const capabilities = RTCRtpReceiver.getCapabilities('video'); 54 const { codecs } = capabilities; 55 // This test verifies that the mandatory VP8 codec is present 56 // and can be preferred. 57 const codec = codecs.find(c => c.mimeType === 'video/VP8'); 58 assert_true(!!codec, 'VP8 video codec was found'); 59 transceiver.setCodecPreferences([codec]); 60 }, `setCodecPreferences() with only VP8 should succeed`); 61 62 test(() => { 63 const pc = new RTCPeerConnection(); 64 const transceiver = pc.addTransceiver('video'); 65 const capabilities = RTCRtpReceiver.getCapabilities('video'); 66 const { codecs } = capabilities; 67 // This test verifies that the mandatory H264 codec is present 68 // and can be preferred. 69 const codec = codecs.find(c => c.mimeType === 'video/H264'); 70 assert_true(!!codec, 'H264 video codec was found'); 71 transceiver.setCodecPreferences([codec]); 72 }, `setCodecPreferences() with only H264 should succeed`); 73 74 async function getRTPMapLinesWithCodecAsFirst(firstCodec) 75 { 76 const codecs = RTCRtpReceiver.getCapabilities('video').codecs; 77 codecs.forEach((codec, idx) => { 78 if (codec.mimeType === firstCodec) { 79 codecs.splice(idx, 1); 80 codecs.unshift(codec); 81 } 82 }); 83 84 const pc = new RTCPeerConnection(); 85 const transceiver = pc.addTransceiver('video'); 86 transceiver.setCodecPreferences(codecs); 87 const offer = await pc.createOffer(); 88 89 return offer.sdp.split('\r\n').filter(line => line.startsWith('a=rtpmap:')); 90 } 91 92 promise_test(async () => { 93 const lines = await getRTPMapLinesWithCodecAsFirst('video/H264'); 94 95 assert_greater_than(lines.length, 1); 96 assert_true(lines[0].indexOf('H264') !== -1, 'H264 should be the first codec'); 97 }, `setCodecPreferences() should allow setting H264 as first codec`); 98 99 promise_test(async () => { 100 const lines = await getRTPMapLinesWithCodecAsFirst('video/VP8'); 101 102 assert_greater_than(lines.length, 1); 103 assert_true(lines[0].indexOf('VP8') !== -1, 'VP8 should be the first codec'); 104 }, `setCodecPreferences() should allow setting VP8 as first codec`); 105 106 test((t) => { 107 const pc = new RTCPeerConnection(); 108 t.add_cleanup(() => pc.close()); 109 const transceiver = pc.addTransceiver('audio'); 110 const capabilities = RTCRtpReceiver.getCapabilities('video'); 111 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(capabilities.codecs)); 112 }, `setCodecPreferences() on audio transceiver with codecs returned from getCapabilities('video') should throw InvalidModificationError`); 113 114 test((t) => { 115 const pc = new RTCPeerConnection(); 116 t.add_cleanup(() => pc.close()); 117 const transceiver = pc.addTransceiver('audio'); 118 const codecs = [{ 119 mimeType: 'data', 120 clockRate: 2000, 121 channels: 2, 122 sdpFmtpLine: '0-15' 123 }]; 124 125 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs)); 126 }, `setCodecPreferences() with user defined codec with invalid mimeType should throw InvalidModificationError`); 127 128 test((t) => { 129 const pc = new RTCPeerConnection(); 130 t.add_cleanup(() => pc.close()); 131 const transceiver = pc.addTransceiver('audio'); 132 const codecs = [{ 133 mimeType: 'audio/piepiper', 134 clockRate: 2000, 135 channels: 2, 136 sdpFmtpLine: '0-15' 137 }]; 138 139 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs)); 140 }, `setCodecPreferences() with user defined codec should throw InvalidModificationError`); 141 142 test((t) => { 143 const pc = new RTCPeerConnection(); 144 t.add_cleanup(() => pc.close()); 145 const transceiver = pc.addTransceiver('audio'); 146 const capabilities = RTCRtpReceiver.getCapabilities('audio'); 147 const codecs = [ 148 ...capabilities.codecs, 149 { 150 mimeType: 'audio/piepiper', 151 clockRate: 2000, 152 channels: 2, 153 sdpFmtpLine: '0-15' 154 }]; 155 156 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs)); 157 }, `setCodecPreferences() with user defined codec together with codecs returned from getCapabilities() should throw InvalidModificationError`); 158 159 test((t) => { 160 const pc = new RTCPeerConnection(); 161 t.add_cleanup(() => pc.close()); 162 const transceiver = pc.addTransceiver('audio'); 163 const capabilities = RTCRtpReceiver.getCapabilities('audio'); 164 const codecs = [capabilities.codecs[0]]; 165 codecs[0].clockRate = codecs[0].clockRate / 2; 166 167 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs)); 168 }, `setCodecPreferences() with modified codec clock rate should throw InvalidModificationError`); 169 170 test((t) => { 171 const pc = new RTCPeerConnection(); 172 t.add_cleanup(() => pc.close()); 173 const transceiver = pc.addTransceiver('audio'); 174 const capabilities = RTCRtpReceiver.getCapabilities('audio'); 175 const codecs = [capabilities.codecs[0]]; 176 codecs[0].channels = codecs[0].channels + 11; 177 178 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs)); 179 }, `setCodecPreferences() with modified codec channel count should throw InvalidModificationError`); 180 181 test((t) => { 182 const pc = new RTCPeerConnection(); 183 t.add_cleanup(() => pc.close()); 184 const transceiver = pc.addTransceiver('audio'); 185 const capabilities = RTCRtpReceiver.getCapabilities('audio'); 186 const codecs = [capabilities.codecs[0]]; 187 codecs[0].sdpFmtpLine = "modifiedparameter=1"; 188 189 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs)); 190 }, `setCodecPreferences() with modified codec parameters should throw InvalidModificationError`); 191 192 test((t) => { 193 const pc = new RTCPeerConnection(); 194 t.add_cleanup(() => pc.close()); 195 const transceiver = pc.addTransceiver('audio'); 196 const capabilities = RTCRtpReceiver.getCapabilities('audio'); 197 198 const { codecs } = capabilities; 199 assert_greater_than(codecs.length, 0, 200 'Expect at least one codec available'); 201 202 const [ codec ] = codecs; 203 const { channels=2 } = codec; 204 codec.channels = channels+1; 205 206 assert_throws_dom('InvalidModificationError', () => transceiver.setCodecPreferences(codecs)); 207 }, `setCodecPreferences() with modified codecs returned from getCapabilities() should throw InvalidModificationError`); 208 209 promise_test(async (t) => { 210 const pc = new RTCPeerConnection(); 211 t.add_cleanup(() => pc.close()); 212 const transceiver = pc.addTransceiver('audio'); 213 const {codecs} = RTCRtpReceiver.getCapabilities('audio'); 214 // Reorder codecs, put PCMU/PCMA first. 215 let firstCodec; 216 let i; 217 for (i = 0; i < codecs.length; i++) { 218 const codec = codecs[i]; 219 if (codec.mimeType === 'audio/PCMU' || codec.mimeType === 'audio/PCMA') { 220 codecs.splice(i, 1); 221 codecs.unshift(codec); 222 firstCodec = codec.mimeType.substr(6); 223 break; 224 } 225 } 226 assert_not_equals(firstCodec, undefined, 'PCMU or PCMA codec not found'); 227 transceiver.setCodecPreferences(codecs); 228 229 const offer = await pc.createOffer(); 230 const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0]; 231 const rtpParameters = SDPUtils.parseRtpParameters(mediaSection); 232 assert_equals(rtpParameters.codecs[0].name, firstCodec); 233 }, `setCodecPreferences() modifies the order of audio codecs in createOffer`); 234 235 promise_test(async (t) => { 236 const pc = new RTCPeerConnection(); 237 t.add_cleanup(() => pc.close()); 238 const transceiver = pc.addTransceiver('video'); 239 const {codecs} = RTCRtpReceiver.getCapabilities('video'); 240 // Reorder codecs, swap H264 and VP8. 241 let vp8 = -1; 242 let h264 = -1; 243 let firstCodec; 244 let i; 245 for (i = 0; i < codecs.length; i++) { 246 const codec = codecs[i]; 247 if (codec.mimeType === 'video/VP8' && vp8 === -1) { 248 vp8 = i; 249 if (h264 !== -1) { 250 codecs[vp8] = codecs[h264]; 251 codecs[h264] = codec; 252 firstCodec = 'VP8'; 253 break; 254 } 255 } 256 if (codec.mimeType === 'video/H264' && h264 === -1) { 257 h264 = i; 258 if (vp8 !== -1) { 259 codecs[h264] = codecs[vp8]; 260 codecs[vp8] = codec; 261 firstCodec = 'H264'; 262 break; 263 } 264 } 265 } 266 assert_not_equals(firstCodec, undefined, 'VP8 and H264 codecs not found'); 267 transceiver.setCodecPreferences(codecs); 268 269 const offer = await pc.createOffer(); 270 const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0]; 271 const rtpParameters = SDPUtils.parseRtpParameters(mediaSection); 272 assert_equals(rtpParameters.codecs[0].name, firstCodec); 273 }, `setCodecPreferences() modifies the order of video codecs in createOffer`); 274 275 ['rtx', 'red', 'ulpfec'].forEach(resiliencyMechanism => { 276 promise_test(async (t) => { 277 const pc = new RTCPeerConnection(); 278 t.add_cleanup(() => pc.close()); 279 const transceiver = pc.addTransceiver('video'); 280 const {codecs} = RTCRtpReceiver.getCapabilities('video'); 281 const filteredCodecs = codecs. 282 filter(codec => codec.mimeType !== 'video/' + resiliencyMechanism); 283 assert_not_equals(codecs.length, filteredCodecs.length); 284 transceiver.setCodecPreferences(filteredCodecs); 285 286 const offer = await pc.createOffer(); 287 const mediaSection = SDPUtils.getMediaSections(offer.sdp)[0]; 288 const rtpParameters = SDPUtils.parseRtpParameters(mediaSection); 289 assert_equals(rtpParameters.codecs.find(codec => codec.name === resiliencyMechanism), 290 undefined); 291 }, `setCodecPreferences() can remove ${resiliencyMechanism}`); 292 }); 293 294 // Tests the note removed as result of discussion in 295 // https://github.com/w3c/webrtc-pc/issues/2933 296 promise_test(async (t) => { 297 const pc1 = new RTCPeerConnection(); 298 t.add_cleanup(() => pc1.close()); 299 const pc2 = new RTCPeerConnection(); 300 t.add_cleanup(() => pc2.close()); 301 302 const transceiver = pc1.addTransceiver('video'); 303 const {codecs} = RTCRtpReceiver.getCapabilities('video'); 304 const vp8 = codecs.find(codec => codec.mimeType === 'video/VP8'); 305 const h264 = codecs.find(codec => codec.mimeType === 'video/H264'); 306 const thirdCodec = codecs.find(codec => ['video/VP9', 'video/AV1'].includes(codec.mimeType)); 307 assert_true(!!vp8); 308 assert_true(!!h264); 309 assert_true(!!thirdCodec); 310 311 transceiver.setCodecPreferences([vp8, thirdCodec]); 312 await pc1.setLocalDescription(); 313 await pc2.setRemoteDescription(pc1.localDescription); 314 const transceiver2 = pc2.getTransceivers()[0]; 315 transceiver2.setCodecPreferences([h264, thirdCodec, vp8]); 316 await pc2.setLocalDescription(); 317 await pc1.setRemoteDescription(pc2.localDescription); 318 const mediaSection = SDPUtils.getMediaSections(pc2.localDescription.sdp)[0]; 319 const rtpParameters = SDPUtils.parseRtpParameters(mediaSection); 320 // Order is determined by pc2 but H264 is not present. 321 assert_equals(rtpParameters.codecs.length, 2); 322 assert_equals(rtpParameters.codecs[0].name, thirdCodec.mimeType.substring(6)); 323 assert_equals(rtpParameters.codecs[1].name, 'VP8'); 324 325 }, `setCodecPreferences() filters on receiver and prefers receiver order`); 326 327 ["audio", "video"].forEach(kind => promise_test(async (t) => { 328 const pc = new RTCPeerConnection(); 329 t.add_cleanup(() => pc.close()); 330 const [codec] = RTCRtpReceiver.getCapabilities(kind).codecs; 331 codec.mimeType = codec.mimeType.toUpperCase(); 332 const transceiver = pc.addTransceiver(kind); 333 transceiver.setCodecPreferences([codec]); 334 335 codec.mimeType = codec.mimeType.toLowerCase(); 336 transceiver.setCodecPreferences([codec]); 337 }, `setCodecPreferences should accept ${kind} codecs regardless of mimeType case`)); 338 339 </script>