| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 1 | <!doctype html> |
| 2 | <meta charset=utf-8> |
| 3 | <meta name="timeout" content="long"> |
| 4 | <title>RTCRtpSendParameters degradationPreference effect</title> |
| 5 | <script src="/resources/testharness.js"></script> |
| 6 | <script src="/resources/testharnessreport.js"></script> |
| 7 | <script src="../webrtc/RTCPeerConnection-helper.js"></script> |
| 8 | <script> |
| 9 | 'use strict'; |
| 10 | |
| 11 | // This file contains tests that check that degradation preference |
| 12 | // actually has the desired effect. These tests take a long time to run. |
| 13 | |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 14 | // The signal generator will generate a video stream with at least this |
| 15 | // many bits per second if unconstrained. |
| 16 | const minUnconstrainedBandwidth = 30000; |
| 17 | |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 18 | // Returns incoming bandwidth usage between stats1 and stats2 |
| 19 | // in bits per second. |
| 20 | function bandwidth(stats1, stats2) { |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 21 | if (!stats1 || !stats2) { |
| 22 | return null; |
| 23 | } |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 24 | const transport1 = [...stats1.values()].filter(({type}) => type === 'transport')[0]; |
| 25 | const transport2 = [...stats2.values()].filter(({type}) => type === 'transport')[0]; |
| 26 | const bytes = transport2.bytesReceived - transport1.bytesReceived; |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 27 | // If time interval is too short for proper measurement, return null. |
| 28 | if (transport1.timestamp > transport2.timestamp - 100) { |
| 29 | return null; |
| 30 | } |
| 31 | // Multiply by 1000 to get per second, multiply by 8 to get bits. |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 32 | const bandwidth = 1000 * 8 * bytes / |
| 33 | (transport2.timestamp - transport1.timestamp); |
| 34 | return bandwidth; |
| 35 | } |
| 36 | |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 37 | let oldStats; |
| 38 | |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 39 | // Returns tuple of { bandwidth, fps, x-res, y-res } |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 40 | // Updates oldStats. |
| 41 | async function measureStuff(pc) { |
| 42 | const stats = await pc.getStats(); |
| 43 | if (!oldStats) { |
| 44 | oldStats = stats; |
| 45 | return {}; |
| 46 | } |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 47 | // RTCInboundStreamStats |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 48 | const oldRtpList = [...oldStats.values()].filter(({type}) => type === 'inbound-rtp'); |
| 49 | const inboundRtpList = [...stats.values()].filter(({type}) => type === 'inbound-rtp'); |
| 50 | const oldRtp = oldRtpList[0]; |
| 51 | const inboundRtp = inboundRtpList[0]; |
| 52 | const fps = 1000.0 * (inboundRtp.framesReceived - oldRtp.framesReceived) / |
| 53 | (inboundRtp.timestamp - oldRtp.timestamp); |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 54 | const result = { |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 55 | bandwidth: bandwidth(oldStats, stats), |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 56 | fps: fps, |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 57 | width: inboundRtp.frameWidth, |
| 58 | height: inboundRtp.frameHeight |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 59 | }; |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 60 | oldStats = stats; |
| 61 | if (!result.bandwidth) { |
| 62 | return {}; |
| 63 | } |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 64 | // Unbreak for debugging. |
| 65 | // con sole.log('Measure: ', performance.now(), " ", JSON.stringify(result)); |
| 66 | return result; |
| 67 | } |
| 68 | |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 69 | promise_test(async t => { |
| 70 | const pc1 = new RTCPeerConnection(); |
| 71 | t.add_cleanup(() => pc1.close()); |
| 72 | const stream = await getNoiseStream({video: true}); |
| 73 | t.add_cleanup(() => stream.getTracks().forEach(track => track.stop())); |
| 74 | const track = stream.getTracks()[0]; |
| 75 | const { sender } = pc1.addTransceiver(track); |
| 76 | |
| 77 | let param = sender.getParameters(); |
| 78 | |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 79 | param.degradationPreference = 'maintain-framerate'; |
| 80 | await sender.setParameters(param); |
| 81 | |
| 82 | const pc2 = new RTCPeerConnection(); |
| 83 | t.add_cleanup(() => pc2.close()); |
| 84 | |
| 85 | exchangeIceCandidates(pc1, pc2); |
| 86 | await exchangeOfferAnswer(pc1, pc2); |
| 87 | await listenToConnected(pc1); |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 88 | // Allow the keyframe to pass. |
| 89 | await new Promise(r => t.step_timeout(r, 1000)); |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 90 | // Wait a few seconds to allow things to settle (rampup) |
| 91 | // We know that the generator is supposed to produce 640x480 |
| 92 | // at 10 fps with a bandwidth exceeding 30 kbits/second. |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 93 | await t.step_wait(async () => { |
| 94 | const measure = await measureStuff(pc2); |
| 95 | return (measure.bandwidth > minUnconstrainedBandwidth && |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 96 | measure.width == 640 && |
| 97 | measure.fps > 9); |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 98 | }, 'Test error: Preconditions not achieved', 30000, 500); |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 99 | |
| 100 | // Measure BW, resolution and frame rate over one second |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 101 | // after measurements have stabilized. |
| 102 | await new Promise(r => t.step_timeout(r, 1000)); |
| 103 | const stats1 = await measureStuff(pc2); |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 104 | |
| 105 | // Constrain BW to 1/2 of measured value |
| 106 | const newBandwidth = stats1.bandwidth / 2; |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 107 | // Guard against inappropriate bandwidth |
| 108 | assert_greater_than(newBandwidth, minUnconstrainedBandwidth/2, |
| 109 | "Test error: Constraint too low"); |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 110 | |
| 111 | const parameters = sender.getParameters(); |
| 112 | parameters.encodings[0].maxBitrate = newBandwidth; |
| 113 | await sender.setParameters(parameters); |
| 114 | // Wait until the expected result happens. |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 115 | const kBandwidthMargin = 1.3; |
| 116 | // It takes time to adapt to a new bandwidth, time to scale down, |
| 117 | // and time to acknowledge that framerate should not be reduced. |
| 118 | // Measured time is around 16 seconds. |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 119 | await t.step_wait(async () => { |
| 120 | let measure = await measureStuff(pc2); |
| 121 | return (measure.bandwidth && |
| 122 | measure.bandwidth < newBandwidth * kBandwidthMargin && |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 123 | measure.width < stats1.width && |
| 124 | measure.fps > stats1.fps * 0.9); |
| Harald Alvestrand | 9df3aaa | 2023-10-04 19:32:10 | [diff] [blame] | 125 | }, 'Adaptation did not succeed', |
| 126 | 30000, 500); |
| Harald Alvestrand | 1af7ffe | 2023-10-03 13:00:58 | [diff] [blame] | 127 | }, 'Maintain-framerate reduces resolution on bandwidth cut', { timeout: 35000 }); |
| 128 | |
| 129 | </script> |