// 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.
#include "media/capture/video/chromeos/video_capture_device_chromeos_delegate.h"
#include <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/bind_post_task.h"
#include "base/task/single_thread_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "chromeos/ash/components/mojo_service_manager/connection.h"
#include "media/capture/video/chromeos/camera_app_device_bridge_impl.h"
#include "media/capture/video/chromeos/camera_device_delegate.h"
#include "media/capture/video/chromeos/camera_hal_delegate.h"
#include "media/capture/video/chromeos/mojom/system_event_monitor.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "third_party/cros_system_api/mojo/service_constants.h"
namespace media {
class VideoCaptureDeviceChromeOSDelegate::PowerObserver
: public cros::mojom::CrosPowerObserver {
public:
PowerObserver(base::WeakPtr<VideoCaptureDeviceChromeOSDelegate> device,
scoped_refptr<base::SingleThreadTaskRunner> device_task_runner)
: device_suspend_handler_(device_task_runner, std::move(device)) {
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_->AddPowerObserver("VideoCaptureDeviceChromeOSDelegate",
receiver_.BindNewPipeAndPassRemote());
}
PowerObserver(const PowerObserver&) = delete;
PowerObserver& operator=(const PowerObserver&) = delete;
~PowerObserver() override = default;
void OnSystemSuspend(OnSystemSuspendCallback callback) override {
device_suspend_handler_.AsyncCall(&DeviceSuspendHandler::TryCloseDevice)
.WithArgs(base::BindPostTaskToCurrentDefault(std::move(callback)));
}
void OnSystemResume() override {
device_suspend_handler_.AsyncCall(&DeviceSuspendHandler::TryOpenDevice);
}
private:
class DeviceSuspendHandler {
public:
DeviceSuspendHandler(
base::WeakPtr<VideoCaptureDeviceChromeOSDelegate> device)
: device_(std::move(device)) {}
void TryOpenDevice() {
if (!device_) {
return;
}
device_->OpenDevice();
}
void TryCloseDevice(OnSystemSuspendCallback callback) {
if (!device_) {
std::move(callback).Run();
return;
}
device_->CloseDevice(std::move(callback));
}
private:
base::WeakPtr<VideoCaptureDeviceChromeOSDelegate> device_;
};
base::SequenceBound<DeviceSuspendHandler> device_suspend_handler_;
mojo::Remote<cros::mojom::CrosSystemEventMonitor> monitor_;
mojo::Receiver<cros::mojom::CrosPowerObserver> receiver_{this};
};
VideoCaptureDeviceChromeOSDelegate::VideoCaptureDeviceChromeOSDelegate(
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
const VideoCaptureDeviceDescriptor& device_descriptor,
CameraHalDelegate* camera_hal_delegate,
base::OnceClosure cleanup_callback)
: device_descriptor_(device_descriptor),
camera_hal_delegate_(camera_hal_delegate),
capture_task_runner_(base::SingleThreadTaskRunner::GetCurrentDefault()),
camera_device_ipc_thread_(std::string("CameraDeviceIpcThread") +
device_descriptor.device_id),
lens_facing_(device_descriptor.facing),
// External cameras have lens_facing as MEDIA_VIDEO_FACING_NONE.
// We don't want to rotate the frame even if the device rotates.
rotates_with_device_(lens_facing_ !=
VideoFacingMode::MEDIA_VIDEO_FACING_NONE),
rotation_(0),
cleanup_callback_(std::move(cleanup_callback)),
device_closed_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
ui_task_runner_(ui_task_runner) {
power_observer_ = base::SequenceBound<PowerObserver>(
ui_task_runner_, weak_ptr_factory_.GetWeakPtr(), capture_task_runner_);
screen_observer_delegate_ = base::SequenceBound<ScreenObserverDelegate>(
ui_task_runner_,
base::BindPostTask(
capture_task_runner_,
base::BindRepeating(&VideoCaptureDeviceChromeOSDelegate::SetRotation,
weak_ptr_factory_.GetWeakPtr())));
}
VideoCaptureDeviceChromeOSDelegate::~VideoCaptureDeviceChromeOSDelegate() {
camera_hal_delegate_->DisableAllVirtualDevices();
}
void VideoCaptureDeviceChromeOSDelegate::Shutdown() {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
if (!HasDeviceClient()) {
DCHECK(!camera_device_ipc_thread_.IsRunning());
// |cleanup_callback_| will call the destructor, so any access to |this|
// after executing |cleanup_callback_| in this function is unsafe.
std::move(cleanup_callback_).Run();
}
}
bool VideoCaptureDeviceChromeOSDelegate::HasDeviceClient() {
return device_context_ && device_context_->HasClient();
}
void VideoCaptureDeviceChromeOSDelegate::AllocateAndStart(
const VideoCaptureParams& params,
std::unique_ptr<VideoCaptureDevice::Client> client,
ClientType client_type) {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
if (!HasDeviceClient()) {
TRACE_EVENT("camera", "Start Device");
if (!camera_device_ipc_thread_.Start()) {
std::string error_msg = "Failed to start device thread";
LOG(ERROR) << error_msg;
client->OnError(
media::VideoCaptureError::kCrosHalV3FailedToStartDeviceThread,
FROM_HERE, error_msg);
return;
}
device_context_ = std::make_unique<CameraDeviceContext>();
if (device_context_->AddClient(client_type, std::move(client))) {
capture_params_[client_type] = params;
camera_device_delegate_ = std::make_unique<CameraDeviceDelegate>(
device_descriptor_, camera_hal_delegate_,
camera_device_ipc_thread_.task_runner(), ui_task_runner_);
OpenDevice();
}
CameraAppDeviceBridgeImpl::GetInstance()->OnVideoCaptureDeviceCreated(
device_descriptor_.device_id, camera_device_ipc_thread_.task_runner());
} else {
if (device_context_->AddClient(client_type, std::move(client))) {
capture_params_[client_type] = params;
ReconfigureStreams();
}
}
}
void VideoCaptureDeviceChromeOSDelegate::StopAndDeAllocate(
ClientType client_type) {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
DCHECK(camera_device_delegate_);
if (device_context_) {
device_context_->RemoveClient(client_type);
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraDeviceDelegate::OnAllBufferRetired,
camera_device_delegate_->GetWeakPtr(), client_type));
}
if (!HasDeviceClient()) {
CloseDevice(base::DoNothing());
CameraAppDeviceBridgeImpl::GetInstance()->OnVideoCaptureDeviceClosing(
device_descriptor_.device_id);
camera_device_ipc_thread_.task_runner()->DeleteSoon(
FROM_HERE, std::move(camera_device_delegate_));
camera_device_ipc_thread_.Stop();
device_context_.reset();
}
}
void VideoCaptureDeviceChromeOSDelegate::TakePhoto(
VideoCaptureDevice::TakePhotoCallback callback) {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
DCHECK(camera_device_delegate_);
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&CameraDeviceDelegate::TakePhoto,
camera_device_delegate_->GetWeakPtr(),
std::move(callback)));
}
void VideoCaptureDeviceChromeOSDelegate::GetPhotoState(
VideoCaptureDevice::GetPhotoStateCallback callback) {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&CameraDeviceDelegate::GetPhotoState,
camera_device_delegate_->GetWeakPtr(),
std::move(callback)));
}
void VideoCaptureDeviceChromeOSDelegate::SetPhotoOptions(
mojom::PhotoSettingsPtr settings,
VideoCaptureDevice::SetPhotoOptionsCallback callback) {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&CameraDeviceDelegate::SetPhotoOptions,
camera_device_delegate_->GetWeakPtr(),
std::move(settings), std::move(callback)));
}
void VideoCaptureDeviceChromeOSDelegate::OpenDevice() {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
if (!camera_device_delegate_) {
return;
}
// It's safe to pass unretained |device_context_| here since
// VideoCaptureDeviceChromeOSDelegate owns |camera_device_delegate_| and makes
// sure |device_context_| outlives |camera_device_delegate_|.
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraDeviceDelegate::AllocateAndStart,
camera_device_delegate_->GetWeakPtr(), capture_params_,
base::Unretained(device_context_.get())));
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraDeviceDelegate::SetRotation,
camera_device_delegate_->GetWeakPtr(), rotation_));
}
void VideoCaptureDeviceChromeOSDelegate::ReconfigureStreams() {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
DCHECK(camera_device_delegate_);
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraDeviceDelegate::ReconfigureStreams,
camera_device_delegate_->GetWeakPtr(), capture_params_));
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraDeviceDelegate::SetRotation,
camera_device_delegate_->GetWeakPtr(), rotation_));
}
void VideoCaptureDeviceChromeOSDelegate::CloseDevice(
base::OnceClosure suspend_callback) {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
if (!camera_device_delegate_) {
std::move(suspend_callback).Run();
return;
}
// We do our best to allow the camera HAL cleanly shut down the device. In
// general we don't trust the camera HAL so if the device does not close in
// time we simply terminate the Mojo channel by resetting
// |camera_device_delegate_|.
//
// VideoCaptureDeviceChromeOSDelegate owns both |camera_device_delegate_| and
// |device_closed_| and it stops |camera_device_ipc_thread_| in
// StopAndDeAllocate, so it's safe to pass |device_closed_| as unretained in
// the callback.
device_closed_.Reset();
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&CameraDeviceDelegate::StopAndDeAllocate,
camera_device_delegate_->GetWeakPtr(),
base::BindOnce(
[](base::WaitableEvent* device_closed) {
device_closed->Signal();
},
base::Unretained(&device_closed_))));
// TODO(kamesan): Reduce the timeout back to 1 second when we have a solution
// in platform level (b/258048698).
const int kWaitTimeoutSecs = 2;
bool is_signaled = device_closed_.TimedWait(base::Seconds(kWaitTimeoutSecs));
if (!is_signaled) {
LOG(WARNING) << "Camera "
<< camera_hal_delegate_->GetCameraIdFromDeviceId(
device_descriptor_.device_id)
<< " can't be closed in " << kWaitTimeoutSecs << " seconds.";
}
std::move(suspend_callback).Run();
}
void VideoCaptureDeviceChromeOSDelegate::SetRotation(int rotation) {
DCHECK(capture_task_runner_->BelongsToCurrentThread());
if (!rotates_with_device_) {
rotation = 0;
} else if (lens_facing_ == VideoFacingMode::MEDIA_VIDEO_FACING_ENVIRONMENT) {
// Original frame when |rotation| = 0
// -----------------------
// | * |
// | * * |
// | * * |
// | ******* |
// | * * |
// | * * |
// -----------------------
//
// |rotation| = 90, this is what back camera sees
// -----------------------
// | ******** |
// | * **** |
// | * *** |
// | * *** |
// | * **** |
// | ******** |
// -----------------------
//
// |rotation| = 90, this is what front camera sees
// -----------------------
// | ******** |
// | **** * |
// | *** * |
// | *** * |
// | **** * |
// | ******** |
// -----------------------
//
// Therefore, for back camera, we need to rotate (360 - |rotation|).
rotation = (360 - rotation) % 360;
}
rotation_ = rotation;
if (camera_device_ipc_thread_.IsRunning()) {
camera_device_ipc_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&CameraDeviceDelegate::SetRotation,
camera_device_delegate_->GetWeakPtr(), rotation_));
}
}
} // namespace media