Add stats googHasEnteredLowResolution.

Indicates if the forced sw fallback has had an effect (or would have had an effect if it had been
enabled).


Bug: webrtc:6634
Change-Id: I574b9001a2fae650fb894a1caa0d0f84257658e3
Reviewed-on: https://webrtc-review.googlesource.com/23300
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Rasmus Brandt <brandtr@webrtc.org>
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#20729}
diff --git a/api/statstypes.cc b/api/statstypes.cc
index 940fb73..ff70bac 100644
--- a/api/statstypes.cc
+++ b/api/statstypes.cc
@@ -547,6 +547,8 @@
       return "googFrameWidthReceived";
     case kStatsValueNameFrameWidthSent:
       return "googFrameWidthSent";
+    case kStatsValueNameHasEnteredLowResolution:
+      return "googHasEnteredLowResolution";
     case kStatsValueNameInitiator:
       return "googInitiator";
     case kStatsValueNameInterframeDelayMaxMs:
diff --git a/api/statstypes.h b/api/statstypes.h
index 9e7f08c..80f0eb5 100644
--- a/api/statstypes.h
+++ b/api/statstypes.h
@@ -185,6 +185,7 @@
     kStatsValueNameFrameWidthInput,
     kStatsValueNameFrameWidthReceived,
     kStatsValueNameFrameWidthSent,
+    kStatsValueNameHasEnteredLowResolution,
     kStatsValueNameInitiator,
     kStatsValueNameInterframeDelayMaxMs,  // Max over last 10 seconds.
     kStatsValueNameIssuerId,
diff --git a/call/video_send_stream.h b/call/video_send_stream.h
index 3bdb8ce..e57307d 100644
--- a/call/video_send_stream.h
+++ b/call/video_send_stream.h
@@ -85,6 +85,7 @@
     // CPU/quality adaptation.
     int number_of_cpu_adapt_changes = 0;
     int number_of_quality_adapt_changes = 0;
+    bool has_entered_low_resolution = false;
     std::map<uint32_t, StreamStats> substreams;
     webrtc::VideoContentType content_type =
         webrtc::VideoContentType::UNSPECIFIED;
diff --git a/media/base/mediachannel.h b/media/base/mediachannel.h
index b101dc7..0b84ff7 100644
--- a/media/base/mediachannel.h
+++ b/media/base/mediachannel.h
@@ -723,6 +723,7 @@
         avg_encode_ms(0),
         encode_usage_percent(0),
         frames_encoded(0),
+        has_entered_low_resolution(false),
         content_type(webrtc::VideoContentType::UNSPECIFIED) {}
 
   std::vector<SsrcGroup> ssrc_groups;
@@ -743,6 +744,7 @@
   int avg_encode_ms;
   int encode_usage_percent;
   uint32_t frames_encoded;
+  bool has_entered_low_resolution;
   rtc::Optional<uint64_t> qp_sum;
   webrtc::VideoContentType content_type;
 };
diff --git a/media/engine/webrtcvideoengine.cc b/media/engine/webrtcvideoengine.cc
index d2457f8..7d5e2f5 100644
--- a/media/engine/webrtcvideoengine.cc
+++ b/media/engine/webrtcvideoengine.cc
@@ -1975,6 +1975,7 @@
   info.adapt_changes = stats.number_of_cpu_adapt_changes;
   info.adapt_reason =
       stats.cpu_limited_resolution ? ADAPTREASON_CPU : ADAPTREASON_NONE;
+  info.has_entered_low_resolution = stats.has_entered_low_resolution;
 
   // Get bandwidth limitation info from stream_->GetStats().
   // Input resolution (output from video_adapter) can be further scaled down or
diff --git a/pc/statscollector.cc b/pc/statscollector.cc
index c81b368..9520176 100644
--- a/pc/statscollector.cc
+++ b/pc/statscollector.cc
@@ -317,6 +317,9 @@
                      (info.adapt_reason & 0x2) > 0);
   report->AddBoolean(StatsReport::kStatsValueNameCpuLimitedResolution,
                      (info.adapt_reason & 0x1) > 0);
+  report->AddBoolean(StatsReport::kStatsValueNameHasEnteredLowResolution,
+                     info.has_entered_low_resolution);
+
   if (info.qp_sum)
     report->AddInt(StatsReport::kStatsValueNameQpSum, *info.qp_sum);
 
diff --git a/video/send_statistics_proxy.cc b/video/send_statistics_proxy.cc
index cc7d034..71c9348 100644
--- a/video/send_statistics_proxy.cc
+++ b/video/send_statistics_proxy.cc
@@ -85,29 +85,37 @@
           codec_info->codecSpecific.VP8.temporalIdx == kNoTemporalIdx);
 }
 
