1 /***
2 This file is part of PulseAudio.
3
4 Copyright 2009,2010 Daniel Mack <daniel@caiaq.de>
5
6 PulseAudio is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published
8 by the Free Software Foundation; either version 2.1 of the License,
9 or (at your option) any later version.
10
11 PulseAudio is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License
17 along with PulseAudio; if not, see <http://www.gnu.org/licenses/>.
18 ***/
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <pulse/xmalloc.h>
25
26 #include <pulsecore/module.h>
27 #include <pulsecore/core-util.h>
28 #include <pulsecore/modargs.h>
29 #include <pulsecore/log.h>
30 #include <pulsecore/llist.h>
31
32 #include <CoreAudio/CoreAudio.h>
33
34 #define DEVICE_MODULE_NAME "module-coreaudio-device"
35
36 PA_MODULE_AUTHOR("Daniel Mack");
37 PA_MODULE_DESCRIPTION("CoreAudio device detection");
38 PA_MODULE_VERSION(PACKAGE_VERSION);
39 PA_MODULE_LOAD_ONCE(true);
40 PA_MODULE_USAGE("ioproc_frames=<passed on to module-coreaudio-device> "
41 "record=<enable source?> "
42 "playback=<enable sink?> ");
43
44 static const char* const valid_modargs[] = {
45 "ioproc_frames",
46 "record",
47 "playback",
48 NULL
49 };
50
51 typedef struct ca_device ca_device;
52
53 struct ca_device {
54 AudioObjectID id;
55 unsigned int module_index;
56 PA_LLIST_FIELDS(ca_device);
57 };
58
59 struct userdata {
60 int detect_fds[2];
61 pa_io_event *detect_io;
62 unsigned int ioproc_frames;
63 bool record;
64 bool playback;
65 PA_LLIST_HEAD(ca_device, devices);
66 };
67
ca_device_added(struct pa_module * m,AudioObjectID id)68 static int ca_device_added(struct pa_module *m, AudioObjectID id) {
69 AudioObjectPropertyAddress property_address;
70 OSStatus err;
71 pa_module *mod;
72 struct userdata *u;
73 struct ca_device *dev;
74 char *args, tmp[64];
75 UInt32 size;
76
77 pa_assert(m);
78 pa_assert_se(u = m->userdata);
79
80 /* To prevent generating a black hole that will suck us in,
81 don't create sources/sinks for PulseAudio virtual devices */
82
83 property_address.mSelector = kAudioDevicePropertyDeviceManufacturer;
84 property_address.mScope = kAudioObjectPropertyScopeGlobal;
85 property_address.mElement = kAudioObjectPropertyElementMaster;
86
87 size = sizeof(tmp);
88 err = AudioObjectGetPropertyData(id, &property_address, 0, NULL, &size, tmp);
89
90 if (!err && pa_streq(tmp, "pulseaudio.org"))
91 return 0;
92
93 if (u->ioproc_frames)
94 args = pa_sprintf_malloc("object_id=%d ioproc_frames=%d record=%d playback=%d", (int) id, u->ioproc_frames, (int) u->record, (int) u->playback);
95 else
96 args = pa_sprintf_malloc("object_id=%d record=%d playback=%d", (int) id, (int) u->record, (int) u->playback);
97
98 pa_log_debug("Loading %s with arguments '%s'", DEVICE_MODULE_NAME, args);
99 pa_module_load(&mod, m->core, DEVICE_MODULE_NAME, args);
100 pa_xfree(args);
101
102 if (!mod) {
103 pa_log_info("Failed to load module %s with arguments '%s'", DEVICE_MODULE_NAME, args);
104 return -1;
105 }
106
107 dev = pa_xnew0(ca_device, 1);
108 dev->module_index = mod->index;
109 dev->id = id;
110
111 PA_LLIST_INIT(ca_device, dev);
112 PA_LLIST_PREPEND(ca_device, u->devices, dev);
113
114 return 0;
115 }
116
ca_update_device_list(struct pa_module * m)117 static int ca_update_device_list(struct pa_module *m) {
118 AudioObjectPropertyAddress property_address;
119 OSStatus err;
120 UInt32 i, size, num_devices;
121 AudioDeviceID *device_id;
122 struct ca_device *dev;
123 struct userdata *u;
124
125 pa_assert(m);
126 pa_assert_se(u = m->userdata);
127
128 property_address.mSelector = kAudioHardwarePropertyDevices;
129 property_address.mScope = kAudioObjectPropertyScopeGlobal;
130 property_address.mElement = kAudioObjectPropertyElementMaster;
131
132 /* get the number of currently available audio devices */
133 err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &property_address, 0, NULL, &size);
134 if (err) {
135 pa_log("Unable to get data size for kAudioHardwarePropertyDevices.");
136 return -1;
137 }
138
139 num_devices = size / sizeof(AudioDeviceID);
140 device_id = pa_xnew(AudioDeviceID, num_devices);
141
142 err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &property_address, 0, NULL, &size, device_id);
143 if (err) {
144 pa_log("Unable to get kAudioHardwarePropertyDevices.");
145 pa_xfree(device_id);
146 return -1;
147 }
148
149 /* scan for devices which are reported but not in our cached list */
150 for (i = 0; i < num_devices; i++) {
151 bool found = false;
152
153 PA_LLIST_FOREACH(dev, u->devices)
154 if (dev->id == device_id[i]) {
155 found = true;
156 break;
157 }
158
159 if (!found)
160 ca_device_added(m, device_id[i]);
161 }
162
163 /* scan for devices which are in our cached list but are not reported */
164 scan_removed:
165
166 PA_LLIST_FOREACH(dev, u->devices) {
167 bool found = false;
168
169 for (i = 0; i < num_devices; i++)
170 if (dev->id == device_id[i]) {
171 found = true;
172 break;
173 }
174
175 if (!found) {
176 pa_log_debug("object id %d has been removed (module index %d) %p", (unsigned int) dev->id, dev->module_index, dev);
177 pa_module_unload_request_by_index(m->core, dev->module_index, true);
178 PA_LLIST_REMOVE(ca_device, u->devices, dev);
179 pa_xfree(dev);
180 /* the current list item pointer is not valid anymore, so start over. */
181 goto scan_removed;
182 }
183 }
184
185 pa_xfree(device_id);
186 return 0;
187 }
188
property_listener_proc(AudioObjectID objectID,UInt32 numberAddresses,const AudioObjectPropertyAddress inAddresses[],void * clientData)189 static OSStatus property_listener_proc(AudioObjectID objectID, UInt32 numberAddresses,
190 const AudioObjectPropertyAddress inAddresses[],
191 void *clientData) {
192 struct userdata *u = clientData;
193 char dummy = 1;
194
195 pa_assert(u);
196
197 /* dispatch module load/unload operations in main thread */
198 write(u->detect_fds[1], &dummy, 1);
199
200 return 0;
201 }
202
detect_handle(pa_mainloop_api * a,pa_io_event * e,int fd,pa_io_event_flags_t events,void * userdata)203 static void detect_handle(pa_mainloop_api *a, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) {
204 pa_module *m = userdata;
205 char dummy;
206
207 pa_assert(m);
208
209 read(fd, &dummy, 1);
210 ca_update_device_list(m);
211 }
212
pa__init(pa_module * m)213 int pa__init(pa_module *m) {
214 struct userdata *u = pa_xnew0(struct userdata, 1);
215 AudioObjectPropertyAddress property_address;
216 pa_modargs *ma;
217
218 pa_assert(m);
219 pa_assert(m->core);
220
221 m->userdata = u;
222
223 if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
224 pa_log("Failed to parse module arguments.");
225 goto fail;
226 }
227
228 /*
229 * Set default value to true if not given as a modarg.
230 * In such a case, pa_modargs_get_value_boolean() will not touch the
231 * buffer.
232 */
233 u->playback = u->record = true;
234
235 if (pa_modargs_get_value_boolean(ma, "record", &u->record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &u->playback) < 0) {
236 pa_log("record= and playback= expect boolean argument.");
237 goto fail;
238 }
239
240 if (!u->playback && !u->record) {
241 pa_log("neither playback nor record enabled for device.");
242 goto fail;
243 }
244
245 pa_modargs_get_value_u32(ma, "ioproc_frames", &u->ioproc_frames);
246
247 property_address.mSelector = kAudioHardwarePropertyDevices;
248 property_address.mScope = kAudioObjectPropertyScopeGlobal;
249 property_address.mElement = kAudioObjectPropertyElementMaster;
250
251 if (AudioObjectAddPropertyListener(kAudioObjectSystemObject, &property_address, property_listener_proc, u)) {
252 pa_log("AudioObjectAddPropertyListener() failed.");
253 goto fail;
254 }
255
256 if (ca_update_device_list(m))
257 goto fail;
258
259 pa_assert_se(pipe(u->detect_fds) == 0);
260 pa_assert_se(u->detect_io = m->core->mainloop->io_new(m->core->mainloop, u->detect_fds[0], PA_IO_EVENT_INPUT, detect_handle, m));
261
262 return 0;
263
264 fail:
265 pa_xfree(u);
266 return -1;
267 }
268
pa__done(pa_module * m)269 void pa__done(pa_module *m) {
270 struct userdata *u;
271 struct ca_device *dev;
272 AudioObjectPropertyAddress property_address;
273
274 pa_assert(m);
275 pa_assert_se(u = m->userdata);
276
277 dev = u->devices;
278
279 property_address.mSelector = kAudioHardwarePropertyDevices;
280 property_address.mScope = kAudioObjectPropertyScopeGlobal;
281 property_address.mElement = kAudioObjectPropertyElementMaster;
282
283 AudioObjectRemovePropertyListener(kAudioObjectSystemObject, &property_address, property_listener_proc, u);
284
285 while (dev) {
286 struct ca_device *next = dev->next;
287
288 pa_module_unload_request_by_index(m->core, dev->module_index, true);
289 pa_xfree(dev);
290
291 dev = next;
292 }
293
294 if (u->detect_fds[0] >= 0)
295 close(u->detect_fds[0]);
296
297 if (u->detect_fds[1] >= 0)
298 close(u->detect_fds[1]);
299
300 if (u->detect_io)
301 m->core->mainloop->io_free(u->detect_io);
302
303 pa_xfree(u);
304 }
305