summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc')
-rw-r--r--third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc284
1 files changed, 284 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc b/third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc
new file mode 100644
index 0000000000..2178957889
--- /dev/null
+++ b/third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "video/encoder_overshoot_detector.h"
+
+#include <string>
+
+#include "api/units/data_rate.h"
+#include "rtc_base/fake_clock.h"
+#include "rtc_base/time_utils.h"
+#include "system_wrappers/include/metrics.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+namespace {
+
+using ::testing::TestWithParam;
+using ::testing::ValuesIn;
+
+static std::string CodecTypeToHistogramSuffix(VideoCodecType codec) {
+ switch (codec) {
+ case kVideoCodecVP8:
+ return "Vp8";
+ case kVideoCodecVP9:
+ return "Vp9";
+ case kVideoCodecAV1:
+ return "Av1";
+ case kVideoCodecH264:
+ return "H264";
+ case kVideoCodecH265:
+ return "H265";
+ case kVideoCodecGeneric:
+ return "Generic";
+ case kVideoCodecMultiplex:
+ return "Multiplex";
+ }
+}
+
+struct TestParams {
+ VideoCodecType codec_type;
+ bool is_screenshare;
+};
+
+} // namespace
+
+class EncoderOvershootDetectorTest : public TestWithParam<TestParams> {
+ public:
+ static constexpr int kDefaultBitrateBps = 300000;
+ static constexpr double kDefaultFrameRateFps = 15;
+ EncoderOvershootDetectorTest()
+ : detector_(kWindowSizeMs,
+ GetParam().codec_type,
+ GetParam().is_screenshare),
+ target_bitrate_(DataRate::BitsPerSec(kDefaultBitrateBps)),
+ target_framerate_fps_(kDefaultFrameRateFps) {}
+
+ protected:
+ void SetUp() override { metrics::Reset(); }
+ void RunConstantUtilizationTest(double actual_utilization_factor,
+ double expected_utilization_factor,
+ double allowed_error,
+ int64_t test_duration_ms) {
+ const int frame_size_bytes =
+ static_cast<int>(actual_utilization_factor *
+ (target_bitrate_.bps() / target_framerate_fps_) / 8);
+ detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
+ rtc::TimeMillis());
+
+ if (rtc::TimeMillis() == 0) {
+ // Encode a first frame which by definition has no overuse factor.
+ detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
+ clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
+ }
+
+ int64_t runtime_us = 0;
+ while (runtime_us < test_duration_ms * 1000) {
+ detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
+ runtime_us += rtc::kNumMicrosecsPerSec / target_framerate_fps_;
+ clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
+ }
+
+ // At constant utilization, both network and media utilization should be
+ // close to expected.
+ const absl::optional<double> network_utilization_factor =
+ detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis());
+ EXPECT_NEAR(network_utilization_factor.value_or(-1),
+ expected_utilization_factor, allowed_error);
+
+ const absl::optional<double> media_utilization_factor =
+ detector_.GetMediaRateUtilizationFactor(rtc::TimeMillis());
+ EXPECT_NEAR(media_utilization_factor.value_or(-1),
+ expected_utilization_factor, allowed_error);
+ }
+
+ static constexpr int64_t kWindowSizeMs = 3000;
+ EncoderOvershootDetector detector_;
+ rtc::ScopedFakeClock clock_;
+ DataRate target_bitrate_;
+ double target_framerate_fps_;
+};
+
+TEST_P(EncoderOvershootDetectorTest, NoUtilizationIfNoRate) {
+ const int frame_size_bytes = 1000;
+ const int64_t time_interval_ms = 33;
+ detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
+ rtc::TimeMillis());
+
+ // No data points, can't determine overshoot rate.
+ EXPECT_FALSE(
+ detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis()).has_value());
+
+ detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
+ clock_.AdvanceTime(TimeDelta::Millis(time_interval_ms));
+ EXPECT_TRUE(
+ detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis()).has_value());
+}
+
+TEST_P(EncoderOvershootDetectorTest, OptimalSize) {
+ // Optimally behaved encoder.
+ // Allow some error margin due to rounding errors, eg due to frame
+ // interval not being an integer.
+ RunConstantUtilizationTest(1.0, 1.0, 0.01, kWindowSizeMs);
+}
+
+TEST_P(EncoderOvershootDetectorTest, Undershoot) {
+ // Undershoot, reported utilization factor should be capped to 1.0 so
+ // that we don't incorrectly boost encoder bitrate during movement.
+ RunConstantUtilizationTest(0.5, 1.0, 0.00, kWindowSizeMs);
+}
+
+TEST_P(EncoderOvershootDetectorTest, Overshoot) {
+ // Overshoot by 20%.
+ // Allow some error margin due to rounding errors.
+ RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs);
+}
+
+TEST_P(EncoderOvershootDetectorTest, ConstantOvershootVaryingRates) {
+ // Overshoot by 20%, but vary framerate and bitrate.
+ // Allow some error margin due to rounding errors.
+ RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs);
+ target_framerate_fps_ /= 2;
+ RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
+ target_bitrate_ = DataRate::BitsPerSec(target_bitrate_.bps() / 2);
+ RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
+}
+
+TEST_P(EncoderOvershootDetectorTest, ConstantRateVaryingOvershoot) {
+ // Overshoot by 10%, keep framerate and bitrate constant.
+ // Allow some error margin due to rounding errors.
+ RunConstantUtilizationTest(1.1, 1.1, 0.01, kWindowSizeMs);
+ // Change overshoot to 20%, run for half window and expect overshoot
+ // to be 15%.
+ RunConstantUtilizationTest(1.2, 1.15, 0.01, kWindowSizeMs / 2);
+ // Keep running at 20% overshoot, after window is full that should now
+ // be the reported overshoot.
+ RunConstantUtilizationTest(1.2, 1.2, 0.01, kWindowSizeMs / 2);
+}
+
+TEST_P(EncoderOvershootDetectorTest, PartialOvershoot) {
+ const int ideal_frame_size_bytes =
+ (target_bitrate_.bps() / target_framerate_fps_) / 8;
+ detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
+ rtc::TimeMillis());
+
+ // Test scenario with average bitrate matching the target bitrate, but
+ // with some utilization factor penalty as the frames can't be paced out
+ // on the network at the target rate.
+ // Insert a series of four frames:
+ // 1) 20% overshoot, not penalized as buffer if empty.
+ // 2) 20% overshoot, the 20% overshoot from the first frame is penalized.
+ // 3) 20% undershoot, negating the overshoot from the last frame.
+ // 4) 20% undershoot, no penalty.
+ // On average then utilization penalty is thus 5%.
+
+ int64_t runtime_us = 0;
+ int i = 0;
+ while (runtime_us < kWindowSizeMs * rtc::kNumMicrosecsPerMillisec) {
+ runtime_us += rtc::kNumMicrosecsPerSec / target_framerate_fps_;
+ clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
+ int frame_size_bytes = (i++ % 4 < 2) ? (ideal_frame_size_bytes * 120) / 100
+ : (ideal_frame_size_bytes * 80) / 100;
+ detector_.OnEncodedFrame(frame_size_bytes, rtc::TimeMillis());
+ }
+
+ // Expect 5% overshoot for network rate, see above.
+ const absl::optional<double> network_utilization_factor =
+ detector_.GetNetworkRateUtilizationFactor(rtc::TimeMillis());
+ EXPECT_NEAR(network_utilization_factor.value_or(-1), 1.05, 0.01);
+
+ // Expect media rate to be on average correct.
+ const absl::optional<double> media_utilization_factor =
+ detector_.GetMediaRateUtilizationFactor(rtc::TimeMillis());
+ EXPECT_NEAR(media_utilization_factor.value_or(-1), 1.00, 0.01);
+}
+
+TEST_P(EncoderOvershootDetectorTest, RecordsZeroErrorMetricWithNoOvershoot) {
+ DataSize ideal_frame_size =
+ target_bitrate_ / Frequency::Hertz(target_framerate_fps_);
+ detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
+ rtc::TimeMillis());
+ detector_.OnEncodedFrame(ideal_frame_size.bytes(), rtc::TimeMillis());
+ detector_.Reset();
+
+ const VideoCodecType codec = GetParam().codec_type;
+ const bool is_screenshare = GetParam().is_screenshare;
+ const std::string rmse_histogram_prefix =
+ is_screenshare ? "WebRTC.Video.Screenshare.RMSEOfEncodingBitrateInKbps."
+ : "WebRTC.Video.RMSEOfEncodingBitrateInKbps.";
+ const std::string overshoot_histogram_prefix =
+ is_screenshare ? "WebRTC.Video.Screenshare.EncodingBitrateOvershoot."
+ : "WebRTC.Video.EncodingBitrateOvershoot.";
+ // RMSE and overshoot percent = 0, since we used ideal frame size.
+ EXPECT_METRIC_EQ(1, metrics::NumSamples(rmse_histogram_prefix +
+ CodecTypeToHistogramSuffix(codec)));
+ EXPECT_METRIC_EQ(
+ 1, metrics::NumEvents(
+ rmse_histogram_prefix + CodecTypeToHistogramSuffix(codec), 0));
+
+ EXPECT_METRIC_EQ(1, metrics::NumSamples(overshoot_histogram_prefix +
+ CodecTypeToHistogramSuffix(codec)));
+ EXPECT_METRIC_EQ(1, metrics::NumEvents(overshoot_histogram_prefix +
+ CodecTypeToHistogramSuffix(codec),
+ 0));
+}
+
+TEST_P(EncoderOvershootDetectorTest,
+ RecordScreenshareZeroMetricWithNoOvershoot) {
+ DataSize ideal_frame_size =
+ target_bitrate_ / Frequency::Hertz(target_framerate_fps_);
+ // Use target frame size with 50% overshoot.
+ DataSize target_frame_size = ideal_frame_size * 3 / 2;
+ detector_.SetTargetRate(target_bitrate_, target_framerate_fps_,
+ rtc::TimeMillis());
+ detector_.OnEncodedFrame(target_frame_size.bytes(), rtc::TimeMillis());
+ detector_.Reset();
+
+ const VideoCodecType codec = GetParam().codec_type;
+ const bool is_screenshare = GetParam().is_screenshare;
+ const std::string rmse_histogram_prefix =
+ is_screenshare ? "WebRTC.Video.Screenshare.RMSEOfEncodingBitrateInKbps."
+ : "WebRTC.Video.RMSEOfEncodingBitrateInKbps.";
+ const std::string overshoot_histogram_prefix =
+ is_screenshare ? "WebRTC.Video.Screenshare.EncodingBitrateOvershoot."
+ : "WebRTC.Video.EncodingBitrateOvershoot.";
+ // Use ideal_frame_size_kbits to represnt ideal_frame_size.bytes()*8/1000,
+ // then rmse_in_kbps = ideal_frame_size_kbits/2
+ // since we use target frame size with 50% overshoot.
+ int64_t rmse_in_kbps = ideal_frame_size.bytes() * 8 / 1000 / 2;
+ EXPECT_METRIC_EQ(1, metrics::NumSamples(rmse_histogram_prefix +
+ CodecTypeToHistogramSuffix(codec)));
+ EXPECT_METRIC_EQ(1, metrics::NumEvents(rmse_histogram_prefix +
+ CodecTypeToHistogramSuffix(codec),
+ rmse_in_kbps));
+ // overshoot percent = 50, since we used ideal_frame_size * 3 / 2;
+ EXPECT_METRIC_EQ(1, metrics::NumSamples(overshoot_histogram_prefix +
+ CodecTypeToHistogramSuffix(codec)));
+ EXPECT_METRIC_EQ(1, metrics::NumEvents(overshoot_histogram_prefix +
+ CodecTypeToHistogramSuffix(codec),
+ 50));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ PerCodecType,
+ EncoderOvershootDetectorTest,
+ ValuesIn<TestParams>({{VideoCodecType::kVideoCodecVP8, false},
+ {VideoCodecType::kVideoCodecVP8, true},
+ {VideoCodecType::kVideoCodecVP9, false},
+ {VideoCodecType::kVideoCodecVP9, true},
+ {VideoCodecType::kVideoCodecAV1, false},
+ {VideoCodecType::kVideoCodecAV1, true},
+ {VideoCodecType::kVideoCodecH264, false},
+ {VideoCodecType::kVideoCodecH264, true},
+ {VideoCodecType::kVideoCodecH265, false},
+ {VideoCodecType::kVideoCodecH265, true}}));
+
+} // namespace webrtc