Remove VideoStreamEncoderResourceManager::active_counts

This introduces a new class for encapsulating the QualityRampupExperiment

R=hbos@webrtc.org

Bug: webrtc:11553
Change-Id: If2f2347cdcbd0c79821355f90e2d7ad3171143b5
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/176363
Commit-Queue: Evan Shrubsole <eshr@google.com>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31531}
diff --git a/rtc_base/experiments/quality_rampup_experiment.cc b/rtc_base/experiments/quality_rampup_experiment.cc
index caf7e62..ee6675c 100644
--- a/rtc_base/experiments/quality_rampup_experiment.cc
+++ b/rtc_base/experiments/quality_rampup_experiment.cc
@@ -70,4 +70,8 @@
   return (now_ms - *start_ms_) >= min_duration_ms_.Value();
 }
 
+bool QualityRampupExperiment::Enabled() const {
+  return min_pixels_ || min_duration_ms_ || max_bitrate_kbps_;
+}
+
 }  // namespace webrtc
diff --git a/rtc_base/experiments/quality_rampup_experiment.h b/rtc_base/experiments/quality_rampup_experiment.h
index ff9d7d3..9d46901 100644
--- a/rtc_base/experiments/quality_rampup_experiment.h
+++ b/rtc_base/experiments/quality_rampup_experiment.h
@@ -33,6 +33,8 @@
   // (max_bitrate_factor_) above |max_bitrate_kbps_| for |min_duration_ms_|.
   bool BwHigh(int64_t now_ms, uint32_t available_bw_kbps);
 
+  bool Enabled() const;
+
  private:
   explicit QualityRampupExperiment(
       const WebRtcKeyValueConfig* const key_value_config);
diff --git a/video/adaptation/BUILD.gn b/video/adaptation/BUILD.gn
index 6618780..50c3610 100644
--- a/video/adaptation/BUILD.gn
+++ b/video/adaptation/BUILD.gn
@@ -14,6 +14,8 @@
     "encode_usage_resource.h",
     "overuse_frame_detector.cc",
     "overuse_frame_detector.h",
+    "quality_rampup_experiment_helper.cc",
+    "quality_rampup_experiment_helper.h",
     "quality_scaler_resource.cc",
     "quality_scaler_resource.h",
     "video_stream_encoder_resource.cc",
