Refactor scaling.

Introduce a new method I420Buffer::CropAndScale, and a static
convenience helper I420Buffer::CenterCropAndScale. Use them for almost
all scaling needs.

Delete the Scaler class and the cricket::VideoFrame::Stretch* methods.

BUG=webrtc:5682
R=pbos@webrtc.org, perkj@webrtc.org, stefan@webrtc.org

Review URL: https://codereview.webrtc.org/2020593002 .

Cr-Commit-Position: refs/heads/master@{#13110}
This commit is contained in:
Niels Möller
2016-06-13 13:06:01 +02:00
parent be99ab9356
commit 718a763d59
33 changed files with 396 additions and 1030 deletions

View File

@ -145,15 +145,16 @@ struct FrameEncodeParams {
// We receive I420Frames as input, but we need to feed CVPixelBuffers into the
// encoder. This performs the copy and format conversion.
// TODO(tkchin): See if encoder will accept i420 frames and compare performance.
bool CopyVideoFrameToPixelBuffer(const webrtc::VideoFrame& frame,
CVPixelBufferRef pixel_buffer) {
bool CopyVideoFrameToPixelBuffer(
const rtc::scoped_refptr<webrtc::VideoFrameBuffer>& frame,
CVPixelBufferRef pixel_buffer) {
RTC_DCHECK(pixel_buffer);
RTC_DCHECK(CVPixelBufferGetPixelFormatType(pixel_buffer) ==
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange);
RTC_DCHECK(CVPixelBufferGetHeightOfPlane(pixel_buffer, 0) ==
static_cast<size_t>(frame.height()));
static_cast<size_t>(frame->height()));
RTC_DCHECK(CVPixelBufferGetWidthOfPlane(pixel_buffer, 0) ==
static_cast<size_t>(frame.width()));
static_cast<size_t>(frame->width()));
CVReturn cvRet = CVPixelBufferLockBaseAddress(pixel_buffer, 0);
if (cvRet != kCVReturnSuccess) {
@ -168,14 +169,11 @@ bool CopyVideoFrameToPixelBuffer(const webrtc::VideoFrame& frame,
int dst_stride_uv = CVPixelBufferGetBytesPerRowOfPlane(pixel_buffer, 1);
// Convert I420 to NV12.
int ret = libyuv::I420ToNV12(
frame.video_frame_buffer()->DataY(),
frame.video_frame_buffer()->StrideY(),
frame.video_frame_buffer()->DataU(),
frame.video_frame_buffer()->StrideU(),
frame.video_frame_buffer()->DataV(),
frame.video_frame_buffer()->StrideV(),
frame->DataY(), frame->StrideY(),
frame->DataU(), frame->StrideU(),
frame->DataV(), frame->StrideV(),
dst_y, dst_stride_y, dst_uv, dst_stride_uv,
frame.width(), frame.height());
frame->width(), frame->height());
CVPixelBufferUnlockBaseAddress(pixel_buffer, 0);
if (ret) {
LOG(LS_ERROR) << "Error converting I420 VideoFrame to NV12 :" << ret;
@ -247,11 +245,12 @@ int H264VideoToolboxEncoder::InitEncode(const VideoCodec* codec_settings,
return ResetCompressionSession();
}
const VideoFrame& H264VideoToolboxEncoder::GetScaledFrameOnEncode(
const VideoFrame& frame) {
rtc::scoped_refptr<VideoFrameBuffer>
H264VideoToolboxEncoder::GetScaledBufferOnEncode(
const rtc::scoped_refptr<VideoFrameBuffer>& frame) {
rtc::CritScope lock(&quality_scaler_crit_);
quality_scaler_.OnEncodeFrame(frame);
return quality_scaler_.GetScaledFrame(frame);
quality_scaler_.OnEncodeFrame(frame->width(), frame->height());
return quality_scaler_.GetScaledBuffer(frame);
}
int H264VideoToolboxEncoder::Encode(
@ -270,11 +269,12 @@ int H264VideoToolboxEncoder::Encode(
}
#endif
bool is_keyframe_required = false;
const VideoFrame& input_image = GetScaledFrameOnEncode(frame);
rtc::scoped_refptr<VideoFrameBuffer> input_image(
GetScaledBufferOnEncode(frame.video_frame_buffer()));
if (input_image.width() != width_ || input_image.height() != height_) {
width_ = input_image.width();
height_ = input_image.height();
if (input_image->width() != width_ || input_image->height() != height_) {
width_ = input_image->width();
height_ = input_image->height();
int ret = ResetCompressionSession();
if (ret < 0)
return ret;
@ -327,7 +327,7 @@ int H264VideoToolboxEncoder::Encode(
}
CMTime presentation_time_stamp =
CMTimeMake(input_image.render_time_ms(), 1000);
CMTimeMake(frame.render_time_ms(), 1000);
CFDictionaryRef frame_properties = nullptr;
if (is_keyframe_required) {
CFTypeRef keys[] = {kVTEncodeFrameOptionKey_ForceKeyFrame};
@ -336,8 +336,8 @@ int H264VideoToolboxEncoder::Encode(
}
std::unique_ptr<internal::FrameEncodeParams> encode_params;
encode_params.reset(new internal::FrameEncodeParams(
this, codec_specific_info, width_, height_, input_image.render_time_ms(),
input_image.timestamp(), input_image.rotation()));
this, codec_specific_info, width_, height_, frame.render_time_ms(),
frame.timestamp(), frame.rotation()));
// Update the bitrate if needed.
SetBitrateBps(bitrate_adjuster_.GetAdjustedBitrateBps());

View File

@ -70,7 +70,8 @@ class H264VideoToolboxEncoder : public H264Encoder {
int ResetCompressionSession();
void ConfigureCompressionSession();
void DestroyCompressionSession();
const VideoFrame& GetScaledFrameOnEncode(const VideoFrame& frame);
rtc::scoped_refptr<VideoFrameBuffer> GetScaledBufferOnEncode(
const rtc::scoped_refptr<VideoFrameBuffer>& frame);
void SetBitrateBps(uint32_t bitrate_bps);
void SetEncoderBitrateBps(uint32_t bitrate_bps);

View File

@ -66,8 +66,7 @@ VideoProcessorImpl::VideoProcessorImpl(webrtc::VideoEncoder* encoder,
num_dropped_frames_(0),
num_spatial_resizes_(0),
last_encoder_frame_width_(0),
last_encoder_frame_height_(0),
scaler_() {
last_encoder_frame_height_(0) {
assert(encoder);
assert(decoder);
assert(frame_reader);
@ -335,23 +334,16 @@ void VideoProcessorImpl::FrameDecoded(const VideoFrame& image) {
// upsample back to original size: needed for PSNR and SSIM computations.
if (image.width() != config_.codec_settings->width ||
image.height() != config_.codec_settings->height) {
VideoFrame up_image;
int ret_val = scaler_.Set(
image.width(), image.height(), config_.codec_settings->width,
config_.codec_settings->height, kI420, kI420, kScaleBilinear);
assert(ret_val >= 0);
if (ret_val < 0) {
fprintf(stderr, "Failed to set scalar for frame: %d, return code: %d\n",
frame_number, ret_val);
}
ret_val = scaler_.Scale(image, &up_image);
assert(ret_val >= 0);
if (ret_val < 0) {
fprintf(stderr, "Failed to scale frame: %d, return code: %d\n",
frame_number, ret_val);
}
rtc::scoped_refptr<I420Buffer> up_image(
new rtc::RefCountedObject<I420Buffer>(config_.codec_settings->width,
config_.codec_settings->height));
// Should be the same aspect ratio, no cropping needed.
up_image->ScaleFrom(image.video_frame_buffer());
// TODO(mikhal): Extracting the buffer for now - need to update test.
size_t length = CalcBufferSize(kI420, up_image.width(), up_image.height());
size_t length =
CalcBufferSize(kI420, up_image->width(), up_image->height());
std::unique_ptr<uint8_t[]> image_buffer(new uint8_t[length]);
int extracted_length = ExtractBuffer(up_image, length, image_buffer.get());
assert(extracted_length > 0);

View File

@ -14,7 +14,6 @@
#include <string>
#include "webrtc/base/checks.h"
#include "webrtc/common_video/libyuv/include/scaler.h"
#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/video_coding/include/video_codec_interface.h"
#include "webrtc/modules/video_coding/codecs/test/packet_manipulator.h"
@ -219,7 +218,6 @@ class VideoProcessorImpl : public VideoProcessor {
int num_spatial_resizes_;
int last_encoder_frame_width_;
int last_encoder_frame_height_;
Scaler scaler_;
// Statistics
double bit_rate_factor_; // multiply frame length with this to get bit rate

View File

@ -525,8 +525,8 @@ class VideoProcessorIntegrationTest : public testing::Test {
EXPECT_GT(psnr_result.min, quality_metrics.minimum_min_psnr);
EXPECT_GT(ssim_result.average, quality_metrics.minimum_avg_ssim);
EXPECT_GT(ssim_result.min, quality_metrics.minimum_min_ssim);
if (!remove(config_.output_filename.c_str())) {
fprintf(stderr, "Failed to remove temporary file!");
if (remove(config_.output_filename.c_str()) < 0) {
fprintf(stderr, "Failed to remove temporary file!\n");
}
}
};

View File

@ -729,40 +729,40 @@ int VP8EncoderImpl::Encode(const VideoFrame& frame,
if (encoded_complete_callback_ == NULL)
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
if (quality_scaler_enabled_)
quality_scaler_.OnEncodeFrame(frame);
const VideoFrame& input_image =
quality_scaler_enabled_ ? quality_scaler_.GetScaledFrame(frame) : frame;
rtc::scoped_refptr<VideoFrameBuffer> input_image = frame.video_frame_buffer();
if (quality_scaler_enabled_ && (input_image.width() != codec_.width ||
input_image.height() != codec_.height)) {
int ret = UpdateCodecFrameSize(input_image);
if (ret < 0)
return ret;
if (quality_scaler_enabled_) {
quality_scaler_.OnEncodeFrame(frame.width(), frame.height());
input_image = quality_scaler_.GetScaledBuffer(input_image);
if (input_image->width() != codec_.width ||
input_image->height() != codec_.height) {
int ret =
UpdateCodecFrameSize(input_image->width(), input_image->height());
if (ret < 0)
return ret;
}
}
// Since we are extracting raw pointers from |input_image| to
// |raw_images_[0]|, the resolution of these frames must match. Note that
// |input_image| might be scaled from |frame|. In that case, the resolution of
// |raw_images_[0]| should have been updated in UpdateCodecFrameSize.
RTC_DCHECK_EQ(input_image.width(), static_cast<int>(raw_images_[0].d_w));
RTC_DCHECK_EQ(input_image.height(), static_cast<int>(raw_images_[0].d_h));
RTC_DCHECK_EQ(input_image->width(), static_cast<int>(raw_images_[0].d_w));
RTC_DCHECK_EQ(input_image->height(), static_cast<int>(raw_images_[0].d_h));
// Image in vpx_image_t format.
// Input image is const. VP8's raw image is not defined as const.
raw_images_[0].planes[VPX_PLANE_Y] =
const_cast<uint8_t*>(input_image.video_frame_buffer()->DataY());
const_cast<uint8_t*>(input_image->DataY());
raw_images_[0].planes[VPX_PLANE_U] =
const_cast<uint8_t*>(input_image.video_frame_buffer()->DataU());
const_cast<uint8_t*>(input_image->DataU());
raw_images_[0].planes[VPX_PLANE_V] =
const_cast<uint8_t*>(input_image.video_frame_buffer()->DataV());
const_cast<uint8_t*>(input_image->DataV());
raw_images_[0].stride[VPX_PLANE_Y] =
input_image.video_frame_buffer()->StrideY();
raw_images_[0].stride[VPX_PLANE_U] =
input_image.video_frame_buffer()->StrideU();
raw_images_[0].stride[VPX_PLANE_V] =
input_image.video_frame_buffer()->StrideV();
raw_images_[0].stride[VPX_PLANE_Y] = input_image->StrideY();
raw_images_[0].stride[VPX_PLANE_U] = input_image->StrideU();
raw_images_[0].stride[VPX_PLANE_V] = input_image->StrideV();
for (size_t i = 1; i < encoders_.size(); ++i) {
// Scale the image down a number of times by downsampling factor
@ -781,7 +781,7 @@ int VP8EncoderImpl::Encode(const VideoFrame& frame,
}
vpx_enc_frame_flags_t flags[kMaxSimulcastStreams];
for (size_t i = 0; i < encoders_.size(); ++i) {
int ret = temporal_layers_[i]->EncodeFlags(input_image.timestamp());
int ret = temporal_layers_[i]->EncodeFlags(frame.timestamp());
if (ret < 0) {
// Drop this frame.
return WEBRTC_VIDEO_CODEC_OK;
@ -833,11 +833,11 @@ int VP8EncoderImpl::Encode(const VideoFrame& frame,
rps_.ReceivedRPSI(codec_specific_info->codecSpecific.VP8.pictureIdRPSI);
}
if (codec_specific_info->codecSpecific.VP8.hasReceivedSLI) {
sendRefresh = rps_.ReceivedSLI(input_image.timestamp());
sendRefresh = rps_.ReceivedSLI(frame.timestamp());
}
for (size_t i = 0; i < encoders_.size(); ++i) {
flags[i] = rps_.EncodeFlags(picture_id_[i], sendRefresh,
input_image.timestamp());
frame.timestamp());
}
} else {
if (codec_specific_info->codecSpecific.VP8.hasReceivedRPSI) {
@ -905,17 +905,18 @@ int VP8EncoderImpl::Encode(const VideoFrame& frame,
if (error)
return WEBRTC_VIDEO_CODEC_ERROR;
timestamp_ += duration;
return GetEncodedPartitions(input_image, only_predict_from_key_frame);
// Examines frame timestamps only.
return GetEncodedPartitions(frame, only_predict_from_key_frame);
}
// TODO(pbos): Make sure this works for properly for >1 encoders.
int VP8EncoderImpl::UpdateCodecFrameSize(const VideoFrame& input_image) {
codec_.width = input_image.width();
codec_.height = input_image.height();
int VP8EncoderImpl::UpdateCodecFrameSize(int width, int height) {
codec_.width = width;
codec_.height = height;
if (codec_.numberOfSimulcastStreams <= 1) {
// For now scaling is only used for single-layer streams.
codec_.simulcastStream[0].width = input_image.width();
codec_.simulcastStream[0].height = input_image.height();
codec_.simulcastStream[0].width = width;
codec_.simulcastStream[0].height = height;
}
// Update the cpu_speed setting for resolution change.
vpx_codec_control(&(encoders_[0]), VP8E_SET_CPUUSED,

View File

@ -75,7 +75,7 @@ class VP8EncoderImpl : public VP8Encoder {
int InitAndSetControlSettings();
// Update frame size for codec.
int UpdateCodecFrameSize(const VideoFrame& input_image);
int UpdateCodecFrameSize(int width, int height);
void PopulateCodecSpecific(CodecSpecificInfo* codec_specific,
const vpx_codec_cx_pkt& pkt,