From c1dba056346c9e01ff129a96bf0ac68a34ebb71e Mon Sep 17 00:00:00 2001 From: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> Date: Wed, 4 Jun 2014 12:02:19 -0400 Subject: [PATCH] video: fix VideoScaler code and add keep aspect feature * Safer scale_and_pad code using libav format description - Support not planar or YUV planar output format * add option to keep input frame aspect (used by mixer and encoder) Refs #49037 Refs #48679 Change-Id: I75a5f79377c50fdbf878153b1e894fd6e0a81fcd --- daemon/src/video/video_encoder.cpp | 11 +++-- daemon/src/video/video_mixer.cpp | 14 +----- daemon/src/video/video_scaler.cpp | 73 +++++++++++++++++++++++++----- daemon/src/video/video_scaler.h | 7 ++- 4 files changed, 75 insertions(+), 30 deletions(-) diff --git a/daemon/src/video/video_encoder.cpp b/daemon/src/video/video_encoder.cpp index 76a27c4370..80fb338623 100644 --- a/daemon/src/video/video_encoder.cpp +++ b/daemon/src/video/video_encoder.cpp @@ -221,10 +221,13 @@ void print_averror(const char *funcname, int err) int VideoEncoder::encode(VideoFrame &input, bool is_keyframe, int64_t frame_number) { - int ret; - AVFrame *frame = scaledFrame_.get(); + /* Prepare a frame suitable to our encoder frame format, + * keeping also the input aspect ratio. + */ + scaledFrame_.clear(); // to fill blank space left by the "keep aspect" + scaler_.scale_with_aspect(input, scaledFrame_); - scaler_.scale(input, scaledFrame_); + AVFrame *frame = scaledFrame_.get(); frame->pts = frame_number; if (is_keyframe) { @@ -245,7 +248,7 @@ int VideoEncoder::encode(VideoFrame &input, bool is_keyframe, int64_t frame_numb #if LIBAVCODEC_VERSION_MAJOR >= 54 int got_packet; - ret = avcodec_encode_video2(encoderCtx_, &pkt, frame, &got_packet); + int ret = avcodec_encode_video2(encoderCtx_, &pkt, frame, &got_packet); if (ret < 0) { print_averror("avcodec_encode_video2", ret); av_free_packet(&pkt); diff --git a/daemon/src/video/video_mixer.cpp b/daemon/src/video/video_mixer.cpp index c5202a63bf..d926382e27 100644 --- a/daemon/src/video/video_mixer.cpp +++ b/daemon/src/video/video_mixer.cpp @@ -163,19 +163,7 @@ void VideoMixer::render_frame(VideoFrame& output, const VideoFrame& input, int xoff = (index % zoom) * cell_width; int yoff = (index / zoom) * cell_height; - /* Corrections to respect input frame ratio */ - const float local_ratio = width_ / height_; - const float input_ratio = input.getWidth() / input.getHeight(); - if (local_ratio >= input_ratio) { - xoff += (cell_width * (1. - input_ratio)) / 2; - cell_width *= input_ratio; - } else { - const int fixed_height = cell_width / input_ratio; - yoff += (cell_height - fixed_height) / 2; - cell_height = fixed_height; - } - - scaler_.scale_and_pad(input, output, xoff, yoff, cell_width, cell_height); + scaler_.scale_and_pad(input, output, xoff, yoff, cell_width, cell_height, true); } void VideoMixer::setDimensions(int width, int height) diff --git a/daemon/src/video/video_scaler.cpp b/daemon/src/video/video_scaler.cpp index 5cb9220349..16f60864fa 100644 --- a/daemon/src/video/video_scaler.cpp +++ b/daemon/src/video/video_scaler.cpp @@ -33,6 +33,8 @@ #include "video_scaler.h" #include "logger.h" +#include <cassert> + namespace sfl_video { VideoScaler::VideoScaler() : ctx_(0), mode_(SWS_FAST_BILINEAR) {} @@ -59,28 +61,62 @@ void VideoScaler::scale(const VideoFrame &input, VideoFrame &output) } sws_scale(ctx_, input_frame->data, input_frame->linesize, 0, - input_frame->height, output_frame->data, output_frame->linesize); + input_frame->height, output_frame->data, + output_frame->linesize); +} + +void VideoScaler::scale_with_aspect(const VideoFrame &input, VideoFrame &output) +{ + AVFrame *output_frame = output.get(); + scale_and_pad(input, output, 0, 0, output_frame->width, + output_frame->height, true); +} + +static inline bool is_yuv_planar(const AVPixFmtDescriptor *desc) +{ + unsigned used_bit_mask = (1u << desc->nb_components) - 1; + + if (not (desc->flags & PIX_FMT_PLANAR) + or desc->flags & PIX_FMT_RGB) + return false; + + /* handle formats that do not use all planes */ + for (unsigned i = 0; i < desc->nb_components; ++i) + used_bit_mask &= ~(1u << desc->comp[i].plane); + + return not used_bit_mask; } void VideoScaler::scale_and_pad(const VideoFrame &input, VideoFrame &output, unsigned xoff, unsigned yoff, - unsigned dest_width, unsigned dest_height) + unsigned dest_width, unsigned dest_height, + bool keep_aspect) { const AVFrame *input_frame = input.get(); AVFrame *output_frame = output.get(); - uint8_t *data[AV_NUM_DATA_POINTERS]; - for (int i = 0; i < AV_NUM_DATA_POINTERS; i++) { - if (output_frame->data[i]) { - const unsigned divisor = i == 0 ? 1 : 2; - unsigned offset = (yoff * output_frame->linesize[i] + xoff) / divisor; - data[i] = output_frame->data[i] + offset; + /* Correct destination width/height and offset if we need to keep input + * frame aspect. + */ + if (keep_aspect) { + const float local_ratio = (float)dest_width / dest_height; + const float input_ratio = (float)input_frame->width / input_frame->height; + + if (local_ratio > input_ratio) { + auto old_dest_width = dest_width; + dest_width = dest_height * input_ratio; + xoff += (old_dest_width - dest_width) / 2; } else { - data[i] = 0; - break; + auto old_dest_heigth = dest_height; + dest_height = dest_width / input_ratio; + yoff += (old_dest_heigth - dest_height) / 2; } } + // buffer overflow checks + assert(xoff + dest_width <= (unsigned)output_frame->width); + assert(yoff + dest_height <= (unsigned)output_frame->height); + ctx_ = sws_getCachedContext(ctx_, input_frame->width, input_frame->height, @@ -95,8 +131,23 @@ void VideoScaler::scale_and_pad(const VideoFrame &input, VideoFrame &output, return; } + // Make an offset'ed copy of output data from xoff and yoff + const AVPixFmtDescriptor *out_desc = av_pix_fmt_desc_get((AVPixelFormat)output_frame->format); + if (is_yuv_planar(out_desc)) { + unsigned x_shift = out_desc->log2_chroma_w; + unsigned y_shift = out_desc->log2_chroma_h; + + tmp_data_[0] = output_frame->data[0] + yoff * output_frame->linesize[0] + xoff; + tmp_data_[1] = output_frame->data[1] + (yoff >> y_shift) * output_frame->linesize[1] + (xoff >> x_shift); + tmp_data_[2] = output_frame->data[2] + (yoff >> y_shift) * output_frame->linesize[2] + (xoff >> x_shift); + tmp_data_[3] = nullptr; + } else { + memcpy(tmp_data_, output_frame->data, sizeof(tmp_data_)); + tmp_data_[0] += yoff * output_frame->linesize[0] + xoff; + } + sws_scale(ctx_, input_frame->data, input_frame->linesize, 0, - input_frame->height, data, output_frame->linesize); + input_frame->height, tmp_data_, output_frame->linesize); } void VideoScaler::reset() diff --git a/daemon/src/video/video_scaler.h b/daemon/src/video/video_scaler.h index cde1c4e0a2..8ff7cca192 100644 --- a/daemon/src/video/video_scaler.h +++ b/daemon/src/video/video_scaler.h @@ -44,16 +44,19 @@ class VideoScaler { public: VideoScaler(); ~VideoScaler(); - void scale(const VideoFrame &input, VideoFrame &output); void reset(); + void scale(const VideoFrame &input, VideoFrame &output); + void scale_with_aspect(const VideoFrame &input, VideoFrame &output); void scale_and_pad(const VideoFrame &input, VideoFrame &output, unsigned xoff, unsigned yoff, - unsigned dest_width, unsigned dest_height); + unsigned dest_width, unsigned dest_height, + bool keep_aspect); private: NON_COPYABLE(VideoScaler); SwsContext *ctx_; int mode_; + uint8_t *tmp_data_[4]; // used by scale_and_pad }; } -- GitLab