blob: b3abc0851156f4c7a7810f4f202af99ceeb7ebdb [file] [log] [blame]
// Copyright 2018 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/vaapi/vaapi_dmabuf_video_frame_mapper.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "media/base/color_plane_layout.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/macros.h"
#include "media/gpu/vaapi/vaapi_utils.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
namespace media {
namespace {
constexpr VAImageFormat kImageFormatNV12{.fourcc = VA_FOURCC_NV12,
.byte_order = VA_LSB_FIRST,
.bits_per_pixel = 12};
constexpr VAImageFormat kImageFormatP010{.fourcc = VA_FOURCC_P010,
.byte_order = VA_LSB_FIRST,
.bits_per_pixel = 16};
void ConvertP010ToP016LE(const uint16_t* src,
int src_stride,
uint16_t* dst,
int dst_stride,
int width,
int height) {
// The P010 buffer layout is (meaningful 10bits:0) in two bytes like
// ABCDEFGHIJ000000. However, libvpx's output is (0:meaningful 10bits) in two
// bytes like 000000ABCDEFGHIJ. Although the P016LE buffer layout is
// undefined, we locally define the layout as the same as libvpx's layout and
// convert here for testing.
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
constexpr int kShiftBits = 6;
const uint16_t v = src[j];
dst[j] = v >> kShiftBits;
}
src = reinterpret_cast<const uint16_t*>(
reinterpret_cast<const uint8_t*>(src) + src_stride);
dst = reinterpret_cast<uint16_t*>(reinterpret_cast<uint8_t*>(dst) +
dst_stride);
}
}
void DeallocateBuffers(std::unique_ptr<ScopedVAImage> va_image,
scoped_refptr<const VideoFrame> /* video_frame */) {
// The |video_frame| will be released here and it will be returned to pool if
// client uses video frame pool.
// Destructing ScopedVAImage releases its owned memory.
DCHECK(va_image->IsValid());
}
scoped_refptr<VideoFrame> CreateMappedVideoFrame(
scoped_refptr<const VideoFrame> src_video_frame,
std::unique_ptr<ScopedVAImage> va_image) {
DCHECK(va_image);
// ScopedVAImage manages the resource of mapped data. That is, ScopedVAImage's
// dtor releases the mapped resource.
constexpr size_t kNumPlanes = 2u;
DCHECK_EQ(VideoFrame::NumPlanes(src_video_frame->format()), kNumPlanes);
if (va_image->image()->num_planes != kNumPlanes) {
VLOGF(1) << "The number of planes of VAImage is not expected. "
<< "(expected: " << kNumPlanes
<< ", VAImage: " << va_image->image()->num_planes << ")";
return nullptr;
}
// All the planes are stored in the same buffer, VAImage.va_buffer.
std::vector<ColorPlaneLayout> planes(kNumPlanes);
uint8_t* addrs[VideoFrame::kMaxPlanes] = {};
for (size_t i = 0; i < kNumPlanes; i++) {
planes[i].stride = va_image->image()->pitches[i];
planes[i].offset = va_image->image()->offsets[i];
addrs[i] = static_cast<uint8_t*>(va_image->va_buffer()->data()) +
va_image->image()->offsets[i];
}
// The size of each plane is not given by VAImage. We compute the size to be
// mapped from offset and the entire buffer size (data_size).
for (size_t i = 0; i < kNumPlanes; i++) {
if (i < kNumPlanes - 1)
planes[i].size = planes[i + 1].offset - planes[i].offset;
else
planes[i].size = va_image->image()->data_size - planes[i].offset;
}
// Create new buffers and copy P010 buffers into because we should not modify
// va_image->va_buffer->data().
std::vector<std::unique_ptr<uint16_t[]>> p016le_buffers(kNumPlanes);
if (src_video_frame->format() == PIXEL_FORMAT_P016LE) {
for (size_t i = 0; i < kNumPlanes; i++) {
const gfx::Size plane_dimensions_in_bytes = VideoFrame::PlaneSize(
PIXEL_FORMAT_P016LE, i, src_video_frame->visible_rect().size());
const gfx::Size plane_dimensions_in_16bit_words(
plane_dimensions_in_bytes.width() / 2,
plane_dimensions_in_bytes.height());
p016le_buffers[i] = std::make_unique<uint16_t[]>(planes[i].size / 2);
ConvertP010ToP016LE(reinterpret_cast<const uint16_t*>(addrs[i]),
planes[i].stride, p016le_buffers[i].get(),
planes[i].stride,
plane_dimensions_in_16bit_words.width(),
plane_dimensions_in_16bit_words.height());
addrs[i] = reinterpret_cast<uint8_t*>(p016le_buffers[i].get());
}
}
auto mapped_layout = VideoFrameLayout::CreateWithPlanes(
src_video_frame->format(),
gfx::Size(va_image->image()->width, va_image->image()->height),
std::move(planes));
if (!mapped_layout) {
VLOGF(1) << "Failed to create VideoFrameLayout for VAImage";
return nullptr;
}
auto video_frame = VideoFrame::WrapExternalYuvDataWithLayout(
*mapped_layout, src_video_frame->visible_rect(),
src_video_frame->visible_rect().size(), addrs[0], addrs[1], addrs[2],
src_video_frame->timestamp());
if (!video_frame)
return nullptr;
// The source video frame should not be released until the mapped
// |video_frame| is destructed, because |video_frame| holds |va_image|.
video_frame->AddDestructionObserver(base::BindOnce(
DeallocateBuffers, std::move(va_image), std::move(src_video_frame)));
for (auto&& buffer : p016le_buffers) {
video_frame->AddDestructionObserver(
base::BindOnce(base::DoNothing::Once<std::unique_ptr<uint16_t[]>>(),
std::move(buffer)));
}
return video_frame;
}
bool IsFormatSupported(VideoPixelFormat format) {
return format == PIXEL_FORMAT_NV12 || format == PIXEL_FORMAT_P016LE;
}
} // namespace
// static
std::unique_ptr<VideoFrameMapper> VaapiDmaBufVideoFrameMapper::Create(
VideoPixelFormat format) {
if (!IsFormatSupported(format)) {
VLOGF(1) << " Unsupported format: " << VideoPixelFormatToString(format);
return nullptr;
}
auto video_frame_mapper =
base::WrapUnique(new VaapiDmaBufVideoFrameMapper(format));
if (!video_frame_mapper->vaapi_wrapper_)
return nullptr;
return video_frame_mapper;
}
VaapiDmaBufVideoFrameMapper::VaapiDmaBufVideoFrameMapper(
VideoPixelFormat format)
: VideoFrameMapper(format),
vaapi_wrapper_(VaapiWrapper::Create(VaapiWrapper::kVideoProcess,
VAProfileNone,
EncryptionScheme::kUnencrypted,
base::DoNothing())) {}
VaapiDmaBufVideoFrameMapper::~VaapiDmaBufVideoFrameMapper() {}
scoped_refptr<VideoFrame> VaapiDmaBufVideoFrameMapper::Map(
scoped_refptr<const VideoFrame> video_frame) const {
DCHECK(vaapi_wrapper_);
if (!video_frame) {
LOG(ERROR) << "Video frame is nullptr";
return nullptr;
}
if (!video_frame->HasDmaBufs())
return nullptr;
if (video_frame->format() != format_) {
VLOGF(1) << "Unexpected format, got: "
<< VideoPixelFormatToString(video_frame->format())
<< ", expected: " << VideoPixelFormatToString(format_);
return nullptr;
}
scoped_refptr<gfx::NativePixmap> pixmap =
CreateNativePixmapDmaBuf(video_frame.get());
if (!pixmap) {
VLOGF(1) << "Failed to create NativePixmap from VideoFrame";
return nullptr;
}
scoped_refptr<VASurface> va_surface =
vaapi_wrapper_->CreateVASurfaceForPixmap(std::move(pixmap));
if (!va_surface) {
VLOGF(1) << "Failed to create VASurface";
return nullptr;
}
// Map tiled NV12 or P010 buffer by CreateVaImage so that mapped buffers can
// be accessed as non-tiled NV12 or P016LE buffer.
VAImageFormat va_image_format = video_frame->format() == PIXEL_FORMAT_NV12
? kImageFormatNV12
: kImageFormatP010;
auto va_image = vaapi_wrapper_->CreateVaImage(
va_surface->id(), &va_image_format, va_surface->size());
if (!va_image || !va_image->IsValid()) {
VLOGF(1) << "Failed in CreateVaImage.";
return nullptr;
}
return CreateMappedVideoFrame(std::move(video_frame), std::move(va_image));
}
} // namespace media