Update adaptation stats to support degradations in both resolution and framerate.

Add AdaptCounter class which holds the number of downgrade counts per degradation way (resolution/fps) and reason (cpu/quality).

BUG=webrtc:7607

Review-Url: https://codereview.webrtc.org/2871623002
Cr-Commit-Position: refs/heads/master@{#18156}
diff --git a/webrtc/video/send_statistics_proxy.cc b/webrtc/video/send_statistics_proxy.cc
index 71d980a..cea9ce7 100644
--- a/webrtc/video/send_statistics_proxy.cc
+++ b/webrtc/video/send_statistics_proxy.cc
@@ -305,8 +305,8 @@
   }
 
   if (first_rtp_stats_time_ms_ != -1) {
-    quality_scaling_timer_.Stop(clock_->TimeInMilliseconds());
-    int64_t elapsed_sec = quality_scaling_timer_.total_ms / 1000;
+    quality_adapt_timer_.Stop(clock_->TimeInMilliseconds());
+    int64_t elapsed_sec = quality_adapt_timer_.total_ms / 1000;
     if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
       int quality_changes = current_stats.number_of_quality_adapt_changes -
                             start_stats_.number_of_quality_adapt_changes;
@@ -314,8 +314,8 @@
                                 uma_prefix_ + "AdaptChangesPerMinute.Quality",
                                 quality_changes * 60 / elapsed_sec);
     }
-    cpu_scaling_timer_.Stop(clock_->TimeInMilliseconds());
-    elapsed_sec = cpu_scaling_timer_.total_ms / 1000;
+    cpu_adapt_timer_.Stop(clock_->TimeInMilliseconds());
+    elapsed_sec = cpu_adapt_timer_.total_ms / 1000;
     if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
       int cpu_changes = current_stats.number_of_cpu_adapt_changes -
                         start_stats_.number_of_cpu_adapt_changes;
@@ -510,14 +510,14 @@
     uma_container_->retransmit_byte_counter_.ProcessAndPauseForDuration(kMinMs);
     uma_container_->fec_byte_counter_.ProcessAndPauseForDuration(kMinMs);
     // Stop adaptation stats.
-    uma_container_->cpu_scaling_timer_.Stop(now_ms);
-    uma_container_->quality_scaling_timer_.Stop(now_ms);
+    uma_container_->cpu_adapt_timer_.Stop(now_ms);
+    uma_container_->quality_adapt_timer_.Stop(now_ms);
   } else {
     // Start adaptation stats if scaling is enabled.
     if (cpu_downscales_ >= 0)
-      uma_container_->cpu_scaling_timer_.Start(now_ms);
+      uma_container_->cpu_adapt_timer_.Start(now_ms);
     if (quality_downscales_ >= 0)
-      uma_container_->quality_scaling_timer_.Start(now_ms);
+      uma_container_->quality_adapt_timer_.Start(now_ms);
     // Stop pause explicitly for stats that may be zero/not updated for some
     // time.
     uma_container_->rtx_byte_counter_.ProcessAndStopPause();
@@ -734,50 +734,53 @@
       "ssrc", rtp_config_.ssrcs[0]);
 }
 
-void SendStatisticsProxy::SetCpuScalingStats(int num_cpu_downscales) {
+void SendStatisticsProxy::SetAdaptationStats(
+    const ViEEncoder::AdaptCounts& cpu_counts,
+    const ViEEncoder::AdaptCounts& quality_counts) {
   rtc::CritScope lock(&crit_);
-  cpu_downscales_ = num_cpu_downscales;
-  stats_.cpu_limited_resolution = num_cpu_downscales > 0;
-
-  if (num_cpu_downscales >= 0) {
-    // Scaling enabled.
-    if (!stats_.suspended)
-      uma_container_->cpu_scaling_timer_.Start(clock_->TimeInMilliseconds());
-    return;
-  }
-  uma_container_->cpu_scaling_timer_.Stop(clock_->TimeInMilliseconds());
+  SetAdaptTimer(cpu_counts, &uma_container_->cpu_adapt_timer_);
+  SetAdaptTimer(quality_counts, &uma_container_->quality_adapt_timer_);
+  UpdateAdaptationStats(cpu_counts, quality_counts);
 }
 
-void SendStatisticsProxy::SetQualityScalingStats(int num_quality_downscales) {
+void SendStatisticsProxy::OnCpuAdaptationChanged(
+    const ViEEncoder::AdaptCounts& cpu_counts,
+    const ViEEncoder::AdaptCounts& quality_counts) {
   rtc::CritScope lock(&crit_);
-  quality_downscales_ = num_quality_downscales;
-  stats_.bw_limited_resolution = quality_downscales_ > 0;
-
-  if (num_quality_downscales >= 0) {
-    // Scaling enabled.
-    if (!stats_.suspended) {
-      uma_container_->quality_scaling_timer_.Start(
-          clock_->TimeInMilliseconds());
-    }
-    return;
-  }
-  uma_container_->quality_scaling_timer_.Stop(clock_->TimeInMilliseconds());
-}
-
-void SendStatisticsProxy::OnCpuRestrictedResolutionChanged(
-    bool cpu_restricted_resolution) {
-  rtc::CritScope lock(&crit_);
-  stats_.cpu_limited_resolution = cpu_restricted_resolution;
   ++stats_.number_of_cpu_adapt_changes;
+  UpdateAdaptationStats(cpu_counts, quality_counts);
   TRACE_EVENT_INSTANT0("webrtc_stats", "WebRTC.Video.CpuAdaptationChanges");
 }
 
-void SendStatisticsProxy::OnQualityRestrictedResolutionChanged(
-    int num_quality_downscales) {
+void SendStatisticsProxy::OnQualityAdaptationChanged(
+    const ViEEncoder::AdaptCounts& cpu_counts,
+    const ViEEncoder::AdaptCounts& quality_counts) {
   rtc::CritScope lock(&crit_);
   ++stats_.number_of_quality_adapt_changes;
-  quality_downscales_ = num_quality_downscales;
-  stats_.bw_limited_resolution = quality_downscales_ > 0;
+  UpdateAdaptationStats(cpu_counts, quality_counts);
+}
+
+void SendStatisticsProxy::UpdateAdaptationStats(
+    const ViEEncoder::AdaptCounts& cpu_counts,
+    const ViEEncoder::AdaptCounts& quality_counts) {
+  cpu_downscales_ = cpu_counts.resolution;
+  quality_downscales_ = quality_counts.resolution;
+
+  stats_.cpu_limited_resolution = cpu_counts.resolution > 0;
+  stats_.cpu_limited_framerate = cpu_counts.fps > 0;
+  stats_.bw_limited_resolution = quality_counts.resolution > 0;
+  stats_.bw_limited_framerate = quality_counts.fps > 0;
+}
+
+void SendStatisticsProxy::SetAdaptTimer(const ViEEncoder::AdaptCounts& counts,
+                                        StatsTimer* timer) {
+  if (counts.resolution >= 0 || counts.fps >= 0) {
+    // Adaptation enabled.
+    if (!stats_.suspended)
+      timer->Start(clock_->TimeInMilliseconds());
+    return;
+  }
+  timer->Stop(clock_->TimeInMilliseconds());
 }
 
 void SendStatisticsProxy::RtcpPacketTypesCounterUpdated(
@@ -833,8 +836,8 @@
   if (uma_container_->first_rtp_stats_time_ms_ == -1) {
     int64_t now_ms = clock_->TimeInMilliseconds();
     uma_container_->first_rtp_stats_time_ms_ = now_ms;
-    uma_container_->cpu_scaling_timer_.Restart(now_ms);
-    uma_container_->quality_scaling_timer_.Restart(now_ms);
+    uma_container_->cpu_adapt_timer_.Restart(now_ms);
+    uma_container_->quality_adapt_timer_.Restart(now_ms);
   }
 
   uma_container_->total_byte_counter_.Set(counters.transmitted.TotalBytes(),
diff --git a/webrtc/video/send_statistics_proxy.h b/webrtc/video/send_statistics_proxy.h
index 10d72c7..763a7a8 100644
--- a/webrtc/video/send_statistics_proxy.h
+++ b/webrtc/video/send_statistics_proxy.h
@@ -57,10 +57,14 @@
   // Used to update incoming frame rate.
   void OnIncomingFrame(int width, int height);
 
-  void OnCpuRestrictedResolutionChanged(bool cpu_restricted_resolution);
-  void OnQualityRestrictedResolutionChanged(int num_quality_downscales);
-  void SetCpuScalingStats(int num_cpu_downscales);  // -1: disabled.
-  void SetQualityScalingStats(int num_quality_downscales);  // -1: disabled.
+  // Adaptation stats.
+  void SetAdaptationStats(const ViEEncoder::AdaptCounts& cpu_counts,
+                          const ViEEncoder::AdaptCounts& quality_counts);
+  void OnCpuAdaptationChanged(const ViEEncoder::AdaptCounts& cpu_counts,
+                              const ViEEncoder::AdaptCounts& quality_counts);
+  void OnQualityAdaptationChanged(
+      const ViEEncoder::AdaptCounts& cpu_counts,
+      const ViEEncoder::AdaptCounts& quality_counts);
 
   void OnEncoderStatsUpdate(uint32_t framerate, uint32_t bitrate);
   void OnSuspendChange(bool is_suspended);
@@ -160,6 +164,12 @@
   VideoSendStream::StreamStats* GetStatsEntry(uint32_t ssrc)
       EXCLUSIVE_LOCKS_REQUIRED(crit_);
 
+  void SetAdaptTimer(const ViEEncoder::AdaptCounts& counts, StatsTimer* timer)
+      EXCLUSIVE_LOCKS_REQUIRED(crit_);
+  void UpdateAdaptationStats(const ViEEncoder::AdaptCounts& cpu_counts,
+                             const ViEEncoder::AdaptCounts& quality_counts)
+      EXCLUSIVE_LOCKS_REQUIRED(crit_);
+
   Clock* const clock_;
   const std::string payload_name_;
   const VideoSendStream::Config::Rtp rtp_config_;
@@ -215,8 +225,8 @@
     RateAccCounter fec_byte_counter_;
     int64_t first_rtcp_stats_time_ms_;
     int64_t first_rtp_stats_time_ms_;
-    StatsTimer cpu_scaling_timer_;
-    StatsTimer quality_scaling_timer_;
+    StatsTimer cpu_adapt_timer_;
+    StatsTimer quality_adapt_timer_;
     BoolSampleCounter paused_time_counter_;
     TargetRateUpdates target_rate_updates_;
     ReportBlockStats report_block_stats_;
diff --git a/webrtc/video/send_statistics_proxy_unittest.cc b/webrtc/video/send_statistics_proxy_unittest.cc
index edb7564..4f09e48 100644
--- a/webrtc/video/send_statistics_proxy_unittest.cc
+++ b/webrtc/video/send_statistics_proxy_unittest.cc
@@ -367,61 +367,99 @@
   EXPECT_EQ(rtc::Optional<uint64_t>(), statistics_proxy_->GetStats().qp_sum);
 }
 
-TEST_F(SendStatisticsProxyTest, SetCpuScalingUpdatesStats) {
+TEST_F(SendStatisticsProxyTest, GetCpuAdaptationStats) {
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_framerate);
   EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution);
-  statistics_proxy_->SetCpuScalingStats(-1);
+  cpu_counts.fps = 1;
+  cpu_counts.resolution = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_TRUE(statistics_proxy_->GetStats().cpu_limited_framerate);
   EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution);
-  statistics_proxy_->SetCpuScalingStats(0);
-  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution);
-  statistics_proxy_->SetCpuScalingStats(1);
+  cpu_counts.fps = 0;
+  cpu_counts.resolution = 1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_framerate);
   EXPECT_TRUE(statistics_proxy_->GetStats().cpu_limited_resolution);
-}
-
-TEST_F(SendStatisticsProxyTest, SetQualityScalingUpdatesStats) {
-  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
-  statistics_proxy_->SetQualityScalingStats(-1);
-  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
-  statistics_proxy_->SetQualityScalingStats(0);
-  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
-  statistics_proxy_->SetQualityScalingStats(1);
-  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
-}
-
-TEST_F(SendStatisticsProxyTest, GetStatsReportsCpuResolutionChanges) {
+  cpu_counts.fps = 1;
+  cpu_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_TRUE(statistics_proxy_->GetStats().cpu_limited_framerate);
   EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution);
