1 /*
2 * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11 #include "video/encoder_overshoot_detector.h"
12
13 #include "api/units/data_rate.h"
14 #include "rtc_base/fake_clock.h"
15 #include "rtc_base/time_utils.h"
16 #include "test/gtest.h"
17
18 namespace webrtc {
19
20 class EncoderOvershootDetectorTest : public ::testing::Test {
21 public:
22 static constexpr int kDefaultBitrateBps = 300000;
23 static constexpr double kDefaultFrameRateFps = 15;
EncoderOvershootDetectorTest()24 EncoderOvershootDetectorTest()
25 : detector_(kWindowSizeMs),
26 target_bitrate_(DataRate::BitsPerSec(kDefaultBitrateBps)),
27 target_framerate_fps_(kDefaultFrameRateFps) {}
28
29 protected:
RunConstantUtilizationTest(double actual_utilization_factor,double expected_utilization_factor,double allowed_error,int64_t test_duration_ms)30 void RunConstantUtilizationTest(double actual_utilization_factor,
31 double expected_utilization_factor,
32 double allowed_error,
33 int64_t test_duration_ms) {
34 const int frame_size_bytes =
35 static_cast<int>(actual_utilization_factor *
36 (target_bitrate_.bps() / target_framerate_fps_) / 8);
37 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
38 rtc::TimeMillis());
39
40 if (rtc::TimeMillis() == 0) {
41 // Encode a first frame which by definition has no overuse factor.
42 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
43 clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
44 }
45
46 int64_t runtime_us = 0;
47 while (runtime_us < test_duration_ms * 1000) {
48 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
49 runtime_us += rtc::kNumMicrosecsPerSec / target_framerate_fps_;
50 clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
51 }
52
53 // At constant utilization, both network and media utilization should be
54 // close to expected.
55 const absl::optional<double> network_utilization_factor =
56 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis());
57 EXPECT_NEAR(network_utilization_factor.value_or(-1),
58 expected_utilization_factor, allowed_error);
59
60 const absl::optional<double> media_utilization_factor =
61 detector_.GetMediaRateUtilizationFactor(rtc::TimeMillis());
62 EXPECT_NEAR(media_utilization_factor.value_or(-1),
63 expected_utilization_factor, allowed_error);
64 }
65
66 static constexpr int64_t kWindowSizeMs = 3000;
67 EncoderOvershootDetector detector_;
68 rtc::ScopedFakeClock clock_;
69 DataRate target_bitrate_;
70 double target_framerate_fps_;
71 };
72
TEST_F(EncoderOvershootDetectorTest,NoUtilizationIfNoRate)73 TEST_F(EncoderOvershootDetectorTest, NoUtilizationIfNoRate) {
74 const int frame_size_bytes = 1000;
75 const int64_t time_interval_ms = 33;
76 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
77 rtc::TimeMillis());
78
79 // No data points, can't determine overshoot rate.
80 EXPECT_FALSE(
81 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis()).has_value());
82
83 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
84 clock_.AdvanceTime(TimeDelta::Millis(time_interval_ms));
85 EXPECT_TRUE(
86 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis()).has_value());
87 }
88
TEST_F(EncoderOvershootDetectorTest,OptimalSize)89 TEST_F(EncoderOvershootDetectorTest, OptimalSize) {
90 // Optimally behaved encoder.
91 // Allow some error margin due to rounding errors, eg due to frame
92 // interval not being an integer.
93 RunConstantUtilizationTest(1.0, 1.0, 0.01, kWindowSizeMs);
94 }
95
TEST_F(EncoderOvershootDetectorTest,Undershoot)96 TEST_F(EncoderOvershootDetectorTest, Undershoot) {
97 // Undershoot, reported utilization factor should be capped to 1.0 so
98 // that we don't incorrectly boost encoder bitrate during movement.
99 RunConstantUtilizationTest(0.5, 1.0, 0.00, kWindowSizeMs);
100 }
101
TEST_F(EncoderOvershootDetectorTest,Overshoot)102 TEST_F(EncoderOvershootDetectorTest, Overshoot) {
103 // Overshoot by 20%.
104 // Allow some error margin due to rounding errors.
105 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs);
106 }
107
TEST_F(EncoderOvershootDetectorTest,ConstantOvershootVaryingRates)108 TEST_F(EncoderOvershootDetectorTest, ConstantOvershootVaryingRates) {
109 // Overshoot by 20%, but vary framerate and bitrate.
110 // Allow some error margin due to rounding errors.
111 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs);
112 target_framerate_fps_ /= 2;
113 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
114 target_bitrate_ = DataRate::BitsPerSec(target_bitrate_.bps() / 2);
115 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
116 }
117
TEST_F(EncoderOvershootDetectorTest,ConstantRateVaryingOvershoot)118 TEST_F(EncoderOvershootDetectorTest, ConstantRateVaryingOvershoot) {
119 // Overshoot by 10%, keep framerate and bitrate constant.
120 // Allow some error margin due to rounding errors.
121 RunConstantUtilizationTest(1.1, 1.1, 0.01, kWindowSizeMs);
122 // Change overshoot to 20%, run for half window and expect overshoot
123 // to be 15%.
124 RunConstantUtilizationTest(1.2, 1.15, 0.01, kWindowSizeMs / 2);
125 // Keep running at 20% overshoot, after window is full that should now
126 // be the reported overshoot.
127 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
128 }
129
TEST_F(EncoderOvershootDetectorTest,PartialOvershoot)130 TEST_F(EncoderOvershootDetectorTest, PartialOvershoot) {
131 const int ideal_frame_size_bytes =
132 (target_bitrate_.bps() / target_framerate_fps_) / 8;
133 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
134 rtc::TimeMillis());
135
136 // Test scenario with average bitrate matching the target bitrate, but
137 // with some utilization factor penalty as the frames can't be paced out
138 // on the network at the target rate.
139 // Insert a series of four frames:
140 // 1) 20% overshoot, not penalized as buffer if empty.
141 // 2) 20% overshoot, the 20% overshoot from the first frame is penalized.
142 // 3) 20% undershoot, negating the overshoot from the last frame.
143 // 4) 20% undershoot, no penalty.
144 // On average then utilization penalty is thus 5%.
145
146 int64_t runtime_us = 0;
147 int i = 0;
148 while (runtime_us < kWindowSizeMs * rtc::kNumMicrosecsPerMillisec) {
149 runtime_us += rtc::kNumMicrosecsPerSec / target_framerate_fps_;
150 clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
151 int frame_size_bytes = (i++ % 4 < 2) ? (ideal_frame_size_bytes * 120) / 100
152 : (ideal_frame_size_bytes * 80) / 100;
153 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
154 }
155
156 // Expect 5% overshoot for network rate, see above.
157 const absl::optional<double> network_utilization_factor =
158 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis());
159 EXPECT_NEAR(network_utilization_factor.value_or(-1), 1.05, 0.01);
160
161 // Expect media rate to be on average correct.
162 const absl::optional<double> media_utilization_factor =
163 detector_.GetMediaRateUtilizationFactor(rtc::TimeMillis());
164 EXPECT_NEAR(media_utilization_factor.value_or(-1), 1.00, 0.01);
165 }
166 } // namespace webrtc
167