Add default downscale threshold to QualityScaler.

Prevents downscaling below 160x90 or 90x160 to gain more quality.

BUG=4625
R=mflodman@webrtc.org

Review URL: https://codereview.webrtc.org/1160403004.

Cr-Commit-Position: refs/heads/master@{#9480}
diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
index e5a776e..4370424 100644
--- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
+++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
@@ -583,7 +583,7 @@
   }
 
   rps_.Init();
-  quality_scaler_.Init(codec_.qpMax / kDefaultLowQpDenominator);
+  quality_scaler_.Init(codec_.qpMax / QualityScaler::kDefaultLowQpDenominator);
   quality_scaler_.ReportFramerate(codec_.maxFramerate);
 
   return InitAndSetControlSettings();
diff --git a/webrtc/modules/video_coding/utility/include/quality_scaler.h b/webrtc/modules/video_coding/utility/include/quality_scaler.h
index 3253435..1d6c917 100644
--- a/webrtc/modules/video_coding/utility/include/quality_scaler.h
+++ b/webrtc/modules/video_coding/utility/include/quality_scaler.h
@@ -15,9 +15,10 @@
 #include "webrtc/modules/video_coding/utility/include/moving_average.h"
 
 namespace webrtc {
-const int kDefaultLowQpDenominator = 3;
 class QualityScaler {
  public:
+  static const int kDefaultLowQpDenominator;
+  static const int kDefaultMinDownscaleDimension;
   struct Resolution {
     int width;
     int height;
diff --git a/webrtc/modules/video_coding/utility/quality_scaler.cc b/webrtc/modules/video_coding/utility/quality_scaler.cc
index 3c9c6c4..7a2a9c0 100644
--- a/webrtc/modules/video_coding/utility/quality_scaler.cc
+++ b/webrtc/modules/video_coding/utility/quality_scaler.cc
@@ -15,9 +15,17 @@
 static const int kMeasureSeconds = 5;
 static const int kFramedropPercentThreshold = 60;
 
+const int QualityScaler::kDefaultLowQpDenominator = 3;
+// Note that this is the same for width and height to permit 120x90 in both
+// portrait and landscape mode.
+const int QualityScaler::kDefaultMinDownscaleDimension = 90;
+
 QualityScaler::QualityScaler()
-    : num_samples_(0), low_qp_threshold_(-1), downscale_shift_(0),
-      min_width_(0), min_height_(0) {
+    : num_samples_(0),
+      low_qp_threshold_(-1),
+      downscale_shift_(0),
+      min_width_(kDefaultMinDownscaleDimension),
+      min_height_(kDefaultMinDownscaleDimension) {
 }
 
 void QualityScaler::Init(int low_qp_threshold) {
@@ -68,18 +76,13 @@
 
   assert(downscale_shift_ >= 0);
   for (int shift = downscale_shift_;
-       shift > 0 && res.width > 1 && res.height > 1;
+       shift > 0 && (res.width >> 1 >= min_width_) &&
+           (res.height >> 1 >= min_height_);
        --shift) {
     res.width >>= 1;
     res.height >>= 1;
   }
 
-  // Set this limitation for VP8 HW encoder to avoid crash.
-  if (min_width_ > 0 && res.width * res.height < min_width_ * min_height_) {
-    res.width = min_width_;
-    res.height = min_height_;
-  }
-
   return res;
 }
 
diff --git a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
index b93fd96..c09dffb 100644
--- a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
+++ b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
@@ -31,11 +31,11 @@
   QualityScalerTest() {
     input_frame_.CreateEmptyFrame(
         kWidth, kHeight, kWidth, kHalfWidth, kHalfWidth);
-    qs_.Init(kMaxQp / kDefaultLowQpDenominator);
+    qs_.Init(kMaxQp / QualityScaler::kDefaultLowQpDenominator);
     qs_.ReportFramerate(kFramerate);
   }
 
-  void TriggerScale(ScaleDirection scale_direction) {
+  bool TriggerScale(ScaleDirection scale_direction) {
     int initial_width = qs_.GetScaledResolution(input_frame_).width;
     for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
       switch (scale_direction) {
@@ -48,10 +48,10 @@
       }
 
       if (qs_.GetScaledResolution(input_frame_).width != initial_width)
-        return;
+        return true;
     }
 
-    FAIL() << "No downscale within " << kNumSeconds << " seconds.";
+    return false;
   }
 
   void ExpectOriginalFrame() {
@@ -70,6 +70,11 @@
 
   void DoesNotDownscaleFrameDimensions(int width, int height);
 
+  void DownscaleEndsAt(int input_width,
+                       int input_height,
+                       int end_width,
+                       int end_height);
+
   QualityScaler qs_;
   VideoFrame input_frame_;
 };
@@ -85,7 +90,8 @@
 }
 
 TEST_F(QualityScalerTest, DownscalesAfterContinuousFramedrop) {
-  TriggerScale(kScaleDown);
+  EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within " << kNumSeconds
+                                        << " seconds.";
   QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
   EXPECT_LT(res.width, input_frame_.width());
   EXPECT_LT(res.height, input_frame_.height());
@@ -130,8 +136,9 @@
   int min_dimension = initial_min_dimension;
   int current_shift = 0;
   // Drop all frames to force-trigger downscaling.
-  while (min_dimension > 16) {
-    TriggerScale(kScaleDown);
+  while (min_dimension >= 2 * QualityScaler::kDefaultMinDownscaleDimension) {
+    EXPECT_TRUE(TriggerScale(kScaleDown)) << "No downscale within "
+                                          << kNumSeconds << " seconds.";
     QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
     min_dimension = res.width < res.height ? res.width : res.height;
     ++current_shift;
@@ -142,7 +149,8 @@
 
   // Make sure we can scale back with good-quality frames.
   while (min_dimension < initial_min_dimension) {
-    TriggerScale(kScaleUp);
+    EXPECT_TRUE(TriggerScale(kScaleUp)) << "No upscale within " << kNumSeconds
+                                        << " seconds.";
     QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
     min_dimension = res.width < res.height ? res.width : res.height;
     --current_shift;
@@ -195,4 +203,72 @@
   DoesNotDownscaleFrameDimensions(1, 1);
 }
 
+TEST_F(QualityScalerTest, DoesNotDownscaleBelow2xDefaultMinDimensionsWidth) {
+  DoesNotDownscaleFrameDimensions(
+      2 * QualityScaler::kDefaultMinDownscaleDimension - 1, 1000);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleBelow2xDefaultMinDimensionsHeight) {
+  DoesNotDownscaleFrameDimensions(
+      1000, 2 * QualityScaler::kDefaultMinDownscaleDimension - 1);
+}
+
+void QualityScalerTest::DownscaleEndsAt(int input_width,
+                                        int input_height,
+                                        int end_width,
+                                        int end_height) {
+  // Create a frame with 2x expected end width/height to verify that we can
+  // scale down to expected end width/height.
+  input_frame_.CreateEmptyFrame(input_width, input_height, input_width,
+                                (input_width + 1) / 2, (input_width + 1) / 2);
+
+  int last_width = input_width;
+  int last_height = input_height;
+  // Drop all frames to force-trigger downscaling.
+  while (true) {
+    TriggerScale(kScaleDown);
+    QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
+    if (last_width == res.width) {
+      EXPECT_EQ(last_height, res.height);
+      EXPECT_EQ(end_width, res.width);
+      EXPECT_EQ(end_height, res.height);
+      break;
+    }
+    last_width = res.width;
+    last_height = res.height;
+  }
+}
+
+TEST_F(QualityScalerTest, DefaultDownscalesTo160x90) {
+  DownscaleEndsAt(320, 180, 160, 90);
+}
+
+TEST_F(QualityScalerTest, DefaultDownscalesTo90x160) {
+  DownscaleEndsAt(180, 320, 90, 160);
+}
+
+TEST_F(QualityScalerTest, DefaultDownscalesFrom1280x720To160x90) {
+  DownscaleEndsAt(1280, 720, 160, 90);
+}
+
+TEST_F(QualityScalerTest, DefaultDoesntDownscaleBelow160x90) {
+  DownscaleEndsAt(320 - 1, 180 - 1, 320 - 1, 180 - 1);
+}
+
+TEST_F(QualityScalerTest, DefaultDoesntDownscaleBelow90x160) {
+  DownscaleEndsAt(180 - 1, 320 - 1, 180 - 1, 320 - 1);
+}
+
+TEST_F(QualityScalerTest, RespectsMinResolutionWidth) {
+  // Should end at 200x100, as width can't go lower.
+  qs_.SetMinResolution(200, 10);
+  DownscaleEndsAt(1600, 800, 200, 100);
+}
+
+TEST_F(QualityScalerTest, RespectsMinResolutionHeight) {
+  // Should end at 100x200, as height can't go lower.
+  qs_.SetMinResolution(10, 200);
+  DownscaleEndsAt(800, 1600, 100, 200);
+}
+
 }  // namespace webrtc