diff options
Diffstat (limited to 'third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/video/encoder_overshoot_detector_unittest.cc | 284 |
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 |