blob: b83b41aa60d8b3c72b2d4f63f95d3f3434f88d18 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "media/gpu/v4l2/v4l2_vda_helpers.h"
#include "base/bind.h"
#include "media/base/color_plane_layout.h"
#include "media/base/video_codecs.h"
#include "media/gpu/chromeos/fourcc.h"
#include "media/gpu/macros.h"
#include "media/gpu/v4l2/v4l2_device.h"
#include "media/gpu/v4l2/v4l2_image_processor_backend.h"
#include "media/video/h264_parser.h"
namespace media {
namespace v4l2_vda_helpers {
absl::optional<Fourcc> FindImageProcessorInputFormat(V4L2Device* vda_device) {
std::vector<uint32_t> processor_input_formats =
V4L2ImageProcessorBackend::GetSupportedInputFormats();
struct v4l2_fmtdesc fmtdesc;
memset(&fmtdesc, 0, sizeof(fmtdesc));
fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
while (vda_device->Ioctl(VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
if (std::find(processor_input_formats.begin(),
processor_input_formats.end(),
fmtdesc.pixelformat) != processor_input_formats.end()) {
DVLOGF(3) << "Image processor input format=" << fmtdesc.description;
return Fourcc::FromV4L2PixFmt(fmtdesc.pixelformat);
}
++fmtdesc.index;
}
return absl::nullopt;
}
absl::optional<Fourcc> FindImageProcessorOutputFormat(V4L2Device* ip_device) {
// Prefer YVU420 and NV12 because ArcGpuVideoDecodeAccelerator only supports
// single physical plane.
static constexpr uint32_t kPreferredFormats[] = {V4L2_PIX_FMT_NV12,
V4L2_PIX_FMT_YVU420};
auto preferred_formats_first = [](uint32_t a, uint32_t b) -> bool {
auto* iter_a = std::find(std::begin(kPreferredFormats),
std::end(kPreferredFormats), a);
auto* iter_b = std::find(std::begin(kPreferredFormats),
std::end(kPreferredFormats), b);
return iter_a < iter_b;
};
std::vector<uint32_t> processor_output_formats =
V4L2ImageProcessorBackend::GetSupportedOutputFormats();
// Move the preferred formats to the front.
std::sort(processor_output_formats.begin(), processor_output_formats.end(),
preferred_formats_first);
for (uint32_t processor_output_format : processor_output_formats) {
auto fourcc = Fourcc::FromV4L2PixFmt(processor_output_format);
if (fourcc && ip_device->CanCreateEGLImageFrom(*fourcc)) {
DVLOGF(3) << "Image processor output format=" << processor_output_format;
return fourcc;
}
}
return absl::nullopt;
}
std::unique_ptr<ImageProcessor> CreateImageProcessor(
const Fourcc vda_output_format,
const Fourcc ip_output_format,
const gfx::Size& vda_output_coded_size,
const gfx::Size& ip_output_coded_size,
const gfx::Size& visible_size,
VideoFrame::StorageType output_storage_type,
size_t nb_buffers,
scoped_refptr<V4L2Device> image_processor_device,
ImageProcessor::OutputMode image_processor_output_mode,
scoped_refptr<base::SequencedTaskRunner> client_task_runner,
ImageProcessor::ErrorCB error_cb) {
// TODO(crbug.com/917798): Use ImageProcessorFactory::Create() once we remove
// |image_processor_device_| from V4L2VideoDecodeAccelerator.
auto image_processor = ImageProcessor::Create(
base::BindRepeating(&V4L2ImageProcessorBackend::Create,
image_processor_device, nb_buffers),
ImageProcessor::PortConfig(vda_output_format, vda_output_coded_size, {},
gfx::Rect(visible_size),
{VideoFrame::STORAGE_DMABUFS}),
ImageProcessor::PortConfig(ip_output_format, ip_output_coded_size, {},
gfx::Rect(visible_size),
{output_storage_type}),
{image_processor_output_mode}, VIDEO_ROTATION_0, std::move(error_cb),
std::move(client_task_runner));
if (!image_processor)
return nullptr;
if (image_processor->output_config().size != ip_output_coded_size) {
VLOGF(1) << "Image processor should be able to use the requested output "
<< "coded size " << ip_output_coded_size.ToString()
<< " without adjusting to "
<< image_processor->output_config().size.ToString();
return nullptr;
}
if (image_processor->input_config().size != vda_output_coded_size) {
VLOGF(1) << "Image processor should be able to take the output coded "
<< "size of decoder " << vda_output_coded_size.ToString()
<< " without adjusting to "
<< image_processor->input_config().size.ToString();
return nullptr;
}
return image_processor;
}
gfx::Size NativePixmapSizeFromHandle(const gfx::NativePixmapHandle& handle,
const Fourcc fourcc,
const gfx::Size& current_size) {
const uint32_t stride = handle.planes[0].stride;
const uint32_t horiz_bits_per_pixel =
VideoFrame::PlaneHorizontalBitsPerPixel(fourcc.ToVideoPixelFormat(), 0);
DCHECK_NE(horiz_bits_per_pixel, 0u);
// Stride must fit exactly on a byte boundary (8 bits per byte)
DCHECK_EQ((stride * 8) % horiz_bits_per_pixel, 0u);
// Actual width of buffer is stride (in bits) divided by bits per pixel.
int adjusted_coded_width = stride * 8 / horiz_bits_per_pixel;
// If the buffer is multi-planar, then the height of the buffer does not
// matter as long as it covers the visible area and we can just return
// the current height.
// For single-planar however, the actual height can be inferred by dividing
// the start offset of the second plane by the stride of the first plane,
// since the second plane is supposed to start right after the first one.
int adjusted_coded_height =
handle.planes.size() > 1 && handle.planes[1].offset != 0
? handle.planes[1].offset / adjusted_coded_width
: current_size.height();
DCHECK_GE(adjusted_coded_width, current_size.width());
DCHECK_GE(adjusted_coded_height, current_size.height());
return gfx::Size(adjusted_coded_width, adjusted_coded_height);
}
// static
std::unique_ptr<InputBufferFragmentSplitter>
InputBufferFragmentSplitter::CreateFromProfile(
media::VideoCodecProfile profile) {
switch (VideoCodecProfileToVideoCodec(profile)) {
case VideoCodec::kH264:
return std::make_unique<
v4l2_vda_helpers::H264InputBufferFragmentSplitter>();
case VideoCodec::kVP8:
case VideoCodec::kVP9:
// VP8/VP9 don't need any frame splitting, use the default implementation.
return std::make_unique<v4l2_vda_helpers::InputBufferFragmentSplitter>();
default:
LOG(ERROR) << "Unhandled profile: " << profile;
return nullptr;
}
}
bool InputBufferFragmentSplitter::AdvanceFrameFragment(const uint8_t* data,
size_t size,
size_t* endpos) {
*endpos = size;
return true;
}
void InputBufferFragmentSplitter::Reset() {}
bool InputBufferFragmentSplitter::IsPartialFramePending() const {
return false;
}
H264InputBufferFragmentSplitter::H264InputBufferFragmentSplitter()
: h264_parser_(new H264Parser()) {}
H264InputBufferFragmentSplitter::~H264InputBufferFragmentSplitter() = default;
bool H264InputBufferFragmentSplitter::AdvanceFrameFragment(const uint8_t* data,
size_t size,
size_t* endpos) {
DCHECK(h264_parser_);
// For H264, we need to feed HW one frame at a time. This is going to take
// some parsing of our input stream.
h264_parser_->SetStream(data, size);
H264NALU nalu;
H264Parser::Result result;
bool has_frame_data = false;
*endpos = 0;
// Keep on peeking the next NALs while they don't indicate a frame
// boundary.
while (true) {
bool end_of_frame = false;
result = h264_parser_->AdvanceToNextNALU(&nalu);
if (result == H264Parser::kInvalidStream ||
result == H264Parser::kUnsupportedStream) {
return false;
}
if (result == H264Parser::kEOStream) {
// We've reached the end of the buffer before finding a frame boundary.
if (has_frame_data)
partial_frame_pending_ = true;
*endpos = size;
return true;
}
switch (nalu.nal_unit_type) {
case H264NALU::kNonIDRSlice:
case H264NALU::kIDRSlice:
if (nalu.size < 1)
return false;
has_frame_data = true;
// For these two, if the "first_mb_in_slice" field is zero, start a
// new frame and return. This field is Exp-Golomb coded starting on
// the eighth data bit of the NAL; a zero value is encoded with a
// leading '1' bit in the byte, which we can detect as the byte being
// (unsigned) greater than or equal to 0x80.
if (nalu.data[1] >= 0x80) {
end_of_frame = true;
break;
}
break;
case H264NALU::kSEIMessage:
case H264NALU::kSPS:
case H264NALU::kPPS:
case H264NALU::kAUD:
case H264NALU::kEOSeq:
case H264NALU::kEOStream:
case H264NALU::kFiller:
case H264NALU::kSPSExt:
case H264NALU::kReserved14:
case H264NALU::kReserved15:
case H264NALU::kReserved16:
case H264NALU::kReserved17:
case H264NALU::kReserved18:
// These unconditionally signal a frame boundary.
end_of_frame = true;
break;
default:
// For all others, keep going.
break;
}
if (end_of_frame) {
if (!partial_frame_pending_ && *endpos == 0) {
// The frame was previously restarted, and we haven't filled the
// current frame with any contents yet. Start the new frame here and
// continue parsing NALs.
} else {
// The frame wasn't previously restarted and/or we have contents for
// the current frame; signal the start of a new frame here: we don't
// have a partial frame anymore.
partial_frame_pending_ = false;
return true;
}
}
*endpos = (nalu.data + nalu.size) - data;
}
NOTREACHED();
return false;
}
void H264InputBufferFragmentSplitter::Reset() {
partial_frame_pending_ = false;
h264_parser_.reset(new H264Parser());
}
bool H264InputBufferFragmentSplitter::IsPartialFramePending() const {
return partial_frame_pending_;
}
} // namespace v4l2_vda_helpers
} // namespace media