- add sources.
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / media / video_capture_manager.cc
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.
4
5 #include "content/browser/renderer_host/media/video_capture_manager.h"
6
7 #include <set>
8
9 #include "base/bind.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"
25
26 #if defined(ENABLE_SCREEN_CAPTURE)
27 #include "content/browser/renderer_host/media/desktop_capture_device.h"
28 #endif
29
30 namespace content {
31
32 VideoCaptureManager::DeviceEntry::DeviceEntry(
33     MediaStreamType stream_type,
34     const std::string& id,
35     scoped_ptr<VideoCaptureController> controller)
36     : stream_type(stream_type),
37       id(id),
38       video_capture_controller(controller.Pass()) {}
39
40 VideoCaptureManager::DeviceEntry::~DeviceEntry() {}
41
42 VideoCaptureManager::VideoCaptureManager()
43     : listener_(NULL),
44       new_capture_session_id_(1),
45       use_fake_device_(false) {
46 }
47
48 VideoCaptureManager::~VideoCaptureManager() {
49   DCHECK(devices_.empty());
50 }
51
52 void VideoCaptureManager::Register(MediaStreamProviderListener* listener,
53                                    base::MessageLoopProxy* device_thread_loop) {
54   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
55   DCHECK(!listener_);
56   DCHECK(!device_loop_.get());
57   listener_ = listener;
58   device_loop_ = device_thread_loop;
59 }
60
61 void VideoCaptureManager::Unregister() {
62   DCHECK(listener_);
63   listener_ = NULL;
64 }
65
66 void VideoCaptureManager::EnumerateDevices(MediaStreamType stream_type) {
67   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
68   DVLOG(1) << "VideoCaptureManager::EnumerateDevices, type " << stream_type;
69   DCHECK(listener_);
70   base::PostTaskAndReplyWithResult(
71       device_loop_, FROM_HERE,
72       base::Bind(&VideoCaptureManager::GetAvailableDevicesOnDeviceThread, this,
73                  stream_type),
74       base::Bind(&VideoCaptureManager::OnDevicesEnumerated, this, stream_type));
75 }
76
77 int VideoCaptureManager::Open(const StreamDeviceInfo& device_info) {
78   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
79   DCHECK(listener_);
80
81   // Generate a new id for the session being opened.
82   const int capture_session_id = new_capture_session_id_++;
83
84   DCHECK(sessions_.find(capture_session_id) == sessions_.end());
85   DVLOG(1) << "VideoCaptureManager::Open, id " << capture_session_id;
86
87   // We just save the stream info for processing later.
88   sessions_[capture_session_id] = device_info.device;
89
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;
97 }
98
99 void VideoCaptureManager::Close(int capture_session_id) {
100   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
101   DCHECK(listener_);
102   DVLOG(1) << "VideoCaptureManager::Close, id " << capture_session_id;
103
104   std::map<int, MediaStreamDevice>::iterator session_it =
105       sessions_.find(capture_session_id);
106   if (session_it == sessions_.end()) {
107     NOTREACHED();
108     return;
109   }
110
111   DeviceEntry* const existing_device = GetDeviceEntryForMediaStreamDevice(
112       session_it->second);
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);
117
118     // StopSession() may have removed the last client, so we might need to
119     // close the device.
120     DestroyDeviceEntryIfNoClients(existing_device);
121   }
122
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);
128 }
129
130 void VideoCaptureManager::UseFakeDevice() {
131   use_fake_device_ = true;
132 }
133
134 void VideoCaptureManager::DoStartDeviceOnDeviceThread(
135     DeviceEntry* entry,
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());
140
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);
149       if (found) {
150         video_capture_device.reset(use_fake_device_ ?
151             media::FakeVideoCaptureDevice::Create(*found) :
152             media::VideoCaptureDevice::Create(*found));
153       }
154       break;
155     }
156     case MEDIA_TAB_VIDEO_CAPTURE: {
157       video_capture_device.reset(
158           WebContentsVideoCaptureDevice::Create(entry->id));
159       break;
160     }
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);
166       }
167 #endif  // defined(ENABLE_SCREEN_CAPTURE)
168       break;
169     }
170     default: {
171       NOTIMPLEMENTED();
172       break;
173     }
174   }
175
176   if (!video_capture_device) {
177     device_client->OnError();
178     return;
179   }
180
181   video_capture_device->AllocateAndStart(capture_params, device_client.Pass());
182   entry->video_capture_device = video_capture_device.Pass();
183 }
184
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
197          << ")";
198
199   DeviceEntry* entry = GetOrCreateDeviceEntry(params.session_id);
200   if (!entry) {
201     done_cb.Run(base::WeakPtr<VideoCaptureController>());
202     return;
203   }
204
205   DCHECK(entry->video_capture_controller);
206
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 << ")";
211
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;
218
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())));
223   }
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,
227                                              client_handler,
228                                              client_render_process,
229                                              params);
230 }
231
232 void VideoCaptureManager::StopCaptureForClient(
233     VideoCaptureController* controller,
234     VideoCaptureControllerID client_id,
235     VideoCaptureControllerEventHandler* client_handler) {
236   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
237   DCHECK(controller);
238   DCHECK(client_handler);
239
240   DeviceEntry* entry = GetDeviceEntryForController(controller);
241   if (!entry) {
242     NOTREACHED();
243     return;
244   }
245
246   // Detach client from controller.
247   int session_id = controller->RemoveClient(client_id, client_handler);
248   DVLOG(1) << "VideoCaptureManager::StopCaptureForClient, session_id = "
249            << session_id;
250
251   // If controller has no more clients, delete controller and device.
252   DestroyDeviceEntryIfNoClients(entry);
253 }
254
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();
260   }
261   entry->video_capture_device.reset();
262 }
263
264 void VideoCaptureManager::OnOpened(MediaStreamType stream_type,
265                                    int capture_session_id) {
266   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
267   if (!listener_) {
268     // Listener has been removed.
269     return;
270   }
271   listener_->Opened(stream_type, capture_session_id);
272 }
273
274 void VideoCaptureManager::OnClosed(MediaStreamType stream_type,
275                                    int capture_session_id) {
276   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
277   if (!listener_) {
278     // Listener has been removed.
279     return;
280   }
281   listener_->Closed(stream_type, capture_session_id);
282 }
283
284 void VideoCaptureManager::OnDevicesEnumerated(
285     MediaStreamType stream_type,
286     const media::VideoCaptureDevice::Names& device_names) {
287   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
288
289   if (!listener_) {
290     // Listener has been removed.
291     return;
292   }
293
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()));
300   }
301
302   listener_->DevicesEnumerated(stream_type, devices);
303 }
304
305 bool VideoCaptureManager::IsOnDeviceThread() const {
306   return device_loop_->BelongsToCurrentThread();
307 }
308
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;
316
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);
324       } else {
325         media::FakeVideoCaptureDevice::GetDeviceNames(&result);
326       }
327
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
330       // eliminate it?
331       video_capture_devices_ = result;
332       break;
333
334     case MEDIA_DESKTOP_VIDEO_CAPTURE:
335       // Do nothing.
336       break;
337
338     default:
339       NOTREACHED();
340       break;
341   }
342   return result;
343 }
344
345 VideoCaptureManager::DeviceEntry*
346 VideoCaptureManager::GetDeviceEntryForMediaStreamDevice(
347     const MediaStreamDevice& device_info) {
348   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
349
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) {
355       return device;
356     }
357   }
358   return NULL;
359 }
360
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) {
368       return *it;
369     }
370   }
371   return NULL;
372 }
373
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 << ")";
380
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(
388         FROM_HERE,
389         base::Bind(&VideoCaptureManager::DoStopDeviceOnDeviceThread, this,
390                    base::Owned(entry)));
391   }
392 }
393
394 VideoCaptureManager::DeviceEntry* VideoCaptureManager::GetOrCreateDeviceEntry(
395     int capture_session_id) {
396   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
397
398   std::map<int, MediaStreamDevice>::iterator session_it =
399       sessions_.find(capture_session_id);
400   if (session_it == sessions_.end()) {
401     return NULL;
402   }
403   const MediaStreamDevice& device_info = session_it->second;
404
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;
412   }
413
414   scoped_ptr<VideoCaptureController> video_capture_controller(
415       new VideoCaptureController());
416   DeviceEntry* new_device = new DeviceEntry(device_info.type,
417                                             device_info.id,
418                                             video_capture_controller.Pass());
419   devices_.insert(new_device);
420   return new_device;
421 }
422
423 }  // namespace content