diff --git a/modules/video_coding/utility/quality_scaler.cc b/modules/video_coding/utility/quality_scaler.cc index d31b2cd9c7..a866aeb764 100644 --- a/modules/video_coding/utility/quality_scaler.cc +++ b/modules/video_coding/utility/quality_scaler.cc @@ -166,6 +166,21 @@ void QualityScaler::ReportQp(int qp, int64_t time_sent_us) { qp_smoother_low_->Add(qp, time_sent_us); } +bool QualityScaler::QpFastFilterLow() const { + RTC_DCHECK_RUN_ON(&task_checker_); + size_t num_frames = config_.use_all_drop_reasons + ? framedrop_percent_all_.Size() + : framedrop_percent_media_opt_.Size(); + const size_t kMinNumFrames = 10; + if (num_frames < kMinNumFrames) { + return false; // Wait for more frames before making a decision. + } + absl::optional avg_qp_high = qp_smoother_high_ + ? qp_smoother_high_->GetAvg() + : average_qp_.GetAverageRoundedDown(); + return (avg_qp_high) ? (avg_qp_high.value() <= thresholds_.low) : false; +} + void QualityScaler::CheckQp() { RTC_DCHECK_RUN_ON(&task_checker_); // Should be set through InitEncode -> Should be set by now. diff --git a/modules/video_coding/utility/quality_scaler.h b/modules/video_coding/utility/quality_scaler.h index 9a1b384c0c..eccd8f08c9 100644 --- a/modules/video_coding/utility/quality_scaler.h +++ b/modules/video_coding/utility/quality_scaler.h @@ -64,6 +64,7 @@ class QualityScaler { void ReportQp(int qp, int64_t time_sent_us); void SetQpThresholds(VideoEncoder::QpThresholds thresholds); + bool QpFastFilterLow() const; // The following members declared protected for testing purposes. protected: diff --git a/rtc_base/experiments/BUILD.gn b/rtc_base/experiments/BUILD.gn index 5b055bf22b..add3988672 100644 --- a/rtc_base/experiments/BUILD.gn +++ b/rtc_base/experiments/BUILD.gn @@ -46,6 +46,21 @@ rtc_library("field_trial_parser") { ] } +rtc_library("quality_rampup_experiment") { + sources = [ + "quality_rampup_experiment.cc", + "quality_rampup_experiment.h", + ] + deps = [ + ":field_trial_parser", + "../:rtc_base_approved", + "../../api/transport:field_trial_based_config", + "../../api/transport:webrtc_key_value_config", + "../../system_wrappers:field_trial", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + rtc_library("quality_scaler_settings") { sources = [ "quality_scaler_settings.cc", @@ -221,6 +236,7 @@ if (rtc_include_tests) { "keyframe_interval_settings_unittest.cc", "min_video_bitrate_experiment_unittest.cc", "normalize_simulcast_size_experiment_unittest.cc", + "quality_rampup_experiment_unittest.cc", "quality_scaler_settings_unittest.cc", "quality_scaling_experiment_unittest.cc", "rate_control_settings_unittest.cc", @@ -235,6 +251,7 @@ if (rtc_include_tests) { ":keyframe_interval_settings_experiment", ":min_video_bitrate_experiment", ":normalize_simulcast_size_experiment", + ":quality_rampup_experiment", ":quality_scaler_settings", ":quality_scaling_experiment", ":rate_control_settings", diff --git a/rtc_base/experiments/quality_rampup_experiment.cc b/rtc_base/experiments/quality_rampup_experiment.cc new file mode 100644 index 0000000000..caf7e62368 --- /dev/null +++ b/rtc_base/experiments/quality_rampup_experiment.cc @@ -0,0 +1,73 @@ +/* + * 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 "rtc_base/experiments/quality_rampup_experiment.h" + +#include + +#include "api/transport/field_trial_based_config.h" +#include "rtc_base/logging.h" + +namespace webrtc { + +QualityRampupExperiment::QualityRampupExperiment( + const WebRtcKeyValueConfig* const key_value_config) + : min_pixels_("min_pixels"), + min_duration_ms_("min_duration_ms"), + max_bitrate_factor_("max_bitrate_factor") { + ParseFieldTrial( + {&min_pixels_, &min_duration_ms_, &max_bitrate_factor_}, + key_value_config->Lookup("WebRTC-Video-QualityRampupSettings")); +} + +QualityRampupExperiment QualityRampupExperiment::ParseSettings() { + FieldTrialBasedConfig field_trial_config; + return QualityRampupExperiment(&field_trial_config); +} + +absl::optional QualityRampupExperiment::MinPixels() const { + return min_pixels_.GetOptional(); +} + +absl::optional QualityRampupExperiment::MinDurationMs() const { + return min_duration_ms_.GetOptional(); +} + +absl::optional QualityRampupExperiment::MaxBitrateFactor() const { + return max_bitrate_factor_.GetOptional(); +} + +void QualityRampupExperiment::SetMaxBitrate(int pixels, + uint32_t max_bitrate_kbps) { + if (!min_pixels_ || pixels < min_pixels_.Value() || max_bitrate_kbps == 0) { + return; + } + max_bitrate_kbps_ = std::max(max_bitrate_kbps_.value_or(0), max_bitrate_kbps); +} + +bool QualityRampupExperiment::BwHigh(int64_t now_ms, + uint32_t available_bw_kbps) { + if (!min_pixels_ || !min_duration_ms_ || !max_bitrate_kbps_) { + return false; + } + + if (available_bw_kbps < + max_bitrate_kbps_.value() * MaxBitrateFactor().value_or(1)) { + start_ms_.reset(); + return false; + } + + if (!start_ms_) + start_ms_ = now_ms; + + return (now_ms - *start_ms_) >= min_duration_ms_.Value(); +} + +} // namespace webrtc diff --git a/rtc_base/experiments/quality_rampup_experiment.h b/rtc_base/experiments/quality_rampup_experiment.h new file mode 100644 index 0000000000..ff9d7d38e5 --- /dev/null +++ b/rtc_base/experiments/quality_rampup_experiment.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef RTC_BASE_EXPERIMENTS_QUALITY_RAMPUP_EXPERIMENT_H_ +#define RTC_BASE_EXPERIMENTS_QUALITY_RAMPUP_EXPERIMENT_H_ + +#include "absl/types/optional.h" +#include "api/transport/webrtc_key_value_config.h" +#include "rtc_base/experiments/field_trial_parser.h" + +namespace webrtc { + +class QualityRampupExperiment final { + public: + static QualityRampupExperiment ParseSettings(); + + absl::optional MinPixels() const; + absl::optional MinDurationMs() const; + absl::optional MaxBitrateFactor() const; + + // Sets the max bitrate and the frame size. + // The call has no effect if the frame size is less than |min_pixels_|. + void SetMaxBitrate(int pixels, uint32_t max_bitrate_kbps); + + // Returns true if the available bandwidth is a certain percentage + // (max_bitrate_factor_) above |max_bitrate_kbps_| for |min_duration_ms_|. + bool BwHigh(int64_t now_ms, uint32_t available_bw_kbps); + + private: + explicit QualityRampupExperiment( + const WebRtcKeyValueConfig* const key_value_config); + + FieldTrialOptional min_pixels_; + FieldTrialOptional min_duration_ms_; + FieldTrialOptional max_bitrate_factor_; + + absl::optional start_ms_; + absl::optional max_bitrate_kbps_; +}; + +} // namespace webrtc + +#endif // RTC_BASE_EXPERIMENTS_QUALITY_RAMPUP_EXPERIMENT_H_ diff --git a/rtc_base/experiments/quality_rampup_experiment_unittest.cc b/rtc_base/experiments/quality_rampup_experiment_unittest.cc new file mode 100644 index 0000000000..b0ede34791 --- /dev/null +++ b/rtc_base/experiments/quality_rampup_experiment_unittest.cc @@ -0,0 +1,139 @@ +/* + * Copyright 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 "rtc_base/experiments/quality_rampup_experiment.h" + +#include "test/field_trial.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +class QualityRampupExperimentTest : public ::testing::Test { + protected: + int64_t NowMs() const { return current_ms_; } + int64_t AdvanceMs(int64_t delta_ms) { + current_ms_ += delta_ms; + return current_ms_; + } + int64_t current_ms_ = 2345; +}; + +TEST_F(QualityRampupExperimentTest, ValuesNotSetByDefault) { + const auto settings = QualityRampupExperiment::ParseSettings(); + EXPECT_FALSE(settings.MinPixels()); + EXPECT_FALSE(settings.MinDurationMs()); + EXPECT_FALSE(settings.MaxBitrateFactor()); +} + +TEST_F(QualityRampupExperimentTest, ParseMinPixels) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/min_pixels:10000/"); + EXPECT_EQ(10000, QualityRampupExperiment::ParseSettings().MinPixels()); +} + +TEST_F(QualityRampupExperimentTest, ParseMinDuration) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/min_duration_ms:987/"); + EXPECT_EQ(987, QualityRampupExperiment::ParseSettings().MinDurationMs()); +} + +TEST_F(QualityRampupExperimentTest, ParseMaxBitrateFactor) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/max_bitrate_factor:1.23/"); + EXPECT_EQ(1.23, QualityRampupExperiment::ParseSettings().MaxBitrateFactor()); +} + +TEST_F(QualityRampupExperimentTest, ReportsBwHighWhenDurationPassed) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/" + "min_pixels:10000,min_duration_ms:2000/"); + auto exp = QualityRampupExperiment::ParseSettings(); + EXPECT_EQ(10000, exp.MinPixels()); + EXPECT_EQ(2000, exp.MinDurationMs()); + + const uint32_t kMaxKbps = 800; + exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps); + + const uint32_t kAvailableKbps = kMaxKbps; + EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000 - 1), kAvailableKbps)); + EXPECT_TRUE(exp.BwHigh(AdvanceMs(1), kAvailableKbps)); +} + +TEST_F(QualityRampupExperimentTest, UsesMaxSetBitrate) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/" + "min_pixels:10000,min_duration_ms:2000/"); + auto exp = QualityRampupExperiment::ParseSettings(); + + const uint32_t kMaxKbps = 800; + exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps); + exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps - 1); + + EXPECT_FALSE(exp.BwHigh(NowMs(), kMaxKbps - 1)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000), kMaxKbps - 1)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kMaxKbps)); + EXPECT_TRUE(exp.BwHigh(AdvanceMs(2000), kMaxKbps)); +} + +TEST_F(QualityRampupExperimentTest, DoesNotReportBwHighIfBelowMinPixels) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/" + "min_pixels:10000,min_duration_ms:2000/"); + auto exp = QualityRampupExperiment::ParseSettings(); + + const uint32_t kMaxKbps = 800; + exp.SetMaxBitrate(/*pixels*/ 9999, kMaxKbps); + + const uint32_t kAvailableKbps = kMaxKbps; + EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000), kAvailableKbps)); +} + +TEST_F(QualityRampupExperimentTest, ReportsBwHighWithMaxBitrateFactor) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/" + "min_pixels:10000,min_duration_ms:2000,max_bitrate_factor:1.5/"); + auto exp = QualityRampupExperiment::ParseSettings(); + EXPECT_EQ(10000, exp.MinPixels()); + EXPECT_EQ(2000, exp.MinDurationMs()); + EXPECT_EQ(1.5, exp.MaxBitrateFactor()); + + const uint32_t kMaxKbps = 800; + exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps); + + const uint32_t kAvailableKbps = kMaxKbps * 1.5; + EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps - 1)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000), kAvailableKbps - 1)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kAvailableKbps)); + EXPECT_TRUE(exp.BwHigh(AdvanceMs(2000), kAvailableKbps)); +} + +TEST_F(QualityRampupExperimentTest, ReportsBwHigh) { + test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/" + "min_pixels:10000,min_duration_ms:2000/"); + auto exp = QualityRampupExperiment::ParseSettings(); + + const uint32_t kMaxKbps = 800; + exp.SetMaxBitrate(/*pixels*/ 10000, kMaxKbps); + + const uint32_t kAvailableKbps = kMaxKbps; + EXPECT_FALSE(exp.BwHigh(NowMs(), kAvailableKbps)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000 - 1), kAvailableKbps)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kAvailableKbps - 1)); // Below, reset. + EXPECT_FALSE(exp.BwHigh(AdvanceMs(1), kAvailableKbps)); + EXPECT_FALSE(exp.BwHigh(AdvanceMs(2000 - 1), kAvailableKbps)); + EXPECT_TRUE(exp.BwHigh(AdvanceMs(1), kAvailableKbps)); +} + +} // namespace +} // namespace webrtc diff --git a/test/fake_encoder.cc b/test/fake_encoder.cc index 8ee1e99a9b..64b4a4e9ff 100644 --- a/test/fake_encoder.cc +++ b/test/fake_encoder.cc @@ -72,6 +72,11 @@ void FakeEncoder::SetMaxBitrate(int max_kbps) { SetRates(current_rate_settings_); } +void FakeEncoder::SetQp(int qp) { + rtc::CritScope cs(&crit_sect_); + qp_ = qp; +} + int32_t FakeEncoder::InitEncode(const VideoCodec* config, const Settings& settings) { rtc::CritScope cs(&crit_sect_); @@ -93,6 +98,7 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image, VideoCodecMode mode; bool keyframe; uint32_t counter; + absl::optional qp; { rtc::CritScope cs(&crit_sect_); max_framerate = config_.maxFramerate; @@ -109,6 +115,7 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image, keyframe = pending_keyframe_; pending_keyframe_ = false; counter = counter_++; + qp = qp_; } FrameInfo frame_info = @@ -134,6 +141,8 @@ int32_t FakeEncoder::Encode(const VideoFrame& input_image, : VideoFrameType::kVideoFrameDelta; encoded._encodedWidth = simulcast_streams[i].width; encoded._encodedHeight = simulcast_streams[i].height; + if (qp) + encoded.qp_ = *qp; encoded.SetSpatialIndex(i); CodecSpecificInfo codec_specific; std::unique_ptr fragmentation = diff --git a/test/fake_encoder.h b/test/fake_encoder.h index 566980e329..39838d16f1 100644 --- a/test/fake_encoder.h +++ b/test/fake_encoder.h @@ -41,6 +41,7 @@ class FakeEncoder : public VideoEncoder { // Sets max bitrate. Not thread-safe, call before registering the encoder. void SetMaxBitrate(int max_kbps); + void SetQp(int qp); void SetFecControllerOverride( FecControllerOverride* fec_controller_override) override; @@ -98,6 +99,7 @@ class FakeEncoder : public VideoEncoder { uint32_t counter_ RTC_GUARDED_BY(crit_sect_); rtc::CriticalSection crit_sect_; bool used_layers_[kMaxSimulcastStreams]; + absl::optional qp_ RTC_GUARDED_BY(crit_sect_); // Current byte debt to be payed over a number of frames. // The debt is acquired by keyframes overshooting the bitrate target. diff --git a/video/BUILD.gn b/video/BUILD.gn index de35ec9435..eb714db7d2 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -218,6 +218,7 @@ rtc_library("video_stream_encoder_impl") { "../rtc_base/experiments:alr_experiment", "../rtc_base/experiments:balanced_degradation_settings", "../rtc_base/experiments:field_trial_parser", + "../rtc_base/experiments:quality_rampup_experiment", "../rtc_base/experiments:quality_scaler_settings", "../rtc_base/experiments:quality_scaling_experiment", "../rtc_base/experiments:rate_control_settings", diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc index 6be0276baf..05615f6652 100644 --- a/video/video_stream_encoder.cc +++ b/video/video_stream_encoder.cc @@ -483,6 +483,8 @@ VideoStreamEncoder::VideoStreamEncoder( initial_framedrop_(0), initial_framedrop_on_bwe_enabled_( webrtc::field_trial::IsEnabled(kInitialFramedropFieldTrial)), + quality_rampup_done_(false), + quality_rampup_experiment_(QualityRampupExperiment::ParseSettings()), quality_scaling_experiment_enabled_(QualityScalingExperiment::Enabled()), source_proxy_(new VideoSourceProxy(this)), sink_(nullptr), @@ -879,6 +881,8 @@ void VideoStreamEncoder::ReconfigureEncoder() { send_codec_ = codec; encoder_switch_experiment_.SetCodec(send_codec_.codecType); + quality_rampup_experiment_.SetMaxBitrate( + last_frame_info_->width * last_frame_info_->height, codec.maxBitrate); // Keep the same encoder, as long as the video_format is unchanged. // Encoder creation block is split in two since EncoderInfo needed to start @@ -1359,6 +1363,16 @@ void VideoStreamEncoder::MaybeEncodeVideoFrame(const VideoFrame& video_frame, } initial_framedrop_ = kMaxInitialFramedrop; + if (!quality_rampup_done_ && TryQualityRampup(now_ms) && + GetConstAdaptCounter().ResolutionCount(kQuality) > 0 && + GetConstAdaptCounter().TotalCount(kCpu) == 0) { + RTC_LOG(LS_INFO) << "Reset quality limitations."; + last_adaptation_request_.reset(); + source_proxy_->ResetPixelFpsCount(); + adapt_counters_.clear(); + quality_rampup_done_ = true; + } + if (EncoderPaused()) { // Storing references to a native buffer risks blocking frame capture. if (video_frame.video_frame_buffer()->type() != @@ -1900,6 +1914,25 @@ bool VideoStreamEncoder::DropDueToSize(uint32_t pixel_count) const { return false; } +bool VideoStreamEncoder::TryQualityRampup(int64_t now_ms) { + if (!quality_scaler_) + return false; + + uint32_t bw_kbps = last_encoder_rate_settings_ + ? last_encoder_rate_settings_->rate_control + .bandwidth_allocation.kbps() + : 0; + + if (quality_rampup_experiment_.BwHigh(now_ms, bw_kbps)) { + // Verify that encoder is at max bitrate and the QP is low. + if (encoder_start_bitrate_bps_ == send_codec_.maxBitrate * 1000 && + quality_scaler_->QpFastFilterLow()) { + return true; + } + } + return false; +} + bool VideoStreamEncoder::AdaptDown(AdaptReason reason) { RTC_DCHECK_RUN_ON(&encoder_queue_); AdaptationRequest adaptation_request = { diff --git a/video/video_stream_encoder.h b/video/video_stream_encoder.h index 1b76e2bd44..149fcd647b 100644 --- a/video/video_stream_encoder.h +++ b/video/video_stream_encoder.h @@ -31,6 +31,7 @@ #include "rtc_base/critical_section.h" #include "rtc_base/event.h" #include "rtc_base/experiments/balanced_degradation_settings.h" +#include "rtc_base/experiments/quality_rampup_experiment.h" #include "rtc_base/experiments/quality_scaler_settings.h" #include "rtc_base/experiments/rate_control_settings.h" #include "rtc_base/numerics/exp_filter.h" @@ -158,6 +159,7 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface, // Indicates wether frame should be dropped because the pixel count is too // large for the current bitrate configuration. bool DropDueToSize(uint32_t pixel_count) const RTC_RUN_ON(&encoder_queue_); + bool TryQualityRampup(int64_t now_ms) RTC_RUN_ON(&encoder_queue_); // Implements EncodedImageCallback. EncodedImageCallback::Result OnEncodedImage( @@ -238,6 +240,9 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface, int initial_framedrop_; const bool initial_framedrop_on_bwe_enabled_; bool has_seen_first_significant_bwe_change_ = false; + bool quality_rampup_done_ RTC_GUARDED_BY(&encoder_queue_); + QualityRampupExperiment quality_rampup_experiment_ + RTC_GUARDED_BY(&encoder_queue_); const bool quality_scaling_experiment_enabled_; diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc index 52d98f4d72..068e4dd1df 100644 --- a/video/video_stream_encoder_unittest.cc +++ b/video/video_stream_encoder_unittest.cc @@ -55,6 +55,8 @@ using ::testing::StrictMock; namespace { const int kMinPixelsPerFrame = 320 * 180; +const int kQpLow = 1; +const int kQpHigh = 2; const int kMinFramerateFps = 2; const int kMinBalancedFramerateFps = 7; const int64_t kFrameTimeoutMs = 100; @@ -683,8 +685,8 @@ class VideoStreamEncoderTest : public ::testing::Test { EncoderInfo info; if (initialized_ == EncoderState::kInitialized) { if (quality_scaling_) { - info.scaling_settings = - VideoEncoder::ScalingSettings(1, 2, kMinPixelsPerFrame); + info.scaling_settings = VideoEncoder::ScalingSettings( + kQpLow, kQpHigh, kMinPixelsPerFrame); } info.is_hardware_accelerated = is_hardware_accelerated_; for (int i = 0; i < kMaxSpatialLayers; ++i) { @@ -3704,6 +3706,69 @@ TEST_F(VideoStreamEncoderTest, InitialFrameDropActivatesWhenBweDrops) { video_stream_encoder_->Stop(); } +TEST_F(VideoStreamEncoderTest, RampsUpInQualityWhenBwIsHigh) { + webrtc::test::ScopedFieldTrials field_trials( + "WebRTC-Video-QualityRampupSettings/min_pixels:1,min_duration_ms:2000/"); + + // Reset encoder for field trials to take effect. + VideoEncoderConfig config = video_encoder_config_.Copy(); + config.max_bitrate_bps = kTargetBitrateBps; + ConfigureEncoder(std::move(config)); + fake_encoder_.SetQp(kQpLow); + + // Enable MAINTAIN_FRAMERATE preference. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + video_stream_encoder_->SetSource(&source, + DegradationPreference::MAINTAIN_FRAMERATE); + + // Start at low bitrate. + const int kLowBitrateBps = 200000; + video_stream_encoder_->OnBitrateUpdated(DataRate::bps(kLowBitrateBps), + DataRate::bps(kLowBitrateBps), + DataRate::bps(kLowBitrateBps), 0, 0); + + // Expect first frame to be dropped and resolution to be limited. + const int kWidth = 1280; + const int kHeight = 720; + const int64_t kFrameIntervalMs = 100; + int64_t timestamp_ms = kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + ExpectDroppedFrame(); + EXPECT_LT(source.sink_wants().max_pixel_count, kWidth * kHeight); + + // Increase bitrate to encoder max. + video_stream_encoder_->OnBitrateUpdated(DataRate::bps(config.max_bitrate_bps), + DataRate::bps(config.max_bitrate_bps), + DataRate::bps(config.max_bitrate_bps), + 0, 0); + + // Insert frames and advance |min_duration_ms|. + for (size_t i = 1; i <= 10; i++) { + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + WaitForEncodedFrame(timestamp_ms); + } + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_LT(source.sink_wants().max_pixel_count, kWidth * kHeight); + + fake_clock_.AdvanceTime(TimeDelta::ms(2000)); + + // Insert frame should trigger high BW and release quality limitation. + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionMax(source.sink_wants()); + + // Frame should not be adapted. + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + WaitForEncodedFrame(kWidth, kHeight); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + + video_stream_encoder_->Stop(); +} + TEST_F(VideoStreamEncoderTest, ResolutionNotAdaptedForTooSmallFrame_MaintainFramerateMode) { const int kTooSmallWidth = 10;