Chromium Code Reviews| 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..3d8a2a7cebbb61c54974fd829c06a03fb0f96ae5 |
| --- /dev/null |
| +++ b/content/browser/renderer_host/media/media_devices_manager.cc |
| @@ -0,0 +1,502 @@ |
| +// 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_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 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
|
| + 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 invalidations is |
| +// significantly lower than the cost of issuing multiple unnecessary |
| +// enumerations. |
| +class MediaDevicesManager::CacheInfo { |
| + public: |
| + CacheInfo() |
| + : current_event_sequence_(0), |
| + seq_last_update_(0), |
| + seq_last_invalidation_(0), |
| + is_update_ongoing_(false) {} |
| + |
| + void InvalidateCache() { seq_last_invalidation_ = NewEventSequence(); } |
| + |
| + bool IsLastUpdateValid() { |
| + return seq_last_update_ > seq_last_invalidation_ && !is_update_ongoing_; |
| + } |
| + |
| + void UpdateStarted() { |
| + DCHECK(!is_update_ongoing_); |
| + seq_last_update_ = NewEventSequence(); |
| + is_update_ongoing_ = true; |
| + } |
| + |
| + void UpdateCompleted() { |
| + DCHECK(is_update_ongoing_); |
| + is_update_ongoing_ = false; |
| + } |
| + |
| + 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.
|
| + |
| + private: |
| + int64_t NewEventSequence() { return ++current_event_sequence_; } |
| + |
| + int64_t current_event_sequence_; |
| + int64_t seq_last_update_; |
| + int64_t seq_last_invalidation_; |
| + bool is_update_ongoing_; |
| +}; |
| + |
| +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(IsValidMediaDeviceType(type)); |
| + DCHECK(type == MEDIA_DEVICE_TYPE_AUDIO_INPUT || |
| + type == MEDIA_DEVICE_TYPE_VIDEO_INPUT); |
| + MediaDeviceInfoArray& old_snapshot = current_snapshot_[type]; |
| + |
| + for (const auto& old_device_info : old_snapshot) { |
| + 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_) |
| + media_stream_manager_->StopRemovedDevice(type, old_device_info); |
| + } |
| + |
| + if (media_stream_manager_) |
| + media_stream_manager_->NotifyDevicesChanged(type, new_snapshot); |
| +} |
| + |
| +void MediaDevicesManager::NotifyDeviceChangeSubscribers( |
| + MediaDeviceType type, |
| + const MediaDeviceInfoArray& snapshot) { |
| + DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| + |
| + // 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. |
| + MediaStreamType stream_type = type == MEDIA_DEVICE_TYPE_VIDEO_INPUT |
| + ? MEDIA_DEVICE_VIDEO_CAPTURE |
| + : MEDIA_DEVICE_AUDIO_CAPTURE; |
| + if (media_stream_manager_) |
| + media_stream_manager_->NotifyDeviceChangeSubscribers(stream_type); |
| +} |
| + |
| +} // namespace content |