blob: 79da41260a3ee26cfefbc80a2e8f0263733caf8a [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.
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include <set>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "content/browser/renderer_host/media/video_capture_controller.h"
#include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
#include "content/browser/renderer_host/media/web_contents_video_capture_device.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_switches.h"
#include "content/public/common/desktop_media_id.h"
#include "content/public/common/media_stream_request.h"
#include "media/base/scoped_histogram_timer.h"
#include "media/video/capture/fake_video_capture_device.h"
#include "media/video/capture/video_capture_device.h"
#if defined(ENABLE_SCREEN_CAPTURE)
#include "content/browser/renderer_host/media/desktop_capture_device.h"
#endif
namespace content {
// Starting id for the first capture session.
// VideoCaptureManager::kStartOpenSessionId is used as default id without
// explicitly calling open device.
enum { kFirstSessionId = VideoCaptureManager::kStartOpenSessionId + 1 };
struct VideoCaptureManager::Controller {
Controller(
VideoCaptureController* vc_controller,
VideoCaptureControllerEventHandler* handler)
: controller(vc_controller),
ready_to_delete(false) {
handlers.push_front(handler);
}
~Controller() {}
scoped_refptr<VideoCaptureController> controller;
bool ready_to_delete;
Handlers handlers;
};
VideoCaptureManager::VideoCaptureManager()
: listener_(NULL),
new_capture_session_id_(kFirstSessionId),
use_fake_device_(false) {
}
VideoCaptureManager::~VideoCaptureManager() {
DCHECK(devices_.empty());
DCHECK(controllers_.empty());
}
void VideoCaptureManager::Register(MediaStreamProviderListener* listener,
base::MessageLoopProxy* device_thread_loop) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(!listener_);
DCHECK(!device_loop_.get());
listener_ = listener;
device_loop_ = device_thread_loop;
}
void VideoCaptureManager::Unregister() {
DCHECK(listener_);
listener_ = NULL;
}
void VideoCaptureManager::EnumerateDevices(MediaStreamType stream_type) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(listener_);
device_loop_->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureManager::OnEnumerateDevices, this, stream_type));
}
int VideoCaptureManager::Open(const StreamDeviceInfo& device) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(listener_);
// Generate a new id for this device.
int video_capture_session_id = new_capture_session_id_++;
device_loop_->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureManager::OnOpen, this, video_capture_session_id,
device));
return video_capture_session_id;
}
void VideoCaptureManager::Close(int capture_session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DCHECK(listener_);
DVLOG(1) << "VideoCaptureManager::Close, id " << capture_session_id;
device_loop_->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureManager::OnClose, this, capture_session_id));
}
void VideoCaptureManager::Start(
const media::VideoCaptureParams& capture_params,
media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
device_loop_->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureManager::OnStart, this, capture_params,
video_capture_receiver));
}
void VideoCaptureManager::Stop(
const media::VideoCaptureSessionId& capture_session_id,
base::Closure stopped_cb) {
DVLOG(1) << "VideoCaptureManager::Stop, id " << capture_session_id;
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
device_loop_->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureManager::OnStop, this, capture_session_id,
stopped_cb));
}
void VideoCaptureManager::UseFakeDevice() {
use_fake_device_ = true;
}
void VideoCaptureManager::OnEnumerateDevices(MediaStreamType stream_type) {
SCOPED_UMA_HISTOGRAM_TIMER(
"Media.VideoCaptureManager.OnEnumerateDevicesTime");
DCHECK(IsOnDeviceThread());
media::VideoCaptureDevice::Names device_names;
GetAvailableDevices(stream_type, &device_names);
scoped_ptr<StreamDeviceInfoArray> devices(new StreamDeviceInfoArray());
for (media::VideoCaptureDevice::Names::iterator it =
device_names.begin(); it != device_names.end(); ++it) {
bool opened = DeviceOpened(*it);
devices->push_back(StreamDeviceInfo(
stream_type, it->GetNameAndModel(), it->id(), opened));
}
PostOnDevicesEnumerated(stream_type, devices.Pass());
}
void VideoCaptureManager::OnOpen(int capture_session_id,
const StreamDeviceInfo& device) {
SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnOpenTime");
DCHECK(IsOnDeviceThread());
DCHECK(devices_.find(capture_session_id) == devices_.end());
DVLOG(1) << "VideoCaptureManager::OnOpen, id " << capture_session_id;
// Check if another session has already opened this device. If so, just
// use that opened device.
media::VideoCaptureDevice* opened_video_capture_device =
GetOpenedDevice(device);
if (opened_video_capture_device) {
DeviceEntry& new_entry = devices_[capture_session_id];
new_entry.stream_type = device.device.type;
new_entry.capture_device = opened_video_capture_device;
PostOnOpened(device.device.type, capture_session_id);
return;
}
scoped_ptr<media::VideoCaptureDevice> video_capture_device;
// Open the device.
switch (device.device.type) {
case MEDIA_DEVICE_VIDEO_CAPTURE: {
// We look up the device id from the renderer in our local enumeration
// since the renderer does not have all the information that might be
// held in the browser-side VideoCaptureDevice::Name structure.
media::VideoCaptureDevice::Name* found =
video_capture_devices_.FindById(device.device.id);
if (found) {
video_capture_device.reset(use_fake_device_ ?
media::FakeVideoCaptureDevice::Create(*found) :
media::VideoCaptureDevice::Create(*found));
}
break;
}
case MEDIA_TAB_VIDEO_CAPTURE: {
video_capture_device.reset(
WebContentsVideoCaptureDevice::Create(device.device.id));
break;
}
case MEDIA_DESKTOP_VIDEO_CAPTURE: {
#if defined(ENABLE_SCREEN_CAPTURE)
DesktopMediaID id = DesktopMediaID::Parse(device.device.id);
if (id.type != DesktopMediaID::TYPE_NONE) {
video_capture_device = DesktopCaptureDevice::Create(id);
}
#endif // defined(ENABLE_SCREEN_CAPTURE)
break;
}
default: {
NOTIMPLEMENTED();
break;
}
}
if (!video_capture_device) {
PostOnError(capture_session_id, kDeviceNotAvailable);
return;
}
DeviceEntry& new_entry = devices_[capture_session_id];
new_entry.stream_type = device.device.type;
new_entry.capture_device = video_capture_device.release();
PostOnOpened(device.device.type, capture_session_id);
}
void VideoCaptureManager::OnClose(int capture_session_id) {
SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnCloseTime");
DCHECK(IsOnDeviceThread());
DVLOG(1) << "VideoCaptureManager::OnClose, id " << capture_session_id;
VideoCaptureDevices::iterator device_it = devices_.find(capture_session_id);
if (device_it == devices_.end()) {
return;
}
const DeviceEntry removed_entry = device_it->second;
devices_.erase(device_it);
Controllers::iterator cit = controllers_.find(removed_entry.capture_device);
if (cit != controllers_.end()) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&VideoCaptureController::StopSession,
cit->second->controller, capture_session_id));
}
if (!DeviceInUse(removed_entry.capture_device)) {
// No other users of this device, deallocate (if not done already) and
// delete the device. No need to take care of the controller, that is done
// by |OnStop|.
removed_entry.capture_device->DeAllocate();
Controllers::iterator cit = controllers_.find(removed_entry.capture_device);
if (cit != controllers_.end()) {
delete cit->second;
controllers_.erase(cit);
}
delete removed_entry.capture_device;
}
PostOnClosed(removed_entry.stream_type, capture_session_id);
}
void VideoCaptureManager::OnStart(
const media::VideoCaptureParams capture_params,
media::VideoCaptureDevice::EventHandler* video_capture_receiver) {
SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnStartTime");
DCHECK(IsOnDeviceThread());
DCHECK(video_capture_receiver != NULL);
DVLOG(1) << "VideoCaptureManager::OnStart, (" << capture_params.width
<< ", " << capture_params.height
<< ", " << capture_params.frame_per_second
<< ", " << capture_params.session_id
<< ")";
media::VideoCaptureDevice* video_capture_device =
GetDeviceInternal(capture_params.session_id);
if (!video_capture_device) {
// Invalid session id.
video_capture_receiver->OnError();
return;
}
// TODO(mcasas): Variable resolution video capture devices, are not yet
// fully supported, see crbug.com/261410, second part, and crbug.com/266082 .
if (capture_params.frame_size_type !=
media::ConstantResolutionVideoCaptureDevice) {
LOG(DFATAL) << "Only constant Video Capture resolution device supported.";
video_capture_receiver->OnError();
return;
}
Controllers::iterator cit = controllers_.find(video_capture_device);
if (cit != controllers_.end()) {
cit->second->ready_to_delete = false;
}
// Possible errors are signaled to video_capture_receiver by
// video_capture_device. video_capture_receiver to perform actions.
media::VideoCaptureCapability params_as_capability_copy;
params_as_capability_copy.width = capture_params.width;
params_as_capability_copy.height = capture_params.height;
params_as_capability_copy.frame_rate = capture_params.frame_per_second;
params_as_capability_copy.session_id = capture_params.session_id;
params_as_capability_copy.frame_size_type = capture_params.frame_size_type;
video_capture_device->Allocate(params_as_capability_copy,
video_capture_receiver);
video_capture_device->Start();
}
void VideoCaptureManager::OnStop(
const media::VideoCaptureSessionId capture_session_id,
base::Closure stopped_cb) {
SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.OnStopTime");
DCHECK(IsOnDeviceThread());
DVLOG(1) << "VideoCaptureManager::OnStop, id " << capture_session_id;
VideoCaptureDevices::iterator it = devices_.find(capture_session_id);
if (it != devices_.end()) {
media::VideoCaptureDevice* video_capture_device = it->second.capture_device;
// Possible errors are signaled to video_capture_receiver by
// video_capture_device. video_capture_receiver to perform actions.
video_capture_device->Stop();
video_capture_device->DeAllocate();
Controllers::iterator cit = controllers_.find(video_capture_device);
if (cit != controllers_.end()) {
cit->second->ready_to_delete = true;
if (cit->second->handlers.empty()) {
delete cit->second;
controllers_.erase(cit);
}
}
}
if (!stopped_cb.is_null())
stopped_cb.Run();
if (capture_session_id == kStartOpenSessionId) {
// This device was opened from Start(), not Open(). Close it!
OnClose(capture_session_id);
}
}
void VideoCaptureManager::OnOpened(MediaStreamType stream_type,
int capture_session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!listener_) {
// Listener has been removed.
return;
}
listener_->Opened(stream_type, capture_session_id);
}
void VideoCaptureManager::OnClosed(MediaStreamType stream_type,
int capture_session_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!listener_) {
// Listener has been removed.
return;
}
listener_->Closed(stream_type, capture_session_id);
}
void VideoCaptureManager::OnDevicesEnumerated(
MediaStreamType stream_type,
scoped_ptr<StreamDeviceInfoArray> devices) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!listener_) {
// Listener has been removed.
return;
}
listener_->DevicesEnumerated(stream_type, *devices);
}
void VideoCaptureManager::OnError(MediaStreamType stream_type,
int capture_session_id,
MediaStreamProviderError error) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!listener_) {
// Listener has been removed.
return;
}
listener_->Error(stream_type, capture_session_id, error);
}
void VideoCaptureManager::PostOnOpened(
MediaStreamType stream_type, int capture_session_id) {
DCHECK(IsOnDeviceThread());
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&VideoCaptureManager::OnOpened, this,
stream_type, capture_session_id));
}
void VideoCaptureManager::PostOnClosed(
MediaStreamType stream_type, int capture_session_id) {
DCHECK(IsOnDeviceThread());
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&VideoCaptureManager::OnClosed, this,
stream_type, capture_session_id));
}
void VideoCaptureManager::PostOnDevicesEnumerated(
MediaStreamType stream_type,
scoped_ptr<StreamDeviceInfoArray> devices) {
DCHECK(IsOnDeviceThread());
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&VideoCaptureManager::OnDevicesEnumerated,
this, stream_type, base::Passed(&devices)));
}
void VideoCaptureManager::PostOnError(int capture_session_id,
MediaStreamProviderError error) {
DCHECK(IsOnDeviceThread());
MediaStreamType stream_type = MEDIA_DEVICE_VIDEO_CAPTURE;
VideoCaptureDevices::const_iterator it = devices_.find(capture_session_id);
if (it != devices_.end())
stream_type = it->second.stream_type;
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&VideoCaptureManager::OnError, this,
stream_type, capture_session_id, error));
}
bool VideoCaptureManager::IsOnDeviceThread() const {
return device_loop_->BelongsToCurrentThread();
}
void VideoCaptureManager::GetAvailableDevices(
MediaStreamType stream_type,
media::VideoCaptureDevice::Names* device_names) {
DCHECK(IsOnDeviceThread());
switch (stream_type) {
case MEDIA_DEVICE_VIDEO_CAPTURE:
// Cache the latest enumeration of video capture devices.
// We'll refer to this list again in OnOpen to avoid having to
// enumerate the devices again.
video_capture_devices_.clear();
if (!use_fake_device_) {
media::VideoCaptureDevice::GetDeviceNames(&video_capture_devices_);
} else {
media::FakeVideoCaptureDevice::GetDeviceNames(&video_capture_devices_);
}
*device_names = video_capture_devices_;
break;
case MEDIA_DESKTOP_VIDEO_CAPTURE:
device_names->clear();
break;
default:
NOTREACHED();
break;
}
}
bool VideoCaptureManager::DeviceOpened(
const media::VideoCaptureDevice::Name& device_name) {
DCHECK(IsOnDeviceThread());
for (VideoCaptureDevices::iterator it = devices_.begin();
it != devices_.end(); ++it) {
if (device_name.id() == it->second.capture_device->device_name().id()) {
// We've found the device!
return true;
}
}
return false;
}
media::VideoCaptureDevice* VideoCaptureManager::GetOpenedDevice(
const StreamDeviceInfo& device_info) {
DCHECK(IsOnDeviceThread());
for (VideoCaptureDevices::iterator it = devices_.begin();
it != devices_.end(); it++) {
if (device_info.device.id ==
it->second.capture_device->device_name().id()) {
return it->second.capture_device;
}
}
return NULL;
}
bool VideoCaptureManager::DeviceInUse(
const media::VideoCaptureDevice* video_capture_device) {
DCHECK(IsOnDeviceThread());
for (VideoCaptureDevices::iterator it = devices_.begin();
it != devices_.end(); ++it) {
if (video_capture_device == it->second.capture_device) {
// We've found the device!
return true;
}
}
return false;
}
void VideoCaptureManager::AddController(
const media::VideoCaptureParams& capture_params,
VideoCaptureControllerEventHandler* handler,
base::Callback<void(VideoCaptureController*)> added_cb) {
DCHECK(handler);
device_loop_->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureManager::DoAddControllerOnDeviceThread,
this, capture_params, handler, added_cb));
}
void VideoCaptureManager::DoAddControllerOnDeviceThread(
const media::VideoCaptureParams capture_params,
VideoCaptureControllerEventHandler* handler,
base::Callback<void(VideoCaptureController*)> added_cb) {
DCHECK(IsOnDeviceThread());
media::VideoCaptureDevice* video_capture_device =
GetDeviceInternal(capture_params.session_id);
scoped_refptr<VideoCaptureController> controller;
if (video_capture_device) {
Controllers::iterator cit = controllers_.find(video_capture_device);
if (cit == controllers_.end()) {
controller = new VideoCaptureController(this);
controllers_[video_capture_device] =
new Controller(controller.get(), handler);
} else {
controllers_[video_capture_device]->handlers.push_front(handler);
controller = controllers_[video_capture_device]->controller;
}
}
added_cb.Run(controller.get());
}
void VideoCaptureManager::RemoveController(
VideoCaptureController* controller,
VideoCaptureControllerEventHandler* handler) {
DCHECK(handler);
device_loop_->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureManager::DoRemoveControllerOnDeviceThread, this,
make_scoped_refptr(controller), handler));
}
void VideoCaptureManager::DoRemoveControllerOnDeviceThread(
VideoCaptureController* controller,
VideoCaptureControllerEventHandler* handler) {
DCHECK(IsOnDeviceThread());
for (Controllers::iterator cit = controllers_.begin();
cit != controllers_.end(); ++cit) {
if (controller == cit->second->controller.get()) {
Handlers& handlers = cit->second->handlers;
for (Handlers::iterator hit = handlers.begin();
hit != handlers.end(); ++hit) {
if ((*hit) == handler) {
handlers.erase(hit);
break;
}
}
if (handlers.empty() && cit->second->ready_to_delete) {
delete cit->second;
controllers_.erase(cit);
}
return;
}
}
}
media::VideoCaptureDevice* VideoCaptureManager::GetDeviceInternal(
int capture_session_id) {
DCHECK(IsOnDeviceThread());
VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
if (dit != devices_.end()) {
return dit->second.capture_device;
}
// Solution for not using MediaStreamManager.
// This session id won't be returned by Open().
if (capture_session_id == kStartOpenSessionId) {
media::VideoCaptureDevice::Names device_names;
GetAvailableDevices(MEDIA_DEVICE_VIDEO_CAPTURE, &device_names);
if (device_names.empty()) {
// No devices available.
return NULL;
}
StreamDeviceInfo device(MEDIA_DEVICE_VIDEO_CAPTURE,
device_names.front().GetNameAndModel(),
device_names.front().id(),
false);
// Call OnOpen to open using the first device in the list.
OnOpen(capture_session_id, device);
VideoCaptureDevices::iterator dit = devices_.find(capture_session_id);
if (dit != devices_.end()) {
return dit->second.capture_device;
}
}
return NULL;
}
} // namespace content