blob: 217895788934ab33b404c5e00e30f390a69a4dc7 [file] [log] [blame]
Erik Språng7ca375c2019-02-06 15:20:171/*
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"
Jonas Olssona4d87372019-07-05 17:08:3312
yingyingmad3416972023-04-20 06:28:0113#include <string>
14
Erik Språng7ca375c2019-02-06 15:20:1715#include "api/units/data_rate.h"
16#include "rtc_base/fake_clock.h"
17#include "rtc_base/time_utils.h"
yingyingmad3416972023-04-20 06:28:0118#include "system_wrappers/include/metrics.h"
Erik Språng7ca375c2019-02-06 15:20:1719#include "test/gtest.h"
20
21namespace webrtc {
22
yingyingmad3416972023-04-20 06:28:0123namespace {
24
25using ::testing::TestWithParam;
26using ::testing::ValuesIn;
27
28static std::string CodecTypeToHistogramSuffix(VideoCodecType codec) {
29 switch (codec) {
30 case kVideoCodecVP8:
31 return "Vp8";
32 case kVideoCodecVP9:
33 return "Vp9";
34 case kVideoCodecAV1:
35 return "Av1";
36 case kVideoCodecH264:
37 return "H264";
qwu16ae82df72023-09-20 05:10:3138 case kVideoCodecH265:
39 return "H265";
yingyingmad3416972023-04-20 06:28:0140 case kVideoCodecGeneric:
41 return "Generic";
42 case kVideoCodecMultiplex:
43 return "Multiplex";
44 }
45}
46
47struct TestParams {
48 VideoCodecType codec_type;
49 bool is_screenshare;
50};
51
52} // namespace
53
54class EncoderOvershootDetectorTest : public TestWithParam<TestParams> {
Erik Språng7ca375c2019-02-06 15:20:1755 public:
56 static constexpr int kDefaultBitrateBps = 300000;
57 static constexpr double kDefaultFrameRateFps = 15;
58 EncoderOvershootDetectorTest()
yingyingmad3416972023-04-20 06:28:0159 : detector_(kWindowSizeMs,
60 GetParam().codec_type,
61 GetParam().is_screenshare),
Danil Chapovalovcad3e0e2020-02-17 17:46:0762 target_bitrate_(DataRate::BitsPerSec(kDefaultBitrateBps)),
Erik Språng7ca375c2019-02-06 15:20:1763 target_framerate_fps_(kDefaultFrameRateFps) {}
64
65 protected:
yingyingmad3416972023-04-20 06:28:0166 void SetUp() override { metrics::Reset(); }
Erik Språng7ca375c2019-02-06 15:20:1767 void RunConstantUtilizationTest(double actual_utilization_factor,
68 double expected_utilization_factor,
69 double allowed_error,
70 int64_t test_duration_ms) {
71 const int frame_size_bytes =
72 static_cast<int>(actual_utilization_factor *
73 (target_bitrate_.bps() / target_framerate_fps_) / 8);
74 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
75 rtc::TimeMillis());
76
77 if (rtc::TimeMillis() == 0) {
78 // Encode a first frame which by definition has no overuse factor.
79 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
Danil Chapovalov0c626af2020-02-10 10:16:0080 clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
Erik Språng7ca375c2019-02-06 15:20:1781 }
82
83 int64_t runtime_us = 0;
84 while (runtime_us < test_duration_ms * 1000) {
85 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
86 runtime_us += rtc::kNumMicrosecsPerSec / target_framerate_fps_;
Danil Chapovalov0c626af2020-02-10 10:16:0087 clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
Erik Språng7ca375c2019-02-06 15:20:1788 }
89
Erik Språng6c072ef2019-04-01 10:57:2890 // At constant utilization, both network and media utilization should be
91 // close to expected.
92 const absl::optional<double> network_utilization_factor =
93 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis());
94 EXPECT_NEAR(network_utilization_factor.value_or(-1),
95 expected_utilization_factor, allowed_error);
96
97 const absl::optional<double> media_utilization_factor =
98 detector_.GetMediaRateUtilizationFactor(rtc::TimeMillis());
99 EXPECT_NEAR(media_utilization_factor.value_or(-1),
100 expected_utilization_factor, allowed_error);
Erik Språng7ca375c2019-02-06 15:20:17101 }
102
103 static constexpr int64_t kWindowSizeMs = 3000;
104 EncoderOvershootDetector detector_;
105 rtc::ScopedFakeClock clock_;
106 DataRate target_bitrate_;
107 double target_framerate_fps_;
108};
109
yingyingmad3416972023-04-20 06:28:01110TEST_P(EncoderOvershootDetectorTest, NoUtilizationIfNoRate) {
Erik Språng7ca375c2019-02-06 15:20:17111 const int frame_size_bytes = 1000;
112 const int64_t time_interval_ms = 33;
113 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
114 rtc::TimeMillis());
115
116 // No data points, can't determine overshoot rate.
Erik Språng6c072ef2019-04-01 10:57:28117 EXPECT_FALSE(
118 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis()).has_value());
Erik Språng7ca375c2019-02-06 15:20:17119
120 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
Danil Chapovalov0c626af2020-02-10 10:16:00121 clock_.AdvanceTime(TimeDelta::Millis(time_interval_ms));
Erik Språng6c072ef2019-04-01 10:57:28122 EXPECT_TRUE(
123 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis()).has_value());
Erik Språng7ca375c2019-02-06 15:20:17124}
125
yingyingmad3416972023-04-20 06:28:01126TEST_P(EncoderOvershootDetectorTest, OptimalSize) {
Erik Språng7ca375c2019-02-06 15:20:17127 // Optimally behaved encoder.
128 // Allow some error margin due to rounding errors, eg due to frame
129 // interval not being an integer.
130 RunConstantUtilizationTest(1.0, 1.0, 0.01, kWindowSizeMs);
131}
132
yingyingmad3416972023-04-20 06:28:01133TEST_P(EncoderOvershootDetectorTest, Undershoot) {
Erik Språng7ca375c2019-02-06 15:20:17134 // Undershoot, reported utilization factor should be capped to 1.0 so
135 // that we don't incorrectly boost encoder bitrate during movement.
136 RunConstantUtilizationTest(0.5, 1.0, 0.00, kWindowSizeMs);
137}
138
yingyingmad3416972023-04-20 06:28:01139TEST_P(EncoderOvershootDetectorTest, Overshoot) {
Erik Språng7ca375c2019-02-06 15:20:17140 // Overshoot by 20%.
141 // Allow some error margin due to rounding errors.
142 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs);
143}
144
yingyingmad3416972023-04-20 06:28:01145TEST_P(EncoderOvershootDetectorTest, ConstantOvershootVaryingRates) {
Erik Språng7ca375c2019-02-06 15:20:17146 // Overshoot by 20%, but vary framerate and bitrate.
147 // Allow some error margin due to rounding errors.
148 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs);
149 target_framerate_fps_ /= 2;
150 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
Danil Chapovalovcad3e0e2020-02-17 17:46:07151 target_bitrate_ = DataRate::BitsPerSec(target_bitrate_.bps() / 2);
Erik Språng7ca375c2019-02-06 15:20:17152 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
153}
154
yingyingmad3416972023-04-20 06:28:01155TEST_P(EncoderOvershootDetectorTest, ConstantRateVaryingOvershoot) {
Erik Språng7ca375c2019-02-06 15:20:17156 // Overshoot by 10%, keep framerate and bitrate constant.
157 // Allow some error margin due to rounding errors.
158 RunConstantUtilizationTest(1.1, 1.1, 0.01, kWindowSizeMs);
159 // Change overshoot to 20%, run for half window and expect overshoot
160 // to be 15%.
161 RunConstantUtilizationTest(1.2, 1.15, 0.01, kWindowSizeMs / 2);
162 // Keep running at 20% overshoot, after window is full that should now
163 // be the reported overshoot.
164 RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
165}
166
yingyingmad3416972023-04-20 06:28:01167TEST_P(EncoderOvershootDetectorTest, PartialOvershoot) {
Erik Språng7ca375c2019-02-06 15:20:17168 const int ideal_frame_size_bytes =
169 (target_bitrate_.bps() / target_framerate_fps_) / 8;
170 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
171 rtc::TimeMillis());
172
173 // Test scenario with average bitrate matching the target bitrate, but
174 // with some utilization factor penalty as the frames can't be paced out
175 // on the network at the target rate.
176 // Insert a series of four frames:
177 // 1) 20% overshoot, not penalized as buffer if empty.
178 // 2) 20% overshoot, the 20% overshoot from the first frame is penalized.
179 // 3) 20% undershoot, negating the overshoot from the last frame.
180 // 4) 20% undershoot, no penalty.
181 // On average then utilization penalty is thus 5%.
182
183 int64_t runtime_us = 0;
184 int i = 0;
185 while (runtime_us < kWindowSizeMs * rtc::kNumMicrosecsPerMillisec) {
186 runtime_us += rtc::kNumMicrosecsPerSec / target_framerate_fps_;
Danil Chapovalov0c626af2020-02-10 10:16:00187 clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
Erik Språng7ca375c2019-02-06 15:20:17188 int frame_size_bytes = (i++ % 4 < 2) ? (ideal_frame_size_bytes * 120) / 100
189 : (ideal_frame_size_bytes * 80) / 100;
190 detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
191 }
192
Erik Språng6c072ef2019-04-01 10:57:28193 // Expect 5% overshoot for network rate, see above.
194 const absl::optional<double> network_utilization_factor =
195 detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis());
196 EXPECT_NEAR(network_utilization_factor.value_or(-1), 1.05, 0.01);
Erik Språng7ca375c2019-02-06 15:20:17197
Erik Språng6c072ef2019-04-01 10:57:28198 // Expect media rate to be on average correct.
199 const absl::optional<double> media_utilization_factor =
200 detector_.GetMediaRateUtilizationFactor(rtc::TimeMillis());
201 EXPECT_NEAR(media_utilization_factor.value_or(-1), 1.00, 0.01);
202}
yingyingmad3416972023-04-20 06:28:01203
204TEST_P(EncoderOvershootDetectorTest, RecordsZeroErrorMetricWithNoOvershoot) {
205 DataSize ideal_frame_size =
206 target_bitrate_ / Frequency::Hertz(target_framerate_fps_);
207 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
208 rtc::TimeMillis());
209 detector_.OnEncodedFrame(ideal_frame_size.bytes(), rtc::TimeMillis());
210 detector_.Reset();
211
212 const VideoCodecType codec = GetParam().codec_type;
213 const bool is_screenshare = GetParam().is_screenshare;
214 const std::string rmse_histogram_prefix =
215 is_screenshare ? "WebRTC.Video.Screenshare.RMSEOfEncodingBitrateInKbps."
216 : "WebRTC.Video.RMSEOfEncodingBitrateInKbps.";
217 const std::string overshoot_histogram_prefix =
218 is_screenshare ? "WebRTC.Video.Screenshare.EncodingBitrateOvershoot."
219 : "WebRTC.Video.EncodingBitrateOvershoot.";
220 // RMSE and overshoot percent = 0, since we used ideal frame size.
221 EXPECT_METRIC_EQ(1, metrics::NumSamples(rmse_histogram_prefix +
222 CodecTypeToHistogramSuffix(codec)));
223 EXPECT_METRIC_EQ(
224 1, metrics::NumEvents(
225 rmse_histogram_prefix + CodecTypeToHistogramSuffix(codec), 0));
226
227 EXPECT_METRIC_EQ(1, metrics::NumSamples(overshoot_histogram_prefix +
228 CodecTypeToHistogramSuffix(codec)));
229 EXPECT_METRIC_EQ(1, metrics::NumEvents(overshoot_histogram_prefix +
230 CodecTypeToHistogramSuffix(codec),
231 0));
232}
233
234TEST_P(EncoderOvershootDetectorTest,
235 RecordScreenshareZeroMetricWithNoOvershoot) {
236 DataSize ideal_frame_size =
237 target_bitrate_ / Frequency::Hertz(target_framerate_fps_);
238 // Use target frame size with 50% overshoot.
239 DataSize target_frame_size = ideal_frame_size * 3 / 2;
240 detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
241 rtc::TimeMillis());
242 detector_.OnEncodedFrame(target_frame_size.bytes(), rtc::TimeMillis());
243 detector_.Reset();
244
245 const VideoCodecType codec = GetParam().codec_type;
246 const bool is_screenshare = GetParam().is_screenshare;
247 const std::string rmse_histogram_prefix =
248 is_screenshare ? "WebRTC.Video.Screenshare.RMSEOfEncodingBitrateInKbps."
249 : "WebRTC.Video.RMSEOfEncodingBitrateInKbps.";
250 const std::string overshoot_histogram_prefix =
251 is_screenshare ? "WebRTC.Video.Screenshare.EncodingBitrateOvershoot."
252 : "WebRTC.Video.EncodingBitrateOvershoot.";
253 // Use ideal_frame_size_kbits to represnt ideal_frame_size.bytes()*8/1000,
254 // then rmse_in_kbps = ideal_frame_size_kbits/2
255 // since we use target frame size with 50% overshoot.
256 int64_t rmse_in_kbps = ideal_frame_size.bytes() * 8 / 1000 / 2;
257 EXPECT_METRIC_EQ(1, metrics::NumSamples(rmse_histogram_prefix +
258 CodecTypeToHistogramSuffix(codec)));
259 EXPECT_METRIC_EQ(1, metrics::NumEvents(rmse_histogram_prefix +
260 CodecTypeToHistogramSuffix(codec),
261 rmse_in_kbps));
262 // overshoot percent = 50, since we used ideal_frame_size * 3 / 2;
263 EXPECT_METRIC_EQ(1, metrics::NumSamples(overshoot_histogram_prefix +
264 CodecTypeToHistogramSuffix(codec)));
265 EXPECT_METRIC_EQ(1, metrics::NumEvents(overshoot_histogram_prefix +
266 CodecTypeToHistogramSuffix(codec),
267 50));
268}
269
270INSTANTIATE_TEST_SUITE_P(
271 PerCodecType,
272 EncoderOvershootDetectorTest,
273 ValuesIn<TestParams>({{VideoCodecType::kVideoCodecVP8, false},
274 {VideoCodecType::kVideoCodecVP8, true},
275 {VideoCodecType::kVideoCodecVP9, false},
276 {VideoCodecType::kVideoCodecVP9, true},
277 {VideoCodecType::kVideoCodecAV1, false},
278 {VideoCodecType::kVideoCodecAV1, true},
279 {VideoCodecType::kVideoCodecH264, false},
qwu16ae82df72023-09-20 05:10:31280 {VideoCodecType::kVideoCodecH264, true},
281 {VideoCodecType::kVideoCodecH265, false},
282 {VideoCodecType::kVideoCodecH265, true}}));
yingyingmad3416972023-04-20 06:28:01283
Erik Språng7ca375c2019-02-06 15:20:17284} // namespace webrtc