Exclude initial adapt downs in stats for quality adapt changes per minute.

Make WebRTC.Video.AdaptChangesPerMinute.Quality stats only based on changes during a call.

Discard initial quality adapt changes due to bitrate (MaximumFrameSizeForBitrate).
Makes stats only based on changes determined by the quality scaler.

Bug: none
Change-Id: I461b65e65634565ade87b1336cf5206aa14926ff
Reviewed-on: https://webrtc-review.googlesource.com/37660
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21585}
diff --git a/video/send_statistics_proxy.cc b/video/send_statistics_proxy.cc
index 0d2f8f6..4375e84 100644
--- a/video/send_statistics_proxy.cc
+++ b/video/send_statistics_proxy.cc
@@ -439,6 +439,11 @@
     if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
       int quality_changes = current_stats.number_of_quality_adapt_changes -
                             start_stats_.number_of_quality_adapt_changes;
+      // Only base stats on changes during a call, discard initial changes.
+      int initial_changes =
+          initial_quality_changes_.down + initial_quality_changes_.up;
+      if (initial_changes <= quality_changes)
+        quality_changes -= initial_changes;
       RTC_HISTOGRAMS_COUNTS_100(kIndex,
                                 uma_prefix_ + "AdaptChangesPerMinute.Quality",
                                 quality_changes * 60 / elapsed_sec);
@@ -987,6 +992,7 @@
     const VideoStreamEncoder::AdaptCounts& cpu_counts,
     const VideoStreamEncoder::AdaptCounts& quality_counts) {
   rtc::CritScope lock(&crit_);
+  TryUpdateInitialQualityResolutionAdaptUp(quality_counts);
   ++stats_.number_of_quality_adapt_changes;
   UpdateAdaptationStats(cpu_counts, quality_counts);
 }
@@ -1003,6 +1009,27 @@
   stats_.bw_limited_framerate = quality_counts.fps > 0;
 }
 
+// TODO(asapersson): Include fps changes.
+void SendStatisticsProxy::OnInitialQualityResolutionAdaptDown() {
+  rtc::CritScope lock(&crit_);
+  ++uma_container_->initial_quality_changes_.down;
+}
+
+void SendStatisticsProxy::TryUpdateInitialQualityResolutionAdaptUp(
+    const VideoStreamEncoder::AdaptCounts& quality_counts) {
+  if (uma_container_->initial_quality_changes_.down == 0)
+    return;
+
+  if (quality_downscales_ > 0 &&
+      quality_counts.resolution < quality_downscales_) {
+    // Adapting up in quality.
+    if (uma_container_->initial_quality_changes_.down >
+        uma_container_->initial_quality_changes_.up) {
+      ++uma_container_->initial_quality_changes_.up;
+    }
+  }
+}
+
 void SendStatisticsProxy::SetAdaptTimer(
     const VideoStreamEncoder::AdaptCounts& counts,
     StatsTimer* timer) {
diff --git a/video/send_statistics_proxy.h b/video/send_statistics_proxy.h
index c90f88a..1aa836e 100644
--- a/video/send_statistics_proxy.h
+++ b/video/send_statistics_proxy.h
@@ -74,6 +74,7 @@
       const VideoStreamEncoder::AdaptCounts& cpu_counts,
       const VideoStreamEncoder::AdaptCounts& quality_counts);
   void OnMinPixelLimitReached();
+  void OnInitialQualityResolutionAdaptDown();
 
   void OnSuspendChange(bool is_suspended);
   void OnInactiveSsrc(uint32_t ssrc);
@@ -181,6 +182,10 @@
     SampleCounter vp9;   // QP range: 0-255.
     SampleCounter h264;  // QP range: 0-51.
   };
+  struct AdaptChanges {
+    int down = 0;
+    int up = 0;
+  };
 
   // Map holding encoded frames (mapped by timestamp).
   // If simulcast layers are encoded on different threads, there is no guarantee
@@ -217,6 +222,9 @@
       const VideoStreamEncoder::AdaptCounts& cpu_counts,
       const VideoStreamEncoder::AdaptCounts& quality_counts)
       RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+  void TryUpdateInitialQualityResolutionAdaptUp(
+      const VideoStreamEncoder::AdaptCounts& quality_counts)
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
 
   void UpdateEncoderFallbackStats(const CodecSpecificInfo* codec_info,
                                   int pixels)
@@ -298,6 +306,7 @@
     size_t num_streams_;  // Number of configured streams to encoder.
     size_t num_pixels_highest_stream_;
     EncodedFrameMap encoded_frames_;
+    AdaptChanges initial_quality_changes_;
 
     std::map<int, QpCounters>
         qp_counters_;  // QP counters mapped by spatial idx.
diff --git a/video/send_statistics_proxy_unittest.cc b/video/send_statistics_proxy_unittest.cc
index 1d2edc6..01cbfe8 100644
--- a/video/send_statistics_proxy_unittest.cc
+++ b/video/send_statistics_proxy_unittest.cc
@@ -513,6 +513,102 @@
   EXPECT_EQ(1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Cpu", 6));
 }
 
