// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/chromeos_camera/mojo_mjpeg_decode_accelerator_service.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/files/platform_file.h"
#include "base/files/scoped_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/chromeos_camera/common/dmabuf.mojom.h"
#include "components/chromeos_camera/dmabuf_utils.h"
#include "media/base/bitstream_buffer.h"
#include "media/base/media_switches.h"
#include "media/base/video_frame.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace {
bool VerifyDecodeParams(const gfx::Size& coded_size,
mojo::ScopedSharedBufferHandle* output_handle,
uint32_t output_buffer_size) {
const int kJpegMaxDimension = UINT16_MAX;
if (coded_size.IsEmpty() || coded_size.width() > kJpegMaxDimension ||
coded_size.height() > kJpegMaxDimension) {
LOG(ERROR) << "invalid coded_size " << coded_size.ToString();
return false;
}
if (!output_handle->is_valid()) {
LOG(ERROR) << "invalid output_handle";
return false;
}
uint32_t allocation_size =
media::VideoFrame::AllocationSize(media::PIXEL_FORMAT_I420, coded_size);
if (output_buffer_size < allocation_size) {
DLOG(ERROR) << "output_buffer_size is too small: " << output_buffer_size
<< ". It needs: " << allocation_size;
return false;
}
return true;
}
} // namespace
namespace chromeos_camera {
// static
void MojoMjpegDecodeAcceleratorService::Create(
mojo::PendingReceiver<chromeos_camera::mojom::MjpegDecodeAccelerator>
receiver,
base::RepeatingCallback<void(MjpegDecodeOnBeginFrameCB)> cb) {
auto* jpeg_decoder = new MojoMjpegDecodeAcceleratorService();
jpeg_decoder->set_begin_frame_cb_ = std::move(cb);
mojo::MakeSelfOwnedReceiver(base::WrapUnique(jpeg_decoder),
std::move(receiver));
}
struct MojoMjpegDecodeAcceleratorService::DecodeTask {
DecodeTask(int32_t task_id,
base::ScopedFD src_dmabuf_fd,
size_t src_size,
off_t src_offset,
scoped_refptr<media::VideoFrame> dst_frame)
: task_id(task_id),
src_dmabuf_fd(std::move(src_dmabuf_fd)),
src_size(src_size),
src_offset(src_offset),
dst_frame(dst_frame) {}
int32_t task_id;
base::ScopedFD src_dmabuf_fd;
size_t src_size;
off_t src_offset;
scoped_refptr<media::VideoFrame> dst_frame;
};
MojoMjpegDecodeAcceleratorService::MojoMjpegDecodeAcceleratorService()
: accelerator_initialized_(false), weak_this_factory_(this) {}
MojoMjpegDecodeAcceleratorService::~MojoMjpegDecodeAcceleratorService() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
#if defined(ARCH_CPU_X86_FAMILY) && BUILDFLAG(IS_CHROMEOS)
if (base::FeatureList::IsEnabled(media::kVSyncMjpegDecoding)) {
set_begin_frame_cb_.Run(std::nullopt);
vsync_driven_decoding_ = false;
}
#endif
accelerator_.reset();
}
void MojoMjpegDecodeAcceleratorService::VideoFrameReady(
int32_t bitstream_buffer_id) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
NotifyDecodeStatus(
bitstream_buffer_id,
::chromeos_camera::MjpegDecodeAccelerator::Error::NO_ERRORS);
}
void MojoMjpegDecodeAcceleratorService::NotifyError(
int32_t bitstream_buffer_id,
::chromeos_camera::MjpegDecodeAccelerator::Error error) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
NotifyDecodeStatus(bitstream_buffer_id, error);
}
void MojoMjpegDecodeAcceleratorService::InitializeInternal(
std::vector<GpuMjpegDecodeAcceleratorFactory::CreateAcceleratorCB>
remaining_accelerator_factory_functions,
InitializeCallback init_cb) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (remaining_accelerator_factory_functions.empty()) {
DLOG(ERROR) << "All JPEG accelerators failed to initialize";
std::move(init_cb).Run(false);
return;
}
accelerator_ = std::move(remaining_accelerator_factory_functions.front())
.Run(base::SingleThreadTaskRunner::GetCurrentDefault());
remaining_accelerator_factory_functions.erase(
remaining_accelerator_factory_functions.begin());
if (!accelerator_) {
OnInitialize(std::move(remaining_accelerator_factory_functions),
std::move(init_cb), /*last_initialize_result=*/false);
return;
}
#if defined(ARCH_CPU_X86_FAMILY) && BUILDFLAG(IS_CHROMEOS)
if (base::FeatureList::IsEnabled(media::kVSyncMjpegDecoding)) {
vsync_driven_decoding_ = true;
set_begin_frame_cb_.Run(base::BindRepeating(
&MojoMjpegDecodeAcceleratorService::DecodeWithDmaBufOnBeginFrame,
weak_this_factory_.GetWeakPtr()));
}
#endif
accelerator_->InitializeAsync(
this, base::BindOnce(&MojoMjpegDecodeAcceleratorService::OnInitialize,
weak_this_factory_.GetWeakPtr(),
std::move(remaining_accelerator_factory_functions),
std::move(init_cb)));
}
void MojoMjpegDecodeAcceleratorService::Initialize(
InitializeCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
// When adding non-chromeos platforms, VideoCaptureGpuJpegDecoder::Initialize
// needs to be updated.
InitializeInternal(
GpuMjpegDecodeAcceleratorFactory::GetAcceleratorFactories(),
std::move(callback));
}
void MojoMjpegDecodeAcceleratorService::OnInitialize(
std::vector<GpuMjpegDecodeAcceleratorFactory::CreateAcceleratorCB>
remaining_accelerator_factory_functions,
InitializeCallback init_cb,
bool last_initialize_result) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (last_initialize_result) {
accelerator_initialized_ = true;
std::move(init_cb).Run(true);
return;
}
// Note that we can't call InitializeInternal() directly. The reason is that
// InitializeInternal() may destroy |accelerator_| which could cause a
// use-after-free if |accelerator_| needs to do more stuff after calling
// OnInitialize().
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(&MojoMjpegDecodeAcceleratorService::InitializeInternal,
weak_this_factory_.GetWeakPtr(),
std::move(remaining_accelerator_factory_functions),
std::move(init_cb)));
}
void MojoMjpegDecodeAcceleratorService::Decode(
media::BitstreamBuffer input_buffer,
const gfx::Size& coded_size,
mojo::ScopedSharedBufferHandle output_handle,
uint32_t output_buffer_size,
DecodeCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
TRACE_EVENT0("jpeg", "MojoMjpegDecodeAcceleratorService::Decode");
DCHECK_EQ(mojo_cb_map_.count(input_buffer.id()), 0u);
if (mojo_cb_map_.count(input_buffer.id()) != 0) {
NotifyDecodeStatus(
input_buffer.id(),
::chromeos_camera::MjpegDecodeAccelerator::Error::INVALID_ARGUMENT);
return;
}
mojo_cb_map_[input_buffer.id()] =
base::BindOnce(std::move(callback), input_buffer.id());
if (!VerifyDecodeParams(coded_size, &output_handle, output_buffer_size)) {
NotifyDecodeStatus(
input_buffer.id(),
::chromeos_camera::MjpegDecodeAccelerator::Error::INVALID_ARGUMENT);
return;
}
base::UnsafeSharedMemoryRegion output_region =
mojo::UnwrapUnsafeSharedMemoryRegion(std::move(output_handle));
DCHECK(output_region.IsValid());
DCHECK_GE(output_region.GetSize(), output_buffer_size);
base::WritableSharedMemoryMapping mapping =
output_region.MapAt(0, output_buffer_size);
if (!mapping.IsValid()) {
LOG(ERROR) << "Could not map output shared memory for input buffer id "
<< input_buffer.id();
NotifyDecodeStatus(
input_buffer.id(),
::chromeos_camera::MjpegDecodeAccelerator::Error::PLATFORM_FAILURE);
return;
}
uint8_t* shm_memory = mapping.GetMemoryAsSpan<uint8_t>().data();
scoped_refptr<media::VideoFrame> frame = media::VideoFrame::WrapExternalData(
media::PIXEL_FORMAT_I420, // format
coded_size, // coded_size
gfx::Rect(coded_size), // visible_rect
coded_size, // natural_size
shm_memory, // data
output_buffer_size, // data_size
base::TimeDelta()); // timestamp
if (!frame.get()) {
LOG(ERROR) << "Could not create VideoFrame for input buffer id "
<< input_buffer.id();
NotifyDecodeStatus(
input_buffer.id(),
::chromeos_camera::MjpegDecodeAccelerator::Error::PLATFORM_FAILURE);
return;
}
// BackWithOwnedSharedMemory() is not executed because
// MjpegDecodeAccelerator doesn't use shared memory region of the frame.
// Just attach the video frame so that the mapped memory is valid until the
// VideoFrame is alive.
frame->AddDestructionObserver(base::BindOnce(
[](base::UnsafeSharedMemoryRegion, base::WritableSharedMemoryMapping) {},
std::move(output_region), std::move(mapping)));
if (!accelerator_initialized_) {
NotifyDecodeStatus(
input_buffer.id(),
::chromeos_camera::MjpegDecodeAccelerator::Error::PLATFORM_FAILURE);
return;
}
DCHECK(accelerator_);
accelerator_->Decode(std::move(input_buffer), frame);
}
void MojoMjpegDecodeAcceleratorService::DecodeWithDmaBuf(
int32_t task_id,
mojo::ScopedHandle src_dmabuf_fd,
uint32_t src_size,
uint32_t src_offset,
mojom::DmaBufVideoFramePtr dst_frame,
DecodeWithDmaBufCallback callback) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
TRACE_EVENT0("jpeg", __FUNCTION__);
if (src_size == 0) {
LOG(ERROR) << "Input buffer size should be positive";
std::move(callback).Run(
::chromeos_camera::MjpegDecodeAccelerator::Error::INVALID_ARGUMENT);
return;
}
mojo::PlatformHandle src_handle =
mojo::UnwrapPlatformHandle(std::move(src_dmabuf_fd));
if (!src_handle.is_valid()) {
LOG(ERROR) << "Invalid input DMA-buf FD";
std::move(callback).Run(
::chromeos_camera::MjpegDecodeAccelerator::Error::INVALID_ARGUMENT);
return;
}
const gfx::Size coded_size(base::checked_cast<int>(dst_frame->coded_width),
base::checked_cast<int>(dst_frame->coded_height));
scoped_refptr<media::VideoFrame> frame = ConstructVideoFrame(
std::move(dst_frame->planes), dst_frame->format, coded_size,
dst_frame->has_modifier ? dst_frame->modifier
: gfx::NativePixmapHandle::kNoModifier);
if (!frame) {
LOG(ERROR) << "Failed to create video frame";
std::move(callback).Run(
::chromeos_camera::MjpegDecodeAccelerator::Error::INVALID_ARGUMENT);
return;
}
DCHECK_EQ(mojo_cb_map_.count(task_id), 0u);
mojo_cb_map_[task_id] = std::move(callback);
if (!accelerator_initialized_) {
NotifyDecodeStatus(
task_id,
::chromeos_camera::MjpegDecodeAccelerator::Error::PLATFORM_FAILURE);
return;
}
DCHECK(accelerator_);
if (!vsync_driven_decoding_) {
accelerator_->Decode(
task_id, src_handle.TakeFD(), base::strict_cast<size_t>(src_size),
base::strict_cast<off_t>(src_offset), std::move(frame));
} else {
// Stores task to pending_tasks_ and wait for VSync event to process.
input_queue_.emplace_back(
task_id, src_handle.TakeFD(), base::strict_cast<size_t>(src_size),
base::strict_cast<off_t>(src_offset), std::move(frame));
}
}
void MojoMjpegDecodeAcceleratorService::DecodeWithDmaBufOnBeginFrame() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
for (auto& input : input_queue_) {
accelerator_->Decode(input.task_id, std::move(input.src_dmabuf_fd),
input.src_size, input.src_offset,
std::move(input.dst_frame));
}
input_queue_.clear();
}
void MojoMjpegDecodeAcceleratorService::Uninitialize() {
// TODO(c.padhi): see http://crbug.com/699255.
NOTIMPLEMENTED();
}
void MojoMjpegDecodeAcceleratorService::NotifyDecodeStatus(
int32_t bitstream_buffer_id,
::chromeos_camera::MjpegDecodeAccelerator::Error error) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
auto iter = mojo_cb_map_.find(bitstream_buffer_id);
DCHECK(iter != mojo_cb_map_.end());
if (iter == mojo_cb_map_.end()) {
// Silently ignoring abnormal case.
return;
}
MojoCallback mojo_cb = std::move(iter->second);
mojo_cb_map_.erase(iter);
std::move(mojo_cb).Run(error);
}
} // namespace chromeos_camera