From 30ab015fc908e766b9da9a825f1e39b53c9a387b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=85sa=20Persson?= Date: Tue, 27 Aug 2019 12:22:33 +0200 Subject: [PATCH] BalancedDegradationSettings: add min bitrate configuration for resolution. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add separate setting for configuring min bitrate that only applies when adapting up in resolution. Bug: none Change-Id: I83d33ac3110a22602065b8d83130e3f619cb1eba Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/150329 Commit-Queue: Åsa Persson Reviewed-by: Mirta Dvornicic Cr-Commit-Position: refs/heads/master@{#28970} --- .../balanced_degradation_settings.cc | 31 +++ .../balanced_degradation_settings.h | 10 +- .../balanced_degradation_settings_unittest.cc | 60 ++++- video/video_stream_encoder.cc | 6 + video/video_stream_encoder_unittest.cc | 207 ++++++++++++++++-- 5 files changed, 295 insertions(+), 19 deletions(-) diff --git a/rtc_base/experiments/balanced_degradation_settings.cc b/rtc_base/experiments/balanced_degradation_settings.cc index 40c9e87a69..bfb0a5f1c0 100644 --- a/rtc_base/experiments/balanced_degradation_settings.cc +++ b/rtc_base/experiments/balanced_degradation_settings.cc @@ -27,6 +27,7 @@ std::vector DefaultConfigs() { return {{320 * 240, 7, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -35,6 +36,7 @@ std::vector DefaultConfigs() { {480 * 270, 10, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -43,6 +45,7 @@ std::vector DefaultConfigs() { {640 * 480, 15, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -221,6 +224,7 @@ BalancedDegradationSettings::Config::Config() = default; BalancedDegradationSettings::Config::Config(int pixels, int fps, int kbps, + int kbps_res, int fps_diff, CodecTypeSpecific vp8, CodecTypeSpecific vp9, @@ -229,6 +233,7 @@ BalancedDegradationSettings::Config::Config(int pixels, : pixels(pixels), fps(fps), kbps(kbps), + kbps_res(kbps_res), fps_diff(fps_diff), vp8(vp8), vp9(vp9), @@ -240,6 +245,8 @@ BalancedDegradationSettings::BalancedDegradationSettings() { {FieldTrialStructMember("pixels", [](Config* c) { return &c->pixels; }), FieldTrialStructMember("fps", [](Config* c) { return &c->fps; }), FieldTrialStructMember("kbps", [](Config* c) { return &c->kbps; }), + FieldTrialStructMember("kbps_res", + [](Config* c) { return &c->kbps_res; }), FieldTrialStructMember("fps_diff", [](Config* c) { return &c->fps_diff; }), FieldTrialStructMember("vp8_qp_low", @@ -317,6 +324,18 @@ absl::optional BalancedDegradationSettings::NextHigherBitrateKbps( return absl::nullopt; } +absl::optional +BalancedDegradationSettings::ResolutionNextHigherBitrateKbps(int pixels) const { + for (size_t i = 0; i < configs_.size() - 1; ++i) { + if (pixels <= configs_[i].pixels) { + return (configs_[i + 1].kbps_res > 0) + ? absl::optional(configs_[i + 1].kbps_res) + : absl::nullopt; + } + } + return absl::nullopt; +} + bool BalancedDegradationSettings::CanAdaptUp(int pixels, uint32_t bitrate_bps) const { absl::optional next_layer_min_kbps = NextHigherBitrateKbps(pixels); @@ -327,6 +346,18 @@ bool BalancedDegradationSettings::CanAdaptUp(int pixels, static_cast(next_layer_min_kbps.value() * 1000); } +bool BalancedDegradationSettings::CanAdaptUpResolution( + int pixels, + uint32_t bitrate_bps) const { + absl::optional next_layer_min_kbps = + ResolutionNextHigherBitrateKbps(pixels); + if (!next_layer_min_kbps.has_value() || bitrate_bps == 0) { + return true; // No limit configured or bitrate provided. + } + return bitrate_bps >= + static_cast(next_layer_min_kbps.value() * 1000); +} + absl::optional BalancedDegradationSettings::MinFpsDiff(int pixels) const { for (const auto& config : configs_) { if (pixels <= config.pixels) { diff --git a/rtc_base/experiments/balanced_degradation_settings.h b/rtc_base/experiments/balanced_degradation_settings.h index 05e23640e1..31d07810f6 100644 --- a/rtc_base/experiments/balanced_degradation_settings.h +++ b/rtc_base/experiments/balanced_degradation_settings.h @@ -47,6 +47,7 @@ class BalancedDegradationSettings { Config(int pixels, int fps, int kbps, + int kbps_res, int fps_diff, CodecTypeSpecific vp8, CodecTypeSpecific vp9, @@ -55,14 +56,15 @@ class BalancedDegradationSettings { bool operator==(const Config& o) const { return pixels == o.pixels && fps == o.fps && kbps == o.kbps && - fps_diff == o.fps_diff && vp8 == o.vp8 && vp9 == o.vp9 && - h264 == o.h264 && generic == o.generic; + kbps_res == o.kbps_res && fps_diff == o.fps_diff && vp8 == o.vp8 && + vp9 == o.vp9 && h264 == o.h264 && generic == o.generic; } int pixels = 0; // Video frame size. // If the frame size is less than or equal to |pixels|: int fps = 0; // Min framerate to be used. - int kbps = 0; // Min bitrate needed to adapt up to this resolution. + int kbps = 0; // Min bitrate needed to adapt up (resolution/fps). + int kbps_res = 0; // Min bitrate needed to adapt up in resolution. int fps_diff = kNoFpsDiff; // Min fps reduction needed (input fps - |fps|) // w/o triggering a new subsequent downgrade // check. @@ -81,9 +83,11 @@ class BalancedDegradationSettings { // Gets the bitrate for the first resolution above |pixels|. absl::optional NextHigherBitrateKbps(int pixels) const; + absl::optional ResolutionNextHigherBitrateKbps(int pixels) const; // Checks if quality can be increased based on |pixels| and |bitrate_bps|. bool CanAdaptUp(int pixels, uint32_t bitrate_bps) const; + bool CanAdaptUpResolution(int pixels, uint32_t bitrate_bps) const; // Gets the min framerate diff from |configs_| based on |pixels|. absl::optional MinFpsDiff(int pixels) const; diff --git a/rtc_base/experiments/balanced_degradation_settings_unittest.cc b/rtc_base/experiments/balanced_degradation_settings_unittest.cc index b8ddf9a03a..7399bf27a6 100644 --- a/rtc_base/experiments/balanced_degradation_settings_unittest.cc +++ b/rtc_base/experiments/balanced_degradation_settings_unittest.cc @@ -26,6 +26,7 @@ void VerifyIsDefault( 320 * 240, 7, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -35,6 +36,7 @@ void VerifyIsDefault( 480 * 270, 10, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -44,6 +46,7 @@ void VerifyIsDefault( 640 * 480, 15, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -57,6 +60,9 @@ TEST(BalancedDegradationSettings, GetsDefaultConfigIfNoList) { BalancedDegradationSettings settings; VerifyIsDefault(settings.GetConfigs()); EXPECT_FALSE(settings.NextHigherBitrateKbps(1)); + EXPECT_FALSE(settings.ResolutionNextHigherBitrateKbps(1)); + EXPECT_TRUE(settings.CanAdaptUp(1, /*bitrate_bps*/ 1)); + EXPECT_TRUE(settings.CanAdaptUpResolution(1, /*bitrate_bps*/ 1)); EXPECT_FALSE(settings.MinFpsDiff(1)); EXPECT_FALSE(settings.GetQpThresholds(kVideoCodecVP8, 1)); EXPECT_FALSE(settings.GetQpThresholds(kVideoCodecVP9, 1)); @@ -76,6 +82,7 @@ TEST(BalancedDegradationSettings, GetsConfig) { 11, 5, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -85,6 +92,7 @@ TEST(BalancedDegradationSettings, GetsConfig) { 22, 15, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -94,6 +102,7 @@ TEST(BalancedDegradationSettings, GetsConfig) { 33, 25, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -137,6 +146,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithSpecificFps) { 1000, 5, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 7}, {0, 0, 9}, @@ -146,6 +156,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithSpecificFps) { 2000, 15, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 8}, {0, 0, 10}, @@ -155,6 +166,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithSpecificFps) { 3000, 25, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 9}, {0, 0, 11}, @@ -285,7 +297,7 @@ TEST(BalancedDegradationSettings, GetsUnlimitedForMaxValidFps) { TEST(BalancedDegradationSettings, GetsConfigWithBitrate) { webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Video-BalancedDegradationSettings/" - "pixels:11|22|33,fps:5|15|25,kbps:44|88|99/"); + "pixels:11|22|33,fps:5|15|25,kbps:44|88|99,kbps_res:55|111|222/"); BalancedDegradationSettings settings; EXPECT_THAT(settings.GetConfigs(), ::testing::ElementsAre( @@ -293,6 +305,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithBitrate) { 11, 5, 44, + 55, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -302,6 +315,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithBitrate) { 22, 15, 88, + 111, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -311,6 +325,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithBitrate) { 33, 25, 99, + 222, BalancedDegradationSettings::kNoFpsDiff, {0, 0, 0}, {0, 0, 0}, @@ -373,6 +388,46 @@ TEST(BalancedDegradationSettings, CanAdaptUpIfBitrateGeNextHigherKbpsLimit) { EXPECT_TRUE(settings.CanAdaptUp(3001, 1)); // No limit. } +TEST(BalancedDegradationSettings, GetsResolutionNextHigherBitrate) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Video-BalancedDegradationSettings/" + "pixels:1000|2000|3000,fps:5|15|25,kbps_res:44|88|99/"); + BalancedDegradationSettings settings; + EXPECT_EQ(88, settings.ResolutionNextHigherBitrateKbps(1)); + EXPECT_EQ(88, settings.ResolutionNextHigherBitrateKbps(1000)); + EXPECT_EQ(99, settings.ResolutionNextHigherBitrateKbps(1001)); + EXPECT_EQ(99, settings.ResolutionNextHigherBitrateKbps(2000)); + EXPECT_FALSE(settings.ResolutionNextHigherBitrateKbps(2001)); +} + +TEST(BalancedDegradationSettings, + GetsResolutionNextHigherBitrateWithUnsetValue) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Video-BalancedDegradationSettings/" + "pixels:1000|2000|3000,fps:5|15|25,kbps_res:10|0|20/"); + BalancedDegradationSettings settings; + EXPECT_FALSE(settings.ResolutionNextHigherBitrateKbps(1)); + EXPECT_FALSE(settings.ResolutionNextHigherBitrateKbps(1000)); + EXPECT_EQ(20, settings.ResolutionNextHigherBitrateKbps(1001)); + EXPECT_EQ(20, settings.ResolutionNextHigherBitrateKbps(2000)); + EXPECT_FALSE(settings.ResolutionNextHigherBitrateKbps(2001)); +} + +TEST(BalancedDegradationSettings, + CanAdaptUpResolutionIfBitrateGeNextHigherKbpsLimit) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Video-BalancedDegradationSettings/" + "pixels:1000|2000|3000|4000,fps:5|15|25|30,kbps_res:0|80|0|90/"); + BalancedDegradationSettings settings; + EXPECT_TRUE(settings.CanAdaptUpResolution(1000, 0)); // No bitrate provided. + EXPECT_FALSE(settings.CanAdaptUpResolution(1000, 79000)); + EXPECT_TRUE(settings.CanAdaptUpResolution(1000, 80000)); + EXPECT_TRUE(settings.CanAdaptUpResolution(1001, 1)); // No limit configured. + EXPECT_FALSE(settings.CanAdaptUpResolution(3000, 89000)); + EXPECT_TRUE(settings.CanAdaptUpResolution(3000, 90000)); + EXPECT_TRUE(settings.CanAdaptUpResolution(3001, 1)); // No limit. +} + TEST(BalancedDegradationSettings, GetsFpsDiff) { webrtc::test::ScopedFieldTrials field_trials( "WebRTC-Video-BalancedDegradationSettings/" @@ -423,6 +478,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithQpThresholds) { 1000, 5, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {89, 90, 0}, {27, 120, 0}, @@ -432,6 +488,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithQpThresholds) { 2000, 15, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {90, 91, 0}, {28, 130, 0}, @@ -441,6 +498,7 @@ TEST(BalancedDegradationSettings, GetsConfigWithQpThresholds) { 3000, 25, 0, + 0, BalancedDegradationSettings::kNoFpsDiff, {88, 92, 0}, {29, 140, 0}, diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc index 7dfffb0a42..0b558206e0 100644 --- a/video/video_stream_encoder.cc +++ b/video/video_stream_encoder.cc @@ -1931,6 +1931,12 @@ void VideoStreamEncoder::AdaptUp(AdaptReason reason) { } break; } + // Check if resolution should be increased based on bitrate. + if (reason == kQuality && + !balanced_settings_.CanAdaptUpResolution( + last_frame_info_->pixel_count(), encoder_start_bitrate_bps_)) { + return; + } // Scale up resolution. RTC_FALLTHROUGH(); } diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc index 1ad4cbffd5..c0fa5dc8c6 100644 --- a/video/video_stream_encoder_unittest.cc +++ b/video/video_stream_encoder_unittest.cc @@ -2628,8 +2628,6 @@ TEST_F(VideoStreamEncoderTest, NoAdaptUpIfBwEstimateIsLessThanMinBitrate) { source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); sink_.WaitForEncodedFrame(kWidth, kHeight); VerifyFpsMaxResolutionMax(source.sink_wants()); - EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); - EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); // Trigger adapt down, expect scaled down framerate (640x360@14fps). @@ -2638,8 +2636,6 @@ TEST_F(VideoStreamEncoderTest, NoAdaptUpIfBwEstimateIsLessThanMinBitrate) { source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); sink_.WaitForEncodedFrame(timestamp_ms); VerifyFpsEqResolutionMax(source.sink_wants(), 14); - EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); - EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); // Trigger adapt down, expect scaled down resolution (480x270@14fps). @@ -2648,31 +2644,212 @@ TEST_F(VideoStreamEncoderTest, NoAdaptUpIfBwEstimateIsLessThanMinBitrate) { source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); sink_.WaitForEncodedFrame(timestamp_ms); VerifyFpsEqResolutionLt(source.sink_wants(), source.last_wants()); - EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); - EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); - // Trigger adapt up, expect no upscale (target bitrate < min bitrate). + // Trigger adapt down, expect scaled down framerate (480x270@10fps). + video_stream_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsLtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_EQ(source.sink_wants().max_framerate_fps, 10); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no upscale in fps (target bitrate < min bitrate). video_stream_encoder_->TriggerQualityHigh(); timestamp_ms += kFrameIntervalMs; source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); sink_.WaitForEncodedFrame(timestamp_ms); - EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); - EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); - EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes); - // Trigger adapt up, expect upscaled resolution (target bitrate == min - // bitrate). + // Trigger adapt up, expect upscaled fps (target bitrate == min bitrate). video_stream_encoder_->OnBitrateUpdated(DataRate::bps(kMinBitrateBps), DataRate::bps(kMinBitrateBps), 0, 0); video_stream_encoder_->TriggerQualityHigh(); timestamp_ms += kFrameIntervalMs; source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); sink_.WaitForEncodedFrame(timestamp_ms); - EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); - EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(source.sink_wants().max_framerate_fps, 14); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + video_stream_encoder_->Stop(); +} + +TEST_F(VideoStreamEncoderTest, + NoAdaptUpInResolutionIfBwEstimateIsLessThanMinBitrate) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Video-BalancedDegradationSettings/" + "pixels:57600|129600|230400,fps:7|10|14,kbps_res:0|0|435/"); + // Reset encoder for field trials to take effect. + ConfigureEncoder(video_encoder_config_.Copy()); + + const int kWidth = 640; // pixels:640x360=230400 + const int kHeight = 360; + const int64_t kFrameIntervalMs = 150; + const int kResolutionMinBitrateBps = 435000; + const int kTooLowMinResolutionBitrateBps = 434000; + video_stream_encoder_->OnBitrateUpdated( + DataRate::bps(kTooLowMinResolutionBitrateBps), + DataRate::bps(kTooLowMinResolutionBitrateBps), 0, 0); + + // Enable BALANCED preference, no initial limitation. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + video_stream_encoder_->SetSource(&source, + webrtc::DegradationPreference::BALANCED); + + int64_t timestamp_ms = kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyFpsMaxResolutionMax(source.sink_wants()); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down framerate (640x360@14fps). + video_stream_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionMax(source.sink_wants(), 14); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down resolution (480x270@14fps). + video_stream_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionLt(source.sink_wants(), source.last_wants()); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down framerate (480x270@10fps). + video_stream_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsLtResolutionEq(source.sink_wants(), source.last_wants()); EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes); + // Trigger adapt up, expect upscaled fps (no bitrate limit) (480x270@14fps). + video_stream_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsGtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no upscale in res (target bitrate < min bitrate). + video_stream_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect upscaled res (target bitrate == min bitrate). + video_stream_encoder_->OnBitrateUpdated( + DataRate::bps(kResolutionMinBitrateBps), + DataRate::bps(kResolutionMinBitrateBps), 0, 0); + video_stream_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_EQ(5, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + video_stream_encoder_->Stop(); +} + +TEST_F(VideoStreamEncoderTest, + NoAdaptUpInFpsAndResolutionIfBwEstimateIsLessThanMinBitrate) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Video-BalancedDegradationSettings/" + "pixels:57600|129600|230400,fps:7|10|14,kbps:0|0|425,kbps_res:0|0|435/"); + // Reset encoder for field trials to take effect. + ConfigureEncoder(video_encoder_config_.Copy()); + + const int kWidth = 640; // pixels:640x360=230400 + const int kHeight = 360; + const int64_t kFrameIntervalMs = 150; + const int kMinBitrateBps = 425000; + const int kTooLowMinBitrateBps = 424000; + const int kResolutionMinBitrateBps = 435000; + const int kTooLowMinResolutionBitrateBps = 434000; + video_stream_encoder_->OnBitrateUpdated(DataRate::bps(kTooLowMinBitrateBps), + DataRate::bps(kTooLowMinBitrateBps), + 0, 0); + + // Enable BALANCED preference, no initial limitation. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + video_stream_encoder_->SetSource(&source, + webrtc::DegradationPreference::BALANCED); + + int64_t timestamp_ms = kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyFpsMaxResolutionMax(source.sink_wants()); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down framerate (640x360@14fps). + video_stream_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionMax(source.sink_wants(), 14); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down resolution (480x270@14fps). + video_stream_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionLt(source.sink_wants(), source.last_wants()); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down framerate (480x270@10fps). + video_stream_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsLtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no upscale (target bitrate < min bitrate). + video_stream_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect upscaled fps (target bitrate == min bitrate). + video_stream_encoder_->OnBitrateUpdated(DataRate::bps(kMinBitrateBps), + DataRate::bps(kMinBitrateBps), 0, 0); + video_stream_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsGtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no upscale in res (target bitrate < min bitrate). + video_stream_encoder_->OnBitrateUpdated( + DataRate::bps(kTooLowMinResolutionBitrateBps), + DataRate::bps(kTooLowMinResolutionBitrateBps), 0, 0); + video_stream_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect upscaled res (target bitrate == min bitrate). + video_stream_encoder_->OnBitrateUpdated( + DataRate::bps(kResolutionMinBitrateBps), + DataRate::bps(kResolutionMinBitrateBps), 0, 0); + video_stream_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_EQ(5, stats_proxy_->GetStats().number_of_quality_adapt_changes); + video_stream_encoder_->Stop(); } @@ -3727,7 +3904,7 @@ TEST_F(VideoStreamEncoderTest, EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); EXPECT_EQ(13, stats_proxy_->GetStats().number_of_quality_adapt_changes); - // Trigger adapt up, expect no restriction (1280x720fps@30fps). + // Trigger adapt up, expect no restriction (1280x720fps@30fps). video_stream_encoder_->TriggerQualityHigh(); timestamp_ms += kFrameIntervalMs; source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));