+  cpu_counts.fps = -1;
+  cpu_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_framerate);
+  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution);
+}
+
+TEST_F(SendStatisticsProxyTest, GetQualityAdaptationStats) {
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+  quality_counts.fps = 1;
+  quality_counts.resolution = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_framerate);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+  quality_counts.fps = 0;
+  quality_counts.resolution = 1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
+  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
+  quality_counts.fps = 1;
+  quality_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_framerate);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+  quality_counts.fps = -1;
+  quality_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+}
+
+TEST_F(SendStatisticsProxyTest, GetStatsReportsCpuAdaptChanges) {
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
   EXPECT_EQ(0, statistics_proxy_->GetStats().number_of_cpu_adapt_changes);
 
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  cpu_counts.resolution = 1;
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
+  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_framerate);
   EXPECT_TRUE(statistics_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(1, statistics_proxy_->GetStats().number_of_cpu_adapt_changes);
 
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(false);
-  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_resolution);
+  cpu_counts.resolution = 2;
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
+  EXPECT_FALSE(statistics_proxy_->GetStats().cpu_limited_framerate);
+  EXPECT_TRUE(statistics_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(2, statistics_proxy_->GetStats().number_of_cpu_adapt_changes);
+  EXPECT_EQ(0, statistics_proxy_->GetStats().number_of_quality_adapt_changes);
 }
 
-TEST_F(SendStatisticsProxyTest, GetStatsReportsQualityResolutionChanges) {
-  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+TEST_F(SendStatisticsProxyTest, GetStatsReportsQualityAdaptChanges) {
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
   EXPECT_EQ(0, statistics_proxy_->GetStats().number_of_quality_adapt_changes);
 
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(1);
-  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
+  quality_counts.fps = 1;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_framerate);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
   EXPECT_EQ(1, statistics_proxy_->GetStats().number_of_quality_adapt_changes);
 
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(2);
-  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
-  EXPECT_EQ(2, statistics_proxy_->GetStats().number_of_quality_adapt_changes);
-
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(1);
-  EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
-  EXPECT_EQ(3, statistics_proxy_->GetStats().number_of_quality_adapt_changes);
-
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(0);
+  quality_counts.fps = 0;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
   EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
-  EXPECT_EQ(4, statistics_proxy_->GetStats().number_of_quality_adapt_changes);
+  EXPECT_EQ(2, statistics_proxy_->GetStats().number_of_quality_adapt_changes);
+  EXPECT_EQ(0, statistics_proxy_->GetStats().number_of_cpu_adapt_changes);
 }
 
