| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/api/audio/audio_service.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/containers/contains.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chromeos/ash/components/audio/audio_device.h" |
| #include "chromeos/ash/components/audio/cras_audio_handler.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/browser/api/audio/audio_device_id_calculator.h" |
| |
| namespace extensions { |
| |
| using api::audio::AudioDeviceInfo; |
| using ::ash::AudioDevice; |
| using ::ash::AudioDeviceType; |
| using ::ash::CrasAudioHandler; |
| using ::content::BrowserThread; |
| |
| namespace { |
| |
| api::audio::DeviceType GetAsAudioApiDeviceType(AudioDeviceType type) { |
| switch (type) { |
| case AudioDeviceType::kHeadphone: |
| return api::audio::DeviceType::kHeadphone; |
| case AudioDeviceType::kMic: |
| return api::audio::DeviceType::kMic; |
| case AudioDeviceType::kUsb: |
| return api::audio::DeviceType::kUsb; |
| case AudioDeviceType::kBluetooth: |
| case AudioDeviceType::kBluetoothNbMic: |
| return api::audio::DeviceType::kBluetooth; |
| case AudioDeviceType::kHdmi: |
| return api::audio::DeviceType::kHdmi; |
| case AudioDeviceType::kInternalSpeaker: |
| return api::audio::DeviceType::kInternalSpeaker; |
| case AudioDeviceType::kInternalMic: |
| return api::audio::DeviceType::kInternalMic; |
| case AudioDeviceType::kFrontMic: |
| return api::audio::DeviceType::kFrontMic; |
| case AudioDeviceType::kRearMic: |
| return api::audio::DeviceType::kRearMic; |
| case AudioDeviceType::kKeyboardMic: |
| return api::audio::DeviceType::kKeyboardMic; |
| case AudioDeviceType::kHotword: |
| return api::audio::DeviceType::kHotword; |
| case AudioDeviceType::kLineout: |
| return api::audio::DeviceType::kLineout; |
| case AudioDeviceType::kPostMixLoopback: |
| return api::audio::DeviceType::kPostMixLoopback; |
| case AudioDeviceType::kPostDspLoopback: |
| return api::audio::DeviceType::kPostDspLoopback; |
| case AudioDeviceType::kAlsaLoopback: |
| return api::audio::DeviceType::kAlsaLoopback; |
| case AudioDeviceType::kOther: |
| return api::audio::DeviceType::kOther; |
| } |
| |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| class AudioServiceImpl : public AudioService, |
| public CrasAudioHandler::AudioObserver { |
| public: |
| explicit AudioServiceImpl(AudioDeviceIdCalculator* id_calculator); |
| |
| AudioServiceImpl(const AudioServiceImpl&) = delete; |
| AudioServiceImpl& operator=(const AudioServiceImpl&) = delete; |
| |
| ~AudioServiceImpl() override; |
| |
| // Called by listeners to this service to add/remove themselves as observers. |
| void AddObserver(AudioService::Observer* observer) override; |
| void RemoveObserver(AudioService::Observer* observer) override; |
| |
| // Start to query audio device information. |
| void GetDevices( |
| const api::audio::DeviceFilter* filter, |
| base::OnceCallback<void(bool, DeviceInfoList)> callback) override; |
| void SetActiveDeviceLists(const DeviceIdList* input_devices, |
| const DeviceIdList* output_devives, |
| base::OnceCallback<void(bool)> callback) override; |
| void SetDeviceSoundLevel(const std::string& device_id, |
| int level_value, |
| base::OnceCallback<void(bool)> callback) override; |
| void SetMute(bool is_input, |
| bool value, |
| base::OnceCallback<void(bool)> callback) override; |
| void GetMute(bool is_input, |
| base::OnceCallback<void(bool, bool)> callback) override; |
| |
| protected: |
| // CrasAudioHandler::AudioObserver overrides. |
| void OnOutputNodeVolumeChanged(uint64_t id, int volume) override; |
| void OnInputNodeGainChanged(uint64_t id, int gain) override; |
| void OnOutputMuteChanged(bool mute_on) override; |
| void OnInputMuteChanged( |
| bool mute_on, |
| CrasAudioHandler::InputMuteChangeMethod method) override; |
| void OnAudioNodesChanged() override; |
| void OnActiveOutputNodeChanged() override; |
| void OnActiveInputNodeChanged() override; |
| |
| private: |
| void NotifyLevelChanged(uint64_t id, int level); |
| void NotifyMuteChanged(bool is_input, bool is_muted); |
| void NotifyDevicesChanged(); |
| |
| uint64_t GetIdFromStr(const std::string& id_str); |
| bool GetAudioNodeIdList(const DeviceIdList& ids, |
| bool is_input, |
| CrasAudioHandler::NodeIdList* node_ids); |
| AudioDeviceInfo ToAudioDeviceInfo(const AudioDevice& device); |
| |
| // List of observers. |
| base::ObserverList<AudioService::Observer>::Unchecked observer_list_; |
| |
| raw_ptr<CrasAudioHandler, DanglingUntriaged> cras_audio_handler_; |
| |
| raw_ptr<AudioDeviceIdCalculator> id_calculator_; |
| |
| // Note: This should remain the last member so it'll be destroyed and |
| // invalidate the weak pointers before any other members are destroyed. |
| base::WeakPtrFactory<AudioServiceImpl> weak_ptr_factory_{this}; |
| }; |
| |
| AudioServiceImpl::AudioServiceImpl(AudioDeviceIdCalculator* id_calculator) |
| : cras_audio_handler_(CrasAudioHandler::Get()), |
| id_calculator_(id_calculator) { |
| CHECK(id_calculator_); |
| |
| if (cras_audio_handler_) |
| cras_audio_handler_->AddAudioObserver(this); |
| } |
| |
| AudioServiceImpl::~AudioServiceImpl() { |
| // The CrasAudioHandler global instance may have already been destroyed, so |
| // do not used the cached pointer here. |
| if (CrasAudioHandler::Get()) |
| CrasAudioHandler::Get()->RemoveAudioObserver(this); |
| } |
| |
| void AudioServiceImpl::AddObserver(AudioService::Observer* observer) { |
| observer_list_.AddObserver(observer); |
| } |
| |
| void AudioServiceImpl::RemoveObserver(AudioService::Observer* observer) { |
| observer_list_.RemoveObserver(observer); |
| } |
| |
| void AudioServiceImpl::GetDevices( |
| const api::audio::DeviceFilter* filter, |
| base::OnceCallback<void(bool, DeviceInfoList)> callback) { |
| DeviceInfoList devices_out; |
| if (!cras_audio_handler_) { |
| std::move(callback).Run(false, std::move(devices_out)); |
| return; |
| } |
| |
| ash::AudioDeviceList devices; |
| cras_audio_handler_->GetAudioDevices(&devices); |
| |
| bool accept_input = |
| !(filter && filter->stream_types) || |
| base::Contains(*filter->stream_types, api::audio::StreamType::kInput); |
| bool accept_output = |
| !(filter && filter->stream_types) || |
| base::Contains(*filter->stream_types, api::audio::StreamType::kOutput); |
| |
| for (const auto& device : devices) { |
| if (filter && filter->is_active && *filter->is_active != device.active) |
| continue; |
| if (device.is_input && !accept_input) |
| continue; |
| if (!device.is_input && !accept_output) |
| continue; |
| devices_out.push_back(ToAudioDeviceInfo(device)); |
| } |
| |
| std::move(callback).Run(true, std::move(devices_out)); |
| } |
| |
| void AudioServiceImpl::SetActiveDeviceLists( |
| const DeviceIdList* input_devices, |
| const DeviceIdList* output_devives, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(cras_audio_handler_); |
| if (!cras_audio_handler_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| CrasAudioHandler::NodeIdList input_nodes; |
| if (input_devices && |
| !GetAudioNodeIdList(*input_devices, true, &input_nodes)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| CrasAudioHandler::NodeIdList output_nodes; |
| if (output_devives && |
| !GetAudioNodeIdList(*output_devives, false, &output_nodes)) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| bool success = true; |
| if (output_devives) { |
| success = cras_audio_handler_->SetActiveOutputNodes(output_nodes); |
| DCHECK(success); |
| } |
| |
| if (input_devices) { |
| success = success && cras_audio_handler_->SetActiveInputNodes(input_nodes); |
| DCHECK(success); |
| } |
| std::move(callback).Run(success); |
| } |
| |
| void AudioServiceImpl::SetDeviceSoundLevel( |
| const std::string& device_id, |
| int level_value, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(cras_audio_handler_); |
| if (!cras_audio_handler_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| const AudioDevice* device = |
| cras_audio_handler_->GetDeviceFromId(GetIdFromStr(device_id)); |
| if (!device) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (level_value != -1) { |
| cras_audio_handler_->SetVolumeGainPercentForDevice(device->id, level_value); |
| std::move(callback).Run(true); |
| } else { |
| std::move(callback).Run(false); |
| } |
| } |
| |
| void AudioServiceImpl::SetMute(bool is_input, |
| bool value, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(cras_audio_handler_); |
| if (!cras_audio_handler_) { |
| std::move(callback).Run(false); |
| return; |
| } |
| |
| if (is_input) |
| cras_audio_handler_->SetInputMute( |
| value, CrasAudioHandler::InputMuteChangeMethod::kOther); |
| else |
| cras_audio_handler_->SetOutputMute(value); |
| |
| std::move(callback).Run(true); |
| } |
| |
| void AudioServiceImpl::GetMute(bool is_input, |
| base::OnceCallback<void(bool, bool)> callback) { |
| DCHECK(cras_audio_handler_); |
| if (!cras_audio_handler_) { |
| std::move(callback).Run(false, false); |
| return; |
| } |
| |
| const bool is_muted_result = is_input ? cras_audio_handler_->IsInputMuted() |
| : cras_audio_handler_->IsOutputMuted(); |
| std::move(callback).Run(true, is_muted_result); |
| } |
| |
| uint64_t AudioServiceImpl::GetIdFromStr(const std::string& id_str) { |
| uint64_t device_id; |
| if (!base::StringToUint64(id_str, &device_id)) |
| return 0; |
| else |
| return device_id; |
| } |
| |
| bool AudioServiceImpl::GetAudioNodeIdList( |
| const DeviceIdList& ids, |
| bool is_input, |
| CrasAudioHandler::NodeIdList* node_ids) { |
| for (const auto& device_id : ids) { |
| const AudioDevice* device = |
| cras_audio_handler_->GetDeviceFromId(GetIdFromStr(device_id)); |
| if (!device) |
| return false; |
| if (device->is_input != is_input) |
| return false; |
| node_ids->push_back(device->id); |
| } |
| return true; |
| } |
| |
| AudioDeviceInfo AudioServiceImpl::ToAudioDeviceInfo(const AudioDevice& device) { |
| AudioDeviceInfo info; |
| info.id = base::NumberToString(device.id); |
| info.stream_type = device.is_input |
| ? extensions::api::audio::StreamType::kInput |
| : extensions::api::audio::StreamType::kOutput; |
| info.device_type = GetAsAudioApiDeviceType(device.type); |
| info.display_name = device.display_name; |
| info.device_name = device.device_name; |
| info.is_active = device.active; |
| info.level = |
| device.is_input |
| ? cras_audio_handler_->GetInputGainPercentForDevice(device.id) |
| : cras_audio_handler_->GetOutputVolumePercentForDevice(device.id); |
| info.stable_device_id = |
| id_calculator_->GetStableDeviceId(device.stable_device_id); |
| |
| return info; |
| } |
| |
| void AudioServiceImpl::OnOutputNodeVolumeChanged(uint64_t id, int volume) { |
| NotifyLevelChanged(id, volume); |
| } |
| |
| void AudioServiceImpl::OnOutputMuteChanged(bool mute_on) { |
| NotifyMuteChanged(false, mute_on); |
| } |
| |
| void AudioServiceImpl::OnInputNodeGainChanged(uint64_t id, int gain) { |
| NotifyLevelChanged(id, gain); |
| } |
| |
| void AudioServiceImpl::OnInputMuteChanged( |
| bool mute_on, |
| CrasAudioHandler::InputMuteChangeMethod method) { |
| NotifyMuteChanged(true, mute_on); |
| } |
| |
| void AudioServiceImpl::OnAudioNodesChanged() { |
| NotifyDevicesChanged(); |
| } |
| |
| void AudioServiceImpl::OnActiveOutputNodeChanged() {} |
| |
| void AudioServiceImpl::OnActiveInputNodeChanged() {} |
| |
| void AudioServiceImpl::NotifyLevelChanged(uint64_t id, int level) { |
| for (auto& observer : observer_list_) |
| observer.OnLevelChanged(base::NumberToString(id), level); |
| } |
| |
| void AudioServiceImpl::NotifyMuteChanged(bool is_input, bool is_muted) { |
| for (auto& observer : observer_list_) |
| observer.OnMuteChanged(is_input, is_muted); |
| } |
| |
| void AudioServiceImpl::NotifyDevicesChanged() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| ash::AudioDeviceList devices; |
| cras_audio_handler_->GetAudioDevices(&devices); |
| |
| DeviceInfoList device_info_list; |
| for (const auto& device : devices) { |
| device_info_list.push_back(ToAudioDeviceInfo(device)); |
| } |
| |
| for (auto& observer : observer_list_) |
| observer.OnDevicesChanged(device_info_list); |
| } |
| |
| AudioService::Ptr AudioService::CreateInstance( |
| AudioDeviceIdCalculator* id_calculator) { |
| return std::make_unique<AudioServiceImpl>(id_calculator); |
| } |
| |
| } // namespace extensions |