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/media_switches.h"
23 #include "media/base/scoped_histogram_timer.h"
24 #include "media/video/capture/fake_video_capture_device.h"
25 #include "media/video/capture/file_video_capture_device.h"
26 #include "media/video/capture/video_capture_device.h"
28 #if defined(ENABLE_SCREEN_CAPTURE)
29 #include "content/browser/renderer_host/media/desktop_capture_device.h"
30 #if defined(OS_CHROMEOS)
31 #include "content/browser/renderer_host/media/desktop_capture_device_aura.h"
37 VideoCaptureManager::DeviceEntry::DeviceEntry(
38 MediaStreamType stream_type
,
39 const std::string
& id
,
40 scoped_ptr
<VideoCaptureController
> controller
)
41 : stream_type(stream_type
),
43 video_capture_controller(controller
.Pass()) {}
45 VideoCaptureManager::DeviceEntry::~DeviceEntry() {}
47 VideoCaptureManager::VideoCaptureManager()
49 new_capture_session_id_(1),
50 artificial_device_source_for_testing_ (DISABLED
) {
53 VideoCaptureManager::~VideoCaptureManager() {
54 DCHECK(devices_
.empty());
57 void VideoCaptureManager::Register(MediaStreamProviderListener
* listener
,
58 base::MessageLoopProxy
* device_thread_loop
) {
59 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
61 DCHECK(!device_loop_
.get());
63 device_loop_
= device_thread_loop
;
66 void VideoCaptureManager::Unregister() {
71 void VideoCaptureManager::EnumerateDevices(MediaStreamType stream_type
) {
72 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
73 DVLOG(1) << "VideoCaptureManager::EnumerateDevices, type " << stream_type
;
75 base::PostTaskAndReplyWithResult(
76 device_loop_
, FROM_HERE
,
77 base::Bind(&VideoCaptureManager::GetAvailableDevicesOnDeviceThread
, this,
79 base::Bind(&VideoCaptureManager::OnDevicesEnumerated
, this, stream_type
));
82 int VideoCaptureManager::Open(const StreamDeviceInfo
& device_info
) {
83 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
86 // Generate a new id for the session being opened.
87 const int capture_session_id
= new_capture_session_id_
++;
89 DCHECK(sessions_
.find(capture_session_id
) == sessions_
.end());
90 DVLOG(1) << "VideoCaptureManager::Open, id " << capture_session_id
;
92 // We just save the stream info for processing later.
93 sessions_
[capture_session_id
] = device_info
.device
;
95 // Notify our listener asynchronously; this ensures that we return
96 // |capture_session_id| to the caller of this function before using that same
97 // id in a listener event.
98 base::MessageLoop::current()->PostTask(FROM_HERE
,
99 base::Bind(&VideoCaptureManager::OnOpened
, this,
100 device_info
.device
.type
, capture_session_id
));
101 return capture_session_id
;
104 void VideoCaptureManager::Close(int capture_session_id
) {
105 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
107 DVLOG(1) << "VideoCaptureManager::Close, id " << capture_session_id
;
109 std::map
<int, MediaStreamDevice
>::iterator session_it
=
110 sessions_
.find(capture_session_id
);
111 if (session_it
== sessions_
.end()) {
116 DeviceEntry
* const existing_device
= GetDeviceEntryForMediaStreamDevice(
118 if (existing_device
) {
119 // Remove any client that is still using the session. This is safe to call
120 // even if there are no clients using the session.
121 existing_device
->video_capture_controller
->StopSession(capture_session_id
);
123 // StopSession() may have removed the last client, so we might need to
125 DestroyDeviceEntryIfNoClients(existing_device
);
128 // Notify listeners asynchronously, and forget the session.
129 base::MessageLoop::current()->PostTask(FROM_HERE
,
130 base::Bind(&VideoCaptureManager::OnClosed
, this, session_it
->second
.type
,
131 capture_session_id
));
132 sessions_
.erase(session_it
);
135 void VideoCaptureManager::UseFakeDevice() {
136 if (CommandLine::ForCurrentProcess()->HasSwitch(
137 switches::kUseFileForFakeVideoCapture
)) {
138 artificial_device_source_for_testing_
= Y4M_FILE
;
140 artificial_device_source_for_testing_
= TEST_PATTERN
;
144 void VideoCaptureManager::DoStartDeviceOnDeviceThread(
146 const media::VideoCaptureParams
& params
,
147 scoped_ptr
<media::VideoCaptureDevice::Client
> device_client
) {
148 SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StartDeviceTime");
149 DCHECK(IsOnDeviceThread());
151 scoped_ptr
<media::VideoCaptureDevice
> video_capture_device
;
152 switch (entry
->stream_type
) {
153 case MEDIA_DEVICE_VIDEO_CAPTURE
: {
154 // We look up the device id from the renderer in our local enumeration
155 // since the renderer does not have all the information that might be
156 // held in the browser-side VideoCaptureDevice::Name structure.
157 media::VideoCaptureDevice::Name
* found
=
158 video_capture_devices_
.FindById(entry
->id
);
160 switch (artificial_device_source_for_testing_
) {
162 video_capture_device
.reset(
163 media::VideoCaptureDevice::Create(*found
));
166 video_capture_device
.reset(
167 media::FakeVideoCaptureDevice::Create(*found
));
170 video_capture_device
.reset(
171 media::FileVideoCaptureDevice::Create(*found
));
177 case MEDIA_TAB_VIDEO_CAPTURE
: {
178 video_capture_device
.reset(
179 WebContentsVideoCaptureDevice::Create(entry
->id
));
182 case MEDIA_DESKTOP_VIDEO_CAPTURE
: {
183 #if defined(ENABLE_SCREEN_CAPTURE)
184 DesktopMediaID id
= DesktopMediaID::Parse(entry
->id
);
185 if (id
.type
!= DesktopMediaID::TYPE_NONE
) {
186 #if defined(OS_CHROMEOS)
187 // TODO(hshi): enable this path for Ash windows in metro mode.
188 video_capture_device
.reset(DesktopCaptureDeviceAura::Create(id
));
190 video_capture_device
= DesktopCaptureDevice::Create(id
);
193 #endif // defined(ENABLE_SCREEN_CAPTURE)
202 if (!video_capture_device
) {
203 device_client
->OnError();
207 video_capture_device
->AllocateAndStart(params
, device_client
.Pass());
208 entry
->video_capture_device
= video_capture_device
.Pass();
211 void VideoCaptureManager::StartCaptureForClient(
212 media::VideoCaptureSessionId session_id
,
213 const media::VideoCaptureParams
& params
,
214 base::ProcessHandle client_render_process
,
215 VideoCaptureControllerID client_id
,
216 VideoCaptureControllerEventHandler
* client_handler
,
217 const DoneCB
& done_cb
) {
218 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
219 DVLOG(1) << "VideoCaptureManager::StartCaptureForClient, "
220 << params
.requested_format
.frame_size
.ToString() << ", "
221 << params
.requested_format
.frame_rate
<< ", #" << session_id
<< ")";
223 DeviceEntry
* entry
= GetOrCreateDeviceEntry(session_id
);
225 done_cb
.Run(base::WeakPtr
<VideoCaptureController
>());
229 DCHECK(entry
->video_capture_controller
);
231 // First client starts the device.
232 if (entry
->video_capture_controller
->GetClientCount() == 0) {
233 DVLOG(1) << "VideoCaptureManager starting device (type = "
234 << entry
->stream_type
<< ", id = " << entry
->id
<< ")";
236 device_loop_
->PostTask(
239 &VideoCaptureManager::DoStartDeviceOnDeviceThread
,
243 base::Passed(entry
->video_capture_controller
->NewDeviceClient())));
245 // Run the callback first, as AddClient() may trigger OnFrameInfo().
246 done_cb
.Run(entry
->video_capture_controller
->GetWeakPtr());
247 entry
->video_capture_controller
->AddClient(
248 client_id
, client_handler
, client_render_process
, session_id
, params
);
251 void VideoCaptureManager::StopCaptureForClient(
252 VideoCaptureController
* controller
,
253 VideoCaptureControllerID client_id
,
254 VideoCaptureControllerEventHandler
* client_handler
) {
255 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
257 DCHECK(client_handler
);
259 DeviceEntry
* entry
= GetDeviceEntryForController(controller
);
265 // Detach client from controller.
266 int session_id
= controller
->RemoveClient(client_id
, client_handler
);
267 DVLOG(1) << "VideoCaptureManager::StopCaptureForClient, session_id = "
270 // If controller has no more clients, delete controller and device.
271 DestroyDeviceEntryIfNoClients(entry
);
274 void VideoCaptureManager::DoStopDeviceOnDeviceThread(DeviceEntry
* entry
) {
275 SCOPED_UMA_HISTOGRAM_TIMER("Media.VideoCaptureManager.StopDeviceTime");
276 DCHECK(IsOnDeviceThread());
277 if (entry
->video_capture_device
) {
278 entry
->video_capture_device
->StopAndDeAllocate();
280 entry
->video_capture_device
.reset();
283 void VideoCaptureManager::OnOpened(MediaStreamType stream_type
,
284 int capture_session_id
) {
285 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
287 // Listener has been removed.
290 listener_
->Opened(stream_type
, capture_session_id
);
293 void VideoCaptureManager::OnClosed(MediaStreamType stream_type
,
294 int capture_session_id
) {
295 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
297 // Listener has been removed.
300 listener_
->Closed(stream_type
, capture_session_id
);
303 void VideoCaptureManager::OnDevicesEnumerated(
304 MediaStreamType stream_type
,
305 const media::VideoCaptureDevice::Names
& device_names
) {
306 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
309 // Listener has been removed.
313 // Transform from VCD::Name to StreamDeviceInfo.
314 StreamDeviceInfoArray devices
;
315 for (media::VideoCaptureDevice::Names::const_iterator it
=
316 device_names
.begin(); it
!= device_names
.end(); ++it
) {
317 devices
.push_back(StreamDeviceInfo(
318 stream_type
, it
->GetNameAndModel(), it
->id()));
321 listener_
->DevicesEnumerated(stream_type
, devices
);
324 bool VideoCaptureManager::IsOnDeviceThread() const {
325 return device_loop_
->BelongsToCurrentThread();
328 media::VideoCaptureDevice::Names
329 VideoCaptureManager::GetAvailableDevicesOnDeviceThread(
330 MediaStreamType stream_type
) {
331 SCOPED_UMA_HISTOGRAM_TIMER(
332 "Media.VideoCaptureManager.GetAvailableDevicesTime");
333 DCHECK(IsOnDeviceThread());
334 media::VideoCaptureDevice::Names result
;
336 switch (stream_type
) {
337 case MEDIA_DEVICE_VIDEO_CAPTURE
:
338 // Cache the latest enumeration of video capture devices.
339 // We'll refer to this list again in OnOpen to avoid having to
340 // enumerate the devices again.
341 switch (artificial_device_source_for_testing_
) {
343 media::VideoCaptureDevice::GetDeviceNames(&result
);
346 media::FakeVideoCaptureDevice::GetDeviceNames(&result
);
349 media::FileVideoCaptureDevice::GetDeviceNames(&result
);
353 // TODO(nick): The correctness of device start depends on this cache being
354 // maintained, but it seems a little odd to keep a cache here. Can we
356 video_capture_devices_
= result
;
359 case MEDIA_DESKTOP_VIDEO_CAPTURE
:
370 VideoCaptureManager::DeviceEntry
*
371 VideoCaptureManager::GetDeviceEntryForMediaStreamDevice(
372 const MediaStreamDevice
& device_info
) {
373 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
375 for (DeviceEntries::iterator it
= devices_
.begin();
376 it
!= devices_
.end(); ++it
) {
377 DeviceEntry
* device
= *it
;
378 if (device_info
.type
== device
->stream_type
&&
379 device_info
.id
== device
->id
) {
386 VideoCaptureManager::DeviceEntry
*
387 VideoCaptureManager::GetDeviceEntryForController(
388 const VideoCaptureController
* controller
) {
389 // Look up |controller| in |devices_|.
390 for (DeviceEntries::iterator it
= devices_
.begin();
391 it
!= devices_
.end(); ++it
) {
392 if ((*it
)->video_capture_controller
.get() == controller
) {
399 void VideoCaptureManager::DestroyDeviceEntryIfNoClients(DeviceEntry
* entry
) {
400 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
401 // Removal of the last client stops the device.
402 if (entry
->video_capture_controller
->GetClientCount() == 0) {
403 DVLOG(1) << "VideoCaptureManager stopping device (type = "
404 << entry
->stream_type
<< ", id = " << entry
->id
<< ")";
406 // The DeviceEntry is removed from |devices_| immediately. The controller is
407 // deleted immediately, and the device is freed asynchronously. After this
408 // point, subsequent requests to open this same device ID will create a new
409 // DeviceEntry, VideoCaptureController, and VideoCaptureDevice.
410 devices_
.erase(entry
);
411 entry
->video_capture_controller
.reset();
412 device_loop_
->PostTask(
414 base::Bind(&VideoCaptureManager::DoStopDeviceOnDeviceThread
, this,
415 base::Owned(entry
)));
419 VideoCaptureManager::DeviceEntry
* VideoCaptureManager::GetOrCreateDeviceEntry(
420 int capture_session_id
) {
421 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
423 std::map
<int, MediaStreamDevice
>::iterator session_it
=
424 sessions_
.find(capture_session_id
);
425 if (session_it
== sessions_
.end()) {
428 const MediaStreamDevice
& device_info
= session_it
->second
;
430 // Check if another session has already opened this device. If so, just
431 // use that opened device.
432 DeviceEntry
* const existing_device
=
433 GetDeviceEntryForMediaStreamDevice(device_info
);
434 if (existing_device
) {
435 DCHECK_EQ(device_info
.type
, existing_device
->stream_type
);
436 return existing_device
;
439 scoped_ptr
<VideoCaptureController
> video_capture_controller(
440 new VideoCaptureController());
441 DeviceEntry
* new_device
= new DeviceEntry(device_info
.type
,
443 video_capture_controller
.Pass());
444 devices_
.insert(new_device
);
448 } // namespace content