// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "media/capture/video/chromeos/camera_hal_delegate.h"
#include <fcntl.h>
#include <sys/uio.h>
#include <string_view>
#include <utility>
#include "base/containers/flat_map.h"
#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/posix/safe_strerror.h"
#include "base/process/launch.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/unguessable_token.h"
#include "chromeos/ash/components/mojo_service_manager/connection.h"
#include "components/device_event_log/device_event_log.h"
#include "media/capture/video/chromeos/camera_buffer_factory.h"
#include "media/capture/video/chromeos/camera_hal_dispatcher_impl.h"
#include "media/capture/video/chromeos/camera_metadata_utils.h"
#include "media/capture/video/chromeos/mojom/system_event_monitor.mojom.h"
#include "media/capture/video/chromeos/mojom/video_capture_device_info_monitor.mojom.h"
#include "media/capture/video/chromeos/video_capture_device_chromeos_delegate.h"
#include "media/capture/video/chromeos/video_capture_device_chromeos_halv3.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "third_party/cros_system_api/mojo/service_constants.h"
namespace media {
namespace {
constexpr int32_t kDefaultFps = 30;
constexpr char kVirtualPrefix[] = "VIRTUAL_";
const std::unordered_set<int32_t> module_id_set = {
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kLifeCamHD3000_Microsoft),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kC270_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kHDC615_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kHDProC920_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kC930e_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kC925e_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kC922ProStream_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kBRIOUltraHD_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kC920HDPro_Logitech),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kC920PROHD_Logitech),
static_cast<int32_t>(CameraHalDelegate::PopularCamPeriphModuleID::kCam_ARC),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kLiveStreamer313_Sunplus),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kVitadeAF_Microdia),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kCam_Sonix),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kVZR_IPEVO),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::k808Camera9_Generalplus),
static_cast<int32_t>(
CameraHalDelegate::PopularCamPeriphModuleID::kNexiGoN60FHD_2MUVC),
};
constexpr base::TimeDelta kEventWaitTimeoutSecs = base::Seconds(1);
// ash::system::StatisticsProvider::IsRunningOnVM() isn't available in unittest.
bool IsRunningOnVM() {
static bool is_vm = []() {
std::string output;
if (!base::GetAppOutput({"crossystem", "inside_vm"}, &output)) {
return false;
}
return output == "1";
}();
return is_vm;
}
bool IsVividLoaded() {
std::string output;
if (!base::GetAppOutput({"lsmod"}, &output)) {
return false;
}
std::vector<std::string_view> lines = base::SplitStringPieceUsingSubstr(
output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
return base::ranges::any_of(lines, [](const auto& line) {
return base::StartsWith(line, "vivid", base::CompareCase::SENSITIVE);
});
}
base::flat_set<int32_t> GetAvailableFramerates(
const cros::mojom::CameraInfoPtr& camera_info) {
base::flat_set<int32_t> candidates;
auto available_fps_ranges = GetMetadataEntryAsSpan<int32_t>(
camera_info->static_camera_characteristics,
cros::mojom::CameraMetadataTag::
ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
if (available_fps_ranges.empty()) {
// If there is no available target fps ranges listed in metadata, we set a
// default fps as candidate.
LOG(WARNING) << "No available fps ranges in metadata. Set default fps as "
"candidate.";
candidates.insert(kDefaultFps);
return candidates;
}
// The available target fps ranges are stored as pairs int32s: (min, max) x n.
const size_t kRangeMaxOffset = 1;
const size_t kRangeSize = 2;
for (size_t i = 0; i < available_fps_ranges.size(); i += kRangeSize) {
candidates.insert(available_fps_ranges[i + kRangeMaxOffset]);
}
return candidates;
}
} // namespace
class CameraHalDelegate::SystemEventMonitorProxy
: public cros::mojom::CrosLidObserver {
public:
explicit SystemEventMonitorProxy(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
: ui_task_runner_(std::move(ui_task_runner)) {
ui_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SystemEventMonitorProxy::InitOnUIThread, GetWeakPtr()));
}
SystemEventMonitorProxy(const SystemEventMonitorProxy&) = delete;
SystemEventMonitorProxy& operator=(const SystemEventMonitorProxy&) = delete;
~SystemEventMonitorProxy() override {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
}
void NotifyVideoCaptureDevicesChanged() {
ui_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&SystemEventMonitorProxy::
NotifyVideoCaptureDevicesChangedOnUIThread,
GetWeakPtr()));
}
void OnLidStateChanged(cros::mojom::LidState state) override {
bool is_lid_state_changed = false;
{
base::AutoLock lock(lid_lock_);
if (lid_state_ != state) {
lid_state_ = state;
is_lid_state_changed = true;
}
}
if (is_lid_state_changed) {
NotifyVideoCaptureDevicesChangedOnUIThread();
}
}
cros::mojom::LidState GetLidState() {
base::AutoLock lock(lid_lock_);
return lid_state_;
}
private:
void InitOnUIThread() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
if (!ash::mojo_service_manager::IsServiceManagerBound()) {
return;
}
ash::mojo_service_manager::GetServiceManagerProxy()->Request(
/*service_name=*/chromeos::mojo_services::kCrosSystemEventMonitor,
std::nullopt, monitor_.BindNewPipeAndPassReceiver().PassPipe());
monitor_->AddLidObserver(receiver_.BindNewPipeAndPassRemote());
}
void NotifyVideoCaptureDevicesChangedOnUIThread() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
if (!monitor_.is_bound()) {
return;
}
monitor_->NotifyDeviceChanged(cros::mojom::DeviceType::kVideoCapture);
}
base::WeakPtr<CameraHalDelegate::SystemEventMonitorProxy> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
base::Lock lid_lock_;
cros::mojom::LidState lid_state_ GUARDED_BY(lid_lock_) =
cros::mojom::LidState::kNotPresent;
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_;
mojo::Remote<cros::mojom::CrosSystemEventMonitor> monitor_;
mojo::Receiver<cros::mojom::CrosLidObserver> receiver_{this};
base::WeakPtrFactory<CameraHalDelegate::SystemEventMonitorProxy>
weak_ptr_factory_{this};
};
class CameraHalDelegate::VCDInfoMonitorImpl
: public cros::mojom::VideoCaptureDeviceInfoMonitor,
public chromeos::mojo_service_manager::mojom::ServiceProvider {
public:
VCDInfoMonitorImpl() {
if (!ash::mojo_service_manager::IsServiceManagerBound()) {
return;
}
vcd_info_observers_.set_disconnect_handler(base::BindRepeating(
&VCDInfoMonitorImpl::RemoveObserver, weak_factory_.GetWeakPtr()));
auto* proxy = ash::mojo_service_manager::GetServiceManagerProxy();
proxy->Register(/*service_name=*/chromeos::mojo_services::
kVideoCaptureDeviceInfoMonitor,
provider_receiver_.BindNewPipeAndPassRemote());
}
VCDInfoMonitorImpl(const VCDInfoMonitorImpl&) = delete;
VCDInfoMonitorImpl& operator=(const VCDInfoMonitorImpl&) = delete;
~VCDInfoMonitorImpl() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void AddCameraIdToDeviceIdMapping(int32_t camera_id, std::string device_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (camera_id_to_device_id_.find(camera_id) ==
camera_id_to_device_id_.end() ||
camera_id_to_device_id_[camera_id] != device_id) {
camera_id_to_device_id_[camera_id] = device_id;
for (auto& observer : vcd_info_observers_) {
observer->OnGetCameraIdToDeviceIdMapping(camera_id, device_id);
}
}
}
// chromeos::mojo_service_manager::mojom::ServiceProvider overrides.
void Request(
chromeos::mojo_service_manager::mojom::ProcessIdentityPtr identity,
mojo::ScopedMessagePipeHandle receiver) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
receiver_set_.Add(
this, mojo::PendingReceiver<cros::mojom::VideoCaptureDeviceInfoMonitor>(
std::move(receiver)));
}
// cros::mojom::VideoCaptureDeviceInfoMonitor overrides.
void AddVideoCaptureDeviceInfoObserver(
mojo::PendingRemote<cros::mojom::VideoCaptureDeviceInfoObserver> observer)
override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto id = vcd_info_observers_.Add(std::move(observer));
for (const auto& [camera_id, device_id] : camera_id_to_device_id_) {
vcd_info_observers_.Get(id)->OnGetCameraIdToDeviceIdMapping(camera_id,
device_id);
}
}
void CleanMappings() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
camera_id_to_device_id_.clear();
}
private:
void RemoveObserver(mojo::RemoteSetElementId id) {
vcd_info_observers_.Remove(id);
}
base::flat_map<int32_t, std::string> camera_id_to_device_id_;
SEQUENCE_CHECKER(sequence_checker_);
mojo::RemoteSet<cros::mojom::VideoCaptureDeviceInfoObserver>
vcd_info_observers_;
mojo::ReceiverSet<cros::mojom::VideoCaptureDeviceInfoMonitor> receiver_set_;
mojo::Receiver<chromeos::mojo_service_manager::mojom::ServiceProvider>
provider_receiver_{this};
base::WeakPtrFactory<VCDInfoMonitorImpl> weak_factory_{this};
};
class CameraHalDelegate::VideoCaptureDeviceDelegateMap {
public:
VideoCaptureDeviceDelegateMap() = default;
VideoCaptureDeviceDelegateMap(const VideoCaptureDeviceDelegateMap&) = delete;
VideoCaptureDeviceDelegateMap& operator=(
const VideoCaptureDeviceDelegateMap&) = delete;
~VideoCaptureDeviceDelegateMap() = default;
bool HasVCDDelegate(int camera_id) {
return vcd_delegates_.find(camera_id) != vcd_delegates_.end();
}
void Insert(const int camera_id,
std::unique_ptr<VideoCaptureDeviceChromeOSDelegate> delegate) {
vcd_delegates_[camera_id] = std::move(delegate);
}
void Erase(const int camera_id) { vcd_delegates_.erase(camera_id); }
VideoCaptureDeviceChromeOSDelegate* Get(const int camera_id) {
DCHECK(HasVCDDelegate(camera_id));
return vcd_delegates_[camera_id].get();
}
base::WeakPtr<CameraHalDelegate::VideoCaptureDeviceDelegateMap> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
base::flat_map<int, std::unique_ptr<VideoCaptureDeviceChromeOSDelegate>>
vcd_delegates_;
base::WeakPtrFactory<CameraHalDelegate::VideoCaptureDeviceDelegateMap>
weak_ptr_factory_{this};
};
class CameraHalDelegate::CameraModuleConnector {
public:
using OnGetCameraModuleCallback = base::RepeatingCallback<void(
mojo::PendingRemote<cros::mojom::CameraModule> camera_module)>;
explicit CameraModuleConnector(
OnGetCameraModuleCallback on_get_camera_module_callback)
: on_get_camera_module_callback_(on_get_camera_module_callback) {
mojo_service_manager_observer_ = MojoServiceManagerObserver::Create(
chromeos::mojo_services::kCrosCameraService,
base::BindRepeating(&CameraModuleConnector::ConnectToCameraService,
weak_factory_.GetWeakPtr()),
base::DoNothing());
}
~CameraModuleConnector() = default;
CameraModuleConnector(const CameraModuleConnector&) = delete;
CameraModuleConnector& operator=(const CameraModuleConnector&) = delete;
private:
void ConnectToCameraService() {
ash::mojo_service_manager::GetServiceManagerProxy()->Request(
chromeos::mojo_services::kCrosCameraService, std::nullopt,
camera_service_.BindNewPipeAndPassReceiver().PassPipe());
camera_service_.set_disconnect_handler(
base::BindOnce(&CameraModuleConnector::OnCameraServiceConnectionError,
weak_factory_.GetWeakPtr()));
camera_service_->GetCameraModule(
cros::mojom::CameraClientType::CHROME,
base::BindOnce(&CameraModuleConnector::OnGetCameraModule,
base::Unretained(this)));
}
void OnGetCameraModule(
mojo::PendingRemote<cros::mojom::CameraModule> camera_module) {
on_get_camera_module_callback_.Run(std::move(camera_module));
}
void OnCameraServiceConnectionError() { camera_service_.reset(); }
OnGetCameraModuleCallback on_get_camera_module_callback_;
std::unique_ptr<MojoServiceManagerObserver> mojo_service_manager_observer_;
mojo::Remote<cros::mojom::CrosCameraService> camera_service_;
base::WeakPtrFactory<CameraModuleConnector> weak_factory_{this};
};
CameraHalDelegate::CameraHalDelegate(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner)
: camera_module_has_been_set_(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
builtin_camera_info_updated_(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
external_camera_info_updated_(
base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::SIGNALED),
has_camera_connected_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
num_builtin_cameras_(0),
camera_buffer_factory_(new CameraBufferFactory()),
camera_hal_ipc_thread_("CameraHalIpcThread"),
camera_module_callbacks_(this),
vcd_delegate_map_(new VideoCaptureDeviceDelegateMap()),
system_event_monitor_proxy_(new SystemEventMonitorProxy(ui_task_runner)),
vcd_info_monitor_impl_(ui_task_runner),
ui_task_runner_(std::move(ui_task_runner)) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
bool CameraHalDelegate::Init() {
if (!camera_hal_ipc_thread_.Start()) {
LOG(ERROR) << "CameraHalDelegate IPC thread failed to start";
return false;
}
ipc_task_runner_ = camera_hal_ipc_thread_.task_runner();
vendor_tag_ops_delegate_ =
std::make_unique<VendorTagOpsDelegate>(ipc_task_runner_);
return true;
}
CameraHalDelegate::~CameraHalDelegate() {
if (ipc_task_runner_) {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CameraHalDelegate::ResetMojoInterfaceOnIpcThread,
base::Unretained(this)));
}
camera_hal_ipc_thread_.Stop();
ui_task_runner_->DeleteSoon(FROM_HERE,
std::move(system_event_monitor_proxy_));
}
void CameraHalDelegate::SetCameraModule(
mojo::PendingRemote<cros::mojom::CameraModule> camera_module) {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CameraHalDelegate::SetCameraModuleOnIpcThread,
base::Unretained(this), std::move(camera_module)));
}
void CameraHalDelegate::SetCameraModuleOnIpcThread(
mojo::PendingRemote<cros::mojom::CameraModule> camera_module) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (camera_module_.is_bound()) {
LOG(ERROR) << "CameraModule is already bound";
return;
}
if (!camera_module.is_valid()) {
LOG(ERROR) << "Invalid pending camera module remote";
return;
}
camera_module_.Bind(std::move(camera_module));
camera_module_.set_disconnect_handler(
base::BindOnce(&CameraHalDelegate::ResetMojoInterfaceOnIpcThread,
base::Unretained(this)));
camera_module_has_been_set_.Signal();
// Trigger ondevicechange event to notify clients that built-in camera device
// info can now be queried.
NotifyVideoCaptureDevicesChanged();
}
std::unique_ptr<VideoCaptureDevice> CameraHalDelegate::CreateDevice(
scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_screen_observer,
const VideoCaptureDeviceDescriptor& device_descriptor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!UpdateBuiltInCameraInfo()) {
return nullptr;
}
int camera_id = GetCameraIdFromDeviceId(device_descriptor.device_id);
if (camera_id == -1) {
LOG(ERROR) << "Invalid camera device: " << device_descriptor.device_id;
return nullptr;
}
auto* delegate =
GetVCDDelegate(task_runner_for_screen_observer, device_descriptor);
return std::make_unique<VideoCaptureDeviceChromeOSHalv3>(delegate,
device_descriptor);
}
void CameraHalDelegate::GetSupportedFormats(
const cros::mojom::CameraInfoPtr& camera_info,
VideoCaptureFormats* supported_formats) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::flat_set<int32_t> candidate_fps_set =
GetAvailableFramerates(camera_info);
const cros::mojom::CameraMetadataEntryPtr* min_frame_durations =
GetMetadataEntry(camera_info->static_camera_characteristics,
cros::mojom::CameraMetadataTag::
ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
if (!min_frame_durations) {
LOG(ERROR)
<< "Failed to get available min frame durations from camera info";
return;
}
// The min frame durations are stored as tuples of four int64s:
// (hal_pixel_format, width, height, ns) x n
const size_t kStreamFormatOffset = 0;
const size_t kStreamWidthOffset = 1;
const size_t kStreamHeightOffset = 2;
const size_t kStreamDurationOffset = 3;
const size_t kStreamDurationSize = 4;
int64_t* iter =
reinterpret_cast<int64_t*>((*min_frame_durations)->data.data());
for (size_t i = 0; i < (*min_frame_durations)->count;
i += kStreamDurationSize) {
auto hal_format =
static_cast<cros::mojom::HalPixelFormat>(iter[kStreamFormatOffset]);
int32_t width = base::checked_cast<int32_t>(iter[kStreamWidthOffset]);
int32_t height = base::checked_cast<int32_t>(iter[kStreamHeightOffset]);
int64_t duration = iter[kStreamDurationOffset];
iter += kStreamDurationSize;
if (hal_format == cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB) {
// Skip BLOB formats and use it only for TakePicture() since it's
// inefficient to stream JPEG frames for CrOS camera HAL.
continue;
}
if (duration <= 0) {
LOG(ERROR) << "Ignoring invalid frame duration: " << duration;
continue;
}
float max_fps = 1.0 * 1000000000LL / duration;
// There's no consumer information here to determine the buffer usage, so
// hard-code the usage that all the clients should be using.
constexpr gfx::BufferUsage kClientBufferUsage =
gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE;
const ChromiumPixelFormat cr_format =
camera_buffer_factory_->ResolveStreamBufferFormat(hal_format,
kClientBufferUsage);
if (cr_format.video_format == PIXEL_FORMAT_UNKNOWN) {
continue;
}
for (auto fps : candidate_fps_set) {
if (fps > max_fps) {
continue;
}
CAMERA_LOG(DEBUG) << "Supported format: " << width << "x" << height
<< " fps=" << fps
<< " format=" << cr_format.video_format;
supported_formats->emplace_back(gfx::Size(width, height), fps,
cr_format.video_format);
}
}
}
void CameraHalDelegate::GetDevicesInfo(
VideoCaptureDeviceFactory::GetDevicesInfoCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!UpdateBuiltInCameraInfo()) {
std::move(callback).Run({});
return;
}
if (!external_camera_info_updated_.TimedWait(kEventWaitTimeoutSecs)) {
LOG(ERROR) << "Failed to get camera info from all external cameras";
}
if (IsRunningOnVM() && IsVividLoaded()) {
has_camera_connected_.TimedWait(kEventWaitTimeoutSecs);
}
std::vector<VideoCaptureDeviceInfo> devices_info;
{
base::AutoLock info_lock(camera_info_lock_);
base::AutoLock id_map_lock(device_id_to_camera_id_lock_);
base::AutoLock virtual_lock(enable_virtual_device_lock_);
for (const auto& it : camera_info_) {
int camera_id = it.first;
const cros::mojom::CameraInfoPtr& camera_info = it.second;
if (!camera_info) {
continue;
}
auto get_vendor_string = [&](const std::string& key) -> const char* {
const VendorTagInfo* info =
vendor_tag_ops_delegate_->GetInfoByName(key);
if (info == nullptr) {
return nullptr;
}
auto val = GetMetadataEntryAsSpan<char>(
camera_info->static_camera_characteristics, info->tag);
return val.empty() ? nullptr : val.data();
};
VideoCaptureDeviceDescriptor desc;
desc.capture_api = VideoCaptureApi::ANDROID_API2_LIMITED;
desc.transport_type = VideoCaptureTransportType::OTHER_TRANSPORT;
switch (camera_info->facing) {
case cros::mojom::CameraFacing::CAMERA_FACING_BACK:
desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT;
desc.device_id = base::NumberToString(camera_id);
desc.set_display_name("Back Camera");
break;
case cros::mojom::CameraFacing::CAMERA_FACING_FRONT:
desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_USER;
desc.device_id = base::NumberToString(camera_id);
desc.set_display_name("Front Camera");
break;
case cros::mojom::CameraFacing::CAMERA_FACING_EXTERNAL: {
desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE;
// The webcam_private api expects that |device_id| to be set as the
// corresponding device path for external cameras used in GVC system.
auto* path = get_vendor_string("com.google.usb.devicePath");
desc.device_id =
path != nullptr ? path : base::NumberToString(camera_id);
auto* name = get_vendor_string("com.google.usb.modelName");
desc.set_display_name(name != nullptr ? name : "External Camera");
break;
// Mojo validates the input parameters for us so we don't need to
// worry about malformed values.
}
case cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_BACK:
case cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_FRONT:
case cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_EXTERNAL:
// |camera_info_| should not have these facing types.
LOG(ERROR) << "Invalid facing type: " << camera_info->facing;
break;
}
auto* vid = get_vendor_string("com.google.usb.vendorId");
auto* pid = get_vendor_string("com.google.usb.productId");
if (vid != nullptr && pid != nullptr) {
desc.model_id = base::StrCat({vid, ":", pid});
}
desc.set_control_support(GetControlSupport(camera_info));
device_id_to_camera_id_[desc.device_id] = camera_id;
vcd_info_monitor_impl_
.AsyncCall(&VCDInfoMonitorImpl::AddCameraIdToDeviceIdMapping)
.WithArgs(camera_id, desc.device_id);
devices_info.emplace_back(desc);
GetSupportedFormats(camera_info_[camera_id],
&devices_info.back().supported_formats);
// Create a virtual device when multiple streams are enabled.
if (enable_virtual_device_[camera_id]) {
desc.facing = VideoFacingMode::MEDIA_VIDEO_FACING_NONE;
desc.device_id =
std::string(kVirtualPrefix) + base::NumberToString(camera_id);
desc.set_display_name("Virtual Camera");
device_id_to_camera_id_[desc.device_id] = camera_id;
// We don't need to add virtual camera for mutli-stream to the camera_id
// <-> device_id map. Otherwise, it will overrides the device_id for the
// real device. Moreover, the multi-stream logic is going to be removed.
devices_info.emplace_back(desc);
GetSupportedFormats(camera_info_[camera_id],
&devices_info.back().supported_formats);
}
}
}
// TODO(jcliang): Remove this after JS API supports query camera facing
// (http://crbug.com/543997).
cros::mojom::LidState lid_state = system_event_monitor_proxy_->GetLidState();
std::sort(
devices_info.begin(), devices_info.end(),
[&](const VideoCaptureDeviceInfo& a, const VideoCaptureDeviceInfo& b) {
auto IsExternalCamera = [](const VideoCaptureDeviceInfo& vcd_info) {
return vcd_info.descriptor.facing ==
VideoFacingMode::MEDIA_VIDEO_FACING_NONE;
};
if (lid_state == cros::mojom::LidState::kClosed) {
if (IsExternalCamera(a) == IsExternalCamera(b))
return a.descriptor < b.descriptor;
return IsExternalCamera(a);
}
return a.descriptor < b.descriptor;
});
DVLOG(1) << "Number of devices: " << devices_info.size();
std::move(callback).Run(std::move(devices_info));
}
VideoCaptureControlSupport CameraHalDelegate::GetControlSupport(
const cros::mojom::CameraInfoPtr& camera_info) {
VideoCaptureControlSupport control_support;
auto is_vendor_range_valid = [&](const std::string& key) -> bool {
const VendorTagInfo* info = vendor_tag_ops_delegate_->GetInfoByName(key);
if (info == nullptr)
return false;
auto range = GetMetadataEntryAsSpan<int32_t>(
camera_info->static_camera_characteristics, info->tag);
return range.size() == 3 && range[0] < range[1];
};
if (is_vendor_range_valid("com.google.control.panRange"))
control_support.pan = true;
if (is_vendor_range_valid("com.google.control.tiltRange"))
control_support.tilt = true;
if (is_vendor_range_valid("com.google.control.zoomRange"))
control_support.zoom = true;
auto max_digital_zoom = GetMetadataEntryAsSpan<float>(
camera_info->static_camera_characteristics,
cros::mojom::CameraMetadataTag::
ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
if (max_digital_zoom.size() == 1 && max_digital_zoom[0] > 1) {
control_support.zoom = true;
}
return control_support;
}
cros::mojom::CameraInfoPtr CameraHalDelegate::GetCameraInfoFromDeviceId(
const std::string& device_id) {
base::AutoLock lock(camera_info_lock_);
int camera_id = GetCameraIdFromDeviceId(device_id);
if (camera_id == -1) {
return {};
}
auto it = camera_info_.find(camera_id);
if (it == camera_info_.end()) {
return {};
}
auto info = it->second.Clone();
if (base::StartsWith(device_id, std::string(kVirtualPrefix))) {
switch (it->second->facing) {
case cros::mojom::CameraFacing::CAMERA_FACING_BACK:
info->facing = cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_BACK;
break;
case cros::mojom::CameraFacing::CAMERA_FACING_FRONT:
info->facing = cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_FRONT;
break;
case cros::mojom::CameraFacing::CAMERA_FACING_EXTERNAL:
info->facing =
cros::mojom::CameraFacing::CAMERA_FACING_VIRTUAL_EXTERNAL;
break;
default:
break;
}
}
return info;
}
void CameraHalDelegate::EnableVirtualDevice(const std::string& device_id,
bool enable) {
if (base::StartsWith(device_id, std::string(kVirtualPrefix))) {
return;
}
auto camera_id = GetCameraIdFromDeviceId(device_id);
if (camera_id != -1) {
base::AutoLock lock(enable_virtual_device_lock_);
enable_virtual_device_[camera_id] = enable;
}
}
void CameraHalDelegate::DisableAllVirtualDevices() {
base::AutoLock lock(enable_virtual_device_lock_);
for (auto& it : enable_virtual_device_) {
it.second = false;
}
}
const VendorTagInfo* CameraHalDelegate::GetVendorTagInfoByName(
const std::string& full_name) {
return vendor_tag_ops_delegate_->GetInfoByName(full_name);
}
void CameraHalDelegate::OpenDevice(
int32_t camera_id,
const std::string& module_id,
mojo::PendingReceiver<cros::mojom::Camera3DeviceOps> device_ops_receiver,
OpenDeviceCallback callback) {
DCHECK(!ipc_task_runner_->BelongsToCurrentThread());
// This method may be called on any thread except |ipc_task_runner_|.
// Currently this method is used by CameraDeviceDelegate to open a camera
// device.
camera_module_has_been_set_.Wait();
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CameraHalDelegate::OpenDeviceOnIpcThread,
base::Unretained(this), camera_id, module_id,
std::move(device_ops_receiver), std::move(callback)));
}
int CameraHalDelegate::GetCameraIdFromDeviceId(const std::string& device_id) {
base::AutoLock lock(device_id_to_camera_id_lock_);
auto it = device_id_to_camera_id_.find(device_id);
if (it == device_id_to_camera_id_.end()) {
return -1;
}
return it->second;
}
VideoCaptureDeviceChromeOSDelegate* CameraHalDelegate::GetVCDDelegate(
scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_screen_observer,
const VideoCaptureDeviceDescriptor& device_descriptor) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto camera_id = GetCameraIdFromDeviceId(device_descriptor.device_id);
if (!vcd_delegate_map_->HasVCDDelegate(camera_id) ||
vcd_delegate_map_->Get(camera_id)->HasDeviceClient() == 0) {
// Don't post |cleanup_callback| to any thread, otherwise there will be a
// race condition if |CreateDevice| is scheduled during the cleanup of the
// same device.
auto cleanup_callback =
base::BindOnce(&VideoCaptureDeviceDelegateMap::Erase,
vcd_delegate_map_->GetWeakPtr(), camera_id);
auto delegate = std::make_unique<VideoCaptureDeviceChromeOSDelegate>(
std::move(task_runner_for_screen_observer), device_descriptor, this,
std::move(cleanup_callback));
vcd_delegate_map_->Insert(camera_id, std::move(delegate));
}
return vcd_delegate_map_->Get(camera_id);
}
void CameraHalDelegate::ResetMojoInterfaceOnIpcThread() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
camera_module_.reset();
camera_module_callbacks_.reset();
vendor_tag_ops_delegate_->Reset();
builtin_camera_info_updated_.Reset();
camera_module_has_been_set_.Reset();
has_camera_connected_.Reset();
external_camera_info_updated_.Signal();
// Clear all cached camera info, especially external cameras.
base::AutoLock lock(camera_info_lock_);
camera_info_.clear();
pending_external_camera_info_.clear();
vcd_info_monitor_impl_.AsyncCall(&VCDInfoMonitorImpl::CleanMappings);
NotifyVideoCaptureDevicesChanged();
}
bool CameraHalDelegate::UpdateBuiltInCameraInfo() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!ipc_task_runner_->BelongsToCurrentThread());
if (!camera_module_has_been_set_.TimedWait(kEventWaitTimeoutSecs)) {
LOG(ERROR) << "Camera module not set; platform camera service might not be "
"ready yet";
return false;
}
if (builtin_camera_info_updated_.IsSignaled()) {
return true;
}
// The built-in camera are static per specification of the Android camera HAL
// v3 specification. We only update the built-in camera info once.
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CameraHalDelegate::UpdateBuiltInCameraInfoOnIpcThread,
base::Unretained(this)));
if (!builtin_camera_info_updated_.TimedWait(kEventWaitTimeoutSecs)) {
LOG(ERROR) << "Timed out getting camera info";
return false;
}
return true;
}
void CameraHalDelegate::UpdateBuiltInCameraInfoOnIpcThread() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
camera_module_->GetNumberOfCameras(
base::BindOnce(&CameraHalDelegate::OnGotNumberOfCamerasOnIpcThread,
base::Unretained(this)));
}
void CameraHalDelegate::OnGotNumberOfCamerasOnIpcThread(int32_t num_cameras) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
base::AutoLock lock(camera_info_lock_);
if (num_cameras < 0) {
builtin_camera_info_updated_.Signal();
LOG(ERROR) << "Failed to get number of cameras: " << num_cameras;
return;
}
CAMERA_LOG(EVENT) << "Number of built-in cameras: " << num_cameras;
num_builtin_cameras_ = num_cameras;
// Per camera HAL v3 specification SetCallbacks() should be called after the
// first time GetNumberOfCameras() is called, and before other CameraModule
// functions are called.
camera_module_->SetCallbacksAssociated(
camera_module_callbacks_.BindNewEndpointAndPassRemote(),
base::BindOnce(&CameraHalDelegate::OnSetCallbacksOnIpcThread,
base::Unretained(this)));
camera_module_->GetVendorTagOps(
vendor_tag_ops_delegate_->MakeReceiver(),
base::BindOnce(&CameraHalDelegate::OnGotVendorTagOpsOnIpcThread,
base::Unretained(this)));
}
void CameraHalDelegate::OnSetCallbacksOnIpcThread(int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
base::AutoLock lock(camera_info_lock_);
if (result) {
num_builtin_cameras_ = 0;
builtin_camera_info_updated_.Signal();
LOG(ERROR) << "Failed to set camera module callbacks: "
<< base::safe_strerror(-result);
return;
}
if (num_builtin_cameras_ == 0) {
builtin_camera_info_updated_.Signal();
return;
}
for (size_t camera_id = 0; camera_id < num_builtin_cameras_; ++camera_id) {
GetCameraInfoOnIpcThread(
camera_id,
base::BindOnce(&CameraHalDelegate::OnGotCameraInfoOnIpcThread,
base::Unretained(this), camera_id));
}
}
void CameraHalDelegate::OnGotVendorTagOpsOnIpcThread() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
vendor_tag_ops_delegate_->Initialize();
}
void CameraHalDelegate::GetCameraInfoOnIpcThread(
int32_t camera_id,
GetCameraInfoCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
camera_module_->GetCameraInfo(camera_id, std::move(callback));
}
void CameraHalDelegate::OnGotCameraInfoOnIpcThread(
int32_t camera_id,
int32_t result,
cros::mojom::CameraInfoPtr camera_info) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
DVLOG(1) << "Got camera info of camera " << camera_id;
if (result) {
LOG(ERROR) << "Failed to get camera info. Camera id: " << camera_id;
return;
}
SortCameraMetadata(&camera_info->static_camera_characteristics);
base::AutoLock lock(camera_info_lock_);
camera_info_[camera_id] = std::move(camera_info);
if (camera_id < base::checked_cast<int32_t>(num_builtin_cameras_)) {
// |camera_info_| might contain some entries for external cameras as well,
// we should check all built-in cameras explicitly.
bool all_updated = [&]() {
camera_info_lock_.AssertAcquired();
for (size_t i = 0; i < num_builtin_cameras_; i++) {
if (camera_info_.find(i) == camera_info_.end()) {
return false;
}
}
return true;
}();
if (all_updated) {
builtin_camera_info_updated_.Signal();
}
} else {
// It's an external camera.
pending_external_camera_info_.erase(camera_id);
if (pending_external_camera_info_.empty()) {
external_camera_info_updated_.Signal();
}
NotifyVideoCaptureDevicesChanged();
}
if (camera_info_.size() == 1) {
has_camera_connected_.Signal();
}
}
int32_t CameraHalDelegate::GetMaskedModuleID(const std::string module_id) {
if (module_id.size() == 9) {
int vid = strtol(module_id.substr(0, 4).c_str(), nullptr, 16);
int pid = strtol(module_id.substr(5, 8).c_str(), nullptr, 16);
int decimal_module_id = (vid << 16) + pid;
if (base::Contains(module_id_set, decimal_module_id)) {
return decimal_module_id;
}
}
return static_cast<int32_t>(PopularCamPeriphModuleID::kOthers);
}
void CameraHalDelegate::OpenDeviceOnIpcThread(
int32_t camera_id,
const std::string& module_id, /* such as abcd:1234, 8 digits hex string */
mojo::PendingReceiver<cros::mojom::Camera3DeviceOps> device_ops_receiver,
OpenDeviceCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
base::UmaHistogramSparse("ChromeOS.Camera.ModuleID",
GetMaskedModuleID(module_id));
camera_module_->OpenDevice(camera_id, std::move(device_ops_receiver),
std::move(callback));
}
// CameraModuleCallbacks implementations.
void CameraHalDelegate::CameraDeviceStatusChange(
int32_t camera_id,
cros::mojom::CameraDeviceStatus new_status) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
CAMERA_LOG(EVENT) << "camera_id = " << camera_id
<< ", new_status = " << new_status;
base::AutoLock lock(camera_info_lock_);
auto it = camera_info_.find(camera_id);
switch (new_status) {
case cros::mojom::CameraDeviceStatus::CAMERA_DEVICE_STATUS_PRESENT:
if (it == camera_info_.end()) {
// Get info for the newly connected external camera.
// |has_camera_connected_| might be signaled in
// OnGotCameraInfoOnIpcThread().
pending_external_camera_info_.insert(camera_id);
if (pending_external_camera_info_.size() == 1) {
external_camera_info_updated_.Reset();
}
GetCameraInfoOnIpcThread(
camera_id,
base::BindOnce(&CameraHalDelegate::OnGotCameraInfoOnIpcThread,
base::Unretained(this), camera_id));
} else {
LOG(WARNING) << "Ignore duplicated camera_id = " << camera_id;
}
break;
case cros::mojom::CameraDeviceStatus::CAMERA_DEVICE_STATUS_NOT_PRESENT:
if (it != camera_info_.end()) {
camera_info_.erase(it);
if (camera_info_.empty()) {
has_camera_connected_.Reset();
}
NotifyVideoCaptureDevicesChanged();
} else {
LOG(WARNING) << "Ignore nonexistent camera_id = " << camera_id;
}
break;
default:
NOTREACHED_IN_MIGRATION() << "Unexpected new status " << new_status;
}
}
void CameraHalDelegate::TorchModeStatusChange(
int32_t camera_id,
cros::mojom::TorchModeStatus new_status) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
// Do nothing here as we don't care about torch mode status.
}
void CameraHalDelegate::BootStrapCameraServiceConnection() {
camera_module_connector_ = base::SequenceBound<CameraModuleConnector>(
ui_task_runner_,
base::BindPostTask(
ipc_task_runner_,
base::BindRepeating(&CameraHalDelegate::SetCameraModuleOnIpcThread,
base::Unretained(this))));
}
bool CameraHalDelegate::WaitForCameraModuleReadyForTesting() {
DCHECK(!ipc_task_runner_->BelongsToCurrentThread());
if (camera_module_has_been_set_.IsSignaled()) {
return true;
}
return camera_module_has_been_set_.TimedWait(base::Seconds(10));
}
void CameraHalDelegate::NotifyVideoCaptureDevicesChanged() {
system_event_monitor_proxy_->NotifyVideoCaptureDevicesChanged();
}
} // namespace media