| /* |
| * Copyright 2018 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "common/jpeg/jpeg_encode_accelerator_impl.h" |
| |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| #include <utility> |
| |
| #include <algorithm> |
| |
| #include <base/check.h> |
| #include <base/functional/bind.h> |
| #include <base/logging.h> |
| #include <base/memory/ptr_util.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <base/run_loop.h> |
| #include <chromeos/mojo/service_constants.h> |
| #include <mojo/public/c/system/buffer.h> |
| #include <mojo/public/cpp/system/buffer.h> |
| #include <mojo/public/cpp/system/platform_handle.h> |
| |
| #include "camera/mojo/gpu/dmabuf.mojom.h" |
| #include "common/jpeg/tracing.h" |
| #include "cros-camera/common.h" |
| #include "cros-camera/future.h" |
| #include "cros-camera/ipc_util.h" |
| |
| #define STATIC_ASSERT_ENUM(name) \ |
| static_assert(static_cast<int>(JpegEncodeAccelerator::name) == \ |
| static_cast<int>(mojom::EncodeStatus::name), \ |
| "mismatching enum: " #name) |
| |
| namespace cros { |
| |
| STATIC_ASSERT_ENUM(ENCODE_OK); |
| STATIC_ASSERT_ENUM(HW_JPEG_ENCODE_NOT_SUPPORTED); |
| STATIC_ASSERT_ENUM(THREAD_CREATION_FAILED); |
| STATIC_ASSERT_ENUM(INVALID_ARGUMENT); |
| STATIC_ASSERT_ENUM(INACCESSIBLE_OUTPUT_BUFFER); |
| STATIC_ASSERT_ENUM(PARSE_IMAGE_FAILED); |
| STATIC_ASSERT_ENUM(PLATFORM_FAILURE); |
| |
| // static |
| std::unique_ptr<JpegEncodeAccelerator> JpegEncodeAccelerator::CreateInstance( |
| CameraMojoChannelManagerToken* token) { |
| return base::WrapUnique<JpegEncodeAccelerator>(new JpegEncodeAcceleratorImpl( |
| CameraMojoChannelManager::FromToken(token))); |
| } |
| |
| JpegEncodeAcceleratorImpl::JpegEncodeAcceleratorImpl( |
| CameraMojoChannelManager* mojo_manager) |
| : task_id_(0), |
| mojo_manager_(mojo_manager), |
| cancellation_relay_(new CancellationRelay), |
| ipc_bridge_(new IPCBridge(mojo_manager, cancellation_relay_.get())) { |
| TRACE_JPEG_DEBUG(); |
| } |
| |
| JpegEncodeAcceleratorImpl::~JpegEncodeAcceleratorImpl() { |
| TRACE_JPEG_DEBUG(); |
| |
| bool result = mojo_manager_->GetIpcTaskRunner()->DeleteSoon( |
| FROM_HERE, std::move(ipc_bridge_)); |
| DCHECK(result); |
| cancellation_relay_ = nullptr; |
| } |
| |
| bool JpegEncodeAcceleratorImpl::Start() { |
| TRACE_JPEG(); |
| |
| auto is_initialized = Future<bool>::Create(cancellation_relay_.get()); |
| |
| mojo_manager_->GetIpcTaskRunner()->PostTask( |
| FROM_HERE, base::BindOnce(&JpegEncodeAcceleratorImpl::IPCBridge::Start, |
| ipc_bridge_->GetWeakPtr(), |
| cros::GetFutureCallback(is_initialized))); |
| if (!is_initialized->Wait()) { |
| return false; |
| } |
| |
| return is_initialized->Get(); |
| } |
| |
| int JpegEncodeAcceleratorImpl::EncodeSync(int input_fd, |
| const uint8_t* input_buffer, |
| uint32_t input_buffer_size, |
| int32_t coded_size_width, |
| int32_t coded_size_height, |
| const uint8_t* exif_buffer, |
| uint32_t exif_buffer_size, |
| int output_fd, |
| uint32_t output_buffer_size, |
| uint32_t* output_data_size) { |
| TRACE_JPEG("width", coded_size_width, "height", coded_size_height); |
| |
| int32_t task_id = task_id_; |
| // Mask against 30 bits, to avoid (undefined) wraparound on signed integer. |
| task_id_ = (task_id_ + 1) & 0x3FFFFFFF; |
| |
| auto future = Future<int>::Create(cancellation_relay_.get()); |
| auto callback = |
| base::BindOnce(&JpegEncodeAcceleratorImpl::IPCBridge::EncodeSyncCallback, |
| ipc_bridge_->GetWeakPtr(), GetFutureCallback(future), |
| output_data_size, task_id); |
| |
| mojo_manager_->GetIpcTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&JpegEncodeAcceleratorImpl::IPCBridge::EncodeLegacy, |
| ipc_bridge_->GetWeakPtr(), task_id, input_fd, input_buffer, |
| input_buffer_size, coded_size_width, coded_size_height, |
| exif_buffer, exif_buffer_size, output_fd, |
| output_buffer_size, std::move(callback))); |
| |
| if (!future->Wait()) { |
| if (!ipc_bridge_->IsReady()) { |
| LOGF(WARNING) << "There may be an mojo channel error."; |
| return TRY_START_AGAIN; |
| } |
| LOGF(WARNING) << "There is no encode response from JEA mojo channel."; |
| return NO_ENCODE_RESPONSE; |
| } |
| |
| return future->Get(); |
| } |
| |
| int JpegEncodeAcceleratorImpl::EncodeSync( |
| uint32_t input_format, |
| const std::vector<JpegCompressor::DmaBufPlane>& input_planes, |
| const std::vector<JpegCompressor::DmaBufPlane>& output_planes, |
| const uint8_t* exif_buffer, |
| uint32_t exif_buffer_size, |
| int coded_size_width, |
| int coded_size_height, |
| int quality, |
| uint64_t input_modifier, |
| uint32_t* output_data_size) { |
| TRACE_JPEG("width", coded_size_width, "height", coded_size_height); |
| |
| int32_t task_id = task_id_; |
| // Mask against 30 bits, to avoid (undefined) wraparound on signed integer. |
| task_id_ = (task_id_ + 1) & 0x3FFFFFFF; |
| |
| auto future = Future<int>::Create(cancellation_relay_.get()); |
| auto callback = |
| base::BindOnce(&JpegEncodeAcceleratorImpl::IPCBridge::EncodeSyncCallback, |
| ipc_bridge_->GetWeakPtr(), GetFutureCallback(future), |
| output_data_size, task_id); |
| |
| mojo_manager_->GetIpcTaskRunner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&JpegEncodeAcceleratorImpl::IPCBridge::Encode, |
| ipc_bridge_->GetWeakPtr(), task_id, input_format, |
| std::move(input_planes), std::move(output_planes), |
| exif_buffer, exif_buffer_size, coded_size_width, |
| coded_size_height, quality, input_modifier, |
| std::move(callback))); |
| |
| if (!future->Wait()) { |
| if (!ipc_bridge_->IsReady()) { |
| LOGF(WARNING) << "There may be an mojo channel error."; |
| return TRY_START_AGAIN; |
| } |
| LOGF(WARNING) << "There is no encode response from JEA mojo channel."; |
| return NO_ENCODE_RESPONSE; |
| } |
| |
| return future->Get(); |
| } |
| |
| JpegEncodeAcceleratorImpl::IPCBridge::IPCBridge( |
| CameraMojoChannelManager* mojo_manager, |
| CancellationRelay* cancellation_relay) |
| : mojo_manager_(mojo_manager), |
| cancellation_relay_(cancellation_relay), |
| ipc_task_runner_(mojo_manager_->GetIpcTaskRunner()) { |
| TRACE_JPEG_DEBUG(); |
| } |
| |
| JpegEncodeAcceleratorImpl::IPCBridge::~IPCBridge() { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| TRACE_JPEG_DEBUG(); |
| |
| Destroy(); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge:: |
| RequestAcceleratorFromServiceManager() { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| mojo_manager_->RequestServiceFromMojoServiceManager( |
| /*service_name=*/chromeos::mojo_services::kCrosJpegAccelerator, |
| accelerator_provider_.BindNewPipeAndPassReceiver().PassPipe()); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::Start( |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| TRACE_JPEG_DEBUG(); |
| |
| if (jea_.is_bound()) { |
| std::move(callback).Run(true); |
| return; |
| } |
| mojo_manager_->IsServiceRegistered( |
| chromeos::mojo_services::kCrosJpegAccelerator, |
| base::BindOnce(&IPCBridge::TryGetAndInitializeAccelerator, GetWeakPtr(), |
| std::move(callback))); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::TryGetAndInitializeAccelerator( |
| base::OnceCallback<void(bool)> callback, |
| bool is_accelerator_service_registered) { |
| if (!is_accelerator_service_registered) { |
| std::move(callback).Run(false); |
| return; |
| } |
| RequestAcceleratorFromServiceManager(); |
| mojo::PendingReceiver<mojom::JpegEncodeAccelerator> receiver = |
| jea_.BindNewPipeAndPassReceiver(); |
| jea_.set_disconnect_handler(base::BindOnce( |
| &JpegEncodeAcceleratorImpl::IPCBridge::OnJpegEncodeAcceleratorError, |
| GetWeakPtr())); |
| accelerator_provider_->GetJpegEncodeAccelerator(std::move(receiver)); |
| Initialize(std::move(callback)); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::Destroy() { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| TRACE_JPEG_DEBUG(); |
| |
| jea_.reset(); |
| accelerator_provider_.reset(); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::EncodeLegacy( |
| int32_t task_id, |
| int input_fd, |
| const uint8_t* input_buffer, |
| uint32_t input_buffer_size, |
| int32_t coded_size_width, |
| int32_t coded_size_height, |
| const uint8_t* exif_buffer, |
| uint32_t exif_buffer_size, |
| int output_fd, |
| uint32_t output_buffer_size, |
| EncodeWithFDCallback callback) { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| TRACE_JPEG_DEBUG("width", coded_size_width, "height", coded_size_height); |
| |
| if (!jea_.is_bound()) { |
| std::move(callback).Run(0, TRY_START_AGAIN); |
| return; |
| } |
| |
| base::WritableSharedMemoryRegion input_shm_region = |
| base::WritableSharedMemoryRegion::Create(input_buffer_size); |
| if (!input_shm_region.IsValid()) { |
| LOGF(WARNING) << "Create shared memory region for input failed, size=" |
| << input_buffer_size; |
| std::move(callback).Run(0, SHARED_MEMORY_FAIL); |
| return; |
| } |
| base::WritableSharedMemoryMapping input_shm_mapping = input_shm_region.Map(); |
| if (!input_shm_mapping.IsValid()) { |
| LOGF(WARNING) << "Create mapping for input failed, size=" |
| << input_buffer_size; |
| std::move(callback).Run(0, SHARED_MEMORY_FAIL); |
| return; |
| } |
| // Copy content from input buffer or file descriptor to shared memory. |
| if (input_buffer) { |
| memcpy(input_shm_mapping.memory(), input_buffer, input_buffer_size); |
| } else { |
| uint8_t* mmap_buf = static_cast<uint8_t*>( |
| mmap(NULL, input_buffer_size, PROT_READ, MAP_SHARED, input_fd, 0)); |
| |
| if (mmap_buf == MAP_FAILED) { |
| LOGF(WARNING) << "MMAP for input_fd:" << input_fd << " Failed."; |
| std::move(callback).Run(0, MMAP_FAIL); |
| return; |
| } |
| |
| memcpy(input_shm_mapping.memory(), mmap_buf, input_buffer_size); |
| munmap(mmap_buf, input_buffer_size); |
| } |
| |
| // Create WritableSharedMemory{Region,Mapping} for Exif buffer and copy data |
| // into it. |
| // Create a dummy |exif_shm| even if |exif_buffer_size| is 0. |
| uint32_t exif_shm_size = std::max(exif_buffer_size, 1u); |
| base::WritableSharedMemoryRegion exif_shm_region = |
| base::WritableSharedMemoryRegion::Create(exif_shm_size); |
| base::WritableSharedMemoryMapping exif_shm_mapping = exif_shm_region.Map(); |
| if (!exif_shm_mapping.IsValid()) { |
| LOGF(WARNING) << "Create and Map for exif failed, size=" << exif_shm_size; |
| std::move(callback).Run(0, SHARED_MEMORY_FAIL); |
| return; |
| } |
| if (exif_buffer_size) { |
| memcpy(exif_shm_mapping.memory(), exif_buffer, exif_buffer_size); |
| } |
| |
| base::subtle::PlatformSharedMemoryRegion input_platform_shm = |
| base::WritableSharedMemoryRegion::TakeHandleForSerialization( |
| std::move(input_shm_region)); |
| base::subtle::PlatformSharedMemoryRegion exif_platform_shm = |
| base::WritableSharedMemoryRegion::TakeHandleForSerialization( |
| std::move(exif_shm_region)); |
| |
| int dup_output_fd = HANDLE_EINTR(dup(output_fd)); |
| |
| mojo::ScopedHandle input_handle = mojo::WrapPlatformFile( |
| std::move(input_platform_shm.PassPlatformHandle().fd)); |
| mojo::ScopedHandle exif_handle = mojo::WrapPlatformFile( |
| std::move(exif_platform_shm.PassPlatformHandle().fd)); |
| mojo::ScopedHandle output_handle = |
| mojo::WrapPlatformFile(base::ScopedPlatformFile(dup_output_fd)); |
| |
| jea_->EncodeWithFD( |
| task_id, std::move(input_handle), input_buffer_size, coded_size_width, |
| coded_size_height, std::move(exif_handle), exif_buffer_size, |
| std::move(output_handle), output_buffer_size, |
| base::BindOnce(&JpegEncodeAcceleratorImpl::IPCBridge::OnEncodeAck, |
| GetWeakPtr(), std::move(callback))); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::Encode( |
| int32_t task_id, |
| uint32_t input_format, |
| const std::vector<JpegCompressor::DmaBufPlane>& input_planes, |
| const std::vector<JpegCompressor::DmaBufPlane>& output_planes, |
| const uint8_t* exif_buffer, |
| uint32_t exif_buffer_size, |
| int coded_size_width, |
| int coded_size_height, |
| int quality, |
| uint64_t input_modifier, |
| EncodeWithDmaBufCallback callback) { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| TRACE_JPEG_DEBUG("width", coded_size_width, "height", coded_size_height); |
| |
| if (!jea_.is_bound()) { |
| std::move(callback).Run(0, TRY_START_AGAIN); |
| return; |
| } |
| |
| // Create SharedMemory for Exif buffer and copy data into it. |
| // Create a dummy |exif_shm| even if |exif_buffer_size| is 0. |
| uint32_t exif_shm_size = std::max(exif_buffer_size, 1u); |
| base::WritableSharedMemoryRegion exif_shm_region = |
| base::WritableSharedMemoryRegion::Create(exif_shm_size); |
| if (!exif_shm_region.IsValid()) { |
| LOGF(WARNING) << "Create shared memory region for exif failed, size=" |
| << exif_shm_size; |
| std::move(callback).Run(0, SHARED_MEMORY_FAIL); |
| return; |
| } |
| base::WritableSharedMemoryMapping exif_shm_mapping = exif_shm_region.Map(); |
| if (!exif_shm_mapping.IsValid()) { |
| LOGF(WARNING) << "Create mapping for exif failed, size=" << exif_shm_size; |
| std::move(callback).Run(0, SHARED_MEMORY_FAIL); |
| return; |
| } |
| if (exif_buffer_size) { |
| memcpy(exif_shm_mapping.memory(), exif_buffer, exif_buffer_size); |
| } |
| base::subtle::PlatformSharedMemoryRegion exif_platform_shm = |
| base::WritableSharedMemoryRegion::TakeHandleForSerialization( |
| std::move(exif_shm_region)); |
| |
| mojo::ScopedHandle exif_handle = mojo::WrapPlatformFile( |
| std::move(exif_platform_shm.PassPlatformHandle().fd)); |
| |
| auto WrapToMojoPlanes = |
| [](const std::vector<JpegCompressor::DmaBufPlane>& planes) { |
| std::vector<cros::mojom::DmaBufPlanePtr> mojo_planes; |
| for (auto plane : planes) { |
| auto mojo_plane = cros::mojom::DmaBufPlane::New(); |
| mojo_plane->fd_handle = mojo::WrapPlatformFile( |
| base::ScopedPlatformFile(HANDLE_EINTR(dup(plane.fd)))); |
| mojo_plane->stride = plane.stride; |
| mojo_plane->offset = plane.offset; |
| mojo_plane->size = plane.size; |
| mojo_planes.push_back(std::move(mojo_plane)); |
| } |
| return mojo_planes; |
| }; |
| |
| std::vector<cros::mojom::DmaBufPlanePtr> mojo_input_planes = |
| WrapToMojoPlanes(input_planes); |
| std::vector<cros::mojom::DmaBufPlanePtr> mojo_output_planes = |
| WrapToMojoPlanes(output_planes); |
| |
| jea_->EncodeWithDmaBuf( |
| task_id, input_format, std::move(mojo_input_planes), |
| std::move(mojo_output_planes), std::move(exif_handle), exif_buffer_size, |
| coded_size_width, coded_size_height, quality, /*has_input_modifier=*/true, |
| input_modifier, |
| base::BindOnce(&JpegEncodeAcceleratorImpl::IPCBridge::OnEncodeDmaBufAck, |
| GetWeakPtr(), std::move(callback))); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::EncodeSyncCallback( |
| base::OnceCallback<void(int)> callback, |
| uint32_t* output_data_size, |
| int32_t task_id, |
| uint32_t output_size, |
| int status) { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| *output_data_size = output_size; |
| std::move(callback).Run(status); |
| } |
| |
| base::WeakPtr<JpegEncodeAcceleratorImpl::IPCBridge> |
| JpegEncodeAcceleratorImpl::IPCBridge::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| bool JpegEncodeAcceleratorImpl::IPCBridge::IsReady() { |
| return jea_.is_bound(); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::Initialize( |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| TRACE_JPEG_DEBUG(); |
| |
| jea_->Initialize(std::move(callback)); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::OnJpegEncodeAcceleratorError() { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| TRACE_JPEG(); |
| |
| LOGF(ERROR) << "There is a mojo error for JpegEncodeAccelerator"; |
| cancellation_relay_->CancelAllFutures(); |
| Destroy(); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::OnEncodeAck( |
| EncodeWithFDCallback callback, |
| int32_t task_id, |
| uint32_t output_size, |
| mojom::EncodeStatus status) { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| std::move(callback).Run(output_size, static_cast<int>(status)); |
| } |
| |
| void JpegEncodeAcceleratorImpl::IPCBridge::OnEncodeDmaBufAck( |
| EncodeWithDmaBufCallback callback, |
| uint32_t output_size, |
| mojom::EncodeStatus status) { |
| DCHECK(ipc_task_runner_->BelongsToCurrentThread()); |
| std::move(callback).Run(output_size, static_cast<int>(status)); |
| } |
| |
| } // namespace cros |