blob: d8f7a8a895675c747468b9fdb508aced041afa0c [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "arc/vm/libvda/decode/gpu/gpu_vda_impl.h"
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/callback.h>
#include <base/files/file_util.h>
#include <base/memory/ref_counted.h>
#include <base/single_thread_task_runner.h>
#include <base/stl_util.h>
#include <base/synchronization/waitable_event.h>
#include <chromeos/dbus/service_constants.h>
#include <dbus/bus.h>
#include <dbus/message.h>
#include <dbus/object_path.h>
#include <dbus/object_proxy.h>
#include <mojo/public/cpp/bindings/binding.h>
#include <mojo/public/cpp/system/platform_handle.h>
#include <sys/eventfd.h>
#include "arc/vm/libvda/gbm_util.h"
#include "arc/vm/libvda/gpu/format_util.h"
#include "arc/vm/libvda/gpu/mojom/video_decode_accelerator.mojom.h"
namespace arc {
namespace {
// TODO(alexlau): Query for this instead of hard coding.
constexpr vda_input_format_t kInputFormats[] = {
{VP8PROFILE_MIN /* profile */, 2 /* min_width */, 2 /* min_height */,
1920 /* max_width */, 1080 /* max_height */},
{VP9PROFILE_PROFILE0 /* profile */, 2 /* min_width */, 2 /* min_height */,
1920 /* max_width */, 1080 /* max_height */},
{H264PROFILE_MAIN /* profile */, 2 /* min_width */, 2 /* min_height */,
1920 /* max_width */, 1080 /* max_height */}};
inline vda_result_t ConvertResult(
arc::mojom::VideoDecodeAccelerator::Result error) {
switch (error) {
case arc::mojom::VideoDecodeAccelerator::Result::SUCCESS:
return SUCCESS;
case arc::mojom::VideoDecodeAccelerator::Result::ILLEGAL_STATE:
return ILLEGAL_STATE;
case arc::mojom::VideoDecodeAccelerator::Result::INVALID_ARGUMENT:
return INVALID_ARGUMENT;
case arc::mojom::VideoDecodeAccelerator::Result::UNREADABLE_INPUT:
return UNREADABLE_INPUT;
case arc::mojom::VideoDecodeAccelerator::Result::PLATFORM_FAILURE:
return PLATFORM_FAILURE;
case arc::mojom::VideoDecodeAccelerator::Result::INSUFFICIENT_RESOURCES:
return INSUFFICIENT_RESOURCES;
case arc::mojom::VideoDecodeAccelerator::Result::CANCELLED:
return CANCELLED;
default:
DLOG(ERROR) << "Unknown error code: " << error;
return PLATFORM_FAILURE;
}
}
inline arc::mojom::HalPixelFormat ConvertPixelFormatToHalPixelFormat(
vda_pixel_format_t format) {
switch (format) {
case YV12:
return arc::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YV12;
case NV12:
return arc::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_NV12;
default:
NOTREACHED();
}
}
bool CheckValidOutputFormat(vda_pixel_format_t format, size_t num_planes) {
switch (format) {
case NV12:
if (num_planes != 2) {
LOG(ERROR) << "Invalid number of planes for NV12 format, expected 2 "
"but received "
<< num_planes;
return false;
}
break;
case YV12:
if (num_planes != 3) {
LOG(ERROR) << "Invalid number of planes for YV12 format, expected 3 "
"but received "
<< num_planes;
return false;
}
break;
default:
LOG(WARNING) << "Unexpected format: " << format;
return false;
}
return true;
}
// GpuVdaContext is the GPU decode session context created by GpuVdaImpl which
// handles all mojo VideoDecodeClient invocations and callbacks.
class GpuVdaContext : public VdaContext, arc::mojom::VideoDecodeClient {
public:
// Create a new GpuVdaContext. Must be called on |ipc_task_runner|.
GpuVdaContext(
const scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
arc::mojom::VideoDecodeAcceleratorPtr vda_ptr);
GpuVdaContext(const GpuVdaContext&) = delete;
GpuVdaContext& operator=(const GpuVdaContext&) = delete;
~GpuVdaContext();
using InitializeCallback = base::OnceCallback<void(vda_result_t)>;
// Initializes the VDA context object. When complete, callback is called with
// the result. Must be called on |ipc_task_runner_|.
void Initialize(vda_profile_t profile, InitializeCallback callback);
// VdaContext overrides.
vda_result_t Decode(int32_t bitstream_id,
base::ScopedFD fd,
uint32_t offset,
uint32_t bytes_used) override;
vda_result_t SetOutputBufferCount(size_t num_output_buffers) override;
vda_result_t UseOutputBuffer(int32_t picture_buffer_id,
vda_pixel_format_t format,
base::ScopedFD fd,
size_t num_planes,
video_frame_plane_t* planes) override;
vda_result_t ReuseOutputBuffer(int32_t picture_buffer_id) override;
vda_result_t Reset() override;
vda_result_t Flush() override;
// arc::mojom::VideoDecodeClient overrides.
void ProvidePictureBuffers(arc::mojom::PictureBufferFormatPtr format_ptr,
arc::mojom::RectPtr visible_rect_ptr) override;
void ProvidePictureBuffersDeprecated(
arc::mojom::PictureBufferFormatPtr format_ptr) override;
void PictureReady(arc::mojom::PicturePtr) override;
void NotifyError(arc::mojom::VideoDecodeAccelerator::Result error) override;
void NotifyEndOfBitstreamBuffer(int32_t bitstream_id) override;
private:
// Callback invoked when VideoDecodeAcceleratorPtr::Initialize completes.
void OnInitialized(InitializeCallback callback,
arc::mojom::VideoDecodeAccelerator::Result result);
// Callback for VideoDecodeAcceleratorPtr connection errors.
void OnVdaError(uint32_t custom_reason, const std::string& description);
// Callback for VideoDecodeClientPtr connection errors.
void OnVdaClientError(uint32_t custom_reason, const std::string& description);
// Callback invoked when VideoDecodeAcceleratorPtr::Reset completes.
void OnResetDone(arc::mojom::VideoDecodeAccelerator::Result result);
// Callback invoked when VideoDecodeAcceleratorPtr::Flush completes.
void OnFlushDone(arc::mojom::VideoDecodeAccelerator::Result result);
// Executes a decode. Called on |ipc_task_runner_|.
void DecodeOnIpcThread(int32_t bitstream_id,
base::ScopedFD fd,
uint32_t offset,
uint32_t bytes_used);
// Handles a SetOutputBuffer request by invoking a VideoDecodeAccelerator mojo
// function. Called on |ipc_task_runner_|.
void SetOutputBufferCountOnIpcThread(size_t num_output_buffers);
// Handles a UseOutputBuffer request by invoking a VideoDecodeAccelerator mojo
// function. Called on |ipc_task_runner_|.
void UseOutputBufferOnIpcThread(int32_t picture_buffer_id,
vda_pixel_format_t format,
base::ScopedFD fd,
std::vector<video_frame_plane_t> planes);
// Handles a ReuseOutputBuffer request by invoking a VideoDecodeAccelerator
// mojo function. Called on |ipc_task_runner_|.
void ReuseOutputBufferOnIpcThread(int32_t picture_buffer_id);
// Handles a Reset request by invoking a VideoDecodeAccelerator mojo function.
// Called on |ipc_task_runner_|.
void ResetOnIpcThread();
// Handles a SetOutputBuffer request by invoking a VideoDecodeAccelerator mojo
// function. Called on |ipc_task_runner_|.
void FlushOnIpcThread();
scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner_;
// TODO(alexlau): Use THREAD_CHECKER macro after libchrome uprev
// (crbug.com/909719).
base::ThreadChecker ipc_thread_checker_;
arc::mojom::VideoDecodeAcceleratorPtr vda_ptr_;
mojo::Binding<arc::mojom::VideoDecodeClient> binding_;
std::set<int32_t> decoding_bitstream_ids_;
};
GpuVdaContext::GpuVdaContext(
const scoped_refptr<base::SingleThreadTaskRunner> ipc_task_runner,
arc::mojom::VideoDecodeAcceleratorPtr vda_ptr)
: ipc_task_runner_(std::move(ipc_task_runner)),
vda_ptr_(std::move(vda_ptr)),
binding_(this) {
// Since ipc_thread_checker_ binds to whichever thread it's created on, check
// that we're on the correct thread first using BelongsToCurrentThread.
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
vda_ptr_.set_connection_error_with_reason_handler(
base::BindRepeating(&GpuVdaContext::OnVdaError, base::Unretained(this)));
DLOG(INFO) << "Created new GPU context";
}
void GpuVdaContext::Initialize(vda_profile_t profile,
InitializeCallback callback) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
arc::mojom::VideoDecodeClientPtr client_ptr;
binding_.Bind(mojo::MakeRequest(&client_ptr));
binding_.set_connection_error_with_reason_handler(base::BindRepeating(
&GpuVdaContext::OnVdaClientError, base::Unretained(this)));
arc::mojom::VideoDecodeAcceleratorConfigPtr config =
arc::mojom::VideoDecodeAcceleratorConfig::New();
// TODO(alexlau): Think about how to specify secure mode dynamically.
config->secure_mode = false;
config->profile = ConvertCodecProfileToMojoProfile(profile);
vda_ptr_->Initialize(
std::move(config), std::move(client_ptr),
base::BindRepeating(&GpuVdaContext::OnInitialized, base::Unretained(this),
base::Passed(std::move(callback))));
}
void GpuVdaContext::OnInitialized(
InitializeCallback callback,
arc::mojom::VideoDecodeAccelerator::Result result) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
std::move(callback).Run(ConvertResult(result));
}
GpuVdaContext::~GpuVdaContext() {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
}
void GpuVdaContext::OnVdaError(uint32_t custom_reason,
const std::string& description) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
DLOG(ERROR) << "VideoDecodeAccelerator mojo connection error. custom_reason="
<< custom_reason << " description=" << description;
}
void GpuVdaContext::OnVdaClientError(uint32_t custom_reason,
const std::string& description) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
DLOG(ERROR) << "VideoDecodeClient mojo connection error. custom_reason="
<< custom_reason << " description=" << description;
}
vda_result_t GpuVdaContext::Decode(int32_t bitstream_id,
base::ScopedFD fd,
uint32_t offset,
uint32_t bytes_used) {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&GpuVdaContext::DecodeOnIpcThread, base::Unretained(this),
bitstream_id, std::move(fd), offset, bytes_used));
return SUCCESS;
}
vda_result_t GpuVdaContext::SetOutputBufferCount(size_t num_output_buffers) {
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&GpuVdaContext::SetOutputBufferCountOnIpcThread,
base::Unretained(this), num_output_buffers));
return SUCCESS;
}
void GpuVdaContext::SetOutputBufferCountOnIpcThread(size_t num_output_buffers) {
vda_ptr_->AssignPictureBuffers(num_output_buffers);
}
void GpuVdaContext::DecodeOnIpcThread(int32_t bitstream_id,
base::ScopedFD fd,
uint32_t offset,
uint32_t bytes_used) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
mojo::ScopedHandle handle_fd = mojo::WrapPlatformFile(std::move(fd));
if (!handle_fd.is_valid()) {
LOG(ERROR) << "Invalid bitstream handle.";
return;
}
decoding_bitstream_ids_.insert(bitstream_id);
arc::mojom::BitstreamBufferPtr buf = arc::mojom::BitstreamBuffer::New();
buf->bitstream_id = bitstream_id;
buf->handle_fd = std::move(handle_fd);
buf->offset = offset;
buf->bytes_used = bytes_used;
vda_ptr_->Decode(std::move(buf));
}
vda_result_t GpuVdaContext::UseOutputBuffer(int32_t picture_buffer_id,
vda_pixel_format_t format,
base::ScopedFD fd,
size_t num_planes,
video_frame_plane_t* planes) {
if (!CheckValidOutputFormat(format, num_planes))
return INVALID_ARGUMENT;
// Move semantics don't seem to work with mojo pointers so copy the
// video_frame_plane_t objects and handle in the ipc thread. This allows
// |planes| ownership to be retained by the user.
std::vector<video_frame_plane_t> planes_vector(planes, planes + num_planes);
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&GpuVdaContext::UseOutputBufferOnIpcThread,
base::Unretained(this), picture_buffer_id, format,
std::move(fd), std::move(planes_vector)));
return SUCCESS;
}
void GpuVdaContext::UseOutputBufferOnIpcThread(
int32_t picture_buffer_id,
vda_pixel_format_t format,
base::ScopedFD fd,
std::vector<video_frame_plane_t> planes) {
mojo::ScopedHandle handle_fd = mojo::WrapPlatformFile(std::move(fd));
if (!handle_fd.is_valid()) {
LOG(ERROR) << "Invalid output buffer handle.";
return;
}
std::vector<arc::mojom::VideoFramePlanePtr> mojo_planes;
for (const auto& plane : planes) {
arc::mojom::VideoFramePlanePtr mojo_plane =
arc::mojom::VideoFramePlane::New();
mojo_plane->offset = plane.offset;
mojo_plane->stride = plane.stride;
mojo_planes.push_back(std::move(mojo_plane));
}
vda_ptr_->ImportBufferForPicture(
picture_buffer_id, ConvertPixelFormatToHalPixelFormat(format),
std::move(handle_fd), std::move(mojo_planes));
}
vda_result_t GpuVdaContext::ReuseOutputBuffer(int32_t picture_buffer_id) {
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&GpuVdaContext::ReuseOutputBufferOnIpcThread,
base::Unretained(this), picture_buffer_id));
return SUCCESS;
}
void GpuVdaContext::ReuseOutputBufferOnIpcThread(int32_t picture_buffer_id) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
vda_ptr_->ReusePictureBuffer(picture_buffer_id);
}
vda_result GpuVdaContext::Reset() {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&GpuVdaContext::ResetOnIpcThread, base::Unretained(this)));
return SUCCESS;
}
void GpuVdaContext::ResetOnIpcThread() {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
vda_ptr_->Reset(
base::BindRepeating(&GpuVdaContext::OnResetDone, base::Unretained(this)));
}
void GpuVdaContext::OnResetDone(
arc::mojom::VideoDecodeAccelerator::Result result) {
DispatchResetResponse(ConvertResult(result));
}
vda_result GpuVdaContext::Flush() {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&GpuVdaContext::FlushOnIpcThread, base::Unretained(this)));
return SUCCESS;
}
void GpuVdaContext::FlushOnIpcThread() {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
vda_ptr_->Flush(
base::BindRepeating(&GpuVdaContext::OnFlushDone, base::Unretained(this)));
}
void GpuVdaContext::OnFlushDone(
arc::mojom::VideoDecodeAccelerator::Result result) {
DispatchFlushResponse(ConvertResult(result));
}
// VideoDecodeClient implementation function.
void GpuVdaContext::ProvidePictureBuffers(
arc::mojom::PictureBufferFormatPtr format_ptr,
arc::mojom::RectPtr visible_rect_ptr) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
DispatchProvidePictureBuffers(
format_ptr->min_num_buffers, format_ptr->coded_size->width,
format_ptr->coded_size->height, visible_rect_ptr->left,
visible_rect_ptr->top, visible_rect_ptr->right, visible_rect_ptr->bottom);
}
void GpuVdaContext::ProvidePictureBuffersDeprecated(
arc::mojom::PictureBufferFormatPtr format_ptr) {
NOTIMPLEMENTED();
}
// VideoDecodeClient implementation function.
void GpuVdaContext::PictureReady(arc::mojom::PicturePtr picture_ptr) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
DispatchPictureReady(
picture_ptr->picture_buffer_id, picture_ptr->bitstream_id,
picture_ptr->crop_rect->left, picture_ptr->crop_rect->top,
picture_ptr->crop_rect->right, picture_ptr->crop_rect->bottom);
}
// VideoDecodeClient implementation function.
void GpuVdaContext::NotifyError(
arc::mojom::VideoDecodeAccelerator::Result error) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
DispatchNotifyError(ConvertResult(error));
}
// VideoDecodeClient implementation function.
void GpuVdaContext::NotifyEndOfBitstreamBuffer(int32_t bitstream_id) {
DCHECK(ipc_thread_checker_.CalledOnValidThread());
DispatchNotifyEndOfBitstreamBuffer(bitstream_id);
if (decoding_bitstream_ids_.erase(bitstream_id) == 0) {
LOG(ERROR) << "Could not find bitstream id: " << bitstream_id;
return;
}
}
} // namespace
// static
GpuVdaImpl* GpuVdaImpl::Create(VafConnection* conn) {
auto impl = std::make_unique<GpuVdaImpl>(conn);
if (!impl->Initialize()) {
LOG(ERROR) << "Could not initialize GpuVdaImpl.";
return nullptr;
}
return impl.release();
}
GpuVdaImpl::GpuVdaImpl(VafConnection* conn) : connection_(conn) {}
GpuVdaImpl::~GpuVdaImpl() = default;
std::vector<vda_input_format_t> GpuVdaImpl::GetSupportedInputFormats() {
std::vector<vda_input_format_t> supported_input_formats;
for (int i = 0; i < base::size(kInputFormats); i++) {
auto* context = InitDecodeSession(kInputFormats[i].profile);
if (context) {
supported_input_formats.emplace_back(kInputFormats[i]);
CloseDecodeSession(context);
}
}
return supported_input_formats;
}
bool GpuVdaImpl::PopulateCapabilities() {
input_formats_ = GetSupportedInputFormats();
if (input_formats_.empty())
return false;
capabilities_.num_input_formats = input_formats_.size();
capabilities_.input_formats = input_formats_.data();
output_formats_ = GetSupportedRawFormats(GbmUsageType::DECODE);
if (output_formats_.empty())
return false;
capabilities_.num_output_formats = output_formats_.size();
capabilities_.output_formats = output_formats_.data();
return true;
}
bool GpuVdaImpl::Initialize() {
ipc_task_runner_ = connection_->GetIpcTaskRunner();
if (!PopulateCapabilities()) {
ipc_task_runner_.reset();
return false;
}
return true;
}
VdaContext* GpuVdaImpl::InitDecodeSession(vda_profile_t profile) {
if (!ipc_task_runner_) {
DLOG(FATAL) << "InitDecodeSession called before successful Initialize().";
return nullptr;
}
DCHECK(!ipc_task_runner_->BelongsToCurrentThread());
DLOG(INFO) << "Initializing decode session with profile " << profile;
base::WaitableEvent init_complete_event(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED);
VdaContext* context = nullptr;
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&GpuVdaImpl::InitDecodeSessionOnIpcThread,
base::Unretained(this), profile,
&init_complete_event, &context));
init_complete_event.Wait();
return context;
}
void GpuVdaImpl::InitDecodeSessionOnIpcThread(
vda_profile_t profile,
base::WaitableEvent* init_complete_event,
VdaContext** out_context) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
arc::mojom::VideoDecodeAcceleratorPtr vda_ptr;
connection_->CreateDecodeAccelerator(&vda_ptr);
std::unique_ptr<GpuVdaContext> context =
std::make_unique<GpuVdaContext>(ipc_task_runner_, std::move(vda_ptr));
GpuVdaContext* context_ptr = context.get();
context_ptr->Initialize(
profile,
base::BindRepeating(
&GpuVdaImpl::InitDecodeSessionAfterContextInitializedOnIpcThread,
base::Unretained(this), init_complete_event, out_context,
base::Passed(std::move(context))));
}
void GpuVdaImpl::InitDecodeSessionAfterContextInitializedOnIpcThread(
base::WaitableEvent* init_complete_event,
VdaContext** out_context,
std::unique_ptr<VdaContext> context,
vda_result_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (result == SUCCESS) {
*out_context = context.release();
} else {
DLOG(ERROR) << "Failed to initialize decode session.";
}
init_complete_event->Signal();
}
void GpuVdaImpl::CloseDecodeSession(VdaContext* context) {
if (!ipc_task_runner_) {
DLOG(FATAL) << "CloseDecodeSession called before successful Initialize().";
return;
}
DLOG(INFO) << "Closing decode session";
RunTaskOnThread(ipc_task_runner_,
base::BindOnce(&GpuVdaImpl::CloseDecodeSessionOnIpcThread,
base::Unretained(this), context));
}
void GpuVdaImpl::CloseDecodeSessionOnIpcThread(VdaContext* context) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
delete context;
}
} // namespace arc