blob: 7f090232777729137e6ad5deca154c8c950479e2 [file] [log] [blame]
// Copyright (c) 2012 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.
//
// Implementation notes about interactions with VideoCaptureImpl.
//
// How is VideoCaptureImpl used:
//
// VideoCaptureImpl is an IO thread object while VideoCaptureImplManager
// lives only on the render thread. It is only possible to access an
// object of VideoCaptureImpl via a task on the IO thread.
//
// How is VideoCaptureImpl deleted:
//
// A task is posted to the IO thread to delete a VideoCaptureImpl.
// Immediately after that the pointer to it is dropped. This means no
// access to this VideoCaptureImpl object is possible on the render
// thread. Also note that VideoCaptureImpl does not post task to itself.
//
// The use of Unretained:
//
// We make sure deletion is the last task on the IO thread for a
// VideoCaptureImpl object. This allows the use of Unretained() binding.
#include "content/renderer/media/video_capture_impl_manager.h"
#include <algorithm>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/child/child_process.h"
#include "content/renderer/media/video_capture_impl.h"
namespace content {
struct VideoCaptureImplManager::DeviceEntry {
media::VideoCaptureSessionId session_id;
// To be used and destroyed only on the IO thread.
std::unique_ptr<VideoCaptureImpl> impl;
// Number of clients using |impl|.
int client_count;
// This is set to true if this device is being suspended, via
// VideoCaptureImplManager::Suspend().
// See also: VideoCaptureImplManager::is_suspending_all_.
bool is_individually_suspended;
DeviceEntry()
: session_id(0), client_count(0), is_individually_suspended(false) {}
DeviceEntry(DeviceEntry&& other) = default;
DeviceEntry& operator=(DeviceEntry&& other) = default;
~DeviceEntry() = default;
};
VideoCaptureImplManager::VideoCaptureImplManager()
: next_client_id_(0),
render_main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
is_suspending_all_(false),
weak_factory_(this) {}
VideoCaptureImplManager::~VideoCaptureImplManager() {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
if (devices_.empty())
return;
// Forcibly release all video capture resources.
for (auto& entry : devices_) {
ChildProcess::current()->io_task_runner()->DeleteSoon(FROM_HERE,
entry.impl.release());
}
devices_.clear();
}
base::Closure VideoCaptureImplManager::UseDevice(
media::VideoCaptureSessionId id) {
DVLOG(1) << __func__ << " session id: " << id;
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
if (it == devices_.end()) {
devices_.push_back(DeviceEntry());
it = devices_.end() - 1;
it->session_id = id;
it->impl = CreateVideoCaptureImplForTesting(id);
if (!it->impl)
it->impl.reset(new VideoCaptureImpl(id));
}
++it->client_count;
// Design limit: When there are multiple clients, VideoCaptureImplManager
// would have to individually track which ones requested suspending/resuming,
// in order to determine whether the whole device should be suspended.
// Instead, handle the non-common use case of multiple clients by just
// resuming the suspended device, and disable suspend functionality while
// there are multiple clients.
if (it->is_individually_suspended)
Resume(id);
return base::Bind(&VideoCaptureImplManager::UnrefDevice,
weak_factory_.GetWeakPtr(), id);
}
base::Closure VideoCaptureImplManager::StartCapture(
media::VideoCaptureSessionId id,
const media::VideoCaptureParams& params,
const VideoCaptureStateUpdateCB& state_update_cb,
const VideoCaptureDeliverFrameCB& deliver_frame_cb) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
// This ID is used to identify a client of VideoCaptureImpl.
const int client_id = ++next_client_id_;
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::StartCapture,
base::Unretained(it->impl.get()), client_id,
params, state_update_cb, deliver_frame_cb));
return base::Bind(&VideoCaptureImplManager::StopCapture,
weak_factory_.GetWeakPtr(), client_id, id);
}
void VideoCaptureImplManager::RequestRefreshFrame(
media::VideoCaptureSessionId id) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::RequestRefreshFrame,
base::Unretained(it->impl.get())));
}
void VideoCaptureImplManager::Suspend(media::VideoCaptureSessionId id) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
if (it->is_individually_suspended)
return; // Device has already been individually suspended.
if (it->client_count > 1)
return; // Punt when there is >1 client (see comments in UseDevice()).
it->is_individually_suspended = true;
if (is_suspending_all_)
return; // Device should already be suspended.
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::SuspendCapture,
base::Unretained(it->impl.get()), true));
}
void VideoCaptureImplManager::Resume(media::VideoCaptureSessionId id) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
if (!it->is_individually_suspended)
return; // Device was not individually suspended.
it->is_individually_suspended = false;
if (is_suspending_all_)
return; // Device must remain suspended until all are resumed.
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::SuspendCapture,
base::Unretained(it->impl.get()), false));
}
void VideoCaptureImplManager::GetDeviceSupportedFormats(
media::VideoCaptureSessionId id,
const VideoCaptureDeviceFormatsCB& callback) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::GetDeviceSupportedFormats,
base::Unretained(it->impl.get()), callback));
}
void VideoCaptureImplManager::GetDeviceFormatsInUse(
media::VideoCaptureSessionId id,
const VideoCaptureDeviceFormatsCB& callback) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::GetDeviceFormatsInUse,
base::Unretained(it->impl.get()), callback));
}
std::unique_ptr<VideoCaptureImpl>
VideoCaptureImplManager::CreateVideoCaptureImplForTesting(
media::VideoCaptureSessionId session_id) const {
return std::unique_ptr<VideoCaptureImpl>();
}
void VideoCaptureImplManager::StopCapture(int client_id,
media::VideoCaptureSessionId id) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::StopCapture,
base::Unretained(it->impl.get()), client_id));
}
void VideoCaptureImplManager::UnrefDevice(
media::VideoCaptureSessionId id) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id] (const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
DCHECK_GT(it->client_count, 0);
--it->client_count;
if (it->client_count > 0)
return;
ChildProcess::current()->io_task_runner()->DeleteSoon(FROM_HERE,
it->impl.release());
devices_.erase(it);
}
void VideoCaptureImplManager::SuspendDevices(
const blink::MediaStreamDevices& video_devices,
bool suspend) {
DCHECK(render_main_task_runner_->BelongsToCurrentThread());
if (is_suspending_all_ == suspend)
return;
is_suspending_all_ = suspend;
for (const blink::MediaStreamDevice& device : video_devices) {
const media::VideoCaptureSessionId id = device.session_id;
const auto it = std::find_if(
devices_.begin(), devices_.end(),
[id](const DeviceEntry& entry) { return entry.session_id == id; });
DCHECK(it != devices_.end());
if (it->is_individually_suspended)
continue; // Either: 1) Already suspended; or 2) Should not be resumed.
ChildProcess::current()->io_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&VideoCaptureImpl::SuspendCapture,
base::Unretained(it->impl.get()), suspend));
}
}
} // namespace content