| // Copyright (c) 2013 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 "media/audio/mac/audio_device_listener_mac.h" |
| |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "base/mac/mac_logging.h" |
| #include "base/single_thread_task_runner.h" |
| #include "media/audio/audio_manager.h" |
| #include "media/audio/mac/core_audio_util_mac.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace media { |
| |
| const AudioObjectPropertyAddress |
| AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress = { |
| kAudioHardwarePropertyDefaultOutputDevice, |
| kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; |
| |
| const AudioObjectPropertyAddress |
| AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress = { |
| kAudioHardwarePropertyDefaultInputDevice, |
| kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster}; |
| |
| const AudioObjectPropertyAddress |
| AudioDeviceListenerMac::kDevicesPropertyAddress = { |
| kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, |
| kAudioObjectPropertyElementMaster}; |
| |
| const AudioObjectPropertyAddress kPropertyOutputSourceChanged = { |
| kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput, |
| kAudioObjectPropertyElementMaster}; |
| |
| const AudioObjectPropertyAddress kPropertyInputSourceChanged = { |
| kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput, |
| kAudioObjectPropertyElementMaster}; |
| |
| class AudioDeviceListenerMac::PropertyListener { |
| public: |
| PropertyListener(AudioObjectID monitored_object, |
| const AudioObjectPropertyAddress* property, |
| base::RepeatingClosure callback) |
| : monitored_object_(monitored_object), |
| address_(property), |
| callback_(std::move(callback)) {} |
| |
| AudioObjectID monitored_object() const { return monitored_object_; } |
| const base::RepeatingClosure& callback() const { return callback_; } |
| const AudioObjectPropertyAddress* property() const { return address_; } |
| |
| private: |
| AudioObjectID monitored_object_; |
| const AudioObjectPropertyAddress* address_; |
| base::RepeatingClosure callback_; |
| }; |
| |
| // Callback from the system when an event occurs; this must be called on the |
| // MessageLoop that created the AudioManager. |
| // static |
| OSStatus AudioDeviceListenerMac::OnEvent( |
| AudioObjectID object, |
| UInt32 num_addresses, |
| const AudioObjectPropertyAddress addresses[], |
| void* context) { |
| PropertyListener* listener = static_cast<PropertyListener*>(context); |
| if (object != listener->monitored_object()) |
| return noErr; |
| |
| for (UInt32 i = 0; i < num_addresses; ++i) { |
| if (addresses[i].mSelector == listener->property()->mSelector && |
| addresses[i].mScope == listener->property()->mScope && |
| addresses[i].mElement == listener->property()->mElement && context) { |
| listener->callback().Run(); |
| break; |
| } |
| } |
| |
| return noErr; |
| } |
| |
| AudioDeviceListenerMac::AudioDeviceListenerMac( |
| const base::RepeatingClosure listener_cb, |
| bool monitor_default_input, |
| bool monitor_addition_removal, |
| bool monitor_sources) |
| : weak_factory_(this) { |
| listener_cb_ = std::move(listener_cb); |
| |
| // Changes to the default output device are always monitored. |
| default_output_listener_ = std::make_unique<PropertyListener>( |
| kAudioObjectSystemObject, &kDefaultOutputDeviceChangePropertyAddress, |
| listener_cb_); |
| if (!AddPropertyListener(default_output_listener_.get())) |
| default_output_listener_.reset(); |
| |
| if (monitor_default_input) { |
| default_input_listener_ = std::make_unique<PropertyListener>( |
| kAudioObjectSystemObject, &kDefaultInputDeviceChangePropertyAddress, |
| listener_cb_); |
| if (!AddPropertyListener(default_input_listener_.get())) |
| default_input_listener_.reset(); |
| } |
| if (monitor_addition_removal) { |
| addition_removal_listener_ = std::make_unique<PropertyListener>( |
| kAudioObjectSystemObject, &kDevicesPropertyAddress, |
| monitor_sources ? media::BindToCurrentLoop(base::BindRepeating( |
| &AudioDeviceListenerMac::OnDevicesAddedOrRemoved, |
| weak_factory_.GetWeakPtr())) |
| : listener_cb_); |
| if (!AddPropertyListener(addition_removal_listener_.get())) |
| addition_removal_listener_.reset(); |
| |
| // Sources can be monitored only if addition/removal is monitored. |
| if (monitor_sources) |
| UpdateSourceListeners(); |
| } |
| } |
| |
| AudioDeviceListenerMac::~AudioDeviceListenerMac() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // Since we're running on the same CFRunLoop, there can be no outstanding |
| // callbacks in flight. |
| if (default_output_listener_) |
| RemovePropertyListener(default_output_listener_.get()); |
| if (default_input_listener_) |
| RemovePropertyListener(default_input_listener_.get()); |
| if (addition_removal_listener_) |
| RemovePropertyListener(addition_removal_listener_.get()); |
| for (const auto& entry : source_listeners_) |
| RemovePropertyListener(entry.second.get()); |
| } |
| |
| bool AudioDeviceListenerMac::AddPropertyListener( |
| AudioDeviceListenerMac::PropertyListener* property_listener) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| OSStatus result = AudioObjectAddPropertyListener( |
| property_listener->monitored_object(), property_listener->property(), |
| &AudioDeviceListenerMac::OnEvent, property_listener); |
| bool success = result == noErr; |
| if (!success) |
| OSSTATUS_DLOG(ERROR, result) << "AudioObjectAddPropertyListener() failed!"; |
| |
| return success; |
| } |
| |
| void AudioDeviceListenerMac::RemovePropertyListener( |
| AudioDeviceListenerMac::PropertyListener* property_listener) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| OSStatus result = AudioObjectRemovePropertyListener( |
| property_listener->monitored_object(), property_listener->property(), |
| &AudioDeviceListenerMac::OnEvent, property_listener); |
| OSSTATUS_DLOG_IF(ERROR, result != noErr, result) |
| << "AudioObjectRemovePropertyListener() failed!"; |
| } |
| |
| void AudioDeviceListenerMac::OnDevicesAddedOrRemoved() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| UpdateSourceListeners(); |
| listener_cb_.Run(); |
| } |
| |
| void AudioDeviceListenerMac::UpdateSourceListeners() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| std::vector<AudioObjectID> device_ids = |
| core_audio_mac::GetAllAudioDeviceIDs(); |
| for (bool is_input : {true, false}) { |
| for (auto device_id : device_ids) { |
| const AudioObjectPropertyAddress* property_address = |
| is_input ? &kPropertyInputSourceChanged |
| : &kPropertyOutputSourceChanged; |
| SourceListenerKey key = {device_id, is_input}; |
| auto it_key = source_listeners_.find(key); |
| bool is_monitored = it_key != source_listeners_.end(); |
| if (core_audio_mac::GetDeviceSource(device_id, is_input)) { |
| if (!is_monitored) { |
| // Start monitoring if the device has source and is not currently |
| // being monitored. |
| std::unique_ptr<PropertyListener> source_listener = |
| std::make_unique<PropertyListener>(device_id, property_address, |
| listener_cb_); |
| if (AddPropertyListener(source_listener.get())) { |
| source_listeners_[key] = std::move(source_listener); |
| } else { |
| source_listener.reset(); |
| } |
| } |
| } else if (is_monitored) { |
| // Stop monitoring if the device has no source but is currently being |
| // monitored. |
| RemovePropertyListener(it_key->second.get()); |
| source_listeners_.erase(it_key); |
| } |
| } |
| } |
| } |
| |
| } // namespace media |