-TEST_F(SendStatisticsProxyTest, AdaptChangesNotReported_ScalingNotEnabled) {
+TEST_F(SendStatisticsProxyTest, AdaptChangesNotReported_AdaptationNotEnabled) {
   // First RTP packet sent.
   UpdateDataCounters(kFirstSsrc);
   // Min runtime has passed.
@@ -435,9 +473,10 @@
 TEST_F(SendStatisticsProxyTest, AdaptChangesNotReported_MinRuntimeNotPassed) {
   // First RTP packet sent.
   UpdateDataCounters(kFirstSsrc);
-  // Enable scaling.
-  statistics_proxy_->SetQualityScalingStats(0);
-  statistics_proxy_->SetCpuScalingStats(0);
+  // Enable adaptation.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   // Min runtime has not passed.
   fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000 - 1);
   statistics_proxy_.reset();
@@ -446,26 +485,18 @@
             metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality"));
 }
 
-TEST_F(SendStatisticsProxyTest, ZeroCpuAdaptChangesReported) {
+TEST_F(SendStatisticsProxyTest, ZeroAdaptChangesReported) {
   // First RTP packet sent.
   UpdateDataCounters(kFirstSsrc);
-  // Enable scaling.
-  statistics_proxy_->SetCpuScalingStats(0);
+  // Enable adaptation.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   // Min runtime has passed.
   fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000);
   statistics_proxy_.reset();
   EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu"));
   EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Cpu", 0));
-}
-
-TEST_F(SendStatisticsProxyTest, ZeroQualityAdaptChangesReported) {
-  // First RTP packet sent.
-  UpdateDataCounters(kFirstSsrc);
-  // Enable scaling.
-  statistics_proxy_->SetQualityScalingStats(0);
-  // Min runtime has passed.
-  fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000);
-  statistics_proxy_.reset();
   EXPECT_EQ(1,
             metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality"));
   EXPECT_EQ(
@@ -475,10 +506,12 @@
 TEST_F(SendStatisticsProxyTest, CpuAdaptChangesReported) {
   // First RTP packet sent.
   UpdateDataCounters(kFirstSsrc);
-  // Enable scaling.
+  // Enable adaptation.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   // Adapt changes: 1, elapsed time: 10 sec => 6 per minute.
-  statistics_proxy_->SetCpuScalingStats(0);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
   statistics_proxy_.reset();
   EXPECT_EQ(1, metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Cpu"));
@@ -489,34 +522,42 @@
   // First RTP packet sent.
   UpdateDataCounters(kFirstSsrc);
 
-  // Disable scaling.
-  statistics_proxy_->SetQualityScalingStats(-1);
+  // Disable quality adaptation.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  quality_counts.fps = -1;
+  quality_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
 
-  // Enable scaling.
+  // Enable quality adaptation.
   // Adapt changes: 2, elapsed time: 20 sec.
-  statistics_proxy_->SetQualityScalingStats(0);
+  quality_counts.fps = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(5000);
-  statistics_proxy_->SetQualityScalingStats(1);
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(9000);
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(1);
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(6000);
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(2);
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
 
-  // Disable scaling.
-  statistics_proxy_->SetQualityScalingStats(-1);
+  // Disable quality adaptation.
+  quality_counts.fps = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(30000);
 
-  // Enable scaling.
+  // Enable quality adaptation.
   // Adapt changes: 1, elapsed time: 10 sec.
-  statistics_proxy_->SetQualityScalingStats(0);
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(1);
+  quality_counts.resolution = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
 
-  // Disable scaling.
-  statistics_proxy_->SetQualityScalingStats(-1);
+  // Disable quality adaptation.
+  quality_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(5000);
-  statistics_proxy_->SetQualityScalingStats(-1);
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(20000);
 
   // Adapt changes: 3, elapsed time: 30 sec => 6 per minute.
@@ -549,12 +590,14 @@
   // First RTP packet sent.
   UpdateDataCounters(kFirstSsrc);
 
-  // Enable scaling.
+  // Enable adaptation.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
   // Adapt changes: 2, elapsed time: 20 sec.
-  statistics_proxy_->SetQualityScalingStats(0);
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(20000);
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(1);
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(2);
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
 
   // Suspend and resume video.
   statistics_proxy_->OnSuspendChange(true);
@@ -562,7 +605,7 @@
   statistics_proxy_->OnSuspendChange(false);
 
   // Adapt changes: 1, elapsed time: 10 sec.
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(3);
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
 
   // Adapt changes: 3, elapsed time: 30 sec => 6 per minute.
@@ -581,18 +624,22 @@
   statistics_proxy_->OnSuspendChange(false);
   fake_clock_.AdvanceTimeMilliseconds(30000);
 
-  // Enable scaling.
+  // Enable adaptation.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
   // Adapt changes: 1, elapsed time: 20 sec.
-  statistics_proxy_->SetCpuScalingStats(0);
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
 
   // Video not suspended, stats time already started.
   statistics_proxy_->OnSuspendChange(false);
   fake_clock_.AdvanceTimeMilliseconds(10000);
 
-  // Disable scaling.
-  statistics_proxy_->SetCpuScalingStats(-1);
+  // Disable adaptation.
+  cpu_counts.fps = -1;
+  cpu_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(30000);
 
   // Suspend and resume video, stats time not started when scaling not enabled.
@@ -601,11 +648,13 @@
   statistics_proxy_->OnSuspendChange(false);
   fake_clock_.AdvanceTimeMilliseconds(30000);
 
-  // Enable scaling.
+  // Enable adaptation.
   // Adapt changes: 1, elapsed time: 10 sec.
-  statistics_proxy_->SetCpuScalingStats(0);
+  cpu_counts.fps = 0;
+  cpu_counts.resolution = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
 
   // Adapt changes: 2, elapsed time: 30 sec => 4 per minute.
   statistics_proxy_.reset();
@@ -620,15 +669,17 @@
   // Video suspended.
   statistics_proxy_->OnSuspendChange(true);
 
-  // Enable scaling, stats time not started when suspended.
-  statistics_proxy_->SetCpuScalingStats(0);
+  // Enable adaptation, stats time not started when suspended.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
 
   // Resume video, stats time started.
   // Adapt changes: 1, elapsed time: 10 sec.
   statistics_proxy_->OnSuspendChange(false);
   fake_clock_.AdvanceTimeMilliseconds(10000);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
 
   // Adapt changes: 1, elapsed time: 10 sec => 6 per minute.
   statistics_proxy_.reset();
@@ -637,15 +688,17 @@
 }
 
 TEST_F(SendStatisticsProxyTest, AdaptChangesStatsRestartsOnFirstSentPacket) {
-  // Send first packet, scaling enabled.
+  // Send first packet, adaptation enabled.
   // Elapsed time before first packet is sent should be excluded.
-  statistics_proxy_->SetQualityScalingStats(0);
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
   UpdateDataCounters(kFirstSsrc);
 
   // Adapt changes: 1, elapsed time: 10 sec.
   fake_clock_.AdvanceTimeMilliseconds(10000);
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(1);
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
   UpdateDataCounters(kFirstSsrc);
 
   // Adapt changes: 1, elapsed time: 10 sec => 6 per minute.
@@ -657,24 +710,29 @@
 }
 
 TEST_F(SendStatisticsProxyTest, AdaptChangesStatsStartedAfterFirstSentPacket) {
-  // Enable and disable scaling.
-  statistics_proxy_->SetCpuScalingStats(0);
+  // Enable and disable adaptation.
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(60000);
-  statistics_proxy_->SetCpuScalingStats(-1);
+  cpu_counts.fps = -1;
+  cpu_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
 
   // Send first packet, scaling disabled.
   // Elapsed time before first packet is sent should be excluded.
   UpdateDataCounters(kFirstSsrc);
   fake_clock_.AdvanceTimeMilliseconds(60000);
 
-  // Enable scaling.
-  statistics_proxy_->SetCpuScalingStats(0);
+  // Enable adaptation.
+  cpu_counts.resolution = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(10000);
   UpdateDataCounters(kFirstSsrc);
 
   // Adapt changes: 1, elapsed time: 20 sec.
   fake_clock_.AdvanceTimeMilliseconds(10000);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
 
   // Adapt changes: 1, elapsed time: 20 sec => 3 per minute.
   statistics_proxy_.reset();
@@ -683,14 +741,18 @@
 }
 
 TEST_F(SendStatisticsProxyTest, AdaptChangesReportedAfterContentSwitch) {
-  // First RTP packet sent, scaling enabled.
+  // First RTP packet sent, cpu adaptation enabled.
   UpdateDataCounters(kFirstSsrc);
-  statistics_proxy_->SetCpuScalingStats(0);
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  quality_counts.fps = -1;
+  quality_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
 
   // Adapt changes: 2, elapsed time: 15 sec => 8 per minute.
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(6000);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(9000);
 
   // Switch content type, real-time stats should be updated.
@@ -704,13 +766,13 @@
 
   // First RTP packet sent, scaling enabled.
   UpdateDataCounters(kFirstSsrc);
-  statistics_proxy_->SetCpuScalingStats(0);
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
 
   // Adapt changes: 4, elapsed time: 120 sec => 2 per minute.
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
   fake_clock_.AdvanceTimeMilliseconds(120000);
 
   statistics_proxy_.reset();
@@ -842,8 +904,10 @@
 }
 
 TEST_F(SendStatisticsProxyTest, CpuLimitedHistogramNotUpdatedWhenDisabled) {
-  const int kNumDownscales = -1;
-  statistics_proxy_->SetQualityScalingStats(kNumDownscales);
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  cpu_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
 
   for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
     statistics_proxy_->OnIncomingFrame(kWidth, kHeight);
@@ -854,13 +918,16 @@
 }
 
 TEST_F(SendStatisticsProxyTest, CpuLimitedHistogramUpdated) {
-  const int kNumDownscales = 0;
-  statistics_proxy_->SetCpuScalingStats(kNumDownscales);
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  cpu_counts.resolution = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
 
   for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
     statistics_proxy_->OnIncomingFrame(kWidth, kHeight);
 
-  statistics_proxy_->OnCpuRestrictedResolutionChanged(true);
+  cpu_counts.resolution = 1;
+  statistics_proxy_->OnCpuAdaptationChanged(cpu_counts, quality_counts);
 
   for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
     statistics_proxy_->OnIncomingFrame(kWidth, kHeight);
@@ -1149,9 +1216,11 @@
 
 TEST_F(SendStatisticsProxyTest,
        QualityLimitedHistogramsNotUpdatedWhenDisabled) {
-  const int kNumDownscales = -1;
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  quality_counts.resolution = -1;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   EncodedImage encoded_image;
-  statistics_proxy_->SetQualityScalingStats(kNumDownscales);
   for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
     statistics_proxy_->OnSendEncodedImage(encoded_image, &kDefaultCodecInfo);
 
@@ -1165,9 +1234,11 @@
 
 TEST_F(SendStatisticsProxyTest,
        QualityLimitedHistogramsUpdatedWhenEnabled_NoResolutionDownscale) {
-  const int kNumDownscales = 0;
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  quality_counts.resolution = 0;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   EncodedImage encoded_image;
-  statistics_proxy_->SetQualityScalingStats(kNumDownscales);
   for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
     statistics_proxy_->OnSendEncodedImage(encoded_image, &kDefaultCodecInfo);
 
@@ -1185,8 +1256,11 @@
 TEST_F(SendStatisticsProxyTest,
        QualityLimitedHistogramsUpdatedWhenEnabled_TwoResolutionDownscales) {
   const int kDownscales = 2;
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  quality_counts.resolution = kDownscales;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
   EncodedImage encoded_image;
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(kDownscales);
   for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
     statistics_proxy_->OnSendEncodedImage(encoded_image, &kDefaultCodecInfo);
   // Histograms are updated when the statistics_proxy_ is deleted.
@@ -1221,7 +1295,10 @@
   EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
 
   // Resolution scaled due to quality.
-  statistics_proxy_->OnQualityRestrictedResolutionChanged(1);
+  ViEEncoder::AdaptCounts cpu_counts;
+  ViEEncoder::AdaptCounts quality_counts;
+  quality_counts.resolution = 1;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
   statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
   EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
 }
diff --git a/webrtc/video/vie_encoder.cc b/webrtc/video/vie_encoder.cc
index cd0f3eb..a3b071a 100644
--- a/webrtc/video/vie_encoder.cc
+++ b/webrtc/video/vie_encoder.cc
@@ -73,6 +73,22 @@
   return std::numeric_limits<uint32_t>::max();
 }
 
+bool IsResolutionScalingEnabled(
+    VideoSendStream::DegradationPreference degradation_preference) {
+  return degradation_preference ==
+             VideoSendStream::DegradationPreference::kMaintainFramerate ||
+         degradation_preference ==
+             VideoSendStream::DegradationPreference::kBalanced;
+}
+
+bool IsFramerateScalingEnabled(
+    VideoSendStream::DegradationPreference degradation_preference) {
+  return degradation_preference ==
+             VideoSendStream::DegradationPreference::kMaintainResolution ||
+         degradation_preference ==
+             VideoSendStream::DegradationPreference::kBalanced;
+}
+
 }  //  namespace
 
 class ViEEncoder::ConfigureEncoderTask : public rtc::QueuedTask {
@@ -420,8 +436,7 @@
 
 void ViEEncoder::SetSource(
     rtc::VideoSourceInterface<VideoFrame>* source,
-    const VideoSendStream::VideoSendStream::DegradationPreference&
-        degradation_preference) {
+    const VideoSendStream::DegradationPreference& degradation_preference) {
   RTC_DCHECK_RUN_ON(&thread_checker_);
   source_proxy_->SetSource(source, degradation_preference);
   encoder_queue_.PostTask([this, degradation_preference] {
@@ -547,27 +562,24 @@
   const bool quality_scaling_allowed =
       degradation_preference_allows_scaling && scaling_settings.enabled;
 
-  const std::vector<int>& scale_counters = GetScaleCounters();
-  stats_proxy_->SetCpuScalingStats(
-      degradation_preference_allows_scaling ? scale_counters[kCpu] : -1);
-  stats_proxy_->SetQualityScalingStats(
-      quality_scaling_allowed ? scale_counters[kQuality] : -1);
-
   if (quality_scaling_allowed) {
-    // Abort if quality scaler has already been configured.
-    if (quality_scaler_.get() != nullptr)
-      return;
-    // Drop frames and scale down until desired quality is achieved.
-    if (scaling_settings.thresholds) {
-      quality_scaler_.reset(
-          new QualityScaler(this, *(scaling_settings.thresholds)));
-    } else {
-      quality_scaler_.reset(new QualityScaler(this, codec_type_));
+    if (quality_scaler_.get() == nullptr) {
+      // Quality scaler has not already been configured.
+      // Drop frames and scale down until desired quality is achieved.
+      if (scaling_settings.thresholds) {
+        quality_scaler_.reset(
+            new QualityScaler(this, *(scaling_settings.thresholds)));
+      } else {
+        quality_scaler_.reset(new QualityScaler(this, codec_type_));
+      }
     }
   } else {
     quality_scaler_.reset(nullptr);
     initial_rampup_ = kMaxInitialFramedrop;
   }
+
+  stats_proxy_->SetAdaptationStats(GetActiveCounts(kCpu),
+                                   GetActiveCounts(kQuality));
 }
 
 void ViEEncoder::OnFrame(const VideoFrame& video_frame) {
@@ -797,6 +809,7 @@
       last_frame_info_->pixel_count(),
       stats_proxy_->GetStats().input_frame_rate,
       AdaptationRequest::Mode::kAdaptDown};
+
   bool downgrade_requested =
       last_adaptation_request_ &&
       last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptDown;
@@ -834,8 +847,7 @@
   }
 
   if (reason == kCpu) {
-    const int cpu_scale_counter = GetScaleCounters()[reason];
-    if (cpu_scale_counter >= max_downgrades)
+    if (GetConstAdaptCounter().TotalCount(kCpu) >= max_downgrades)
       return;
   }
 
@@ -848,11 +860,13 @@
         return;
       }
       LOG(LS_INFO) << "Scaling down resolution.";
+      GetAdaptCounter().IncrementResolution(reason, 1);
       break;
     case VideoSendStream::DegradationPreference::kMaintainResolution:
       source_proxy_->RequestFramerateLowerThan(
           adaptation_request.framerate_fps_);
       LOG(LS_INFO) << "Scaling down framerate.";
+      GetAdaptCounter().IncrementFramerate(reason, 1);
       break;
     case VideoSendStream::DegradationPreference::kDegradationDisabled:
       RTC_NOTREACHED();
@@ -860,32 +874,20 @@
 
   last_adaptation_request_.emplace(adaptation_request);
 
-  IncrementScaleCounter(reason, 1);
+  UpdateAdaptationStats(reason);
 
-  // Update stats.
-  const std::vector<int>& scale_counters = GetScaleCounters();
-  switch (reason) {
-    case kQuality:
-      stats_proxy_->OnQualityRestrictedResolutionChanged(
-          scale_counters[reason]);
-      break;
-    case kCpu:
-      stats_proxy_->OnCpuRestrictedResolutionChanged(true);
-      break;
-  }
-
-  for (size_t i = 0; i < kScaleReasonSize; ++i) {
-    LOG(LS_INFO) << "Scaled " << scale_counters[i]
-                 << " times for reason: " << (i ? "cpu" : "quality");
-  }
+  LOG(LS_INFO) << GetConstAdaptCounter().ToString();
 }
 
 void ViEEncoder::AdaptUp(AdaptReason reason) {
   RTC_DCHECK_RUN_ON(&encoder_queue_);
-  int scale_counter = GetScaleCounters()[reason];
-  if (scale_counter == 0)
+
+  const AdaptCounter& adapt_counter = GetConstAdaptCounter();
+  int num_downgrades = adapt_counter.TotalCount(reason);
+  if (num_downgrades == 0)
     return;
-  RTC_DCHECK_GT(scale_counter, 0);
+  RTC_DCHECK_GT(num_downgrades, 0);
+
   AdaptationRequest adaptation_request = {
       last_frame_info_->pixel_count(),
       stats_proxy_->GetStats().input_frame_rate,
@@ -894,6 +896,7 @@
   bool adapt_up_requested =
       last_adaptation_request_ &&
       last_adaptation_request_->mode_ == AdaptationRequest::Mode::kAdaptUp;
+
   switch (degradation_preference_) {
     case VideoSendStream::DegradationPreference::kBalanced:
       FALLTHROUGH();
@@ -914,20 +917,11 @@
       return;
   }
 
-  // Decrease counter of how many times we have scaled down, for this
-  // degradation preference mode and reason.
-  IncrementScaleCounter(reason, -1);
-
-  // Get a sum of how many times have scaled down, in total, for this
-  // degradation preference mode. If it is 0, remove any restraints.
-  const std::vector<int>& scale_counters = GetScaleCounters();
-  const int scale_sum =
-      std::accumulate(scale_counters.begin(), scale_counters.end(), 0);
   switch (degradation_preference_) {
     case VideoSendStream::DegradationPreference::kBalanced:
       FALLTHROUGH();
     case VideoSendStream::DegradationPreference::kMaintainFramerate:
-      if (scale_sum == 0) {
+      if (adapt_counter.TotalCount() == 1) {
         LOG(LS_INFO) << "Removing resolution down-scaling setting.";
         source_proxy_->RequestHigherResolutionThan(
             std::numeric_limits<int>::max());
@@ -936,9 +930,10 @@
             adaptation_request.input_pixel_count_);
         LOG(LS_INFO) << "Scaling up resolution.";
       }
+      GetAdaptCounter().IncrementResolution(reason, -1);
       break;
     case VideoSendStream::DegradationPreference::kMaintainResolution:
-      if (scale_sum == 0) {
+      if (adapt_counter.TotalCount() == 1) {
         LOG(LS_INFO) << "Removing framerate down-scaling setting.";
         source_proxy_->RequestHigherFramerateThan(
             std::numeric_limits<int>::max());
@@ -947,6 +942,7 @@
             adaptation_request.framerate_fps_);
         LOG(LS_INFO) << "Scaling up framerate.";
       }
+      GetAdaptCounter().IncrementFramerate(reason, -1);
       break;
     case VideoSendStream::DegradationPreference::kDegradationDisabled:
       RTC_NOTREACHED();
@@ -954,40 +950,120 @@
 
   last_adaptation_request_.emplace(adaptation_request);
 
-  // Update stats.
+  UpdateAdaptationStats(reason);
+
+  LOG(LS_INFO) << adapt_counter.ToString();
+}
+
+void ViEEncoder::UpdateAdaptationStats(AdaptReason reason) {
   switch (reason) {
-    case kQuality:
-      stats_proxy_->OnQualityRestrictedResolutionChanged(
-          scale_counters[reason]);
-      break;
     case kCpu:
-      stats_proxy_->OnCpuRestrictedResolutionChanged(scale_counters[reason] >
-                                                     0);
+      stats_proxy_->OnCpuAdaptationChanged(GetActiveCounts(kCpu),
+                                           GetActiveCounts(kQuality));
+      break;
+    case kQuality:
+      stats_proxy_->OnQualityAdaptationChanged(GetActiveCounts(kCpu),
+                                               GetActiveCounts(kQuality));
       break;
   }
-
-  for (size_t i = 0; i < kScaleReasonSize; ++i) {
-    LOG(LS_INFO) << "Scaled " << scale_counters[i]
-                 << " times for reason: " << (i ? "cpu" : "quality");
-  }
 }
 
-const std::vector<int>& ViEEncoder::GetScaleCounters() {
-  auto it = scale_counters_.find(degradation_preference_);
-  if (it == scale_counters_.end()) {
-    scale_counters_[degradation_preference_].resize(kScaleReasonSize);
-    return scale_counters_[degradation_preference_];
+ViEEncoder::AdaptCounts ViEEncoder::GetActiveCounts(AdaptReason reason) {
+  ViEEncoder::AdaptCounts counts = GetConstAdaptCounter().Counts(reason);
+  switch (reason) {
+    case kCpu:
+      if (!IsFramerateScalingEnabled(degradation_preference_))
+        counts.fps = -1;
+      if (!IsResolutionScalingEnabled(degradation_preference_))
+        counts.resolution = -1;
+      break;
+    case kQuality:
+      if (!IsFramerateScalingEnabled(degradation_preference_) ||
+          !quality_scaler_) {
+        counts.fps = -1;
+      }
+      if (!IsResolutionScalingEnabled(degradation_preference_) ||
+          !quality_scaler_) {
+        counts.resolution = -1;
+      }
+      break;
   }
-  return it->second;
+  return counts;
 }
 
-void ViEEncoder::IncrementScaleCounter(int reason, int delta) {
-  // Get the counters and validate. This may also lazily initialize the state.
-  const std::vector<int>& counter = GetScaleCounters();
-  if (delta < 0) {
-    RTC_DCHECK_GE(counter[reason], delta);
+ViEEncoder::AdaptCounter& ViEEncoder::GetAdaptCounter() {
+  return adapt_counters_[degradation_preference_];
+}
+
+const ViEEncoder::AdaptCounter& ViEEncoder::GetConstAdaptCounter() {
+  return adapt_counters_[degradation_preference_];
+}
+
+// Class holding adaptation information.
+ViEEncoder::AdaptCounter::AdaptCounter() {
+  fps_counters_.resize(kScaleReasonSize);
+  resolution_counters_.resize(kScaleReasonSize);
+}
+
+ViEEncoder::AdaptCounter::~AdaptCounter() {}
+
+std::string ViEEncoder::AdaptCounter::ToString() const {
+  std::stringstream ss;
+  ss << "Downgrade counts: fps: {" << ToString(fps_counters_);
+  ss << "}, resolution: {" << ToString(resolution_counters_) << "}";
+  return ss.str();
+}
+
+ViEEncoder::AdaptCounts ViEEncoder::AdaptCounter::Counts(int reason) const {
+  AdaptCounts counts;
+  counts.fps = fps_counters_[reason];
+  counts.resolution = resolution_counters_[reason];
+  return counts;
+}
+
+void ViEEncoder::AdaptCounter::IncrementFramerate(int reason, int delta) {
+  fps_counters_[reason] += delta;
+}
+
+void ViEEncoder::AdaptCounter::IncrementResolution(int reason, int delta) {
+  resolution_counters_[reason] += delta;
+}
+
+int ViEEncoder::AdaptCounter::FramerateCount() const {
+  return Count(fps_counters_);
+}
+
+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];
+}
+
+int ViEEncoder::AdaptCounter::ResolutionCount(int reason) const {
+  return resolution_counters_[reason];
+}
+
+int ViEEncoder::AdaptCounter::TotalCount(int reason) const {
+  return FramerateCount(reason) + ResolutionCount(reason);
+}
+
+int ViEEncoder::AdaptCounter::Count(const std::vector<int>& counters) const {
+  return std::accumulate(counters.begin(), counters.end(), 0);
+}
+
+std::string ViEEncoder::AdaptCounter::ToString(
+    const std::vector<int>& counters) const {
+  std::stringstream ss;
+  for (size_t reason = 0; reason < kScaleReasonSize; ++reason) {
+    ss << (reason ? " cpu" : "quality") << ":" << counters[reason];
   }
-  scale_counters_[degradation_preference_][reason] += delta;
+  return ss.str();
 }
 
 }  // namespace webrtc
diff --git a/webrtc/video/vie_encoder.h b/webrtc/video/vie_encoder.h
index 8481117..f2d7cfd 100644
--- a/webrtc/video/vie_encoder.h
+++ b/webrtc/video/vie_encoder.h
@@ -62,6 +62,12 @@
         int min_transmit_bitrate_bps) = 0;
   };
 
+  // Number of resolution and framerate reductions (-1: disabled).
+  struct AdaptCounts {
+    int resolution = 0;
+    int fps = 0;
+  };
+
   // Downscale resolution at most 2 times for CPU reasons.
   static const int kMaxCpuResolutionDowngrades = 2;
   // Downscale framerate at most 4 times.
@@ -172,10 +178,44 @@
   void TraceFrameDropStart();
   void TraceFrameDropEnd();
 
-  const std::vector<int>& GetScaleCounters()
-      EXCLUSIVE_LOCKS_REQUIRED(&encoder_queue_);
-  void IncrementScaleCounter(int reason, int delta)
-      EXCLUSIVE_LOCKS_REQUIRED(&encoder_queue_);
+  // Class holding adaptation information.
+  class AdaptCounter final {
+   public:
+    AdaptCounter();
+    ~AdaptCounter();
+
+    // Get number of adaptation downscales for |reason|.
+    AdaptCounts Counts(int reason) const;
+
+    std::string ToString() const;
+
+    void IncrementFramerate(int reason, int delta);
+    void IncrementResolution(int reason, int delta);
+
+    // 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;
+    int ResolutionCount(int reason) const;
+    int TotalCount(int reason) const;
+
+   private:
+    std::string ToString(const std::vector<int>& counters) const;
+    int Count(const std::vector<int>& counters) const;
+
+    // Degradation counters holding number of framerate/resolution reductions
+    // per adapt reason.
+    std::vector<int> fps_counters_;
+    std::vector<int> resolution_counters_;
+  };
+
+  AdaptCounter& GetAdaptCounter() RUN_ON(&encoder_queue_);
+  const AdaptCounter& GetConstAdaptCounter() RUN_ON(&encoder_queue_);
+  void UpdateAdaptationStats(AdaptReason reason) RUN_ON(&encoder_queue_);
+  AdaptCounts GetActiveCounts(AdaptReason reason) RUN_ON(&encoder_queue_);
 
   rtc::Event shutdown_event_;
 
@@ -214,13 +254,14 @@
   uint32_t last_observed_bitrate_bps_ ACCESS_ON(&encoder_queue_);
   bool encoder_paused_and_dropped_frame_ ACCESS_ON(&encoder_queue_);
   Clock* const clock_;
-  // Counters used for deciding if the video resolution is currently
-  // restricted, and if so, why, on a per degradation preference basis.
+  // Counters used for deciding if the video resolution or framerate is
+  // currently restricted, and if so, why, on a per degradation preference
+  // basis.
   // TODO(sprang): Replace this with a state holding a relative overuse measure
   // instead, that can be translated into suitable down-scale or fps limit.
-  std::map<const VideoSendStream::DegradationPreference, std::vector<int>>
-      scale_counters_ ACCESS_ON(&encoder_queue_);
-  // Set depending on degradation preferences
+  std::map<const VideoSendStream::DegradationPreference, AdaptCounter>
+      adapt_counters_ ACCESS_ON(&encoder_queue_);
+  // Set depending on degradation preferences.
   VideoSendStream::DegradationPreference degradation_preference_
       ACCESS_ON(&encoder_queue_);
 
diff --git a/webrtc/video/vie_encoder_unittest.cc b/webrtc/video/vie_encoder_unittest.cc
index d2f46e6..eb39bf7 100644
--- a/webrtc/video/vie_encoder_unittest.cc
+++ b/webrtc/video/vie_encoder_unittest.cc
@@ -139,6 +139,11 @@
     return adaptation_enabled_;
   }
 
+  rtc::VideoSinkWants last_wants() const {
+    rtc::CritScope cs(&crit_);
+    return last_wants_;
+  }
+
   void IncomingCapturedFrame(const VideoFrame& video_frame) override {
     int cropped_width = 0;
     int cropped_height = 0;
@@ -163,14 +168,15 @@
   void AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink,
                        const rtc::VideoSinkWants& wants) override {
     rtc::CritScope cs(&crit_);
+    last_wants_ = sink_wants();
     adapter_.OnResolutionFramerateRequest(wants.target_pixel_count,
                                           wants.max_pixel_count,
                                           wants.max_framerate_fps);
     test::FrameForwarder::AddOrUpdateSink(sink, wants);
   }
-
   cricket::VideoAdapter adapter_;
   bool adaptation_enabled_ GUARDED_BY(crit_);
+  rtc::VideoSinkWants last_wants_ GUARDED_BY(crit_);
 };
 
 class MockableSendStatisticsProxy : public SendStatisticsProxy {
@@ -281,16 +287,41 @@
   }
 
   void VerifyNoLimitation(const rtc::VideoSinkWants& wants) {
-    EXPECT_FALSE(wants.target_pixel_count);
-    EXPECT_EQ(std::numeric_limits<int>::max(), wants.max_pixel_count);
     EXPECT_EQ(std::numeric_limits<int>::max(), wants.max_framerate_fps);
+    EXPECT_EQ(std::numeric_limits<int>::max(), wants.max_pixel_count);
+    EXPECT_FALSE(wants.target_pixel_count);
   }
 
-  void VerifyResolutionLimitationLessThan(const rtc::VideoSinkWants& wants,
-                                          int pixel_count) {
+  void VerifyFpsEqResolutionEq(const rtc::VideoSinkWants& wants1,
+                               const rtc::VideoSinkWants& wants2) {
+    EXPECT_EQ(wants1.max_framerate_fps, wants2.max_framerate_fps);
+    EXPECT_EQ(wants1.max_pixel_count, wants2.max_pixel_count);
+  }
+
+  void VerifyFpsMaxResolutionLt(const rtc::VideoSinkWants& wants1,
+                                const rtc::VideoSinkWants& wants2) {
+    EXPECT_EQ(std::numeric_limits<int>::max(), wants1.max_framerate_fps);
+    EXPECT_LT(wants1.max_pixel_count, wants2.max_pixel_count);
+    EXPECT_GT(wants1.max_pixel_count, 0);
+  }
+
+  void VerifyFpsMaxResolutionGt(const rtc::VideoSinkWants& wants1,
+                                const rtc::VideoSinkWants& wants2) {
+    EXPECT_EQ(std::numeric_limits<int>::max(), wants1.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);
     EXPECT_LT(wants.max_pixel_count, pixel_count);
     EXPECT_GT(wants.max_pixel_count, 0);
-    EXPECT_EQ(std::numeric_limits<int>::max(), wants.max_framerate_fps);
+  }
+
+  void VerifyFpsLtResolutionMax(const rtc::VideoSinkWants& wants, int fps) {
+    EXPECT_LT(wants.max_framerate_fps, fps);
+    EXPECT_EQ(std::numeric_limits<int>::max(), wants.max_pixel_count);
+    EXPECT_FALSE(wants.target_pixel_count);
   }
 
   class TestEncoder : public test::FakeEncoder {
@@ -946,6 +977,7 @@
   video_source_.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight));
   sink_.WaitForEncodedFrame(1);
   VideoSendStream::Stats stats = stats_proxy_->GetStats();
+  EXPECT_FALSE(stats.bw_limited_resolution);
   EXPECT_FALSE(stats.cpu_limited_resolution);
   EXPECT_EQ(0, stats.number_of_cpu_adapt_changes);
 
@@ -954,6 +986,7 @@
   video_source_.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight));
   sink_.WaitForEncodedFrame(2);
   stats = stats_proxy_->GetStats();
+  EXPECT_FALSE(stats.bw_limited_resolution);
   EXPECT_TRUE(stats.cpu_limited_resolution);
   EXPECT_EQ(1, stats.number_of_cpu_adapt_changes);
 
@@ -966,6 +999,7 @@
   new_video_source.IncomingCapturedFrame(CreateFrame(3, kWidth, kHeight));
   sink_.WaitForEncodedFrame(3);
   stats = stats_proxy_->GetStats();
+  EXPECT_FALSE(stats.bw_limited_resolution);
   EXPECT_TRUE(stats.cpu_limited_resolution);
   EXPECT_EQ(1, stats.number_of_cpu_adapt_changes);
 
@@ -977,6 +1011,7 @@
   new_video_source.IncomingCapturedFrame(CreateFrame(4, kWidth, kHeight));
   sink_.WaitForEncodedFrame(4);
   stats = stats_proxy_->GetStats();
+  EXPECT_FALSE(stats.bw_limited_resolution);
   EXPECT_FALSE(stats.cpu_limited_resolution);
   EXPECT_EQ(1, stats.number_of_cpu_adapt_changes);
 
@@ -988,6 +1023,7 @@
   new_video_source.IncomingCapturedFrame(CreateFrame(5, kWidth, kHeight));
   sink_.WaitForEncodedFrame(5);
   stats = stats_proxy_->GetStats();
+  EXPECT_FALSE(stats.bw_limited_resolution);
   EXPECT_TRUE(stats.cpu_limited_resolution);
   EXPECT_EQ(1, stats.number_of_cpu_adapt_changes);
 
@@ -996,6 +1032,7 @@
   new_video_source.IncomingCapturedFrame(CreateFrame(6, kWidth, kHeight));
   sink_.WaitForEncodedFrame(6);
   stats = stats_proxy_->GetStats();
+  EXPECT_FALSE(stats.bw_limited_resolution);
   EXPECT_FALSE(stats.cpu_limited_resolution);
   EXPECT_EQ(2, stats.number_of_cpu_adapt_changes);
   EXPECT_EQ(0, stats.number_of_quality_adapt_changes);
@@ -1138,6 +1175,7 @@
   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 cpu adaptation by frame dropping.
@@ -1150,10 +1188,11 @@
   stats = stats_proxy_->GetStats();
   // Not adapted at first.
   EXPECT_FALSE(stats.cpu_limited_resolution);
+  EXPECT_FALSE(stats.cpu_limited_framerate);
   EXPECT_EQ(1, stats.number_of_cpu_adapt_changes);
 
   // Force an input frame rate to be available, or the adaptation call won't
-  // know what framerate to adapt form.
+  // know what framerate to adapt from.
   VideoSendStream::Stats mock_stats = stats_proxy_->GetStats();
   mock_stats.input_frame_rate = 30;
   stats_proxy_->SetMockStats(mock_stats);
@@ -1166,7 +1205,8 @@
 
   // Framerate now adapted.
   stats = stats_proxy_->GetStats();
-  EXPECT_TRUE(stats.cpu_limited_resolution);
+  EXPECT_FALSE(stats.cpu_limited_resolution);
+  EXPECT_TRUE(stats.cpu_limited_framerate);
   EXPECT_EQ(2, stats.number_of_cpu_adapt_changes);
 
   // Disable CPU adaptation.
@@ -1314,7 +1354,7 @@
 
   // Trigger adapt down, expect scaled down resolution.
   vie_encoder_->TriggerCpuOveruse();
-  VerifyResolutionLimitationLessThan(source.sink_wants(), kWidth * kHeight);
+  VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight);
   const int kLastMaxPixelCount = source.sink_wants().max_pixel_count;
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1366,18 +1406,45 @@
   source.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight));
   sink_.WaitForEncodedFrame(kWidth, kHeight);
   VerifyNoLimitation(source.sink_wants());
