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/logging.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/stl_util.h"
13 #include "base/task_runner_util.h"
14 #include "base/threading/sequenced_worker_pool.h"
15 #include "content/browser/media/capture/web_contents_video_capture_device.h"
16 #include "content/browser/renderer_host/media/video_capture_controller.h"
17 #include "content/browser/renderer_host/media/video_capture_controller_event_handler.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/desktop_media_id.h"
20 #include "content/public/common/content_switches.h"
21 #include "content/public/common/media_stream_request.h"
22 #include "media/base/scoped_histogram_timer.h"
23 #include "media/video/capture/video_capture_device.h"
24 #include "media/video/capture/video_capture_device_factory.h"
26 #if defined(ENABLE_SCREEN_CAPTURE)
27 #include "content/browser/media/capture/desktop_capture_device.h"
29 #include "content/browser/media/capture/desktop_capture_device_aura.h"
35 // Compares two VideoCaptureFormat by checking smallest frame_size area, then
36 // by _largest_ frame_rate. Used to order a VideoCaptureFormats vector so that
37 // the first entry for a given resolution has the largest frame rate, as needed
38 // by the ConsolidateCaptureFormats() method.
39 bool IsCaptureFormatSmaller(const media::VideoCaptureFormat
& format1
,
40 const media::VideoCaptureFormat
& format2
) {
41 if (format1
.frame_size
.GetArea() == format2
.frame_size
.GetArea())
42 return format1
.frame_rate
> format2
.frame_rate
;
43 return format1
.frame_size
.GetArea() < format2
.frame_size
.GetArea();
46 bool IsCaptureFormatSizeEqual(const media::VideoCaptureFormat
& format1
,
47 const media::VideoCaptureFormat
& format2
) {
48 return format1
.frame_size
.GetArea() == format2
.frame_size
.GetArea();
51 // This function receives a list of capture formats, removes duplicated
52 // resolutions while keeping the highest frame rate for each, and forcing I420
54 void ConsolidateCaptureFormats(media::VideoCaptureFormats
* formats
) {
57 std::sort(formats
->begin(), formats
->end(), IsCaptureFormatSmaller
);
58 // Due to the ordering imposed, the largest frame_rate is kept while removing
59 // duplicated resolutions.
60 media::VideoCaptureFormats::iterator last
=
61 std::unique(formats
->begin(), formats
->end(), IsCaptureFormatSizeEqual
);
62 formats
->erase(last
, formats
->end());
63 // Mark all formats as I420, since this is what the renderer side will get
64 // anyhow: the actual pixel format is decided at the device level.
65 for (media::VideoCaptureFormats::iterator it
= formats
->begin();
66 it
!= formats
->end(); ++it
) {
67 it
->pixel_format
= media::PIXEL_FORMAT_I420
;
75 VideoCaptureManager::DeviceEntry::DeviceEntry(
76 MediaStreamType stream_type
,
77 const std::string
& id
,
78 scoped_ptr
<VideoCaptureController
> controller
)
79 : stream_type(stream_type
),
81 video_capture_controller(controller
.Pass()) {}
83 VideoCaptureManager::DeviceEntry::~DeviceEntry() {}
85 VideoCaptureManager::DeviceInfo::DeviceInfo() {}
87 VideoCaptureManager::DeviceInfo::DeviceInfo(
88 const media::VideoCaptureDevice::Name
& name
,
89 const media::VideoCaptureFormats
& supported_formats
)
91 supported_formats(supported_formats
) {}
93 VideoCaptureManager::DeviceInfo::~DeviceInfo() {}
95 VideoCaptureManager::VideoCaptureManager(
96 scoped_ptr
<media::VideoCaptureDeviceFactory
> factory
)
98 new_capture_session_id_(1),
99 video_capture_device_factory_(factory
.Pass()) {
102 VideoCaptureManager::~VideoCaptureManager() {
103 DCHECK(devices_
.empty());
106 void VideoCaptureManager::Register(
107 MediaStreamProviderListener
* listener
,
108 const scoped_refptr
<base::SingleThreadTaskRunner
>& device_task_runner
) {
109 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
111 DCHECK(!device_task_runner_
.get());
112 listener_
= listener
;
113 device_task_runner_
= device_task_runner
;
116 void VideoCaptureManager::Unregister() {
121 void VideoCaptureManager::EnumerateDevices(MediaStreamType stream_type
) {
122 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
123 DVLOG(1) << "VideoCaptureManager::EnumerateDevices, type " << stream_type
;
125 base::PostTaskAndReplyWithResult(
126 device_task_runner_
, FROM_HERE
,
127 base::Bind(&VideoCaptureManager::GetAvailableDevicesInfoOnDeviceThread
,
128 this, stream_type
, devices_info_cache_
),
129 base::Bind(&VideoCaptureManager::OnDevicesInfoEnumerated
, this,
133 int VideoCaptureManager::Open(const StreamDeviceInfo
& device_info
) {
134 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
137 // Generate a new id for the session being opened.
138 const media::VideoCaptureSessionId capture_session_id
=
139 new_capture_session_id_
++;
141 DCHECK(sessions_
.find(capture_session_id
) == sessions_
.end());
142 DVLOG(1) << "VideoCaptureManager::Open, id " << capture_session_id
;
144 // We just save the stream info for processing later.
145 sessions_
[capture_session_id
] = device_info
.device
;
147 // Notify our listener asynchronously; this ensures that we return
148 // |capture_session_id| to the caller of this function before using that same
149 // id in a listener event.
150 base::MessageLoop::current()->PostTask(FROM_HERE
,
151 base::Bind(&VideoCaptureManager::OnOpened
, this,
152 device_info
.device
.type
, capture_session_id
));
153 return capture_session_id
;
156 void VideoCaptureManager::Close(int capture_session_id
) {
157 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
159 DVLOG(1) << "VideoCaptureManager::Close, id " << capture_session_id
;
161 SessionMap::iterator session_it
= sessions_
.find(capture_session_id
);
162 if (session_it
== sessions_
.end()) {
167 DeviceEntry
* const existing_device
= GetDeviceEntryForMediaStreamDevice(
169 if (existing_device
) {
170 // Remove any client that is still using the session. This is safe to call
171 // even if there are no clients using the session.
172 existing_device
->video_capture_controller
->StopSession(capture_session_id
);
174 // StopSession() may have removed the last client, so we might need to
176 DestroyDeviceEntryIfNoClients(existing_device
);
179 // Notify listeners asynchronously, and forget the session.
180 base::MessageLoop::current()->PostTask(FROM_HERE
,
181 base::Bind(&VideoCaptureManager::OnClosed
, this, session_it
->second
.type
,
182 capture_session_id
));
183 sessions_
.erase(session_it
);
186 void VideoCaptureManager::DoStartDeviceOnDeviceThread(
187 media::VideoCaptureSessionId session_id
,
189 const media::VideoCaptureParams
& params
,
190 scoped_ptr
<media::VideoCaptureDevice::Client
> device_client
) {
191 SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StartDeviceTime");
192 DCHECK(IsOnDeviceThread());
194 scoped_ptr
<media::VideoCaptureDevice
> video_capture_device
;
195 switch (entry
->stream_type
) {
196 case MEDIA_DEVICE_VIDEO_CAPTURE
: {
197 // We look up the device id from the renderer in our local enumeration
198 // since the renderer does not have all the information that might be
199 // held in the browser-side VideoCaptureDevice::Name structure.
200 DeviceInfo
* found
= FindDeviceInfoById(entry
->id
, devices_info_cache_
);
202 video_capture_device
=
203 video_capture_device_factory_
->Create(found
->name
);
207 case MEDIA_TAB_VIDEO_CAPTURE
: {
208 video_capture_device
.reset(
209 WebContentsVideoCaptureDevice::Create(entry
->id
));
212 case MEDIA_DESKTOP_VIDEO_CAPTURE
: {
213 #if defined(ENABLE_SCREEN_CAPTURE)
214 DesktopMediaID id
= DesktopMediaID::Parse(entry
->id
);
215 #if defined(USE_AURA)
216 if (id
.type
== DesktopMediaID::TYPE_AURA_WINDOW
) {
217 video_capture_device
.reset(DesktopCaptureDeviceAura::Create(id
));
220 if (id
.type
!= DesktopMediaID::TYPE_NONE
&&
221 id
.type
!= DesktopMediaID::TYPE_AURA_WINDOW
) {
222 video_capture_device
= DesktopCaptureDevice::Create(id
);
223 if (notification_window_ids_
.find(session_id
) !=
224 notification_window_ids_
.end()) {
225 static_cast<DesktopCaptureDevice
*>(video_capture_device
.get())
226 ->SetNotificationWindowId(notification_window_ids_
[session_id
]);
229 #endif // defined(ENABLE_SCREEN_CAPTURE)
238 if (!video_capture_device
) {
239 device_client
->OnError("Could not create capture device");
243 video_capture_device
->AllocateAndStart(params
, device_client
.Pass());
244 entry
->video_capture_device
= video_capture_device
.Pass();
247 void VideoCaptureManager::StartCaptureForClient(
248 media::VideoCaptureSessionId session_id
,
249 const media::VideoCaptureParams
& params
,
250 base::ProcessHandle client_render_process
,
251 VideoCaptureControllerID client_id
,
252 VideoCaptureControllerEventHandler
* client_handler
,
253 const DoneCB
& done_cb
) {
254 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
255 DVLOG(1) << "VideoCaptureManager::StartCaptureForClient, "
256 << params
.requested_format
.frame_size
.ToString() << ", "
257 << params
.requested_format
.frame_rate
<< ", #" << session_id
<< ")";
259 DeviceEntry
* entry
= GetOrCreateDeviceEntry(session_id
);
261 done_cb
.Run(base::WeakPtr
<VideoCaptureController
>());
265 DCHECK(entry
->video_capture_controller
);
267 // First client starts the device.
268 if (entry
->video_capture_controller
->GetClientCount() == 0) {
269 DVLOG(1) << "VideoCaptureManager starting device (type = "
270 << entry
->stream_type
<< ", id = " << entry
->id
<< ")";
272 device_task_runner_
->PostTask(
275 &VideoCaptureManager::DoStartDeviceOnDeviceThread
,
280 base::Passed(entry
->video_capture_controller
->NewDeviceClient())));
282 // Run the callback first, as AddClient() may trigger OnFrameInfo().
283 done_cb
.Run(entry
->video_capture_controller
->GetWeakPtr());
284 entry
->video_capture_controller
->AddClient(
285 client_id
, client_handler
, client_render_process
, session_id
, params
);
288 void VideoCaptureManager::StopCaptureForClient(
289 VideoCaptureController
* controller
,
290 VideoCaptureControllerID client_id
,
291 VideoCaptureControllerEventHandler
* client_handler
,
292 bool aborted_due_to_error
) {
293 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
295 DCHECK(client_handler
);
297 DeviceEntry
* entry
= GetDeviceEntryForController(controller
);
302 if (aborted_due_to_error
) {
303 SessionMap::iterator it
;
304 for (it
= sessions_
.begin(); it
!= sessions_
.end(); ++it
) {
305 if (it
->second
.type
== entry
->stream_type
&&
306 it
->second
.id
== entry
->id
) {
307 listener_
->Aborted(it
->second
.type
, it
->first
);
313 // Detach client from controller.
314 media::VideoCaptureSessionId session_id
=
315 controller
->RemoveClient(client_id
, client_handler
);
316 DVLOG(1) << "VideoCaptureManager::StopCaptureForClient, session_id = "
319 // If controller has no more clients, delete controller and device.
320 DestroyDeviceEntryIfNoClients(entry
);
323 bool VideoCaptureManager::GetDeviceSupportedFormats(
324 media::VideoCaptureSessionId capture_session_id
,
325 media::VideoCaptureFormats
* supported_formats
) {
326 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
327 DCHECK(supported_formats
->empty());
329 SessionMap::iterator it
= sessions_
.find(capture_session_id
);
330 if (it
== sessions_
.end())
332 DVLOG(1) << "GetDeviceSupportedFormats for device: " << it
->second
.name
;
334 // Return all available formats of the device, regardless its started state.
335 DeviceInfo
* existing_device
=
336 FindDeviceInfoById(it
->second
.id
, devices_info_cache_
);
338 *supported_formats
= existing_device
->supported_formats
;
342 bool VideoCaptureManager::GetDeviceFormatsInUse(
343 media::VideoCaptureSessionId capture_session_id
,
344 media::VideoCaptureFormats
* formats_in_use
) {
345 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
346 DCHECK(formats_in_use
->empty());
348 SessionMap::iterator it
= sessions_
.find(capture_session_id
);
349 if (it
== sessions_
.end())
351 DVLOG(1) << "GetDeviceFormatsInUse for device: " << it
->second
.name
;
353 // Return the currently in-use format(s) of the device, if it's started.
354 DeviceEntry
* device_in_use
=
355 GetDeviceEntryForMediaStreamDevice(it
->second
);
357 // Currently only one format-in-use is supported at the VCC level.
358 formats_in_use
->push_back(
359 device_in_use
->video_capture_controller
->GetVideoCaptureFormat());
364 void VideoCaptureManager::SetDesktopCaptureWindowId(
365 media::VideoCaptureSessionId session_id
,
366 gfx::NativeViewId window_id
) {
367 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
368 SessionMap::iterator session_it
= sessions_
.find(session_id
);
369 if (session_it
== sessions_
.end()) {
370 device_task_runner_
->PostTask(
373 &VideoCaptureManager::SaveDesktopCaptureWindowIdOnDeviceThread
,
380 DeviceEntry
* const existing_device
=
381 GetDeviceEntryForMediaStreamDevice(session_it
->second
);
382 if (!existing_device
)
385 DCHECK_EQ(MEDIA_DESKTOP_VIDEO_CAPTURE
, existing_device
->stream_type
);
386 DesktopMediaID id
= DesktopMediaID::Parse(existing_device
->id
);
387 if (id
.type
== DesktopMediaID::TYPE_NONE
||
388 id
.type
== DesktopMediaID::TYPE_AURA_WINDOW
) {
392 device_task_runner_
->PostTask(
394 base::Bind(&VideoCaptureManager::SetDesktopCaptureWindowIdOnDeviceThread
,
400 void VideoCaptureManager::DoStopDeviceOnDeviceThread(DeviceEntry
* entry
) {
401 SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StopDeviceTime");
402 DCHECK(IsOnDeviceThread());
403 if (entry
->video_capture_device
) {
404 entry
->video_capture_device
->StopAndDeAllocate();
406 entry
->video_capture_device
.reset();
409 void VideoCaptureManager::OnOpened(
410 MediaStreamType stream_type
,
411 media::VideoCaptureSessionId capture_session_id
) {
412 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
414 // Listener has been removed.
417 listener_
->Opened(stream_type
, capture_session_id
);
420 void VideoCaptureManager::OnClosed(
421 MediaStreamType stream_type
,
422 media::VideoCaptureSessionId capture_session_id
) {
423 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
425 // Listener has been removed.
428 listener_
->Closed(stream_type
, capture_session_id
);
431 void VideoCaptureManager::OnDevicesInfoEnumerated(
432 MediaStreamType stream_type
,
433 const DeviceInfos
& new_devices_info_cache
) {
434 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
437 // Listener has been removed.
440 devices_info_cache_
= new_devices_info_cache
;
442 // Walk the |devices_info_cache_| and transform from VCD::Name to
443 // StreamDeviceInfo for return purposes.
444 StreamDeviceInfoArray devices
;
445 for (DeviceInfos::const_iterator it
= devices_info_cache_
.begin();
446 it
!= devices_info_cache_
.end(); ++it
) {
447 devices
.push_back(StreamDeviceInfo(
448 stream_type
, it
->name
.GetNameAndModel(), it
->name
.id()));
450 listener_
->DevicesEnumerated(stream_type
, devices
);
453 bool VideoCaptureManager::IsOnDeviceThread() const {
454 return device_task_runner_
->BelongsToCurrentThread();
457 VideoCaptureManager::DeviceInfos
458 VideoCaptureManager::GetAvailableDevicesInfoOnDeviceThread(
459 MediaStreamType stream_type
,
460 const DeviceInfos
& old_device_info_cache
) {
461 SCOPED_UMA_HISTOGRAM_TIMER(
462 "Media.VideoCaptureManager.GetAvailableDevicesInfoOnDeviceThreadTime");
463 DCHECK(IsOnDeviceThread());
464 media::VideoCaptureDevice::Names names_snapshot
;
465 switch (stream_type
) {
466 case MEDIA_DEVICE_VIDEO_CAPTURE
:
467 // Cache the latest enumeration of video capture devices.
468 // We'll refer to this list again in OnOpen to avoid having to
469 // enumerate the devices again.
470 video_capture_device_factory_
->GetDeviceNames(&names_snapshot
);
472 case MEDIA_DESKTOP_VIDEO_CAPTURE
:
480 // Construct |new_devices_info_cache| with the cached devices that are still
481 // present in the system, and remove their names from |names_snapshot|, so we
482 // keep there the truly new devices.
483 DeviceInfos new_devices_info_cache
;
484 for (DeviceInfos::const_iterator it_device_info
=
485 old_device_info_cache
.begin();
486 it_device_info
!= old_device_info_cache
.end(); ++it_device_info
) {
487 for (media::VideoCaptureDevice::Names::iterator it
=
488 names_snapshot
.begin();
489 it
!= names_snapshot
.end(); ++it
) {
490 if (it_device_info
->name
.id() == it
->id()) {
491 new_devices_info_cache
.push_back(*it_device_info
);
492 names_snapshot
.erase(it
);
498 // Get the supported capture formats for the new devices in |names_snapshot|.
499 for (media::VideoCaptureDevice::Names::const_iterator it
=
500 names_snapshot
.begin();
501 it
!= names_snapshot
.end(); ++it
) {
502 media::VideoCaptureFormats supported_formats
;
503 DeviceInfo
device_info(*it
, media::VideoCaptureFormats());
504 video_capture_device_factory_
->GetDeviceSupportedFormats(
505 *it
, &(device_info
.supported_formats
));
506 ConsolidateCaptureFormats(&device_info
.supported_formats
);
507 new_devices_info_cache
.push_back(device_info
);
509 return new_devices_info_cache
;
512 VideoCaptureManager::DeviceEntry
*
513 VideoCaptureManager::GetDeviceEntryForMediaStreamDevice(
514 const MediaStreamDevice
& device_info
) {
515 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
517 for (DeviceEntries::iterator it
= devices_
.begin();
518 it
!= devices_
.end(); ++it
) {
519 DeviceEntry
* device
= *it
;
520 if (device_info
.type
== device
->stream_type
&&
521 device_info
.id
== device
->id
) {
528 VideoCaptureManager::DeviceEntry
*
529 VideoCaptureManager::GetDeviceEntryForController(
530 const VideoCaptureController
* controller
) const {
531 // Look up |controller| in |devices_|.
532 for (DeviceEntries::const_iterator it
= devices_
.begin();
533 it
!= devices_
.end(); ++it
) {
534 if ((*it
)->video_capture_controller
.get() == controller
) {
541 void VideoCaptureManager::DestroyDeviceEntryIfNoClients(DeviceEntry
* entry
) {
542 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
543 // Removal of the last client stops the device.
544 if (entry
->video_capture_controller
->GetClientCount() == 0) {
545 DVLOG(1) << "VideoCaptureManager stopping device (type = "
546 << entry
->stream_type
<< ", id = " << entry
->id
<< ")";
548 // The DeviceEntry is removed from |devices_| immediately. The controller is
549 // deleted immediately, and the device is freed asynchronously. After this
550 // point, subsequent requests to open this same device ID will create a new
551 // DeviceEntry, VideoCaptureController, and VideoCaptureDevice.
552 devices_
.erase(entry
);
553 entry
->video_capture_controller
.reset();
554 device_task_runner_
->PostTask(
556 base::Bind(&VideoCaptureManager::DoStopDeviceOnDeviceThread
, this,
557 base::Owned(entry
)));
561 VideoCaptureManager::DeviceEntry
* VideoCaptureManager::GetOrCreateDeviceEntry(
562 media::VideoCaptureSessionId capture_session_id
) {
563 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
565 SessionMap::iterator session_it
= sessions_
.find(capture_session_id
);
566 if (session_it
== sessions_
.end()) {
569 const MediaStreamDevice
& device_info
= session_it
->second
;
571 // Check if another session has already opened this device. If so, just
572 // use that opened device.
573 DeviceEntry
* const existing_device
=
574 GetDeviceEntryForMediaStreamDevice(device_info
);
575 if (existing_device
) {
576 DCHECK_EQ(device_info
.type
, existing_device
->stream_type
);
577 return existing_device
;
580 scoped_ptr
<VideoCaptureController
> video_capture_controller(
581 new VideoCaptureController());
582 DeviceEntry
* new_device
= new DeviceEntry(device_info
.type
,
584 video_capture_controller
.Pass());
585 devices_
.insert(new_device
);
589 VideoCaptureManager::DeviceInfo
* VideoCaptureManager::FindDeviceInfoById(
590 const std::string
& id
,
591 DeviceInfos
& device_vector
) {
592 for (DeviceInfos::iterator it
= device_vector
.begin();
593 it
!= device_vector
.end(); ++it
) {
594 if (it
->name
.id() == id
)
600 void VideoCaptureManager::SetDesktopCaptureWindowIdOnDeviceThread(
602 gfx::NativeViewId window_id
) {
603 DCHECK(IsOnDeviceThread());
604 DCHECK(entry
->stream_type
== MEDIA_DESKTOP_VIDEO_CAPTURE
);
605 #if defined(ENABLE_SCREEN_CAPTURE)
606 DesktopCaptureDevice
* device
=
607 static_cast<DesktopCaptureDevice
*>(entry
->video_capture_device
.get());
608 device
->SetNotificationWindowId(window_id
);
612 void VideoCaptureManager::SaveDesktopCaptureWindowIdOnDeviceThread(
613 media::VideoCaptureSessionId session_id
,
614 gfx::NativeViewId window_id
) {
615 DCHECK(IsOnDeviceThread());
616 DCHECK(notification_window_ids_
.find(session_id
) ==
617 notification_window_ids_
.end());
618 notification_window_ids_
[session_id
] = window_id
;
621 } // namespace content