| // Copyright 2018 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_jpeg_encode_accelerator_service.h" |
| |
| #include <linux/videodev2.h> |
| #include <stdint.h> |
| #include <sys/mman.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/platform_shared_memory_region.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/read_only_shared_memory_region.h" |
| #include "base/memory/unsafe_shared_memory_region.h" |
| #include "base/memory/writable_shared_memory_region.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/trace_event/trace_event.h" |
| #include "components/chromeos_camera/common/dmabuf.mojom.h" |
| #include "components/chromeos_camera/dmabuf_utils.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/size.h" |
| #include "ui/gfx/linux/native_pixmap_dmabuf.h" |
| |
| namespace chromeos_camera { |
| |
| namespace { |
| |
| const int kJpegQuality = 90; |
| |
| media::VideoPixelFormat ToVideoPixelFormat(uint32_t fourcc_fmt) { |
| switch (fourcc_fmt) { |
| case V4L2_PIX_FMT_NV12: |
| case V4L2_PIX_FMT_NV12M: |
| return media::PIXEL_FORMAT_NV12; |
| |
| case V4L2_PIX_FMT_YUV420: |
| case V4L2_PIX_FMT_YUV420M: |
| return media::PIXEL_FORMAT_I420; |
| |
| case V4L2_PIX_FMT_RGB32: |
| return media::PIXEL_FORMAT_BGRA; |
| |
| default: |
| return media::PIXEL_FORMAT_UNKNOWN; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| void MojoJpegEncodeAcceleratorService::Create( |
| mojo::PendingReceiver<chromeos_camera::mojom::JpegEncodeAccelerator> |
| receiver) { |
| auto* jpeg_encoder = new MojoJpegEncodeAcceleratorService(); |
| mojo::MakeSelfOwnedReceiver(base::WrapUnique(jpeg_encoder), |
| std::move(receiver)); |
| } |
| |
| MojoJpegEncodeAcceleratorService::MojoJpegEncodeAcceleratorService() |
| : accelerator_initialized_(false), weak_this_factory_(this) {} |
| |
| MojoJpegEncodeAcceleratorService::~MojoJpegEncodeAcceleratorService() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| accelerator_.reset(); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::VideoFrameReady( |
| int32_t task_id, |
| size_t encoded_picture_size) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| NotifyEncodeStatus( |
| task_id, encoded_picture_size, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::ENCODE_OK); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::NotifyError( |
| int32_t task_id, |
| ::chromeos_camera::JpegEncodeAccelerator::Status error) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| NotifyEncodeStatus(task_id, 0, error); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::InitializeInternal( |
| std::vector<GpuJpegEncodeAcceleratorFactory::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=*/ |
| ::chromeos_camera::JpegEncodeAccelerator::HW_JPEG_ENCODE_NOT_SUPPORTED); |
| return; |
| } |
| accelerator_->InitializeAsync( |
| this, base::BindOnce(&MojoJpegEncodeAcceleratorService::OnInitialize, |
| weak_this_factory_.GetWeakPtr(), |
| std::move(remaining_accelerator_factory_functions), |
| std::move(init_cb))); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::Initialize(InitializeCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // When adding non-chromeos platforms, VideoCaptureGpuJpegEncoder::Initialize |
| // needs to be updated. |
| |
| InitializeInternal(GpuJpegEncodeAcceleratorFactory::GetAcceleratorFactories(), |
| std::move(callback)); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::OnInitialize( |
| std::vector<GpuJpegEncodeAcceleratorFactory::CreateAcceleratorCB> |
| remaining_accelerator_factory_functions, |
| InitializeCallback init_cb, |
| chromeos_camera::JpegEncodeAccelerator::Status last_initialize_result) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (last_initialize_result == |
| ::chromeos_camera::JpegEncodeAccelerator::ENCODE_OK) { |
| 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(&MojoJpegEncodeAcceleratorService::InitializeInternal, |
| weak_this_factory_.GetWeakPtr(), |
| std::move(remaining_accelerator_factory_functions), |
| std::move(init_cb))); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::EncodeWithFD( |
| int32_t task_id, |
| mojo::ScopedHandle input_handle, |
| uint32_t input_buffer_size, |
| int32_t coded_size_width, |
| int32_t coded_size_height, |
| mojo::ScopedHandle exif_handle, |
| uint32_t exif_buffer_size, |
| mojo::ScopedHandle output_handle, |
| uint32_t output_buffer_size, |
| EncodeWithFDCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!accelerator_initialized_) { |
| std::move(callback).Run( |
| task_id, 0, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| |
| base::ScopedPlatformFile input_fd; |
| base::ScopedPlatformFile exif_fd; |
| base::ScopedPlatformFile output_fd; |
| MojoResult result; |
| |
| if (coded_size_width <= 0 || coded_size_height <= 0) { |
| std::move(callback).Run( |
| task_id, 0, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::INVALID_ARGUMENT); |
| return; |
| } |
| |
| result = mojo::UnwrapPlatformFile(std::move(input_handle), &input_fd); |
| if (result != MOJO_RESULT_OK) { |
| std::move(callback).Run( |
| task_id, 0, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| |
| result = mojo::UnwrapPlatformFile(std::move(exif_handle), &exif_fd); |
| if (result != MOJO_RESULT_OK) { |
| std::move(callback).Run( |
| task_id, 0, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| |
| result = mojo::UnwrapPlatformFile(std::move(output_handle), &output_fd); |
| if (result != MOJO_RESULT_OK) { |
| std::move(callback).Run( |
| task_id, 0, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| // TODO(b/3832599): Make |input_region| read-only. |
| base::WritableSharedMemoryRegion writable_input_region = |
| base::WritableSharedMemoryRegion::Deserialize( |
| base::subtle::PlatformSharedMemoryRegion::Take( |
| std::move(input_fd), |
| base::subtle::PlatformSharedMemoryRegion::Mode::kWritable, |
| input_buffer_size, base::UnguessableToken::Create())); |
| base::ReadOnlySharedMemoryRegion input_region = |
| base::WritableSharedMemoryRegion::ConvertToReadOnly( |
| std::move(writable_input_region)); |
| |
| base::UnsafeSharedMemoryRegion output_shm_region = |
| base::UnsafeSharedMemoryRegion::Deserialize( |
| base::subtle::PlatformSharedMemoryRegion::Take( |
| std::move(output_fd), |
| base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, |
| output_buffer_size, base::UnguessableToken::Create())); |
| |
| media::BitstreamBuffer output_buffer(task_id, std::move(output_shm_region), |
| output_buffer_size); |
| std::unique_ptr<media::BitstreamBuffer> exif_buffer; |
| if (exif_buffer_size > 0) { |
| base::UnsafeSharedMemoryRegion exif_shm_region = |
| base::UnsafeSharedMemoryRegion::Deserialize( |
| base::subtle::PlatformSharedMemoryRegion::Take( |
| std::move(exif_fd), |
| base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, |
| exif_buffer_size, base::UnguessableToken::Create())); |
| exif_buffer = std::make_unique<media::BitstreamBuffer>( |
| task_id, std::move(exif_shm_region), exif_buffer_size); |
| } |
| gfx::Size coded_size(coded_size_width, coded_size_height); |
| |
| if (encode_cb_map_.find(task_id) != encode_cb_map_.end()) { |
| mojo::ReportBadMessage("task_id is already registered in encode_cb_map_"); |
| return; |
| } |
| auto wrapped_callback = base::BindOnce( |
| [](int32_t task_id, EncodeWithFDCallback callback, |
| uint32_t encoded_picture_size, |
| ::chromeos_camera::JpegEncodeAccelerator::Status error) { |
| std::move(callback).Run(task_id, encoded_picture_size, error); |
| }, |
| task_id, std::move(callback)); |
| encode_cb_map_.emplace(task_id, std::move(wrapped_callback)); |
| |
| base::ReadOnlySharedMemoryMapping input_mapping = input_region.Map(); |
| if (!input_mapping.IsValid()) { |
| DLOG(ERROR) << "Could not map input shared memory for buffer id " |
| << task_id; |
| NotifyEncodeStatus( |
| task_id, 0, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| |
| const uint8_t* input_shm_memory = |
| input_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 |
| input_shm_memory, // data |
| input_buffer_size, // data_size |
| base::TimeDelta()); // timestamp |
| if (!frame.get()) { |
| LOG(ERROR) << "Could not create VideoFrame for buffer id " << task_id; |
| NotifyEncodeStatus( |
| task_id, 0, |
| ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| frame->BackWithOwnedSharedMemory(std::move(input_region), |
| std::move(input_mapping)); |
| |
| DCHECK(accelerator_); |
| accelerator_->Encode(frame, kJpegQuality, exif_buffer.get(), |
| std::move(output_buffer)); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::EncodeWithDmaBuf( |
| int32_t task_id, |
| uint32_t input_format, |
| std::vector<chromeos_camera::mojom::DmaBufPlanePtr> input_planes, |
| std::vector<chromeos_camera::mojom::DmaBufPlanePtr> output_planes, |
| mojo::ScopedHandle exif_handle, |
| uint32_t exif_buffer_size, |
| int32_t coded_size_width, |
| int32_t coded_size_height, |
| int32_t quality, |
| bool has_input_modifier, |
| uint64_t input_modifier, |
| EncodeWithDmaBufCallback callback) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!accelerator_initialized_) { |
| std::move(callback).Run( |
| 0, ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| |
| const gfx::Size coded_size(coded_size_width, coded_size_height); |
| if (coded_size.IsEmpty()) { |
| std::move(callback).Run( |
| 0, ::chromeos_camera::JpegEncodeAccelerator::Status::INVALID_ARGUMENT); |
| return; |
| } |
| if (encode_cb_map_.find(task_id) != encode_cb_map_.end()) { |
| mojo::ReportBadMessage("task_id is already registered in encode_cb_map_"); |
| return; |
| } |
| |
| base::ScopedPlatformFile exif_fd; |
| auto result = mojo::UnwrapPlatformFile(std::move(exif_handle), &exif_fd); |
| if (result != MOJO_RESULT_OK) { |
| std::move(callback).Run( |
| 0, ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| |
| auto input_video_frame = ConstructVideoFrame( |
| std::move(input_planes), ToVideoPixelFormat(input_format), coded_size, |
| has_input_modifier ? input_modifier |
| : gfx::NativePixmapHandle::kNoModifier); |
| if (!input_video_frame) { |
| std::move(callback).Run( |
| 0, ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| auto output_video_frame = ConstructVideoFrame( |
| std::move(output_planes), media::PIXEL_FORMAT_MJPEG, coded_size); |
| if (!output_video_frame) { |
| std::move(callback).Run( |
| 0, ::chromeos_camera::JpegEncodeAccelerator::Status::PLATFORM_FAILURE); |
| return; |
| } |
| std::unique_ptr<media::BitstreamBuffer> exif_buffer; |
| if (exif_buffer_size > 0) { |
| // Currently we use our zero-based |task_id| as id of |exif_buffer| to track |
| // the encode task process from both Chrome OS and Chrome side. |
| base::UnsafeSharedMemoryRegion exif_shm_region = |
| base::UnsafeSharedMemoryRegion::Deserialize( |
| base::subtle::PlatformSharedMemoryRegion::Take( |
| std::move(exif_fd), |
| base::subtle::PlatformSharedMemoryRegion::Mode::kUnsafe, |
| exif_buffer_size, base::UnguessableToken::Create())); |
| exif_buffer = std::make_unique<media::BitstreamBuffer>( |
| task_id, std::move(exif_shm_region), exif_buffer_size); |
| } |
| encode_cb_map_.emplace(task_id, std::move(callback)); |
| |
| DCHECK(accelerator_); |
| accelerator_->EncodeWithDmaBuf(input_video_frame, output_video_frame, quality, |
| task_id, exif_buffer.get()); |
| } |
| |
| void MojoJpegEncodeAcceleratorService::NotifyEncodeStatus( |
| int32_t task_id, |
| size_t encoded_picture_size, |
| ::chromeos_camera::JpegEncodeAccelerator::Status error) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| auto iter = encode_cb_map_.find(task_id); |
| DCHECK(iter != encode_cb_map_.end()); |
| EncodeWithDmaBufCallback encode_cb = std::move(iter->second); |
| encode_cb_map_.erase(iter); |
| std::move(encode_cb).Run(encoded_picture_size, error); |
| } |
| |
| } // namespace chromeos_camera |