blob: 8498ba62fdee51a11b87f54b937db23aec787635 [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_jpeg_decode_accelerator.h"
#include <stddef.h>
#include <utility>
#include <va/va.h>
#include "base/bind.h"
#include "base/containers/span.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.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 "media/base/bitstream_buffer.h"
#include "media/base/unaligned_shared_memory.h"
#include "media/base/video_frame.h"
#include "media/base/video_types.h"
#include "media/gpu/macros.h"
#include "media/gpu/vaapi/vaapi_utils.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "third_party/libyuv/include/libyuv.h"
#include "ui/gfx/geometry/size.h"
namespace media {
namespace {
// UMA errors that the VaapiJpegDecodeAccelerator class reports.
enum VAJDADecoderFailure {
VAAPI_ERROR = 0,
VAJDA_DECODER_FAILURES_MAX,
};
static void ReportToUMA(VAJDADecoderFailure failure) {
UMA_HISTOGRAM_ENUMERATION("Media.VAJDA.DecoderFailure", failure,
VAJDA_DECODER_FAILURES_MAX + 1);
}
static JpegDecodeAccelerator::Error VaapiJpegDecodeStatusToError(
VaapiJpegDecodeStatus status) {
switch (status) {
case VaapiJpegDecodeStatus::kSuccess:
return JpegDecodeAccelerator::Error::NO_ERRORS;
case VaapiJpegDecodeStatus::kParseJpegFailed:
return JpegDecodeAccelerator::Error::PARSE_JPEG_FAILED;
case VaapiJpegDecodeStatus::kUnsupportedSubsampling:
return JpegDecodeAccelerator::Error::UNSUPPORTED_JPEG;
default:
return JpegDecodeAccelerator::Error::PLATFORM_FAILURE;
}
}
static 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_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 VaapiJpegDecodeAccelerator::NotifyError(int32_t bitstream_buffer_id,
Error error) {
if (!task_runner_->BelongsToCurrentThread()) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&VaapiJpegDecodeAccelerator::NotifyError,
weak_this_factory_.GetWeakPtr(),
bitstream_buffer_id, error));
return;
}
VLOGF(1) << "Notifying of error " << error;
DCHECK(client_);
client_->NotifyError(bitstream_buffer_id, error);
}
void VaapiJpegDecodeAccelerator::VideoFrameReady(int32_t bitstream_buffer_id) {
DCHECK(task_runner_->BelongsToCurrentThread());
client_->VideoFrameReady(bitstream_buffer_id);
}
VaapiJpegDecodeAccelerator::VaapiJpegDecodeAccelerator(
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
: task_runner_(base::ThreadTaskRunnerHandle::Get()),
io_task_runner_(io_task_runner),
client_(nullptr),
decoder_thread_("VaapiJpegDecoderThread"),
weak_this_factory_(this) {}
VaapiJpegDecodeAccelerator::~VaapiJpegDecodeAccelerator() {
DCHECK(task_runner_->BelongsToCurrentThread());
VLOGF(2) << "Destroying VaapiJpegDecodeAccelerator";
weak_this_factory_.InvalidateWeakPtrs();
decoder_thread_.Stop();
}
bool VaapiJpegDecodeAccelerator::Initialize(Client* client) {
VLOGF(2);
DCHECK(task_runner_->BelongsToCurrentThread());
client_ = client;
if (!decoder_.Initialize(base::BindRepeating(&ReportToUMA, VAAPI_ERROR)))
return false;
if (!decoder_thread_.Start()) {
VLOGF(1) << "Failed to start decoding thread.";
return false;
}
decoder_task_runner_ = decoder_thread_.task_runner();
return true;
}
bool VaapiJpegDecodeAccelerator::OutputPictureOnTaskRunner(
std::unique_ptr<ScopedVAImage> scoped_image,
int32_t input_buffer_id,
const scoped_refptr<VideoFrame>& video_frame) {
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
TRACE_EVENT1("jpeg", "VaapiJpegDecodeAccelerator::OutputPictureOnTaskRunner",
"input_buffer_id", input_buffer_id);
// Copy image content from VAImage to VideoFrame. If the image is not in the
// I420 format we'll have to convert it.
DCHECK(scoped_image);
auto* mem = static_cast<uint8_t*>(scoped_image->va_buffer()->data());
const VAImage* image = scoped_image->image();
DCHECK_GE(base::strict_cast<int>(image->width),
video_frame->coded_size().width());
DCHECK_GE(base::strict_cast<int>(image->height),
video_frame->coded_size().height());
DCHECK(VerifyDataSize(image));
uint8_t* dst_y = video_frame->data(VideoFrame::kYPlane);
uint8_t* dst_u = video_frame->data(VideoFrame::kUPlane);
uint8_t* dst_v = video_frame->data(VideoFrame::kVPlane);
size_t dst_y_stride = video_frame->stride(VideoFrame::kYPlane);
size_t dst_u_stride = video_frame->stride(VideoFrame::kUPlane);
size_t dst_v_stride = video_frame->stride(VideoFrame::kVPlane);
switch (image->format.fourcc) {
case VA_FOURCC_I420: {
DCHECK_EQ(image->num_planes, 3u);
const uint8_t* src_y = mem + image->offsets[0];
const uint8_t* src_u = mem + image->offsets[1];
const uint8_t* src_v = mem + image->offsets[2];
const size_t src_y_stride = image->pitches[0];
const size_t src_u_stride = image->pitches[1];
const size_t src_v_stride = image->pitches[2];
if (libyuv::I420Copy(src_y, src_y_stride, src_u, src_u_stride, src_v,
src_v_stride, dst_y, dst_y_stride, dst_u,
dst_u_stride, dst_v, dst_v_stride,
video_frame->coded_size().width(),
video_frame->coded_size().height())) {
VLOGF(1) << "I420Copy failed";
return false;
}
break;
}
case VA_FOURCC_YUY2:
case VA_FOURCC('Y', 'U', 'Y', 'V'): {
DCHECK_EQ(image->num_planes, 1u);
const uint8_t* src_yuy2 = mem + image->offsets[0];
const size_t src_yuy2_stride = image->pitches[0];
if (libyuv::YUY2ToI420(src_yuy2, src_yuy2_stride, dst_y, dst_y_stride,
dst_u, dst_u_stride, dst_v, dst_v_stride,
video_frame->coded_size().width(),
video_frame->coded_size().height())) {
VLOGF(1) << "YUY2ToI420 failed";
return false;
}
break;
}
default:
VLOGF(1) << "Can't convert image to I420: unsupported format "
<< FourccToString(image->format.fourcc);
return false;
}
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&VaapiJpegDecodeAccelerator::VideoFrameReady,
weak_this_factory_.GetWeakPtr(), input_buffer_id));
return true;
}
void VaapiJpegDecodeAccelerator::DecodeTask(
int32_t bitstream_buffer_id,
std::unique_ptr<UnalignedSharedMemory> shm,
scoped_refptr<VideoFrame> video_frame) {
DVLOGF(4);
DCHECK(decoder_task_runner_->BelongsToCurrentThread());
TRACE_EVENT0("jpeg", "DecodeTask");
VaapiJpegDecodeStatus status;
std::unique_ptr<ScopedVAImage> image = decoder_.DoDecode(
base::make_span<const uint8_t>(static_cast<const uint8_t*>(shm->memory()),
shm->size()),
&status);
if (status != VaapiJpegDecodeStatus::kSuccess) {
NotifyError(bitstream_buffer_id, VaapiJpegDecodeStatusToError(status));
return;
}
if (!OutputPictureOnTaskRunner(std::move(image), bitstream_buffer_id,
video_frame)) {
VLOGF(1) << "Output picture failed";
NotifyError(bitstream_buffer_id, PLATFORM_FAILURE);
}
}
void VaapiJpegDecodeAccelerator::Decode(
const BitstreamBuffer& bitstream_buffer,
const scoped_refptr<VideoFrame>& video_frame) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
TRACE_EVENT1("jpeg", "Decode", "input_id", bitstream_buffer.id());
DVLOGF(4) << "Mapping new input buffer id: " << bitstream_buffer.id()
<< " size: " << bitstream_buffer.size();
// UnalignedSharedMemory will take over the |bitstream_buffer.handle()|.
auto shm = std::make_unique<UnalignedSharedMemory>(
bitstream_buffer.handle(), bitstream_buffer.size(), true);
if (bitstream_buffer.id() < 0) {
VLOGF(1) << "Invalid bitstream_buffer, id: " << bitstream_buffer.id();
NotifyError(bitstream_buffer.id(), INVALID_ARGUMENT);
return;
}
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(&VaapiJpegDecodeAccelerator::DecodeTask,
base::Unretained(this), bitstream_buffer.id(),
std::move(shm), std::move(video_frame)));
}
bool VaapiJpegDecodeAccelerator::IsSupported() {
return VaapiWrapper::IsJpegDecodeSupported();
}
} // namespace media