blob: 0e35b369257163e650f644cde33705c54abf1328 [file] [log] [blame]
/*
* 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.h"
#include <algorithm>
#include <cmath>
#include <utility>
#include <base/bind.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/posix/safe_strerror.h>
#include "common/libcamera_connector/camera_metadata_utils.h"
#include "common/libcamera_connector/supported_formats.h"
#include "common/libcamera_connector/types.h"
#include "cros-camera/camera_service_connector.h"
#include "cros-camera/common.h"
#include "cros-camera/future.h"
namespace {
std::string GetCameraName(const cros::mojom::CameraInfoPtr& info) {
switch (info->facing) {
case cros::mojom::CameraFacing::CAMERA_FACING_BACK:
return "Back Camera";
case cros::mojom::CameraFacing::CAMERA_FACING_FRONT:
return "Front Camera";
case cros::mojom::CameraFacing::CAMERA_FACING_EXTERNAL:
return "External Camera";
default:
return "Unknown Camera";
}
}
int GetCameraFacing(const cros::mojom::CameraInfoPtr& info) {
switch (info->facing) {
case cros::mojom::CameraFacing::CAMERA_FACING_BACK:
return CROS_CAM_FACING_BACK;
case cros::mojom::CameraFacing::CAMERA_FACING_FRONT:
return CROS_CAM_FACING_FRONT;
case cros::mojom::CameraFacing::CAMERA_FACING_EXTERNAL:
return CROS_CAM_FACING_EXTERNAL;
default:
LOGF(ERROR) << "unknown facing " << info->facing;
return CROS_CAM_FACING_EXTERNAL;
}
}
} // namespace
namespace cros {
CameraClient::CameraClient()
: ipc_thread_("CamClient"),
camera_hal_client_(this),
cam_info_callback_(nullptr),
capture_started_(false) {}
void CameraClient::Init(RegisterClientCallback register_client_callback,
IntOnceCallback init_callback) {
bool ret = ipc_thread_.StartWithOptions(
base::Thread::Options(base::MessageLoop::TYPE_IO, 0));
if (!ret) {
LOGF(ERROR) << "Failed to start IPC thread";
std::move(init_callback).Run(-ENODEV);
return;
}
init_callback_ = std::move(init_callback);
ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraClient::RegisterClient, base::Unretained(this),
std::move(register_client_callback)));
}
int CameraClient::Exit() {
VLOGF_ENTER();
int ret = 0;
{
base::AutoLock l(capture_started_lock_);
if (capture_started_) {
auto future = cros::Future<int>::Create(nullptr);
stop_callback_ = cros::GetFutureCallback(future);
client_ops_.StopCapture(
base::Bind(&CameraClient::OnClosedDevice, base::Unretained(this)));
ret = future->Get();
}
}
ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraClient::CloseOnThread, base::Unretained(this)));
ipc_thread_.Stop();
return ret;
}
void CameraClient::SetUpChannel(mojom::CameraModulePtr camera_module) {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
LOGF(INFO) << "Received camera module from camera HAL dispatcher";
camera_module_ = std::move(camera_module);
GetNumberOfCameras();
}
int CameraClient::SetCameraInfoCallback(cros_cam_get_cam_info_cb_t callback,
void* context) {
VLOGF_ENTER();
cam_info_callback_ = callback;
cam_info_context_ = context;
SendCameraInfo();
return 0;
}
int CameraClient::StartCapture(const cros_cam_capture_request_t* request,
cros_cam_capture_cb_t callback,
void* context) {
VLOGF_ENTER();
if (!IsDeviceActive(request->id)) {
LOGF(ERROR) << "Cannot start capture on an inactive device";
return -ENODEV;
}
LOGF(INFO) << "Starting capture";
// TODO(b/151047930): Check whether this format info is actually supported.
request_camera_id_ = request->id;
request_format_ = *request->format;
request_callback_ = callback;
request_context_ = context;
auto future = cros::Future<int>::Create(nullptr);
start_callback_ = cros::GetFutureCallback(future);
base::AutoLock l(capture_started_lock_);
if (capture_started_) {
LOGF(WARNING) << "Capture already started";
return -EINVAL;
}
client_ops_.Init(base::BindOnce(&CameraClient::OnDeviceOpsReceived,
base::Unretained(this)));
return future->Get();
}
int CameraClient::StopCapture(int id) {
VLOGF_ENTER();
if (!IsDeviceActive(id)) {
LOGF(ERROR) << "Cannot stop capture on an inactive device";
return -ENODEV;
}
LOGF(INFO) << "Stopping capture";
// TODO(lnishan): Support multi-device streaming.
CHECK_EQ(request_camera_id_, id);
base::AutoLock l(capture_started_lock_);
if (!capture_started_) {
LOGF(WARNING) << "Capture already stopped";
return -EPERM;
}
auto future = cros::Future<int>::Create(nullptr);
stop_callback_ = cros::GetFutureCallback(future);
client_ops_.StopCapture(
base::Bind(&CameraClient::OnClosedDevice, base::Unretained(this)));
return future->Get();
}
void CameraClient::RegisterClient(
RegisterClientCallback register_client_callback) {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
mojom::CameraHalClientPtr client_ptr;
camera_hal_client_.Bind(mojo::MakeRequest(&client_ptr));
std::move(register_client_callback).Run(std::move(client_ptr));
}
void CameraClient::CloseOnThread() {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
camera_hal_client_.Close();
}
void CameraClient::GetNumberOfCameras() {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
camera_module_->GetNumberOfCameras(
base::Bind(&CameraClient::OnGotNumberOfCameras, base::Unretained(this)));
}
void CameraClient::OnGotNumberOfCameras(int32_t num_builtin_cameras) {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
num_builtin_cameras_ = num_builtin_cameras;
LOGF(INFO) << "Number of builtin cameras: " << num_builtin_cameras_;
for (int32_t i = 0; i < num_builtin_cameras_; ++i) {
camera_id_list_.push_back(i);
}
if (num_builtin_cameras_ == 0) {
std::move(init_callback_).Run(0);
return;
}
camera_id_iter_ = camera_id_list_.begin();
GetCameraInfo(*camera_id_iter_);
}
void CameraClient::GetCameraInfo(int32_t camera_id) {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
camera_module_->GetCameraInfo(
camera_id,
base::Bind(&CameraClient::OnGotCameraInfo, base::Unretained(this)));
}
void CameraClient::OnGotCameraInfo(int32_t result, mojom::CameraInfoPtr info) {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
int32_t camera_id = *camera_id_iter_;
if (result != 0) {
LOGF(ERROR) << "Failed to get camera info of " << camera_id << ": "
<< base::safe_strerror(-result);
std::move(init_callback_).Run(-ENODEV);
return;
}
LOGF(INFO) << "Gotten camera info of " << camera_id;
auto& camera_info = camera_info_map_[camera_id];
camera_info.facing = GetCameraFacing(info);
camera_info.name = GetCameraName(info);
auto& format_info = camera_info_map_[camera_id].format_info;
auto min_frame_durations = GetMetadataEntryAsSpan<int64_t>(
info->static_camera_characteristics,
mojom::CameraMetadataTag::ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
for (size_t i = 0; i < min_frame_durations.size(); i += 4) {
int64_t hal_pixel_format = min_frame_durations[i + 0];
int64_t width = min_frame_durations[i + 1];
int64_t height = min_frame_durations[i + 2];
int64_t duration_ns = min_frame_durations[i + 3];
uint32_t fourcc = GetV4L2PixelFormat(hal_pixel_format);
if (fourcc == 0) {
VLOGF(1) << "Skip unsupported format " << hal_pixel_format;
continue;
}
cros_cam_format_info_t info = {
.fourcc = fourcc,
.width = static_cast<int>(width),
.height = static_cast<int>(height),
.fps = static_cast<int>(round(1e9 / duration_ns)),
};
format_info.push_back(std::move(info));
}
camera_info.jpeg_max_size = GetMetadataEntryAsSpan<int32_t>(
info->static_camera_characteristics,
mojom::CameraMetadataTag::ANDROID_JPEG_MAX_SIZE)[0];
++camera_id_iter_;
if (camera_id_iter_ == camera_id_list_.end()) {
std::move(init_callback_).Run(0);
} else {
GetCameraInfo(*camera_id_iter_);
}
}
void CameraClient::SendCameraInfo() {
VLOGF_ENTER();
if (cam_info_callback_ == nullptr) {
return;
}
for (auto& camera_id : camera_id_list_) {
auto it = camera_info_map_.find(camera_id);
if (camera_info_map_.find(camera_id) == camera_info_map_.end()) {
LOGF(ERROR) << "Cannot find the info of camera " << camera_id;
continue;
}
cros_cam_info_t cam_info = {
.id = camera_id,
.facing = it->second.facing,
.name = it->second.name.c_str(),
.format_count = static_cast<int>(it->second.format_info.size()),
.format_info = it->second.format_info.data()};
int ret =
(*cam_info_callback_)(cam_info_context_, &cam_info, /*is_removed=*/0);
if (ret != 0) {
// Deregister callback
cam_info_callback_ = nullptr;
cam_info_context_ = nullptr;
break;
}
}
}
void CameraClient::OnDeviceOpsReceived(
mojom::Camera3DeviceOpsRequest device_ops_request) {
VLOGF_ENTER();
ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraClient::OpenDeviceOnThread, base::Unretained(this),
std::move(device_ops_request)));
}
void CameraClient::OpenDeviceOnThread(
mojom::Camera3DeviceOpsRequest device_ops_request) {
VLOGF_ENTER();
DCHECK(ipc_thread_.task_runner()->BelongsToCurrentThread());
camera_module_->OpenDevice(
request_camera_id_, std::move(device_ops_request),
base::Bind(&CameraClient::OnOpenedDevice, base::Unretained(this)));
}
void CameraClient::OnOpenedDevice(int32_t result) {
if (result != 0) {
LOGF(ERROR) << "Failed to open camera " << request_camera_id_;
} else {
LOGF(INFO) << "Camera opened successfully";
client_ops_.StartCapture(
request_camera_id_, &request_format_, request_callback_,
request_context_, camera_info_map_[request_camera_id_].jpeg_max_size);
// Caller should hold the |capture_started_lock_| until the device is
// opened.
CHECK(!capture_started_lock_.Try());
capture_started_ = true;
}
std::move(start_callback_).Run(result);
}
void CameraClient::OnClosedDevice(int32_t result) {
if (result != 0) {
LOGF(ERROR) << "Failed to close camera " << request_camera_id_;
} else {
LOGF(INFO) << "Camera closed successfully";
}
// Caller should hold the |capture_started_lock_| until the device is closed.
CHECK(!capture_started_lock_.Try());
// Capture is marked stopped regardless of the result. When an error takes
// place, we don't want to close or use the camera again.
capture_started_ = false;
std::move(stop_callback_).Run(result);
}
bool CameraClient::IsDeviceActive(int device) {
return camera_info_map_.find(device) != camera_info_map_.end();
}
} // namespace cros