1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/media/video_capture_manager.h"
10 #include "base/command_line.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/stl_util.h"
14 #include "base/threading/sequenced_worker_pool.h"
15 #include "content/browser/renderer_host/media/video_capture_controller.h"
16 #include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
17 #include "content/browser/renderer_host/media/web_contents_video_capture_device.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/common/content_switches.h"
20 #include "content/public/common/desktop_media_id.h"
21 #include "content/public/common/media_stream_request.h"
22 #include "media/base/scoped_histogram_timer.h"
23 #include "media/video/capture/fake_video_capture_device.h"
24 #include "media/video/capture/video_capture_device.h"
26 #if defined(ENABLE_SCREEN_CAPTURE)
27 #include "content/browser/renderer_host/media/desktop_capture_device.h"
32 VideoCaptureManager::DeviceEntry::DeviceEntry(
33 MediaStreamType stream_type,
34 const std::string& id,
35 scoped_ptr<VideoCaptureController> controller)
36 : stream_type(stream_type),
38 video_capture_controller(controller.Pass()) {}
40 VideoCaptureManager::DeviceEntry::~DeviceEntry() {}
42 VideoCaptureManager::VideoCaptureManager()
44 new_capture_session_id_(1),
45 use_fake_device_(false) {
48 VideoCaptureManager::~VideoCaptureManager() {
49 DCHECK(devices_.empty());
52 void VideoCaptureManager::Register(MediaStreamProviderListener* listener,
53 base::MessageLoopProxy* device_thread_loop) {
54 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
56 DCHECK(!device_loop_.get());
58 device_loop_ = device_thread_loop;
61 void VideoCaptureManager::Unregister() {
66 void VideoCaptureManager::EnumerateDevices(MediaStreamType stream_type) {
67 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
68 DVLOG(1) << "VideoCaptureManager::EnumerateDevices, type " << stream_type;
70 base::PostTaskAndReplyWithResult(
71 device_loop_, FROM_HERE,
72 base::Bind(&VideoCaptureManager::GetAvailableDevicesOnDeviceThread, this,
74 base::Bind(&VideoCaptureManager::OnDevicesEnumerated, this, stream_type));
77 int VideoCaptureManager::Open(const StreamDeviceInfo& device_info) {
78 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
81 // Generate a new id for the session being opened.
82 const int capture_session_id = new_capture_session_id_++;
84 DCHECK(sessions_.find(capture_session_id) == sessions_.end());
85 DVLOG(1) << "VideoCaptureManager::Open, id " << capture_session_id;
87 // We just save the stream info for processing later.
88 sessions_[capture_session_id] = device_info.device;
90 // Notify our listener asynchronously; this ensures that we return
91 // |capture_session_id| to the caller of this function before using that same
92 // id in a listener event.
93 base::MessageLoop::current()->PostTask(FROM_HERE,
94 base::Bind(&VideoCaptureManager::OnOpened, this,
95 device_info.device.type, capture_session_id));
96 return capture_session_id;
99 void VideoCaptureManager::Close(int capture_session_id) {
100 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
102 DVLOG(1) << "VideoCaptureManager::Close, id " << capture_session_id;
104 std::map<int, MediaStreamDevice>::iterator session_it =
105 sessions_.find(capture_session_id);
106 if (session_it == sessions_.end()) {
111 DeviceEntry* const existing_device = GetDeviceEntryForMediaStreamDevice(
113 if (existing_device) {
114 // Remove any client that is still using the session. This is safe to call
115 // even if there are no clients using the session.
116 existing_device->video_capture_controller->StopSession(capture_session_id);
118 // StopSession() may have removed the last client, so we might need to
120 DestroyDeviceEntryIfNoClients(existing_device);
123 // Notify listeners asynchronously, and forget the session.
124 base::MessageLoop::current()->PostTask(FROM_HERE,
125 base::Bind(&VideoCaptureManager::OnClosed, this, session_it->second.type,
126 capture_session_id));
127 sessions_.erase(session_it);
130 void VideoCaptureManager::UseFakeDevice() {
131 use_fake_device_ = true;
134 void VideoCaptureManager::DoStartDeviceOnDeviceThread(
136 const media::VideoCaptureCapability& capture_params,
137 scoped_ptr<media::VideoCaptureDevice::Client> device_client) {
138 SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StartDeviceTime");
139 DCHECK(IsOnDeviceThread());
141 scoped_ptr<media::VideoCaptureDevice> video_capture_device;
142 switch (entry->stream_type) {
143 case MEDIA_DEVICE_VIDEO_CAPTURE: {
144 // We look up the device id from the renderer in our local enumeration
145 // since the renderer does not have all the information that might be
146 // held in the browser-side VideoCaptureDevice::Name structure.
147 media::VideoCaptureDevice::Name* found =
148 video_capture_devices_.FindById(entry->id);
150 video_capture_device.reset(use_fake_device_ ?
151 media::FakeVideoCaptureDevice::Create(*found) :
152 media::VideoCaptureDevice::Create(*found));
156 case MEDIA_TAB_VIDEO_CAPTURE: {
157 video_capture_device.reset(
158 WebContentsVideoCaptureDevice::Create(entry->id));
161 case MEDIA_DESKTOP_VIDEO_CAPTURE: {
162 #if defined(ENABLE_SCREEN_CAPTURE)
163 DesktopMediaID id = DesktopMediaID::Parse(entry->id);
164 if (id.type != DesktopMediaID::TYPE_NONE) {
165 video_capture_device = DesktopCaptureDevice::Create(id);
167 #endif // defined(ENABLE_SCREEN_CAPTURE)
176 if (!video_capture_device) {
177 device_client->OnError();
181 video_capture_device->AllocateAndStart(capture_params, device_client.Pass());
182 entry->video_capture_device = video_capture_device.Pass();
185 void VideoCaptureManager::StartCaptureForClient(
186 const media::VideoCaptureParams& params,
187 base::ProcessHandle client_render_process,
188 VideoCaptureControllerID client_id,
189 VideoCaptureControllerEventHandler* client_handler,
190 const DoneCB& done_cb) {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
192 DVLOG(1) << "VideoCaptureManager::StartCaptureForClient, ("
193 << params.requested_format.width
194 << ", " << params.requested_format.height
195 << ", " << params.requested_format.frame_rate
196 << ", #" << params.session_id
199 DeviceEntry* entry = GetOrCreateDeviceEntry(params.session_id);
201 done_cb.Run(base::WeakPtr<VideoCaptureController>());
205 DCHECK(entry->video_capture_controller);
207 // First client starts the device.
208 if (entry->video_capture_controller->GetClientCount() == 0) {
209 DVLOG(1) << "VideoCaptureManager starting device (type = "
210 << entry->stream_type << ", id = " << entry->id << ")";
212 media::VideoCaptureCapability params_as_capability;
213 params_as_capability.width = params.requested_format.width;
214 params_as_capability.height = params.requested_format.height;
215 params_as_capability.frame_rate = params.requested_format.frame_rate;
216 params_as_capability.frame_size_type =
217 params.requested_format.frame_size_type;
219 device_loop_->PostTask(FROM_HERE, base::Bind(
220 &VideoCaptureManager::DoStartDeviceOnDeviceThread, this,
221 entry, params_as_capability,
222 base::Passed(entry->video_capture_controller->NewDeviceClient())));
224 // Run the callback first, as AddClient() may trigger OnFrameInfo().
225 done_cb.Run(entry->video_capture_controller->GetWeakPtr());
226 entry->video_capture_controller->AddClient(client_id,
228 client_render_process,
232 void VideoCaptureManager::StopCaptureForClient(
233 VideoCaptureController* controller,
234 VideoCaptureControllerID client_id,
235 VideoCaptureControllerEventHandler* client_handler) {
236 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
238 DCHECK(client_handler);
240 DeviceEntry* entry = GetDeviceEntryForController(controller);
246 // Detach client from controller.
247 int session_id = controller->RemoveClient(client_id, client_handler);
248 DVLOG(1) << "VideoCaptureManager::StopCaptureForClient, session_id = "
251 // If controller has no more clients, delete controller and device.
252 DestroyDeviceEntryIfNoClients(entry);
255 void VideoCaptureManager::DoStopDeviceOnDeviceThread(DeviceEntry* entry) {
256 SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StopDeviceTime");
257 DCHECK(IsOnDeviceThread());
258 if (entry->video_capture_device) {
259 entry->video_capture_device->StopAndDeAllocate();
261 entry->video_capture_device.reset();
264 void VideoCaptureManager::OnOpened(MediaStreamType stream_type,
265 int capture_session_id) {
266 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
268 // Listener has been removed.
271 listener_->Opened(stream_type, capture_session_id);
274 void VideoCaptureManager::OnClosed(MediaStreamType stream_type,
275 int capture_session_id) {
276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
278 // Listener has been removed.
281 listener_->Closed(stream_type, capture_session_id);
284 void VideoCaptureManager::OnDevicesEnumerated(
285 MediaStreamType stream_type,
286 const media::VideoCaptureDevice::Names& device_names) {
287 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
290 // Listener has been removed.
294 // Transform from VCD::Name to StreamDeviceInfo.
295 StreamDeviceInfoArray devices;
296 for (media::VideoCaptureDevice::Names::const_iterator it =
297 device_names.begin(); it != device_names.end(); ++it) {
298 devices.push_back(StreamDeviceInfo(
299 stream_type, it->GetNameAndModel(), it->id()));
302 listener_->DevicesEnumerated(stream_type, devices);
305 bool VideoCaptureManager::IsOnDeviceThread() const {
306 return device_loop_->BelongsToCurrentThread();
309 media::VideoCaptureDevice::Names
310 VideoCaptureManager::GetAvailableDevicesOnDeviceThread(
311 MediaStreamType stream_type) {
312 SCOPED_UMA_HISTOGRAM_TIMER(
313 "Media.VideoCaptureManager.GetAvailableDevicesTime");
314 DCHECK(IsOnDeviceThread());
315 media::VideoCaptureDevice::Names result;
317 switch (stream_type) {
318 case MEDIA_DEVICE_VIDEO_CAPTURE:
319 // Cache the latest enumeration of video capture devices.
320 // We'll refer to this list again in OnOpen to avoid having to
321 // enumerate the devices again.
322 if (!use_fake_device_) {
323 media::VideoCaptureDevice::GetDeviceNames(&result);
325 media::FakeVideoCaptureDevice::GetDeviceNames(&result);
328 // TODO(nick): The correctness of device start depends on this cache being
329 // maintained, but it seems a little odd to keep a cache here. Can we
331 video_capture_devices_ = result;
334 case MEDIA_DESKTOP_VIDEO_CAPTURE:
345 VideoCaptureManager::DeviceEntry*
346 VideoCaptureManager::GetDeviceEntryForMediaStreamDevice(
347 const MediaStreamDevice& device_info) {
348 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
350 for (DeviceEntries::iterator it = devices_.begin();
351 it != devices_.end(); ++it) {
352 DeviceEntry* device = *it;
353 if (device_info.type == device->stream_type &&
354 device_info.id == device->id) {
361 VideoCaptureManager::DeviceEntry*
362 VideoCaptureManager::GetDeviceEntryForController(
363 const VideoCaptureController* controller) {
364 // Look up |controller| in |devices_|.
365 for (DeviceEntries::iterator it = devices_.begin();
366 it != devices_.end(); ++it) {
367 if ((*it)->video_capture_controller.get() == controller) {
374 void VideoCaptureManager::DestroyDeviceEntryIfNoClients(DeviceEntry* entry) {
375 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
376 // Removal of the last client stops the device.
377 if (entry->video_capture_controller->GetClientCount() == 0) {
378 DVLOG(1) << "VideoCaptureManager stopping device (type = "
379 << entry->stream_type << ", id = " << entry->id << ")";
381 // The DeviceEntry is removed from |devices_| immediately. The controller is
382 // deleted immediately, and the device is freed asynchronously. After this
383 // point, subsequent requests to open this same device ID will create a new
384 // DeviceEntry, VideoCaptureController, and VideoCaptureDevice.
385 devices_.erase(entry);
386 entry->video_capture_controller.reset();
387 device_loop_->PostTask(
389 base::Bind(&VideoCaptureManager::DoStopDeviceOnDeviceThread, this,
390 base::Owned(entry)));
394 VideoCaptureManager::DeviceEntry* VideoCaptureManager::GetOrCreateDeviceEntry(
395 int capture_session_id) {
396 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
398 std::map<int, MediaStreamDevice>::iterator session_it =
399 sessions_.find(capture_session_id);
400 if (session_it == sessions_.end()) {
403 const MediaStreamDevice& device_info = session_it->second;
405 // Check if another session has already opened this device. If so, just
406 // use that opened device.
407 DeviceEntry* const existing_device =
408 GetDeviceEntryForMediaStreamDevice(device_info);
409 if (existing_device) {
410 DCHECK_EQ(device_info.type, existing_device->stream_type);
411 return existing_device;
414 scoped_ptr<VideoCaptureController> video_capture_controller(
415 new VideoCaptureController());
416 DeviceEntry* new_device = new DeviceEntry(device_info.type,
418 video_capture_controller.Pass());
419 devices_.insert(new_device);
423 } // namespace content