| Index: media/capture/video/chromeos/arc_camera3_service.cc
|
| diff --git a/media/capture/video/chromeos/arc_camera3_service.cc b/media/capture/video/chromeos/arc_camera3_service.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..4e27c2d9d625b29dbf043d15e91d0ab4b7effcc6
|
| --- /dev/null
|
| +++ b/media/capture/video/chromeos/arc_camera3_service.cc
|
| @@ -0,0 +1,327 @@
|
| +// 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/arc_camera3_service.h"
|
| +
|
| +#include <fcntl.h>
|
| +#include <grp.h>
|
| +#include <poll.h>
|
| +#include <sys/uio.h>
|
| +
|
| +#include "base/files/file_path.h"
|
| +#include "base/files/file_util.h"
|
| +#include "base/posix/eintr_wrapper.h"
|
| +#include "mojo/edk/embedder/embedder.h"
|
| +#include "mojo/edk/embedder/named_platform_handle.h"
|
| +#include "mojo/edk/embedder/named_platform_handle_utils.h"
|
| +#include "mojo/edk/embedder/pending_process_connection.h"
|
| +#include "mojo/edk/embedder/platform_channel_pair.h"
|
| +#include "mojo/edk/embedder/platform_channel_utils_posix.h"
|
| +#include "mojo/edk/embedder/platform_handle_vector.h"
|
| +#include "mojo/edk/embedder/scoped_platform_handle.h"
|
| +
|
| +namespace media {
|
| +
|
| +namespace {
|
| +
|
| +const base::FilePath::CharType kArcCamera3SocketPath[] =
|
| + "/var/run/camera/camera3.sock";
|
| +const char kArcCameraGroup[] = "arc-camera";
|
| +
|
| +// Creates a pipe. Returns true on success, otherwise false.
|
| +// On success, |read_fd| will be set to the fd of the read side, and
|
| +// |write_fd| will be set to the one of write side.
|
| +static bool CreatePipe(base::ScopedFD* read_fd, base::ScopedFD* write_fd) {
|
| + int fds[2];
|
| + if (pipe2(fds, O_NONBLOCK | O_CLOEXEC) < 0) {
|
| + PLOG(ERROR) << "pipe2 failed: ";
|
| + return false;
|
| + }
|
| +
|
| + read_fd->reset(fds[0]);
|
| + write_fd->reset(fds[1]);
|
| + return true;
|
| +}
|
| +
|
| +// Waits until |raw_socket_fd| is readable.
|
| +// The operation may be cancelled originally triggered by user interaction to
|
| +// disable ARC, or ARC instance is unexpectedly stopped (e.g. crash).
|
| +// To notify such a situation, |raw_cancel_fd| is also passed to here, and the
|
| +// write side will be closed in such a case.
|
| +static bool WaitForSocketReadable(int raw_socket_fd, int raw_cancel_fd) {
|
| + struct pollfd fds[2] = {
|
| + {raw_socket_fd, POLLIN, 0}, {raw_cancel_fd, POLLIN, 0},
|
| + };
|
| +
|
| + if (HANDLE_EINTR(poll(fds, arraysize(fds), -1)) <= 0) {
|
| + PLOG(ERROR) << "poll failed: ";
|
| + return false;
|
| + }
|
| +
|
| + if (fds[1].revents) {
|
| + VLOG(1) << "Stop() was called";
|
| + return false;
|
| + }
|
| +
|
| + DCHECK(fds[0].revents);
|
| + return true;
|
| +}
|
| +
|
| +class MojoCameraClient : public ArcCamera3Service::CameraClientObserver {
|
| + public:
|
| + explicit MojoCameraClient(arc::mojom::ArcCamera3ClientPtr client)
|
| + : client_(std::move(client)) {}
|
| +
|
| + void NotifyCameraHalRegistered(
|
| + arc::mojom::CameraModulePtr camera_module) override {
|
| + client_->NotifyCameraHalRegistered(std::move(camera_module));
|
| + }
|
| +
|
| + arc::mojom::ArcCamera3ClientPtr& client() { return client_; }
|
| +
|
| + private:
|
| + arc::mojom::ArcCamera3ClientPtr client_;
|
| + DISALLOW_IMPLICIT_CONSTRUCTORS(MojoCameraClient);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +ArcCamera3Service::CameraHal::CameraHal(arc::mojom::ArcCamera3HalPtr camera_hal)
|
| + : camera_hal_(std::move(camera_hal)) {}
|
| +
|
| +void ArcCamera3Service::CameraHal::OpenCameraHal(
|
| + const base::Callback<void(arc::mojom::CameraModulePtr camera_module)>
|
| + callback) {
|
| + camera_hal_->OpenCameraHal(callback);
|
| +}
|
| +
|
| +ArcCamera3Service::ArcCamera3Service()
|
| + : proxy_thread_("ProxyThread"), blocking_io_thread_("BlockingIOThread") {}
|
| +
|
| +ArcCamera3Service::~ArcCamera3Service() {
|
| + VLOG(1) << "Stopping ArcCamera3Service...";
|
| + proxy_thread_.task_runner()->PostTask(
|
| + FROM_HERE, base::Bind(&ArcCamera3Service::StopOnProxyThread,
|
| + base::Unretained(this)));
|
| + proxy_thread_.Stop();
|
| + blocking_io_thread_.Stop();
|
| + VLOG(1) << "ArcCamera3Service stopped";
|
| +}
|
| +
|
| +// static
|
| +ArcCamera3Service* ArcCamera3Service::GetInstance() {
|
| + return base::Singleton<ArcCamera3Service>::get();
|
| +}
|
| +
|
| +bool ArcCamera3Service::Start() {
|
| + DCHECK(!proxy_thread_.IsRunning());
|
| + DCHECK(!blocking_io_thread_.IsRunning());
|
| +
|
| + proxy_thread_.Start();
|
| + blocking_io_thread_.Start();
|
| + proxy_task_runner_ = proxy_thread_.task_runner();
|
| + blocking_io_task_runner_ = blocking_io_thread_.task_runner();
|
| +
|
| + blocking_io_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&ArcCamera3Service::CreateSocket, base::Unretained(this)));
|
| + return true;
|
| +}
|
| +
|
| +void ArcCamera3Service::StopOnProxyThread() {
|
| + DCHECK(proxy_task_runner_->BelongsToCurrentThread());
|
| + base::DeleteFile(base::FilePath(kArcCamera3SocketPath),
|
| + /* recursive */ false);
|
| + // Close |cancel_pipe_| to quit the loop in WaitForIncomingConnection.
|
| + cancel_pipe_.reset();
|
| + client_observers_.clear();
|
| + camera_hal_.reset();
|
| + bindings_.clear();
|
| +}
|
| +
|
| +void ArcCamera3Service::AddClientObserver(
|
| + std::unique_ptr<CameraClientObserver> observer) {
|
| + if (camera_hal_) {
|
| + proxy_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&CameraHal::OpenCameraHal, camera_hal_->AsWeakPtr(),
|
| + base::Bind(&CameraClientObserver::NotifyCameraHalRegistered,
|
| + base::AsWeakPtr(observer.get()))));
|
| + }
|
| + client_observers_.insert(std::move(observer));
|
| +}
|
| +
|
| +void ArcCamera3Service::RegisterCameraHal(
|
| + arc::mojom::ArcCamera3HalPtr camera_hal) {
|
| + DCHECK(proxy_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(!camera_hal_);
|
| + camera_hal.set_connection_error_handler(base::Bind(
|
| + &ArcCamera3Service::OnCameraHalConnectionError, base::Unretained(this)));
|
| + camera_hal_.reset(new CameraHal(std::move(camera_hal)));
|
| + VLOG(1) << "Camera HAL registered";
|
| + for (auto& observer : client_observers_) {
|
| + proxy_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&CameraHal::OpenCameraHal, camera_hal_->AsWeakPtr(),
|
| + base::Bind(&CameraClientObserver::NotifyCameraHalRegistered,
|
| + base::AsWeakPtr(observer.get()))));
|
| + }
|
| +}
|
| +
|
| +void ArcCamera3Service::RegisterClient(arc::mojom::ArcCamera3ClientPtr client) {
|
| + DCHECK(proxy_task_runner_->BelongsToCurrentThread());
|
| + MojoCameraClient* camera_client = new MojoCameraClient(std::move(client));
|
| + std::unique_ptr<CameraClientObserver> client_observer(camera_client);
|
| + camera_client->client().set_connection_error_handler(base::Bind(
|
| + &ArcCamera3Service::OnMojoClientConnectionError, base::Unretained(this),
|
| + base::Unretained(client_observer.get())));
|
| + AddClientObserver(std::move(client_observer));
|
| + VLOG(1) << "New mojo client registered";
|
| +}
|
| +
|
| +void ArcCamera3Service::CreateSocket() {
|
| + DCHECK(blocking_io_task_runner_->BelongsToCurrentThread());
|
| +
|
| + base::FilePath socket_path(kArcCamera3SocketPath);
|
| + mojo::edk::ScopedPlatformHandle socket_fd = mojo::edk::CreateServerHandle(
|
| + mojo::edk::NamedPlatformHandle(socket_path.value()));
|
| + if (!socket_fd.is_valid()) {
|
| + LOG(ERROR) << "Failed to create the socket file: " << kArcCamera3SocketPath;
|
| + return;
|
| + }
|
| +
|
| + // Change permissions on the socket.
|
| + struct group arc_camera_group;
|
| + struct group* result = nullptr;
|
| + char buf[1024];
|
| + if (HANDLE_EINTR(getgrnam_r(kArcCameraGroup, &arc_camera_group, buf,
|
| + sizeof(buf), &result)) < 0) {
|
| + PLOG(ERROR) << "getgrnam_r failed: ";
|
| + return;
|
| + }
|
| +
|
| + if (!result) {
|
| + LOG(ERROR) << "Group '" << kArcCameraGroup << "' not found";
|
| + return;
|
| + }
|
| +
|
| + if (HANDLE_EINTR(chown(kArcCamera3SocketPath, -1, arc_camera_group.gr_gid)) <
|
| + 0) {
|
| + PLOG(ERROR) << "chown failed: ";
|
| + return;
|
| + }
|
| +
|
| + if (!base::SetPosixFilePermissions(socket_path, 0660)) {
|
| + PLOG(ERROR) << "Could not set permissions: " << socket_path.value() << ": ";
|
| + return;
|
| + }
|
| +
|
| + blocking_io_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&ArcCamera3Service::CreateServiceLoop,
|
| + base::Unretained(this), base::Passed(&socket_fd)));
|
| +}
|
| +
|
| +void ArcCamera3Service::CreateServiceLoop(
|
| + mojo::edk::ScopedPlatformHandle socket_fd) {
|
| + DCHECK(blocking_io_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(!proxy_fd_.is_valid());
|
| + DCHECK(!cancel_pipe_.is_valid());
|
| + DCHECK(socket_fd.is_valid());
|
| +
|
| + proxy_fd_ = std::move(socket_fd);
|
| +
|
| + base::ScopedFD cancel_fd;
|
| + if (!CreatePipe(&cancel_fd, &cancel_pipe_)) {
|
| + LOG(ERROR) << "Failed to create cancel pipe";
|
| + return;
|
| + }
|
| +
|
| + blocking_io_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&ArcCamera3Service::WaitForIncomingConnection,
|
| + base::Unretained(this), base::Passed(&cancel_fd)));
|
| + VLOG(1) << "ArcCamera3Service started";
|
| +}
|
| +
|
| +void ArcCamera3Service::WaitForIncomingConnection(base::ScopedFD cancel_fd) {
|
| + DCHECK(blocking_io_task_runner_->BelongsToCurrentThread());
|
| + DCHECK(proxy_fd_.is_valid());
|
| + VLOG(1) << "Waiting for incoming connection...";
|
| +
|
| + if (!proxy_fd_.is_valid()) {
|
| + LOG(ERROR) << "Invalid proxy fd";
|
| + return;
|
| + }
|
| +
|
| + if (!WaitForSocketReadable(proxy_fd_.get().handle, cancel_fd.get())) {
|
| + VLOG(1) << "Quit ArcCamera3Service IO thread";
|
| + return;
|
| + }
|
| +
|
| + mojo::edk::ScopedPlatformHandle accepted_fd;
|
| + if (mojo::edk::ServerAcceptConnection(proxy_fd_.get(), &accepted_fd, false) &&
|
| + accepted_fd.is_valid()) {
|
| + VLOG(1) << "Accepted a connection";
|
| + // Hardcode pid 0 since it is unused in mojo.
|
| + const base::ProcessHandle kUnusedChildProcessHandle = 0;
|
| + mojo::edk::PlatformChannelPair channel_pair;
|
| + mojo::edk::PendingProcessConnection process;
|
| + process.Connect(
|
| + kUnusedChildProcessHandle,
|
| + mojo::edk::ConnectionParams(channel_pair.PassServerHandle()));
|
| +
|
| + mojo::edk::ScopedPlatformHandleVectorPtr handles(
|
| + new mojo::edk::PlatformHandleVector{
|
| + channel_pair.PassClientHandle().release()});
|
| +
|
| + std::string token;
|
| + mojo::ScopedMessagePipeHandle message_pipe =
|
| + process.CreateMessagePipe(&token);
|
| +
|
| + struct iovec iov = {const_cast<char*>(token.c_str()), token.length()};
|
| + ssize_t result = mojo::edk::PlatformChannelSendmsgWithHandles(
|
| + accepted_fd.get(), &iov, 1, handles->data(), handles->size());
|
| + if (result == -1) {
|
| + PLOG(ERROR) << "sendmsg failed: ";
|
| + } else {
|
| + proxy_task_runner_->PostTask(
|
| + FROM_HERE,
|
| + base::Bind(&ArcCamera3Service::OnPeerConnected,
|
| + base::Unretained(this), base::Passed(&message_pipe)));
|
| + }
|
| + }
|
| +
|
| + // Continue waiting for new connections.
|
| + blocking_io_task_runner_->PostTask(
|
| + FROM_HERE, base::Bind(&ArcCamera3Service::WaitForIncomingConnection,
|
| + base::Unretained(this), base::Passed(&cancel_fd)));
|
| +}
|
| +
|
| +void ArcCamera3Service::OnPeerConnected(
|
| + mojo::ScopedMessagePipeHandle message_pipe) {
|
| + DCHECK(proxy_task_runner_->BelongsToCurrentThread());
|
| + auto binding = base::MakeUnique<mojo::Binding<arc::mojom::ArcCamera3Service>>(
|
| + this, std::move(message_pipe));
|
| + bindings_.push_back(std::move(binding));
|
| + VLOG(1) << "New binding added";
|
| +}
|
| +
|
| +void ArcCamera3Service::OnCameraHalConnectionError() {
|
| + DCHECK(proxy_task_runner_->BelongsToCurrentThread());
|
| + VLOG(1) << "Camera HAL connection lost";
|
| + camera_hal_.reset();
|
| +}
|
| +
|
| +void ArcCamera3Service::OnMojoClientConnectionError(
|
| + CameraClientObserver* client_observer) {
|
| + DCHECK(proxy_task_runner_->BelongsToCurrentThread());
|
| + for (auto& it : client_observers_) {
|
| + if (it.get() == client_observer) {
|
| + client_observers_.erase(it);
|
| + VLOG(1) << "Mojo client connection lost";
|
| + break;
|
| + }
|
| + }
|
| +}
|
| +
|
| +} // namespace media
|
|
|