blob: 3dd6a8c91a68a3de68f68587922f3227d85657e4 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "garnet/bin/mediaplayer/ffmpeg/ffmpeg_video_frame_layout.h"
#include "garnet/bin/mediaplayer/ffmpeg/av_codec_context.h"
#include "lib/fxl/logging.h"
namespace media_player {
namespace {
constexpr uint32_t kFrameSizeAlignment = 16;
constexpr uint32_t kFrameSizePadding = 16;
// Rounds up |value| to the nearest multiple of |alignment|. |alignment| must
// be a power of 2.
inline uint32_t RoundUpToAlign(uint32_t value, uint32_t alignment) {
return ((value + (alignment - 1)) & ~(alignment - 1));
}
VideoStreamType::Extent FfmpegCommonAlignment(
const VideoStreamType::PixelFormatInfo& info) {
uint32_t max_sample_width = 0;
uint32_t max_sample_height = 0;
for (uint32_t plane = 0; plane < info.plane_count_; ++plane) {
const VideoStreamType::Extent sample_size =
info.sample_size_for_plane(plane);
max_sample_width = std::max(max_sample_width, sample_size.width());
max_sample_height = std::max(max_sample_height, sample_size.height());
}
return VideoStreamType::Extent(max_sample_width, max_sample_height);
}
VideoStreamType::Extent FfmpegAlignedSize(
const VideoStreamType::Extent& unaligned_size,
const VideoStreamType::PixelFormatInfo& info) {
const VideoStreamType::Extent alignment = FfmpegCommonAlignment(info);
const VideoStreamType::Extent adjusted = VideoStreamType::Extent(
RoundUpToAlign(unaligned_size.width(), alignment.width()),
RoundUpToAlign(unaligned_size.height(), alignment.height()));
FXL_DCHECK((adjusted.width() % alignment.width() == 0) &&
(adjusted.height() % alignment.height() == 0));
return adjusted;
}
} // namespace
// static
size_t FfmpegVideoFrameLayout::LayoutFrame(
VideoStreamType::PixelFormat pixel_format,
const VideoStreamType::Extent& coded_size,
std::vector<uint32_t>* line_stride_out,
std::vector<uint32_t>* plane_offset_out, uint32_t* coded_width_out,
uint32_t* coded_height_out) {
FXL_DCHECK(line_stride_out != nullptr);
FXL_DCHECK(plane_offset_out != nullptr);
FXL_DCHECK(coded_width_out != nullptr);
FXL_DCHECK(coded_height_out != nullptr);
const VideoStreamType::PixelFormatInfo& info =
VideoStreamType::InfoForPixelFormat(pixel_format);
line_stride_out->resize(info.plane_count_);
plane_offset_out->resize(info.plane_count_);
VideoStreamType::Extent aligned_size = FfmpegAlignedSize(coded_size, info);
// TODO(dalesat): Redo the alignment to just obey avcodec_align_dimensions2.
// TODO(dalesat): Get rid of superfluous stuff in VideoStreamType.
// Because we're aligning Y, U and V the same amount, we need to double the
// alignment to satisfy U and V, which have half as many bytes per line. This
// isn't necessarily true of all formats, but YV12 is the only one we care
// about. When we switch to using avcodec_align_dimensions2, this nonsense
// will go away.
const uint32_t new_coded_width =
RoundUpToAlign(aligned_size.width(), kFrameSizeAlignment * 2);
// The *4 in alignment for height is because of the YUV thing mentioned above
// and because some formats (e.g. h264) allow interlaced coding, and then the
// size needs to be a multiple of two macroblocks (vertically). See
// avcodec_align_dimensions2.
const uint32_t new_coded_height = RoundUpToAlign(
info.RowCount(0, aligned_size.height()), kFrameSizeAlignment * 4);
// We need to use |LayoutPlaneIndexToYuv| here, because the we want the
// strides and offsets in YUV order, but we calculate them in layout order.
// There's a lengthy explanation of these terms above the declaration of
// |VideoStreamType::PixelFormatInfo::LayoutPlaneIndexToYuv|.
uint32_t plane_offset = 0;
for (uint32_t layout_plane_index = 0; layout_plane_index < info.plane_count_;
++layout_plane_index) {
uint32_t yuv_plane_index = info.LayoutPlaneIndexToYuv(layout_plane_index);
uint32_t line_stride =
info.BytesPerRow(layout_plane_index, new_coded_width);
(*line_stride_out)[yuv_plane_index] = line_stride;
(*plane_offset_out)[yuv_plane_index] = plane_offset;
plane_offset +=
info.RowCount(layout_plane_index, new_coded_height) * line_stride;
}
*coded_width_out = new_coded_width;
*coded_height_out = new_coded_height;
// The extra line of UV being allocated is because h264 chroma MC overreads
// by one line in some cases, see avcodec_align_dimensions2() and
// h264_chromamc.asm:put_h264_chroma_mc4_ssse3().
//
// This is a bit of a hack and is really only here because of ffmpeg-specific
// issues. It works because we happen to know that info.plane_count_ - 1 is
// the U plane for the format we currently care about.
return static_cast<size_t>(plane_offset +
(*line_stride_out)[info.plane_count_ - 1] +
kFrameSizePadding);
}
bool FfmpegVideoFrameLayout::Update(const AVCodecContext& context) {
if (coded_width_ == context.coded_width &&
coded_height_ == context.coded_height &&
pixel_format_ == context.pix_fmt) {
return false;
}
coded_width_ = context.coded_width;
coded_height_ = context.coded_height;
pixel_format_ = context.pix_fmt;
uint32_t adjusted_coded_width_not_used;
uint32_t adjusted_coded_height_not_used;
buffer_size_ =
LayoutFrame(PixelFormatFromAVPixelFormat(pixel_format_),
VideoStreamType::Extent(coded_width_, coded_height_),
&line_stride_, &plane_offset_, &adjusted_coded_width_not_used,
&adjusted_coded_height_not_used);
return true;
}
} // namespace media_player