• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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