RTCRtpCorruptionDetection-headerExtensionControl.html (6777B)
1 <!doctype html> 2 <meta charset=utf-8> 3 <title>Coruption Detection Header Extension</title> 4 <script src=/resources/testharness.js></script> 5 <script src=/resources/testharnessreport.js></script> 6 <script src="../webrtc/RTCPeerConnection-helper.js"></script> 7 <script> 8 'use strict'; 9 10 // If the `corruption-detection` header does not exists among the header 11 // extensions, it does not do anything. 12 function enableCorruptionDetectionIfExists(transceiver) { 13 const extensions = transceiver.getHeaderExtensionsToNegotiate(); 14 for (let i = 0; i < extensions.length; ++i) { 15 if (extensions[i].uri.includes('corruption-detection')) { 16 extensions[i].direction = 'sendrecv'; 17 } 18 } 19 transceiver.setHeaderExtensionsToNegotiate(extensions); 20 } 21 22 // Adds corruption detection RTP header extension to both peers' video section. 23 async function doSdpNegotiationWithCorruptionDetection(pc1, pc2) { 24 // Create offer with corruption-detection. 25 pc1.getTransceivers().forEach((transceiver) => { 26 enableCorruptionDetectionIfExists(transceiver); 27 }); 28 await pc1.setLocalDescription(); 29 await pc2.setRemoteDescription(pc1.localDescription); 30 31 // Create answer with corruption-detection. 32 pc2.getTransceivers().forEach((transceiver) => { 33 enableCorruptionDetectionIfExists(transceiver); 34 }); 35 await pc2.setLocalDescription(); 36 await pc1.setRemoteDescription(pc2.localDescription); 37 } 38 39 // Returns the inbound stats based on the kind. 40 // @param {string} [kind] - Either 'video' or 'audio'. 41 async function getInboundRtpStats(t, pc, kind) { 42 while (true) { 43 const stats = await pc.getStats(); 44 const values = [...stats.values()]; 45 const inboundRtp = values.find(s => s.type == 'inbound-rtp' && s.kind == kind); 46 // If video is transmitted, expect the corruption metrics to be populated. 47 if (inboundRtp && kind == 'video' && 48 (inboundRtp.corruptionMeasurements ??0 > 0)) { 49 return inboundRtp; 50 } 51 52 // If video is not transmitted, expect anything in the stream to be populated, 53 // to indicated that something is flowing in the pipeline. 54 if (inboundRtp && kind == 'audio' && 55 (inboundRtp.audioLevel ??0 > 0)) { 56 return inboundRtp; 57 } 58 59 await new Promise(r => t.step_timeout(r, 1000)); 60 } 61 } 62 63 async function createAudioVideoTracksWithCleanUp(t) { 64 const stream = await getNoiseStream({video: true, audio: true}); 65 const audioTrack = stream.getAudioTracks()[0]; 66 const videoTrack = stream.getVideoTracks()[0]; 67 t.add_cleanup(() => audioTrack.stop()); 68 t.add_cleanup(() => videoTrack.stop()); 69 return [audioTrack, videoTrack, stream]; 70 } 71 72 promise_test(async t => { 73 const pc1 = createPeerConnectionWithCleanup(t); 74 const pc2 = createPeerConnectionWithCleanup(t); 75 76 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 77 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 78 79 // Only add a video track to pc1. 80 const [audioTrack, videoTrack, stream] = 81 await createAudioVideoTracksWithCleanUp(t); 82 pc1.addTrack(videoTrack, stream); 83 84 doSdpNegotiationWithCorruptionDetection(pc1, pc2); 85 86 // Corruption score is calculated on receive side (`pc2`). 87 const inboundRtp = await getInboundRtpStats(t, pc2, 'video'); 88 assert_not_equals(inboundRtp.totalCorruptionProbability, undefined); 89 assert_not_equals(inboundRtp.totalSquaredCorruptionProbability, undefined); 90 assert_not_equals(inboundRtp.corruptionMeasurements, undefined); 91 }, 'If the corruption-detection header extension is present in the RTP packets,' + 92 'corruption metrics must be present.'); 93 94 promise_test(async t => { 95 const pc1 = createPeerConnectionWithCleanup(t); 96 const pc2 = createPeerConnectionWithCleanup(t); 97 98 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 99 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 100 101 // Add audio and video tracks to both pc1 and pc2. 102 const [audioTrack, videoTrack, stream] = 103 await createAudioVideoTracksWithCleanUp(t); 104 pc1.addTrack(audioTrack, stream); 105 pc1.addTrack(videoTrack, stream); 106 pc2.addTrack(audioTrack, stream); 107 pc2.addTrack(videoTrack, stream); 108 109 doSdpNegotiationWithCorruptionDetection(pc1, pc2); 110 111 function checkInboundRtpStats(inboundRtp) { 112 assert_not_equals(inboundRtp.totalCorruptionProbability, undefined); 113 assert_not_equals(inboundRtp.totalSquaredCorruptionProbability, undefined); 114 assert_not_equals(inboundRtp.corruptionMeasurements, undefined); 115 } 116 117 const inboundRtpPc1 = await getInboundRtpStats(t, pc1, 'video'); 118 const inboundRtpPc2 = await getInboundRtpStats(t, pc2, 'video'); 119 checkInboundRtpStats(inboundRtpPc1); 120 checkInboundRtpStats(inboundRtpPc2); 121 }, 'If the corruption-detection header extension is present in the RTP packets,' + 122 'corruption metrics must be present, both ways.'); 123 124 promise_test(async t => { 125 const pc1 = createPeerConnectionWithCleanup(t); 126 const pc2 = createPeerConnectionWithCleanup(t); 127 128 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 129 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 130 131 // Only add a video track to pc1. 132 const [audioTrack, videoTrack, stream] = 133 await createAudioVideoTracksWithCleanUp(t); 134 pc1.addTrack(videoTrack, stream); 135 136 doSdpNegotiationWithCorruptionDetection(pc1, pc2); 137 138 const inboundRtp = await getInboundRtpStats(t, pc2, 'video'); 139 140 // This check does not guarantee that each measurement is in the range [0, 1]. 141 // But it is the best we can do. 142 const mean = inboundRtp.totalCorruptionProbability / inboundRtp.corruptionMeasurements; 143 assert_less_than_equal(mean, 1); 144 assert_greater_than_equal(mean, 0); 145 }, 'Each measurement added to totalCorruptionProbability MUST be in the range [0.0, 1.0].'); 146 147 promise_test(async t => { 148 const pc1 = createPeerConnectionWithCleanup(t); 149 const pc2 = createPeerConnectionWithCleanup(t); 150 151 pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate); 152 pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate); 153 154 // Only add an audio track to pc1. 155 const [audioTrack, videoTrack, stream] = 156 await createAudioVideoTracksWithCleanUp(t); 157 pc1.addTrack(audioTrack, stream); 158 159 // Some browsers need an audio element attached to the DOM. 160 pc2.ontrack = (e) => { 161 const element = document.createElement('audio'); 162 element.autoplay = true; 163 element.srcObject = e.streams[0]; 164 document.body.appendChild(element); 165 t.add_cleanup(() => { document.body.removeChild(element) }); 166 }; 167 168 doSdpNegotiationWithCorruptionDetection(pc1, pc2); 169 170 const inboundRtp = await getInboundRtpStats (t, pc2, 'audio'); 171 assert_equals(inboundRtp.totalCorruptionProbability, undefined); 172 assert_equals(inboundRtp.totalSquaredCorruptionProbability, undefined); 173 assert_equals(inboundRtp.corruptionMeasurements, undefined); 174 }, 'Corruption metrics must not exists for audio.'); 175 176 </script>