-rtc::Optional<int> GetFallbackMaxPixelsFromFieldTrial() {
-  if (!webrtc::field_trial::IsEnabled(kVp8ForcedFallbackEncoderFieldTrial))
-    return rtc::Optional<int>();
-
-  std::string group =
-      webrtc::field_trial::FindFullName(kVp8ForcedFallbackEncoderFieldTrial);
+rtc::Optional<int> GetFallbackMaxPixels(const std::string& group) {
   if (group.empty())
     return rtc::Optional<int>();
 
   int min_pixels;
   int max_pixels;
   int min_bps;
-  if (sscanf(group.c_str(), "Enabled-%d,%d,%d", &min_pixels, &max_pixels,
-             &min_bps) != 3) {
+  if (sscanf(group.c_str(), "-%d,%d,%d", &min_pixels, &max_pixels, &min_bps) !=
+      3) {
     return rtc::Optional<int>();
   }
 
-  if (min_pixels <= 0 || max_pixels <= 0 || max_pixels < min_pixels ||
-      min_bps <= 0) {
-    return rtc::Optional<int>();  // Do not log stats.
-  }
+  if (min_pixels <= 0 || max_pixels <= 0 || max_pixels < min_pixels)
+    return rtc::Optional<int>();
+
   return rtc::Optional<int>(max_pixels);
 }
+
+rtc::Optional<int> GetFallbackMaxPixelsIfFieldTrialEnabled() {
+  std::string group =
+      webrtc::field_trial::FindFullName(kVp8ForcedFallbackEncoderFieldTrial);
+  return (group.find("Enabled") == 0) ? GetFallbackMaxPixels(group.substr(7))
+                                      : rtc::Optional<int>();
+}
+
+rtc::Optional<int> GetFallbackMaxPixelsIfFieldTrialDisabled() {
+  std::string group =
+      webrtc::field_trial::FindFullName(kVp8ForcedFallbackEncoderFieldTrial);
+  return (group.find("Disabled") == 0) ? GetFallbackMaxPixels(group.substr(8))
+                                       : rtc::Optional<int>();
+}
 }  // namespace
 
 
@@ -120,7 +128,8 @@
     : clock_(clock),
       payload_name_(config.encoder_settings.payload_name),
       rtp_config_(config.rtp),
-      fallback_max_pixels_(GetFallbackMaxPixelsFromFieldTrial()),
+      fallback_max_pixels_(GetFallbackMaxPixelsIfFieldTrialEnabled()),
+      fallback_max_pixels_disabled_(GetFallbackMaxPixelsIfFieldTrialDisabled()),
       content_type_(content_type),
       start_ms_(clock->TimeInMilliseconds()),
       encode_time_(kEncodeTimeWeigthFactor),
@@ -729,6 +738,8 @@
 void SendStatisticsProxy::UpdateEncoderFallbackStats(
     const CodecSpecificInfo* codec_info,
     int pixels) {
+  UpdateFallbackDisabledStats(codec_info, pixels);
+
   if (!fallback_max_pixels_ || !uma_container_->fallback_info_.is_possible) {
     return;
   }
@@ -755,6 +766,7 @@
       fallback_info->is_possible = false;
       return;
     }
+    stats_.has_entered_low_resolution = true;
     ++fallback_info->on_off_events;
   }
 
@@ -772,6 +784,32 @@
   fallback_info->last_update_ms.emplace(now_ms);
 }
 
+void SendStatisticsProxy::UpdateFallbackDisabledStats(
+    const CodecSpecificInfo* codec_info,
+    int pixels) {
+  if (!fallback_max_pixels_disabled_ ||
+      !uma_container_->fallback_info_disabled_.is_possible ||
+      stats_.has_entered_low_resolution) {
+    return;
+  }
+
+  if (!IsForcedFallbackPossible(codec_info) ||
+      strcmp(codec_info->codec_name, kVp8SwCodecName) == 0) {
+    uma_container_->fallback_info_disabled_.is_possible = false;
+    return;
+  }
+
+  if (pixels <= *fallback_max_pixels_disabled_ ||
+      uma_container_->fallback_info_disabled_.min_pixel_limit_reached) {
+    stats_.has_entered_low_resolution = true;
+  }
+}
+
+void SendStatisticsProxy::OnMinPixelLimitReached() {
+  rtc::CritScope lock(&crit_);
+  uma_container_->fallback_info_disabled_.min_pixel_limit_reached = true;
+}
+
 void SendStatisticsProxy::OnSendEncodedImage(
     const EncodedImage& encoded_image,
     const CodecSpecificInfo* codec_info) {
diff --git a/video/send_statistics_proxy.h b/video/send_statistics_proxy.h
index a543d92..094e78f 100644
--- a/video/send_statistics_proxy.h
+++ b/video/send_statistics_proxy.h
@@ -73,6 +73,7 @@
   void OnQualityAdaptationChanged(
       const VideoStreamEncoder::AdaptCounts& cpu_counts,
       const VideoStreamEncoder::AdaptCounts& quality_counts);
+  void OnMinPixelLimitReached();
 
   void OnSuspendChange(bool is_suspended);
   void OnInactiveSsrc(uint32_t ssrc);
@@ -163,6 +164,10 @@
     rtc::Optional<int64_t> last_update_ms;
     const int max_frame_diff_ms = 2000;
   };
