Index: content/browser/renderer_host/media/media_devices_manager.cc |
diff --git a/content/browser/renderer_host/media/media_devices_manager.cc b/content/browser/renderer_host/media/media_devices_manager.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..de50a1f8206d1fb8c1d834de26e54220415513bc |
--- /dev/null |
+++ b/content/browser/renderer_host/media/media_devices_manager.cc |
@@ -0,0 +1,521 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "content/browser/renderer_host/media/media_devices_manager.h" |
+ |
+#include <stddef.h> |
+#include <stdint.h> |
+ |
+#include <algorithm> |
+#include <string> |
+ |
+#include "base/command_line.h" |
+#include "base/location.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/task_runner_util.h" |
+#include "base/threading/thread_checker.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "build/build_config.h" |
+#include "content/browser/renderer_host/media/media_stream_manager.h" |
+#include "content/browser/renderer_host/media/video_capture_manager.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "media/audio/audio_device_description.h" |
+#include "media/audio/audio_manager.h" |
+#include "media/base/media_switches.h" |
+ |
+#if defined(OS_MACOSX) |
+#include "base/bind_helpers.h" |
+#include "base/profiler/scoped_tracker.h" |
+#include "base/single_thread_task_runner.h" |
+#include "content/browser/browser_main_loop.h" |
+#include "media/device_monitors/device_monitor_mac.h" |
+#endif |
+ |
+namespace content { |
+ |
+namespace { |
+ |
+// Private helper method to generate a string for the log message that lists the |
+// human readable names of |devices|. |
+std::string GetLogMessageString(MediaDeviceType device_type, |
+ const MediaDeviceInfoArray& device_infos) { |
+ std::string output_string = |
+ base::StringPrintf("Getting devices of type %d:\n", device_type); |
+ if (device_infos.empty()) |
+ return output_string + "No devices found."; |
+ for (const auto& device_info : device_infos) |
+ output_string += " " + device_info.label + "\n"; |
+ return output_string; |
+} |
+ |
+MediaDeviceInfoArray EnumerateAudioDevicesOnDeviceThread( |
+ media::AudioManager* audio_manager, |
+ bool is_input) { |
+ DCHECK(audio_manager->GetTaskRunner()->BelongsToCurrentThread()); |
+ |
+ MediaDeviceInfoArray snapshot; |
+ media::AudioDeviceNames device_names; |
+ if (is_input) |
+ audio_manager->GetAudioInputDeviceNames(&device_names); |
+ else |
+ audio_manager->GetAudioOutputDeviceNames(&device_names); |
+ |
+ for (const media::AudioDeviceName& name : device_names) { |
+ snapshot.emplace_back( |
+ name.unique_id, name.device_name, |
+ is_input ? audio_manager->GetGroupIDInput(name.unique_id) |
+ : audio_manager->GetGroupIDOutput(name.unique_id)); |
+ } |
+ |
+ return snapshot; |
+} |
+ |
+MediaDeviceInfoArray GetFakeAudioDevices(bool is_input) { |
+ MediaDeviceInfoArray result; |
+ if (is_input) { |
+ result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId, |
+ "Fake Default Audio Input", |
+ "fake_group_audio_input_default"); |
+ result.emplace_back("fake_audio_input_1", "Fake Audio Input 1", |
+ "fake_group_audio_input_1"); |
+ result.emplace_back("fake_audio_input_2", "Fake Audio Input 2", |
+ "fake_group_audio_input_2"); |
+ } else { |
+ result.emplace_back(media::AudioDeviceDescription::kDefaultDeviceId, |
+ "Fake Default Audio Output", |
+ "fake_group_audio_output_default"); |
+ result.emplace_back("fake_audio_output_1", "Fake Audio Output 1", |
+ "fake_group_audio_output_1"); |
+ result.emplace_back("fake_audio_output_2", "Fake Audio Output 2", |
+ "fake_group_audio_output_2"); |
+ } |
+ |
+ return result; |
+} |
+ |
+} // namespace |
+ |
+struct MediaDevicesManager::EnumerationRequest { |
+ EnumerationRequest(const BoolDeviceTypes& requested_types, |
+ const EnumerationCallback& callback) |
+ : callback(callback) { |
+ requested = requested_types; |
+ has_seen_result.fill(false); |
+ } |
+ |
+ BoolDeviceTypes requested; |
+ BoolDeviceTypes has_seen_result; |
+ EnumerationCallback callback; |
+}; |
+ |
+// This class helps manage the consistency of cached enumeration results. |
+// It uses a sequence number for each invalidation and enumeration. |
+// A cache is considered valid if the sequence number for the last enumeration |
+// is greater than the sequence number for the last invalidation. |
+// The advantage of using invalidations over directly issuing enumerations upon |
+// each system notification is that some platforms issue multiple notifications |
+// on each device change. The cost of performing multiple redundant |
+// invalidations is significantly lower than the cost of issuing multiple |
+// redundant enumerations. |
+class MediaDevicesManager::CacheInfo { |
+ public: |
+ CacheInfo() |
+ : current_event_sequence_(0), |
+ seq_last_update_(0), |
+ seq_last_invalidation_(0), |
+ is_update_ongoing_(false) {} |
+ |
+ void InvalidateCache() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ seq_last_invalidation_ = NewEventSequence(); |
+ } |
+ |
+ bool IsLastUpdateValid() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return seq_last_update_ > seq_last_invalidation_ && !is_update_ongoing_; |
+ } |
+ |
+ void UpdateStarted() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(!is_update_ongoing_); |
+ seq_last_update_ = NewEventSequence(); |
+ is_update_ongoing_ = true; |
+ } |
+ |
+ void UpdateCompleted() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ DCHECK(is_update_ongoing_); |
+ is_update_ongoing_ = false; |
+ } |
+ |
+ bool is_update_ongoing() const { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return is_update_ongoing_; |
+ } |
+ |
+ private: |
+ int64_t NewEventSequence() { |
+ DCHECK(thread_checker_.CalledOnValidThread()); |
+ return ++current_event_sequence_; |
+ } |
+ |
+ int64_t current_event_sequence_; |
+ int64_t seq_last_update_; |
+ int64_t seq_last_invalidation_; |
+ bool is_update_ongoing_; |
+ base::ThreadChecker thread_checker_; |
+}; |
+ |
+MediaDevicesManager::MediaDevicesManager( |
+ media::AudioManager* audio_manager, |
+ const scoped_refptr<VideoCaptureManager>& video_capture_manager, |
+ MediaStreamManager* media_stream_manager) |
+ : use_fake_devices_(base::CommandLine::ForCurrentProcess()->HasSwitch( |
+ switches::kUseFakeDeviceForMediaStream)), |
+ audio_manager_(audio_manager), |
+ video_capture_manager_(video_capture_manager), |
+ media_stream_manager_(media_stream_manager), |
+ cache_infos_(NUM_MEDIA_DEVICE_TYPES), |
+ monitoring_started_(false), |
+ weak_factory_(this) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(audio_manager_); |
+ DCHECK(video_capture_manager_.get()); |
+ cache_policies_.fill(CachePolicy::NO_CACHE); |
+ has_seen_result_.fill(false); |
+} |
+ |
+MediaDevicesManager::~MediaDevicesManager() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+} |
+ |
+void MediaDevicesManager::EnumerateDevices( |
+ const BoolDeviceTypes& requested_types, |
+ const EnumerationCallback& callback) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ StartMonitoring(); |
+ |
+ requests_.emplace_back(requested_types, callback); |
+ bool all_results_cached = true; |
+ for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) { |
+ if (requested_types[i] && cache_policies_[i] == CachePolicy::NO_CACHE) { |
+ all_results_cached = false; |
+ DoEnumerateDevices(static_cast<MediaDeviceType>(i)); |
+ } |
+ } |
+ |
+ if (all_results_cached) |
+ ProcessRequests(); |
+} |
+ |
+void MediaDevicesManager::SetCachePolicy(MediaDeviceType type, |
+ CachePolicy policy) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(IsValidMediaDeviceType(type)); |
+ if (cache_policies_[type] == policy) |
+ return; |
+ |
+ cache_policies_[type] = policy; |
+ // If the new policy is SYSTEM_MONITOR, issue an enumeration to populate the |
+ // cache. |
+ if (policy == CachePolicy::SYSTEM_MONITOR) { |
+ cache_infos_[type].InvalidateCache(); |
+ DoEnumerateDevices(type); |
+ } |
+} |
+ |
+void MediaDevicesManager::StartMonitoring() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ if (monitoring_started_) |
+ return; |
+ |
+ if (!base::SystemMonitor::Get()) |
+ return; |
+ |
+ monitoring_started_ = true; |
+ base::SystemMonitor::Get()->AddDevicesChangedObserver(this); |
+ |
+ for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) { |
+ DCHECK(cache_policies_[i] != CachePolicy::SYSTEM_MONITOR); |
+ SetCachePolicy(static_cast<MediaDeviceType>(i), |
+ CachePolicy::SYSTEM_MONITOR); |
+ } |
+ |
+#if defined(OS_MACOSX) |
+ BrowserThread::PostTask( |
+ BrowserThread::UI, FROM_HERE, |
+ base::Bind(&MediaDevicesManager::StartMonitoringOnUIThread, |
+ base::Unretained(this))); |
+ |
+ // TODO(guidou): Remove this statement once the Mac device monitor is fixed to |
+ // correctly report device-change events for output-only audio devices. |
+ // See http://crbug.com/648173. |
+ SetCachePolicy(MEDIA_DEVICE_TYPE_AUDIO_OUTPUT, CachePolicy::NO_CACHE); |
+#endif |
+} |
+ |
+#if defined(OS_MACOSX) |
+void MediaDevicesManager::StartMonitoringOnUIThread() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::UI); |
+ // TODO(erikchen): Remove ScopedTracker below once crbug.com/458404 is fixed. |
+ tracked_objects::ScopedTracker tracking_profile1( |
+ FROM_HERE_WITH_EXPLICIT_FUNCTION( |
+ "458404 MediaDevicesManager::GetBrowserMainLoop")); |
+ BrowserMainLoop* browser_main_loop = content::BrowserMainLoop::GetInstance(); |
+ if (!browser_main_loop) |
+ return; |
+ |
+ // TODO(erikchen): Remove ScopedTracker below once crbug.com/458404 is |
+ // fixed. |
+ tracked_objects::ScopedTracker tracking_profile2( |
+ FROM_HERE_WITH_EXPLICIT_FUNCTION( |
+ "458404 MediaDevicesManager::GetTaskRunner")); |
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner = |
+ audio_manager_->GetTaskRunner(); |
+ // TODO(erikchen): Remove ScopedTracker below once crbug.com/458404 is |
+ // fixed. |
+ tracked_objects::ScopedTracker tracking_profile3( |
+ FROM_HERE_WITH_EXPLICIT_FUNCTION( |
+ "458404 MediaDevicesManager::DeviceMonitorMac::StartMonitoring")); |
+ browser_main_loop->device_monitor_mac()->StartMonitoring(task_runner); |
+} |
+#endif |
+ |
+void MediaDevicesManager::StopMonitoring() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ if (!monitoring_started_) |
+ return; |
+ base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); |
+ monitoring_started_ = false; |
+ for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) |
+ SetCachePolicy(static_cast<MediaDeviceType>(i), CachePolicy::NO_CACHE); |
+} |
+ |
+bool MediaDevicesManager::IsMonitoringStarted() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ return monitoring_started_; |
+} |
+ |
+void MediaDevicesManager::OnDevicesChanged( |
+ base::SystemMonitor::DeviceType device_type) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ switch (device_type) { |
+ case base::SystemMonitor::DEVTYPE_AUDIO: |
+ HandleDevicesChanged(MEDIA_DEVICE_TYPE_AUDIO_INPUT); |
+ HandleDevicesChanged(MEDIA_DEVICE_TYPE_AUDIO_OUTPUT); |
+ break; |
+ case base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE: |
+ HandleDevicesChanged(MEDIA_DEVICE_TYPE_VIDEO_INPUT); |
+ break; |
+ default: |
+ break; // Uninteresting device change. |
+ } |
+} |
+ |
+MediaDeviceInfoArray MediaDevicesManager::GetCachedDeviceInfo( |
+ MediaDeviceType type) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ return current_snapshot_[type]; |
+} |
+ |
+void MediaDevicesManager::DoEnumerateDevices(MediaDeviceType type) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(IsValidMediaDeviceType(type)); |
+ CacheInfo& cache_info = cache_infos_[type]; |
+ if (cache_info.is_update_ongoing()) |
+ return; |
+ |
+ cache_info.UpdateStarted(); |
+ switch (type) { |
+ case MEDIA_DEVICE_TYPE_AUDIO_INPUT: |
+ EnumerateAudioDevices(true /* is_input */); |
+ break; |
+ case MEDIA_DEVICE_TYPE_VIDEO_INPUT: |
+ video_capture_manager_->EnumerateDevices( |
+ base::Bind(&MediaDevicesManager::VideoInputDevicesEnumerated, |
+ weak_factory_.GetWeakPtr())); |
+ break; |
+ case MEDIA_DEVICE_TYPE_AUDIO_OUTPUT: |
+ EnumerateAudioDevices(false /* is_input */); |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+} |
+ |
+void MediaDevicesManager::EnumerateAudioDevices(bool is_input) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ MediaDeviceType type = |
+ is_input ? MEDIA_DEVICE_TYPE_AUDIO_INPUT : MEDIA_DEVICE_TYPE_AUDIO_OUTPUT; |
+ if (use_fake_devices_) { |
+ base::ThreadTaskRunnerHandle::Get()->PostTask( |
+ FROM_HERE, base::Bind(&MediaDevicesManager::DevicesEnumerated, |
+ weak_factory_.GetWeakPtr(), type, |
+ GetFakeAudioDevices(is_input))); |
+ return; |
+ } |
+ base::PostTaskAndReplyWithResult( |
+ audio_manager_->GetTaskRunner(), FROM_HERE, |
+ base::Bind(&EnumerateAudioDevicesOnDeviceThread, audio_manager_, |
+ is_input), |
+ base::Bind(&MediaDevicesManager::DevicesEnumerated, |
+ weak_factory_.GetWeakPtr(), type)); |
+} |
+ |
+void MediaDevicesManager::VideoInputDevicesEnumerated( |
+ const media::VideoCaptureDeviceDescriptors& descriptors) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ MediaDeviceInfoArray snapshot; |
+ for (const auto& descriptor : descriptors) { |
+ snapshot.emplace_back(descriptor.device_id, descriptor.GetNameAndModel(), |
+ std::string()); |
+ } |
+ DevicesEnumerated(MEDIA_DEVICE_TYPE_VIDEO_INPUT, snapshot); |
+} |
+ |
+void MediaDevicesManager::DevicesEnumerated( |
+ MediaDeviceType type, |
+ const MediaDeviceInfoArray& snapshot) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(IsValidMediaDeviceType(type)); |
+ UpdateSnapshot(type, snapshot); |
+ cache_infos_[type].UpdateCompleted(); |
+ has_seen_result_[type] = true; |
+ |
+ std::string log_message = |
+ "New device enumeration result:\n" + GetLogMessageString(type, snapshot); |
+ MediaStreamManager::SendMessageToNativeLog(log_message); |
+ |
+ if (cache_policies_[type] == CachePolicy::NO_CACHE) { |
+ for (auto& request : requests_) |
+ request.has_seen_result[type] = true; |
+ } |
+ |
+ // Note that IsLastUpdateValid is always true when policy is NO_CACHE. |
+ if (cache_infos_[type].IsLastUpdateValid()) { |
+ ProcessRequests(); |
+ } else { |
+ DoEnumerateDevices(type); |
+ } |
+} |
+ |
+void MediaDevicesManager::UpdateSnapshot( |
+ MediaDeviceType type, |
+ const MediaDeviceInfoArray& new_snapshot) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(IsValidMediaDeviceType(type)); |
+ |
+ // Only cache the device list when the device list has been changed. |
+ bool need_update_device_change_subscribers = false; |
+ MediaDeviceInfoArray& old_snapshot = current_snapshot_[type]; |
+ |
+ if (old_snapshot.size() != new_snapshot.size() || |
+ !std::equal(new_snapshot.begin(), new_snapshot.end(), |
+ old_snapshot.begin())) { |
+ if (type == MEDIA_DEVICE_TYPE_AUDIO_INPUT || |
+ type == MEDIA_DEVICE_TYPE_VIDEO_INPUT) { |
+ NotifyMediaStreamManager(type, new_snapshot); |
+ } |
+ |
+ // Do not notify device-change subscribers after the first enumeration |
+ // result, since it is not due to an actual device change. |
+ need_update_device_change_subscribers = |
+ has_seen_result_[type] && |
+ (old_snapshot.size() != 0 || new_snapshot.size() != 0); |
+ current_snapshot_[type] = new_snapshot; |
+ } |
+ |
+ if (need_update_device_change_subscribers) |
+ NotifyDeviceChangeSubscribers(type, new_snapshot); |
+} |
+ |
+void MediaDevicesManager::ProcessRequests() { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ requests_.erase(std::remove_if(requests_.begin(), requests_.end(), |
+ [this](const EnumerationRequest& request) { |
+ if (IsEnumerationRequestReady(request)) { |
+ request.callback.Run(current_snapshot_); |
+ return true; |
+ } |
+ return false; |
+ }), |
+ requests_.end()); |
+} |
+ |
+bool MediaDevicesManager::IsEnumerationRequestReady( |
+ const EnumerationRequest& request_info) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ bool is_ready = true; |
+ for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) { |
+ if (!request_info.requested[i]) |
+ continue; |
+ switch (cache_policies_[i]) { |
+ case CachePolicy::SYSTEM_MONITOR: |
+ if (!cache_infos_[i].IsLastUpdateValid()) |
+ is_ready = false; |
+ break; |
+ case CachePolicy::NO_CACHE: |
+ if (!request_info.has_seen_result[i]) |
+ is_ready = false; |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+ } |
+ return is_ready; |
+} |
+ |
+void MediaDevicesManager::HandleDevicesChanged(MediaDeviceType type) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(IsValidMediaDeviceType(type)); |
+ cache_infos_[type].InvalidateCache(); |
+ DoEnumerateDevices(type); |
+} |
+ |
+void MediaDevicesManager::NotifyMediaStreamManager( |
+ MediaDeviceType type, |
+ const MediaDeviceInfoArray& new_snapshot) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(type == MEDIA_DEVICE_TYPE_AUDIO_INPUT || |
+ type == MEDIA_DEVICE_TYPE_VIDEO_INPUT); |
+ |
+ if (!media_stream_manager_) |
+ return; |
+ |
+ for (const auto& old_device_info : current_snapshot_[type]) { |
+ auto it = std::find_if(new_snapshot.begin(), new_snapshot.end(), |
+ [&old_device_info](const MediaDeviceInfo& info) { |
+ return info.device_id == old_device_info.device_id; |
+ }); |
+ |
+ // If a device was removed, notify the MediaStreamManager to stop all |
+ // streams using that device. |
+ if (it == new_snapshot.end()) |
+ media_stream_manager_->StopRemovedDevice(type, old_device_info); |
+ } |
+ |
+ media_stream_manager_->NotifyDevicesChanged(type, new_snapshot); |
+} |
+ |
+void MediaDevicesManager::NotifyDeviceChangeSubscribers( |
+ MediaDeviceType type, |
+ const MediaDeviceInfoArray& snapshot) { |
+ DCHECK_CURRENTLY_ON(BrowserThread::IO); |
+ DCHECK(IsValidMediaDeviceType(type)); |
+ |
+ // TODO(guidou): Use device types instead of stream types, and remove the |
+ // call to MediaStreamManager once handling of device-change subscriptions |
+ // is removed from MediaStreamManager. See http://crbug.com/334244. |
+ // |permission_type| is used by MediaStreamManager to decide which permission |
+ // to check before forwarding the event to the renderer process. Since there |
+ // is no separate permission for audio output devices yet, use |
+ // MEDIA_DEVICE_AUDIO_CAPTURE for both audio input and output. |
+ MediaStreamType permission_type = type == MEDIA_DEVICE_TYPE_VIDEO_INPUT |
+ ? MEDIA_DEVICE_VIDEO_CAPTURE |
+ : MEDIA_DEVICE_AUDIO_CAPTURE; |
+ if (media_stream_manager_) |
+ media_stream_manager_->NotifyDeviceChangeSubscribers(permission_type); |
+} |
+ |
+} // namespace content |