blob: cf551b2f82783a71251e5faeab72134186cae14e [file] [log] [blame]
// Copyright 2015 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_mjpeg_decode_accelerator.h"
#include <stddef.h>
#include <sys/mman.h>
#include <va/va.h>
#include <array>
#include <utility>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/files/scoped_file.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/page_size.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/checked_math.h"
#include "base/numerics/safe_conversions.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/trace_event.h"
#include "gpu/ipc/common/gpu_memory_buffer_impl.h"
#include "media/base/bind_to_current_loop.h"
#include "media/base/bitstream_buffer.h"
#include "media/base/format_utils.h"
#include "media/base/unaligned_shared_memory.h"
#include "media/base/video_frame.h"
#include "media/base/video_frame_layout.h"
#include "media/base/video_util.h"
#include "media/gpu/chromeos/fourcc.h"
#include "media/gpu/chromeos/libyuv_image_processor_backend.h"
#include "media/gpu/chromeos/platform_video_frame_utils.h"
#include "media/gpu/macros.h"
#include "media/gpu/vaapi/va_surface.h"
#include "media/gpu/vaapi/vaapi_image_decoder.h"
#include "media/gpu/vaapi/vaapi_utils.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/gpu_memory_buffer.h"
namespace media {
namespace {
void ReportToVAJDAResponseToClientUMA(
chromeos_camera::MjpegDecodeAccelerator::Error response) {
UMA_HISTOGRAM_ENUMERATION(
"Media.VAJDA.ResponseToClient", response,
chromeos_camera::MjpegDecodeAccelerator::Error::MJDA_ERROR_CODE_MAX + 1);
}
chromeos_camera::MjpegDecodeAccelerator::Error VaapiJpegDecodeStatusToError(
VaapiImageDecodeStatus status) {
switch (status) {
case VaapiImageDecodeStatus::kSuccess:
return chromeos_camera::MjpegDecodeAccelerator::Error::NO_ERRORS;
case VaapiImageDecodeStatus::kParseFailed:
return chromeos_camera::MjpegDecodeAccelerator::Error::PARSE_JPEG_FAILED;
case VaapiImageDecodeStatus::kUnsupportedSubsampling:
return chromeos_camera::MjpegDecodeAccelerator::Error::UNSUPPORTED_JPEG;
default:
return chromeos_camera::MjpegDecodeAccelerator::Error::PLATFORM_FAILURE;
}
}
bool VerifyDataSize(const VAImage* image) {
const gfx::Size dimensions(base::strict_cast<int>(image->width),
base::strict_cast<int>(image->height));
size_t min_size = 0;
if (image->format.fourcc == VA_FOURCC_I420) {
min_size = VideoFrame::AllocationSize(PIXEL_FORMAT_I420, dimensions);
} else if (image->format.fourcc == VA_FOURCC_NV12) {
min_size = VideoFrame::AllocationSize(PIXEL_FORMAT_NV12, dimensions);
} else if (image->format.fourcc == VA_FOURCC_YUY2 ||
image->format.fourcc == VA_FOURCC('Y', 'U', 'Y', 'V')) {
min_size = VideoFrame::AllocationSize(PIXEL_FORMAT_YUY2, dimensions);
} else {
return false;
}
return base::strict_cast<size_t>(image->data_size) >= min_size;
}
} // namespace
void VaapiMjpegDecodeAccelerator::NotifyError(int32_t task_id, Error error) {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VaapiMjpegDecodeAccelerator::NotifyError,
weak_this_factory_.GetWeakPtr(), task_id, error));
return;
}
VLOGF(1) << "Notifying of error " << error;
// |error| shouldn't be NO_ERRORS because successful decodes should be handled
// by VideoFrameReady().
DCHECK_NE(chromeos_camera::MjpegDecodeAccelerator::Error::NO_ERRORS, error);
ReportToVAJDAResponseToClientUMA(error);
DCHECK(client_);
client_->NotifyError(task_id, error);
}
void VaapiMjpegDecodeAccelerator::VideoFrameReady(int32_t task_id) {
DCHECK(task_runner_->BelongsToCurrentThread());
ReportToVAJDAResponseToClientUMA(
chromeos_camera::MjpegDecodeAccelerator::Error::NO_ERRORS);
client_->VideoFrameReady(task_id);
}
VaapiMjpegDecodeAccelerator::VaapiMjpegDecodeAccelerator(
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
: task_runner_(base::ThreadTaskRunnerHandle::Get()),
io_task_runner_(io_task_runner),
client_(nullptr),
decoder_thread_("VaapiMjpegDecoderThread"),
weak_this_factory_(this) {}
// Some members expect to be destroyed on the |decoder_thread_|.
void VaapiMjpegDecodeAccelerator::CleanUpOnDecoderThread() {
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
DCHECK(vpp_vaapi_wrapper_->HasOneRef());
vpp_vaapi_wrapper_.reset();
decoder_.reset();
image_processor_.reset();
}
VaapiMjpegDecodeAccelerator::~VaapiMjpegDecodeAccelerator() {
DCHECK(task_runner_->BelongsToCurrentThread());
VLOGF(2) << "Destroying VaapiMjpegDecodeAccelerator";
weak_this_factory_.InvalidateWeakPtrs();
if (decoder_task_runner_) {
// base::Unretained() is fine here because we control |decoder_task_runner_|
// lifetime.
decoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VaapiMjpegDecodeAccelerator::CleanUpOnDecoderThread,
base::Unretained(this)));
}
decoder_thread_.Stop();
}
void VaapiMjpegDecodeAccelerator::InitializeOnDecoderTaskRunner(
chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
decoder_ = std::make_unique<media::VaapiJpegDecoder>();
if (!decoder_->Initialize(base::BindRepeating(
&ReportVaapiErrorToUMA,
"Media.VaapiMjpegDecodeAccelerator.VAAPIError"))) {
VLOGF(1) << "Failed initializing |decoder_|";
std::move(init_cb).Run(false);
return;
}
vpp_vaapi_wrapper_ = VaapiWrapper::Create(
VaapiWrapper::kVideoProcess, VAProfileNone,
EncryptionScheme::kUnencrypted,
base::BindRepeating(&ReportVaapiErrorToUMA,
"Media.VaapiMjpegDecodeAccelerator.Vpp.VAAPIError"));
if (!vpp_vaapi_wrapper_) {
VLOGF(1) << "Failed initializing VAAPI for VPP";
std::move(init_cb).Run(false);
return;
}
// Size is irrelevant for a VPP context.
if (!vpp_vaapi_wrapper_->CreateContext(gfx::Size())) {
VLOGF(1) << "Failed to create context for VPP";
std::move(init_cb).Run(false);
return;
}
std::move(init_cb).Run(true);
}
void VaapiMjpegDecodeAccelerator::InitializeOnTaskRunner(
chromeos_camera::MjpegDecodeAccelerator::Client* client,
chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_ = client;
if (!decoder_thread_.Start()) {
VLOGF(1) << "Failed to start decoding thread.";
std::move(init_cb).Run(false);
return;
}
decoder_task_runner_ = decoder_thread_.task_runner();
// base::Unretained() is fine here because we control |decoder_task_runner_|
// lifetime.
decoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&VaapiMjpegDecodeAccelerator::InitializeOnDecoderTaskRunner,
base::Unretained(this), std::move(init_cb)));
}
void VaapiMjpegDecodeAccelerator::InitializeAsync(
chromeos_camera::MjpegDecodeAccelerator::Client* client,
chromeos_camera::MjpegDecodeAccelerator::InitCB init_cb) {
VLOGF(2);
DCHECK(task_runner_->BelongsToCurrentThread());
// To guarantee that the caller receives an asynchronous call after the
// return path, we are making use of InitializeOnTaskRunner.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VaapiMjpegDecodeAccelerator::InitializeOnTaskRunner,
weak_this_factory_.GetWeakPtr(), client,
BindToCurrentLoop(std::move(init_cb))));
}
void VaapiMjpegDecodeAccelerator::CreateImageProcessor(
const VideoFrame* src_frame,
const VideoFrame* dst_frame) {
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
// The fourcc of |src_frame| will be either Fourcc(YUYV) or Fourcc(YU12) based
// on the implementation of OutputPictureLibYuvOnTaskRunner(). The fourcc of
// |dst_frame| should have been validated in DecodeImpl().
const auto src_fourcc = Fourcc::FromVideoPixelFormat(src_frame->format());
DCHECK(src_fourcc.has_value());
const auto dst_fourcc = Fourcc::FromVideoPixelFormat(dst_frame->format());
DCHECK(dst_fourcc.has_value());
const ImageProcessorBackend::PortConfig input_config(
*src_fourcc, src_frame->coded_size(), src_frame->layout().planes(),
src_frame->visible_rect(), {src_frame->storage_type()});
const ImageProcessorBackend::PortConfig output_config(
*dst_fourcc, dst_frame->coded_size(), dst_frame->layout().planes(),
dst_frame->visible_rect(), {dst_frame->storage_type()});
if (image_processor_ && image_processor_->input_config() == input_config &&
image_processor_->output_config() == output_config) {
return;
}
// The error callback is posted to the same thread that
// LibYUVImageProcessorBackend::Create() is called on
// (i.e., |decoder_thread_|) and we control the lifetime of |decoder_thread_|.
// Therefore, base::Unretained(this) is safe.
image_processor_ = LibYUVImageProcessorBackend::Create(
input_config, output_config, {ImageProcessorBackend::OutputMode::IMPORT},
VIDEO_ROTATION_0,
base::BindRepeating(&VaapiMjpegDecodeAccelerator::OnImageProcessorError,
base::Unretained(this)),
decoder_task_runner_);
}
bool VaapiMjpegDecodeAccelerator::OutputPictureLibYuvOnTaskRunner(
int32_t task_id,
std::unique_ptr<ScopedVAImage> scoped_image,
scoped_refptr<VideoFrame> video_frame,
const gfx::Rect& crop_rect) {
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
TRACE_EVENT1("jpeg", __func__, "task_id", task_id);
DCHECK(scoped_image);
const VAImage* image = scoped_image->image();
DCHECK(VerifyDataSize(image));
const gfx::Size src_size(base::strict_cast<int>(image->width),
base::strict_cast<int>(image->height));
DCHECK(gfx::Rect(src_size).Contains(crop_rect));
// Wrap |image| into VideoFrame.
std::vector<int32_t> strides(image->num_planes);
for (uint32_t i = 0; i < image->num_planes; ++i) {
if (!base::CheckedNumeric<uint32_t>(image->pitches[i])
.AssignIfValid(&strides[i])) {
VLOGF(1) << "Invalid VAImage stride " << image->pitches[i]
<< " for plane " << i;
return false;
}
}
auto* const data = static_cast<uint8_t*>(scoped_image->va_buffer()->data());
scoped_refptr<VideoFrame> src_frame;
switch (image->format.fourcc) {
case VA_FOURCC_YUY2:
case VA_FOURCC('Y', 'U', 'Y', 'V'): {
auto layout = VideoFrameLayout::CreateWithStrides(PIXEL_FORMAT_YUY2,
src_size, strides);
if (!layout.has_value()) {
VLOGF(1) << "Failed to create video frame layout";
return false;
}
src_frame = VideoFrame::WrapExternalDataWithLayout(
*layout, crop_rect, crop_rect.size(), data + image->offsets[0],
base::strict_cast<size_t>(image->data_size), base::TimeDelta());
break;
}
case VA_FOURCC_I420: {
auto layout = VideoFrameLayout::CreateWithStrides(PIXEL_FORMAT_I420,
src_size, strides);
if (!layout.has_value()) {
VLOGF(1) << "Failed to create video frame layout";
return false;
}
src_frame = VideoFrame::WrapExternalYuvDataWithLayout(
*layout, crop_rect, crop_rect.size(), data + image->offsets[0],
data + image->offsets[1], data + image->offsets[2],
base::TimeDelta());
break;
}
default:
VLOGF(1) << "Unsupported VA image format: "
<< FourccToString(image->format.fourcc);
return false;
}
if (!src_frame) {
VLOGF(1) << "Failed to create video frame";
return false;
}
CreateImageProcessor(src_frame.get(), video_frame.get());
if (!image_processor_) {
VLOGF(1) << "Failed to create image processor";
return false;
}
image_processor_->Process(
std::move(src_frame), std::move(video_frame),
base::BindOnce(
[](scoped_refptr<base::SingleThreadTaskRunner> runner,
base::OnceClosure cb, scoped_refptr<VideoFrame> frame) {
runner->PostTask(FROM_HERE, std::move(cb));
},
task_runner_,
base::BindOnce(&VaapiMjpegDecodeAccelerator::VideoFrameReady,
weak_this_factory_.GetWeakPtr(), task_id)));
return true;
}
void VaapiMjpegDecodeAccelerator::OnImageProcessorError() {
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
VLOGF(1) << "Failed to process frames using the libyuv image processor";
NotifyError(kInvalidTaskId, PLATFORM_FAILURE);
image_processor_.reset();
}
bool VaapiMjpegDecodeAccelerator::OutputPictureVppOnTaskRunner(
int32_t task_id,
const ScopedVASurface* surface,
scoped_refptr<VideoFrame> video_frame,
const gfx::Rect& crop_rect) {
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
DCHECK(surface);
DCHECK(video_frame);
DCHECK(gfx::Rect(surface->size()).Contains(crop_rect));
TRACE_EVENT1("jpeg", __func__, "task_id", task_id);
scoped_refptr<gfx::NativePixmap> pixmap =
CreateNativePixmapDmaBuf(video_frame.get());
if (!pixmap) {
VLOGF(1) << "Failed to create NativePixmap from VideoFrame";
return false;
}
// Bind a VA surface to |video_frame|.
scoped_refptr<VASurface> output_surface =
vpp_vaapi_wrapper_->CreateVASurfaceForPixmap(std::move(pixmap));
if (!output_surface) {
VLOGF(1) << "Cannot create VA surface for output buffer";
return false;
}
scoped_refptr<VASurface> src_surface = base::MakeRefCounted<VASurface>(
surface->id(), surface->size(), surface->format(),
/*release_cb=*/base::DoNothing());
// We should call vaSyncSurface() when passing surface between contexts. See:
// https://lists.01.org/pipermail/intel-vaapi-media/2019-June/000131.html
if (!vpp_vaapi_wrapper_->SyncSurface(surface->id())) {
VLOGF(1) << "Cannot sync VPP input surface";
return false;
}
if (!vpp_vaapi_wrapper_->BlitSurface(*src_surface, *output_surface,
crop_rect)) {
VLOGF(1) << "Cannot convert decoded image into output buffer";
return false;
}
// Sync target surface since the buffer is returning to client.
if (!vpp_vaapi_wrapper_->SyncSurface(output_surface->id())) {
VLOGF(1) << "Cannot sync VPP output surface";
return false;
}
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiMjpegDecodeAccelerator::VideoFrameReady,
weak_this_factory_.GetWeakPtr(), task_id));
return true;
}
void VaapiMjpegDecodeAccelerator::DecodeFromShmTask(
int32_t task_id,
std::unique_ptr<UnalignedSharedMemory> shm,
scoped_refptr<VideoFrame> dst_frame) {
DVLOGF(4);
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
TRACE_EVENT0("jpeg", __func__);
auto src_image =
base::make_span(static_cast<const uint8_t*>(shm->memory()), shm->size());
DecodeImpl(task_id, src_image, std::move(dst_frame));
}
void VaapiMjpegDecodeAccelerator::DecodeFromDmaBufTask(
int32_t task_id,
base::ScopedFD src_dmabuf_fd,
size_t src_size,
off_t src_offset,
scoped_refptr<VideoFrame> dst_frame) {
DVLOGF(4);
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
TRACE_EVENT0("jpeg", __func__);
// The DMA-buf FD should be mapped as read-only since it may only have read
// permission, e.g. when it comes from camera driver.
DCHECK(src_dmabuf_fd.is_valid());
DCHECK_GT(src_size, 0u);
void* src_addr = mmap(nullptr, src_size, PROT_READ, MAP_SHARED,
src_dmabuf_fd.get(), src_offset);
if (src_addr == MAP_FAILED) {
VPLOGF(1) << "Failed to map input DMA buffer";
NotifyError(task_id, UNREADABLE_INPUT);
return;
}
base::span<const uint8_t> src_image =
base::make_span(static_cast<const uint8_t*>(src_addr), src_size);
DecodeImpl(task_id, src_image, std::move(dst_frame));
const int ret = munmap(src_addr, src_size);
DPCHECK(ret == 0);
}
void VaapiMjpegDecodeAccelerator::DecodeImpl(
int32_t task_id,
base::span<const uint8_t> src_image,
scoped_refptr<VideoFrame> dst_frame) {
VaapiImageDecodeStatus status = decoder_->Decode(src_image);
if (status != VaapiImageDecodeStatus::kSuccess) {
VLOGF(1) << "Failed to decode JPEG image";
NotifyError(task_id, VaapiJpegDecodeStatusToError(status));
return;
}
const ScopedVASurface* surface = decoder_->GetScopedVASurface();
DCHECK(surface);
DCHECK(surface->IsValid());
// For camera captures, we assume that the visible size is the same as the
// coded size.
if (dst_frame->visible_rect().size() != dst_frame->coded_size() ||
dst_frame->visible_rect().x() != 0 ||
dst_frame->visible_rect().y() != 0) {
VLOGF(1)
<< "The video frame visible size should be the same as the coded size";
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
// Note that |surface->size()| is the visible size of the JPEG image. The
// underlying VASurface size (coded size) can be larger because of alignments.
if (surface->size().width() < dst_frame->visible_rect().width() ||
surface->size().height() < dst_frame->visible_rect().height()) {
VLOGF(1) << "Invalid JPEG image and video frame sizes: "
<< surface->size().ToString() << ", "
<< dst_frame->visible_rect().size().ToString();
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
// For DMA-buf backed |dst_frame|, we will import it as a VA surface and use
// VPP to convert the decoded |surface| into it, if the formats and sizes are
// supported.
const auto dst_frame_fourcc =
Fourcc::FromVideoPixelFormat(dst_frame->format());
if (!dst_frame_fourcc) {
VLOGF(1) << "Unsupported video frame format: " << dst_frame->format();
NotifyError(task_id, PLATFORM_FAILURE);
return;
}
const auto dst_frame_va_fourcc = dst_frame_fourcc->ToVAFourCC();
if (!dst_frame_va_fourcc) {
VLOGF(1) << "Unsupported video frame format: " << dst_frame->format();
NotifyError(task_id, PLATFORM_FAILURE);
return;
}
// Crop and scale the decoded image into |dst_frame|.
// The VPP is known to have some problems with odd-sized buffers, so we
// request a crop rectangle whose dimensions are aligned to 2.
const gfx::Rect crop_rect = CropSizeForScalingToTarget(
surface->size(), dst_frame->visible_rect().size(), /*alignment=*/2u);
if (crop_rect.IsEmpty()) {
VLOGF(1) << "Failed to calculate crop rectangle for "
<< surface->size().ToString() << " to "
<< dst_frame->visible_rect().size().ToString();
NotifyError(task_id, PLATFORM_FAILURE);
return;
}
// TODO(kamesan): move HasDmaBufs() to DCHECK when we deprecate
// shared-memory-backed video frame.
// Check all the sizes involved until we figure out the definition of min/max
// resolutions in the VPP profile (b/195312242).
if (dst_frame->HasDmaBufs() &&
VaapiWrapper::IsVppResolutionAllowed(surface->size()) &&
VaapiWrapper::IsVppResolutionAllowed(crop_rect.size()) &&
VaapiWrapper::IsVppResolutionAllowed(dst_frame->visible_rect().size()) &&
VaapiWrapper::IsVppSupportedForJpegDecodedSurfaceToFourCC(
surface->format(), *dst_frame_va_fourcc)) {
if (!OutputPictureVppOnTaskRunner(task_id, surface, std::move(dst_frame),
crop_rect)) {
VLOGF(1) << "Output picture using VPP failed";
NotifyError(task_id, PLATFORM_FAILURE);
}
return;
}
// Fallback to do conversion by libyuv. This happens when:
// 1. |dst_frame| is backed by shared memory.
// 2. VPP doesn't support the format conversion. This is intended for AMD
// VAAPI driver whose VPP only supports converting decoded 4:2:0 JPEGs.
std::unique_ptr<ScopedVAImage> image =
decoder_->GetImage(*dst_frame_va_fourcc, &status);
if (status != VaapiImageDecodeStatus::kSuccess) {
NotifyError(task_id, VaapiJpegDecodeStatusToError(status));
return;
}
DCHECK_EQ(image->image()->width, surface->size().width());
DCHECK_EQ(image->image()->height, surface->size().height());
if (!OutputPictureLibYuvOnTaskRunner(task_id, std::move(image),
std::move(dst_frame), crop_rect)) {
VLOGF(1) << "Output picture using libyuv failed";
NotifyError(task_id, PLATFORM_FAILURE);
}
}
void VaapiMjpegDecodeAccelerator::Decode(
BitstreamBuffer bitstream_buffer,
scoped_refptr<VideoFrame> video_frame) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
TRACE_EVENT1("jpeg", __func__, "input_id", bitstream_buffer.id());
DVLOGF(4) << "Mapping new input buffer id: " << bitstream_buffer.id()
<< " size: " << bitstream_buffer.size();
if (bitstream_buffer.id() < 0) {
VLOGF(1) << "Invalid bitstream_buffer, id: " << bitstream_buffer.id();
NotifyError(bitstream_buffer.id(), INVALID_ARGUMENT);
return;
}
// Validate output video frame.
if (!video_frame->IsMappable() && !video_frame->HasDmaBufs()) {
VLOGF(1) << "Unsupported output frame storage type";
NotifyError(bitstream_buffer.id(), INVALID_ARGUMENT);
return;
}
if ((video_frame->visible_rect().width() & 1) ||
(video_frame->visible_rect().height() & 1)) {
VLOGF(1) << "Video frame visible size has odd dimension";
NotifyError(bitstream_buffer.id(), PLATFORM_FAILURE);
return;
}
// UnalignedSharedMemory will take over the |bitstream_buffer.handle()|.
auto shm = std::make_unique<UnalignedSharedMemory>(
bitstream_buffer.TakeRegion(), bitstream_buffer.size(),
false /* read_only */);
if (!shm->MapAt(bitstream_buffer.offset(), bitstream_buffer.size())) {
VLOGF(1) << "Failed to map input buffer";
NotifyError(bitstream_buffer.id(), UNREADABLE_INPUT);
return;
}
// It's safe to use base::Unretained(this) because |decoder_task_runner_| runs
// tasks on |decoder_thread_| which is stopped in the destructor of |this|.
decoder_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiMjpegDecodeAccelerator::DecodeFromShmTask,
base::Unretained(this), bitstream_buffer.id(),
std::move(shm), std::move(video_frame)));
}
void VaapiMjpegDecodeAccelerator::Decode(int32_t task_id,
base::ScopedFD src_dmabuf_fd,
size_t src_size,
off_t src_offset,
scoped_refptr<VideoFrame> dst_frame) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
TRACE_EVENT1("jpeg", __func__, "task_id", task_id);
if (task_id < 0) {
VLOGF(1) << "Invalid task id: " << task_id;
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
// Validate input arguments.
if (!src_dmabuf_fd.is_valid()) {
VLOGF(1) << "Invalid input buffer FD";
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
if (src_size == 0) {
VLOGF(1) << "Input buffer size is zero";
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
const size_t page_size = base::GetPageSize();
if (src_offset < 0 || src_offset % page_size != 0) {
VLOGF(1) << "Input buffer offset (" << src_offset
<< ") should be non-negative and aligned to page size ("
<< page_size << ")";
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
// Validate output video frame.
if (!dst_frame->IsMappable() && !dst_frame->HasDmaBufs()) {
VLOGF(1) << "Unsupported output frame storage type";
NotifyError(task_id, INVALID_ARGUMENT);
return;
}
if ((dst_frame->visible_rect().width() & 1) ||
(dst_frame->visible_rect().height() & 1)) {
VLOGF(1) << "Output frame visible size has odd dimension";
NotifyError(task_id, PLATFORM_FAILURE);
return;
}
// It's safe to use base::Unretained(this) because |decoder_task_runner_| runs
// tasks on |decoder_thread_| which is stopped in the destructor of |this|.
decoder_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VaapiMjpegDecodeAccelerator::DecodeFromDmaBufTask,
base::Unretained(this), task_id, std::move(src_dmabuf_fd),
src_size, src_offset, std::move(dst_frame)));
}
bool VaapiMjpegDecodeAccelerator::IsSupported() {
return VaapiWrapper::IsDecodeSupported(VAProfileJPEGBaseline);
}
} // namespace media