OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 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/media_devices_manager.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <stdint.h> | |
9 | |
10 #include <algorithm> | |
11 #include <string> | |
12 | |
13 #include "base/command_line.h" | |
14 #include "base/location.h" | |
15 #include "base/strings/stringprintf.h" | |
16 #include "base/task_runner_util.h" | |
17 #include "base/threading/thread_task_runner_handle.h" | |
18 #include "build/build_config.h" | |
19 #include "content/browser/renderer_host/media/media_stream_manager.h" | |
20 #include "content/browser/renderer_host/media/video_capture_manager.h" | |
21 #include "content/public/browser/browser_thread.h" | |
22 #include "media/audio/audio_device_description.h" | |
23 #include "media/audio/audio_manager.h" | |
24 #include "media/base/media_switches.h" | |
25 | |
26 #if defined(OS_MACOSX) | |
27 #include "base/bind_helpers.h" | |
28 #include "base/profiler/scoped_tracker.h" | |
29 #include "base/single_thread_task_runner.h" | |
30 #include "content/browser/browser_main_loop.h" | |
31 #include "media/device_monitors/device_monitor_mac.h" | |
32 #endif | |
33 | |
34 namespace content { | |
35 | |
36 namespace { | |
37 | |
38 // Private helper method to generate a string for the log message that lists the | |
39 // human readable names of |devices|. | |
40 std::string GetLogMessageString(MediaDeviceType device_type, | |
41 const MediaDeviceInfoArray& device_infos) { | |
42 std::string output_string = | |
43 base::StringPrintf("Getting devices for stream type %d:\n", device_type); | |
tommi (sloooow) - chröme
2016/09/20 08:47:15
nit: do we want to have a line feed in there? may
Guido Urdaneta
2016/09/20 13:59:26
There is a newline because it lists each device in
tommi (sloooow) - chröme
2016/09/20 14:51:08
ok as is then
| |
44 if (device_infos.empty()) | |
45 return output_string + "No devices found."; | |
46 for (const auto& device_info : device_infos) | |
47 output_string += " " + device_info.label + "\n"; | |
48 return output_string; | |
49 } | |
50 | |
51 MediaDeviceInfoArray EnumerateAudioDevicesOnDeviceThread( | |
52 media::AudioManager* audio_manager, | |
53 bool is_input) { | |
54 DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread()); | |
55 | |
56 MediaDeviceInfoArray snapshot; | |
57 media::AudioDeviceNames device_names; | |
58 if (is_input) | |
59 audio_manager->GetAudioInputDeviceNames(&device_names); | |
60 else | |
61 audio_manager->GetAudioOutputDeviceNames(&device_names); | |
62 | |
63 for (const media::AudioDeviceName& name : device_names) { | |
64 snapshot.emplace_back( | |
65 name.unique_id, name.device_name, | |
66 is_input ? audio_manager->GetGroupIDInput(name.unique_id) | |
67 : audio_manager->GetGroupIDOutput(name.unique_id)); | |
68 } | |
69 | |
70 return snapshot; | |
71 } | |
72 | |
73 MediaDeviceInfoArray GetFakeAudioDevices(bool is_input) { | |
74 MediaDeviceInfoArray result; | |
75 if (is_input) { | |
76 result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId, | |
77 "Fake Default Audio Input", | |
78 "fake_group_audio_input_default"); | |
79 result.emplace_back("fake_audio_input_1", "Fake Audio Input 1", | |
80 "fake_group_audio_input_1"); | |
81 result.emplace_back("fake_audio_input_2", "Fake Audio Input 2", | |
82 "fake_group_audio_input_2"); | |
83 } else { | |
84 result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId, | |
85 "Fake Default Audio Output", | |
86 "fake_group_audio_output_default"); | |
87 result.emplace_back("fake_audio_output_1", "Fake Audio Output 1", | |
88 "fake_group_audio_output_1"); | |
89 result.emplace_back("fake_audio_output_2", "Fake Audio Output 2", | |
90 "fake_group_audio_output_2"); | |
91 } | |
92 | |
93 return result; | |
94 } | |
95 | |
96 } // namespace | |
97 | |
98 struct MediaDevicesManager::EnumerationRequest { | |
99 EnumerationRequest(const BoolDeviceTypes& requested_types, | |
100 const EnumerationCallback& callback) | |
101 : callback(callback) { | |
102 requested = requested_types; | |
103 has_seen_result.fill(false); | |
104 } | |
105 | |
106 BoolDeviceTypes requested; | |
107 BoolDeviceTypes has_seen_result; | |
108 EnumerationCallback callback; | |
109 }; | |
110 | |
111 // This class helps manage the consistency of cached enumeration results. | |
112 // It uses a sequence number for each invalidation and enumeration. | |
113 // A cache is considered valid if the sequence number for the last enumeration | |
114 // is greater than the sequence number for the last invalidation. | |
115 // The advantage of using invalidations over directly issuing enumerations upon | |
116 // each system notification is that some platforms issue multiple notifications | |
117 // on each device change. The cost of performing multiple invalidations is | |
118 // significantly lower than the cost of issuing multiple unnecessary | |
119 // enumerations. | |
120 class MediaDevicesManager::CacheInfo { | |
121 public: | |
122 CacheInfo() | |
123 : current_event_sequence_(0), | |
124 seq_last_update_(0), | |
125 seq_last_invalidation_(0), | |
126 is_update_ongoing_(false) {} | |
127 | |
128 void InvalidateCache() { seq_last_invalidation_ = NewEventSequence(); } | |
129 | |
130 bool IsLastUpdateValid() { | |
131 return seq_last_update_ > seq_last_invalidation_ && !is_update_ongoing_; | |
132 } | |
133 | |
134 void UpdateStarted() { | |
135 DCHECK(!is_update_ongoing_); | |
136 seq_last_update_ = NewEventSequence(); | |
137 is_update_ongoing_ = true; | |
138 } | |
139 | |
140 void UpdateCompleted() { | |
141 DCHECK(is_update_ongoing_); | |
142 is_update_ongoing_ = false; | |
143 } | |
144 | |
145 bool is_update_ongoing() { return is_update_ongoing_; } | |
tommi (sloooow) - chröme
2016/09/20 08:47:14
const?
btw, would it make sense to have thread ch
Guido Urdaneta
2016/09/20 13:59:26
Done.
| |
146 | |
147 private: | |
148 int64_t NewEventSequence() { return ++current_event_sequence_; } | |
149 | |
150 int64_t current_event_sequence_; | |
151 int64_t seq_last_update_; | |
152 int64_t seq_last_invalidation_; | |
153 bool is_update_ongoing_; | |
154 }; | |
155 | |
156 MediaDevicesManager::MediaDevicesManager( | |
157 media::AudioManager* audio_manager, | |
158 const scoped_refptr<VideoCaptureManager>& video_capture_manager, | |
159 MediaStreamManager* media_stream_manager) | |
160 : use_fake_devices_(base::CommandLine::ForCurrentProcess()->HasSwitch( | |
161 switches::kUseFakeDeviceForMediaStream)), | |
162 audio_manager_(audio_manager), | |
163 video_capture_manager_(video_capture_manager), | |
164 media_stream_manager_(media_stream_manager), | |
165 cache_infos_(NUM_MEDIA_DEVICE_TYPES), | |
166 monitoring_started_(false), | |
167 weak_factory_(this) { | |
168 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
169 DCHECK(audio_manager_); | |
170 DCHECK(video_capture_manager_.get()); | |
171 cache_policies_.fill(CachePolicy::NO_CACHE); | |
172 has_seen_result_.fill(false); | |
173 } | |
174 | |
175 MediaDevicesManager::~MediaDevicesManager() { | |
176 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
177 } | |
178 | |
179 void MediaDevicesManager::EnumerateDevices( | |
180 const BoolDeviceTypes& requested_types, | |
181 const EnumerationCallback& callback) { | |
182 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
183 StartMonitoring(); | |
184 | |
185 requests_.emplace_back(requested_types, callback); | |
186 bool all_results_cached = true; | |
187 for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) { | |
188 if (requested_types[i] && cache_policies_[i] == CachePolicy::NO_CACHE) { | |
189 all_results_cached = false; | |
190 DoEnumerateDevices(static_cast<MediaDeviceType>(i)); | |
191 } | |
192 } | |
193 | |
194 if (all_results_cached) | |
195 ProcessRequests(); | |
196 } | |
197 | |
198 void MediaDevicesManager::SetCachePolicy(MediaDeviceType type, | |
199 CachePolicy policy) { | |
200 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
201 DCHECK(IsValidMediaDeviceType(type)); | |
202 if (cache_policies_[type] == policy) | |
203 return; | |
204 | |
205 cache_policies_[type] = policy; | |
206 // If the new policy is SYSTEM_MONITOR, issue an enumeration to populate the | |
207 // cache. | |
208 if (policy == CachePolicy::SYSTEM_MONITOR) { | |
209 cache_infos_[type].InvalidateCache(); | |
210 DoEnumerateDevices(type); | |
211 } | |
212 } | |
213 | |
214 void MediaDevicesManager::StartMonitoring() { | |
215 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
216 if (monitoring_started_) | |
217 return; | |
218 | |
219 if (!base::SystemMonitor::Get()) | |
220 return; | |
221 | |
222 monitoring_started_ = true; | |
223 base::SystemMonitor::Get()->AddDevicesChangedObserver(this); | |
224 | |
225 for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) { | |
226 DCHECK(cache_policies_[i] != CachePolicy::SYSTEM_MONITOR); | |
227 SetCachePolicy(static_cast<MediaDeviceType>(i), | |
228 CachePolicy::SYSTEM_MONITOR); | |
229 } | |
230 | |
231 #if defined(OS_MACOSX) | |
232 BrowserThread::PostTask( | |
233 BrowserThread::UI, FROM_HERE, | |
234 base::Bind(&MediaDevicesManager::StartMonitoringOnUIThread, | |
235 base::Unretained(this))); | |
236 | |
237 // TODO(guidou): Remove this statement once the Mac device monitor is fixed to | |
238 // correctly report device-change events for output-only audio devices. | |
239 // See http://crbug.com/648173. | |
240 SetCachePolicy(MEDIA_DEVICE_TYPE_AUDIO_OUTPUT, CachePolicy::NO_CACHE); | |
241 #endif | |
242 } | |
243 | |
244 #if defined(OS_MACOSX) | |
245 void MediaDevicesManager::StartMonitoringOnUIThread() { | |
246 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
247 // TODO(erikchen): Remove ScopedTracker below once crbug.com/458404 is fixed. | |
248 tracked_objects::ScopedTracker tracking_profile1( | |
249 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
250 "458404 MediaDevicesManager::GetBrowserMainLoop")); | |
251 BrowserMainLoop* browser_main_loop = content::BrowserMainLoop::GetInstance(); | |
252 if (!browser_main_loop) | |
253 return; | |
254 | |
255 // TODO(erikchen): Remove ScopedTracker below once crbug.com/458404 is | |
256 // fixed. | |
257 tracked_objects::ScopedTracker tracking_profile2( | |
258 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
259 "458404 MediaDevicesManager::GetTaskRunner")); | |
260 const scoped_refptr<base::SingleThreadTaskRunner> task_runner = | |
261 audio_manager_->GetTaskRunner(); | |
262 // TODO(erikchen): Remove ScopedTracker below once crbug.com/458404 is | |
263 // fixed. | |
264 tracked_objects::ScopedTracker tracking_profile3( | |
265 FROM_HERE_WITH_EXPLICIT_FUNCTION( | |
266 "458404 MediaDevicesManager::DeviceMonitorMac::StartMonitoring")); | |
267 browser_main_loop->device_monitor_mac()->StartMonitoring(task_runner); | |
268 } | |
269 #endif | |
270 | |
271 void MediaDevicesManager::StopMonitoring() { | |
272 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
273 if (!monitoring_started_) | |
274 return; | |
275 base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); | |
276 monitoring_started_ = false; | |
277 for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) | |
278 SetCachePolicy(static_cast<MediaDeviceType>(i), CachePolicy::NO_CACHE); | |
279 } | |
280 | |
281 bool MediaDevicesManager::IsMonitoringStarted() { | |
282 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
283 return monitoring_started_; | |
284 } | |
285 | |
286 void MediaDevicesManager::OnDevicesChanged( | |
287 base::SystemMonitor::DeviceType device_type) { | |
288 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
289 switch (device_type) { | |
290 case base::SystemMonitor::DEVTYPE_AUDIO: | |
291 HandleDevicesChanged(MEDIA_DEVICE_TYPE_AUDIO_INPUT); | |
292 HandleDevicesChanged(MEDIA_DEVICE_TYPE_AUDIO_OUTPUT); | |
293 break; | |
294 case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: | |
295 HandleDevicesChanged(MEDIA_DEVICE_TYPE_VIDEO_INPUT); | |
296 break; | |
297 default: | |
298 break; // Uninteresting device change. | |
299 } | |
300 } | |
301 | |
302 MediaDeviceInfoArray MediaDevicesManager::GetCachedDeviceInfo( | |
303 MediaDeviceType type) { | |
304 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
305 return current_snapshot_[type]; | |
306 } | |
307 | |
308 void MediaDevicesManager::DoEnumerateDevices(MediaDeviceType type) { | |
309 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
310 DCHECK(IsValidMediaDeviceType(type)); | |
311 CacheInfo& cache_info = cache_infos_[type]; | |
312 if (cache_info.is_update_ongoing()) | |
313 return; | |
314 | |
315 cache_info.UpdateStarted(); | |
316 switch (type) { | |
317 case MEDIA_DEVICE_TYPE_AUDIO_INPUT: | |
318 EnumerateAudioDevices(true /* is_input */); | |
319 break; | |
320 case MEDIA_DEVICE_TYPE_VIDEO_INPUT: | |
321 video_capture_manager_->EnumerateDevices( | |
322 base::Bind(&MediaDevicesManager::VideoInputDevicesEnumerated, | |
323 weak_factory_.GetWeakPtr())); | |
324 break; | |
325 case MEDIA_DEVICE_TYPE_AUDIO_OUTPUT: | |
326 EnumerateAudioDevices(false /* is_input */); | |
327 break; | |
328 default: | |
329 NOTREACHED(); | |
330 } | |
331 } | |
332 | |
333 void MediaDevicesManager::EnumerateAudioDevices(bool is_input) { | |
334 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
335 MediaDeviceType type = | |
336 is_input ? MEDIA_DEVICE_TYPE_AUDIO_INPUT : MEDIA_DEVICE_TYPE_AUDIO_OUTPUT; | |
337 if (use_fake_devices_) { | |
338 base::ThreadTaskRunnerHandle::Get()->PostTask( | |
339 FROM_HERE, base::Bind(&MediaDevicesManager::DevicesEnumerated, | |
340 weak_factory_.GetWeakPtr(), type, | |
341 GetFakeAudioDevices(is_input))); | |
342 return; | |
343 } | |
344 base::PostTaskAndReplyWithResult( | |
345 audio_manager_->GetTaskRunner(), FROM_HERE, | |
346 base::Bind(&EnumerateAudioDevicesOnDeviceThread, audio_manager_, | |
347 is_input), | |
348 base::Bind(&MediaDevicesManager::DevicesEnumerated, | |
349 weak_factory_.GetWeakPtr(), type)); | |
350 } | |
351 | |
352 void MediaDevicesManager::VideoInputDevicesEnumerated( | |
353 const media::VideoCaptureDeviceDescriptors& descriptors) { | |
354 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
355 MediaDeviceInfoArray snapshot; | |
356 for (const auto& descriptor : descriptors) { | |
357 snapshot.emplace_back(descriptor.device_id, descriptor.GetNameAndModel(), | |
358 std::string()); | |
359 } | |
360 DevicesEnumerated(MEDIA_DEVICE_TYPE_VIDEO_INPUT, snapshot); | |
361 } | |
362 | |
363 void MediaDevicesManager::DevicesEnumerated( | |
364 MediaDeviceType type, | |
365 const MediaDeviceInfoArray& snapshot) { | |
366 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
367 DCHECK(IsValidMediaDeviceType(type)); | |
368 UpdateSnapshot(type, snapshot); | |
369 cache_infos_[type].UpdateCompleted(); | |
370 has_seen_result_[type] = true; | |
371 | |
372 std::string log_message = | |
373 "New device enumeration result:\n" + GetLogMessageString(type, snapshot); | |
374 MediaStreamManager::SendMessageToNativeLog(log_message); | |
375 | |
376 if (cache_policies_[type] == CachePolicy::NO_CACHE) { | |
377 for (auto& request : requests_) | |
378 request.has_seen_result[type] = true; | |
379 } | |
380 | |
381 // Note that IsLastUpdateValid is always true when policy is NO_CACHE. | |
382 if (cache_infos_[type].IsLastUpdateValid()) { | |
383 ProcessRequests(); | |
384 } else { | |
385 DoEnumerateDevices(type); | |
386 } | |
387 } | |
388 | |
389 void MediaDevicesManager::UpdateSnapshot( | |
390 MediaDeviceType type, | |
391 const MediaDeviceInfoArray& new_snapshot) { | |
392 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
393 DCHECK(IsValidMediaDeviceType(type)); | |
394 | |
395 // Only cache the device list when the device list has been changed. | |
396 bool need_update_device_change_subscribers = false; | |
397 MediaDeviceInfoArray& old_snapshot = current_snapshot_[type]; | |
398 | |
399 if (old_snapshot.size() != new_snapshot.size() || | |
400 !std::equal(new_snapshot.begin(), new_snapshot.end(), | |
401 old_snapshot.begin())) { | |
402 if (type == MEDIA_DEVICE_TYPE_AUDIO_INPUT || | |
403 type == MEDIA_DEVICE_TYPE_VIDEO_INPUT) { | |
404 NotifyMediaStreamManager(type, new_snapshot); | |
405 } | |
406 | |
407 // Do not notify device-change subscribers after the first enumeration | |
408 // result, since it is not due to an actual device change. | |
409 need_update_device_change_subscribers = | |
410 has_seen_result_[type] && | |
411 (old_snapshot.size() != 0 || new_snapshot.size() != 0); | |
412 current_snapshot_[type] = new_snapshot; | |
413 } | |
414 | |
415 if (need_update_device_change_subscribers) | |
416 NotifyDeviceChangeSubscribers(type, new_snapshot); | |
417 } | |
418 | |
419 void MediaDevicesManager::ProcessRequests() { | |
420 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
421 requests_.erase(std::remove_if(requests_.begin(), requests_.end(), | |
422 [this](const EnumerationRequest& request) { | |
423 if (IsEnumerationRequestReady(request)) { | |
424 request.callback.Run(current_snapshot_); | |
425 return true; | |
426 } | |
427 return false; | |
428 }), | |
429 requests_.end()); | |
430 } | |
431 | |
432 bool MediaDevicesManager::IsEnumerationRequestReady( | |
433 const EnumerationRequest& request_info) { | |
434 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
435 bool is_ready = true; | |
436 for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) { | |
437 if (!request_info.requested[i]) | |
438 continue; | |
439 switch (cache_policies_[i]) { | |
440 case CachePolicy::SYSTEM_MONITOR: | |
441 if (!cache_infos_[i].IsLastUpdateValid()) | |
442 is_ready = false; | |
443 break; | |
444 case CachePolicy::NO_CACHE: | |
445 if (!request_info.has_seen_result[i]) | |
446 is_ready = false; | |
447 break; | |
448 default: | |
449 NOTREACHED(); | |
450 } | |
451 } | |
452 return is_ready; | |
453 } | |
454 | |
455 void MediaDevicesManager::HandleDevicesChanged(MediaDeviceType type) { | |
456 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
457 DCHECK(IsValidMediaDeviceType(type)); | |
458 cache_infos_[type].InvalidateCache(); | |
459 DoEnumerateDevices(type); | |
460 } | |
461 | |
462 void MediaDevicesManager::NotifyMediaStreamManager( | |
463 MediaDeviceType type, | |
464 const MediaDeviceInfoArray& new_snapshot) { | |
465 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
466 DCHECK(IsValidMediaDeviceType(type)); | |
467 DCHECK(type == MEDIA_DEVICE_TYPE_AUDIO_INPUT || | |
468 type == MEDIA_DEVICE_TYPE_VIDEO_INPUT); | |
469 MediaDeviceInfoArray& old_snapshot = current_snapshot_[type]; | |
470 | |
471 for (const auto& old_device_info : old_snapshot) { | |
472 auto it = std::find_if(new_snapshot.begin(), new_snapshot.end(), | |
473 [&old_device_info](const MediaDeviceInfo& info) { | |
474 return info.device_id == old_device_info.device_id; | |
475 }); | |
476 | |
477 // If a device was removed, notify the MediaStreamManager to stop all | |
478 // streams using that device. | |
479 if (it == new_snapshot.end() && media_stream_manager_) | |
480 media_stream_manager_->StopRemovedDevice(type, old_device_info); | |
481 } | |
482 | |
483 if (media_stream_manager_) | |
484 media_stream_manager_->NotifyDevicesChanged(type, new_snapshot); | |
485 } | |
486 | |
487 void MediaDevicesManager::NotifyDeviceChangeSubscribers( | |
488 MediaDeviceType type, | |
489 const MediaDeviceInfoArray& snapshot) { | |
490 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
491 | |
492 // TODO(guidou): Use device types instead of stream types, and remove the | |
493 // call to MediaStreamManager once handling of device-change subscriptions | |
494 // is removed from MediaStreamManager. See http://crbug.com/334244. | |
495 MediaStreamType stream_type = type == MEDIA_DEVICE_TYPE_VIDEO_INPUT | |
496 ? MEDIA_DEVICE_VIDEO_CAPTURE | |
497 : MEDIA_DEVICE_AUDIO_CAPTURE; | |
498 if (media_stream_manager_) | |
499 media_stream_manager_->NotifyDeviceChangeSubscribers(stream_type); | |
500 } | |
501 | |
502 } // namespace content | |
OLD | NEW |