| Index: webrtc/video/vie_encoder_unittest.cc
 | 
| diff --git a/webrtc/video/vie_encoder_unittest.cc b/webrtc/video/vie_encoder_unittest.cc
 | 
| index eb39bf7c4d909b40861500c0174e7f7740ece200..778cd82e7d3de895b8ae47f9d8807120a7129c01 100644
 | 
| --- a/webrtc/video/vie_encoder_unittest.cc
 | 
| +++ b/webrtc/video/vie_encoder_unittest.cc
 | 
| @@ -34,6 +34,9 @@ namespace {
 | 
|  const int kMinPixelsPerFrame = 320 * 180;
 | 
|  const int kMinFramerateFps = 2;
 | 
|  const int64_t kFrameTimeoutMs = 100;
 | 
| +const int kBpsLimitFor15Fps = 500000;
 | 
| +const int kBpsLimitFor10Fps = 130000;
 | 
| +const int kBpsLimitFor7Fps = 60000;
 | 
|  }  // namespace
 | 
|  
 | 
|  namespace webrtc {
 | 
| @@ -311,6 +314,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<int>::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<int>::max(), wants.max_framerate_fps);
 | 
| @@ -324,6 +358,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<int>::max(), wants.max_pixel_count);
 | 
| +    EXPECT_FALSE(wants.target_pixel_count);
 | 
| +  }
 | 
| +
 | 
|    class TestEncoder : public test::FakeEncoder {
 | 
|     public:
 | 
|      TestEncoder()
 | 
| @@ -808,6 +849,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(kBpsLimitFor15Fps + 1, 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());
 | 
| @@ -1048,8 +1142,8 @@ TEST_F(ViEEncoderTest, SwitchingSourceKeepsQualityAdaptation) {
 | 
|    video_source_.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight));
 | 
|    sink_.WaitForEncodedFrame(1);
 | 
|    VideoSendStream::Stats stats = stats_proxy_->GetStats();
 | 
| -  EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
|    EXPECT_FALSE(stats.bw_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.bw_limited_framerate);
 | 
|    EXPECT_EQ(0, stats.number_of_quality_adapt_changes);
 | 
|  
 | 
|    // Set new source with adaptation still enabled.
 | 
| @@ -1060,8 +1154,8 @@ TEST_F(ViEEncoderTest, SwitchingSourceKeepsQualityAdaptation) {
 | 
|    new_video_source.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight));
 | 
|    sink_.WaitForEncodedFrame(2);
 | 
|    stats = stats_proxy_->GetStats();
 | 
| -  EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
|    EXPECT_FALSE(stats.bw_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.bw_limited_framerate);
 | 
|    EXPECT_EQ(0, stats.number_of_quality_adapt_changes);
 | 
|  
 | 
|    // Trigger adapt down.
 | 
