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