summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/encoder_bitrate_adjuster_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/video/encoder_bitrate_adjuster_unittest.cc')
-rw-r--r--third_party/libwebrtc/video/encoder_bitrate_adjuster_unittest.cc506
1 files changed, 506 insertions, 0 deletions
diff --git a/third_party/libwebrtc/video/encoder_bitrate_adjuster_unittest.cc b/third_party/libwebrtc/video/encoder_bitrate_adjuster_unittest.cc
new file mode 100644
index 0000000000..4ec223a208
--- /dev/null
+++ b/third_party/libwebrtc/video/encoder_bitrate_adjuster_unittest.cc
@@ -0,0 +1,506 @@
+/*
+ * 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_bitrate_adjuster.h"
+
+#include <memory>
+#include <vector>
+
+#include "api/units/data_rate.h"
+#include "rtc_base/fake_clock.h"
+#include "rtc_base/numerics/safe_conversions.h"
+#include "test/field_trial.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace test {
+
+class EncoderBitrateAdjusterTest : public ::testing::Test {
+ public:
+ static constexpr int64_t kWindowSizeMs = 3000;
+ static constexpr int kDefaultBitrateBps = 300000;
+ static constexpr int kDefaultFrameRateFps = 30;
+ // For network utilization higher than media utilization, loop over a
+ // sequence where the first half undershoots and the second half overshoots
+ // by the same amount.
+ static constexpr int kSequenceLength = 4;
+ static_assert(kSequenceLength % 2 == 0, "Sequence length must be even.");
+
+ EncoderBitrateAdjusterTest()
+ : target_bitrate_(DataRate::BitsPerSec(kDefaultBitrateBps)),
+ target_framerate_fps_(kDefaultFrameRateFps),
+ tl_pattern_idx_{},
+ sequence_idx_{} {}
+
+ protected:
+ void SetUpAdjuster(size_t num_spatial_layers,
+ size_t num_temporal_layers,
+ bool vp9_svc) {
+ // Initialize some default VideoCodec instance with the given number of
+ // layers.
+ if (vp9_svc) {
+ codec_.codecType = VideoCodecType::kVideoCodecVP9;
+ codec_.numberOfSimulcastStreams = 1;
+ codec_.VP9()->numberOfSpatialLayers = num_spatial_layers;
+ codec_.VP9()->numberOfTemporalLayers = num_temporal_layers;
+ for (size_t si = 0; si < num_spatial_layers; ++si) {
+ codec_.spatialLayers[si].minBitrate = 100 * (1 << si);
+ codec_.spatialLayers[si].targetBitrate = 200 * (1 << si);
+ codec_.spatialLayers[si].maxBitrate = 300 * (1 << si);
+ codec_.spatialLayers[si].active = true;
+ codec_.spatialLayers[si].numberOfTemporalLayers = num_temporal_layers;
+ }
+ } else {
+ codec_.codecType = VideoCodecType::kVideoCodecVP8;
+ codec_.numberOfSimulcastStreams = num_spatial_layers;
+ codec_.VP8()->numberOfTemporalLayers = num_temporal_layers;
+ for (size_t si = 0; si < num_spatial_layers; ++si) {
+ codec_.simulcastStream[si].minBitrate = 100 * (1 << si);
+ codec_.simulcastStream[si].targetBitrate = 200 * (1 << si);
+ codec_.simulcastStream[si].maxBitrate = 300 * (1 << si);
+ codec_.simulcastStream[si].active = true;
+ codec_.simulcastStream[si].numberOfTemporalLayers = num_temporal_layers;
+ }
+ }
+
+ for (size_t si = 0; si < num_spatial_layers; ++si) {
+ encoder_info_.fps_allocation[si].resize(num_temporal_layers);
+ double fraction = 1.0;
+ for (int ti = num_temporal_layers - 1; ti >= 0; --ti) {
+ encoder_info_.fps_allocation[si][ti] = static_cast<uint8_t>(
+ VideoEncoder::EncoderInfo::kMaxFramerateFraction * fraction + 0.5);
+ fraction /= 2.0;
+ }
+ }
+
+ adjuster_ = std::make_unique<EncoderBitrateAdjuster>(codec_);
+ adjuster_->OnEncoderInfo(encoder_info_);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ }
+
+ void InsertFrames(std::vector<std::vector<double>> media_utilization_factors,
+ int64_t duration_ms) {
+ InsertFrames(media_utilization_factors, media_utilization_factors,
+ duration_ms);
+ }
+
+ void InsertFrames(
+ std::vector<std::vector<double>> media_utilization_factors,
+ std::vector<std::vector<double>> network_utilization_factors,
+ int64_t duration_ms) {
+ RTC_DCHECK_EQ(media_utilization_factors.size(),
+ network_utilization_factors.size());
+
+ const int64_t start_us = rtc::TimeMicros();
+ while (rtc::TimeMicros() <
+ start_us + (duration_ms * rtc::kNumMicrosecsPerMillisec)) {
+ clock_.AdvanceTime(TimeDelta::Seconds(1) / target_framerate_fps_);
+ for (size_t si = 0; si < NumSpatialLayers(); ++si) {
+ const std::vector<int>& tl_pattern =
+ kTlPatterns[NumTemporalLayers(si) - 1];
+ const size_t ti =
+ tl_pattern[(tl_pattern_idx_[si]++) % tl_pattern.size()];
+
+ uint32_t layer_bitrate_bps =
+ current_adjusted_allocation_.GetBitrate(si, ti);
+ double layer_framerate_fps = target_framerate_fps_;
+ if (encoder_info_.fps_allocation[si].size() > ti) {
+ uint8_t layer_fps_fraction = encoder_info_.fps_allocation[si][ti];
+ if (ti > 0) {
+ // We're interested in the frame rate for this layer only, not
+ // cumulative frame rate.
+ layer_fps_fraction -= encoder_info_.fps_allocation[si][ti - 1];
+ }
+ layer_framerate_fps =
+ (target_framerate_fps_ * layer_fps_fraction) /
+ VideoEncoder::EncoderInfo::kMaxFramerateFraction;
+ }
+ double media_utilization_factor = 1.0;
+ double network_utilization_factor = 1.0;
+ if (media_utilization_factors.size() > si) {
+ RTC_DCHECK_EQ(media_utilization_factors[si].size(),
+ network_utilization_factors[si].size());
+ if (media_utilization_factors[si].size() > ti) {
+ media_utilization_factor = media_utilization_factors[si][ti];
+ network_utilization_factor = network_utilization_factors[si][ti];
+ }
+ }
+ RTC_DCHECK_GE(network_utilization_factor, media_utilization_factor);
+
+ // Frame size based on constant (media) overshoot.
+ const size_t media_frame_size = media_utilization_factor *
+ (layer_bitrate_bps / 8.0) /
+ layer_framerate_fps;
+
+ constexpr int kFramesWithPenalty = (kSequenceLength / 2) - 1;
+ RTC_DCHECK_GT(kFramesWithPenalty, 0);
+
+ // The positive/negative size diff needed to achieve network rate but
+ // not media rate penalty is the difference between the utilization
+ // factors times the media rate frame size, then scaled by the fraction
+ // between total frames and penalized frames in the sequence.
+ // Cap to media frame size to avoid negative size undershoot.
+ const size_t network_frame_size_diff_bytes = std::min(
+ media_frame_size,
+ static_cast<size_t>(
+ (((network_utilization_factor - media_utilization_factor) *
+ media_frame_size) *
+ kSequenceLength) /
+ kFramesWithPenalty +
+ 0.5));
+
+ int sequence_idx = sequence_idx_[si][ti];
+ sequence_idx_[si][ti] = (sequence_idx_[si][ti] + 1) % kSequenceLength;
+ const DataSize frame_size = DataSize::Bytes(
+ (sequence_idx < kSequenceLength / 2)
+ ? media_frame_size - network_frame_size_diff_bytes
+ : media_frame_size + network_frame_size_diff_bytes);
+
+ adjuster_->OnEncodedFrame(frame_size, si, ti);
+ sequence_idx = ++sequence_idx % kSequenceLength;
+ }
+ }
+ }
+
+ size_t NumSpatialLayers() const {
+ if (codec_.codecType == VideoCodecType::kVideoCodecVP9) {
+ return codec_.VP9().numberOfSpatialLayers;
+ }
+ return codec_.numberOfSimulcastStreams;
+ }
+
+ size_t NumTemporalLayers(int spatial_index) {
+ if (codec_.codecType == VideoCodecType::kVideoCodecVP9) {
+ return codec_.spatialLayers[spatial_index].numberOfTemporalLayers;
+ }
+ return codec_.simulcastStream[spatial_index].numberOfTemporalLayers;
+ }
+
+ void ExpectNear(const VideoBitrateAllocation& expected_allocation,
+ const VideoBitrateAllocation& actual_allocation,
+ double allowed_error_fraction) {
+ for (size_t si = 0; si < kMaxSpatialLayers; ++si) {
+ for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) {
+ if (expected_allocation.HasBitrate(si, ti)) {
+ EXPECT_TRUE(actual_allocation.HasBitrate(si, ti));
+ uint32_t expected_layer_bitrate_bps =
+ expected_allocation.GetBitrate(si, ti);
+ EXPECT_NEAR(expected_layer_bitrate_bps,
+ actual_allocation.GetBitrate(si, ti),
+ static_cast<uint32_t>(expected_layer_bitrate_bps *
+ allowed_error_fraction));
+ } else {
+ EXPECT_FALSE(actual_allocation.HasBitrate(si, ti));
+ }
+ }
+ }
+ }
+
+ VideoBitrateAllocation MultiplyAllocation(
+ const VideoBitrateAllocation& allocation,
+ double factor) {
+ VideoBitrateAllocation multiplied_allocation;
+ for (size_t si = 0; si < kMaxSpatialLayers; ++si) {
+ for (size_t ti = 0; ti < kMaxTemporalStreams; ++ti) {
+ if (allocation.HasBitrate(si, ti)) {
+ multiplied_allocation.SetBitrate(
+ si, ti,
+ static_cast<uint32_t>(factor * allocation.GetBitrate(si, ti) +
+ 0.5));
+ }
+ }
+ }
+ return multiplied_allocation;
+ }
+
+ VideoCodec codec_;
+ VideoEncoder::EncoderInfo encoder_info_;
+ std::unique_ptr<EncoderBitrateAdjuster> adjuster_;
+ VideoBitrateAllocation current_input_allocation_;
+ VideoBitrateAllocation current_adjusted_allocation_;
+ rtc::ScopedFakeClock clock_;
+ DataRate target_bitrate_;
+ double target_framerate_fps_;
+ int tl_pattern_idx_[kMaxSpatialLayers];
+ int sequence_idx_[kMaxSpatialLayers][kMaxTemporalStreams];
+
+ const std::vector<int> kTlPatterns[kMaxTemporalStreams] = {
+ {0},
+ {0, 1},
+ {0, 2, 1, 2},
+ {0, 3, 2, 3, 1, 3, 2, 3}};
+};
+
+TEST_F(EncoderBitrateAdjusterTest, SingleLayerOptimal) {
+ // Single layer, well behaved encoder.
+ current_input_allocation_.SetBitrate(0, 0, 300000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 1, false);
+ InsertFrames({{1.0}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // Adjusted allocation near input. Allow 1% error margin due to rounding
+ // errors etc.
+ ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.01);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, SingleLayerOveruse) {
+ // Single layer, well behaved encoder.
+ current_input_allocation_.SetBitrate(0, 0, 300000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 1, false);
+ InsertFrames({{1.2}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // Adjusted allocation lowered by 20%.
+ ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.2),
+ current_adjusted_allocation_, 0.01);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, SingleLayerUnderuse) {
+ // Single layer, well behaved encoder.
+ current_input_allocation_.SetBitrate(0, 0, 300000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 1, false);
+ InsertFrames({{0.5}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // Undershoot, adjusted should exactly match input.
+ ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.00);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersOptimalSize) {
+ // Three temporal layers, 60%/20%/20% bps distro, well behaved encoder.
+ current_input_allocation_.SetBitrate(0, 0, 180000);
+ current_input_allocation_.SetBitrate(0, 1, 60000);
+ current_input_allocation_.SetBitrate(0, 2, 60000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 3, false);
+ InsertFrames({{1.0, 1.0, 1.0}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.01);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersOvershoot) {
+ // Three temporal layers, 60%/20%/20% bps distro.
+ // 10% overshoot on all layers.
+ current_input_allocation_.SetBitrate(0, 0, 180000);
+ current_input_allocation_.SetBitrate(0, 1, 60000);
+ current_input_allocation_.SetBitrate(0, 2, 60000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 3, false);
+ InsertFrames({{1.1, 1.1, 1.1}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // Adjusted allocation lowered by 10%.
+ ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.1),
+ current_adjusted_allocation_, 0.01);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersUndershoot) {
+ // Three temporal layers, 60%/20%/20% bps distro, undershoot all layers.
+ current_input_allocation_.SetBitrate(0, 0, 180000);
+ current_input_allocation_.SetBitrate(0, 1, 60000);
+ current_input_allocation_.SetBitrate(0, 2, 60000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 3, false);
+ InsertFrames({{0.8, 0.8, 0.8}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // Adjusted allocation identical since we don't boost bitrates.
+ ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.0);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersSkewedOvershoot) {
+ // Three temporal layers, 60%/20%/20% bps distro.
+ // 10% overshoot on base layer, 20% on higher layers.
+ current_input_allocation_.SetBitrate(0, 0, 180000);
+ current_input_allocation_.SetBitrate(0, 1, 60000);
+ current_input_allocation_.SetBitrate(0, 2, 60000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 3, false);
+ InsertFrames({{1.1, 1.2, 1.2}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // Expected overshoot is weighted by bitrate:
+ // (0.6 * 1.1 + 0.2 * 1.2 + 0.2 * 1.2) = 1.14
+ ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.14),
+ current_adjusted_allocation_, 0.01);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, ThreeTemporalLayersNonLayeredEncoder) {
+ // Three temporal layers, 60%/20%/20% bps allocation, 10% overshoot,
+ // encoder does not actually support temporal layers.
+ current_input_allocation_.SetBitrate(0, 0, 180000);
+ current_input_allocation_.SetBitrate(0, 1, 60000);
+ current_input_allocation_.SetBitrate(0, 2, 60000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 1, false);
+ InsertFrames({{1.1}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // Expect the actual 10% overuse to be detected and the allocation to
+ // only contain the one entry.
+ VideoBitrateAllocation expected_allocation;
+ expected_allocation.SetBitrate(
+ 0, 0,
+ static_cast<uint32_t>(current_input_allocation_.get_sum_bps() / 1.10));
+ ExpectNear(expected_allocation, current_adjusted_allocation_, 0.01);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, IgnoredStream) {
+ // Encoder with three temporal layers, but in a mode that does not support
+ // deterministic frame rate. Those are ignored, even if bitrate overshoots.
+ current_input_allocation_.SetBitrate(0, 0, 180000);
+ current_input_allocation_.SetBitrate(0, 1, 60000);
+ target_framerate_fps_ = 30;
+ SetUpAdjuster(1, 1, false);
+ encoder_info_.fps_allocation[0].clear();
+ adjuster_->OnEncoderInfo(encoder_info_);
+
+ InsertFrames({{1.1}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+
+ // Values passed through.
+ ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.00);
+}
+
+TEST_F(EncoderBitrateAdjusterTest, DifferentSpatialOvershoots) {
+ // Two streams, both with three temporal layers.
+ // S0 has 5% overshoot, S1 has 25% overshoot.
+ current_input_allocation_.SetBitrate(0, 0, 180000);
+ current_input_allocation_.SetBitrate(0, 1, 60000);
+ current_input_allocation_.SetBitrate(0, 2, 60000);
+ current_input_allocation_.SetBitrate(1, 0, 400000);
+ current_input_allocation_.SetBitrate(1, 1, 150000);
+ current_input_allocation_.SetBitrate(1, 2, 150000);
+ target_framerate_fps_ = 30;
+ // Run twice, once configured as simulcast and once as VP9 SVC.
+ for (int i = 0; i < 2; ++i) {
+ SetUpAdjuster(2, 3, i == 0);
+ InsertFrames({{1.05, 1.05, 1.05}, {1.25, 1.25, 1.25}}, kWindowSizeMs);
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ VideoBitrateAllocation expected_allocation;
+ for (size_t ti = 0; ti < 3; ++ti) {
+ expected_allocation.SetBitrate(
+ 0, ti,
+ static_cast<uint32_t>(current_input_allocation_.GetBitrate(0, ti) /
+ 1.05));
+ expected_allocation.SetBitrate(
+ 1, ti,
+ static_cast<uint32_t>(current_input_allocation_.GetBitrate(1, ti) /
+ 1.25));
+ }
+ ExpectNear(expected_allocation, current_adjusted_allocation_, 0.01);
+ }
+}
+
+TEST_F(EncoderBitrateAdjusterTest, HeadroomAllowsOvershootToMediaRate) {
+ // Two streams, both with three temporal layers.
+ // Media rate is 1.0, but network rate is higher.
+ ScopedFieldTrials field_trial(
+ "WebRTC-VideoRateControl/adjuster_use_headroom:true/");
+
+ const uint32_t kS0Bitrate = 300000;
+ const uint32_t kS1Bitrate = 900000;
+ current_input_allocation_.SetBitrate(0, 0, kS0Bitrate / 3);
+ current_input_allocation_.SetBitrate(0, 1, kS0Bitrate / 3);
+ current_input_allocation_.SetBitrate(0, 2, kS0Bitrate / 3);
+ current_input_allocation_.SetBitrate(1, 0, kS1Bitrate / 3);
+ current_input_allocation_.SetBitrate(1, 1, kS1Bitrate / 3);
+ current_input_allocation_.SetBitrate(1, 2, kS1Bitrate / 3);
+
+ target_framerate_fps_ = 30;
+
+ // Run twice, once configured as simulcast and once as VP9 SVC.
+ for (int i = 0; i < 2; ++i) {
+ SetUpAdjuster(2, 3, i == 0);
+ // Network rate has 10% overshoot, but media rate is correct at 1.0.
+ InsertFrames({{1.0, 1.0, 1.0}, {1.0, 1.0, 1.0}},
+ {{1.1, 1.1, 1.1}, {1.1, 1.1, 1.1}},
+ kWindowSizeMs * kSequenceLength);
+
+ // Push back by 10%.
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.1),
+ current_adjusted_allocation_, 0.01);
+
+ // Add 10% link headroom, overshoot is now allowed.
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_,
+ DataRate::BitsPerSec(current_input_allocation_.get_sum_bps() *
+ 1.1)));
+ ExpectNear(current_input_allocation_, current_adjusted_allocation_, 0.01);
+ }
+}
+
+TEST_F(EncoderBitrateAdjusterTest, DontExceedMediaRateEvenWithHeadroom) {
+ // Two streams, both with three temporal layers.
+ // Media rate is 1.1, but network rate is higher.
+ ScopedFieldTrials field_trial(
+ "WebRTC-VideoRateControl/adjuster_use_headroom:true/");
+
+ const uint32_t kS0Bitrate = 300000;
+ const uint32_t kS1Bitrate = 900000;
+ current_input_allocation_.SetBitrate(0, 0, kS0Bitrate / 3);
+ current_input_allocation_.SetBitrate(0, 1, kS0Bitrate / 3);
+ current_input_allocation_.SetBitrate(0, 2, kS0Bitrate / 3);
+ current_input_allocation_.SetBitrate(1, 0, kS1Bitrate / 3);
+ current_input_allocation_.SetBitrate(1, 1, kS1Bitrate / 3);
+ current_input_allocation_.SetBitrate(1, 2, kS1Bitrate / 3);
+
+ target_framerate_fps_ = 30;
+
+ // Run twice, once configured as simulcast and once as VP9 SVC.
+ for (int i = 0; i < 2; ++i) {
+ SetUpAdjuster(2, 3, i == 0);
+ // Network rate has 30% overshoot, media rate has 10% overshoot.
+ InsertFrames({{1.1, 1.1, 1.1}, {1.1, 1.1, 1.1}},
+ {{1.3, 1.3, 1.3}, {1.3, 1.3, 1.3}},
+ kWindowSizeMs * kSequenceLength);
+
+ // Push back by 30%.
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_));
+ // The up-down causes a bit more noise, allow slightly more error margin.
+ ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.3),
+ current_adjusted_allocation_, 0.015);
+
+ // Add 100% link headroom, overshoot from network to media rate is allowed.
+ current_adjusted_allocation_ =
+ adjuster_->AdjustRateAllocation(VideoEncoder::RateControlParameters(
+ current_input_allocation_, target_framerate_fps_,
+ DataRate::BitsPerSec(current_input_allocation_.get_sum_bps() * 2)));
+ ExpectNear(MultiplyAllocation(current_input_allocation_, 1 / 1.1),
+ current_adjusted_allocation_, 0.015);
+ }
+}
+
+} // namespace test
+} // namespace webrtc