-  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);
 
   // Trigger adapt up, expect no change.
   vie_encoder_->TriggerCpuNormalUsage();
   VerifyNoLimitation(source.sink_wants());
-  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);
 
   vie_encoder_->Stop();
 }
 
+TEST_F(ViEEncoderTest, NoChangeForInitialNormalUsage_DisabledMode) {
+  const int kWidth = 1280;
+  const int kHeight = 720;
+  vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0);
+
+  // Enable kDegradationDisabled preference, no initial limitation.
+  test::FrameForwarder source;
+  vie_encoder_->SetSource(
+      &source, VideoSendStream::DegradationPreference::kDegradationDisabled);
+
+  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, AdaptsResolutionForLowQuality_MaintainFramerateMode) {
   const int kWidth = 1280;
   const int kHeight = 720;
@@ -1399,7 +1466,7 @@
   vie_encoder_->TriggerQualityLow();
   source.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight));
   sink_.WaitForEncodedFrame(2);
-  VerifyResolutionLimitationLessThan(source.sink_wants(), kWidth * kHeight);
+  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);
 
@@ -1413,6 +1480,47 @@
   vie_encoder_->Stop();
 }
 
+TEST_F(ViEEncoderTest, AdaptsFramerateForLowQuality_MaintainResolutionMode) {
+  const int kWidth = 1280;
+  const int kHeight = 720;
+  const int kInputFps = 30;
+  vie_encoder_->OnBitrateUpdated(kTargetBitrateBps, 0, 0);
+
+  VideoSendStream::Stats stats = stats_proxy_->GetStats();
+  stats.input_frame_rate = kInputFps;
+  stats_proxy_->SetMockStats(stats);
+
+  // Expect no scaling to begin with (preference: kMaintainFramerate).
+  video_source_.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight));
+  sink_.WaitForEncodedFrame(1);
+  VerifyNoLimitation(video_source_.sink_wants());
+
+  // Trigger adapt down, expect scaled down resolution.
+  vie_encoder_->TriggerQualityLow();
+  video_source_.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight));
+  sink_.WaitForEncodedFrame(2);
+  VerifyFpsMaxResolutionLt(video_source_.sink_wants(), kWidth * kHeight);
+
+  // Enable kMaintainResolution preference.
+  test::FrameForwarder new_video_source;
+  vie_encoder_->SetSource(
+      &new_video_source,
+      VideoSendStream::DegradationPreference::kMaintainResolution);
+  VerifyNoLimitation(new_video_source.sink_wants());
+
+  // Trigger adapt down, expect reduced framerate.
+  vie_encoder_->TriggerQualityLow();
+  new_video_source.IncomingCapturedFrame(CreateFrame(3, kWidth, kHeight));
+  sink_.WaitForEncodedFrame(3);
+  VerifyFpsLtResolutionMax(new_video_source.sink_wants(), kInputFps);
+
+  // Trigger adapt up, expect no restriction.
+  vie_encoder_->TriggerQualityHigh();
+  VerifyNoLimitation(new_video_source.sink_wants());
+
+  vie_encoder_->Stop();
+}
+
 TEST_F(ViEEncoderTest, DoesNotScaleBelowSetResolutionLimit) {
   const int kWidth = 1280;
   const int kHeight = 720;
@@ -1461,7 +1569,7 @@
       &source, VideoSendStream::DegradationPreference::kMaintainFramerate);
 
   source.IncomingCapturedFrame(CreateFrame(1, kWidth, kHeight));