+  struct FallbackEncoderInfoDisabled {
+    bool is_possible = true;
+    bool min_pixel_limit_reached = false;
+  };
   struct StatsTimer {
     void Start(int64_t now_ms);
     void Stop(int64_t now_ms);
@@ -208,11 +213,15 @@
   void UpdateEncoderFallbackStats(const CodecSpecificInfo* codec_info,
                                   int pixels)
       RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+  void UpdateFallbackDisabledStats(const CodecSpecificInfo* codec_info,
+                                   int pixels)
+      RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
 
   Clock* const clock_;
   const std::string payload_name_;
   const VideoSendStream::Config::Rtp rtp_config_;
   const rtc::Optional<int> fallback_max_pixels_;
+  const rtc::Optional<int> fallback_max_pixels_disabled_;
   rtc::CriticalSection crit_;
   VideoEncoderConfig::ContentType content_type_ RTC_GUARDED_BY(crit_);
   const int64_t start_ms_;
@@ -273,6 +282,7 @@
     TargetRateUpdates target_rate_updates_;
     BoolSampleCounter fallback_active_counter_;
     FallbackEncoderInfo fallback_info_;
+    FallbackEncoderInfoDisabled fallback_info_disabled_;
     ReportBlockStats report_block_stats_;
     const VideoSendStream::Stats start_stats_;
     EncodedFrameMap encoded_frames_;
diff --git a/video/send_statistics_proxy_unittest.cc b/video/send_statistics_proxy_unittest.cc
index 881aa7c..a204a46 100644
--- a/video/send_statistics_proxy_unittest.cc
+++ b/video/send_statistics_proxy_unittest.cc
@@ -1867,7 +1867,8 @@
 class ForcedFallbackDisabled : public ForcedFallbackTest {
  public:
   ForcedFallbackDisabled()
-      : ForcedFallbackTest("WebRTC-VP8-Forced-Fallback-Encoder-v2/Disabled/") {}
+      : ForcedFallbackTest("WebRTC-VP8-Forced-Fallback-Encoder-v2/Disabled-1," +
+                           std::to_string(kWidth * kHeight) + ",3/") {}
 };
 
 class ForcedFallbackEnabled : public ForcedFallbackTest {
@@ -1886,6 +1887,7 @@
 
 TEST_F(ForcedFallbackEnabled, StatsUpdated) {
   InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
   statistics_proxy_.reset();
   EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
   EXPECT_EQ(1, metrics::NumEvents(kPrefix + "FallbackTimeInPercent.Vp8", 0));
@@ -1924,11 +1926,50 @@
   EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
 }
 
+TEST_F(ForcedFallbackDisabled, EnteredLowResolutionSetIfAtMaxPixels) {
+  InsertEncodedFrames(1, kFrameIntervalMs);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
+}
+
+TEST_F(ForcedFallbackEnabled, EnteredLowResolutionNotSetIfNotLibvpx) {
+  InsertEncodedFrames(1, kFrameIntervalMs);
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
+}
+
+TEST_F(ForcedFallbackEnabled, EnteredLowResolutionSetIfLibvpx) {
+  codec_info_.codec_name = "libvpx";
+  InsertEncodedFrames(1, kFrameIntervalMs);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
+}
+
+TEST_F(ForcedFallbackDisabled, EnteredLowResolutionNotSetIfAboveMaxPixels) {
+  encoded_image_._encodedWidth = kWidth + 1;
+  InsertEncodedFrames(1, kFrameIntervalMs);
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
+}
+
+TEST_F(ForcedFallbackDisabled, EnteredLowResolutionNotSetIfLibvpx) {
+  codec_info_.codec_name = "libvpx";
+  InsertEncodedFrames(1, kFrameIntervalMs);
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
+}
+
+TEST_F(ForcedFallbackDisabled,
+       EnteredLowResolutionSetIfOnMinPixelLimitReached) {
+  encoded_image_._encodedWidth = kWidth + 1;
+  statistics_proxy_->OnMinPixelLimitReached();
+  InsertEncodedFrames(1, kFrameIntervalMs);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
+}
+
 TEST_F(ForcedFallbackEnabled, OneFallbackEvent) {
   // One change. Video: 20000 ms, fallback: 5000 ms (25%).
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
   InsertEncodedFrames(15, 1000);
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
   codec_info_.codec_name = "libvpx";
   InsertEncodedFrames(5, 1000);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
 
   statistics_proxy_.reset();
   EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
@@ -1943,16 +1984,21 @@
 
   // Three changes. Video: 60000 ms, fallback: 15000 ms (25%).
   InsertEncodedFrames(10, 1000);
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
   codec_info_.codec_name = "libvpx";
   InsertEncodedFrames(15, 500);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
   codec_info_.codec_name = "notlibvpx";
   InsertEncodedFrames(20, 1000);
   InsertEncodedFrames(3, kMaxFrameDiffMs);  // Should not be included.
   InsertEncodedFrames(10, 1000);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
   codec_info_.codec_name = "notlibvpx2";
   InsertEncodedFrames(10, 500);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
   codec_info_.codec_name = "libvpx";
   InsertEncodedFrames(15, 500);
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
 
   statistics_proxy_.reset();
   EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
@@ -1966,6 +2012,7 @@
   codec_info_.codec_name = "libvpx";
   InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
 
+  EXPECT_FALSE(statistics_proxy_->GetStats().has_entered_low_resolution);
   statistics_proxy_.reset();
   EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
   EXPECT_EQ(0, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
@@ -1976,6 +2023,7 @@
   codec_info_.codec_name = "libvpx";
   InsertEncodedFrames(kMinFrames, kFrameIntervalMs);
 
+  EXPECT_TRUE(statistics_proxy_->GetStats().has_entered_low_resolution);
   statistics_proxy_.reset();
   EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackTimeInPercent.Vp8"));
   EXPECT_EQ(1, metrics::NumSamples(kPrefix + "FallbackChangesPerMinute.Vp8"));
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index 6c59670..77945dc 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -228,7 +228,9 @@
       source_->AddOrUpdateSink(video_stream_encoder_, sink_wants_);
   }
 
-  bool RequestResolutionLowerThan(int pixel_count, int min_pixels_per_frame) {
+  bool RequestResolutionLowerThan(int pixel_count,
+                                  int min_pixels_per_frame,
+                                  bool* min_pixels_reached) {
     // Called on the encoder task queue.
     rtc::CritScope lock(&crit_);
     if (!source_ || !IsResolutionScalingEnabled(degradation_preference_)) {
@@ -239,8 +241,11 @@
     // The input video frame size will have a resolution less than or equal to
     // |max_pixel_count| depending on how the source can scale the frame size.
     const int pixels_wanted = (pixel_count * 3) / 5;
-    if (pixels_wanted < min_pixels_per_frame ||
-        pixels_wanted >= sink_wants_.max_pixel_count) {
+    if (pixels_wanted >= sink_wants_.max_pixel_count) {
+      return false;
+    }
+    if (pixels_wanted < min_pixels_per_frame) {
+      *min_pixels_reached = true;
       return false;
     }
     RTC_LOG(LS_INFO) << "Scaling down resolution, max pixels: "
@@ -977,15 +982,20 @@
       // Scale down resolution.
       FALLTHROUGH();
     }
-    case VideoSendStream::DegradationPreference::kMaintainFramerate:
+    case VideoSendStream::DegradationPreference::kMaintainFramerate: {
       // Scale down resolution.
+      bool min_pixels_reached = false;
       if (!source_proxy_->RequestResolutionLowerThan(
               adaptation_request.input_pixel_count_,
-              settings_.encoder->GetScalingSettings().min_pixels_per_frame)) {
+              settings_.encoder->GetScalingSettings().min_pixels_per_frame,
+              &min_pixels_reached)) {
+        if (min_pixels_reached)
+          stats_proxy_->OnMinPixelLimitReached();
         return;
       }
       GetAdaptCounter().IncrementResolution(reason);
       break;
+    }
     case VideoSendStream::DegradationPreference::kMaintainResolution: {
       // Scale down framerate.
       const int requested_framerate = source_proxy_->RequestFramerateLowerThan(