+TEST_F(SendStatisticsProxyTest, ExcludesInitialQualityAdaptDownChange) {
+  // First RTP packet sent.
+  UpdateDataCounters(kFirstSsrc);
+  // Enable adaptation.
+  VideoStreamEncoder::AdaptCounts cpu_counts;
+  VideoStreamEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  // Adapt changes: 1 (1 initial) = 0, elapsed time: 10 sec => 0 per minute.
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnInitialQualityResolutionAdaptDown();
+  fake_clock_.AdvanceTimeMilliseconds(10000);
+  statistics_proxy_.reset();
+  EXPECT_EQ(1,
+            metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality"));
+  EXPECT_EQ(
+      1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Quality", 0));
+}
+
+TEST_F(SendStatisticsProxyTest, ExcludesInitialQualityAdaptDownChanges) {
+  // First RTP packet sent.
+  UpdateDataCounters(kFirstSsrc);
+  // Enable adaptation.
+  VideoStreamEncoder::AdaptCounts cpu_counts;
+  VideoStreamEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  // Adapt changes: 3 (2 initial) = 1, elapsed time: 10 sec => 6 per minute.
+  quality_counts.resolution = 1;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnInitialQualityResolutionAdaptDown();
+  quality_counts.resolution = 2;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnInitialQualityResolutionAdaptDown();
+  quality_counts.resolution = 3;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  fake_clock_.AdvanceTimeMilliseconds(10000);
+  statistics_proxy_.reset();
+  EXPECT_EQ(1,
+            metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality"));
+  EXPECT_EQ(
+      1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Quality", 6));
+}
+
+TEST_F(SendStatisticsProxyTest, InitialQualityAdaptChangesNotExcludedOnError) {
+  // First RTP packet sent.
+  UpdateDataCounters(kFirstSsrc);
+  // Enable adaptation.
+  VideoStreamEncoder::AdaptCounts cpu_counts;
+  VideoStreamEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  // Adapt changes: 1 (2 initial) = 1, elapsed time: 10 sec => 6 per minute.
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnInitialQualityResolutionAdaptDown();
+  statistics_proxy_->OnInitialQualityResolutionAdaptDown();
+  fake_clock_.AdvanceTimeMilliseconds(10000);
+  statistics_proxy_.reset();
+  EXPECT_EQ(1,
+            metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality"));
+  EXPECT_EQ(
+      1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Quality", 6));
+}
+
+TEST_F(SendStatisticsProxyTest, ExcludesInitialQualityAdaptDownAndUpChanges) {
+  // First RTP packet sent.
+  UpdateDataCounters(kFirstSsrc);
+  // Enable adaptation.
+  VideoStreamEncoder::AdaptCounts cpu_counts;
+  VideoStreamEncoder::AdaptCounts quality_counts;
+  statistics_proxy_->SetAdaptationStats(cpu_counts, quality_counts);
+  // Adapt changes: 8 (4 initial) = 4, elapsed time: 10 sec => 24 per minute.
+  quality_counts.resolution = 1;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnInitialQualityResolutionAdaptDown();
+  quality_counts.resolution = 2;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  statistics_proxy_->OnInitialQualityResolutionAdaptDown();
+  quality_counts.resolution = 3;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  quality_counts.fps = 1;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  quality_counts.fps = 0;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  quality_counts.resolution = 2;  // Initial resolution up.
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  quality_counts.resolution = 1;  // Initial resolution up.
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+  quality_counts.resolution = 0;
+  statistics_proxy_->OnQualityAdaptationChanged(cpu_counts, quality_counts);
+
+  fake_clock_.AdvanceTimeMilliseconds(10000);
+  statistics_proxy_.reset();
+  EXPECT_EQ(1,
+            metrics::NumSamples("WebRTC.Video.AdaptChangesPerMinute.Quality"));
+  EXPECT_EQ(
+      1, metrics::NumEvents("WebRTC.Video.AdaptChangesPerMinute.Quality", 24));
+}
+
 TEST_F(SendStatisticsProxyTest, AdaptChangesStatsExcludesDisabledTime) {
   // First RTP packet sent.
   UpdateDataCounters(kFirstSsrc);
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index 9d209dc..7ec7ab3 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -767,7 +767,11 @@
       video_frame.size() >
           MaximumFrameSizeForBitrate(encoder_start_bitrate_bps_ / 1000)) {
     RTC_LOG(LS_INFO) << "Dropping frame. Too large for target bitrate.";
+    int count = GetConstAdaptCounter().ResolutionCount(kQuality);
     AdaptDown(kQuality);
+    if (GetConstAdaptCounter().ResolutionCount(kQuality) > count) {
+      stats_proxy_->OnInitialQualityResolutionAdaptDown();
+    }
     ++initial_rampup_;
     return;
   }