1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "media/audio/mac/audio_device_listener_mac.h"
6
7 #include <vector>
8
9 #include "base/bind.h"
10 #include "base/files/file_path.h"
11 #include "base/logging.h"
12 #include "base/mac/mac_logging.h"
13 #include "base/optional.h"
14 #include "base/single_thread_task_runner.h"
15 #include "media/audio/audio_manager.h"
16 #include "media/audio/mac/core_audio_util_mac.h"
17 #include "media/base/bind_to_current_loop.h"
18
19 namespace media {
20
21 const AudioObjectPropertyAddress
22 AudioDeviceListenerMac::kDefaultOutputDeviceChangePropertyAddress = {
23 kAudioHardwarePropertyDefaultOutputDevice,
24 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
25
26 const AudioObjectPropertyAddress
27 AudioDeviceListenerMac::kDefaultInputDeviceChangePropertyAddress = {
28 kAudioHardwarePropertyDefaultInputDevice,
29 kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster};
30
31 const AudioObjectPropertyAddress
32 AudioDeviceListenerMac::kDevicesPropertyAddress = {
33 kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal,
34 kAudioObjectPropertyElementMaster};
35
36 const AudioObjectPropertyAddress kPropertyOutputSourceChanged = {
37 kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeOutput,
38 kAudioObjectPropertyElementMaster};
39
40 const AudioObjectPropertyAddress kPropertyInputSourceChanged = {
41 kAudioDevicePropertyDataSource, kAudioDevicePropertyScopeInput,
42 kAudioObjectPropertyElementMaster};
43
44 class AudioDeviceListenerMac::PropertyListener {
45 public:
PropertyListener(AudioObjectID monitored_object,const AudioObjectPropertyAddress * property,base::RepeatingClosure callback)46 PropertyListener(AudioObjectID monitored_object,
47 const AudioObjectPropertyAddress* property,
48 base::RepeatingClosure callback)
49 : monitored_object_(monitored_object),
50 address_(property),
51 callback_(std::move(callback)) {}
52
monitored_object() const53 AudioObjectID monitored_object() const { return monitored_object_; }
callback() const54 const base::RepeatingClosure& callback() const { return callback_; }
property() const55 const AudioObjectPropertyAddress* property() const { return address_; }
56
57 private:
58 AudioObjectID monitored_object_;
59 const AudioObjectPropertyAddress* address_;
60 base::RepeatingClosure callback_;
61 };
62
63 // Callback from the system when an event occurs; this must be called on the
64 // MessageLoop that created the AudioManager.
65 // static
OnEvent(AudioObjectID object,UInt32 num_addresses,const AudioObjectPropertyAddress addresses[],void * context)66 OSStatus AudioDeviceListenerMac::OnEvent(
67 AudioObjectID object,
68 UInt32 num_addresses,
69 const AudioObjectPropertyAddress addresses[],
70 void* context) {
71 PropertyListener* listener = static_cast<PropertyListener*>(context);
72 if (object != listener->monitored_object())
73 return noErr;
74
75 for (UInt32 i = 0; i < num_addresses; ++i) {
76 if (addresses[i].mSelector == listener->property()->mSelector &&
77 addresses[i].mScope == listener->property()->mScope &&
78 addresses[i].mElement == listener->property()->mElement && context) {
79 listener->callback().Run();
80 break;
81 }
82 }
83
84 return noErr;
85 }
86
AudioDeviceListenerMac(const base::RepeatingClosure listener_cb,bool monitor_default_input,bool monitor_addition_removal,bool monitor_sources)87 AudioDeviceListenerMac::AudioDeviceListenerMac(
88 const base::RepeatingClosure listener_cb,
89 bool monitor_default_input,
90 bool monitor_addition_removal,
91 bool monitor_sources)
92 : weak_factory_(this) {
93 listener_cb_ = std::move(listener_cb);
94
95 // Changes to the default output device are always monitored.
96 default_output_listener_ = std::make_unique<PropertyListener>(
97 kAudioObjectSystemObject, &kDefaultOutputDeviceChangePropertyAddress,
98 listener_cb_);
99 if (!AddPropertyListener(default_output_listener_.get()))
100 default_output_listener_.reset();
101
102 if (monitor_default_input) {
103 default_input_listener_ = std::make_unique<PropertyListener>(
104 kAudioObjectSystemObject, &kDefaultInputDeviceChangePropertyAddress,
105 listener_cb_);
106 if (!AddPropertyListener(default_input_listener_.get()))
107 default_input_listener_.reset();
108 }
109 if (monitor_addition_removal) {
110 addition_removal_listener_ = std::make_unique<PropertyListener>(
111 kAudioObjectSystemObject, &kDevicesPropertyAddress,
112 monitor_sources ? media::BindToCurrentLoop(base::BindRepeating(
113 &AudioDeviceListenerMac::OnDevicesAddedOrRemoved,
114 weak_factory_.GetWeakPtr()))
115 : listener_cb_);
116 if (!AddPropertyListener(addition_removal_listener_.get()))
117 addition_removal_listener_.reset();
118
119 // Sources can be monitored only if addition/removal is monitored.
120 if (monitor_sources)
121 UpdateSourceListeners();
122 }
123 }
124
~AudioDeviceListenerMac()125 AudioDeviceListenerMac::~AudioDeviceListenerMac() {
126 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
127
128 // Since we're running on the same CFRunLoop, there can be no outstanding
129 // callbacks in flight.
130 if (default_output_listener_)
131 RemovePropertyListener(default_output_listener_.get());
132 if (default_input_listener_)
133 RemovePropertyListener(default_input_listener_.get());
134 if (addition_removal_listener_)
135 RemovePropertyListener(addition_removal_listener_.get());
136 for (const auto& entry : source_listeners_)
137 RemovePropertyListener(entry.second.get());
138 }
139
AddPropertyListener(AudioDeviceListenerMac::PropertyListener * property_listener)140 bool AudioDeviceListenerMac::AddPropertyListener(
141 AudioDeviceListenerMac::PropertyListener* property_listener) {
142 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
143 OSStatus result = AudioObjectAddPropertyListener(
144 property_listener->monitored_object(), property_listener->property(),
145 &AudioDeviceListenerMac::OnEvent, property_listener);
146 bool success = result == noErr;
147 if (!success)
148 OSSTATUS_DLOG(ERROR, result) << "AudioObjectAddPropertyListener() failed!";
149
150 return success;
151 }
152
RemovePropertyListener(AudioDeviceListenerMac::PropertyListener * property_listener)153 void AudioDeviceListenerMac::RemovePropertyListener(
154 AudioDeviceListenerMac::PropertyListener* property_listener) {
155 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
156 OSStatus result = AudioObjectRemovePropertyListener(
157 property_listener->monitored_object(), property_listener->property(),
158 &AudioDeviceListenerMac::OnEvent, property_listener);
159 OSSTATUS_DLOG_IF(ERROR, result != noErr, result)
160 << "AudioObjectRemovePropertyListener() failed!";
161 }
162
OnDevicesAddedOrRemoved()163 void AudioDeviceListenerMac::OnDevicesAddedOrRemoved() {
164 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
165 UpdateSourceListeners();
166 listener_cb_.Run();
167 }
168
UpdateSourceListeners()169 void AudioDeviceListenerMac::UpdateSourceListeners() {
170 DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
171 std::vector<AudioObjectID> device_ids =
172 core_audio_mac::GetAllAudioDeviceIDs();
173 for (bool is_input : {true, false}) {
174 for (auto device_id : device_ids) {
175 const AudioObjectPropertyAddress* property_address =
176 is_input ? &kPropertyInputSourceChanged
177 : &kPropertyOutputSourceChanged;
178 SourceListenerKey key = {device_id, is_input};
179 auto it_key = source_listeners_.find(key);
180 bool is_monitored = it_key != source_listeners_.end();
181 if (core_audio_mac::GetDeviceSource(device_id, is_input)) {
182 if (!is_monitored) {
183 // Start monitoring if the device has source and is not currently
184 // being monitored.
185 std::unique_ptr<PropertyListener> source_listener =
186 std::make_unique<PropertyListener>(device_id, property_address,
187 listener_cb_);
188 if (AddPropertyListener(source_listener.get())) {
189 source_listeners_[key] = std::move(source_listener);
190 } else {
191 source_listener.reset();
192 }
193 }
194 } else if (is_monitored) {
195 // Stop monitoring if the device has no source but is currently being
196 // monitored.
197 RemovePropertyListener(it_key->second.get());
198 source_listeners_.erase(it_key);
199 }
200 }
201 }
202 }
203
204 } // namespace media
205