Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!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>