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 36PA_MODULE_AUTHOR("Daniel Mack"); 37PA_MODULE_DESCRIPTION("CoreAudio device detection"); 38PA_MODULE_VERSION(PACKAGE_VERSION); 39PA_MODULE_LOAD_ONCE(true); 40PA_MODULE_USAGE("ioproc_frames=<passed on to module-coreaudio-device> " 41 "record=<enable source?> " 42 "playback=<enable sink?> "); 43 44static const char* const valid_modargs[] = { 45 "ioproc_frames", 46 "record", 47 "playback", 48 NULL 49}; 50 51typedef struct ca_device ca_device; 52 53struct ca_device { 54 AudioObjectID id; 55 unsigned int module_index; 56 PA_LLIST_FIELDS(ca_device); 57}; 58 59struct 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 68static 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 117static 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 */ 164scan_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 189static 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 203static 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 213int 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 264fail: 265 pa_xfree(u); 266 return -1; 267} 268 269void 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