| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/media_effects/media_device_info.h" |
| |
| #include "base/auto_reset.h" |
| #include "base/no_destructor.h" |
| #include "content/public/browser/audio_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/video_capture_service.h" |
| #include "services/video_capture/public/mojom/video_capture_service.mojom.h" |
| |
| namespace { |
| media_effects::MediaDeviceInfo* g_instance_override = nullptr; |
| } |
| |
| namespace media_effects { |
| |
| std::optional<std::string> GetRealDefaultDeviceId( |
| const std::vector<media::AudioDeviceDescription>& infos) { |
| for (const auto& info : infos) { |
| if (info.is_system_default && |
| !media::AudioDeviceDescription::IsDefaultDevice(info.unique_id)) { |
| return info.unique_id; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<std::string> GetRealCommunicationsDeviceId( |
| const std::vector<media::AudioDeviceDescription>& infos) { |
| for (const auto& info : infos) { |
| if (info.is_communications_device && |
| !media::AudioDeviceDescription::IsCommunicationsDevice( |
| info.unique_id)) { |
| return info.unique_id; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| std::vector<std::string> GetRealAudioDeviceNames( |
| const std::vector<media::AudioDeviceDescription>& infos) { |
| std::vector<std::string> real_names; |
| for (const auto& info : infos) { |
| if (!media::AudioDeviceDescription::IsDefaultDevice(info.unique_id) && |
| !media::AudioDeviceDescription::IsCommunicationsDevice( |
| info.unique_id)) { |
| real_names.push_back(info.device_name); |
| } |
| } |
| return real_names; |
| } |
| |
| std::vector<std::string> GetRealVideoDeviceNames( |
| const std::vector<media::VideoCaptureDeviceInfo>& infos) { |
| std::vector<std::string> names; |
| for (const auto& info : infos) { |
| names.push_back(info.descriptor.GetNameAndModel()); |
| } |
| return names; |
| } |
| |
| MediaDeviceInfo::MediaDeviceInfo() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (auto* monitor = base::SystemMonitor::Get(); monitor) { |
| monitor->AddDevicesChangedObserver(this); |
| } |
| |
| content::GetVideoCaptureService().ConnectToVideoSourceProvider( |
| video_source_provider_.BindNewPipeAndPassReceiver()); |
| content::GetAudioService().BindSystemInfo( |
| audio_system_info_.BindNewPipeAndPassReceiver()); |
| audio_system_info_.reset_on_disconnect(); |
| video_source_provider_.reset_on_disconnect(); |
| |
| // Initialize the device lists. `base::SystemMonitor` only calls observers for |
| // changes that happen after they start observing. |
| OnDevicesChanged(base::SystemMonitor::DeviceType::DEVTYPE_AUDIO); |
| OnDevicesChanged(base::SystemMonitor::DeviceType::DEVTYPE_VIDEO_CAPTURE); |
| } |
| |
| MediaDeviceInfo::~MediaDeviceInfo() { |
| if (auto* monitor = base::SystemMonitor::Get(); monitor) { |
| monitor->RemoveDevicesChangedObserver(this); |
| } |
| } |
| |
| MediaDeviceInfo* MediaDeviceInfo::GetInstance() { |
| if (g_instance_override) { |
| return g_instance_override; |
| } |
| |
| static base::NoDestructor<MediaDeviceInfo> instance; |
| return instance.get(); |
| } |
| |
| std::pair<std::unique_ptr<MediaDeviceInfo>, base::AutoReset<MediaDeviceInfo*>> |
| MediaDeviceInfo::OverrideInstanceForTesting() { |
| auto instance = base::WrapUnique(new MediaDeviceInfo()); |
| base::AutoReset auto_reset{ |
| &g_instance_override, |
| // Can't use std::make_optional because the constructor is private. |
| instance.get()}; |
| return std::make_pair(std::move(instance), std::move(auto_reset)); |
| } |
| |
| std::optional<media::AudioDeviceDescription> |
| MediaDeviceInfo::GetAudioDeviceInfoForId(const std::string& device_id) const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!audio_device_infos_) { |
| return std::nullopt; |
| } |
| |
| auto device_iter = |
| std::ranges::find(audio_device_infos_.value(), device_id, |
| &media::AudioDeviceDescription::unique_id); |
| if (device_iter != audio_device_infos_->end()) { |
| return *device_iter; |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<media::VideoCaptureDeviceInfo> |
| MediaDeviceInfo::GetVideoDeviceInfoForId(const std::string& device_id) const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!video_device_infos_) { |
| return std::nullopt; |
| } |
| |
| auto device_iter = |
| std::ranges::find(video_device_infos_.value(), device_id, |
| [](const media::VideoCaptureDeviceInfo& device) { |
| return device.descriptor.device_id; |
| }); |
| if (device_iter != video_device_infos_->end()) { |
| return *device_iter; |
| } |
| return std::nullopt; |
| } |
| |
| const std::optional<std::vector<media::AudioDeviceDescription>>& |
| MediaDeviceInfo::GetAudioDeviceInfos() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return audio_device_infos_; |
| } |
| const std::optional<std::vector<media::VideoCaptureDeviceInfo>>& |
| MediaDeviceInfo::GetVideoDeviceInfos() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return video_device_infos_; |
| } |
| |
| void MediaDeviceInfo::GetAudioInputStreamParameters( |
| const std::string& device_id, |
| audio::mojom::SystemInfo::GetInputStreamParametersCallback callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (audio_system_info_) { |
| audio_system_info_->GetInputStreamParameters(device_id, |
| std::move(callback)); |
| } |
| } |
| |
| void MediaDeviceInfo::AddObserver(Observer* observer) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!observers_.HasObserver(observer)) { |
| observers_.AddObserver(observer); |
| } |
| } |
| |
| void MediaDeviceInfo::RemoveObserver(Observer* observer) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| observers_.RemoveObserver(observer); |
| } |
| |
| void MediaDeviceInfo::OnDevicesChanged( |
| base::SystemMonitor::DeviceType device_type) { |
| if (device_type == base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE && |
| video_source_provider_) { |
| // Unretained is safe here because the `video_source_provider_` remote will |
| // be destroyed before `this`. |
| video_source_provider_->GetSourceInfos(base::BindOnce( |
| [](MediaDeviceInfo* mdi, |
| video_capture::mojom::VideoSourceProvider::GetSourceInfosResult, |
| const std::vector<media::VideoCaptureDeviceInfo>& device_infos) { |
| mdi->OnVideoDeviceInfosReceived(device_infos); |
| }, |
| base::Unretained(this))); |
| } else if (device_type == base::SystemMonitor::DEVTYPE_AUDIO && |
| audio_system_info_) { |
| // Unretained is safe here because the `audio_system_info_` remote will |
| // be destroyed before `this`. |
| audio_system_info_->GetInputDeviceDescriptions( |
| base::BindOnce(&MediaDeviceInfo::OnAudioDeviceDescriptionsReceived, |
| base::Unretained(this))); |
| } |
| } |
| |
| void MediaDeviceInfo::OnAudioDeviceDescriptionsReceived( |
| const std::vector<media::AudioDeviceDescription> device_infos) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| audio_device_infos_ = device_infos; |
| NotifyAudioDevicesChanged(); |
| } |
| |
| void MediaDeviceInfo::OnVideoDeviceInfosReceived( |
| const std::vector<media::VideoCaptureDeviceInfo>& device_infos) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| video_device_infos_ = device_infos; |
| NotifyVideoDevicesChanged(); |
| } |
| |
| void MediaDeviceInfo::NotifyAudioDevicesChanged() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| for (auto& observer : observers_) { |
| observer.OnAudioDevicesChanged(audio_device_infos_); |
| } |
| } |
| |
| void MediaDeviceInfo::NotifyVideoDevicesChanged() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| for (auto& observer : observers_) { |
| observer.OnVideoDevicesChanged(video_device_infos_); |
| } |
| } |
| |
| } // namespace media_effects |