diff --git a/webrtc/video/vie_encoder.cc b/webrtc/video/vie_encoder.cc index 203d1c405a..63b1c80fef 100644 --- a/webrtc/video/vie_encoder.cc +++ b/webrtc/video/vie_encoder.cc @@ -73,6 +73,29 @@ uint32_t MaximumFrameSizeForBitrate(uint32_t kbps) { return std::numeric_limits::max(); } +// Initial limits for kBalanced degradation preference. +int MinFps(int pixels) { + if (pixels <= 320 * 240) { + return 7; + } else if (pixels <= 480 * 270) { + return 10; + } else if (pixels <= 640 * 480) { + return 15; + } else { + return std::numeric_limits::max(); + } +} + +int MaxFps(int pixels) { + if (pixels <= 320 * 240) { + return 10; + } else if (pixels <= 480 * 270) { + return 15; + } else { + return std::numeric_limits::max(); + } +} + bool IsResolutionScalingEnabled( VideoSendStream::DegradationPreference degradation_preference) { return degradation_preference == @@ -211,7 +234,7 @@ class ViEEncoder::VideoSourceProxy { // the used degradation_preference. switch (degradation_preference_) { case VideoSendStream::DegradationPreference::kBalanced: - FALLTHROUGH(); + break; case VideoSendStream::DegradationPreference::kMaintainFramerate: wants.max_framerate_fps = std::numeric_limits::max(); break; @@ -227,6 +250,15 @@ class ViEEncoder::VideoSourceProxy { return wants; } + void ResetPixelFpsCount() { + rtc::CritScope lock(&crit_); + sink_wants_.max_pixel_count = std::numeric_limits::max(); + sink_wants_.target_pixel_count.reset(); + sink_wants_.max_framerate_fps = std::numeric_limits::max(); + if (source_) + source_->AddOrUpdateSink(vie_encoder_, sink_wants_); + } + bool RequestResolutionLowerThan(int pixel_count) { // Called on the encoder task queue. rtc::CritScope lock(&crit_); @@ -447,6 +479,15 @@ void ViEEncoder::SetSource( // Reset adaptation state, so that we're not tricked into thinking there's // an already pending request of the same type. last_adaptation_request_.reset(); + if (degradation_preference == + VideoSendStream::DegradationPreference::kBalanced || + degradation_preference_ == + VideoSendStream::DegradationPreference::kBalanced) { + // TODO(asapersson): Consider removing |adapt_counters_| map and use one + // AdaptCounter for all modes. + source_proxy_->ResetPixelFpsCount(); + adapt_counters_.clear(); + } } degradation_preference_ = degradation_preference; bool allow_scaling = IsResolutionScalingEnabled(degradation_preference_); @@ -803,12 +844,10 @@ void ViEEncoder::AdaptDown(AdaptReason reason) { last_adaptation_request_ && last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptDown; - int max_downgrades = 0; switch (degradation_preference_) { case VideoSendStream::DegradationPreference::kBalanced: - FALLTHROUGH(); + break; case VideoSendStream::DegradationPreference::kMaintainFramerate: - max_downgrades = kMaxCpuResolutionDowngrades; if (downgrade_requested && adaptation_request.input_pixel_count_ >= last_adaptation_request_->input_pixel_count_) { @@ -818,7 +857,6 @@ void ViEEncoder::AdaptDown(AdaptReason reason) { } break; case VideoSendStream::DegradationPreference::kMaintainResolution: - max_downgrades = kMaxCpuFramerateDowngrades; if (adaptation_request.framerate_fps_ <= 0 || (downgrade_requested && adaptation_request.framerate_fps_ < kMinFramerateFps)) { @@ -836,20 +874,32 @@ void ViEEncoder::AdaptDown(AdaptReason reason) { } if (reason == kCpu) { - if (GetConstAdaptCounter().TotalCount(kCpu) >= max_downgrades) + if (GetConstAdaptCounter().ResolutionCount(kCpu) >= + kMaxCpuResolutionDowngrades || + GetConstAdaptCounter().FramerateCount(kCpu) >= + kMaxCpuFramerateDowngrades) { return; + } } switch (degradation_preference_) { - case VideoSendStream::DegradationPreference::kBalanced: + case VideoSendStream::DegradationPreference::kBalanced: { + // Try scale down framerate, if lower. + int fps = MinFps(last_frame_info_->pixel_count()); + if (source_proxy_->RestrictFramerate(fps)) { + GetAdaptCounter().IncrementFramerate(reason); + break; + } + // Scale down resolution. FALLTHROUGH(); + } case VideoSendStream::DegradationPreference::kMaintainFramerate: // Scale down resolution. if (!source_proxy_->RequestResolutionLowerThan( adaptation_request.input_pixel_count_)) { return; } - GetAdaptCounter().IncrementResolution(reason, 1); + GetAdaptCounter().IncrementResolution(reason); break; case VideoSendStream::DegradationPreference::kMaintainResolution: // Scale down framerate. @@ -857,7 +907,7 @@ void ViEEncoder::AdaptDown(AdaptReason reason) { adaptation_request.framerate_fps_)) { return; } - GetAdaptCounter().IncrementFramerate(reason, 1); + GetAdaptCounter().IncrementFramerate(reason); break; case VideoSendStream::DegradationPreference::kDegradationDisabled: RTC_NOTREACHED(); @@ -888,29 +938,34 @@ void ViEEncoder::AdaptUp(AdaptReason reason) { last_adaptation_request_ && last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptUp; - switch (degradation_preference_) { - case VideoSendStream::DegradationPreference::kBalanced: - FALLTHROUGH(); - case VideoSendStream::DegradationPreference::kMaintainFramerate: - if (adapt_up_requested && - adaptation_request.input_pixel_count_ <= - last_adaptation_request_->input_pixel_count_) { - // Don't request higher resolution if the current resolution is not - // higher than the last time we asked for the resolution to be higher. - return; - } - break; - case VideoSendStream::DegradationPreference::kMaintainResolution: - // TODO(sprang): Don't request higher framerate if we are already at - // max requested fps? - break; - case VideoSendStream::DegradationPreference::kDegradationDisabled: + if (degradation_preference_ == + VideoSendStream::DegradationPreference::kMaintainFramerate) { + if (adapt_up_requested && + adaptation_request.input_pixel_count_ <= + last_adaptation_request_->input_pixel_count_) { + // Don't request higher resolution if the current resolution is not + // higher than the last time we asked for the resolution to be higher. return; + } } switch (degradation_preference_) { - case VideoSendStream::DegradationPreference::kBalanced: + case VideoSendStream::DegradationPreference::kBalanced: { + // Try scale up framerate, if higher. + int fps = MaxFps(last_frame_info_->pixel_count()); + if (source_proxy_->IncreaseFramerate(fps)) { + GetAdaptCounter().DecrementFramerate(reason, fps); + // Reset framerate in case of fewer fps steps down than up. + if (adapt_counter.FramerateCount() == 0 && + fps != std::numeric_limits::max()) { + LOG(LS_INFO) << "Removing framerate down-scaling setting."; + source_proxy_->IncreaseFramerate(std::numeric_limits::max()); + } + break; + } + // Scale up resolution. FALLTHROUGH(); + } case VideoSendStream::DegradationPreference::kMaintainFramerate: { // Scale up resolution. int pixel_count = adaptation_request.input_pixel_count_; @@ -920,7 +975,7 @@ void ViEEncoder::AdaptUp(AdaptReason reason) { } if (!source_proxy_->RequestHigherResolutionThan(pixel_count)) return; - GetAdaptCounter().IncrementResolution(reason, -1); + GetAdaptCounter().DecrementResolution(reason); break; } case VideoSendStream::DegradationPreference::kMaintainResolution: { @@ -932,11 +987,11 @@ void ViEEncoder::AdaptUp(AdaptReason reason) { } if (!source_proxy_->RequestHigherFramerateThan(fps)) return; - GetAdaptCounter().IncrementFramerate(reason, -1); + GetAdaptCounter().DecrementFramerate(reason); break; } case VideoSendStream::DegradationPreference::kDegradationDisabled: - RTC_NOTREACHED(); + return; } last_adaptation_request_.emplace(adaptation_request); @@ -994,6 +1049,7 @@ const ViEEncoder::AdaptCounter& ViEEncoder::GetConstAdaptCounter() { ViEEncoder::AdaptCounter::AdaptCounter() { fps_counters_.resize(kScaleReasonSize); resolution_counters_.resize(kScaleReasonSize); + static_assert(kScaleReasonSize == 2, "Update MoveCount."); } ViEEncoder::AdaptCounter::~AdaptCounter() {} @@ -1012,12 +1068,48 @@ ViEEncoder::AdaptCounts ViEEncoder::AdaptCounter::Counts(int reason) const { return counts; } -void ViEEncoder::AdaptCounter::IncrementFramerate(int reason, int delta) { - fps_counters_[reason] += delta; +void ViEEncoder::AdaptCounter::IncrementFramerate(int reason) { + ++(fps_counters_[reason]); } -void ViEEncoder::AdaptCounter::IncrementResolution(int reason, int delta) { - resolution_counters_[reason] += delta; +void ViEEncoder::AdaptCounter::IncrementResolution(int reason) { + ++(resolution_counters_[reason]); +} + +void ViEEncoder::AdaptCounter::DecrementFramerate(int reason) { + if (fps_counters_[reason] == 0) { + // Balanced mode: Adapt up is in a different order, switch reason. + // E.g. framerate adapt down: quality (2), framerate adapt up: cpu (3). + // 1. Down resolution (cpu): res={quality:0,cpu:1}, fps={quality:0,cpu:0} + // 2. Down fps (quality): res={quality:0,cpu:1}, fps={quality:1,cpu:0} + // 3. Up fps (cpu): res={quality:1,cpu:0}, fps={quality:0,cpu:0} + // 4. Up resolution (quality): res={quality:0,cpu:0}, fps={quality:0,cpu:0} + RTC_DCHECK_GT(TotalCount(reason), 0) << "No downgrade for reason."; + RTC_DCHECK_GT(FramerateCount(), 0) << "Framerate not downgraded."; + MoveCount(&resolution_counters_, reason); + MoveCount(&fps_counters_, (reason + 1) % kScaleReasonSize); + } + --(fps_counters_[reason]); + RTC_DCHECK_GE(fps_counters_[reason], 0); +} + +void ViEEncoder::AdaptCounter::DecrementResolution(int reason) { + if (resolution_counters_[reason] == 0) { + // Balanced mode: Adapt up is in a different order, switch reason. + RTC_DCHECK_GT(TotalCount(reason), 0) << "No downgrade for reason."; + RTC_DCHECK_GT(ResolutionCount(), 0) << "Resolution not downgraded."; + MoveCount(&fps_counters_, reason); + MoveCount(&resolution_counters_, (reason + 1) % kScaleReasonSize); + } + --(resolution_counters_[reason]); + RTC_DCHECK_GE(resolution_counters_[reason], 0); +} + +void ViEEncoder::AdaptCounter::DecrementFramerate(int reason, int cur_fps) { + DecrementFramerate(reason); + // Reset if at max fps (i.e. in case of fewer steps up than down). + if (cur_fps == std::numeric_limits::max()) + std::fill(fps_counters_.begin(), fps_counters_.end(), 0); } int ViEEncoder::AdaptCounter::FramerateCount() const { @@ -1028,10 +1120,6 @@ int ViEEncoder::AdaptCounter::ResolutionCount() const { return Count(resolution_counters_); } -int ViEEncoder::AdaptCounter::TotalCount() const { - return FramerateCount() + ResolutionCount(); -} - int ViEEncoder::AdaptCounter::FramerateCount(int reason) const { return fps_counters_[reason]; } @@ -1048,6 +1136,13 @@ int ViEEncoder::AdaptCounter::Count(const std::vector& counters) const { return std::accumulate(counters.begin(), counters.end(), 0); } +void ViEEncoder::AdaptCounter::MoveCount(std::vector* counters, + int from_reason) { + int to_reason = (from_reason + 1) % kScaleReasonSize; + ++((*counters)[to_reason]); + --((*counters)[from_reason]); +} + std::string ViEEncoder::AdaptCounter::ToString( const std::vector& counters) const { std::stringstream ss; diff --git a/webrtc/video/vie_encoder.h b/webrtc/video/vie_encoder.h index f2d7cfdae2..460abf128e 100644 --- a/webrtc/video/vie_encoder.h +++ b/webrtc/video/vie_encoder.h @@ -189,13 +189,15 @@ class ViEEncoder : public rtc::VideoSinkInterface, std::string ToString() const; - void IncrementFramerate(int reason, int delta); - void IncrementResolution(int reason, int delta); + void IncrementFramerate(int reason); + void IncrementResolution(int reason); + void DecrementFramerate(int reason); + void DecrementResolution(int reason); + void DecrementFramerate(int reason, int cur_fps); // Gets the total number of downgrades (for all adapt reasons). int FramerateCount() const; int ResolutionCount() const; - int TotalCount() const; // Gets the total number of downgrades for |reason|. int FramerateCount(int reason) const; @@ -205,6 +207,7 @@ class ViEEncoder : public rtc::VideoSinkInterface, private: std::string ToString(const std::vector& counters) const; int Count(const std::vector& counters) const; + void MoveCount(std::vector* counters, int from_reason); // Degradation counters holding number of framerate/resolution reductions // per adapt reason. diff --git a/webrtc/video/vie_encoder_unittest.cc b/webrtc/video/vie_encoder_unittest.cc index 7a54fbe925..a016fa9313 100644 --- a/webrtc/video/vie_encoder_unittest.cc +++ b/webrtc/video/vie_encoder_unittest.cc @@ -311,6 +311,37 @@ class ViEEncoderTest : public ::testing::Test { EXPECT_GT(wants1.max_pixel_count, wants2.max_pixel_count); } + void VerifyFpsMaxResolutionEq(const rtc::VideoSinkWants& wants1, + const rtc::VideoSinkWants& wants2) { + EXPECT_EQ(std::numeric_limits::max(), wants1.max_framerate_fps); + EXPECT_EQ(wants1.max_pixel_count, wants2.max_pixel_count); + } + + void VerifyFpsLtResolutionEq(const rtc::VideoSinkWants& wants1, + const rtc::VideoSinkWants& wants2) { + EXPECT_LT(wants1.max_framerate_fps, wants2.max_framerate_fps); + EXPECT_EQ(wants1.max_pixel_count, wants2.max_pixel_count); + } + + void VerifyFpsGtResolutionEq(const rtc::VideoSinkWants& wants1, + const rtc::VideoSinkWants& wants2) { + EXPECT_GT(wants1.max_framerate_fps, wants2.max_framerate_fps); + EXPECT_EQ(wants1.max_pixel_count, wants2.max_pixel_count); + } + + void VerifyFpsEqResolutionLt(const rtc::VideoSinkWants& wants1, + const rtc::VideoSinkWants& wants2) { + EXPECT_EQ(wants1.max_framerate_fps, wants2.max_framerate_fps); + EXPECT_LT(wants1.max_pixel_count, wants2.max_pixel_count); + EXPECT_GT(wants1.max_pixel_count, 0); + } + + void VerifyFpsEqResolutionGt(const rtc::VideoSinkWants& wants1, + const rtc::VideoSinkWants& wants2) { + EXPECT_EQ(wants1.max_framerate_fps, wants2.max_framerate_fps); + EXPECT_GT(wants1.max_pixel_count, wants2.max_pixel_count); + } + void VerifyFpsMaxResolutionLt(const rtc::VideoSinkWants& wants, int pixel_count) { EXPECT_EQ(std::numeric_limits::max(), wants.max_framerate_fps); @@ -324,6 +355,13 @@ class ViEEncoderTest : public ::testing::Test { EXPECT_FALSE(wants.target_pixel_count); } + void VerifyFpsEqResolutionMax(const rtc::VideoSinkWants& wants, + int expected_fps) { + EXPECT_EQ(expected_fps, wants.max_framerate_fps); + EXPECT_EQ(std::numeric_limits::max(), wants.max_pixel_count); + EXPECT_FALSE(wants.target_pixel_count); + } + class TestEncoder : public test::FakeEncoder { public: TestEncoder() @@ -808,6 +846,59 @@ TEST_F(ViEEncoderTest, SinkWantsFromOveruseDetector) { vie_encoder_->Stop(); } +TEST_F(ViEEncoderTest, TestMaxCpuResolutionDowngrades_BalancedMode_NoFpsLimit) { + const int kMaxDowngrades = ViEEncoder::kMaxCpuResolutionDowngrades; + const int kWidth = 1280; + const int kHeight = 720; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + + // Trigger adapt down kMaxCpuDowngrades times. + int t = 1; + for (int i = 1; i <= kMaxDowngrades; ++i) { + source.IncomingCapturedFrame(CreateFrame(t, kWidth, kHeight)); + sink_.WaitForEncodedFrame(t++); + vie_encoder_->TriggerCpuOveruse(); + VerifyFpsMaxResolutionLt(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_EQ(i, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + } + + // Trigger adapt down, max cpu downgrades reach, expect no change. + rtc::VideoSinkWants last_wants = source.sink_wants(); + source.IncomingCapturedFrame(CreateFrame(t, kWidth, kHeight)); + sink_.WaitForEncodedFrame(t++); + vie_encoder_->TriggerCpuOveruse(); + VerifyFpsEqResolutionEq(source.sink_wants(), last_wants); + EXPECT_EQ(last_wants.max_pixel_count, source.sink_wants().max_pixel_count); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_EQ(kMaxDowngrades, + stats_proxy_->GetStats().number_of_cpu_adapt_changes); + + // Trigger adapt up kMaxCpuDowngrades times. + for (int i = 1; i <= kMaxDowngrades; ++i) { + source.IncomingCapturedFrame(CreateFrame(t, kWidth, kHeight)); + sink_.WaitForEncodedFrame(t++); + vie_encoder_->TriggerCpuNormalUsage(); + VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_GT(source.sink_wants().max_pixel_count, last_wants.max_pixel_count); + EXPECT_EQ(kMaxDowngrades + i, + stats_proxy_->GetStats().number_of_cpu_adapt_changes); + } + + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + + vie_encoder_->Stop(); +} TEST_F(ViEEncoderTest, SinkWantsStoredByDegradationPreference) { vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); VerifyNoLimitation(video_source_.sink_wants()); @@ -1376,6 +1467,45 @@ TEST_F(ViEEncoderTest, SkipsSameAdaptDownRequest_MaintainFramerateMode) { vie_encoder_->Stop(); } +TEST_F(ViEEncoderTest, SkipsSameOrLargerAdaptDownRequest_BalancedMode) { + const int kWidth = 1280; + const int kHeight = 720; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + test::FrameForwarder source; + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + source.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight)); + sink_.WaitForEncodedFrame(1); + VerifyNoLimitation(source.sink_wants()); + + // Trigger adapt down, expect scaled down resolution. + vie_encoder_->TriggerQualityLow(); + VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + const int kLastMaxPixelCount = source.sink_wants().max_pixel_count; + + // Trigger adapt down for same input resolution, expect no change. + source.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight)); + sink_.WaitForEncodedFrame(2); + vie_encoder_->TriggerQualityLow(); + EXPECT_EQ(kLastMaxPixelCount, source.sink_wants().max_pixel_count); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down for larger input resolution, expect no change. + source.IncomingCapturedFrame(CreateFrame(3, kWidth + 1, kHeight + 1)); + sink_.WaitForEncodedFrame(3); + vie_encoder_->TriggerQualityLow(); + EXPECT_EQ(kLastMaxPixelCount, source.sink_wants().max_pixel_count); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + vie_encoder_->Stop(); +} + TEST_F(ViEEncoderTest, NoChangeForInitialNormalUsage_MaintainFramerateMode) { const int kWidth = 1280; const int kHeight = 720; @@ -1426,6 +1556,33 @@ TEST_F(ViEEncoderTest, NoChangeForInitialNormalUsage_MaintainResolutionMode) { vie_encoder_->Stop(); } +TEST_F(ViEEncoderTest, NoChangeForInitialNormalUsage_BalancedMode) { + const int kWidth = 1280; + const int kHeight = 720; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + test::FrameForwarder source; + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + + source.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no change. + vie_encoder_->TriggerQualityHigh(); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + vie_encoder_->Stop(); +} + TEST_F(ViEEncoderTest, NoChangeForInitialNormalUsage_DisabledMode) { const int kWidth = 1280; const int kHeight = 720; @@ -1617,6 +1774,59 @@ TEST_F(ViEEncoderTest, vie_encoder_->Stop(); } +TEST_F(ViEEncoderTest, + AdaptsResolutionUpAndDownTwiceForLowQuality_BalancedMode_NoFpsLimit) { + const int kWidth = 1280; + const int kHeight = 720; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + + source.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyNoLimitation(source.sink_wants()); + 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 resolution. + vie_encoder_->TriggerQualityLow(); + source.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight)); + sink_.WaitForEncodedFrame(2); + VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no restriction. + vie_encoder_->TriggerQualityHigh(); + source.IncomingCapturedFrame(CreateFrame(3, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down resolution. + vie_encoder_->TriggerQualityLow(); + source.IncomingCapturedFrame(CreateFrame(4, kWidth, kHeight)); + sink_.WaitForEncodedFrame(4); + VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no restriction. + vie_encoder_->TriggerQualityHigh(); + source.IncomingCapturedFrame(CreateFrame(5, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + vie_encoder_->Stop(); +} + TEST_F(ViEEncoderTest, AdaptsResolutionOnOveruseAndLowQuality_MaintainFramerateMode) { const int kWidth = 1280; @@ -1926,6 +2136,41 @@ TEST_F(ViEEncoderTest, vie_encoder_->Stop(); } +TEST_F(ViEEncoderTest, ResolutionNotAdaptedForTooSmallFrame_BalancedMode) { + const int kTooSmallWidth = 10; + const int kTooSmallHeight = 10; + const int kFpsLimit = 7; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + test::FrameForwarder source; + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + + // Trigger adapt down, expect limited framerate. + source.IncomingCapturedFrame(CreateFrame(1, kTooSmallWidth, kTooSmallHeight)); + sink_.WaitForEncodedFrame(1); + vie_encoder_->TriggerQualityLow(); + VerifyFpsEqResolutionMax(source.sink_wants(), kFpsLimit); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, too small frame, expect no change. + source.IncomingCapturedFrame(CreateFrame(2, kTooSmallWidth, kTooSmallHeight)); + sink_.WaitForEncodedFrame(2); + vie_encoder_->TriggerQualityLow(); + VerifyFpsEqResolutionMax(source.sink_wants(), kFpsLimit); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + vie_encoder_->Stop(); +} + TEST_F(ViEEncoderTest, FailingInitEncodeDoesntCauseCrash) { fake_encoder_.ForceInitEncodeFailure(true); vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); @@ -2118,6 +2363,384 @@ TEST_F(ViEEncoderTest, DoesntAdaptDownPastMinFramerate) { CreateFrame(timestamp_ms, kFrameWidth, kFrameHeight)); sink_.WaitForEncodedFrame(timestamp_ms); } + vie_encoder_->Stop(); } + +TEST_F(ViEEncoderTest, AdaptsResolutionAndFramerateForLowQuality_BalancedMode) { + const int kWidth = 1280; + const int kHeight = 720; + const int64_t kFrameIntervalMs = 150; + int64_t timestamp_ms = kFrameIntervalMs; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down resolution (960x540@30fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down resolution (640x360@30fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionLt(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect reduced fps (640x360@15fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsLtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down resolution (480x270@15fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + 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_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Restrict bitrate, trigger adapt down, expect reduced fps (480x270@10fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsLtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(5, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect scaled down resolution (320x180@10fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + 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_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(6, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect reduced fps (320x180@7fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsLtResolutionEq(source.sink_wants(), source.last_wants()); + rtc::VideoSinkWants last_wants = source.sink_wants(); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(7, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, min resolution reached, expect no change. + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionEq(source.sink_wants(), last_wants); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(7, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt down, expect expect increased fps (320x180@10fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsGtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(8, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect upscaled resolution (480x270@10fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(9, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Increase bitrate, trigger adapt up, expect increased fps (480x270@15fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsGtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(10, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect upscaled resolution (640x360@15fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(11, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect increased fps (640x360@30fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(12, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect upscaled resolution (960x540@30fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + 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). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants()); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_EQ(14, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no change. + vie_encoder_->TriggerQualityHigh(); + VerifyNoLimitation(source.sink_wants()); + EXPECT_EQ(14, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + vie_encoder_->Stop(); +} + +TEST_F(ViEEncoderTest, AdaptWithTwoReasonsAndDifferentOrder_Framerate) { + const int kWidth = 1280; + const int kHeight = 720; + const int64_t kFrameIntervalMs = 150; + int64_t timestamp_ms = kFrameIntervalMs; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger cpu adapt down, expect scaled down resolution (960x540@30fps). + vie_encoder_->TriggerCpuOveruse(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger cpu adapt down, expect scaled down resolution (640x360@30fps). + vie_encoder_->TriggerCpuOveruse(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionLt(source.sink_wants(), source.last_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger quality adapt down, expect reduced fps (640x360@15fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsLtResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger cpu adapt up, expect increased fps (640x360@30fps). + vie_encoder_->TriggerCpuNormalUsage(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionEq(source.sink_wants(), source.last_wants()); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger quality adapt up, expect upscaled resolution (960x540@30fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(3, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger cpu adapt up, expect no restriction (1280x720fps@30fps). + vie_encoder_->TriggerCpuNormalUsage(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants()); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no change. + vie_encoder_->TriggerQualityHigh(); + VerifyNoLimitation(source.sink_wants()); + EXPECT_EQ(4, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + vie_encoder_->Stop(); +} + +TEST_F(ViEEncoderTest, AdaptWithTwoReasonsAndDifferentOrder_Resolution) { + const int kWidth = 640; + const int kHeight = 360; + const int kFpsLimit = 15; + const int64_t kFrameIntervalMs = 150; + int64_t timestamp_ms = kFrameIntervalMs; + vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0); + + // Enable kBalanced preference, no initial limitation. + AdaptingFrameForwarder source; + source.set_adaptation_enabled(true); + vie_encoder_->SetSource(&source, + VideoSendStream::DegradationPreference::kBalanced); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(kWidth, kHeight); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger cpu adapt down, expect scaled down framerate (640x360@15fps). + vie_encoder_->TriggerCpuOveruse(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionMax(source.sink_wants(), kFpsLimit); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger quality adapt down, expect scaled down resolution (480x270@15fps). + vie_encoder_->TriggerQualityLow(); + timestamp_ms += kFrameIntervalMs; + 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_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger cpu adapt up, expect upscaled resolution (640x360@15fps). + vie_encoder_->TriggerCpuNormalUsage(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyFpsEqResolutionGt(source.sink_wants(), source.last_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger quality adapt up, expect increased fps (640x360@30fps). + vie_encoder_->TriggerQualityHigh(); + timestamp_ms += kFrameIntervalMs; + source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight)); + sink_.WaitForEncodedFrame(timestamp_ms); + VerifyNoLimitation(source.sink_wants()); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution); + EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + // Trigger adapt up, expect no change. + vie_encoder_->TriggerQualityHigh(); + VerifyNoLimitation(source.sink_wants()); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes); + EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes); + + vie_encoder_->Stop(); +} + } // namespace webrtc