summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/video_coding/svc/svc_rate_allocator_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/modules/video_coding/svc/svc_rate_allocator_unittest.cc')
-rw-r--r--third_party/libwebrtc/modules/video_coding/svc/svc_rate_allocator_unittest.cc599
1 files changed, 599 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/video_coding/svc/svc_rate_allocator_unittest.cc b/third_party/libwebrtc/modules/video_coding/svc/svc_rate_allocator_unittest.cc
new file mode 100644
index 0000000000..44d1eae667
--- /dev/null
+++ b/third_party/libwebrtc/modules/video_coding/svc/svc_rate_allocator_unittest.cc
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2018 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 "modules/video_coding/svc/svc_rate_allocator.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "modules/video_coding/codecs/vp9/svc_config.h"
+#include "rtc_base/checks.h"
+#include "test/field_trial.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+namespace test {
+namespace {
+static VideoCodec Configure(size_t width,
+ size_t height,
+ size_t num_spatial_layers,
+ size_t num_temporal_layers,
+ bool is_screen_sharing) {
+ VideoCodec codec;
+ codec.width = width;
+ codec.height = height;
+ codec.codecType = kVideoCodecVP9;
+ codec.mode = is_screen_sharing ? VideoCodecMode::kScreensharing
+ : VideoCodecMode::kRealtimeVideo;
+
+ std::vector<SpatialLayer> spatial_layers =
+ GetSvcConfig(width, height, 30, /*first_active_layer=*/0,
+ num_spatial_layers, num_temporal_layers, is_screen_sharing);
+ RTC_CHECK_LE(spatial_layers.size(), kMaxSpatialLayers);
+
+ codec.VP9()->numberOfSpatialLayers =
+ std::min<unsigned char>(num_spatial_layers, spatial_layers.size());
+ codec.VP9()->numberOfTemporalLayers = std::min<unsigned char>(
+ num_temporal_layers, spatial_layers.back().numberOfTemporalLayers);
+
+ for (size_t sl_idx = 0; sl_idx < spatial_layers.size(); ++sl_idx) {
+ codec.spatialLayers[sl_idx] = spatial_layers[sl_idx];
+ }
+
+ return codec;
+}
+
+} // namespace
+
+TEST(SvcRateAllocatorTest, SingleLayerFor320x180Input) {
+ VideoCodec codec = Configure(320, 180, 3, 3, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(1000 * 1000, 30));
+
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1), 0u);
+}
+
+TEST(SvcRateAllocatorTest, TwoLayersFor640x360Input) {
+ VideoCodec codec = Configure(640, 360, 3, 3, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(1000 * 1000, 30));
+
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0u);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2), 0u);
+}
+
+TEST(SvcRateAllocatorTest, ThreeLayersFor1280x720Input) {
+ VideoCodec codec = Configure(1280, 720, 3, 3, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(1000 * 1000, 30));
+
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(2), 0u);
+}
+
+TEST(SvcRateAllocatorTest,
+ BaseLayerNonZeroBitrateEvenIfTotalIfLessThanMinimum) {
+ VideoCodec codec = Configure(1280, 720, 3, 3, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ const SpatialLayer* layers = codec.spatialLayers;
+
+ VideoBitrateAllocation allocation = allocator.Allocate(
+ VideoBitrateAllocationParameters(layers[0].minBitrate * 1000 / 2, 30));
+
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_LT(allocation.GetSpatialLayerSum(0), layers[0].minBitrate * 1000);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1), 0u);
+}
+
+TEST(SvcRateAllocatorTest, Disable640x360Layer) {
+ VideoCodec codec = Configure(1280, 720, 3, 3, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ const SpatialLayer* layers = codec.spatialLayers;
+
+ size_t min_bitrate_for_640x360_layer_kbps =
+ layers[0].minBitrate + layers[1].minBitrate;
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(
+ min_bitrate_for_640x360_layer_kbps * 1000 - 1, 30));
+
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1), 0u);
+}
+
+TEST(SvcRateAllocatorTest, Disable1280x720Layer) {
+ VideoCodec codec = Configure(1280, 720, 3, 3, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ const SpatialLayer* layers = codec.spatialLayers;
+
+ size_t min_bitrate_for_1280x720_layer_kbps =
+ layers[0].minBitrate + layers[1].minBitrate + layers[2].minBitrate;
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(
+ min_bitrate_for_1280x720_layer_kbps * 1000 - 1, 30));
+
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0u);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2), 0u);
+}
+
+TEST(SvcRateAllocatorTest, BitrateIsCapped) {
+ VideoCodec codec = Configure(1280, 720, 3, 3, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ const SpatialLayer* layers = codec.spatialLayers;
+
+ const uint32_t link_mbps = 100;
+ VideoBitrateAllocation allocation = allocator.Allocate(
+ VideoBitrateAllocationParameters(link_mbps * 1000000, 30));
+
+ EXPECT_EQ(allocation.get_sum_kbps(),
+ layers[0].maxBitrate + layers[1].maxBitrate + layers[2].maxBitrate);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(0) / 1000, layers[0].maxBitrate);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1) / 1000, layers[1].maxBitrate);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2) / 1000, layers[2].maxBitrate);
+}
+
+TEST(SvcRateAllocatorTest, MinBitrateToGetQualityLayer) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, true);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ const SpatialLayer* layers = codec.spatialLayers;
+
+ EXPECT_LE(codec.VP9()->numberOfSpatialLayers, 3U);
+
+ VideoBitrateAllocation allocation = allocator.Allocate(
+ VideoBitrateAllocationParameters(layers[0].minBitrate * 1000, 30));
+ EXPECT_EQ(allocation.GetSpatialLayerSum(0) / 1000, layers[0].minBitrate);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1), 0UL);
+
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ (layers[0].targetBitrate + layers[1].minBitrate) * 1000, 30));
+ EXPECT_EQ(allocation.GetSpatialLayerSum(0) / 1000, layers[0].targetBitrate);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1) / 1000, layers[1].minBitrate);
+}
+
+TEST(SvcRateAllocatorTest, DeactivateHigherLayers) {
+ for (int deactivated_idx = 2; deactivated_idx >= 0; --deactivated_idx) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, false);
+ EXPECT_LE(codec.VP9()->numberOfSpatialLayers, 3U);
+
+ for (int i = deactivated_idx; i < 3; ++i)
+ codec.spatialLayers[i].active = false;
+
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ VideoBitrateAllocation allocation = allocator.Allocate(
+ VideoBitrateAllocationParameters(10 * 1000 * 1000, 30));
+
+ // Ensure layers spatial_idx < deactivated_idx are activated.
+ for (int spatial_idx = 0; spatial_idx < deactivated_idx; ++spatial_idx) {
+ EXPECT_GT(allocation.GetSpatialLayerSum(spatial_idx), 0UL);
+ }
+
+ // Ensure layers spatial_idx >= deactivated_idx are deactivated.
+ for (int spatial_idx = deactivated_idx; spatial_idx < 3; ++spatial_idx) {
+ EXPECT_EQ(allocation.GetSpatialLayerSum(spatial_idx), 0UL);
+ }
+ }
+}
+
+TEST(SvcRateAllocatorTest, DeactivateLowerLayers) {
+ for (int deactivated_idx = 0; deactivated_idx < 3; ++deactivated_idx) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, false);
+ EXPECT_LE(codec.VP9()->numberOfSpatialLayers, 3U);
+
+ for (int i = deactivated_idx; i >= 0; --i)
+ codec.spatialLayers[i].active = false;
+
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ VideoBitrateAllocation allocation = allocator.Allocate(
+ VideoBitrateAllocationParameters(10 * 1000 * 1000, 30));
+
+ // Ensure layers spatial_idx <= deactivated_idx are deactivated.
+ for (int spatial_idx = 0; spatial_idx <= deactivated_idx; ++spatial_idx) {
+ EXPECT_EQ(allocation.GetSpatialLayerSum(spatial_idx), 0UL);
+ }
+
+ // Ensure layers spatial_idx > deactivated_idx are activated.
+ for (int spatial_idx = deactivated_idx + 1; spatial_idx < 3;
+ ++spatial_idx) {
+ EXPECT_GT(allocation.GetSpatialLayerSum(spatial_idx), 0UL);
+ }
+ }
+}
+
+TEST(SvcRateAllocatorTest, SignalsBwLimited) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ // Rough estimate calculated by hand.
+ uint32_t min_to_enable_all = 900000;
+
+ EXPECT_TRUE(
+ allocator
+ .Allocate(VideoBitrateAllocationParameters(min_to_enable_all / 2, 30))
+ .is_bw_limited());
+
+ EXPECT_FALSE(
+ allocator
+ .Allocate(VideoBitrateAllocationParameters(min_to_enable_all, 30))
+ .is_bw_limited());
+}
+
+TEST(SvcRateAllocatorTest, NoPaddingIfAllLayersAreDeactivated) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, false);
+ EXPECT_EQ(codec.VP9()->numberOfSpatialLayers, 3U);
+ // Deactivation of base layer deactivates all layers.
+ codec.spatialLayers[0].active = false;
+ codec.spatialLayers[1].active = false;
+ codec.spatialLayers[2].active = false;
+ DataRate padding_rate = SvcRateAllocator::GetPaddingBitrate(codec);
+ EXPECT_EQ(padding_rate, DataRate::Zero());
+}
+
+TEST(SvcRateAllocatorTest, FindLayerTogglingThreshold) {
+ // Let's unit test a utility method of the unit test...
+
+ // Predetermined constants indicating the min bitrate needed for two and three
+ // layers to be enabled respectively, using the config from Configure() with
+ // 1280x720 resolution and three spatial layers.
+ const DataRate kTwoLayerMinRate = DataRate::BitsPerSec(299150);
+ const DataRate kThreeLayerMinRate = DataRate::BitsPerSec(891052);
+
+ VideoCodec codec = Configure(1280, 720, 3, 1, false);
+ absl::InlinedVector<DataRate, kMaxSpatialLayers> layer_start_bitrates =
+ SvcRateAllocator::GetLayerStartBitrates(codec);
+ ASSERT_EQ(layer_start_bitrates.size(), 3u);
+ EXPECT_EQ(layer_start_bitrates[1], kTwoLayerMinRate);
+ EXPECT_EQ(layer_start_bitrates[2], kThreeLayerMinRate);
+}
+
+TEST(SvcRateAllocatorTest, SupportsAv1) {
+ VideoCodec codec;
+ codec.width = 640;
+ codec.height = 360;
+ codec.codecType = kVideoCodecAV1;
+ codec.SetScalabilityMode(ScalabilityMode::kL3T3);
+ codec.spatialLayers[0].active = true;
+ codec.spatialLayers[0].minBitrate = 30;
+ codec.spatialLayers[0].targetBitrate = 51;
+ codec.spatialLayers[0].maxBitrate = 73;
+ codec.spatialLayers[1].active = true;
+ codec.spatialLayers[1].minBitrate = 49;
+ codec.spatialLayers[1].targetBitrate = 64;
+ codec.spatialLayers[1].maxBitrate = 97;
+ codec.spatialLayers[2].active = true;
+ codec.spatialLayers[2].minBitrate = 193;
+ codec.spatialLayers[2].targetBitrate = 305;
+ codec.spatialLayers[2].maxBitrate = 418;
+
+ SvcRateAllocator allocator(codec);
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(1'000'000, 30));
+
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(2), 0u);
+}
+
+TEST(SvcRateAllocatorTest, SupportsAv1WithSkippedLayer) {
+ VideoCodec codec;
+ codec.width = 640;
+ codec.height = 360;
+ codec.codecType = kVideoCodecAV1;
+ codec.SetScalabilityMode(ScalabilityMode::kL3T3);
+ codec.spatialLayers[0].active = false;
+ codec.spatialLayers[0].minBitrate = 30;
+ codec.spatialLayers[0].targetBitrate = 51;
+ codec.spatialLayers[0].maxBitrate = 73;
+ codec.spatialLayers[1].active = true;
+ codec.spatialLayers[1].minBitrate = 49;
+ codec.spatialLayers[1].targetBitrate = 64;
+ codec.spatialLayers[1].maxBitrate = 97;
+ codec.spatialLayers[2].active = true;
+ codec.spatialLayers[2].minBitrate = 193;
+ codec.spatialLayers[2].targetBitrate = 305;
+ codec.spatialLayers[2].maxBitrate = 418;
+
+ SvcRateAllocator allocator(codec);
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(1'000'000, 30));
+
+ EXPECT_EQ(allocation.GetSpatialLayerSum(0), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0u);
+ EXPECT_GT(allocation.GetSpatialLayerSum(2), 0u);
+}
+
+TEST(SvcRateAllocatorTest, UsesScalabilityModeToGetNumberOfLayers) {
+ VideoCodec codec;
+ codec.width = 640;
+ codec.height = 360;
+ codec.codecType = kVideoCodecAV1;
+ codec.SetScalabilityMode(ScalabilityMode::kL2T2);
+ codec.spatialLayers[0].active = true;
+ codec.spatialLayers[0].minBitrate = 30;
+ codec.spatialLayers[0].targetBitrate = 51;
+ codec.spatialLayers[0].maxBitrate = 73;
+ codec.spatialLayers[1].active = true;
+ codec.spatialLayers[1].minBitrate = 49;
+ codec.spatialLayers[1].targetBitrate = 64;
+ codec.spatialLayers[1].maxBitrate = 97;
+ codec.spatialLayers[2].active = true;
+ codec.spatialLayers[2].minBitrate = 193;
+ codec.spatialLayers[2].targetBitrate = 305;
+ codec.spatialLayers[2].maxBitrate = 418;
+
+ SvcRateAllocator allocator(codec);
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(1'000'000, 30));
+
+ // Expect bitrates for 2 temporal layers.
+ EXPECT_TRUE(allocation.HasBitrate(1, /*temporal_index=*/0));
+ EXPECT_TRUE(allocation.HasBitrate(1, /*temporal_index=*/1));
+ EXPECT_FALSE(allocation.HasBitrate(1, /*temporal_index=*/2));
+
+ // expect codec.spatialLayers[2].active is ignored because scability mode uses
+ // just 2 spatial layers.
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2), 0u);
+}
+
+TEST(SvcRateAllocatorTest, CapsAllocationToMaxBitrate) {
+ VideoCodec codec = Configure(1280, 720, 3, 3, false);
+ codec.maxBitrate = 70; // Cap the overall max bitrate to 70kbps.
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ // Allocate 3Mbps which should be enough for all layers.
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(3'000'000, 30));
+
+ // The 3Mbps should be capped to 70kbps, so only first layer is active.
+ EXPECT_EQ(allocation.GetSpatialLayerSum(0), 70'000u);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1), 0u);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2), 0u);
+}
+
+class SvcRateAllocatorTestParametrizedContentType
+ : public ::testing::Test,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ SvcRateAllocatorTestParametrizedContentType()
+ : is_screen_sharing_(GetParam()) {}
+
+ const bool is_screen_sharing_;
+};
+
+TEST_P(SvcRateAllocatorTestParametrizedContentType, MaxBitrate) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, is_screen_sharing_);
+ EXPECT_EQ(SvcRateAllocator::GetMaxBitrate(codec),
+ DataRate::KilobitsPerSec(codec.spatialLayers[0].maxBitrate +
+ codec.spatialLayers[1].maxBitrate +
+ codec.spatialLayers[2].maxBitrate));
+
+ // Deactivate middle layer. This causes deactivation of top layer as well.
+ codec.spatialLayers[1].active = false;
+ EXPECT_EQ(SvcRateAllocator::GetMaxBitrate(codec),
+ DataRate::KilobitsPerSec(codec.spatialLayers[0].maxBitrate));
+}
+
+TEST_P(SvcRateAllocatorTestParametrizedContentType, PaddingBitrate) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, is_screen_sharing_);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ DataRate padding_bitrate = SvcRateAllocator::GetPaddingBitrate(codec);
+
+ VideoBitrateAllocation allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(padding_bitrate, 30));
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0UL);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0UL);
+ EXPECT_GT(allocation.GetSpatialLayerSum(2), 0UL);
+
+ // Allocate 90% of padding bitrate. Top layer should be disabled.
+ allocation = allocator.Allocate(
+ VideoBitrateAllocationParameters(9 * padding_bitrate / 10, 30));
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0UL);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0UL);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2), 0UL);
+
+ // Deactivate top layer.
+ codec.spatialLayers[2].active = false;
+
+ padding_bitrate = SvcRateAllocator::GetPaddingBitrate(codec);
+ allocation =
+ allocator.Allocate(VideoBitrateAllocationParameters(padding_bitrate, 30));
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0UL);
+ EXPECT_GT(allocation.GetSpatialLayerSum(1), 0UL);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2), 0UL);
+
+ allocation = allocator.Allocate(
+ VideoBitrateAllocationParameters(9 * padding_bitrate / 10, 30));
+ EXPECT_GT(allocation.GetSpatialLayerSum(0), 0UL);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(1), 0UL);
+ EXPECT_EQ(allocation.GetSpatialLayerSum(2), 0UL);
+
+ // Deactivate all layers.
+ codec.spatialLayers[0].active = false;
+ codec.spatialLayers[1].active = false;
+ codec.spatialLayers[2].active = false;
+
+ padding_bitrate = SvcRateAllocator::GetPaddingBitrate(codec);
+ // No padding expected.
+ EXPECT_EQ(DataRate::Zero(), padding_bitrate);
+}
+
+TEST_P(SvcRateAllocatorTestParametrizedContentType, StableBitrate) {
+ ScopedFieldTrials field_trial(
+ "WebRTC-StableTargetRate/enabled:true,video_hysteresis_factor:1.0,"
+ "screenshare_hysteresis_factor:1.0/");
+
+ const VideoCodec codec = Configure(1280, 720, 3, 1, is_screen_sharing_);
+ const auto start_rates = SvcRateAllocator::GetLayerStartBitrates(codec);
+ const DataRate min_rate_two_layers = start_rates[1];
+ const DataRate min_rate_three_layers = start_rates[2];
+
+ const DataRate max_rate_one_layer =
+ DataRate::KilobitsPerSec(codec.spatialLayers[0].maxBitrate);
+ const DataRate max_rate_two_layers =
+ is_screen_sharing_
+ ? DataRate::KilobitsPerSec(codec.spatialLayers[0].targetBitrate +
+ codec.spatialLayers[1].maxBitrate)
+ : DataRate::KilobitsPerSec(codec.spatialLayers[0].maxBitrate +
+ codec.spatialLayers[1].maxBitrate);
+
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ // Two layers, stable and target equal.
+ auto allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/min_rate_two_layers,
+ /*stable_bitrate=*/min_rate_two_layers, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_EQ(allocation.get_sum_bps(), min_rate_two_layers.bps());
+
+ // Two layers, stable bitrate too low for two layers.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/min_rate_two_layers,
+ /*stable_bitrate=*/min_rate_two_layers - DataRate::BitsPerSec(1),
+ /*fps=*/30.0));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_EQ(DataRate::BitsPerSec(allocation.get_sum_bps()),
+ std::min(min_rate_two_layers - DataRate::BitsPerSec(1),
+ max_rate_one_layer));
+
+ // Three layers, stable and target equal.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/min_rate_three_layers,
+ /*stable_bitrate=*/min_rate_three_layers, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(2));
+ EXPECT_EQ(allocation.get_sum_bps(), min_rate_three_layers.bps());
+
+ // Three layers, stable bitrate too low for three layers.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/min_rate_three_layers,
+ /*stable_bitrate=*/min_rate_three_layers - DataRate::BitsPerSec(1),
+ /*fps=*/30.0));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+ EXPECT_EQ(DataRate::BitsPerSec(allocation.get_sum_bps()),
+ std::min(min_rate_three_layers - DataRate::BitsPerSec(1),
+ max_rate_two_layers));
+}
+
+TEST_P(SvcRateAllocatorTestParametrizedContentType,
+ StableBitrateWithHysteresis) {
+ const VideoCodec codec = Configure(1280, 720, 3, 1, is_screen_sharing_);
+ const auto start_rates = SvcRateAllocator::GetLayerStartBitrates(codec);
+ const DataRate min_rate_single_layer = start_rates[0];
+ const DataRate min_rate_two_layers = start_rates[1];
+ const DataRate min_rate_three_layers = start_rates[2];
+
+ ScopedFieldTrials field_trial(
+ "WebRTC-StableTargetRate/enabled:true,video_hysteresis_factor:1.1,"
+ "screenshare_hysteresis_factor:1.1/");
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+ // Always use max bitrate as target, verify only stable is used for layer
+ // count selection.
+ const DataRate max_bitrate = allocator.GetMaxBitrate(codec);
+
+ // Start with a single layer.
+ auto allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_single_layer, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+
+ // Min bitrate not enough to enable second layer due to 10% hysteresis.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_two_layers, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+
+ // Add hysteresis, second layer should turn on.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_two_layers * 1.1, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+
+ // Remove hysteresis, second layer should stay on.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_two_layers, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+
+ // Going below min for two layers, second layer should turn off again.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_two_layers - DataRate::BitsPerSec(1),
+ /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+
+ // Min bitrate not enough to enable third layer due to 10% hysteresis.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_three_layers, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+
+ // Add hysteresis, third layer should turn on.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_three_layers * 1.1, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(2));
+
+ // Remove hysteresis, third layer should stay on.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_three_layers, /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(2));
+
+ // Going below min for three layers, third layer should turn off again.
+ allocation = allocator.Allocate(VideoBitrateAllocationParameters(
+ /*total_bitrate=*/max_bitrate,
+ /*stable_bitrate=*/min_rate_three_layers - DataRate::BitsPerSec(1),
+ /*fps=*/30.0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(0));
+ EXPECT_TRUE(allocation.IsSpatialLayerUsed(1));
+ EXPECT_FALSE(allocation.IsSpatialLayerUsed(2));
+}
+
+INSTANTIATE_TEST_SUITE_P(_,
+ SvcRateAllocatorTestParametrizedContentType,
+ ::testing::Bool());
+
+} // namespace test
+} // namespace webrtc