| @@ -1069,8 +1163,8 @@ TEST_F(ViEEncoderTest, SwitchingSourceKeepsQualityAdaptation) {
 | 
|    new_video_source.IncomingCapturedFrame(CreateFrame(3, kWidth, kHeight));
 | 
|    sink_.WaitForEncodedFrame(3);
 | 
|    stats = stats_proxy_->GetStats();
 | 
| -  EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
|    EXPECT_TRUE(stats.bw_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.bw_limited_framerate);
 | 
|    EXPECT_EQ(1, stats.number_of_quality_adapt_changes);
 | 
|  
 | 
|    // Set new source with adaptation still enabled.
 | 
| @@ -1080,8 +1174,8 @@ TEST_F(ViEEncoderTest, SwitchingSourceKeepsQualityAdaptation) {
 | 
|    new_video_source.IncomingCapturedFrame(CreateFrame(4, kWidth, kHeight));
 | 
|    sink_.WaitForEncodedFrame(4);
 | 
|    stats = stats_proxy_->GetStats();
 | 
| -  EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
|    EXPECT_TRUE(stats.bw_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.bw_limited_framerate);
 | 
|    EXPECT_EQ(1, stats.number_of_quality_adapt_changes);
 | 
|  
 | 
|    // Disable resolution scaling.
 | 
| @@ -1092,8 +1186,8 @@ TEST_F(ViEEncoderTest, SwitchingSourceKeepsQualityAdaptation) {
 | 
|    new_video_source.IncomingCapturedFrame(CreateFrame(5, kWidth, kHeight));
 | 
|    sink_.WaitForEncodedFrame(5);
 | 
|    stats = stats_proxy_->GetStats();
 | 
| -  EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
|    EXPECT_FALSE(stats.bw_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.bw_limited_framerate);
 | 
|    EXPECT_EQ(1, stats.number_of_quality_adapt_changes);
 | 
|    EXPECT_EQ(0, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
| @@ -1154,6 +1248,7 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|    sink_.WaitForEncodedFrame(sequence++);
 | 
|    VideoSendStream::Stats stats = stats_proxy_->GetStats();
 | 
|    EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(0, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
|    // Trigger CPU overuse, should now adapt down.
 | 
| @@ -1162,6 +1257,7 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|    sink_.WaitForEncodedFrame(sequence++);
 | 
|    stats = stats_proxy_->GetStats();
 | 
|    EXPECT_TRUE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(1, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
|    // Set new source with adaptation still enabled.
 | 
| @@ -1219,6 +1315,7 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|  
 | 
|    stats = stats_proxy_->GetStats();
 | 
|    EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(2, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
|    // Try to trigger overuse. Should not succeed.
 | 
| @@ -1228,6 +1325,7 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|  
 | 
|    stats = stats_proxy_->GetStats();
 | 
|    EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(2, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
|    // Switch back the source with resolution adaptation enabled.
 | 
| @@ -1238,6 +1336,7 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|    sink_.WaitForEncodedFrame(sequence++);
 | 
|    stats = stats_proxy_->GetStats();
 | 
|    EXPECT_TRUE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(2, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
|    // Trigger CPU normal usage.
 | 
| @@ -1246,6 +1345,7 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|    sink_.WaitForEncodedFrame(sequence++);
 | 
|    stats = stats_proxy_->GetStats();
 | 
|    EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(3, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
|    // Back to the source with adaptation off, set it back to maintain-resolution.
 | 
| @@ -1256,8 +1356,9 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|        CreateFrame(sequence, kWidth, kHeight));
 | 
|    sink_.WaitForEncodedFrame(sequence++);
 | 
|    stats = stats_proxy_->GetStats();
 | 
| -  // Disabled, since we previously switched the source too disabled.
 | 
| +  // Disabled, since we previously switched the source to disabled.
 | 
|    EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_TRUE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(3, stats.number_of_cpu_adapt_changes);
 | 
|  
 | 
|    // Trigger CPU normal usage.
 | 
| @@ -1267,6 +1368,7 @@ TEST_F(ViEEncoderTest, StatsTracksCpuAdaptationStatsWhenSwitchingSource) {
 | 
|    sink_.WaitForEncodedFrame(sequence++);
 | 
|    stats = stats_proxy_->GetStats();
 | 
|    EXPECT_FALSE(stats.cpu_limited_resolution);
 | 
| +  EXPECT_FALSE(stats.cpu_limited_framerate);
 | 
|    EXPECT_EQ(4, stats.number_of_cpu_adapt_changes);
 | 
|    EXPECT_EQ(0, stats.number_of_quality_adapt_changes);
 | 
|  
 | 
| @@ -1368,6 +1470,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(kBpsLimitFor15Fps + 1, 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;
 | 
| @@ -1418,6 +1559,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;
 | 
| @@ -1610,6 +1778,59 @@ TEST_F(ViEEncoderTest,
 | 
|  }
 | 
|  
 | 
|  TEST_F(ViEEncoderTest,
 | 
| +       AdaptsResolutionUpAndDownTwiceForLowQuality_BalancedMode_NoFpsLimit) {
 | 
| +  const int kWidth = 1280;
 | 
| +  const int kHeight = 720;
 | 
| +  vie_encoder_->OnBitrateUpdated(kBpsLimitFor15Fps + 1, 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;
 | 
|    const int kHeight = 720;
 | 
| @@ -1918,6 +2139,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(kBpsLimitFor7Fps, 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);
 | 
| @@ -2112,4 +2368,363 @@ TEST_F(ViEEncoderTest, DoesntAdaptDownPastMinFramerate) {
 | 
|    }
 | 
|    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(kBpsLimitFor15Fps, 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_->OnBitrateUpdated(kBpsLimitFor10Fps, 0, 0);
 | 
| +  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());
 | 
| +  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(6, 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(6, 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(7, stats_proxy_->GetStats().number_of_quality_adapt_changes);
 | 
| +
 | 
| +  // Increase bitrate, trigger adapt up, expect increased fps (480x270@15fps).
 | 
| +  vie_encoder_->OnBitrateUpdated(kBpsLimitFor15Fps + 1, 0, 0);
 | 
| +  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 (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(9, 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(10, 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(11, 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(12, stats_proxy_->GetStats().number_of_quality_adapt_changes);
 | 
| +
 | 
| +  // Trigger adapt up, expect no change.
 | 
| +  vie_encoder_->TriggerQualityHigh();
 | 
| +  VerifyNoLimitation(source.sink_wants());
 | 
| +  EXPECT_EQ(12, 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(kBpsLimitFor15Fps, 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(kBpsLimitFor15Fps, 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
 | 
| 
 |