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
This commit is contained in:
pbos@webrtc.org 2014-09-12 11:51:47 +00:00
parent b5e6bfc76a
commit a0d7827b16
9 changed files with 452 additions and 40 deletions

View File

@ -246,6 +246,7 @@
'video_coding/main/source/qm_select_unittest.cc', 'video_coding/main/source/qm_select_unittest.cc',
'video_coding/main/source/test/stream_generator.cc', 'video_coding/main/source/test/stream_generator.cc',
'video_coding/main/source/test/stream_generator.h', '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/brightness_detection_test.cc',
'video_processing/main/test/unit_test/color_enhancement_test.cc', 'video_processing/main/test/unit_test/color_enhancement_test.cc',
'video_processing/main/test/unit_test/content_metrics_test.cc', 'video_processing/main/test/unit_test/content_metrics_test.cc',

View File

@ -703,34 +703,26 @@ TEST_F(VideoProcessorIntegrationTest,
rc_metrics); rc_metrics);
} }
// Run with no packet loss, at low bitrate, then increase rate somewhat. // Run with no packet loss, at low bitrate. During this time we should've
// Key frame is thrown in every 120 frames. Can expect some frame drops after // resized once.
// 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.
TEST_F(VideoProcessorIntegrationTest, TEST_F(VideoProcessorIntegrationTest,
DISABLED_ON_ANDROID(ProcessNoLossSpatialResizeFrameDrop)) { DISABLED_ON_ANDROID(ProcessNoLossSpatialResizeFrameDrop)) {
config_.networking_config.packet_loss_probability = 0; config_.networking_config.packet_loss_probability = 0;
// Bitrate and frame rate profile. // Bitrate and frame rate profile.
RateProfile rate_profile; RateProfile rate_profile;
SetRateProfilePars(&rate_profile, 0, 100, 30, 0); SetRateProfilePars(&rate_profile, 0, 50, 30, 0);
SetRateProfilePars(&rate_profile, 1, 200, 30, 120); rate_profile.frame_index_rate_update[1] = kNbrFramesLong + 1;
SetRateProfilePars(&rate_profile, 2, 200, 30, 240);
rate_profile.frame_index_rate_update[3] = kNbrFramesLong + 1;
rate_profile.num_frames = kNbrFramesLong; rate_profile.num_frames = kNbrFramesLong;
// Codec/network settings. // Codec/network settings.
CodecConfigPars process_settings; CodecConfigPars process_settings;
SetCodecParameters(&process_settings, 0.0f, 120, 1, false, true, true, true); SetCodecParameters(
// Metrics for expected quality.: lower quality on average from up-sampling &process_settings, 0.0f, kNbrFramesLong, 1, false, true, true, true);
// the down-sampled portion of the run, in case resizer is on. // Metrics for expected quality.
QualityMetrics quality_metrics; 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. // Metrics for rate control.
RateControlMetrics rc_metrics[3]; RateControlMetrics rc_metrics[1];
SetRateControlMetrics(rc_metrics, 0, 45, 30, 75, 20, 70, 0); SetRateControlMetrics(rc_metrics, 0, 160, 60, 120, 20, 70, 1);
SetRateControlMetrics(rc_metrics, 1, 20, 35, 30, 20, 15, 1);
SetRateControlMetrics(rc_metrics, 2, 0, 30, 30, 15, 25, 1);
ProcessFramesAndVerify(quality_metrics, ProcessFramesAndVerify(quality_metrics,
rate_profile, rate_profile,
process_settings, process_settings,

View File

@ -105,6 +105,7 @@ int VP8EncoderImpl::SetRates(uint32_t new_bitrate_kbit,
temporal_layers_->ConfigureBitrates(new_bitrate_kbit, codec_.maxBitrate, temporal_layers_->ConfigureBitrates(new_bitrate_kbit, codec_.maxBitrate,
new_framerate, config_); new_framerate, config_);
codec_.maxFramerate = new_framerate; codec_.maxFramerate = new_framerate;
quality_scaler_.ReportFramerate(new_framerate);
// update encoder context // update encoder context
if (vpx_codec_enc_config_set(encoder_, config_)) { if (vpx_codec_enc_config_set(encoder_, config_)) {
@ -230,8 +231,8 @@ int VP8EncoderImpl::InitEncode(const VideoCodec* inst,
30 : 0; 30 : 0;
config_->rc_end_usage = VPX_CBR; config_->rc_end_usage = VPX_CBR;
config_->g_pass = VPX_RC_ONE_PASS; config_->g_pass = VPX_RC_ONE_PASS;
config_->rc_resize_allowed = inst->codecSpecific.VP8.automaticResizeOn ? // Handle resizing outside of libvpx.
1 : 0; config_->rc_resize_allowed = 0;
config_->rc_min_quantizer = 2; config_->rc_min_quantizer = 2;
config_->rc_max_quantizer = inst->qpMax; config_->rc_max_quantizer = inst->qpMax;
config_->rc_undershoot_pct = 100; config_->rc_undershoot_pct = 100;
@ -272,6 +273,8 @@ int VP8EncoderImpl::InitEncode(const VideoCodec* inst,
cpu_speed_ = -12; cpu_speed_ = -12;
#endif #endif
rps_->Init(); rps_->Init();
quality_scaler_.Init(codec_.qpMax);
quality_scaler_.ReportFramerate(codec_.maxFramerate);
return InitAndSetControlSettings(inst); return InitAndSetControlSettings(inst);
} }
@ -296,6 +299,7 @@ int VP8EncoderImpl::InitAndSetControlSettings(const VideoCodec* inst) {
vpx_codec_control(encoder_, VP8E_SET_MAX_INTRA_BITRATE_PCT, vpx_codec_control(encoder_, VP8E_SET_MAX_INTRA_BITRATE_PCT,
rc_max_intra_target_); rc_max_intra_target_);
inited_ = true; inited_ = true;
return WEBRTC_VIDEO_CODEC_OK; return WEBRTC_VIDEO_CODEC_OK;
} }
@ -315,15 +319,15 @@ uint32_t VP8EncoderImpl::MaxIntraTarget(uint32_t optimalBuffersize) {
return (targetPct < minIntraTh) ? minIntraTh: targetPct; 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 CodecSpecificInfo* codec_specific_info,
const std::vector<VideoFrameType>* frame_types) { 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_) { if (!inited_) {
return WEBRTC_VIDEO_CODEC_UNINITIALIZED; return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
} }
if (input_image.IsZeroSize()) { if (input_frame.IsZeroSize()) {
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER; return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
} }
if (encoded_complete_callback_ == NULL) { if (encoded_complete_callback_ == NULL) {
@ -336,25 +340,31 @@ int VP8EncoderImpl::Encode(const I420VideoFrame& input_image,
frame_type = (*frame_types)[0]; 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. // Check for change in frame size.
if (input_image.width() != codec_.width || if (frame.width() != codec_.width ||
input_image.height() != codec_.height) { frame.height() != codec_.height) {
int ret = UpdateCodecFrameSize(input_image); int ret = UpdateCodecFrameSize(frame);
if (ret < 0) { if (ret < 0) {
return ret; return ret;
} }
} }
// Image in vpx_image_t format. // Image in vpx_image_t format.
// Input image is const. VP8's raw image is not defined as const. // Input frame is const. VP8's raw frame is not defined as const.
raw_->planes[PLANE_Y] = const_cast<uint8_t*>(input_image.buffer(kYPlane)); raw_->planes[PLANE_Y] = const_cast<uint8_t*>(frame.buffer(kYPlane));
raw_->planes[PLANE_U] = const_cast<uint8_t*>(input_image.buffer(kUPlane)); raw_->planes[PLANE_U] = const_cast<uint8_t*>(frame.buffer(kUPlane));
raw_->planes[PLANE_V] = const_cast<uint8_t*>(input_image.buffer(kVPlane)); raw_->planes[PLANE_V] = const_cast<uint8_t*>(frame.buffer(kVPlane));
// TODO(mikhal): Stride should be set in initialization. // TODO(mikhal): Stride should be set in initialization.
raw_->stride[VPX_PLANE_Y] = input_image.stride(kYPlane); raw_->stride[VPX_PLANE_Y] = frame.stride(kYPlane);
raw_->stride[VPX_PLANE_U] = input_image.stride(kUPlane); raw_->stride[VPX_PLANE_U] = frame.stride(kUPlane);
raw_->stride[VPX_PLANE_V] = input_image.stride(kVPlane); 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); bool send_keyframe = (frame_type == kKeyFrame);
if (send_keyframe) { if (send_keyframe) {
@ -370,11 +380,11 @@ int VP8EncoderImpl::Encode(const I420VideoFrame& input_image,
codec_specific_info->codecSpecific.VP8.pictureIdRPSI); codec_specific_info->codecSpecific.VP8.pictureIdRPSI);
} }
if (codec_specific_info->codecSpecific.VP8.hasReceivedSLI) { if (codec_specific_info->codecSpecific.VP8.hasReceivedSLI) {
sendRefresh = rps_->ReceivedSLI(input_image.timestamp()); sendRefresh = rps_->ReceivedSLI(frame.timestamp());
} }
} }
flags = rps_->EncodeFlags(picture_id_, sendRefresh, flags = rps_->EncodeFlags(picture_id_, sendRefresh,
input_image.timestamp()); frame.timestamp());
} }
// TODO(holmer): Ideally the duration should be the timestamp diff of this // 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; timestamp_ += duration;
return GetEncodedPartitions(input_image); return GetEncodedPartitions(frame);
} }
int VP8EncoderImpl::UpdateCodecFrameSize(const I420VideoFrame& input_image) { int VP8EncoderImpl::UpdateCodecFrameSize(const I420VideoFrame& input_image) {
@ -480,6 +490,11 @@ int VP8EncoderImpl::GetEncodedPartitions(const I420VideoFrame& input_image) {
encoded_image_._encodedWidth = codec_.width; encoded_image_._encodedWidth = codec_.width;
encoded_complete_callback_->Encoded(encoded_image_, &codec_specific, encoded_complete_callback_->Encoded(encoded_image_, &codec_specific,
&frag_info); &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; return WEBRTC_VIDEO_CODEC_OK;
} }

View File

@ -14,6 +14,7 @@
#define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_IMPL_H_ #define WEBRTC_MODULES_VIDEO_CODING_CODECS_VP8_IMPL_H_
#include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h" #include "webrtc/modules/video_coding/codecs/vp8/include/vp8.h"
#include "webrtc/modules/video_coding/utility/quality_scaler.h"
// VPX forward declaration // VPX forward declaration
typedef struct vpx_codec_ctx vpx_codec_ctx_t; typedef struct vpx_codec_ctx vpx_codec_ctx_t;
@ -139,6 +140,7 @@ class VP8EncoderImpl : public VP8Encoder {
vpx_codec_ctx_t* encoder_; vpx_codec_ctx_t* encoder_;
vpx_codec_enc_cfg_t* config_; vpx_codec_enc_cfg_t* config_;
vpx_image_t* raw_; vpx_image_t* raw_;
QualityScaler quality_scaler_;
}; // end of VP8Encoder class }; // end of VP8Encoder class

View File

@ -44,8 +44,8 @@
'../test/codec_database_test.cc', '../test/codec_database_test.cc',
'../test/generic_codec_test.cc', '../test/generic_codec_test.cc',
'../test/media_opt_test.cc', '../test/media_opt_test.cc',
'../test/mt_test_common.cc',
'../test/mt_rx_tx_test.cc', '../test/mt_rx_tx_test.cc',
'../test/mt_test_common.cc',
'../test/normal_test.cc', '../test/normal_test.cc',
'../test/quality_modes_test.cc', '../test/quality_modes_test.cc',
'../test/rtp_player.cc', '../test/rtp_player.cc',
@ -53,8 +53,8 @@
'../test/test_util.cc', '../test/test_util.cc',
'../test/tester_main.cc', '../test/tester_main.cc',
'../test/vcm_payload_sink_factory.cc', '../test/vcm_payload_sink_factory.cc',
'../test/video_rtp_play_mt.cc',
'../test/video_rtp_play.cc', '../test/video_rtp_play.cc',
'../test/video_rtp_play_mt.cc',
'../test/video_source.cc', '../test/video_source.cc',
], # sources ], # sources
}, },

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -18,8 +18,10 @@
'<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers', '<(webrtc_root)/system_wrappers/source/system_wrappers.gyp:system_wrappers',
], ],
'sources': [ 'sources': [
'include/frame_dropper.h',
'frame_dropper.cc', 'frame_dropper.cc',
'include/frame_dropper.h',
'quality_scaler.cc',
'quality_scaler.h',
], ],
}, },
], # targets ], # targets