h265-level-id.https.html (7297B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>RTX codec integrity checks</title> 4 <script src="/resources/testharness.js"></script> 5 <script src="/resources/testharnessreport.js"></script> 6 <script src="../RTCPeerConnection-helper.js"></script> 7 <script> 8 'use strict'; 9 10 const kProfileIdKey = 'profile-id'; 11 const kLevelIdKey = 'level-id'; 12 13 // The level-id value for Level X.Y is calculated as (X * 10 + Y) * 3. 14 // The lowest Level, 1.0, is thus (1 * 10 + 0) * 3 = 30. 15 const kH265Level1dot0 = '30'; 16 17 function parseFmtpMap(sdpFmtpLine) { 18 const map = new Map(); 19 // For each entry (semi-colon separated key=value). 20 for (let i = 0; i < sdpFmtpLine.length; ++i) { 21 let entryEnd = sdpFmtpLine.indexOf(';', i); 22 if (entryEnd == -1) { 23 entryEnd = sdpFmtpLine.length; 24 } 25 const entryStr = sdpFmtpLine.substring(i, entryEnd); 26 const keyValue = entryStr.split('='); 27 if (keyValue.length != 2) { 28 throw 'Failed to parse sdpFmtpLine'; 29 } 30 map.set(keyValue[0], keyValue[1]); 31 i = entryEnd; 32 } 33 return map; 34 } 35 36 function findCodecWithProfileId(codecs, mimeType, profileId) { 37 return codecs.find(codec => { 38 if (codec.mimeType != mimeType) { 39 return false; 40 } 41 return parseFmtpMap(codec.sdpFmtpLine).get(kProfileIdKey) == profileId; 42 }); 43 } 44 45 // Returns `[h265SendCodec, h265RecvCodec]` or aborts the calling test with 46 // [PRECONDITION_FAILED]. 47 function getH265CodecsOrFailPrecondition() { 48 const h265SendCodec = RTCRtpSender.getCapabilities('video').codecs.find( 49 c => c.mimeType == 'video/H265'); 50 assert_implements_optional( 51 h265SendCodec !== undefined, 52 `H265 is not available for sending.`); 53 54 const h265SendCodecFmtpMap = parseFmtpMap(h265SendCodec.sdpFmtpLine); 55 const profileId = h265SendCodecFmtpMap.get(kProfileIdKey); 56 assert_not_equals(profileId, undefined, 57 `profile-id is missing from sdpFmtpLine`); 58 59 const h265RecvCodec = findCodecWithProfileId( 60 RTCRtpReceiver.getCapabilities('video').codecs, 'video/H265', profileId); 61 assert_implements_optional( 62 h265RecvCodec !== undefined, 63 `H265 profile-id=${profileId} is not available for receiving.`); 64 65 return [h265SendCodec, h265RecvCodec]; 66 } 67 68 function sdpModifyFmtpLevelId(sdp, newLevelId) { 69 const lines = sdp.split('\r\n'); 70 for (let i = 0; i < lines.length; ++i) { 71 if (!lines[i].startsWith('a=fmtp:')) { 72 continue; 73 } 74 const spaceIndex = lines[i].indexOf(' '); 75 if (spaceIndex == -1) { 76 continue; 77 } 78 const fmtpMap = parseFmtpMap(lines[i].substring(spaceIndex + 1)); 79 if (!fmtpMap.has(kLevelIdKey)) { 80 continue; 81 } 82 fmtpMap.set(kLevelIdKey, newLevelId); 83 const sdpFmtpLine = 84 Array.from(fmtpMap, ([key,value]) => `${key}=${value}`).join(';'); 85 lines[i] = lines[i].substring(0, spaceIndex) + ' ' + sdpFmtpLine; 86 } 87 return lines.join('\r\n'); 88 } 89 90 promise_test(async t => { 91 const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition(); 92 93 const pc1 = new RTCPeerConnection(); 94 t.add_cleanup(() => pc1.close()); 95 const pc2 = new RTCPeerConnection(); 96 t.add_cleanup(() => pc2.close()); 97 98 const pc1Transceiver = pc1.addTransceiver('video', {direction: 'sendonly'}); 99 pc1Transceiver.setCodecPreferences([h265SendCodec]); 100 101 await pc1.setLocalDescription(); 102 await pc2.setRemoteDescription(pc1.localDescription); 103 await pc2.setLocalDescription(); 104 // Modify SDP to tell `pc1` that `pc2` can only receive level-id=30. 105 await pc1.setRemoteDescription({ 106 type: 'answer', 107 sdp: sdpModifyFmtpLevelId(pc2.localDescription.sdp, kH265Level1dot0) 108 }); 109 110 // Confirm level-id=30 was negotiated regardless of sender capabilities. 111 const sender = pc1Transceiver.sender; 112 const params = sender.getParameters(); 113 assert_equals(params.codecs.length, 1); 114 const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine); 115 assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0); 116 }, `Offer to send H265, answer to receive level-id=30 results in level-id=30`); 117 118 promise_test(async t => { 119 const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition(); 120 121 const pc1 = new RTCPeerConnection(); 122 t.add_cleanup(() => pc1.close()); 123 const pc2 = new RTCPeerConnection(); 124 t.add_cleanup(() => pc2.close()); 125 126 const pc1Transceiver = pc1.addTransceiver('video', {direction: 'recvonly'}); 127 pc1Transceiver.setCodecPreferences([h265RecvCodec]); 128 129 await pc1.setLocalDescription(); 130 // Modify SDP to tell `pc2` that `pc1` can only receive level-id=30. 131 await pc2.setRemoteDescription({ 132 type: 'offer', 133 sdp: sdpModifyFmtpLevelId(pc1.localDescription.sdp, kH265Level1dot0) 134 }); 135 const [pc2Transceiver] = pc2.getTransceivers(); 136 pc2Transceiver.direction = 'sendonly'; 137 await pc2.setLocalDescription(); 138 await pc1.setRemoteDescription(pc2.localDescription); 139 140 // Confirm level-id=30 was negotiated regardless of sender capabilities. 141 const sender = pc2Transceiver.sender; 142 const params = sender.getParameters(); 143 assert_equals(params.codecs.length, 1); 144 const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine); 145 assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0); 146 // Setting a codec that was negotiated should always work, regardless of the 147 // level-id in sender capabilities. 148 params.encodings[0].codec = params.codecs[0]; 149 await sender.setParameters(params); 150 assert_equals(sender.getParameters().encodings[0].codec.mimeType, 151 params.codecs[0].mimeType); 152 assert_equals(sender.getParameters().encodings[0].codec.sdpFmtpLine, 153 params.codecs[0].sdpFmtpLine); 154 }, `Offer to receive level-id=30 and set codec from getParameters`); 155 156 promise_test(async t => { 157 const [h265SendCodec, h265RecvCodec] = getH265CodecsOrFailPrecondition(); 158 159 const pc1 = new RTCPeerConnection(); 160 t.add_cleanup(() => pc1.close()); 161 const pc2 = new RTCPeerConnection(); 162 t.add_cleanup(() => pc2.close()); 163 164 const pc1Transceiver = pc1.addTransceiver('video', {direction: 'recvonly'}); 165 pc1Transceiver.setCodecPreferences([h265RecvCodec]); 166 167 await pc1.setLocalDescription(); 168 // Modify SDP to tell `pc2` that `pc1` can only receive level-id=30. 169 await pc2.setRemoteDescription({ 170 type: 'offer', 171 sdp: sdpModifyFmtpLevelId(pc1.localDescription.sdp, kH265Level1dot0) 172 }); 173 const [pc2Transceiver] = pc2.getTransceivers(); 174 pc2Transceiver.direction = 'sendonly'; 175 await pc2.setLocalDescription(); 176 await pc1.setRemoteDescription(pc2.localDescription); 177 178 // Confirm level-id=30 was negotiated regardless of sender capabilities. 179 const sender = pc2Transceiver.sender; 180 const params = sender.getParameters(); 181 assert_equals(params.codecs.length, 1); 182 const negotiatedFmtpMap = parseFmtpMap(params.codecs[0].sdpFmtpLine); 183 assert_equals(negotiatedFmtpMap.get(kLevelIdKey), kH265Level1dot0); 184 // Setting a codec from getCapabilities should work, even if a lower level-id 185 // was negotiated. 186 params.encodings[0].codec = h265SendCodec; 187 await sender.setParameters(params); 188 assert_equals(sender.getParameters().encodings[0].codec.mimeType, 189 h265SendCodec.mimeType); 190 assert_equals(sender.getParameters().encodings[0].codec.sdpFmtpLine, 191 h265SendCodec.sdpFmtpLine); 192 }, `Offer to receive level-id=30 and set codec from getCapabilities`); 193 </script>