diff --git a/webrtc/modules/modules.gyp b/webrtc/modules/modules.gyp index a1a1bf0de2..f517568726 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 e7b934dc23..bd4a563f63 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 @@ TEST_F(VideoProcessorIntegrationTest, 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 4901edff3d..16d105d1bd 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 @@ int VP8EncoderImpl::SetRates(uint32_t new_bitrate_kbit, 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 @@ int VP8EncoderImpl::InitEncode(const VideoCodec* inst, 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 @@ int VP8EncoderImpl::InitEncode(const VideoCodec* inst, cpu_speed_ = -12; #endif rps_->Init(); + quality_scaler_.Init(codec_.qpMax); + quality_scaler_.ReportFramerate(codec_.maxFramerate); return InitAndSetControlSettings(inst); } @@ -296,6 +299,7 @@ int VP8EncoderImpl::InitAndSetControlSettings(const VideoCodec* inst) { 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 @@ uint32_t VP8EncoderImpl::MaxIntraTarget(uint32_t optimalBuffersize) { 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* 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 @@ int VP8EncoderImpl::Encode(const I420VideoFrame& input_image, 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(input_image.buffer(kYPlane)); - raw_->planes[PLANE_U] = const_cast(input_image.buffer(kUPlane)); - raw_->planes[PLANE_V] = const_cast(input_image.buffer(kVPlane)); + // Input frame is const. VP8's raw frame is not defined as const. + raw_->planes[PLANE_Y] = const_cast(frame.buffer(kYPlane)); + raw_->planes[PLANE_U] = const_cast(frame.buffer(kUPlane)); + raw_->planes[PLANE_V] = const_cast(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 @@ int VP8EncoderImpl::Encode(const I420VideoFrame& input_image, 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 @@ int VP8EncoderImpl::Encode(const I420VideoFrame& input_image, } timestamp_ += duration; - return GetEncodedPartitions(input_image); + return GetEncodedPartitions(frame); } int VP8EncoderImpl::UpdateCodecFrameSize(const I420VideoFrame& input_image) { @@ -480,6 +490,11 @@ int VP8EncoderImpl::GetEncodedPartitions(const I420VideoFrame& input_image) { 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 56f7219fc1..08ce3c9134 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 @@ class VP8EncoderImpl : public VP8Encoder { 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 576524d13e..64cb6024f2 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 0000000000..327748d224 --- /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( + 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(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 0000000000..47d6cb1c5e --- /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 + +#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 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 0000000000..381b959c95 --- /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 2f0202b183..dc2b33f227 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