Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add test that "maintain-framerate" does what it says. #30173

Merged
merged 1 commit into from
Oct 3, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions mst-content-hint/RTCRtpSendParameters-degradationEffect.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!doctype html>
<meta charset=utf-8>
<meta name="timeout" content="long">
<title>RTCRtpSendParameters degradationPreference effect</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../webrtc/RTCPeerConnection-helper.js"></script>
<script>
'use strict';

// This file contains tests that check that degradation preference
// actually has the desired effect. These tests take a long time to run.

// Returns incoming bandwidth usage between stats1 and stats2
// in bits per second.
function bandwidth(stats1, stats2) {
const transport1 = [...stats1.values()].filter(({type}) => type === 'transport')[0];
const transport2 = [...stats2.values()].filter(({type}) => type === 'transport')[0];
const bytes = transport2.bytesReceived - transport1.bytesReceived;
// Multiply by 1000 to get per second, divide by 8 to get bits.
const bandwidth = 1000 * 8 * bytes /
(transport2.timestamp - transport1.timestamp);
return bandwidth;
}

// Returns tuple of { bandwidth, fps, x-res, y-res }
async function measureStuff(t, pc, intervalMs) {
const stats1 = await pc.getStats();
await new Promise(r => t.step_timeout(r, intervalMs));
const stats2 = await pc.getStats();
// RTCInboundStreamStats
const inboundRtp1List = [...stats1.values()].filter(({type}) => type === 'inbound-rtp');
const inboundRtp2List = [...stats2.values()].filter(({type}) => type === 'inbound-rtp');
const inboundRtp1 = inboundRtp1List[0];
const inboundRtp2 = inboundRtp2List[0];
const fps = 1000 * (inboundRtp2.framesReceived - inboundRtp1.framesReceived) /
(inboundRtp2.timestamp - inboundRtp1.timestamp);
const result = {
bandwidth: bandwidth(stats1, stats2),
fps: fps,
width: inboundRtp2.frameWidth,
height: inboundRtp2.frameHeight
};
// Unbreak for debugging.
// con sole.log('Measure: ', performance.now(), " ", JSON.stringify(result));
return result;
}

// Wait for a certain condition to be true on the traffic measures
// on the PC. Will typically be conditions on resolution, framerate
// or bandwidth.
async function waitForCondition(t, pc, condition, maxWait, stepName) {
let counter = 1;
let measure = await measureStuff(t, pc, 1000);
while (counter < maxWait && !condition(measure)) {
measure = await measureStuff(t, pc, 1000);
counter += 1;
}
assert_true(condition(measure),
`failure in ${stepName}, measure is ${JSON.stringify(measure)}`);
return condition(measure);
}

promise_test(async t => {
const pc1 = new RTCPeerConnection();
t.add_cleanup(() => pc1.close());
const stream = await getNoiseStream({video: true});
t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
const track = stream.getTracks()[0];
const { sender } = pc1.addTransceiver(track);

let param = sender.getParameters();

assert_equals(param.degradationPreference, undefined,
'Expect initial param.degradationPreference to be undefined');

param.degradationPreference = 'maintain-framerate';
await sender.setParameters(param);

const pc2 = new RTCPeerConnection();
t.add_cleanup(() => pc2.close());

exchangeIceCandidates(pc1, pc2);
await exchangeOfferAnswer(pc1, pc2);
await listenToConnected(pc1);
// Wait a few seconds to allow things to settle (rampup)
// We know that the generator is supposed to produce 640x480
// at 10 fps with a bandwidth exceeding 30 kbits/second.
assert_true(await waitForCondition(t, pc2, (measure) => {
return (measure.bandwidth > 30000 &&
measure.width == 640 &&
measure.fps > 9);
}, 60, 'preconditions'));

// Measure BW, resolution and frame rate over one second
const stats1 = await measureStuff(t, pc2, 1000);

// Constrain BW to 1/2 of measured value
const newBandwidth = stats1.bandwidth / 2;

const parameters = sender.getParameters();
parameters.encodings[0].maxBitrate = newBandwidth;
await sender.setParameters(parameters);
// Wait until the expected result happens.
let stats2 = await measureStuff(t, pc2, 1000);
let counter = 1;
const maxWaitCount = 20;
const kBandwidthMargin = 1.3;
// It takes time to adapt to a new bandwidth, time to scale down,
// and time to acknowledge that framerate should not be reduced.
// Measured time is around 16 seconds.
assert_true(await waitForCondition(t, pc2, (measure) => {
return (measure.bandwidth < newBandwidth * kBandwidthMargin &&
measure.width < stats1.width &&
measure.fps > stats1.fps * 0.9);
}, 60, 'adaptation'),
`Target bandwidth ${newBandwidth * kBandwidthMargin}, target min FPS ${stats1.fps * 0.9}`);
}, 'Maintain-framerate reduces resolution on bandwidth cut', { timeout: 35000 });

</script>