Add ability to downscale content to improve quality.
BUG=3712
R=marpan@google.com, stefan@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/18169004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@7164 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp
index a1a1bf0..f517568 100644
--- a/webrtc/modules/modules.gyp
+++ b/webrtc/modules/modules.gyp
@@ -246,6 +246,7 @@
'video_coding/main/source/qm_select_unittest.cc',
'video_coding/main/source/test/stream_generator.cc',
'video_coding/main/source/test/stream_generator.h',
+ 'video_coding/utility/quality_scaler_unittest.cc',
'video_processing/main/test/unit_test/brightness_detection_test.cc',
'video_processing/main/test/unit_test/color_enhancement_test.cc',
'video_processing/main/test/unit_test/content_metrics_test.cc',
diff --git a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
index e7b934d..bd4a563 100644
--- a/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
+++ b/webrtc/modules/video_coding/codecs/test/videoprocessor_integrationtest.cc
@@ -703,34 +703,26 @@
rc_metrics);
}
-// Run with no packet loss, at low bitrate, then increase rate somewhat.
-// Key frame is thrown in every 120 frames. Can expect some frame drops after
-// key frame, even at high rate. The internal spatial resizer is on, so expect
-// spatial resize down at first key frame, and back up at second key frame.
-// Error_concealment is off in this test since there is a memory leak with
-// resizing and error concealment.
+// Run with no packet loss, at low bitrate. During this time we should've
+// resized once.
TEST_F(VideoProcessorIntegrationTest,
DISABLED_ON_ANDROID(ProcessNoLossSpatialResizeFrameDrop)) {
config_.networking_config.packet_loss_probability = 0;
// Bitrate and frame rate profile.
RateProfile rate_profile;
- SetRateProfilePars(&rate_profile, 0, 100, 30, 0);
- SetRateProfilePars(&rate_profile, 1, 200, 30, 120);
- SetRateProfilePars(&rate_profile, 2, 200, 30, 240);
- rate_profile.frame_index_rate_update[3] = kNbrFramesLong + 1;
+ SetRateProfilePars(&rate_profile, 0, 50, 30, 0);
+ rate_profile.frame_index_rate_update[1] = kNbrFramesLong + 1;
rate_profile.num_frames = kNbrFramesLong;
// Codec/network settings.
CodecConfigPars process_settings;
- SetCodecParameters(&process_settings, 0.0f, 120, 1, false, true, true, true);
- // Metrics for expected quality.: lower quality on average from up-sampling
- // the down-sampled portion of the run, in case resizer is on.
+ SetCodecParameters(
+ &process_settings, 0.0f, kNbrFramesLong, 1, false, true, true, true);
+ // Metrics for expected quality.
QualityMetrics quality_metrics;
- SetQualityMetrics(&quality_metrics, 29.0, 20.0, 0.75, 0.60);
+ SetQualityMetrics(&quality_metrics, 25.0, 15.0, 0.70, 0.40);
// Metrics for rate control.
- RateControlMetrics rc_metrics[3];
- SetRateControlMetrics(rc_metrics, 0, 45, 30, 75, 20, 70, 0);
- SetRateControlMetrics(rc_metrics, 1, 20, 35, 30, 20, 15, 1);
- SetRateControlMetrics(rc_metrics, 2, 0, 30, 30, 15, 25, 1);
+ RateControlMetrics rc_metrics[1];
+ SetRateControlMetrics(rc_metrics, 0, 160, 60, 120, 20, 70, 1);
ProcessFramesAndVerify(quality_metrics,
rate_profile,
process_settings,
diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
index 4901edf..16d105d 100644
--- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
+++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.cc
@@ -105,6 +105,7 @@
temporal_layers_->ConfigureBitrates(new_bitrate_kbit, codec_.maxBitrate,
new_framerate, config_);
codec_.maxFramerate = new_framerate;
+ quality_scaler_.ReportFramerate(new_framerate);
// update encoder context
if (vpx_codec_enc_config_set(encoder_, config_)) {
@@ -230,8 +231,8 @@
30 : 0;
config_->rc_end_usage = VPX_CBR;
config_->g_pass = VPX_RC_ONE_PASS;
- config_->rc_resize_allowed = inst->codecSpecific.VP8.automaticResizeOn ?
- 1 : 0;
+ // Handle resizing outside of libvpx.
+ config_->rc_resize_allowed = 0;
config_->rc_min_quantizer = 2;
config_->rc_max_quantizer = inst->qpMax;
config_->rc_undershoot_pct = 100;
@@ -272,6 +273,8 @@
cpu_speed_ = -12;
#endif
rps_->Init();
+ quality_scaler_.Init(codec_.qpMax);
+ quality_scaler_.ReportFramerate(codec_.maxFramerate);
return InitAndSetControlSettings(inst);
}
@@ -296,6 +299,7 @@
vpx_codec_control(encoder_, VP8E_SET_MAX_INTRA_BITRATE_PCT,
rc_max_intra_target_);
inited_ = true;
+
return WEBRTC_VIDEO_CODEC_OK;
}
@@ -315,15 +319,15 @@
return (targetPct < minIntraTh) ? minIntraTh: targetPct;
}
-int VP8EncoderImpl::Encode(const I420VideoFrame& input_image,
+int VP8EncoderImpl::Encode(const I420VideoFrame& input_frame,
const CodecSpecificInfo* codec_specific_info,
const std::vector<VideoFrameType>* frame_types) {
- TRACE_EVENT1("webrtc", "VP8::Encode", "timestamp", input_image.timestamp());
+ TRACE_EVENT1("webrtc", "VP8::Encode", "timestamp", input_frame.timestamp());
if (!inited_) {
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
}
- if (input_image.IsZeroSize()) {
+ if (input_frame.IsZeroSize()) {
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
}
if (encoded_complete_callback_ == NULL) {
@@ -336,25 +340,31 @@
frame_type = (*frame_types)[0];
}
+ const I420VideoFrame& frame =
+ config_->rc_dropframe_thresh > 0 &&
+ codec_.codecSpecific.VP8.automaticResizeOn
+ ? quality_scaler_.GetScaledFrame(input_frame)
+ : input_frame;
+
// Check for change in frame size.
- if (input_image.width() != codec_.width ||
- input_image.height() != codec_.height) {
- int ret = UpdateCodecFrameSize(input_image);
+ if (frame.width() != codec_.width ||
+ frame.height() != codec_.height) {
+ int ret = UpdateCodecFrameSize(frame);
if (ret < 0) {
return ret;
}
}
// Image in vpx_image_t format.
- // Input image is const. VP8's raw image is not defined as const.
- raw_->planes[PLANE_Y] = const_cast<uint8_t*>(input_image.buffer(kYPlane));
- raw_->planes[PLANE_U] = const_cast<uint8_t*>(input_image.buffer(kUPlane));
- raw_->planes[PLANE_V] = const_cast<uint8_t*>(input_image.buffer(kVPlane));
+ // Input frame is const. VP8's raw frame is not defined as const.
+ raw_->planes[PLANE_Y] = const_cast<uint8_t*>(frame.buffer(kYPlane));
+ raw_->planes[PLANE_U] = const_cast<uint8_t*>(frame.buffer(kUPlane));
+ raw_->planes[PLANE_V] = const_cast<uint8_t*>(frame.buffer(kVPlane));
// TODO(mikhal): Stride should be set in initialization.
- raw_->stride[VPX_PLANE_Y] = input_image.stride(kYPlane);
- raw_->stride[VPX_PLANE_U] = input_image.stride(kUPlane);
- raw_->stride[VPX_PLANE_V] = input_image.stride(kVPlane);
+ raw_->stride[VPX_PLANE_Y] = frame.stride(kYPlane);
+ raw_->stride[VPX_PLANE_U] = frame.stride(kUPlane);
+ raw_->stride[VPX_PLANE_V] = frame.stride(kVPlane);
- int flags = temporal_layers_->EncodeFlags(input_image.timestamp());
+ int flags = temporal_layers_->EncodeFlags(frame.timestamp());
bool send_keyframe = (frame_type == kKeyFrame);
if (send_keyframe) {
@@ -370,11 +380,11 @@
codec_specific_info->codecSpecific.VP8.pictureIdRPSI);
}
if (codec_specific_info->codecSpecific.VP8.hasReceivedSLI) {
- sendRefresh = rps_->ReceivedSLI(input_image.timestamp());
+ sendRefresh = rps_->ReceivedSLI(frame.timestamp());
}
}
flags = rps_->EncodeFlags(picture_id_, sendRefresh,
- input_image.timestamp());
+ frame.timestamp());
}
// TODO(holmer): Ideally the duration should be the timestamp diff of this
@@ -390,7 +400,7 @@
}
timestamp_ += duration;
- return GetEncodedPartitions(input_image);
+ return GetEncodedPartitions(frame);
}
int VP8EncoderImpl::UpdateCodecFrameSize(const I420VideoFrame& input_image) {
@@ -480,6 +490,11 @@
encoded_image_._encodedWidth = codec_.width;
encoded_complete_callback_->Encoded(encoded_image_, &codec_specific,
&frag_info);
+ int qp;
+ vpx_codec_control(encoder_, VP8E_GET_LAST_QUANTIZER_64, &qp);
+ quality_scaler_.ReportEncodedFrame(qp);
+ } else {
+ quality_scaler_.ReportDroppedFrame();
}
return WEBRTC_VIDEO_CODEC_OK;
}
diff --git a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h
index 56f7219..08ce3c9 100644
--- a/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h
+++ b/webrtc/modules/video_coding/codecs/vp8/vp8_impl.h
@@ -14,6 +14,7 @@
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_IMPL_H_
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
+#include "webrtc/modules/video_coding/utility/quality_scaler.h"
// VPX forward declaration
typedef struct vpx_codec_ctx vpx_codec_ctx_t;
@@ -139,6 +140,7 @@
vpx_codec_ctx_t* encoder_;
vpx_codec_enc_cfg_t* config_;
vpx_image_t* raw_;
+ QualityScaler quality_scaler_;
}; // end of VP8Encoder class
diff --git a/webrtc/modules/video_coding/main/source/video_coding_test.gypi b/webrtc/modules/video_coding/main/source/video_coding_test.gypi
index 576524d..64cb602 100644
--- a/webrtc/modules/video_coding/main/source/video_coding_test.gypi
+++ b/webrtc/modules/video_coding/main/source/video_coding_test.gypi
@@ -44,8 +44,8 @@
'../test/codec_database_test.cc',
'../test/generic_codec_test.cc',
'../test/media_opt_test.cc',
- '../test/mt_test_common.cc',
'../test/mt_rx_tx_test.cc',
+ '../test/mt_test_common.cc',
'../test/normal_test.cc',
'../test/quality_modes_test.cc',
'../test/rtp_player.cc',
@@ -53,8 +53,8 @@
'../test/test_util.cc',
'../test/tester_main.cc',
'../test/vcm_payload_sink_factory.cc',
- '../test/video_rtp_play_mt.cc',
'../test/video_rtp_play.cc',
+ '../test/video_rtp_play_mt.cc',
'../test/video_source.cc',
], # sources
},
diff --git a/webrtc/modules/video_coding/utility/quality_scaler.cc b/webrtc/modules/video_coding/utility/quality_scaler.cc
new file mode 100644
index 0000000..327748d
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/quality_scaler.cc
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2014 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 "webrtc/modules/video_coding/utility/quality_scaler.h"
+
+namespace webrtc {
+
+static const int kMinFps = 10;
+static const int kMeasureSeconds = 5;
+static const int kFramedropPercentThreshold = 60;
+static const int kLowQpThresholdDenominator = 3;
+
+QualityScaler::QualityScaler()
+ : num_samples_(0), low_qp_threshold_(-1), downscale_shift_(0) {
+}
+
+void QualityScaler::Init(int max_qp) {
+ ClearSamples();
+ downscale_shift_ = 0;
+ low_qp_threshold_ = max_qp / kLowQpThresholdDenominator ;
+}
+
+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);
+ framedrop_percent_.AddSample(0);
+}
+
+void QualityScaler::ReportDroppedFrame() {
+ framedrop_percent_.AddSample(100);
+}
+
+QualityScaler::Resolution QualityScaler::GetScaledResolution(
+ const I420VideoFrame& frame) {
+ // Both of these 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();
+
+ assert(downscale_shift_ >= 0);
+ for (int shift = downscale_shift_;
+ shift > 0 && res.width > 1 && res.height > 1;
+ --shift) {
+ res.width >>= 1;
+ res.height >>= 1;
+ }
+
+ return res;
+}
+
+const I420VideoFrame& QualityScaler::GetScaledFrame(
+ const I420VideoFrame& frame) {
+ Resolution res = GetScaledResolution(frame);
+ if (res.width == frame.width())
+ return frame;
+
+ scaler_.Set(frame.width(),
+ frame.height(),
+ res.width,
+ res.height,
+ kI420,
+ kI420,
+ kScaleBox);
+ if (scaler_.Scale(frame, &scaled_frame_) != 0)
+ return frame;
+
+ scaled_frame_.set_ntp_time_ms(frame.ntp_time_ms());
+ scaled_frame_.set_timestamp(frame.timestamp());
+ scaled_frame_.set_render_time_ms(frame.render_time_ms());
+
+ 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();
+}
+
+void QualityScaler::AdjustScale(bool up) {
+ downscale_shift_ += up ? -1 : 1;
+ if (downscale_shift_ < 0)
+ downscale_shift_ = 0;
+ ClearSamples();
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/video_coding/utility/quality_scaler.h b/webrtc/modules/video_coding/utility/quality_scaler.h
new file mode 100644
index 0000000..47d6cb1
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/quality_scaler.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2014 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_QUALITY_SCALER_H_
+#define WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
+
+#include <list>
+
+#include "webrtc/common_video/libyuv/include/scaler.h"
+
+namespace webrtc {
+class QualityScaler {
+ public:
+ struct Resolution {
+ int width;
+ int height;
+ };
+
+ QualityScaler();
+ void Init(int max_qp);
+
+ void ReportFramerate(int framerate);
+ void ReportEncodedFrame(int qp);
+ void ReportDroppedFrame();
+
+ 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();
+
+ Scaler scaler_;
+ I420VideoFrame scaled_frame_;
+
+ size_t num_samples_;
+ int low_qp_threshold_;
+ MovingAverage average_qp_;
+ MovingAverage framedrop_percent_;
+
+ int downscale_shift_;
+};
+
+} // namespace webrtc
+
+#endif // WEBRTC_MODULES_VIDEO_CODING_UTILITY_QUALITY_SCALER_H_
diff --git a/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
new file mode 100644
index 0000000..381b959
--- /dev/null
+++ b/webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2014 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 "webrtc/modules/video_coding/utility/quality_scaler.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace webrtc {
+namespace {
+static const int kNumSeconds = 10;
+static const int kWidth = 1920;
+static const int kHalfWidth = kWidth / 2;
+static const int kHeight = 1080;
+static const int kFramerate = 30;
+static const int kLowQp = 15;
+static const int kNormalQp = 30;
+static const int kMaxQp = 56;
+} // namespace
+
+class QualityScalerTest : public ::testing::Test {
+ protected:
+ enum ScaleDirection { kScaleDown, kScaleUp };
+
+ QualityScalerTest() {
+ input_frame_.CreateEmptyFrame(
+ kWidth, kHeight, kWidth, kHalfWidth, kHalfWidth);
+ qs_.Init(kMaxQp);
+ qs_.ReportFramerate(kFramerate);
+ }
+
+ void TriggerScale(ScaleDirection scale_direction) {
+ int initial_width = qs_.GetScaledResolution(input_frame_).width;
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ switch (scale_direction) {
+ case kScaleUp:
+ qs_.ReportEncodedFrame(kLowQp);
+ break;
+ case kScaleDown:
+ qs_.ReportDroppedFrame();
+ break;
+ }
+
+ if (qs_.GetScaledResolution(input_frame_).width != initial_width)
+ return;
+ }
+
+ FAIL() << "No downscale within " << kNumSeconds << " seconds.";
+ }
+
+ void ExpectOriginalFrame() {
+ EXPECT_EQ(&input_frame_, &qs_.GetScaledFrame(input_frame_))
+ << "Using scaled frame instead of original input.";
+ }
+
+ void ExpectScaleUsingReportedResolution() {
+ QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
+ const I420VideoFrame& scaled_frame = qs_.GetScaledFrame(input_frame_);
+ EXPECT_EQ(res.width, scaled_frame.width());
+ EXPECT_EQ(res.height, scaled_frame.height());
+ }
+
+ void ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+
+ void DoesNotDownscaleFrameDimensions(int width, int height);
+
+ QualityScaler qs_;
+ I420VideoFrame input_frame_;
+};
+
+TEST_F(QualityScalerTest, UsesOriginalFrameInitially) {
+ ExpectOriginalFrame();
+}
+
+TEST_F(QualityScalerTest, ReportsOriginalResolutionInitially) {
+ QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
+ EXPECT_EQ(input_frame_.width(), res.width);
+ EXPECT_EQ(input_frame_.height(), res.height);
+}
+
+TEST_F(QualityScalerTest, DownscalesAfterContinuousFramedrop) {
+ TriggerScale(kScaleDown);
+ QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
+ EXPECT_LT(res.width, input_frame_.width());
+ EXPECT_LT(res.height, input_frame_.height());
+}
+
+TEST_F(QualityScalerTest, DownscalesAfterTwoThirdsFramedrop) {
+ for (int i = 0; i < kFramerate * kNumSeconds / 3; ++i) {
+ qs_.ReportEncodedFrame(kNormalQp);
+ qs_.ReportDroppedFrame();
+ qs_.ReportDroppedFrame();
+ if (qs_.GetScaledResolution(input_frame_).width < input_frame_.width())
+ return;
+ }
+
+ FAIL() << "No downscale within " << kNumSeconds << " seconds.";
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleOnNormalQp) {
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportEncodedFrame(kNormalQp);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
+ << "Unexpected scale on half framedrop.";
+ }
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleAfterHalfFramedrop) {
+ for (int i = 0; i < kFramerate * kNumSeconds / 2; ++i) {
+ qs_.ReportEncodedFrame(kNormalQp);
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
+ << "Unexpected scale on half framedrop.";
+
+ qs_.ReportDroppedFrame();
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
+ << "Unexpected scale on half framedrop.";
+ }
+}
+
+void QualityScalerTest::ContinuouslyDownscalesByHalfDimensionsAndBackUp() {
+ const int initial_min_dimension = input_frame_.width() < input_frame_.height()
+ ? input_frame_.width()
+ : input_frame_.height();
+ int min_dimension = initial_min_dimension;
+ int current_shift = 0;
+ // Drop all frames to force-trigger downscaling.
+ while (min_dimension > 16) {
+ TriggerScale(kScaleDown);
+ QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
+ min_dimension = res.width < res.height ? res.width : res.height;
+ ++current_shift;
+ ASSERT_EQ(input_frame_.width() >> current_shift, res.width);
+ ASSERT_EQ(input_frame_.height() >> current_shift, res.height);
+ ExpectScaleUsingReportedResolution();
+ }
+
+ // Make sure we can scale back with good-quality frames.
+ while (min_dimension < initial_min_dimension) {
+ TriggerScale(kScaleUp);
+ QualityScaler::Resolution res = qs_.GetScaledResolution(input_frame_);
+ min_dimension = res.width < res.height ? res.width : res.height;
+ --current_shift;
+ ASSERT_EQ(input_frame_.width() >> current_shift, res.width);
+ ASSERT_EQ(input_frame_.height() >> current_shift, res.height);
+ ExpectScaleUsingReportedResolution();
+ }
+
+ // Verify we don't start upscaling after further low use.
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportEncodedFrame(kLowQp);
+ ExpectOriginalFrame();
+ }
+}
+
+TEST_F(QualityScalerTest, ContinuouslyDownscalesByHalfDimensionsAndBackUp) {
+ ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+}
+
+TEST_F(QualityScalerTest,
+ ContinuouslyDownscalesOddResolutionsByHalfDimensionsAndBackUp) {
+ const int kOddWidth = 517;
+ const int kHalfOddWidth = (kOddWidth + 1) / 2;
+ const int kOddHeight = 1239;
+ input_frame_.CreateEmptyFrame(
+ kOddWidth, kOddHeight, kOddWidth, kHalfOddWidth, kHalfOddWidth);
+ ContinuouslyDownscalesByHalfDimensionsAndBackUp();
+}
+
+void QualityScalerTest::DoesNotDownscaleFrameDimensions(int width, int height) {
+ input_frame_.CreateEmptyFrame(
+ width, height, width, (width + 1) / 2, (width + 1) / 2);
+
+ for (int i = 0; i < kFramerate * kNumSeconds; ++i) {
+ qs_.ReportDroppedFrame();
+ ASSERT_EQ(input_frame_.width(), qs_.GetScaledResolution(input_frame_).width)
+ << "Unexpected scale of minimal-size frame.";
+ }
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1PxWidth) {
+ DoesNotDownscaleFrameDimensions(1, kHeight);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1PxHeight) {
+ DoesNotDownscaleFrameDimensions(kWidth, 1);
+}
+
+TEST_F(QualityScalerTest, DoesNotDownscaleFrom1Px) {
+ DoesNotDownscaleFrameDimensions(1, 1);
+}
+
+} // namespace webrtc
diff --git a/webrtc/modules/video_coding/utility/video_coding_utility.gyp b/webrtc/modules/video_coding/utility/video_coding_utility.gyp
index 2f0202b..dc2b33f 100644
--- a/webrtc/modules/video_coding/utility/video_coding_utility.gyp
+++ b/webrtc/modules/video_coding/utility/video_coding_utility.gyp
@@ -18,8 +18,10 @@
'<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers',
],
'sources': [
- 'include/frame_dropper.h',
'frame_dropper.cc',
+ 'include/frame_dropper.h',
+ 'quality_scaler.cc',
+ 'quality_scaler.h',
],
},
], # targets