Dynamic resolution change for VP8 HW encode.

Off by default for now.

BUG=
R=glaznev@webrtc.org, stefan@webrtc.org
TBR=mflodman@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/45849004

Cr-Commit-Position: refs/heads/master@{#9045}
diff --git a/talk/app/webrtc/java/jni/androidmediaencoder_jni.cc b/talk/app/webrtc/java/jni/androidmediaencoder_jni.cc
index 081387e..00e6a38 100644
--- a/talk/app/webrtc/java/jni/androidmediaencoder_jni.cc
+++ b/talk/app/webrtc/java/jni/androidmediaencoder_jni.cc
@@ -34,6 +34,7 @@
 #include "webrtc/base/logging.h"
 #include "webrtc/base/thread.h"
 #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
+#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
 #include "webrtc/system_wrappers/interface/logcat_trace_context.h"
 #include "third_party/libyuv/include/libyuv/convert.h"
 #include "third_party/libyuv/include/libyuv/convert_from.h"
@@ -96,6 +97,8 @@
   // rtc::MessageHandler implementation.
   void OnMessage(rtc::Message* msg) override;
 
+  void OnDroppedFrame() override;
+
  private:
   // CHECK-fail if not running on |codec_thread_|.
   void CheckOnCodecThread();
@@ -171,7 +174,6 @@
   int frames_received_;  // Number of frames received by encoder.
   int frames_encoded_;  // Number of frames encoded by encoder.
   int frames_dropped_;  // Number of frames dropped by encoder.
-  int frames_resolution_update_;  // Number of frames with new codec resolution.
   int frames_in_queue_;  // Number of frames in encoder queue.
   int64_t start_time_ms_;  // Start time for statistics.
   int current_frames_;  // Number of frames in the current statistics interval.
@@ -193,6 +195,11 @@
   bool drop_next_input_frame_;
   // Global references; must be deleted in Release().
   std::vector<jobject> input_buffers_;
+  scoped_ptr<webrtc::QualityScaler> quality_scaler_;
+  // Target frame size in bytes.
+  int target_framesize_;
+  // Dynamic resolution change, off by default.
+  bool scale_;
 };
 
 MediaCodecVideoEncoder::~MediaCodecVideoEncoder() {
@@ -207,6 +214,7 @@
     inited_(false),
     picture_id_(0),
     codec_thread_(new Thread()),
+    quality_scaler_(new webrtc::QualityScaler()),
     j_media_codec_video_encoder_class_(
         jni,
         FindClass(jni, "org/webrtc/MediaCodecVideoEncoder")),
@@ -270,6 +278,8 @@
     const webrtc::VideoCodec* codec_settings,
     int32_t /* number_of_cores */,
     size_t /* max_payload_size */) {
+  const int kMinWidth = 320;
+  const int kMinHeight = 180;
   if (codec_settings == NULL) {
     ALOGE("NULL VideoCodec instance");
     return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
@@ -279,6 +289,16 @@
       codec_settings->codecType << " for " << codecType_;
 
   ALOGD("InitEncode request");
+  scale_ = false;
+  quality_scaler_->Init(0);
+  quality_scaler_->SetMinResolution(kMinWidth, kMinHeight);
+  quality_scaler_->ReportFramerate(codec_settings->maxFramerate);
+  if (codec_settings->maxFramerate > 0) {
+    target_framesize_ = codec_settings->startBitrate * 1000 /
+        codec_settings->maxFramerate / 8;
+  } else {
+    target_framesize_ = 0;
+  }
   return codec_thread_->Invoke<int32_t>(
       Bind(&MediaCodecVideoEncoder::InitEncodeOnCodecThread,
            this,
@@ -317,6 +337,12 @@
 
 int32_t MediaCodecVideoEncoder::SetRates(uint32_t new_bit_rate,
                                          uint32_t frame_rate) {
+  quality_scaler_->ReportFramerate(frame_rate);
+  if (frame_rate > 0) {
+    target_framesize_ = new_bit_rate * 1000 / frame_rate / 8;
+  } else {
+    target_framesize_ = 0;
+  }
   return codec_thread_->Invoke<int32_t>(
       Bind(&MediaCodecVideoEncoder::SetRatesOnCodecThread,
            this,
@@ -384,7 +410,6 @@
   frames_received_ = 0;
   frames_encoded_ = 0;
   frames_dropped_ = 0;
-  frames_resolution_update_ = 0;
   frames_in_queue_ = 0;
   current_timestamp_us_ = 0;
   start_time_ms_ = GetCurrentTimeMs();
@@ -472,20 +497,18 @@
   }
 
   CHECK(frame_types->size() == 1) << "Unexpected stream count";
-  if (frame.width() != width_ || frame.height() != height_) {
-    frames_resolution_update_++;
-    ALOGD("Unexpected frame resolution change from %d x %d to %d x %d",
-        width_, height_, frame.width(), frame.height());
-    if (frames_resolution_update_ > 3) {
-      // Reset codec if we received more than 3 frames with new resolution.
-      width_ = frame.width();
-      height_ = frame.height();
-      frames_resolution_update_ = 0;
-      ResetCodec();
-    }
+  const I420VideoFrame& input_frame =
+      (scale_ && codecType_ == kVideoCodecVP8) ?
+      quality_scaler_->GetScaledFrame(frame) : frame;
+
+  if (input_frame.width() != width_ || input_frame.height() != height_) {
+    ALOGD("Frame resolution change from %d x %d to %d x %d",
+          width_, height_, input_frame.width(), input_frame.height());
+    width_ = input_frame.width();
+    height_ = input_frame.height();
+    ResetCodec();
     return WEBRTC_VIDEO_CODEC_OK;
   }
-  frames_resolution_update_ = 0;
 
   bool key_frame = frame_types->front() != webrtc::kDeltaFrame;
 
@@ -498,6 +521,8 @@
       ALOGD("Drop frame - encoder is behind by %d ms. Q size: %d",
           encoder_latency_ms, frames_in_queue_);
       frames_dropped_++;
+      // Report dropped frame to quality_scaler_.
+      OnDroppedFrame();
       return WEBRTC_VIDEO_CODEC_OK;
     }
   }
@@ -509,6 +534,8 @@
     // Video codec falls behind - no input buffer available.
     ALOGV("Encoder drop frame - no input buffers available");
     frames_dropped_++;
+    // Report dropped frame to quality_scaler_.
+    OnDroppedFrame();
     return WEBRTC_VIDEO_CODEC_OK;  // TODO(fischman): see webrtc bug 2887.
   }
   if (j_input_buffer_index == -2) {
@@ -525,9 +552,12 @@
   CHECK_EXCEPTION(jni);
   CHECK(yuv_buffer) << "Indirect buffer??";
   CHECK(!libyuv::ConvertFromI420(
-          frame.buffer(webrtc::kYPlane), frame.stride(webrtc::kYPlane),
-          frame.buffer(webrtc::kUPlane), frame.stride(webrtc::kUPlane),
-          frame.buffer(webrtc::kVPlane), frame.stride(webrtc::kVPlane),
+          input_frame.buffer(webrtc::kYPlane),
+          input_frame.stride(webrtc::kYPlane),
+          input_frame.buffer(webrtc::kUPlane),
+          input_frame.stride(webrtc::kUPlane),
+          input_frame.buffer(webrtc::kVPlane),
+          input_frame.stride(webrtc::kVPlane),
           yuv_buffer, width_,
           width_, height_,
           encoder_fourcc_))
@@ -536,8 +566,8 @@
   frames_in_queue_++;
 
   // Save input image timestamps for later output
-  timestamps_.push_back(frame.timestamp());
-  render_times_ms_.push_back(frame.render_time_ms());
+  timestamps_.push_back(input_frame.timestamp());
+  render_times_ms_.push_back(input_frame.render_time_ms());
   frame_rtc_times_ms_.push_back(GetCurrentTimeMs());
 
   bool encode_status = jni->CallBooleanMethod(*j_media_codec_video_encoder_,
@@ -686,6 +716,17 @@
         last_input_timestamp_ms_ - last_output_timestamp_ms_,
         frame_encoding_time_ms);
 
+    if (payload_size) {
+      double framesize_deviation = 0.0;
+      if (target_framesize_ > 0) {
+        framesize_deviation =
+          (double)abs((int)payload_size - target_framesize_) /
+          target_framesize_;
+      }
+      quality_scaler_->ReportNormalizedFrameSizeFluctuation(
+          framesize_deviation);
+    }
+
     // Calculate and print encoding statistics - every 3 seconds.
     frames_encoded_++;
     current_frames_++;
@@ -830,6 +871,9 @@
   return -1;
 }
 
+void MediaCodecVideoEncoder::OnDroppedFrame() {
+  quality_scaler_->ReportDroppedFrame();
+}
 
 MediaCodecVideoEncoderFactory::MediaCodecVideoEncoderFactory() {
   JNIEnv* jni = AttachCurrentThreadIfNeeded();
diff --git a/webrtc/modules/video_coding/BUILD.gn b/webrtc/modules/video_coding/BUILD.gn
index 340a203..bf04adf 100644
--- a/webrtc/modules/video_coding/BUILD.gn
+++ b/webrtc/modules/video_coding/BUILD.gn
@@ -94,8 +94,9 @@
   sources = [
     "utility/frame_dropper.cc",
     "utility/include/frame_dropper.h",
+    "utility/include/moving_average.h",
+    "utility/include/quality_scaler.h",
     "utility/quality_scaler.cc",
-    "utility/quality_scaler.h",
   ]
 
   configs += [ "../..:common_config" ]
diff --git a/webrtc/modules/video_coding/codecs/i420/main/interface/i420.h b/webrtc/modules/video_coding/codecs/i420/main/interface/i420.h
index f115907..e1a13df 100644
--- a/webrtc/modules/video_coding/codecs/i420/main/interface/i420.h
+++ b/webrtc/modules/video_coding/codecs/i420/main/interface/i420.h
@@ -77,6 +77,8 @@
     return WEBRTC_VIDEO_CODEC_OK;
   }
 
+  void OnDroppedFrame() override {}
+
  private:
   static uint8_t* InsertHeader(uint8_t* buffer, uint16_t width,
                                uint16_t height);
diff --git a/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.cc b/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.cc
index 7ab9f67..aa73569 100644
--- a/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.cc
+++ b/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.cc
@@ -501,4 +501,8 @@
   return !streaminfos_.empty();
 }
 
+void SimulcastEncoderAdapter::OnDroppedFrame() {
+  streaminfos_[0].encoder->OnDroppedFrame();
+}
+
 }  // namespace webrtc
diff --git a/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h b/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h
index 430d406..aeaa72a 100644
--- a/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h
+++ b/webrtc/modules/video_coding/codecs/vp8/simulcast_encoder_adapter.h
@@ -55,6 +55,8 @@
                   const CodecSpecificInfo* codecSpecificInfo = NULL,
                   const RTPFragmentationHeader* fragmentation = NULL);
 
+  void OnDroppedFrame() override;
+
  private:
   struct StreamInfo {
     StreamInfo()
diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
index 4f3678f..d8ec286 100644
--- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
+++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
@@ -1035,7 +1035,7 @@
     if (encoded_images_[0]._length > 0) {
       int qp;
       vpx_codec_control(&encoders_[0], VP8E_GET_LAST_QUANTIZER_64, &qp);
-      quality_scaler_.ReportEncodedFrame(qp);
+      quality_scaler_.ReportNormalizedQP(qp);
     } else {
       quality_scaler_.ReportDroppedFrame();
     }
diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h
index 2ddae4b..d9c4f4b 100644
--- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h
+++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h
@@ -27,7 +27,7 @@
 #include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
 #include "webrtc/modules/video_coding/codecs/vp8/reference_picture_selection.h"
 #include "webrtc/modules/video_coding/utility/include/frame_dropper.h"
-#include "webrtc/modules/video_coding/utility/quality_scaler.h"
+#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
 #include "webrtc/video_frame.h"
 
 namespace webrtc {
@@ -56,6 +56,8 @@
 
   virtual int SetRates(uint32_t new_bitrate_kbit, uint32_t frame_rate);
 
+  void OnDroppedFrame() override {}
+
  private:
   void SetupTemporalLayers(int num_streams, int num_temporal_layers,
                            const VideoCodec& codec);
diff --git a/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h b/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h
index 019e73e..28b9ecc 100644
--- a/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h
+++ b/webrtc/modules/video_coding/codecs/vp9/vp9_impl.h
@@ -41,6 +41,8 @@
 
   int SetRates(uint32_t new_bitrate_kbit, uint32_t frame_rate) override;
 
+  void OnDroppedFrame() override {}
+
  private:
   // Determine number of encoder threads to use.
   int NumberOfThreads(int width, int height, int number_of_cores);
diff --git a/webrtc/modules/video_coding/main/source/generic_encoder.cc b/webrtc/modules/video_coding/main/source/generic_encoder.cc
index 58fdda1..fb51cd3 100644
--- a/webrtc/modules/video_coding/main/source/generic_encoder.cc
+++ b/webrtc/modules/video_coding/main/source/generic_encoder.cc
@@ -197,6 +197,10 @@
     return internal_source_;
 }
 
+void VCMGenericEncoder::OnDroppedFrame() {
+  encoder_->OnDroppedFrame();
+}
+
  /***************************
   * Callback Implementation
   ***************************/
diff --git a/webrtc/modules/video_coding/main/source/generic_encoder.h b/webrtc/modules/video_coding/main/source/generic_encoder.h
index ce037d1..15fd0c6 100644
--- a/webrtc/modules/video_coding/main/source/generic_encoder.h
+++ b/webrtc/modules/video_coding/main/source/generic_encoder.h
@@ -136,6 +136,8 @@
 
     bool InternalSource() const;
 
+    void OnDroppedFrame();
+
 private:
     VideoEncoder* const encoder_;
     VideoEncoderRateObserver* const rate_observer_;
diff --git a/webrtc/modules/video_coding/main/source/video_sender.cc b/webrtc/modules/video_coding/main/source/video_sender.cc
index a153ac9..df504cd 100644
--- a/webrtc/modules/video_coding/main/source/video_sender.cc
+++ b/webrtc/modules/video_coding/main/source/video_sender.cc
@@ -17,6 +17,7 @@
 #include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
 #include "webrtc/modules/video_coding/main/source/encoded_frame.h"
 #include "webrtc/modules/video_coding/main/source/video_coding_impl.h"
+#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
 #include "webrtc/system_wrappers/interface/clock.h"
 #include "webrtc/system_wrappers/interface/logging.h"
 
@@ -346,6 +347,7 @@
     return VCM_OK;
   }
   if (_mediaOpt.DropFrame()) {
+    _encoder->OnDroppedFrame();
     return VCM_OK;
   }
   _mediaOpt.UpdateContentData(contentMetrics);
diff --git a/webrtc/modules/video_coding/utility/include/moving_average.h b/webrtc/modules/video_coding/utility/include/moving_average.h
new file mode 100644
index 0000000..49c42c4
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/include/moving_average.h
@@ -0,0 +1,71 @@
+/*
+ *  Copyright (c) 2015 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 WEBRTC_MODULES_VIDEO_CODING_UTILITY_MOVING_AVERAGE_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_MOVING_AVERAGE_H_
+
+#include <list>
+
+#include "webrtc/typedefs.h"
+
+namespace webrtc {
+template<class T>
+class MovingAverage {
+ public:
+  MovingAverage();
+  void AddSample(T sample);
+  bool GetAverage(size_t num_samples, T* average);
+  void Reset();
+  int size();
+
+ private:
+  T sum_;
+  std::list<T> samples_;
+};
+
+template<class T>
+MovingAverage<T>::MovingAverage() : sum_(static_cast<T>(0)) {
+}
+
+template<class T>
+void MovingAverage<T>::AddSample(T sample) {
+  samples_.push_back(sample);
+  sum_ += sample;
+}
+
+template<class T>
+bool MovingAverage<T>::GetAverage(size_t num_samples, T* avg) {
+  if (num_samples > samples_.size())
+    return false;
+
+  // Remove old samples.
+  while (num_samples < samples_.size()) {
+    sum_ -= samples_.front();
+    samples_.pop_front();
+  }
+
+  *avg = sum_ / static_cast<T>(num_samples);
+  return true;
+}
+
+template<class T>
+void MovingAverage<T>::Reset() {
+  sum_ = static_cast<T>(0);
+  samples_.clear();
+}
+
+template<class T>
+int MovingAverage<T>::size() {
+  return samples_.size();
+}
+
+}  // namespace webrtc
+
+#endif  // WEBRTC_MODULES_VIDEO_CODING_MOVING_AVERAGE_SCALER_H_
diff --git a/webrtc/modules/video_coding/utility/quality_scaler.h b/webrtc/modules/video_coding/utility/include/quality_scaler.h
similarity index 65%
rename from webrtc/modules/video_coding/utility/quality_scaler.h
rename to webrtc/modules/video_coding/utility/include/quality_scaler.h
index 47d6cb1..326d887 100644
--- a/webrtc/modules/video_coding/utility/quality_scaler.h
+++ b/webrtc/modules/video_coding/utility/include/quality_scaler.h
@@ -11,9 +11,8 @@
 #ifndef WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
 #define WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
 
-#include <list>
-
 #include "webrtc/common_video/libyuv/include/scaler.h"
+#include "webrtc/modules/video_coding/utility/include/moving_average.h"
 
 namespace webrtc {
 class QualityScaler {
@@ -25,27 +24,20 @@
 
   QualityScaler();
   void Init(int max_qp);
-
+  void SetMinResolution(int min_width, int min_height);
   void ReportFramerate(int framerate);
-  void ReportEncodedFrame(int qp);
-  void ReportDroppedFrame();
 
+  // Report QP for SW encoder, report framesize fluctuation for HW encoder,
+  // only one of these two functions should be called, framesize fluctuation
+  // is to be used only if qp isn't available.
+  void ReportNormalizedQP(int qp);
+  void ReportNormalizedFrameSizeFluctuation(double framesize_deviation);
+  void ReportDroppedFrame();
+  void Reset(int framerate, int bitrate, int width, int height);
   Resolution GetScaledResolution(const I420VideoFrame& frame);
   const I420VideoFrame& GetScaledFrame(const I420VideoFrame& frame);
 
  private:
-  class MovingAverage {
-   public:
-    MovingAverage();
-    void AddSample(int sample);
-    bool GetAverage(size_t num_samples, int* average);
-    void Reset();
-
-   private:
-    int sum_;
-    std::list<int> samples_;
-  };
-
   void AdjustScale(bool up);
   void ClearSamples();
 
@@ -53,11 +45,14 @@
   I420VideoFrame scaled_frame_;
 
   size_t num_samples_;
+  int target_framesize_;
   int low_qp_threshold_;
-  MovingAverage average_qp_;
-  MovingAverage framedrop_percent_;
+  MovingAverage<int> framedrop_percent_;
+  MovingAverage<double> frame_quality_;
 
   int downscale_shift_;
+  int min_width_;
+  int min_height_;
 };
 
 }  // namespace webrtc
diff --git a/webrtc/modules/video_coding/utility/quality_scaler.cc b/webrtc/modules/video_coding/utility/quality_scaler.cc
index 327748d..38b582b 100644
--- a/webrtc/modules/video_coding/utility/quality_scaler.cc
+++ b/webrtc/modules/video_coding/utility/quality_scaler.cc
@@ -7,8 +7,7 @@
  *  in the file PATENTS.  All contributing project authors may
  *  be found in the AUTHORS file in the root of the source tree.
  */
-
-#include "webrtc/modules/video_coding/utility/quality_scaler.h"
+#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
 
 namespace webrtc {
 
@@ -16,25 +15,41 @@
 static const int kMeasureSeconds = 5;
 static const int kFramedropPercentThreshold = 60;
 static const int kLowQpThresholdDenominator = 3;
+static const double kFramesizeFlucThreshold = 0.11;
 
 QualityScaler::QualityScaler()
-    : num_samples_(0), low_qp_threshold_(-1), downscale_shift_(0) {
+    : num_samples_(0), low_qp_threshold_(-1), downscale_shift_(0),
+      min_width_(0), min_height_(0) {
 }
 
 void QualityScaler::Init(int max_qp) {
   ClearSamples();
-  downscale_shift_ = 0;
-  low_qp_threshold_ = max_qp / kLowQpThresholdDenominator ;
+  low_qp_threshold_ = max_qp / kLowQpThresholdDenominator;
 }
 
+void QualityScaler::SetMinResolution(int min_width, int min_height) {
+  min_width_ = min_width;
+  min_height_ = min_height;
+}
+
+// TODO(jackychen): target_framesize should be calculated from average bitrate
+// in the measured period of time.
+// Report framerate(fps) and target_bitrate(kbit/s) to estimate # of samples
+// and get target_framesize_.
 void QualityScaler::ReportFramerate(int framerate) {
   num_samples_ = static_cast<size_t>(
       kMeasureSeconds * (framerate < kMinFps ? kMinFps : framerate));
 }
 
-void QualityScaler::ReportEncodedFrame(int qp) {
-  average_qp_.AddSample(qp);
+void QualityScaler::ReportNormalizedQP(int qp) {
   framedrop_percent_.AddSample(0);
+  frame_quality_.AddSample(static_cast<double>(qp) / low_qp_threshold_);
+}
+
+void QualityScaler::ReportNormalizedFrameSizeFluctuation(
+    double framesize_deviation) {
+  framedrop_percent_.AddSample(0);
+  frame_quality_.AddSample(framesize_deviation / kFramesizeFlucThreshold);
 }
 
 void QualityScaler::ReportDroppedFrame() {
@@ -43,23 +58,25 @@
 
 QualityScaler::Resolution QualityScaler::GetScaledResolution(
     const I420VideoFrame& frame) {
-  // Both of these should be set through InitEncode -> Should be set by now.
+  // Should be set through InitEncode -> Should be set by now.
   assert(low_qp_threshold_ >= 0);
   assert(num_samples_ > 0);
-  // Update scale factor.
-  int avg;
-  if (framedrop_percent_.GetAverage(num_samples_, &avg) &&
-      avg >= kFramedropPercentThreshold) {
-    AdjustScale(false);
-  } else if (average_qp_.GetAverage(num_samples_, &avg) &&
-             avg <= low_qp_threshold_) {
-    AdjustScale(true);
-  }
 
   Resolution res;
   res.width = frame.width();
   res.height = frame.height();
 
+  // Update scale factor.
+  int avg_drop;
+  double avg_quality;
+  if (framedrop_percent_.GetAverage(num_samples_, &avg_drop) &&
+      avg_drop >= kFramedropPercentThreshold) {
+    AdjustScale(false);
+  } else if (frame_quality_.GetAverage(num_samples_, &avg_quality) &&
+      avg_quality <= 1.0) {
+    AdjustScale(true);
+  }
+
   assert(downscale_shift_ >= 0);
   for (int shift = downscale_shift_;
        shift > 0 && res.width > 1 && res.height > 1;
@@ -68,6 +85,12 @@
     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;
 }
 
@@ -94,37 +117,9 @@
   return scaled_frame_;
 }
 
-QualityScaler::MovingAverage::MovingAverage() : sum_(0) {
-}
-
-void QualityScaler::MovingAverage::AddSample(int sample) {
-  samples_.push_back(sample);
-  sum_ += sample;
-}
-
-bool QualityScaler::MovingAverage::GetAverage(size_t num_samples, int* avg) {
-  assert(num_samples > 0);
-  if (num_samples > samples_.size())
-    return false;
-
-  // Remove old samples.
-  while (num_samples < samples_.size()) {
-    sum_ -= samples_.front();
-    samples_.pop_front();
-  }
-
-  *avg = sum_ / static_cast<int>(num_samples);
-  return true;
-}
-
-void QualityScaler::MovingAverage::Reset() {
-  sum_ = 0;
-  samples_.clear();
-}
-
 void QualityScaler::ClearSamples() {
-  average_qp_.Reset();
   framedrop_percent_.Reset();
+  frame_quality_.Reset();
 }
 
 void QualityScaler::AdjustScale(bool up) {
diff --git a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
index 381b959..509b248 100644
--- a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
+++ b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
@@ -8,7 +8,7 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
-#include "webrtc/modules/video_coding/utility/quality_scaler.h"
+#include "webrtc/modules/video_coding/utility/include/quality_scaler.h"
 
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -40,7 +40,7 @@
     for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
       switch (scale_direction) {
         case kScaleUp:
-          qs_.ReportEncodedFrame(kLowQp);
+          qs_.ReportNormalizedQP(kLowQp);
           break;
         case kScaleDown:
           qs_.ReportDroppedFrame();
@@ -93,7 +93,7 @@
 
 TEST_F(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
   for (int i = 0; i < kFramerate * kNumSeconds / 3; ++i) {
-    qs_.ReportEncodedFrame(kNormalQp);
+    qs_.ReportNormalizedQP(kNormalQp);
     qs_.ReportDroppedFrame();
     qs_.ReportDroppedFrame();
     if (qs_.GetScaledResolution(input_frame_).width < input_frame_.width())
@@ -105,7 +105,7 @@
 
 TEST_F(QualityScalerTest, DoesNotDownscaleOnNormalQp) {
   for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
-    qs_.ReportEncodedFrame(kNormalQp);
+    qs_.ReportNormalizedQP(kNormalQp);
     ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
         << "Unexpected scale on half framedrop.";
   }
@@ -113,7 +113,7 @@
 
 TEST_F(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) {
   for (int i = 0; i < kFramerate * kNumSeconds / 2; ++i) {
-    qs_.ReportEncodedFrame(kNormalQp);
+    qs_.ReportNormalizedQP(kNormalQp);
     ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
         << "Unexpected scale on half framedrop.";
 
@@ -153,7 +153,7 @@
 
   // Verify we don't start upscaling after further low use.
   for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
-    qs_.ReportEncodedFrame(kLowQp);
+    qs_.ReportNormalizedQP(kLowQp);
     ExpectOriginalFrame();
   }
 }
diff --git a/webrtc/modules/video_coding/utility/video_coding_utility.gyp b/webrtc/modules/video_coding/utility/video_coding_utility.gyp
index eccc4da..c3649c6 100644
--- a/webrtc/modules/video_coding/utility/video_coding_utility.gyp
+++ b/webrtc/modules/video_coding/utility/video_coding_utility.gyp
@@ -20,8 +20,9 @@
       'sources': [
         'frame_dropper.cc',
         'include/frame_dropper.h',
+        'include/moving_average.h',
         'quality_scaler.cc',
-        'quality_scaler.h',
+        'include/quality_scaler.h',
       ],
     },
   ], # targets
diff --git a/webrtc/video_encoder.h b/webrtc/video_encoder.h
index c933a33..ce2569b 100644
--- a/webrtc/video_encoder.h
+++ b/webrtc/video_encoder.h
@@ -122,6 +122,7 @@
   virtual int32_t CodecConfigParameters(uint8_t* /*buffer*/, int32_t /*size*/) {
     return -1;
   }
+  virtual void OnDroppedFrame() {};
 };
 
 }  // namespace webrtc