/* * Copyright (c) 2016 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/utility/simulcast_rate_allocator.h" #include #include #include #include #include "api/video_codecs/vp8_frame_buffer_controller.h" #include "api/video_codecs/vp8_frame_config.h" #include "api/video_codecs/vp8_temporal_layers.h" #include "rtc_base/checks.h" #include "test/field_trial.h" #include "test/gmock.h" #include "test/gtest.h" namespace webrtc { namespace { using ::testing::_; constexpr uint32_t kFramerateFps = 5; constexpr uint32_t kMinBitrateKbps = 50; // These correspond to kLegacyScreenshareTl(0|1)BitrateKbps in cc. constexpr uint32_t kLegacyScreenshareTargetBitrateKbps = 200; constexpr uint32_t kLegacyScreenshareMaxBitrateKbps = 1000; // Bitrates for upper simulcast screenshare layer. constexpr uint32_t kSimulcastScreenshareMinBitrateKbps = 600; constexpr uint32_t kSimulcastScreenshareMaxBitrateKbps = 1250; // Default video hysteresis factor: allocatable bitrate for next layer must // exceed 20% of min setting in order to be initially turned on. const double kDefaultHysteresis = 1.2; class MockTemporalLayers : public Vp8FrameBufferController { public: MOCK_METHOD(Vp8FrameConfig, NextFrameConfig, (size_t, uint32_t), (override)); MOCK_METHOD(void, OnRatesUpdated, (size_t, const std::vector&, int), (override)); MOCK_METHOD(Vp8EncoderConfig, UpdateConfiguration, (size_t), (override)); MOCK_METHOD(void, OnEncodeDone, (size_t, uint32_t, size_t, bool, int, CodecSpecificInfo*), (override)); }; } // namespace class SimulcastRateAllocatorTest : public ::testing::TestWithParam { public: SimulcastRateAllocatorTest() { codec_.codecType = kVideoCodecVP8; codec_.minBitrate = kMinBitrateKbps; codec_.maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.active = true; CreateAllocator(); } virtual ~SimulcastRateAllocatorTest() {} template void ExpectEqual(uint32_t (&expected)[S], const std::vector& actual) { EXPECT_EQ(S, actual.size()); for (size_t i = 0; i < S; ++i) EXPECT_EQ(expected[i], actual[i]) << "Mismatch at index " << i; } template void ExpectEqual(uint32_t (&expected)[S], const VideoBitrateAllocation& actual) { // EXPECT_EQ(S, actual.size()); uint32_t sum = 0; for (size_t i = 0; i < S; ++i) { uint32_t layer_bitrate = actual.GetSpatialLayerSum(i); if (layer_bitrate == 0) { EXPECT_FALSE(actual.IsSpatialLayerUsed(i)); } EXPECT_EQ(expected[i] * 1000U, layer_bitrate) << "Mismatch at index " << i; sum += layer_bitrate; } EXPECT_EQ(sum, actual.get_sum_bps()); } void CreateAllocator(bool legacy_conference_mode = false) { allocator_.reset(new SimulcastRateAllocator(codec_)); allocator_->SetLegacyConferenceMode(legacy_conference_mode); } void SetupCodec3SL3TL(const std::vector& active_streams) { const size_t num_simulcast_layers = 3; RTC_DCHECK_GE(active_streams.size(), num_simulcast_layers); SetupCodec2SL3TL(active_streams); codec_.numberOfSimulcastStreams = num_simulcast_layers; codec_.simulcastStream[2].numberOfTemporalLayers = 3; codec_.simulcastStream[2].maxBitrate = 4000; codec_.simulcastStream[2].targetBitrate = 3000; codec_.simulcastStream[2].minBitrate = 2000; codec_.simulcastStream[2].active = active_streams[2]; } void SetupCodec2SL3TL(const std::vector& active_streams) { const size_t num_simulcast_layers = 2; RTC_DCHECK_GE(active_streams.size(), num_simulcast_layers); SetupCodec1SL3TL(active_streams); codec_.numberOfSimulcastStreams = num_simulcast_layers; codec_.simulcastStream[1].numberOfTemporalLayers = 3; codec_.simulcastStream[1].maxBitrate = 1000; codec_.simulcastStream[1].targetBitrate = 500; codec_.simulcastStream[1].minBitrate = 50; codec_.simulcastStream[1].active = active_streams[1]; } void SetupCodec1SL3TL(const std::vector& active_streams) { const size_t num_simulcast_layers = 2; RTC_DCHECK_GE(active_streams.size(), num_simulcast_layers); SetupCodec3TL(); codec_.numberOfSimulcastStreams = num_simulcast_layers; codec_.simulcastStream[0].numberOfTemporalLayers = 3; codec_.simulcastStream[0].maxBitrate = 500; codec_.simulcastStream[0].targetBitrate = 100; codec_.simulcastStream[0].minBitrate = 10; codec_.simulcastStream[0].active = active_streams[0]; } void SetupCodec3TL() { codec_.maxBitrate = 0; codec_.VP8()->numberOfTemporalLayers = 3; } VideoBitrateAllocation GetAllocation(uint32_t target_bitrate) { return allocator_->Allocate(VideoBitrateAllocationParameters( DataRate::KilobitsPerSec(target_bitrate), kDefaultFrameRate)); } VideoBitrateAllocation GetAllocation(DataRate target_rate, DataRate stable_rate) { return allocator_->Allocate(VideoBitrateAllocationParameters( target_rate, stable_rate, kDefaultFrameRate)); } DataRate MinRate(size_t layer_index) const { return DataRate::KilobitsPerSec( codec_.simulcastStream[layer_index].minBitrate); } DataRate TargetRate(size_t layer_index) const { return DataRate::KilobitsPerSec( codec_.simulcastStream[layer_index].targetBitrate); } DataRate MaxRate(size_t layer_index) const { return DataRate::KilobitsPerSec( codec_.simulcastStream[layer_index].maxBitrate); } protected: static const int kDefaultFrameRate = 30; VideoCodec codec_; std::unique_ptr allocator_; }; TEST_F(SimulcastRateAllocatorTest, NoSimulcastBelowMin) { uint32_t expected[] = {codec_.minBitrate}; codec_.active = true; ExpectEqual(expected, GetAllocation(codec_.minBitrate - 1)); ExpectEqual(expected, GetAllocation(1)); ExpectEqual(expected, GetAllocation(0)); } TEST_F(SimulcastRateAllocatorTest, NoSimulcastAboveMax) { uint32_t expected[] = {codec_.maxBitrate}; codec_.active = true; ExpectEqual(expected, GetAllocation(codec_.maxBitrate + 1)); ExpectEqual(expected, GetAllocation(std::numeric_limits::max())); } TEST_F(SimulcastRateAllocatorTest, NoSimulcastNoMax) { const uint32_t kMax = VideoBitrateAllocation::kMaxBitrateBps / 1000; codec_.active = true; codec_.maxBitrate = 0; CreateAllocator(); uint32_t expected[] = {kMax}; ExpectEqual(expected, GetAllocation(kMax)); } TEST_F(SimulcastRateAllocatorTest, NoSimulcastWithinLimits) { codec_.active = true; for (uint32_t bitrate = codec_.minBitrate; bitrate <= codec_.maxBitrate; ++bitrate) { uint32_t expected[] = {bitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } } // Tests that when we aren't using simulcast and the codec is marked inactive no // bitrate will be allocated. TEST_F(SimulcastRateAllocatorTest, NoSimulcastInactive) { codec_.active = false; uint32_t expected[] = {0}; CreateAllocator(); ExpectEqual(expected, GetAllocation(kMinBitrateKbps - 10)); ExpectEqual(expected, GetAllocation(kLegacyScreenshareTargetBitrateKbps)); ExpectEqual(expected, GetAllocation(kLegacyScreenshareMaxBitrateKbps + 10)); } TEST_F(SimulcastRateAllocatorTest, SingleSimulcastBelowMin) { // With simulcast, use the min bitrate from the ss spec instead of the global. codec_.numberOfSimulcastStreams = 1; const uint32_t kMin = codec_.minBitrate - 10; codec_.simulcastStream[0].minBitrate = kMin; codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps; codec_.simulcastStream[0].active = true; CreateAllocator(); uint32_t expected[] = {kMin}; ExpectEqual(expected, GetAllocation(kMin - 1)); ExpectEqual(expected, GetAllocation(1)); ExpectEqual(expected, GetAllocation(0)); } TEST_F(SimulcastRateAllocatorTest, SignalsBwLimited) { // Enough to enable all layers. const int kVeryBigBitrate = 100000; // With simulcast, use the min bitrate from the ss spec instead of the global. SetupCodec3SL3TL({true, true, true}); CreateAllocator(); EXPECT_TRUE( GetAllocation(codec_.simulcastStream[0].minBitrate - 10).is_bw_limited()); EXPECT_TRUE( GetAllocation(codec_.simulcastStream[0].targetBitrate).is_bw_limited()); EXPECT_TRUE(GetAllocation(codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[1].minBitrate) .is_bw_limited()); EXPECT_FALSE( GetAllocation( codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[1].targetBitrate + static_cast( codec_.simulcastStream[2].minBitrate * kDefaultHysteresis + 0.5)) .is_bw_limited()); EXPECT_FALSE(GetAllocation(kVeryBigBitrate).is_bw_limited()); } TEST_F(SimulcastRateAllocatorTest, SingleSimulcastAboveMax) { codec_.numberOfSimulcastStreams = 1; codec_.simulcastStream[0].minBitrate = kMinBitrateKbps; const uint32_t kMax = codec_.simulcastStream[0].maxBitrate + 1000; codec_.simulcastStream[0].maxBitrate = kMax; codec_.simulcastStream[0].active = true; CreateAllocator(); uint32_t expected[] = {kMax}; ExpectEqual(expected, GetAllocation(kMax)); ExpectEqual(expected, GetAllocation(kMax + 1)); ExpectEqual(expected, GetAllocation(std::numeric_limits::max())); } TEST_F(SimulcastRateAllocatorTest, SingleSimulcastWithinLimits) { codec_.numberOfSimulcastStreams = 1; codec_.simulcastStream[0].minBitrate = kMinBitrateKbps; codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps; codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.simulcastStream[0].active = true; CreateAllocator(); for (uint32_t bitrate = kMinBitrateKbps; bitrate <= kLegacyScreenshareMaxBitrateKbps; ++bitrate) { uint32_t expected[] = {bitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } } TEST_F(SimulcastRateAllocatorTest, Regular3TLTemporalRateAllocation) { SetupCodec3SL3TL({true, true, true}); CreateAllocator(); const VideoBitrateAllocation alloc = GetAllocation(kMinBitrateKbps); // 40/20/40. EXPECT_EQ(static_cast(0.4 * kMinBitrateKbps), alloc.GetBitrate(0, 0) / 1000); EXPECT_EQ(static_cast(0.2 * kMinBitrateKbps), alloc.GetBitrate(0, 1) / 1000); EXPECT_EQ(static_cast(0.4 * kMinBitrateKbps), alloc.GetBitrate(0, 2) / 1000); } TEST_F(SimulcastRateAllocatorTest, BaseHeavy3TLTemporalRateAllocation) { test::ScopedFieldTrials field_trials( "WebRTC-UseBaseHeavyVP8TL3RateAllocation/Enabled/"); SetupCodec3SL3TL({true, true, true}); CreateAllocator(); const VideoBitrateAllocation alloc = GetAllocation(kMinBitrateKbps); // 60/20/20. EXPECT_EQ(static_cast(0.6 * kMinBitrateKbps), alloc.GetBitrate(0, 0) / 1000); EXPECT_EQ(static_cast(0.2 * kMinBitrateKbps), alloc.GetBitrate(0, 1) / 1000); EXPECT_EQ(static_cast(0.2 * kMinBitrateKbps), alloc.GetBitrate(0, 2) / 1000); } TEST_F(SimulcastRateAllocatorTest, SingleSimulcastInactive) { codec_.numberOfSimulcastStreams = 1; codec_.simulcastStream[0].minBitrate = kMinBitrateKbps; codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps; codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.simulcastStream[0].active = false; CreateAllocator(); uint32_t expected[] = {0}; ExpectEqual(expected, GetAllocation(kMinBitrateKbps - 10)); ExpectEqual(expected, GetAllocation(kLegacyScreenshareTargetBitrateKbps)); ExpectEqual(expected, GetAllocation(kLegacyScreenshareMaxBitrateKbps + 10)); } TEST_F(SimulcastRateAllocatorTest, OneToThreeStreams) { SetupCodec3SL3TL({true, true, true}); CreateAllocator(); { // Single stream, min bitrate. const uint32_t bitrate = codec_.simulcastStream[0].minBitrate; uint32_t expected[] = {bitrate, 0, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Single stream at target bitrate. const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate; uint32_t expected[] = {bitrate, 0, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } uint32_t kMinInitialRateTwoLayers = codec_.simulcastStream[0].targetBitrate + static_cast(codec_.simulcastStream[1].minBitrate * kDefaultHysteresis); { // Bitrate above target for first stream, but below min for the next one. const uint32_t bitrate = kMinInitialRateTwoLayers - 1; uint32_t expected[] = {bitrate, 0, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Just enough for two streams. const uint32_t bitrate = kMinInitialRateTwoLayers; uint32_t expected[] = { codec_.simulcastStream[0].targetBitrate, kMinInitialRateTwoLayers - codec_.simulcastStream[0].targetBitrate, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Second stream maxed out, but not enough for third. const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[1].maxBitrate; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, codec_.simulcastStream[1].maxBitrate, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } uint32_t kMinInitialRateThreeLayers = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[1].targetBitrate + static_cast(codec_.simulcastStream[2].minBitrate * kDefaultHysteresis); { // First two streams maxed out, but not enough for third. Nowhere to put // remaining bits. const uint32_t bitrate = kMinInitialRateThreeLayers - 1; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, codec_.simulcastStream[1].maxBitrate, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Just enough for all three streams. const uint32_t bitrate = kMinInitialRateThreeLayers; uint32_t expected[] = { codec_.simulcastStream[0].targetBitrate, codec_.simulcastStream[1].targetBitrate, static_cast(codec_.simulcastStream[2].minBitrate * kDefaultHysteresis)}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Third maxed out. const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[1].targetBitrate + codec_.simulcastStream[2].maxBitrate; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, codec_.simulcastStream[1].targetBitrate, codec_.simulcastStream[2].maxBitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Enough to max out all streams which will allocate the target amount to // the lower streams. const uint32_t bitrate = codec_.simulcastStream[0].maxBitrate + codec_.simulcastStream[1].maxBitrate + codec_.simulcastStream[2].maxBitrate; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, codec_.simulcastStream[1].targetBitrate, codec_.simulcastStream[2].maxBitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } } // If three simulcast streams that are all inactive, none of them should be // allocated bitrate. TEST_F(SimulcastRateAllocatorTest, ThreeStreamsInactive) { SetupCodec3SL3TL({false, false, false}); CreateAllocator(); // Just enough to allocate the min. const uint32_t min_bitrate = codec_.simulcastStream[0].minBitrate + codec_.simulcastStream[1].minBitrate + codec_.simulcastStream[2].minBitrate; // Enough bitrate to allocate target to all streams. const uint32_t target_bitrate = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[1].targetBitrate + codec_.simulcastStream[2].targetBitrate; // Enough bitrate to allocate max to all streams. const uint32_t max_bitrate = codec_.simulcastStream[0].maxBitrate + codec_.simulcastStream[1].maxBitrate + codec_.simulcastStream[2].maxBitrate; uint32_t expected[] = {0, 0, 0}; ExpectEqual(expected, GetAllocation(0)); ExpectEqual(expected, GetAllocation(min_bitrate)); ExpectEqual(expected, GetAllocation(target_bitrate)); ExpectEqual(expected, GetAllocation(max_bitrate)); } // If there are two simulcast streams, we expect the high active stream to be // allocated as if it is a single active stream. TEST_F(SimulcastRateAllocatorTest, TwoStreamsLowInactive) { SetupCodec2SL3TL({false, true}); CreateAllocator(); const uint32_t kActiveStreamMinBitrate = codec_.simulcastStream[1].minBitrate; const uint32_t kActiveStreamTargetBitrate = codec_.simulcastStream[1].targetBitrate; const uint32_t kActiveStreamMaxBitrate = codec_.simulcastStream[1].maxBitrate; { // Expect that the stream is always allocated its min bitrate. uint32_t expected[] = {0, kActiveStreamMinBitrate}; ExpectEqual(expected, GetAllocation(0)); ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate - 10)); ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate)); } { // The stream should be allocated its target bitrate. uint32_t expected[] = {0, kActiveStreamTargetBitrate}; ExpectEqual(expected, GetAllocation(kActiveStreamTargetBitrate)); } { // The stream should be allocated its max if the target input is sufficient. uint32_t expected[] = {0, kActiveStreamMaxBitrate}; ExpectEqual(expected, GetAllocation(kActiveStreamMaxBitrate)); ExpectEqual(expected, GetAllocation(std::numeric_limits::max())); } } // If there are two simulcast streams, we expect the low active stream to be // allocated as if it is a single active stream. TEST_F(SimulcastRateAllocatorTest, TwoStreamsHighInactive) { SetupCodec2SL3TL({true, false}); CreateAllocator(); const uint32_t kActiveStreamMinBitrate = codec_.simulcastStream[0].minBitrate; const uint32_t kActiveStreamTargetBitrate = codec_.simulcastStream[0].targetBitrate; const uint32_t kActiveStreamMaxBitrate = codec_.simulcastStream[0].maxBitrate; { // Expect that the stream is always allocated its min bitrate. uint32_t expected[] = {kActiveStreamMinBitrate, 0}; ExpectEqual(expected, GetAllocation(0)); ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate - 10)); ExpectEqual(expected, GetAllocation(kActiveStreamMinBitrate)); } { // The stream should be allocated its target bitrate. uint32_t expected[] = {kActiveStreamTargetBitrate, 0}; ExpectEqual(expected, GetAllocation(kActiveStreamTargetBitrate)); } { // The stream should be allocated its max if the target input is sufficent. uint32_t expected[] = {kActiveStreamMaxBitrate, 0}; ExpectEqual(expected, GetAllocation(kActiveStreamMaxBitrate)); ExpectEqual(expected, GetAllocation(std::numeric_limits::max())); } } // If there are three simulcast streams and the middle stream is inactive, the // other two streams should be allocated bitrate the same as if they are two // active simulcast streams. TEST_F(SimulcastRateAllocatorTest, ThreeStreamsMiddleInactive) { SetupCodec3SL3TL({true, false, true}); CreateAllocator(); { const uint32_t kLowStreamMinBitrate = codec_.simulcastStream[0].minBitrate; // The lowest stream should always be allocated its minimum bitrate. uint32_t expected[] = {kLowStreamMinBitrate, 0, 0}; ExpectEqual(expected, GetAllocation(0)); ExpectEqual(expected, GetAllocation(kLowStreamMinBitrate - 10)); ExpectEqual(expected, GetAllocation(kLowStreamMinBitrate)); } { // The lowest stream gets its target bitrate. uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, 0, 0}; ExpectEqual(expected, GetAllocation(codec_.simulcastStream[0].targetBitrate)); } { // The lowest stream gets its max bitrate, but not enough for the high // stream. const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[2].minBitrate - 1; uint32_t expected[] = {codec_.simulcastStream[0].maxBitrate, 0, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Both active streams get allocated target bitrate. const uint32_t bitrate = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[2].targetBitrate; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, 0, codec_.simulcastStream[2].targetBitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Lowest stream gets its target bitrate, high stream gets its max bitrate. uint32_t bitrate = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[2].maxBitrate; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, 0, codec_.simulcastStream[2].maxBitrate}; ExpectEqual(expected, GetAllocation(bitrate)); ExpectEqual(expected, GetAllocation(bitrate + 10)); ExpectEqual(expected, GetAllocation(std::numeric_limits::max())); } } TEST_F(SimulcastRateAllocatorTest, NonConferenceModeScreenshare) { codec_.mode = VideoCodecMode::kScreensharing; SetupCodec3SL3TL({true, true, true}); CreateAllocator(); // Make sure we have enough bitrate for all 3 simulcast layers const uint32_t bitrate = codec_.simulcastStream[0].maxBitrate + codec_.simulcastStream[1].maxBitrate + codec_.simulcastStream[2].maxBitrate; const VideoBitrateAllocation alloc = GetAllocation(bitrate); EXPECT_EQ(alloc.GetTemporalLayerAllocation(0).size(), 3u); EXPECT_EQ(alloc.GetTemporalLayerAllocation(1).size(), 3u); EXPECT_EQ(alloc.GetTemporalLayerAllocation(2).size(), 3u); } TEST_F(SimulcastRateAllocatorTest, StableRate) { webrtc::test::ScopedFieldTrials field_trials( "WebRTC-StableTargetRate/" "enabled:true," "video_hysteresis_factor:1.1/"); SetupCodec3SL3TL({true, true, true}); CreateAllocator(); // Let the volatile rate always be be enough for all streams, in this test we // are only interested in how the stable rate affects enablement. const DataRate volatile_rate = (TargetRate(0) + TargetRate(1) + MinRate(2)) * 1.1; { // On the first call to a new SimulcastRateAllocator instance, hysteresis // is disabled, but stable rate still caps layers. uint32_t expected[] = {TargetRate(0).kbps(), MaxRate(1).kbps()}; ExpectEqual(expected, GetAllocation(volatile_rate, TargetRate(0) + MinRate(1))); } { // Let stable rate go to a bitrate below what is needed for two streams. uint32_t expected[] = {MaxRate(0).kbps(), 0}; ExpectEqual(expected, GetAllocation(volatile_rate, TargetRate(0) + MinRate(1) - DataRate::BitsPerSec(1))); } { // Don't enable stream as we need to get up above hysteresis threshold. uint32_t expected[] = {MaxRate(0).kbps(), 0}; ExpectEqual(expected, GetAllocation(volatile_rate, TargetRate(0) + MinRate(1))); } { // Above threshold with hysteresis, enable second stream. uint32_t expected[] = {TargetRate(0).kbps(), MaxRate(1).kbps()}; ExpectEqual(expected, GetAllocation(volatile_rate, (TargetRate(0) + MinRate(1)) * 1.1)); } { // Enough to enable all thee layers. uint32_t expected[] = { TargetRate(0).kbps(), TargetRate(1).kbps(), (volatile_rate - TargetRate(0) - TargetRate(1)).kbps()}; ExpectEqual(expected, GetAllocation(volatile_rate, volatile_rate)); } { // Drop hysteresis, all three still on. uint32_t expected[] = { TargetRate(0).kbps(), TargetRate(1).kbps(), (volatile_rate - TargetRate(0) - TargetRate(1)).kbps()}; ExpectEqual(expected, GetAllocation(volatile_rate, TargetRate(0) + TargetRate(1) + MinRate(2))); } } class ScreenshareRateAllocationTest : public SimulcastRateAllocatorTest { public: void SetupConferenceScreenshare(bool use_simulcast, bool active = true) { codec_.mode = VideoCodecMode::kScreensharing; codec_.minBitrate = kMinBitrateKbps; codec_.maxBitrate = kLegacyScreenshareMaxBitrateKbps + kSimulcastScreenshareMaxBitrateKbps; if (use_simulcast) { codec_.numberOfSimulcastStreams = 2; codec_.simulcastStream[0].minBitrate = kMinBitrateKbps; codec_.simulcastStream[0].targetBitrate = kLegacyScreenshareTargetBitrateKbps; codec_.simulcastStream[0].maxBitrate = kLegacyScreenshareMaxBitrateKbps; codec_.simulcastStream[0].numberOfTemporalLayers = 2; codec_.simulcastStream[0].active = active; codec_.simulcastStream[1].minBitrate = kSimulcastScreenshareMinBitrateKbps; codec_.simulcastStream[1].targetBitrate = kSimulcastScreenshareMaxBitrateKbps; codec_.simulcastStream[1].maxBitrate = kSimulcastScreenshareMaxBitrateKbps; codec_.simulcastStream[1].numberOfTemporalLayers = 2; codec_.simulcastStream[1].active = active; } else { codec_.numberOfSimulcastStreams = 0; codec_.VP8()->numberOfTemporalLayers = 2; codec_.active = active; } } }; INSTANTIATE_TEST_SUITE_P(ScreenshareTest, ScreenshareRateAllocationTest, ::testing::Bool()); TEST_P(ScreenshareRateAllocationTest, ConferenceBitrateBelowTl0) { SetupConferenceScreenshare(GetParam()); CreateAllocator(true); VideoBitrateAllocation allocation = allocator_->Allocate(VideoBitrateAllocationParameters( kLegacyScreenshareTargetBitrateKbps * 1000, kFramerateFps)); // All allocation should go in TL0. EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, allocation.get_sum_kbps()); EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, allocation.GetBitrate(0, 0) / 1000); EXPECT_EQ(allocation.is_bw_limited(), GetParam()); } TEST_P(ScreenshareRateAllocationTest, ConferenceBitrateAboveTl0) { SetupConferenceScreenshare(GetParam()); CreateAllocator(true); uint32_t target_bitrate_kbps = (kLegacyScreenshareTargetBitrateKbps + kLegacyScreenshareMaxBitrateKbps) / 2; VideoBitrateAllocation allocation = allocator_->Allocate(VideoBitrateAllocationParameters( target_bitrate_kbps * 1000, kFramerateFps)); // Fill TL0, then put the rest in TL1. EXPECT_EQ(target_bitrate_kbps, allocation.get_sum_kbps()); EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, allocation.GetBitrate(0, 0) / 1000); EXPECT_EQ(target_bitrate_kbps - kLegacyScreenshareTargetBitrateKbps, allocation.GetBitrate(0, 1) / 1000); EXPECT_EQ(allocation.is_bw_limited(), GetParam()); } TEST_F(ScreenshareRateAllocationTest, ConferenceBitrateAboveTl1) { // This test is only for the non-simulcast case. SetupConferenceScreenshare(false); CreateAllocator(true); VideoBitrateAllocation allocation = allocator_->Allocate(VideoBitrateAllocationParameters( kLegacyScreenshareMaxBitrateKbps * 2000, kFramerateFps)); // Fill both TL0 and TL1, but no more. EXPECT_EQ(kLegacyScreenshareMaxBitrateKbps, allocation.get_sum_kbps()); EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, allocation.GetBitrate(0, 0) / 1000); EXPECT_EQ( kLegacyScreenshareMaxBitrateKbps - kLegacyScreenshareTargetBitrateKbps, allocation.GetBitrate(0, 1) / 1000); EXPECT_FALSE(allocation.is_bw_limited()); } // This tests when the screenshare is inactive it should be allocated 0 bitrate // for all layers. TEST_P(ScreenshareRateAllocationTest, InactiveScreenshare) { SetupConferenceScreenshare(GetParam(), false); CreateAllocator(); // Enough bitrate for TL0 and TL1. uint32_t target_bitrate_kbps = (kLegacyScreenshareTargetBitrateKbps + kLegacyScreenshareMaxBitrateKbps) / 2; VideoBitrateAllocation allocation = allocator_->Allocate(VideoBitrateAllocationParameters( target_bitrate_kbps * 1000, kFramerateFps)); EXPECT_EQ(0U, allocation.get_sum_kbps()); } TEST_F(ScreenshareRateAllocationTest, Hysteresis) { // This test is only for the simulcast case. SetupConferenceScreenshare(true); CreateAllocator(); // The bitrate at which we would normally enable the upper simulcast stream. const uint32_t default_enable_rate_bps = codec_.simulcastStream[0].targetBitrate + codec_.simulcastStream[1].minBitrate; const uint32_t enable_rate_with_hysteresis_bps = (default_enable_rate_bps * 135) / 100; { // On the first call to a new SimulcastRateAllocator instance, hysteresis // is disabled. const uint32_t bitrate = default_enable_rate_bps; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, codec_.simulcastStream[1].minBitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Go down to a bitrate below what is needed for two streams. const uint32_t bitrate = default_enable_rate_bps - 1; uint32_t expected[] = {bitrate, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Don't enable stream as we need to get up above hysteresis threshold. const uint32_t bitrate = default_enable_rate_bps; uint32_t expected[] = {bitrate, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Above threshold, enable second stream. const uint32_t bitrate = enable_rate_with_hysteresis_bps; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, enable_rate_with_hysteresis_bps - codec_.simulcastStream[0].targetBitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Go down again, still keep the second stream alive. const uint32_t bitrate = default_enable_rate_bps; uint32_t expected[] = {codec_.simulcastStream[0].targetBitrate, codec_.simulcastStream[1].minBitrate}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Go down below default enable, second stream is shut down again. const uint32_t bitrate = default_enable_rate_bps - 1; uint32_t expected[] = {bitrate, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } { // Go up, hysteresis is blocking us again. const uint32_t bitrate = default_enable_rate_bps; uint32_t expected[] = {bitrate, 0}; ExpectEqual(expected, GetAllocation(bitrate)); } } } // namespace webrtc