rtp-headerextensions.html (4998B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>payload type handling (assuming rtcp-mux)</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 // Tests behaviour from https://www.rfc-editor.org/rfc/rfc8834.html#name-header-extensions 10 11 function createOfferSdp(extmaps) { 12 let sdp = `v=0 13 o=- 0 3 IN IP4 127.0.0.1 14 s=- 15 t=0 0 16 a=fingerprint:sha-256 A7:24:72:CA:6E:02:55:39:BA:66:DF:6E:CC:4C:D8:B0:1A:BF:1A:56:65:7D:F4:03:AD:7E:77:43:2A:29:EC:93 17 a=ice-ufrag:6HHHdzzeIhkE0CKj 18 a=ice-pwd:XYDGVpfvklQIEnZ6YnyLsAew 19 `; 20 sdp += 'a=group:BUNDLE ' + ['audio', 'video'].filter(kind => extmaps[kind]).join(' ') + '\r\n'; 21 if (extmaps.audio) { 22 sdp += `m=audio 9 RTP/SAVPF 111 23 c=IN IP4 0.0.0.0 24 a=rtcp-mux 25 a=sendonly 26 a=mid:audio 27 a=rtpmap:111 opus/48000/2 28 a=setup:actpass 29 ` + extmaps.audio.map(ext => SDPUtils.writeExtmap(ext)).join(''); 30 } 31 if (extmaps.video) { 32 sdp += `m=video 9 RTP/SAVPF 112 33 c=IN IP4 0.0.0.0 34 a=rtcp-mux 35 a=sendonly 36 a=mid:video 37 a=rtpmap:112 VP8/90000 38 a=setup:actpass 39 ` + extmaps.video.map(ext => SDPUtils.writeExtmap(ext)).join(''); 40 } 41 return sdp; 42 } 43 44 [ 45 // https://www.rfc-editor.org/rfc/rfc8834.html#section-5.2.4 46 { 47 audio: [{id: 1, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid'}], 48 video: [{id: 1, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid'}], 49 description: 'MID', 50 }, 51 { 52 // https://www.rfc-editor.org/rfc/rfc8834.html#section-5.2.2 53 audio: [{id: 1, uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level'}], 54 description: 'Audio level', 55 }, 56 { 57 // https://www.rfc-editor.org/rfc/rfc8834.html#section-5.2.5 58 video: [{id: 1, uri: 'urn:3gpp:video-orientation'}], 59 description: 'Video orientation', 60 } 61 ].forEach(testcase => { 62 promise_test(async t => { 63 const pc = new RTCPeerConnection(); 64 t.add_cleanup(() => pc.close()); 65 66 await pc.setRemoteDescription({type: 'offer', sdp: createOfferSdp(testcase)}); 67 const answer = await pc.createAnswer(); 68 const sections = SDPUtils.splitSections(answer.sdp); 69 sections.shift(); 70 sections.forEach(section => { 71 const rtpParameters = SDPUtils.parseRtpParameters(section); 72 assert_equals(rtpParameters.headerExtensions.length, 1); 73 assert_equals(rtpParameters.headerExtensions[0].id, testcase[SDPUtils.getKind(section)][0].id); 74 assert_equals(rtpParameters.headerExtensions[0].uri, testcase[SDPUtils.getKind(section)][0].uri); 75 }); 76 }, testcase.description + ' header extension is supported.'); 77 }); 78 79 promise_test(async t => { 80 const pc = new RTCPeerConnection(); 81 t.add_cleanup(() => pc.close()); 82 83 pc.addTransceiver('video'); 84 const offer = await pc.createOffer(); 85 const section = SDPUtils.splitSections(offer.sdp)[1]; 86 const extensions = SDPUtils.matchPrefix(section, 'a=extmap:') 87 .map(line => SDPUtils.parseExtmap(line)); 88 const extension_not_mid = extensions.find(e => e.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid'); 89 await pc.setRemoteDescription({type :'offer', sdp: offer.sdp.replace(extension_not_mid.uri, 'bogus')}); 90 91 await pc.setLocalDescription(); 92 const answer_section = SDPUtils.splitSections(pc.localDescription.sdp)[1]; 93 const answer_extensions = SDPUtils.matchPrefix(answer_section, 'a=extmap:') 94 .map(line => SDPUtils.parseExtmap(line)); 95 assert_equals(answer_extensions.length, extensions.length - 1); 96 assert_false(!!extensions.find(e => e.uri === 'bogus')); 97 for (const answer_extension of answer_extensions) { 98 assert_true(!!extensions.find(e => e.uri === answer_extension.uri)); 99 } 100 }, 'Negotiates the subset of supported extensions offered'); 101 102 promise_test(async t => { 103 const pc = new RTCPeerConnection(); 104 t.add_cleanup(() => pc.close()); 105 106 // Some implementations may refuse 15 as invalid id because of 107 // https://www.rfc-editor.org/rfc/rfc8285#section-4.2 108 // which only applies to one-byte extensions with ids 0-14. 109 const sdp = createOfferSdp({audio: [{ 110 id: 15, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', 111 }]}); 112 await pc.setRemoteDescription({type: 'offer', sdp}); 113 }, 'Supports header extensions with id=15'); 114 115 promise_test(async t => { 116 const pc = new RTCPeerConnection(); 117 t.add_cleanup(() => pc.close()); 118 119 const sdp = createOfferSdp({audio: [{ 120 id: 16, uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', 121 }, { 122 id: 17, uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', 123 }]}); 124 await pc.setRemoteDescription({type: 'offer', sdp}); 125 await pc.setLocalDescription(); 126 const answer_section = SDPUtils.splitSections(pc.localDescription.sdp)[1]; 127 const answer_extensions = SDPUtils.matchPrefix(answer_section, 'a=extmap:') 128 .map(line => SDPUtils.parseExtmap(line)); 129 assert_equals(answer_extensions.length, 2); 130 assert_true(!!answer_extensions.find(e => e.uri === 'urn:ietf:params:rtp-hdrext:sdes:mid')); 131 assert_true(!!answer_extensions.find(e => e.uri === 'urn:ietf:params:rtp-hdrext:ssrc-audio-level')); 132 }, 'Supports two-byte header extensions'); 133 </script>