diff --git a/video/adaptation/quality_rampup_experiment_helper.cc b/video/adaptation/quality_rampup_experiment_helper.cc
new file mode 100644
index 0000000..1e04e55
--- /dev/null
+++ b/video/adaptation/quality_rampup_experiment_helper.cc
@@ -0,0 +1,80 @@
+/*
+ *  Copyright 2020 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "video/adaptation/quality_rampup_experiment_helper.h"
+
+#include <memory>
+#include <utility>
+
+#include "rtc_base/logging.h"
+
+namespace webrtc {
+
+QualityRampUpExperimentHelper::QualityRampUpExperimentHelper(
+    QualityRampUpExperimentListener* experiment_listener,
+    Clock* clock,
+    QualityRampupExperiment experiment)
+    : experiment_listener_(experiment_listener),
+      clock_(clock),
+      quality_rampup_experiment_(std::move(experiment)),
+      cpu_adapted_(false),
+      qp_resolution_adaptations_(0) {
+  RTC_DCHECK(experiment_listener_);
+  RTC_DCHECK(clock_);
+}
+
+std::unique_ptr<QualityRampUpExperimentHelper>
+QualityRampUpExperimentHelper::CreateIfEnabled(
+    QualityRampUpExperimentListener* experiment_listener,
+    Clock* clock) {
+  QualityRampupExperiment experiment = QualityRampupExperiment::ParseSettings();
+  if (experiment.Enabled()) {
+    return std::unique_ptr<QualityRampUpExperimentHelper>(
+        new QualityRampUpExperimentHelper(experiment_listener, clock,
+                                          experiment));
+  }
+  return nullptr;
+}
+
+void QualityRampUpExperimentHelper::PerformQualityRampupExperiment(
+    rtc::scoped_refptr<QualityScalerResource> quality_scaler_resource,
+    uint32_t bw_kbps,
+    uint32_t encoder_target_bitrate,
+    uint32_t max_bitrate_bps,
+    int pixels) {
+  if (!quality_scaler_resource->is_started())
+    return;
+
+  int64_t now_ms = clock_->TimeInMilliseconds();
+  quality_rampup_experiment_.SetMaxBitrate(pixels, max_bitrate_bps);
+
+  bool try_quality_rampup = false;
+  if (quality_rampup_experiment_.BwHigh(now_ms, bw_kbps)) {
+    // Verify that encoder is at max bitrate and the QP is low.
+    if (encoder_target_bitrate == max_bitrate_bps * 1000 &&
+        quality_scaler_resource->QpFastFilterLow()) {
+      try_quality_rampup = true;
+    }
+  }
+  if (try_quality_rampup && qp_resolution_adaptations_ > 0 && !cpu_adapted_) {
+    experiment_listener_->OnQualityRampUp();
+  }
+}
+
+void QualityRampUpExperimentHelper::cpu_adapted(bool cpu_adapted) {
+  cpu_adapted_ = cpu_adapted;
+}
+
+void QualityRampUpExperimentHelper::qp_resolution_adaptations(
+    int qp_resolution_adaptations) {
+  qp_resolution_adaptations_ = qp_resolution_adaptations;
+}
+
+}  // namespace webrtc
diff --git a/video/adaptation/quality_rampup_experiment_helper.h b/video/adaptation/quality_rampup_experiment_helper.h
new file mode 100644
index 0000000..8b6a7e0
--- /dev/null
+++ b/video/adaptation/quality_rampup_experiment_helper.h
@@ -0,0 +1,67 @@
+/*
+ *  Copyright 2020 The WebRTC Project Authors. All rights reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef VIDEO_ADAPTATION_QUALITY_RAMPUP_EXPERIMENT_HELPER_H_
+#define VIDEO_ADAPTATION_QUALITY_RAMPUP_EXPERIMENT_HELPER_H_
+
+#include <memory>
+
+#include "api/scoped_refptr.h"
+#include "rtc_base/experiments/quality_rampup_experiment.h"
+#include "system_wrappers/include/clock.h"
+#include "video/adaptation/quality_scaler_resource.h"
+
+namespace webrtc {
+
+class QualityRampUpExperimentListener {
+ public:
+  virtual ~QualityRampUpExperimentListener() = default;
+  virtual void OnQualityRampUp() = 0;
+};
+
+// Helper class for orchestrating the WebRTC-Video-QualityRampupSettings
+// experiment.
+class QualityRampUpExperimentHelper {
+ public:
+  // Returns a QualityRampUpExperimentHelper if the experiment is enabled,
+  // an nullptr otherwise.
+  static std::unique_ptr<QualityRampUpExperimentHelper> CreateIfEnabled(
+      QualityRampUpExperimentListener* experiment_listener,
+      Clock* clock);
+
+  QualityRampUpExperimentHelper(const QualityRampUpExperimentHelper&) = delete;
+  QualityRampUpExperimentHelper& operator=(
+      const QualityRampUpExperimentHelper&) = delete;
+
+  void cpu_adapted(bool cpu_adapted);
+  void qp_resolution_adaptations(int qp_adaptations);
+
+  void PerformQualityRampupExperiment(
+      rtc::scoped_refptr<QualityScalerResource> quality_scaler_resource,
+      uint32_t bw_kbps,
+      uint32_t encoder_target_bitrate,
+      uint32_t max_bitrate_bps,
+      int pixels);
+
+ private:
+  QualityRampUpExperimentHelper(
+      QualityRampUpExperimentListener* experiment_listener,
+      Clock* clock,
+      QualityRampupExperiment experiment);
+  QualityRampUpExperimentListener* const experiment_listener_;
+  Clock* clock_;
+  QualityRampupExperiment quality_rampup_experiment_;
+  bool cpu_adapted_;
+  int qp_resolution_adaptations_;
+};
+
+}  // namespace webrtc
+
+#endif  // VIDEO_ADAPTATION_QUALITY_RAMPUP_EXPERIMENT_HELPER_H_
diff --git a/video/adaptation/video_stream_encoder_resource_manager.cc b/video/adaptation/video_stream_encoder_resource_manager.cc
index 00ad454..2dfcc16 100644
--- a/video/adaptation/video_stream_encoder_resource_manager.cc
+++ b/video/adaptation/video_stream_encoder_resource_manager.cc
@@ -285,10 +285,9 @@
           std::make_unique<InitialFrameDropper>(quality_scaler_resource_)),
       quality_scaling_experiment_enabled_(QualityScalingExperiment::Enabled()),
       encoder_target_bitrate_bps_(absl::nullopt),
-      quality_rampup_done_(false),
-      quality_rampup_experiment_(QualityRampupExperiment::ParseSettings()),
-      encoder_settings_(absl::nullopt),
-      active_counts_() {
+      quality_rampup_experiment_(
+          QualityRampUpExperimentHelper::CreateIfEnabled(this, clock_)),
+      encoder_settings_(absl::nullopt) {
   RTC_DCHECK(encoder_stats_observer_);
   MapResourceToReason(encode_usage_resource_, VideoAdaptationReason::kCpu);
   MapResourceToReason(quality_scaler_resource_,
@@ -394,10 +393,6 @@
   RTC_DCHECK_RUN_ON(encoder_queue_);
   encoder_settings_ = std::move(encoder_settings);
   bitrate_constraint_->OnEncoderSettingsUpdated(encoder_settings_);
-
-  quality_rampup_experiment_.SetMaxBitrate(
-      LastInputFrameSizeOrDefault(),
-      encoder_settings_->video_codec().maxBitrate);
   MaybeUpdateTargetFrameRate();
 }
 
@@ -491,7 +486,16 @@
 void VideoStreamEncoderResourceManager::OnMaybeEncodeFrame() {
   RTC_DCHECK_RUN_ON(encoder_queue_);
   initial_frame_dropper_->OnMaybeEncodeFrame();
-  MaybePerformQualityRampupExperiment();
+  if (quality_rampup_experiment_) {
+    uint32_t bw_kbps = encoder_rates_.has_value()
+                           ? encoder_rates_.value().bandwidth_allocation.kbps()
+                           : 0;
+    quality_rampup_experiment_->PerformQualityRampupExperiment(
+        quality_scaler_resource_, bw_kbps,
+        encoder_target_bitrate_bps_.value_or(0),
+        encoder_settings_->video_codec().maxBitrate,
+        LastInputFrameSizeOrDefault());
+  }
 }
 
 void VideoStreamEncoderResourceManager::UpdateQualityScalerSettings(
@@ -597,7 +601,7 @@
   // TODO(bugs.webrtc.org/11553) Remove reason parameter and add reset callback.
   if (!reason && adaptation_counters.Total() == 0) {
     // Adaptation was manually reset - clear the per-reason counters too.
-    ResetActiveCounts();
+    encoder_stats_observer_->ClearAdaptationStats();
   }
 
   // The VideoStreamEncoder makes the manager outlive the encoder queue. This
@@ -615,7 +619,7 @@
         resource_limitations) {
   RTC_DCHECK_RUN_ON(resource_adaptation_queue_);
   if (!resource) {
-    ResetActiveCounts();
+    encoder_stats_observer_->ClearAdaptationStats();
     return;
   }
 
@@ -631,13 +635,25 @@
   }
 
   VideoAdaptationReason adaptation_reason = GetReasonFromResource(resource);
-  if (active_counts_[adaptation_reason] != limitations[adaptation_reason]) {
-    active_counts_[adaptation_reason] = limitations[adaptation_reason];
-    encoder_stats_observer_->OnAdaptationChanged(
-        adaptation_reason, active_counts_[VideoAdaptationReason::kCpu],
-        active_counts_[VideoAdaptationReason::kQuality]);
-  }
-  RTC_LOG(LS_INFO) << ActiveCountsToString();
+  encoder_stats_observer_->OnAdaptationChanged(
+      adaptation_reason, limitations[VideoAdaptationReason::kCpu],
+      limitations[VideoAdaptationReason::kQuality]);
+
+  encoder_queue_->PostTask(ToQueuedTask(
+      [cpu_limited = limitations.at(VideoAdaptationReason::kCpu).Total() > 0,
+       qp_resolution_adaptations =
+           limitations.at(VideoAdaptationReason::kQuality)
+               .resolution_adaptations,
+       this]() {
+        RTC_DCHECK_RUN_ON(encoder_queue_);
+        if (quality_rampup_experiment_) {
+          quality_rampup_experiment_->cpu_adapted(cpu_limited);
+          quality_rampup_experiment_->qp_resolution_adaptations(
+              qp_resolution_adaptations);
+        }
+      }));
+
+  RTC_LOG(LS_INFO) << ActiveCountsToString(limitations);
 }
 
 void VideoStreamEncoderResourceManager::MaybeUpdateTargetFrameRate() {
@@ -675,77 +691,19 @@
                                                     quality_settings);
 }
 
-void VideoStreamEncoderResourceManager::MaybePerformQualityRampupExperiment() {
-  RTC_DCHECK_RUN_ON(encoder_queue_);
-  if (!quality_scaler_resource_->is_started())
-    return;
-
-  if (quality_rampup_done_)
-    return;
-
-  int64_t now_ms = clock_->TimeInMilliseconds();
-  uint32_t bw_kbps = encoder_rates_.has_value()
-                         ? encoder_rates_.value().bandwidth_allocation.kbps()
-                         : 0;
-
-  bool try_quality_rampup = false;
-  if (quality_rampup_experiment_.BwHigh(now_ms, bw_kbps)) {
-    // Verify that encoder is at max bitrate and the QP is low.
-    if (encoder_settings_ &&
-        encoder_target_bitrate_bps_.value_or(0) ==
-            encoder_settings_->video_codec().maxBitrate * 1000 &&
-        quality_scaler_resource_->QpFastFilterLow()) {
-      try_quality_rampup = true;
-    }
-  }
-  if (try_quality_rampup) {
-    // The VideoStreamEncoder makes the manager outlive the adaptation queue.
-    // This means that if the task gets executed, |this| has not been freed yet.
-    // TODO(https://crbug.com/webrtc/11565): When the manager no longer outlives
-    // the adaptation queue, add logic to prevent use-after-free on |this|.
-    resource_adaptation_queue_->PostTask([this] {
-      RTC_DCHECK_RUN_ON(resource_adaptation_queue_);
-      if (!adaptation_processor_) {
-        // The processor nulled before this task had a chance to execute. This
-        // happens if the processor is destroyed. No action needed.
-        return;
-      }
-      // TODO(https://crbug.com/webrtc/11392): See if we can rely on the total
-      // counts or the stats, and not the active counts.
-      const VideoAdaptationCounters& qp_counts =
-          active_counts_[VideoAdaptationReason::kQuality];
-      const VideoAdaptationCounters& cpu_counts =
-          active_counts_[VideoAdaptationReason::kCpu];
-      if (!quality_rampup_done_ && qp_counts.resolution_adaptations > 0 &&
-          cpu_counts.Total() == 0) {
-        RTC_LOG(LS_INFO) << "Reset quality limitations.";
-        adaptation_processor_->ResetVideoSourceRestrictions();
-        quality_rampup_done_ = true;
-      }
-    });
-  }
-}
-
-void VideoStreamEncoderResourceManager::ResetActiveCounts() {
-  RTC_DCHECK_RUN_ON(resource_adaptation_queue_);
-  active_counts_.clear();
-  active_counts_[VideoAdaptationReason::kCpu] = VideoAdaptationCounters();
-  active_counts_[VideoAdaptationReason::kQuality] = VideoAdaptationCounters();
-  encoder_stats_observer_->ClearAdaptationStats();
-}
-
-std::string VideoStreamEncoderResourceManager::ActiveCountsToString() const {
-  RTC_DCHECK_RUN_ON(resource_adaptation_queue_);
-  RTC_DCHECK_EQ(2, active_counts_.size());
+// static
+std::string VideoStreamEncoderResourceManager::ActiveCountsToString(
+    const std::map<VideoAdaptationReason, VideoAdaptationCounters>&
+        active_counts) {
   rtc::StringBuilder ss;
 
   ss << "Downgrade counts: fps: {";
-  for (auto& reason_count : active_counts_) {
+  for (auto& reason_count : active_counts) {
     ss << ToString(reason_count.first) << ":";
     ss << reason_count.second.fps_adaptations;
   }
   ss << "}, resolution {";
-  for (auto& reason_count : active_counts_) {
+  for (auto& reason_count : active_counts) {
     ss << ToString(reason_count.first) << ":";
     ss << reason_count.second.resolution_adaptations;
   }
@@ -753,4 +711,23 @@
 
   return ss.Release();
 }
+
+void VideoStreamEncoderResourceManager::OnQualityRampUp() {
+  RTC_DCHECK_RUN_ON(encoder_queue_);
+  // The VideoStreamEncoder makes the manager outlive the adaptation queue.
+  // This means that if the task gets executed, |this| has not been freed yet.
+  // TODO(https://crbug.com/webrtc/11565): When the manager no longer outlives
+  // the adaptation queue, add logic to prevent use-after-free on |this|.
+  resource_adaptation_queue_->PostTask([this] {
+    RTC_DCHECK_RUN_ON(resource_adaptation_queue_);
+    if (!adaptation_processor_) {
+      // The processor nulled before this task had a chance to execute. This
+      // happens if the processor is destroyed. No action needed.
+      return;
+    }
+    RTC_LOG(LS_INFO) << "Reset quality limitations.";
+    adaptation_processor_->ResetVideoSourceRestrictions();
+  });
+  quality_rampup_experiment_.reset();
+}
 }  // namespace webrtc
diff --git a/video/adaptation/video_stream_encoder_resource_manager.h b/video/adaptation/video_stream_encoder_resource_manager.h
index 666e0bb..61ae29b 100644
--- a/video/adaptation/video_stream_encoder_resource_manager.h
+++ b/video/adaptation/video_stream_encoder_resource_manager.h
@@ -36,7 +36,6 @@
 #include "call/adaptation/video_stream_adapter.h"
 #include "call/adaptation/video_stream_input_state_provider.h"
 #include "rtc_base/critical_section.h"
-#include "rtc_base/experiments/quality_rampup_experiment.h"
 #include "rtc_base/experiments/quality_scaler_settings.h"
 #include "rtc_base/ref_count.h"
 #include "rtc_base/strings/string_builder.h"
@@ -44,6 +43,7 @@
 #include "system_wrappers/include/clock.h"
 #include "video/adaptation/encode_usage_resource.h"
 #include "video/adaptation/overuse_frame_detector.h"
+#include "video/adaptation/quality_rampup_experiment_helper.h"
 #include "video/adaptation/quality_scaler_resource.h"
 #include "video/adaptation/video_stream_encoder_resource.h"
 
@@ -64,7 +64,8 @@
 // The manager is also involved with various mitigations not part of the
 // ResourceAdaptationProcessor code such as the inital frame dropping.
 class VideoStreamEncoderResourceManager
-    : public VideoSourceRestrictionsListener {
+    : public VideoSourceRestrictionsListener,
+      public QualityRampUpExperimentListener {
  public:
   VideoStreamEncoderResourceManager(
       VideoStreamInputStateProvider* input_state_provider,
@@ -112,10 +113,7 @@
   void OnFrameDropped(EncodedImageCallback::DropReason reason);
 
   // Resources need to be mapped to an AdaptReason (kCpu or kQuality) in order
-  // to be able to update |active_counts_|, which is used...
-  // - Legacy getStats() purposes.
-  // - Preventing adapting up in some circumstances (which may be questionable).
-  // TODO(hbos): Can we get rid of this?
+  // to update legacy getStats().
   void MapResourceToReason(rtc::scoped_refptr<Resource> resource,
                            VideoAdaptationReason reason);
   std::vector<rtc::scoped_refptr<Resource>> MappedResources() const;
@@ -128,7 +126,7 @@
   bool DropInitialFrames() const;
 
   // VideoSourceRestrictionsListener implementation.
-  // Updates |video_source_restrictions_| and |active_counts_|.
+  // Updates |video_source_restrictions_|.
   void OnVideoSourceRestrictionsUpdated(
       VideoSourceRestrictions restrictions,
       const VideoAdaptationCounters& adaptation_counters,
@@ -138,6 +136,9 @@
       const std::map<rtc::scoped_refptr<Resource>, VideoAdaptationCounters>&
           resource_limitations) override;
 
+  // QualityRampUpExperimentListener implementation.
+  void OnQualityRampUp() override;
+
  private:
   class InitialFrameDropper;
 
@@ -157,15 +158,9 @@
 
   void UpdateStatsAdaptationSettings() const;
 
-  // Checks to see if we should execute the quality rampup experiment. The
-  // experiment resets all video restrictions at the start of the call in the
-  // case the bandwidth estimate is high enough.
-  // TODO(https://crbug.com/webrtc/11222) Move experiment details into an inner
-  // class.
-  void MaybePerformQualityRampupExperiment();
-
-  void ResetActiveCounts();
-  std::string ActiveCountsToString() const;
+  static std::string ActiveCountsToString(
+      const std::map<VideoAdaptationReason, VideoAdaptationCounters>&
+          active_counts);
 
   // TODO(hbos): Add tests for manager's constraints.
   // Does not trigger adaptations, only prevents adapting up resolution.
@@ -260,9 +255,7 @@
       RTC_GUARDED_BY(encoder_queue_);
   absl::optional<VideoEncoder::RateControlParameters> encoder_rates_
       RTC_GUARDED_BY(encoder_queue_);
-  // Used on both the encoder queue and resource adaptation queue.
-  std::atomic<bool> quality_rampup_done_;
-  QualityRampupExperiment quality_rampup_experiment_
+  std::unique_ptr<QualityRampUpExperimentHelper> quality_rampup_experiment_
       RTC_GUARDED_BY(encoder_queue_);
   absl::optional<EncoderSettings> encoder_settings_
       RTC_GUARDED_BY(encoder_queue_);
@@ -280,13 +273,6 @@
   };
   rtc::CriticalSection resource_lock_;
   std::vector<ResourceAndReason> resources_ RTC_GUARDED_BY(&resource_lock_);
-  // One AdaptationCounter for each reason, tracking the number of times we have
-  // adapted for each reason. The sum of active_counts_ MUST always equal the
-  // total adaptation provided by the VideoSourceRestrictions.
-  // TODO(bugs.webrtc.org/11553) Remove active counts by computing them on the
-  // fly. This require changes to MaybePerformQualityRampupExperiment.
-  std::unordered_map<VideoAdaptationReason, VideoAdaptationCounters>
-      active_counts_ RTC_GUARDED_BY(resource_adaptation_queue_);
 };
 
 }  // namespace webrtc