| // 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 "camera3_test/camera3_module_connector.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/no_destructor.h> |
| #include <mojo/core/embedder/embedder.h> |
| #include <mojo/core/embedder/scoped_ipc_support.h> |
| #include <gtest/gtest.h> |
| #include <system/camera_metadata_hidden.h> |
| |
| #include "camera3_test/camera3_device_connector.h" |
| #include "cros-camera/constants.h" |
| #include "cros-camera/future.h" |
| #include "cros-camera/ipc_util.h" |
| |
| namespace camera3_test { |
| |
| HalModuleConnector::HalModuleConnector(camera_module_t* cam_module, |
| cros::CameraThread* hal_thread) |
| : cam_module_(cam_module), hal_thread_(hal_thread) { |
| hal_thread_->PostTaskSync( |
| FROM_HERE, base::Bind(&HalModuleConnector::GetVendorTagsOnHalThread, |
| base::Unretained(this))); |
| } |
| |
| void HalModuleConnector::GetVendorTagsOnHalThread() { |
| vendor_tag_ops_t ops; |
| if (cam_module_->get_vendor_tag_ops != nullptr) { |
| cam_module_->get_vendor_tag_ops(&ops); |
| int count = ops.get_tag_count(&ops); |
| if (count > 0) { |
| std::vector<uint32_t> tag_array(count, 0); |
| ops.get_all_tags(&ops, tag_array.data()); |
| for (const auto& tag : tag_array) { |
| vendor_tag_map_.emplace(std::make_pair( |
| tag, VendorTagInfo{.section_name = ops.get_section_name(&ops, tag), |
| .tag_name = ops.get_tag_name(&ops, tag), |
| .type = ops.get_tag_type(&ops, tag)})); |
| } |
| } |
| } |
| } |
| |
| int HalModuleConnector::GetNumberOfCameras() { |
| if (!cam_module_) { |
| return -ENODEV; |
| } |
| int result = -EINVAL; |
| hal_thread_->PostTaskSync( |
| FROM_HERE, base::Bind(&HalModuleConnector::GetNumberOfCamerasOnHalThread, |
| base::Unretained(this), &result)); |
| return result; |
| } |
| |
| void HalModuleConnector::GetNumberOfCamerasOnHalThread(int* result) { |
| *result = cam_module_->get_number_of_cameras(); |
| } |
| |
| std::unique_ptr<DeviceConnector> HalModuleConnector::OpenDevice(int cam_id) { |
| if (!cam_module_) { |
| return nullptr; |
| } |
| std::unique_ptr<DeviceConnector> dev_connector; |
| hal_thread_->PostTaskSync( |
| FROM_HERE, base::Bind(&HalModuleConnector::OpenDeviceOnHalThread, |
| base::Unretained(this), cam_id, &dev_connector)); |
| return dev_connector; |
| } |
| |
| void HalModuleConnector::OpenDeviceOnHalThread( |
| int cam_id, std::unique_ptr<DeviceConnector>* dev_connector) { |
| hw_device_t* device = nullptr; |
| char cam_id_name[3]; |
| snprintf(cam_id_name, sizeof(cam_id_name), "%d", cam_id); |
| if (cam_module_->common.methods->open(&cam_module_->common, cam_id_name, |
| &device) == 0) { |
| *dev_connector = std::make_unique<HalDeviceConnector>( |
| cam_id, reinterpret_cast<camera3_device_t*>(device)); |
| } |
| } |
| |
| int HalModuleConnector::GetCameraInfo(int cam_id, camera_info* info) { |
| if (!cam_module_) { |
| return -ENODEV; |
| } |
| int result = -ENODEV; |
| hal_thread_->PostTaskSync( |
| FROM_HERE, base::Bind(&HalModuleConnector::GetCameraInfoOnHalThread, |
| base::Unretained(this), cam_id, info, &result)); |
| return result; |
| } |
| |
| void HalModuleConnector::GetCameraInfoOnHalThread(int cam_id, |
| camera_info* info, |
| int* result) { |
| *result = cam_module_->get_camera_info(cam_id, info); |
| } |
| |
| bool HalModuleConnector::GetVendorTagByName(const std::string name, |
| uint32_t* tag) { |
| if (!tag) { |
| return false; |
| } |
| auto it = std::find_if(vendor_tag_map_.begin(), vendor_tag_map_.end(), |
| [&](const std::pair<uint32_t, VendorTagInfo>& v) { |
| return v.second.tag_name == name; |
| }); |
| if (it != vendor_tag_map_.end()) { |
| *tag = it->first; |
| } |
| return it != vendor_tag_map_.end(); |
| } |
| |
| ClientModuleConnector::ClientModuleConnector(CameraHalClient* cam_client) |
| : cam_client_(cam_client) {} |
| |
| int ClientModuleConnector::GetNumberOfCameras() { |
| if (!cam_client_) { |
| return -ENODEV; |
| } |
| return cam_client_->GetNumberOfCameras(); |
| } |
| |
| std::unique_ptr<DeviceConnector> ClientModuleConnector::OpenDevice(int cam_id) { |
| auto dev_connector = std::make_unique<ClientDeviceConnector>(); |
| cam_client_->OpenDevice(cam_id, dev_connector->GetDeviceOpsRequest()); |
| return dev_connector; |
| } |
| |
| int ClientModuleConnector::GetCameraInfo(int cam_id, camera_info* info) { |
| if (!cam_client_) { |
| return -ENODEV; |
| } |
| return cam_client_->GetCameraInfo(cam_id, info); |
| } |
| |
| bool ClientModuleConnector::GetVendorTagByName(const std::string name, |
| uint32_t* tag) { |
| return cam_client_->GetVendorTagByName(name, tag); |
| } |
| |
| // static |
| CameraHalClient* CameraHalClient::GetInstance() { |
| static base::NoDestructor<CameraHalClient> c; |
| return c.get(); |
| } |
| |
| CameraHalClient::CameraHalClient() |
| : ipc_thread_("CameraHALClientIPCThread"), |
| camera_hal_client_(this), |
| mojo_module_callbacks_(this), |
| ipc_initialized_(base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED), |
| vendor_tag_count_(0) {} |
| |
| int CameraHalClient::Start(camera_module_callbacks_t* callbacks) { |
| static constexpr ::base::TimeDelta kIpcTimeout = |
| ::base::TimeDelta::FromSeconds(3); |
| |
| VLOGF_ENTER(); |
| if (!callbacks) { |
| return -EINVAL; |
| } |
| camera_module_callbacks_ = callbacks; |
| mojo::core::Init(); |
| if (!ipc_thread_.StartWithOptions( |
| base::Thread::Options(base::MessagePumpType::IO, 0))) { |
| LOGF(ERROR) << "Failed to start thread"; |
| return -EIO; |
| } |
| ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( |
| ipc_thread_.task_runner(), |
| mojo::core::ScopedIPCSupport::ShutdownPolicy::FAST); |
| |
| auto future = cros::Future<int>::Create(nullptr); |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CameraHalClient::ConnectToDispatcher, |
| base::Unretained(this), cros::GetFutureCallback(future))); |
| int result = future->Get(); |
| if (result != 0) { |
| LOGF(ERROR) << "Failed to connect to dispatcher"; |
| return result; |
| } |
| |
| if (!ipc_initialized_.TimedWait(kIpcTimeout)) { |
| LOGF(ERROR) << "Failed to set up channel and get vendor tags"; |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| void CameraHalClient::ConnectToDispatcher(base::Callback<void(int)> callback) { |
| VLOGF_ENTER(); |
| ASSERT_TRUE(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| mojo::ScopedMessagePipeHandle child_pipe; |
| base::FilePath socket_path(cros::constants::kCrosCameraSocketPathString); |
| if (cros::CreateMojoChannelToParentByUnixDomainSocket( |
| socket_path, &child_pipe) != MOJO_RESULT_OK) { |
| LOGF(ERROR) << "Failed to create mojo channel"; |
| callback.Run(-EIO); |
| return; |
| } |
| |
| dispatcher_ = mojo::MakeProxy( |
| cros::mojom::CameraHalDispatcherPtrInfo(std::move(child_pipe), 0u), |
| ipc_thread_.task_runner()); |
| if (!dispatcher_.is_bound()) { |
| LOGF(ERROR) << "Failed to bind mojo dispatcher"; |
| callback.Run(-EIO); |
| return; |
| } |
| |
| cros::mojom::CameraHalClientPtr client_ptr; |
| camera_hal_client_.Bind(mojo::MakeRequest(&client_ptr)); |
| dispatcher_->RegisterClient(std::move(client_ptr)); |
| callback.Run(0); |
| } |
| |
| void CameraHalClient::SetUpChannel(cros::mojom::CameraModulePtr camera_module) { |
| VLOGF_ENTER(); |
| ASSERT_TRUE(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| camera_module_ = std::move(camera_module); |
| camera_module_.set_connection_error_handler(base::Bind( |
| &CameraHalClient::onIpcConnectionLost, base::Unretained(this))); |
| |
| cros::mojom::CameraModuleCallbacksPtr camera_module_callbacks_ptr; |
| cros::mojom::CameraModuleCallbacksRequest camera_module_callbacks_request = |
| mojo::MakeRequest(&camera_module_callbacks_ptr); |
| mojo_module_callbacks_.Bind(std::move(camera_module_callbacks_request)); |
| camera_module_->SetCallbacks( |
| std::move(camera_module_callbacks_ptr), |
| base::Bind(&CameraHalClient::OnSetCallbacks, base::Unretained(this))); |
| } |
| |
| void CameraHalClient::OnSetCallbacks(int32_t result) { |
| VLOGF_ENTER(); |
| ASSERT_TRUE(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| if (result != 0) { |
| LOGF(ERROR) << "Failed to set callbacks"; |
| exit(EXIT_FAILURE); |
| } |
| |
| cros::mojom::VendorTagOpsRequest ops_req = |
| mojo::MakeRequest(&vendor_tag_ops_); |
| camera_module_->GetVendorTagOps( |
| std::move(ops_req), |
| base::Bind(&CameraHalClient::OnGotVendorTagOps, base::Unretained(this))); |
| } |
| |
| void CameraHalClient::OnGotVendorTagOps() { |
| VLOGF_ENTER(); |
| vendor_tag_ops_->GetAllTags( |
| base::Bind(&CameraHalClient::OnGotAllTags, base::Unretained(this))); |
| } |
| |
| void CameraHalClient::OnGotAllTags(const std::vector<uint32_t>& tag_array) { |
| VLOGF_ENTER(); |
| if (tag_array.empty()) { |
| ipc_initialized_.Signal(); |
| return; |
| } |
| vendor_tag_count_ = tag_array.size(); |
| for (const auto& tag : tag_array) { |
| vendor_tag_ops_->GetSectionName( |
| tag, base::Bind(&CameraHalClient::OnGotSectionName, |
| base::Unretained(this), tag)); |
| } |
| } |
| |
| void CameraHalClient::OnGotSectionName( |
| uint32_t tag, const base::Optional<std::string>& name) { |
| VLOGF_ENTER(); |
| ASSERT_NE(base::nullopt, name); |
| vendor_tag_map_[tag].section_name = *name; |
| |
| vendor_tag_ops_->GetTagName(tag, base::Bind(&CameraHalClient::OnGotTagName, |
| base::Unretained(this), tag)); |
| } |
| |
| void CameraHalClient::OnGotTagName(uint32_t tag, |
| const base::Optional<std::string>& name) { |
| VLOGF_ENTER(); |
| ASSERT_NE(base::nullopt, name); |
| vendor_tag_map_[tag].tag_name = *name; |
| |
| vendor_tag_ops_->GetTagType(tag, base::Bind(&CameraHalClient::OnGotTagType, |
| base::Unretained(this), tag)); |
| } |
| |
| void CameraHalClient::OnGotTagType(uint32_t tag, int32_t type) { |
| VLOGF_ENTER(); |
| vendor_tag_map_[tag].type = type; |
| |
| if ((--vendor_tag_count_) == 0) { |
| for (const auto& it : vendor_tag_map_) { |
| ASSERT_TRUE(vendor_tag_manager_.Add(it.first, it.second.section_name, |
| it.second.tag_name, it.second.type)); |
| } |
| vendor_tag_map_.clear(); |
| if (set_camera_metadata_vendor_ops(&vendor_tag_manager_) != 0) { |
| ADD_FAILURE() << "Failed to set vendor ops to camera metadata"; |
| } |
| |
| ipc_initialized_.Signal(); |
| } |
| } |
| |
| int CameraHalClient::GetNumberOfCameras() { |
| VLOGF_ENTER(); |
| auto future = cros::Future<int32_t>::Create(nullptr); |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CameraHalClient::GetNumberOfCamerasOnIpcThread, |
| base::Unretained(this), cros::GetFutureCallback(future))); |
| if (!future->Wait()) { |
| ADD_FAILURE() << "Wait timeout"; |
| return -ENODEV; |
| } |
| return future->Get(); |
| } |
| |
| void CameraHalClient::GetNumberOfCamerasOnIpcThread( |
| base::Callback<void(int32_t)> cb) { |
| VLOGF_ENTER(); |
| if (!ipc_initialized_.IsSignaled()) { |
| cb.Run(-ENODEV); |
| return; |
| } |
| camera_module_->GetNumberOfCameras(cb); |
| } |
| |
| int CameraHalClient::GetCameraInfo(int cam_id, camera_info* info) { |
| VLOGF_ENTER(); |
| if (!info) { |
| return -EINVAL; |
| } |
| auto future = cros::Future<int32_t>::Create(nullptr); |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraHalClient::GetCameraInfoOnIpcThread, |
| base::Unretained(this), cam_id, info, |
| cros::GetFutureCallback(future))); |
| if (!future->Wait()) { |
| ADD_FAILURE() << "Wait timeout"; |
| return -ENODEV; |
| } |
| return future->Get(); |
| } |
| |
| void CameraHalClient::GetCameraInfoOnIpcThread( |
| int cam_id, camera_info* info, base::Callback<void(int32_t)> cb) { |
| VLOGF_ENTER(); |
| if (!ipc_initialized_.IsSignaled()) { |
| cb.Run(-ENODEV); |
| return; |
| } |
| camera_module_->GetCameraInfo( |
| cam_id, base::Bind(&CameraHalClient::OnGotCameraInfo, |
| base::Unretained(this), cam_id, info, cb)); |
| } |
| |
| void CameraHalClient::OnGotCameraInfo(int cam_id, |
| camera_info* info, |
| base::Callback<void(int32_t)> cb, |
| int32_t result, |
| cros::mojom::CameraInfoPtr info_ptr) { |
| VLOGF_ENTER(); |
| if (result == 0) { |
| memset(info, 0, sizeof(*info)); |
| info->facing = static_cast<int>(info_ptr->facing); |
| info->orientation = info_ptr->orientation; |
| info->device_version = info_ptr->device_version; |
| if (!base::Contains(static_characteristics_map_, cam_id)) { |
| static_characteristics_map_[cam_id] = |
| cros::internal::DeserializeCameraMetadata( |
| info_ptr->static_camera_characteristics); |
| } |
| info->static_camera_characteristics = |
| static_characteristics_map_[cam_id].get(); |
| info->resource_cost = info_ptr->resource_cost->resource_cost; |
| if (!base::Contains(conflicting_devices_map_, cam_id)) { |
| for (const auto& it : *info_ptr->conflicting_devices) { |
| conflicting_devices_char_map_[cam_id].emplace_back(it.begin(), |
| it.end()); |
| conflicting_devices_char_map_[cam_id].back().push_back('\0'); |
| conflicting_devices_map_[cam_id].push_back( |
| conflicting_devices_char_map_[cam_id].back().data()); |
| } |
| } |
| info->conflicting_devices_length = conflicting_devices_map_[cam_id].size(); |
| info->conflicting_devices = conflicting_devices_map_[cam_id].data(); |
| } |
| cb.Run(result); |
| } |
| |
| void CameraHalClient::OpenDevice( |
| int cam_id, cros::mojom::Camera3DeviceOpsRequest dev_ops_req) { |
| VLOGF_ENTER(); |
| auto future = cros::Future<int32_t>::Create(nullptr); |
| ipc_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CameraHalClient::OpenDeviceOnIpcThread, |
| base::Unretained(this), cam_id, std::move(dev_ops_req), |
| cros::GetFutureCallback(future))); |
| if (!future->Wait()) { |
| ADD_FAILURE() << __func__ << " timeout"; |
| } |
| } |
| |
| void CameraHalClient::OpenDeviceOnIpcThread( |
| int cam_id, |
| cros::mojom::Camera3DeviceOpsRequest dev_ops_req, |
| base::Callback<void(int32_t)> cb) { |
| VLOGF_ENTER(); |
| if (!ipc_initialized_.IsSignaled()) { |
| cb.Run(-ENODEV); |
| return; |
| } |
| camera_module_->OpenDevice(cam_id, std::move(dev_ops_req), cb); |
| } |
| |
| bool CameraHalClient::GetVendorTagByName(const std::string name, |
| uint32_t* tag) { |
| if (!tag) { |
| return false; |
| } |
| std::vector<uint32_t> tags(vendor_tag_manager_.GetTagCount()); |
| vendor_tag_manager_.GetAllTags(tags.data()); |
| for (const auto& t : tags) { |
| if (name.compare(vendor_tag_manager_.GetTagName(t)) == 0) { |
| *tag = t; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void CameraHalClient::CameraDeviceStatusChange( |
| int32_t camera_id, cros::mojom::CameraDeviceStatus new_status) { |
| VLOGF_ENTER(); |
| ASSERT_TRUE(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| camera_module_callbacks_->camera_device_status_change( |
| camera_module_callbacks_, camera_id, |
| static_cast<camera_device_status_t>(new_status)); |
| } |
| |
| void CameraHalClient::TorchModeStatusChange( |
| int32_t camera_id, cros::mojom::TorchModeStatus new_status) { |
| VLOGF_ENTER(); |
| ASSERT_TRUE(ipc_thread_.task_runner()->BelongsToCurrentThread()); |
| std::stringstream ss; |
| ss << camera_id; |
| camera_module_callbacks_->torch_mode_status_change( |
| camera_module_callbacks_, ss.str().c_str(), |
| static_cast<camera_device_status_t>(new_status)); |
| } |
| |
| void CameraHalClient::onIpcConnectionLost() { |
| VLOGF_ENTER(); |
| ipc_initialized_.Reset(); |
| static_characteristics_map_.clear(); |
| vendor_tag_map_.clear(); |
| conflicting_devices_char_map_.clear(); |
| conflicting_devices_map_.clear(); |
| } |
| |
| } // namespace camera3_test |