-  sink_.WaitForEncodedFrame(1);
+  sink_.WaitForEncodedFrame(kWidth, kHeight);
   VerifyNoLimitation(source.sink_wants());
   EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(0, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1470,14 +1578,14 @@
   vie_encoder_->TriggerCpuOveruse();
   source.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight));
   sink_.WaitForEncodedFrame(2);
-  VerifyResolutionLimitationLessThan(source.sink_wants(), kWidth * kHeight);
+  VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight);
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
 
   // Trigger adapt up, expect no restriction.
   vie_encoder_->TriggerCpuNormalUsage();
   source.IncomingCapturedFrame(CreateFrame(3, kWidth, kHeight));
-  sink_.WaitForEncodedFrame(3);
+  sink_.WaitForEncodedFrame(kWidth, kHeight);
   VerifyNoLimitation(source.sink_wants());
   EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1486,12 +1594,14 @@
   vie_encoder_->TriggerCpuOveruse();
   source.IncomingCapturedFrame(CreateFrame(4, kWidth, kHeight));
   sink_.WaitForEncodedFrame(4);
-  VerifyResolutionLimitationLessThan(source.sink_wants(), kWidth * kHeight);
+  VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight);
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(3, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
 
   // Trigger adapt up, expect no restriction.
   vie_encoder_->TriggerCpuNormalUsage();
+  source.IncomingCapturedFrame(CreateFrame(5, kWidth, kHeight));
+  sink_.WaitForEncodedFrame(kWidth, kHeight);
   VerifyNoLimitation(source.sink_wants());
   EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_EQ(4, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1523,8 +1633,7 @@
   vie_encoder_->TriggerCpuOveruse();
   source.IncomingCapturedFrame(CreateFrame(2, kWidth, kHeight));
   sink_.WaitForEncodedFrame(2);
-  VerifyResolutionLimitationLessThan(source.sink_wants(), kWidth * kHeight);
-  rtc::VideoSinkWants last_wants = source.sink_wants();
+  VerifyFpsMaxResolutionLt(source.sink_wants(), kWidth * kHeight);
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
   EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1534,8 +1643,8 @@
   vie_encoder_->TriggerCpuOveruse();
   source.IncomingCapturedFrame(CreateFrame(3, kWidth, kHeight));
   sink_.WaitForEncodedFrame(3);
-  EXPECT_LT(source.sink_wants().max_pixel_count, last_wants.max_pixel_count);
-  last_wants = source.sink_wants();
+  VerifyFpsMaxResolutionLt(source.sink_wants(), source.last_wants());
+  rtc::VideoSinkWants last_wants = source.sink_wants();
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
   EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1545,7 +1654,7 @@
   vie_encoder_->TriggerCpuOveruse();
   source.IncomingCapturedFrame(CreateFrame(4, kWidth, kHeight));
   sink_.WaitForEncodedFrame(4);
-  EXPECT_EQ(last_wants.max_pixel_count, source.sink_wants().max_pixel_count);
+  VerifyFpsEqResolutionEq(source.sink_wants(), last_wants);
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
   EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1555,8 +1664,7 @@
   vie_encoder_->TriggerQualityLow();
   source.IncomingCapturedFrame(CreateFrame(5, kWidth, kHeight));
   sink_.WaitForEncodedFrame(5);
-  EXPECT_LT(source.sink_wants().max_pixel_count, last_wants.max_pixel_count);
-  last_wants = source.sink_wants();
+  VerifyFpsMaxResolutionLt(source.sink_wants(), source.last_wants());
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
   EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1566,8 +1674,7 @@
   vie_encoder_->TriggerCpuNormalUsage();
   source.IncomingCapturedFrame(CreateFrame(6, kWidth, kHeight));
   sink_.WaitForEncodedFrame(6);
-  EXPECT_GT(source.sink_wants().max_pixel_count, last_wants.max_pixel_count);
-  last_wants = source.sink_wants();
+  VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants());
   EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
   EXPECT_EQ(3, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1577,7 +1684,7 @@
   vie_encoder_->TriggerCpuNormalUsage();
   source.IncomingCapturedFrame(CreateFrame(7, kWidth, kHeight));
   sink_.WaitForEncodedFrame(7);
-  EXPECT_GT(source.sink_wants().max_pixel_count, last_wants.max_pixel_count);
+  VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants());
   last_wants = source.sink_wants();
   EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
@@ -1588,7 +1695,7 @@
   vie_encoder_->TriggerCpuNormalUsage();
   source.IncomingCapturedFrame(CreateFrame(8, kWidth, kHeight));
   sink_.WaitForEncodedFrame(8);
-  EXPECT_EQ(last_wants.max_pixel_count, source.sink_wants().max_pixel_count);
+  VerifyFpsEqResolutionEq(source.sink_wants(), last_wants);
   EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
   EXPECT_EQ(4, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
@@ -1598,7 +1705,7 @@
   vie_encoder_->TriggerQualityHigh();
   source.IncomingCapturedFrame(CreateFrame(9, kWidth, kHeight));
   sink_.WaitForEncodedFrame(kWidth, kHeight);
-  EXPECT_GT(source.sink_wants().max_pixel_count, last_wants.max_pixel_count);
+  VerifyFpsMaxResolutionGt(source.sink_wants(), source.last_wants());
   VerifyNoLimitation(source.sink_wants());
   EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution);
   EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
@@ -1730,7 +1837,7 @@
   vie_encoder_->Stop();
 }
 
