| /* |
| * Copyright 2020 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 "common/libcamera_connector/camera_client_ops.h" |
| |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/bind_helpers.h> |
| #include <base/posix/safe_strerror.h> |
| #include <mojo/public/cpp/system/platform_handle.h> |
| #include <drm_fourcc.h> |
| #include <hardware/camera3.h> |
| #include <hardware/gralloc.h> |
| #include <sync/sync.h> |
| #include <sys/mman.h> |
| |
| #include "common/libcamera_connector/camera_metadata_utils.h" |
| #include "common/libcamera_connector/supported_formats.h" |
| #include "cros-camera/common.h" |
| #include "mojo/camera3.mojom.h" |
| |
| namespace cros { |
| |
| CameraClientOps::CameraClientOps() : camera3_callback_ops_(this) {} |
| |
| mojom::Camera3DeviceOpsRequest CameraClientOps::Init( |
| CaptureResultCallback result_callback) { |
| VLOGF_ENTER(); |
| |
| ops_runner_ = base::SequencedTaskRunnerHandle::Get(); |
| capturing_ = false; |
| result_callback_ = std::move(result_callback); |
| frame_number_ = 0; |
| return mojo::MakeRequest(&device_ops_); |
| } |
| |
| void CameraClientOps::StartCapture(int32_t camera_id, |
| const cros_cam_format_info_t* format, |
| int32_t jpeg_max_size) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| capturing_ = true; |
| // TODO(b/151047930): Check whether this format info is actually supported. |
| request_camera_id_ = camera_id; |
| request_format_ = *format; |
| jpeg_max_size_ = jpeg_max_size; |
| InitializeDevice(); |
| } |
| |
| void CameraClientOps::StopCapture(IntOnceCallback close_callback) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| capturing_ = false; |
| device_ops_->Close(base::BindOnce(&CameraClientOps::OnClosedDevice, |
| base::Unretained(this), |
| std::move(close_callback))); |
| } |
| |
| void CameraClientOps::Reset() { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| capturing_ = false; |
| device_ops_.reset(); |
| camera3_callback_ops_.Close(); |
| } |
| |
| void CameraClientOps::ProcessCaptureResult( |
| mojom::Camera3CaptureResultPtr result) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| if (!capturing_) { |
| return; |
| } |
| if (result->output_buffers) { |
| CHECK_EQ(result->output_buffers->size(), 1u); |
| const auto& output_buffer = result->output_buffers->front(); |
| if (output_buffer->release_fence.is_valid()) { |
| base::ScopedPlatformFile fence; |
| CHECK_EQ(mojo::UnwrapPlatformFile(std::move(output_buffer->release_fence), |
| &fence), |
| MOJO_RESULT_OK); |
| if (sync_wait(fence.release(), 1000) != 0) { |
| LOGF(ERROR) << "Failed to wait for release fence on buffer"; |
| } |
| } |
| if (output_buffer->status == |
| mojom::Camera3BufferStatus::CAMERA3_BUFFER_STATUS_ERROR) { |
| return; |
| } |
| |
| int64_t page_size = sysconf(_SC_PAGE_SIZE); |
| const auto* buffer_handle_ptr = buffer_manager_.GetBufferHandle( |
| result->output_buffers->at(0)->buffer_id); |
| CHECK_NE(buffer_handle_ptr, nullptr); |
| const auto& buffer_handle = *buffer_handle_ptr; |
| if (buffer_handle->drm_format == DRM_FORMAT_R8) { |
| CHECK_EQ(buffer_handle->fds.size(), 1); |
| |
| const auto* fds_ptr = buffer_manager_.GetFds(output_buffer->buffer_id); |
| |
| uint32_t unaligned_offset = buffer_handle->offsets[0] % page_size; |
| uint32_t mapped_size = buffer_handle->sizes->at(0) + unaligned_offset; |
| uint32_t aligned_offset = buffer_handle->offsets[0] - unaligned_offset; |
| CHECK_NE(fds_ptr, nullptr); |
| void* data = mmap(NULL, mapped_size, PROT_READ | PROT_WRITE, MAP_SHARED, |
| fds_ptr->at(0).get(), aligned_offset); |
| CHECK_NE(data, MAP_FAILED); |
| auto* blob = reinterpret_cast<camera3_jpeg_blob_t*>( |
| static_cast<char*>(data) + unaligned_offset + jpeg_max_size_ - |
| sizeof(camera3_jpeg_blob_t)); |
| CHECK_EQ(blob->jpeg_blob_id, CAMERA3_JPEG_BLOB_ID); |
| |
| cros_cam_frame_t frame = { |
| .format = request_format_, |
| .planes = {{ |
| .stride = 0, |
| .size = static_cast<int>(blob->jpeg_size), |
| .data = static_cast<uint8_t*>(data) + unaligned_offset, |
| }, |
| {.size = 0}, |
| {.size = 0}, |
| {.size = 0}}}; |
| SendCaptureResult(0, &frame); |
| |
| munmap(data, mapped_size); |
| buffer_manager_.ReleaseBuffer(output_buffer->buffer_id); |
| } else if (buffer_handle->drm_format == DRM_FORMAT_NV12) { |
| CHECK_EQ(buffer_handle->fds.size(), 2); |
| |
| const auto* fds_ptr = buffer_manager_.GetFds(output_buffer->buffer_id); |
| |
| uint32_t y_unaligned_offset = buffer_handle->offsets[0] % page_size; |
| uint32_t y_mapped_size = buffer_handle->sizes->at(0) + y_unaligned_offset; |
| uint32_t y_aligned_offset = |
| buffer_handle->offsets[0] - y_unaligned_offset; |
| void* y_ptr = mmap(NULL, y_mapped_size, PROT_READ | PROT_WRITE, |
| MAP_SHARED, fds_ptr->at(0).get(), y_aligned_offset); |
| CHECK_NE(y_ptr, MAP_FAILED); |
| |
| uint32_t cb_unaligned_offset = buffer_handle->offsets[1] % page_size; |
| uint32_t cb_mapped_size = |
| buffer_handle->sizes->at(1) + cb_unaligned_offset; |
| uint32_t cb_aligned_offset = |
| buffer_handle->offsets[1] - cb_unaligned_offset; |
| void* cb_ptr = mmap(NULL, cb_mapped_size, PROT_READ | PROT_WRITE, |
| MAP_SHARED, fds_ptr->at(1).get(), cb_aligned_offset); |
| CHECK_NE(cb_ptr, MAP_FAILED); |
| |
| cros_cam_frame_t frame = { |
| .format = request_format_, |
| .planes = { |
| {.stride = static_cast<int>(buffer_handle->strides[0]), |
| .size = static_cast<int>(buffer_handle->sizes->at(0)), |
| .data = static_cast<uint8_t*>(y_ptr) + y_unaligned_offset}, |
| {.stride = static_cast<int>(buffer_handle->strides[1]), |
| .size = static_cast<int>(buffer_handle->sizes->at(1)), |
| .data = static_cast<uint8_t*>(cb_ptr) + cb_unaligned_offset}, |
| {.size = 0}, |
| {.size = 0}}}; |
| SendCaptureResult(0, &frame); |
| |
| munmap(y_ptr, y_mapped_size); |
| munmap(cb_ptr, cb_mapped_size); |
| buffer_manager_.ReleaseBuffer(output_buffer->buffer_id); |
| } |
| } |
| } |
| |
| void CameraClientOps::Notify(mojom::Camera3NotifyMsgPtr msg) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| // We only need to handle error messages for libcamera_connector. |
| if (msg->type != mojom::Camera3MsgType::CAMERA3_MSG_ERROR) { |
| return; |
| } |
| int status = msg->message->get_error()->error_code == |
| mojom::Camera3ErrorMsgCode::CAMERA3_MSG_ERROR_DEVICE |
| ? -ENODEV |
| : -EIO; |
| SendCaptureResult(status, nullptr); |
| } |
| |
| void CameraClientOps::InitializeDevice() { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| mojom::Camera3CallbackOpsPtr camera3_callback_ops_ptr; |
| mojom::Camera3CallbackOpsRequest camera3_callback_ops_request = |
| mojo::MakeRequest(&camera3_callback_ops_ptr); |
| camera3_callback_ops_.Bind(std::move(camera3_callback_ops_request)); |
| device_ops_->Initialize(std::move(camera3_callback_ops_ptr), |
| base::Bind(&CameraClientOps::OnInitializedDevice, |
| base::Unretained(this))); |
| } |
| |
| void CameraClientOps::OnInitializedDevice(int32_t result) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| if (result != 0) { |
| LOGF(ERROR) << "Failed to initialize device: " |
| << base::safe_strerror(-result); |
| SendCaptureResult(-ENODEV, nullptr); |
| return; |
| } |
| LOGF(INFO) << "Successfully initialized device"; |
| ConfigureStreams(); |
| } |
| |
| void CameraClientOps::ConfigureStreams() { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| if (!capturing_) { |
| return; |
| } |
| |
| mojom::Camera3StreamPtr stream = mojom::Camera3Stream::New(); |
| stream->id = kStreamId; |
| stream->stream_type = mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT; |
| stream->width = request_format_.width; |
| stream->height = request_format_.height; |
| int hal_pixel_format = GetHalPixelFormat(request_format_.fourcc); |
| CHECK_NE(hal_pixel_format, 0); |
| stream->format = static_cast<mojom::HalPixelFormat>(hal_pixel_format); |
| stream->data_space = 0; |
| // TODO(b/151047930): Handle device rotations. |
| stream->rotation = mojom::Camera3StreamRotation::CAMERA3_STREAM_ROTATION_0; |
| |
| mojom::Camera3StreamConfigurationPtr stream_config = |
| mojom::Camera3StreamConfiguration::New(); |
| stream_config->streams.push_back(std::move(stream)); |
| stream_config->operation_mode = mojom::Camera3StreamConfigurationMode:: |
| CAMERA3_STREAM_CONFIGURATION_NORMAL_MODE; |
| |
| device_ops_->ConfigureStreamsAndGetAllocatedBuffers( |
| std::move(stream_config), |
| base::Bind(&CameraClientOps::OnConfiguredStreams, |
| base::Unretained(this))); |
| } |
| |
| void CameraClientOps::OnConfiguredStreams( |
| int32_t result, |
| cros::mojom::Camera3StreamConfigurationPtr updated_config, |
| base::flat_map<uint64_t, std::vector<mojom::Camera3StreamBufferPtr>> |
| allocated_buffers) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| if (result != 0) { |
| LOGF(ERROR) |
| << "Failed to configure streams. Please check your capture parameters: " |
| << base::safe_strerror(-result); |
| SendCaptureResult(-ENODEV, nullptr); |
| return; |
| } |
| LOGF(INFO) << "Stream configured successfully"; |
| stream_config_ = std::move(updated_config); |
| buffer_manager_.Init(std::move(allocated_buffers[kStreamId])); |
| ConstructDefaultRequestSettings(); |
| } |
| |
| void CameraClientOps::ConstructDefaultRequestSettings() { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| if (!capturing_) { |
| return; |
| } |
| |
| // TODO(b/151047930): Support other templates. |
| mojom::Camera3RequestTemplate request_template = |
| mojom::Camera3RequestTemplate::CAMERA3_TEMPLATE_PREVIEW; |
| device_ops_->ConstructDefaultRequestSettings( |
| request_template, |
| base::Bind(&CameraClientOps::OnConstructedDefaultRequestSettings, |
| base::Unretained(this))); |
| } |
| |
| void CameraClientOps::OnConstructedDefaultRequestSettings( |
| mojom::CameraMetadataPtr settings) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| if (settings.is_null()) { |
| LOGF(ERROR) << "Failed to construct the specified capture template"; |
| SendCaptureResult(-ENODEV, nullptr); |
| return; |
| } |
| LOGF(INFO) << "Gotten request template for capture"; |
| request_settings_ = std::move(settings); |
| // TODO(b/151047930): Resolve to a proper fps range. |
| SetFpsRangeInMetadata(&request_settings_, request_format_.fps); |
| ConstructCaptureRequestOnThread(); |
| } |
| |
| void CameraClientOps::ConstructCaptureRequest() { |
| VLOGF_ENTER(); |
| ops_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CameraClientOps::ConstructCaptureRequestOnThread, |
| base::Unretained(this))); |
| } |
| |
| void CameraClientOps::ConstructCaptureRequestOnThread() { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| if (!buffer_manager_.HasFreeBuffers()) { |
| buffer_manager_.SetNotifyBufferCallback(base::BindOnce( |
| &CameraClientOps::ConstructCaptureRequest, base::Unretained(this))); |
| return; |
| } |
| |
| mojom::Camera3CaptureRequestPtr request = mojom::Camera3CaptureRequest::New(); |
| request->frame_number = frame_number_++; |
| request->settings = request_settings_.Clone(); |
| |
| auto buffer = buffer_manager_.AllocateBuffer(); |
| CHECK(!buffer.is_null()); |
| request->output_buffers.push_back(std::move(buffer)); |
| |
| ProcessCaptureRequest(std::move(request)); |
| } |
| |
| void CameraClientOps::ProcessCaptureRequest( |
| mojom::Camera3CaptureRequestPtr request) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| if (!capturing_) { |
| return; |
| } |
| |
| device_ops_->ProcessCaptureRequest( |
| std::move(request), |
| base::Bind(&CameraClientOps::OnProcessedCaptureRequest, |
| base::Unretained(this))); |
| } |
| |
| void CameraClientOps::OnProcessedCaptureRequest(int32_t result) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| if (result != 0) { |
| LOGF(ERROR) << "Failed to send capture request: " |
| << base::safe_strerror(-result); |
| SendCaptureResult(-ENODEV, nullptr); |
| return; |
| } |
| ConstructCaptureRequestOnThread(); |
| } |
| |
| void CameraClientOps::SendCaptureResult(int status, cros_cam_frame_t* frame) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| cros_cam_capture_result_t result = {.status = status, .frame = frame}; |
| result_callback_.Run(result); |
| } |
| |
| void CameraClientOps::OnClosedDevice(IntOnceCallback close_callback, |
| int32_t result) { |
| VLOGF_ENTER(); |
| DCHECK(ops_runner_->RunsTasksInCurrentSequence()); |
| |
| device_ops_.reset(); |
| camera3_callback_ops_.Close(); |
| std::move(close_callback).Run(result); |
| } |
| |
| } // namespace cros |