blob: ed97f81e0b43f411dd48c0437a546d8152ecdcfa [file] [log] [blame]
// Copyright 2017 The Chromium 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 "media/capture/video/chromeos/camera_hal_delegate.h"
#include <fcntl.h>
#include <sys/uio.h>
#include <algorithm>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/posix/safe_strerror.h"
#include "base/process/launch.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_split.h"
#include "base/system/system_monitor.h"
#include "media/capture/video/chromeos/camera_app_device_bridge_impl.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/video_capture_device_chromeos_halv3.h"
namespace media {
namespace {
const base::TimeDelta kEventWaitTimeoutMs =
base::TimeDelta::FromMilliseconds(3000);
class LocalCameraClientObserver : public CameraClientObserver {
public:
explicit LocalCameraClientObserver(
scoped_refptr<CameraHalDelegate> camera_hal_delegate)
: camera_hal_delegate_(std::move(camera_hal_delegate)) {}
void OnChannelCreated(cros::mojom::CameraModulePtr camera_module) override {
camera_hal_delegate_->SetCameraModule(camera_module.PassInterface());
}
private:
scoped_refptr<CameraHalDelegate> camera_hal_delegate_;
DISALLOW_IMPLICIT_CONSTRUCTORS(LocalCameraClientObserver);
};
// chromeos::system::StatisticsProvider::IsRunningOnVM() is not 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<base::StringPiece> lines = base::SplitStringPieceUsingSubstr(
output, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
return std::any_of(lines.begin(), lines.end(), [](const auto& line) {
return base::StartsWith(line, "vivid", base::CompareCase::SENSITIVE);
});
}
void NotifyVideoCaptureDevicesChanged() {
base::SystemMonitor* monitor = base::SystemMonitor::Get();
// |monitor| might be nullptr in unittest.
if (monitor) {
monitor->ProcessDevicesChanged(
base::SystemMonitor::DeviceType::DEVTYPE_VIDEO_CAPTURE);
}
}
} // namespace
CameraHalDelegate::CameraHalDelegate(
scoped_refptr<base::SingleThreadTaskRunner> ipc_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()),
ipc_task_runner_(std::move(ipc_task_runner)),
camera_module_callbacks_(this),
vendor_tag_ops_delegate_(ipc_task_runner_) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
CameraHalDelegate::~CameraHalDelegate() = default;
void CameraHalDelegate::RegisterCameraClient() {
CameraHalDispatcherImpl::GetInstance()->AddClientObserver(
std::make_unique<LocalCameraClientObserver>(this));
}
void CameraHalDelegate::SetCameraModule(
cros::mojom::CameraModulePtrInfo camera_module_ptr_info) {
ipc_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CameraHalDelegate::SetCameraModuleOnIpcThread,
this, base::Passed(&camera_module_ptr_info)));
}
void CameraHalDelegate::Reset() {
ipc_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CameraHalDelegate::ResetMojoInterfaceOnIpcThread, this));
}
std::unique_ptr<VideoCaptureDevice> CameraHalDelegate::CreateDevice(
scoped_refptr<base::SingleThreadTaskRunner> task_runner_for_screen_observer,
const VideoCaptureDeviceDescriptor& device_descriptor,
CameraAppDeviceBridgeImpl* camera_app_device_bridge) {
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;
}
if (camera_app_device_bridge) {
auto* camera_app_device = camera_app_device_bridge->GetCameraAppDevice(
device_descriptor.device_id);
// Since the cleanup callback will be triggered when VideoCaptureDevice died
// and |camera_app_device_bridge| is actually owned by
// VideoCaptureServiceImpl, it should be safe to assume
// |camera_app_device_bridge| is still valid here.
auto cleanup_callback = base::BindOnce(
[](const std::string& device_id, CameraAppDeviceBridgeImpl* bridge) {
bridge->OnDeviceClosed(device_id);
},
device_descriptor.device_id, camera_app_device_bridge);
return std::make_unique<VideoCaptureDeviceChromeOSHalv3>(
std::move(task_runner_for_screen_observer), device_descriptor, this,
camera_app_device, std::move(cleanup_callback));
} else {
return std::make_unique<VideoCaptureDeviceChromeOSHalv3>(
std::move(task_runner_for_screen_observer), device_descriptor, this,
nullptr, base::DoNothing());
}
}
void CameraHalDelegate::GetSupportedFormats(
const VideoCaptureDeviceDescriptor& device_descriptor,
VideoCaptureFormats* supported_formats) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!UpdateBuiltInCameraInfo()) {
return;
}
base::AutoLock lock(camera_info_lock_);
int camera_id = GetCameraIdFromDeviceId(device_descriptor.device_id);
if (camera_id == -1 || camera_info_[camera_id].is_null()) {
LOG(ERROR) << "Invalid camera device: " << device_descriptor.device_id;
return;
}
const cros::mojom::CameraInfoPtr& camera_info = camera_info_[camera_id];
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;
const ChromiumPixelFormat cr_format =
camera_buffer_factory_->ResolveStreamBufferFormat(hal_format);
if (cr_format.video_format == PIXEL_FORMAT_UNKNOWN) {
continue;
}
VLOG(1) << "Supported format: " << width << "x" << height
<< " fps=" << max_fps << " format=" << cr_format.video_format;
supported_formats->emplace_back(gfx::Size(width, height), max_fps,
cr_format.video_format);
}
}
void CameraHalDelegate::GetDeviceDescriptors(
VideoCaptureDeviceDescriptors* device_descriptors) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!UpdateBuiltInCameraInfo()) {
return;
}
if (!external_camera_info_updated_.TimedWait(
base::TimeDelta::FromSeconds(1))) {
LOG(ERROR) << "Failed to get camera info from all external cameras";
}
if (IsRunningOnVM() && IsVividLoaded()) {
has_camera_connected_.TimedWait(base::TimeDelta::FromSeconds(1));
}
base::AutoLock info_lock(camera_info_lock_);
base::AutoLock id_map_lock(device_id_to_camera_id_lock_);
for (const auto& it : camera_info_) {
int camera_id = it.first;
const cros::mojom::CameraInfoPtr& camera_info = it.second;
if (!camera_info) {
continue;
}
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;
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();
};
// 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");
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});
}
break;
// Mojo validates the input parameters for us so we don't need to worry
// about malformed values.
}
}
device_id_to_camera_id_[desc.device_id] = camera_id;
device_descriptors->push_back(desc);
}
// TODO(shik): Report external camera first when lid is closed.
// TODO(jcliang): Remove this after JS API supports query camera facing
// (http://crbug.com/543997).
std::sort(device_descriptors->begin(), device_descriptors->end());
DVLOG(1) << "Number of device descriptors: " << device_descriptors->size();
}
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 {};
}
return it->second.Clone();
}
void CameraHalDelegate::OpenDevice(
int32_t camera_id,
cros::mojom::Camera3DeviceOpsRequest device_ops_request,
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, this, camera_id,
base::Passed(&device_ops_request), 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;
}
void CameraHalDelegate::SetCameraModuleOnIpcThread(
cros::mojom::CameraModulePtrInfo camera_module_ptr_info) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (camera_module_.is_bound()) {
LOG(ERROR) << "CameraModule is already bound";
return;
}
camera_module_ = mojo::MakeProxy(std::move(camera_module_ptr_info));
camera_module_.set_connection_error_handler(
base::BindOnce(&CameraHalDelegate::ResetMojoInterfaceOnIpcThread, this));
camera_module_has_been_set_.Signal();
}
void CameraHalDelegate::ResetMojoInterfaceOnIpcThread() {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
camera_module_.reset();
if (camera_module_callbacks_.is_bound()) {
camera_module_callbacks_.Close();
}
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.
camera_info_.clear();
pending_external_camera_info_.clear();
}
bool CameraHalDelegate::UpdateBuiltInCameraInfo() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!ipc_task_runner_->BelongsToCurrentThread());
camera_module_has_been_set_.Wait();
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,
this));
if (!builtin_camera_info_updated_.TimedWait(kEventWaitTimeoutMs)) {
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, this));
}
void CameraHalDelegate::OnGotNumberOfCamerasOnIpcThread(int32_t num_cameras) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
if (num_cameras < 0) {
builtin_camera_info_updated_.Signal();
LOG(ERROR) << "Failed to get number of cameras: " << num_cameras;
return;
}
VLOG(1) << "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.
cros::mojom::CameraModuleCallbacksPtr camera_module_callbacks_ptr;
cros::mojom::CameraModuleCallbacksRequest camera_module_callbacks_request =
mojo::MakeRequest(&camera_module_callbacks_ptr);
camera_module_callbacks_.Bind(std::move(camera_module_callbacks_request));
camera_module_->SetCallbacks(
std::move(camera_module_callbacks_ptr),
base::BindOnce(&CameraHalDelegate::OnSetCallbacksOnIpcThread, this));
camera_module_->GetVendorTagOps(
vendor_tag_ops_delegate_.MakeRequest(),
base::BindOnce(&CameraHalDelegate::OnGotVendorTagOpsOnIpcThread, this));
}
void CameraHalDelegate::OnSetCallbacksOnIpcThread(int32_t result) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
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, 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;
}
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 = [&]() {
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();
}
}
void CameraHalDelegate::OpenDeviceOnIpcThread(
int32_t camera_id,
cros::mojom::Camera3DeviceOpsRequest device_ops_request,
OpenDeviceCallback callback) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
camera_module_->OpenDevice(camera_id, std::move(device_ops_request),
std::move(callback));
}
// CameraModuleCallbacks implementations.
void CameraHalDelegate::CameraDeviceStatusChange(
int32_t camera_id,
cros::mojom::CameraDeviceStatus new_status) {
DCHECK(ipc_task_runner_->BelongsToCurrentThread());
VLOG(1) << "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, 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() << "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.
}
} // namespace media