| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/components/audio/cros_audio_config_impl.h" |
| |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/time/time.h" |
| #include "base/timer/timer.h" |
| #include "chromeos/ash/components/audio/audio_device.h" |
| #include "chromeos/ash/components/audio/cras_audio_handler.h" |
| #include "chromeos/ash/components/audio/public/mojom/cros_audio_config.mojom-shared.h" |
| |
| namespace ash::audio_config { |
| |
| namespace { |
| |
| constexpr int kDefaultInternalMicId = 0; |
| constexpr base::TimeDelta kMetricsDelayTimerInterval = base::Seconds(2); |
| |
| // Histogram names. |
| constexpr char kOutputMuteChangeHistogramName[] = |
| "ChromeOS.CrosAudioConfig.OutputMuteStateChange"; |
| constexpr char kInputMuteChangeHistogramName[] = |
| "ChromeOS.CrosAudioConfig.InputMuteStateChange"; |
| constexpr char kNoiseCancellationEnabledHistogramName[] = |
| "ChromeOS.CrosAudioConfig.NoiseCancellationEnabled"; |
| constexpr char kOutputVolumeChangeHistogramName[] = |
| "ChromeOS.CrosAudioConfig.OutputVolumeSetTo"; |
| constexpr char kInputGainChangeHistogramName[] = |
| "ChromeOS.CrosAudioConfig.InputGainSetTo"; |
| constexpr char kAudioDeviceChangeHistogramName[] = |
| "ChromeOS.CrosAudioConfig.DeviceChange"; |
| constexpr char kOutputDeviceTypeHistogramName[] = |
| "ChromeOS.CrosAudioConfig.OutputDeviceTypeChangedTo"; |
| constexpr char kInputDeviceTypeHistogramName[] = |
| "ChromeOS.CrosAudioConfig.InputDeviceTypeChangedTo"; |
| |
| // Creates an inactive input device with default property configuration. |
| AudioDevice CreateStubInternalMic() { |
| AudioDevice internal_mic; |
| internal_mic.id = kDefaultInternalMicId; |
| internal_mic.is_input = true; |
| internal_mic.stable_device_id_version = 2; |
| internal_mic.type = AudioDeviceType::kInternalMic; |
| internal_mic.active = false; |
| return internal_mic; |
| } |
| |
| // Updates active and id properties on stub `internal_mic` based on provided |
| // front or rear device. |
| void UpdateInternalMicBasedOnAudioDevice(AudioDevice& internal_mic, |
| const AudioDevice& device) { |
| DCHECK(device.is_input && (device.type == AudioDeviceType::kFrontMic || |
| device.type == AudioDeviceType::kRearMic)); |
| |
| // Update internal_mic id if it has not been set or if the incoming device is |
| // active. |
| if (internal_mic.id == kDefaultInternalMicId || device.active) { |
| internal_mic.id = device.id; |
| } |
| |
| // Update active |
| if (device.active) { |
| internal_mic.active = true; |
| } |
| } |
| |
| // Determines the correct `mojom::AudioEffectState` for an audio device |
| // depending on if: |
| // - the overall device(chromebook) supports noise cancellation |
| // - the provided audio device supports noise cancellation |
| // - if noise cancellation is enabled in CrasAudioHandler |
| mojom::AudioEffectState GetNoiseCancellationState(const AudioDevice& device) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| |
| if (!audio_handler->IsNoiseCancellationSupportedForDevice(device.id)) { |
| return mojom::AudioEffectState::kNotSupported; |
| } |
| |
| // Device supports noise cancellation, get current device wide preference |
| // state from `CrasAudioHandler`. |
| return audio_handler->GetNoiseCancellationState() |
| ? mojom::AudioEffectState::kEnabled |
| : mojom::AudioEffectState::kNotEnabled; |
| } |
| |
| // Determines the correct `mojom::AudioEffectState` for an audio device |
| mojom::AudioEffectState GetStyleTransferState(const AudioDevice& device) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| |
| if (!audio_handler->IsStyleTransferSupportedForDevice(device.id)) { |
| return mojom::AudioEffectState::kNotSupported; |
| } |
| |
| // Device supports style transfer, get current device wide preference |
| // state from `CrasAudioHandler`. |
| return audio_handler->GetStyleTransferState() |
| ? mojom::AudioEffectState::kEnabled |
| : mojom::AudioEffectState::kNotEnabled; |
| } |
| |
| // Determines the correct `mojom::AudioEffectState` for an audio device |
| mojom::AudioEffectState GetForceRespectUiGainsState(const AudioDevice& device) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| CHECK(audio_handler); |
| |
| // Get current device wide preference state from `CrasAudioHandler`. |
| return audio_handler->GetForceRespectUiGainsState() |
| ? mojom::AudioEffectState::kEnabled |
| : mojom::AudioEffectState::kNotEnabled; |
| } |
| |
| void RecordMuteStateChanged(const char* histogram_name, bool muted) { |
| base::UmaHistogramEnumeration( |
| histogram_name, |
| muted ? AudioMuteButtonAction::kMuted : AudioMuteButtonAction::kUnmuted); |
| } |
| |
| // Determines the correct `mojom::AudioEffectState` for an audio device |
| mojom::AudioEffectState GetHfpMicSrState(const AudioDevice& device) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| |
| if (!audio_handler->IsHfpMicSrSupportedForDevice(device.id)) { |
| return mojom::AudioEffectState::kNotSupported; |
| } |
| |
| // Device supports hfp mic sr, get current device wide preference |
| // state from `CrasAudioHandler`. |
| return audio_handler->GetHfpMicSrState() |
| ? mojom::AudioEffectState::kEnabled |
| : mojom::AudioEffectState::kNotEnabled; |
| } |
| |
| // Determines the correct `mojom::AudioEffectState` for an audio device |
| mojom::AudioEffectState GetSpatialAudioState(const AudioDevice& device) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| CHECK(audio_handler); |
| |
| if (!audio_handler->IsSpatialAudioSupportedForDevice(device.id)) { |
| return mojom::AudioEffectState::kNotSupported; |
| } |
| |
| // Get current device wide preference state from `CrasAudioHandler`. |
| return audio_handler->GetSpatialAudioState() |
| ? mojom::AudioEffectState::kEnabled |
| : mojom::AudioEffectState::kNotEnabled; |
| } |
| |
| } // namespace |
| |
| mojom::AudioDeviceType ComputeDeviceType(const AudioDeviceType& device_type) { |
| switch (device_type) { |
| case AudioDeviceType::kHeadphone: |
| return mojom::AudioDeviceType::kHeadphone; |
| case AudioDeviceType::kMic: |
| return mojom::AudioDeviceType::kMic; |
| case AudioDeviceType::kUsb: |
| return mojom::AudioDeviceType::kUsb; |
| case AudioDeviceType::kBluetooth: |
| return mojom::AudioDeviceType::kBluetooth; |
| case AudioDeviceType::kBluetoothNbMic: |
| return mojom::AudioDeviceType::kBluetoothNbMic; |
| case AudioDeviceType::kHdmi: |
| return mojom::AudioDeviceType::kHdmi; |
| case AudioDeviceType::kInternalSpeaker: |
| return mojom::AudioDeviceType::kInternalSpeaker; |
| case AudioDeviceType::kInternalMic: |
| return mojom::AudioDeviceType::kInternalMic; |
| case AudioDeviceType::kFrontMic: |
| return mojom::AudioDeviceType::kFrontMic; |
| case AudioDeviceType::kRearMic: |
| return mojom::AudioDeviceType::kRearMic; |
| case AudioDeviceType::kKeyboardMic: |
| return mojom::AudioDeviceType::kKeyboardMic; |
| case AudioDeviceType::kHotword: |
| return mojom::AudioDeviceType::kHotword; |
| case AudioDeviceType::kPostDspLoopback: |
| return mojom::AudioDeviceType::kPostDspLoopback; |
| case AudioDeviceType::kPostMixLoopback: |
| return mojom::AudioDeviceType::kPostMixLoopback; |
| case AudioDeviceType::kLineout: |
| return mojom::AudioDeviceType::kLineout; |
| case AudioDeviceType::kAlsaLoopback: |
| return mojom::AudioDeviceType::kAlsaLoopback; |
| case AudioDeviceType::kOther: |
| return mojom::AudioDeviceType::kOther; |
| }; |
| } |
| |
| mojom::AudioDevicePtr GenerateMojoAudioDevice(const AudioDevice& device) { |
| mojom::AudioDevicePtr mojo_device = mojom::AudioDevice::New(); |
| mojo_device->id = device.id; |
| mojo_device->display_name = device.display_name; |
| mojo_device->is_active = device.active; |
| mojo_device->device_type = ComputeDeviceType(device.type); |
| mojo_device->noise_cancellation_state = GetNoiseCancellationState(device); |
| mojo_device->style_transfer_state = GetStyleTransferState(device); |
| mojo_device->force_respect_ui_gains_state = |
| GetForceRespectUiGainsState(device); |
| mojo_device->hfp_mic_sr_state = GetHfpMicSrState(device); |
| mojo_device->spatial_audio_state = GetSpatialAudioState(device); |
| return mojo_device; |
| } |
| |
| CrosAudioConfigImpl::CrosAudioConfigImpl() |
| : output_volume_metric_delay_timer_( |
| FROM_HERE, |
| kMetricsDelayTimerInterval, |
| this, |
| &CrosAudioConfigImpl::RecordOutputVolume), |
| input_gain_metric_delay_timer_(FROM_HERE, |
| kMetricsDelayTimerInterval, |
| this, |
| &CrosAudioConfigImpl::RecordInputGain) { |
| CrasAudioHandler::Get()->AddAudioObserver(this); |
| } |
| |
| CrosAudioConfigImpl::~CrosAudioConfigImpl() { |
| if (CrasAudioHandler::Get()) { |
| CrasAudioHandler::Get()->RemoveAudioObserver(this); |
| } |
| } |
| |
| uint8_t CrosAudioConfigImpl::GetOutputVolumePercent() const { |
| return CrasAudioHandler::Get()->GetOutputVolumePercent(); |
| } |
| |
| uint8_t CrosAudioConfigImpl::GetInputGainPercent() const { |
| return CrasAudioHandler::Get()->GetInputGainPercent(); |
| } |
| |
| mojom::MuteState CrosAudioConfigImpl::GetOutputMuteState() const { |
| // TODO(crbug.com/1092970): Add kMutedExternally. |
| if (CrasAudioHandler::Get()->IsOutputMutedByPolicy()) { |
| return mojom::MuteState::kMutedByPolicy; |
| } |
| |
| if (CrasAudioHandler::Get()->IsOutputMuted()) { |
| return mojom::MuteState::kMutedByUser; |
| } |
| |
| return mojom::MuteState::kNotMuted; |
| } |
| |
| void CrosAudioConfigImpl::GetAudioDevices( |
| std::vector<mojom::AudioDevicePtr>* output_devices_out, |
| std::vector<mojom::AudioDevicePtr>* input_devices_out) const { |
| DCHECK(output_devices_out); |
| DCHECK(input_devices_out); |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| AudioDeviceList audio_devices_list; |
| audio_handler->GetAudioDevices(&audio_devices_list); |
| |
| // For device that has dual internal mics, a new AudioDevice will be created |
| // to show only one slider for both the internal mics, and the new AudioDevice |
| // has a new id that doesn't match either the first or active internal mic. |
| bool has_dual_internal_mic = audio_handler->HasDualInternalMic(); |
| AudioDevice internal_mic = CreateStubInternalMic(); |
| |
| for (const auto& device : audio_devices_list) { |
| if (!device.is_for_simple_usage()) { |
| continue; |
| } |
| |
| // If dual mics is enabled and device is front or rear mic then use device |
| // to set common properties on stub internal_mic and skip |
| // adding to list of input devices. |
| if (has_dual_internal_mic && audio_handler->IsFrontOrRearMic(device)) { |
| UpdateInternalMicBasedOnAudioDevice(internal_mic, device); |
| continue; |
| } |
| |
| if (device.is_input) { |
| input_devices_out->push_back(GenerateMojoAudioDevice(device)); |
| } else { |
| output_devices_out->push_back(GenerateMojoAudioDevice(device)); |
| } |
| } |
| |
| // Add stub internal mic in place of front and rear mic devices. |
| if (has_dual_internal_mic) { |
| DCHECK(internal_mic.id != kDefaultInternalMicId); |
| input_devices_out->push_back(GenerateMojoAudioDevice(internal_mic)); |
| } |
| } |
| |
| mojom::MuteState CrosAudioConfigImpl::GetInputMuteState() const { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| if (audio_handler->input_muted_by_microphone_mute_switch() && |
| audio_handler->IsInputMuted()) { |
| return mojom::MuteState::kMutedExternally; |
| } |
| |
| if (audio_handler->IsInputMuted()) { |
| return mojom::MuteState::kMutedByUser; |
| } |
| |
| return mojom::MuteState::kNotMuted; |
| } |
| |
| void CrosAudioConfigImpl::SetOutputMuted(bool muted) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| if (audio_handler->IsOutputMutedByPolicy()) { |
| return; |
| } |
| |
| audio_handler->SetOutputMute( |
| muted, CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| RecordMuteStateChanged(kOutputMuteChangeHistogramName, muted); |
| } |
| |
| void CrosAudioConfigImpl::SetOutputVolumePercent(int8_t volume) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| audio_handler->SetOutputVolumePercent(volume); |
| |
| // If the volume is above certain level and it's muted, it should be unmuted. |
| if (audio_handler->IsOutputMuted() && |
| volume > audio_handler->GetOutputDefaultVolumeMuteThreshold()) { |
| audio_handler->SetOutputMute(false); |
| } |
| |
| last_set_output_volume_ = volume; |
| // Start or reset timer for recording to metrics. |
| output_volume_metric_delay_timer_.Reset(); |
| } |
| |
| void CrosAudioConfigImpl::SetInputGainPercent(uint8_t gain) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| audio_handler->SetInputGainPercent(gain); |
| |
| // Unmute if muted. |
| if (audio_handler->IsInputMuted()) { |
| audio_handler->SetInputMute( |
| false, CrasAudioHandler::InputMuteChangeMethod::kOther); |
| } |
| |
| last_set_input_gain_ = gain; |
| // Start or reset timer for recording to metrics. |
| input_gain_metric_delay_timer_.Reset(); |
| } |
| |
| void CrosAudioConfigImpl::SetActiveDevice(uint64_t device_id) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| const AudioDevice* next_active_device = |
| audio_handler->GetDeviceFromId(device_id); |
| |
| if (!next_active_device) { |
| LOG(ERROR) << "SetActiveDevice: Cannot find device id=" << "0x" << std::hex |
| << device_id; |
| return; |
| } |
| |
| // When device has dual mics the `GetAudioDevices` represents front and rear |
| // mic as a single device. To set active internal mic correctly |
| // `SwitchToFrontOrRearMic` needs to be called. |
| if (audio_handler->HasDualInternalMic() && |
| audio_handler->IsFrontOrRearMic(*next_active_device)) { |
| audio_handler->SwitchToFrontOrRearMic(); |
| } else { |
| audio_handler->SwitchToDevice(*next_active_device, /*notify=*/true, |
| DeviceActivateType::kActivateByUser); |
| } |
| |
| // Record if it was an output or input device that changed. |
| base::UmaHistogramEnumeration(kAudioDeviceChangeHistogramName, |
| next_active_device->is_input |
| ? AudioDeviceChange::kInputDevice |
| : AudioDeviceChange::kOutputDevice); |
| // Record the type of audio device changed. |
| base::UmaHistogramEnumeration(next_active_device->is_input |
| ? kInputDeviceTypeHistogramName |
| : kOutputDeviceTypeHistogramName, |
| next_active_device->type); |
| } |
| |
| void CrosAudioConfigImpl::SetInputMuted(bool muted) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| if (audio_handler->input_muted_by_microphone_mute_switch()) { |
| return; |
| } |
| |
| audio_handler->SetMuteForDevice( |
| audio_handler->GetPrimaryActiveInputNode(), muted, |
| CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| RecordMuteStateChanged(kInputMuteChangeHistogramName, muted); |
| } |
| |
| mojom::VoiceIsolationUIAppearancePtr |
| CrosAudioConfigImpl::GetVoiceIsolationUIAppearance() const { |
| auto mojom_appearance = mojom::VoiceIsolationUIAppearance::New(); |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| if (audio_handler->input_muted_by_microphone_mute_switch()) { |
| return mojom_appearance; |
| } |
| |
| VoiceIsolationUIAppearance appearance = |
| audio_handler->GetVoiceIsolationUIAppearance(); |
| mojom_appearance->toggle_type = |
| static_cast<mojom::AudioEffectType>(appearance.toggle_type); |
| mojom_appearance->effect_mode_options = appearance.effect_mode_options; |
| mojom_appearance->show_effect_fallback_message = |
| appearance.show_effect_fallback_message; |
| return mojom_appearance; |
| } |
| |
| void CrosAudioConfigImpl::RecordVoiceIsolationEnabledChange() { |
| CrasAudioHandler::Get()->RecordVoiceIsolationEnabledChangeSource( |
| CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| } |
| |
| void CrosAudioConfigImpl::RecordVoiceIsolationPreferredEffectChange( |
| audio_config::mojom::AudioEffectType preferred_effect) { |
| CrasAudioHandler::Get()->RecordVoiceIsolationPreferredEffectChange( |
| preferred_effect); |
| } |
| |
| void CrosAudioConfigImpl::SetNoiseCancellationEnabled(bool enabled) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| |
| if (!audio_handler->IsNoiseCancellationSupportedForDevice( |
| audio_handler->GetPrimaryActiveInputNode())) { |
| LOG(ERROR) << "SetNoiseCancellationEnabled: Noise cancellation is not " |
| "supported by active input node."; |
| return; |
| } |
| |
| audio_handler->SetNoiseCancellationState( |
| enabled, CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| base::UmaHistogramBoolean(kNoiseCancellationEnabledHistogramName, enabled); |
| } |
| |
| void CrosAudioConfigImpl::SetStyleTransferEnabled(bool enabled) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| |
| if (!audio_handler->IsStyleTransferSupportedForDevice( |
| audio_handler->GetPrimaryActiveInputNode())) { |
| LOG(ERROR) << "SetStyleTransferEnabled: Style transfer is not " |
| "supported by active input node."; |
| return; |
| } |
| |
| audio_handler->SetStyleTransferState(enabled); |
| } |
| |
| void CrosAudioConfigImpl::SetForceRespectUiGainsEnabled(bool enabled) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| CHECK(audio_handler); |
| |
| audio_handler->SetForceRespectUiGainsState(enabled); |
| } |
| |
| void CrosAudioConfigImpl::SetHfpMicSrEnabled(bool enabled) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| |
| if (!audio_handler->IsHfpMicSrSupportedForDevice( |
| audio_handler->GetPrimaryActiveInputNode())) { |
| LOG(ERROR) << "SetHfpMicSrEnabled: hfp mic sr is not " |
| "supported by active input node."; |
| return; |
| } |
| |
| audio_handler->SetHfpMicSrState( |
| enabled, CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| } |
| |
| void CrosAudioConfigImpl::SetSpatialAudioEnabled(bool enabled) { |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| CHECK(audio_handler); |
| |
| audio_handler->SetSpatialAudioState(enabled); |
| } |
| |
| void CrosAudioConfigImpl::RecordOutputVolume() { |
| base::UmaHistogramExactLinear(kOutputVolumeChangeHistogramName, |
| last_set_output_volume_, |
| /*exclusive_max=*/101); |
| base::UmaHistogramEnumeration( |
| CrasAudioHandler::kOutputVolumeChangedSourceHistogramName, |
| CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| } |
| |
| void CrosAudioConfigImpl::RecordInputGain() { |
| base::UmaHistogramExactLinear(kInputGainChangeHistogramName, |
| last_set_input_gain_, |
| /*exclusive_max=*/101); |
| base::UmaHistogramEnumeration( |
| CrasAudioHandler::kInputGainChangedSourceHistogramName, |
| CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| |
| CrasAudioHandler* audio_handler = CrasAudioHandler::Get(); |
| CHECK(audio_handler); |
| if (!audio_handler->GetForceRespectUiGainsState()) { |
| base::UmaHistogramEnumeration( |
| CrasAudioHandler::kInputGainChangedHistogramName, |
| CrasAudioHandler::AudioSettingsChangeSource::kOsSettings); |
| } |
| } |
| |
| void CrosAudioConfigImpl::OnOutputNodeVolumeChanged(uint64_t node_id, |
| int volume) { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnInputNodeGainChanged(uint64_t node_id, int gain) { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnOutputMuteChanged(bool mute_on) { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnAudioNodesChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnActiveOutputNodeChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnActiveInputNodeChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnInputMuteChanged( |
| bool mute_on, |
| CrasAudioHandler::InputMuteChangeMethod method) { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnInputMutedByMicrophoneMuteSwitchChanged( |
| bool muted) { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnVoiceIsolationUIAppearanceChanged( |
| VoiceIsolationUIAppearance appearance) { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnNoiseCancellationStateChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnStyleTransferStateChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnForceRespectUiGainsStateChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnHfpMicSrStateChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| void CrosAudioConfigImpl::OnSpatialAudioStateChanged() { |
| NotifyObserversAudioSystemPropertiesChanged(); |
| } |
| |
| } // namespace ash::audio_config |