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:
parent
b5e6bfc76a
commit
a0d7827b16
@ -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',
|
||||
|
@ -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,
|
||||
|
@ -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<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 @@ 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<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 @@ 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;
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
},
|
||||
|
137
webrtc/modules/video_coding/utility/quality_scaler.cc
Normal file
137
webrtc/modules/video_coding/utility/quality_scaler.cc
Normal 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
|
65
webrtc/modules/video_coding/utility/quality_scaler.h
Normal file
65
webrtc/modules/video_coding/utility/quality_scaler.h
Normal 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_
|
198
webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
Normal file
198
webrtc/modules/video_coding/utility/quality_scaler_unittest.cc
Normal 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
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user