-TEST_F(ViEEncoderTest, NrOfDroppedFramesLimitedWhenBitrateIsTooLow) {
+TEST_F(ViEEncoderTest, NumberOfDroppedFramesLimitedWhenBitrateIsTooLow) {
   const int kTooLowBitrateForFrameSizeBps = 10000;
   vie_encoder_->OnBitrateUpdated(kTooLowBitrateForFrameSizeBps, 0, 0);
   const int kWidth = 640;
@@ -1774,6 +1881,7 @@
   const int kHeight = 360;
   fake_encoder_.SetQualityScaling(false);
   vie_encoder_->OnBitrateUpdated(kLowTargetBitrateBps, 0, 0);
+
   // Force quality scaler reconfiguration by resetting the source.
   vie_encoder_->SetSource(&video_source_,
                           VideoSendStream::DegradationPreference::kBalanced);
diff --git a/webrtc/video_send_stream.h b/webrtc/video_send_stream.h
index f608394..45d1a55 100644
--- a/webrtc/video_send_stream.h
+++ b/webrtc/video_send_stream.h
@@ -69,6 +69,8 @@
     bool suspended = false;
     bool bw_limited_resolution = false;
     bool cpu_limited_resolution = false;
+    bool bw_limited_framerate = false;
+    bool cpu_limited_framerate = false;
     // Total number of times resolution as been requested to be changed due to
     // CPU/quality adaptation.
     int number_of_cpu_adapt_changes = 0;