[pulseaudio-discuss] [PATCH 7/7] CoreAudio: add audio device module
Daniel Mack
daniel at caiaq.de
Tue Dec 8 18:47:15 PST 2009
This patch adds support for CoreAudio driven devices under Mac OS X. It
is typically instanciated by the CoreAudio device detection module and
handles all available streams on a specific device.
Sinks are created according to the reported stream configuration.
Float32 is used as default audio sample format at it is the only format
CoreAudio speaks natively.
Sources are not implemented yet. Neither is hardware volume control.
---
src/Makefile.am | 9 +-
src/modules/coreaudio/module-coreaudio-device.c | 609 +++++++++++++++++++++++
2 files changed, 617 insertions(+), 1 deletions(-)
create mode 100644 src/modules/coreaudio/module-coreaudio-device.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 70ab5b0..fa5d170 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1068,7 +1068,8 @@ endif
if HAVE_COREAUDIO
modlibexec_LTLIBRARIES += \
- module-coreaudio-detect.la
+ module-coreaudio-detect.la \
+ module-coreaudio-device.la
endif
pulselibexec_PROGRAMS =
@@ -1244,6 +1245,7 @@ SYMDEF_FILES = \
modules/alsa/module-alsa-source-symdef.h \
modules/alsa/module-alsa-card-symdef.h \
modules/coreaudio/module-coreaudio-detect-symdef.h \
+ modules/coreaudio/module-coreaudio-device-symdef.h \
modules/module-solaris-symdef.h \
modules/module-waveout-symdef.h \
modules/module-detect-symdef.h \
@@ -1483,6 +1485,11 @@ module_coreaudio_detect_la_LDFLAGS = $(MODULE_LDFLAGS) \
-Wl,-framework -Wl,AudioUnit -framework AudioUnit
module_coreaudio_detect_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la
+module_coreaudio_device_la_SOURCES = modules/coreaudio/module-coreaudio-device.c
+module_coreaudio_device_la_LDFLAGS = $(MODULE_LDFLAGS) \
+ -Wl,-framework -Wl,Cocoa -framework CoreAudio \
+ -Wl,-framework -Wl,AudioUnit -framework AudioUnit
+module_coreaudio_device_la_LIBADD = $(AM_LIBADD) libpulsecore- at PA_MAJORMINORMICRO@.la libpulsecommon- at PA_MAJORMINORMICRO@.la libpulse.la
# ALSA
libalsa_util_la_SOURCES = modules/alsa/alsa-util.c modules/alsa/alsa-util.h modules/alsa/alsa-mixer.c modules/alsa/alsa-mixer.h modules/alsa/alsa-sink.c modules/alsa/alsa-sink.h modules/alsa/alsa-source.c modules/alsa/alsa-source.h modules/reserve-wrap.c modules/reserve-wrap.h
diff --git a/src/modules/coreaudio/module-coreaudio-device.c b/src/modules/coreaudio/module-coreaudio-device.c
new file mode 100644
index 0000000..fdd429a
--- /dev/null
+++ b/src/modules/coreaudio/module-coreaudio-device.c
@@ -0,0 +1,609 @@
+/***
+ This file is part of PulseAudio.
+
+ Copyright 2009 Daniel Mack <daniel at caiaq.de>
+
+ PulseAudio is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published
+ by the Free Software Foundation; either version 2.1 of the License,
+ or (at your option) any later version.
+
+ PulseAudio is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with PulseAudio; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA.
+***/
+
+/* TODO:
+ - implement sources (inbound data)
+ - implement hardware volume controls
+ - handle audio device stream format changes (will require changes to the core)
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulse/xmalloc.h>
+#include <pulse/util.h>
+
+#include <pulsecore/core-error.h>
+#include <pulsecore/sink.h>
+#include <pulsecore/source.h>
+#include <pulsecore/module.h>
+#include <pulsecore/sample-util.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/log.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/llist.h>
+#include <pulsecore/card.h>
+#include <pulsecore/strbuf.h>
+#include <pulsecore/thread.h>
+#include <pulsecore/thread-mq.h>
+
+#include <CoreAudio/CoreAudio.h>
+#include <CoreAudio/CoreAudioTypes.h>
+#include <CoreAudio/AudioHardware.h>
+
+#include "module-coreaudio-device-symdef.h"
+
+PA_MODULE_AUTHOR("Daniel Mack");
+PA_MODULE_DESCRIPTION("CoreAudio device");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(FALSE);
+PA_MODULE_USAGE(
+ "device_id=<the CoreAudio device id> "
+ "sample_frames=<frames> "
+);
+
+enum {
+ SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX,
+};
+
+typedef struct coreaudio_sink coreaudio_sink;
+
+struct userdata {
+ AudioDeviceID device_id;
+ AudioDeviceIOProcID proc_id;
+
+ pa_thread_mq thread_mq;
+ pa_asyncmsgq *async_msgq;
+
+ pa_rtpoll *rtpoll;
+ pa_thread *thread;
+
+ pa_module *module;
+ pa_card *card;
+ char *audio_buf;
+ pa_bool_t running;
+ unsigned int device_latency;
+
+ char *vendor_name;
+
+ const AudioBufferList *render_input_data;
+ AudioBufferList *render_output_data;
+
+ AudioStreamBasicDescription stream_description;
+
+ PA_LLIST_HEAD(coreaudio_sink, sinks);
+};
+
+struct coreaudio_sink {
+ pa_sink *pa_sink;
+ struct userdata *userdata;
+
+ char *name;
+ unsigned int channel_idx;
+ unsigned int stream_idx;
+ pa_bool_t active;
+
+ pa_channel_map map;
+ pa_sample_spec ss;
+
+ PA_LLIST_FIELDS(coreaudio_sink);
+};
+
+static const char* const valid_modargs[] = {
+ "device_id",
+ NULL
+};
+
+static OSStatus io_render_proc (AudioDeviceID device,
+ const AudioTimeStamp *now,
+ const AudioBufferList *inputData,
+ const AudioTimeStamp *inputTime,
+ AudioBufferList *outputData,
+ const AudioTimeStamp *outputTime,
+ void *clientData)
+{
+ struct userdata *u = clientData;
+
+ pa_assert(u);
+ pa_assert(device == u->device_id);
+
+ u->render_input_data = inputData;
+ u->render_output_data = outputData;
+
+ pa_assert_se(pa_asyncmsgq_send(u->async_msgq, PA_MSGOBJECT(u->sinks->pa_sink), SINK_MESSAGE_RENDER, NULL, 0, NULL) == 0);
+
+ return 0;
+}
+
+static OSStatus ca_stream_format_changed(AudioDeviceID inDevice,
+ UInt32 inChannel,
+ Boolean isInput,
+ AudioDevicePropertyID inPropertyID,
+ void *inClientData)
+{
+ struct userdata *u = inClientData;
+
+ pa_assert(u);
+
+ /* REVISIT: PA can't currently handle external format change requests.
+ * Hence, we set the original format back in this callback to avoid horrible audio artefacts. */
+
+ return AudioDeviceSetProperty(inDevice, NULL, inChannel, isInput, kAudioDevicePropertyStreamFormat, sizeof(u->stream_description), &u->stream_description);
+}
+
+static pa_usec_t get_latency_us(struct userdata *u, unsigned channel_idx, unsigned stream_idx, bool is_input, pa_sample_spec *ss) {
+ UInt32 v, total = u->device_latency;
+ UInt32 err, size = sizeof(v);
+ AudioStreamID *streams;
+
+ pa_assert(u);
+
+ /* get audio stream IDs */
+ err = AudioDeviceGetPropertyInfo(u->device_id, 0, is_input, kAudioDevicePropertyStreams, &size, NULL);
+ if (!err) {
+ streams = alloca(size);
+ AudioDeviceGetProperty(u->device_id, 0, is_input, kAudioDevicePropertyStreams, &size, &streams);
+
+ pa_assert(stream_idx <= (size / sizeof(AudioStreamID)));
+
+ /* get the stream latency */
+ err = AudioStreamGetProperty(streams[stream_idx], is_input, kAudioStreamPropertyLatency, &size, &v);
+ if (!err)
+ total += v;
+ }
+
+ total *= pa_sample_size(ss);
+
+ return pa_bytes_to_usec(total, ss);
+}
+
+static void ca_device_check_device_state(struct userdata *u) {
+ coreaudio_sink *ca_sink;
+ pa_bool_t active = FALSE;
+
+ pa_assert(u);
+
+ for (ca_sink = u->sinks; ca_sink; ca_sink = ca_sink->next)
+ if (ca_sink->active) {
+ active = TRUE;
+ }
+
+ if (active && !u->running)
+ AudioDeviceStart(u->device_id, u->proc_id);
+ else if (!active && u->running)
+ AudioDeviceStop(u->device_id, u->proc_id);
+
+ u->running = active;
+}
+
+static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) {
+ coreaudio_sink *sink = PA_SINK(o)->userdata;
+ struct userdata *u = sink->userdata;
+ unsigned int i;
+ pa_memchunk audio_chunk;
+
+ switch (code) {
+ case SINK_MESSAGE_RENDER: {
+ /* audio out */
+ for (i = 0; i < u->render_output_data->mNumberBuffers; i++) {
+ AudioBuffer *buf = u->render_output_data->mBuffers + i;
+
+ pa_assert(sink);
+
+ if (PA_SINK_IS_OPENED(sink->pa_sink->thread_info.state)) {
+ if (sink->pa_sink->thread_info.rewind_requested)
+ pa_sink_process_rewind(sink->pa_sink, 0);
+
+ audio_chunk.memblock = pa_memblock_new_fixed(u->module->core->mempool, buf->mData, buf->mDataByteSize, FALSE);
+ audio_chunk.length = buf->mDataByteSize;
+ audio_chunk.index = 0;
+
+ pa_sink_render_into_full(sink->pa_sink, &audio_chunk);
+ pa_memblock_unref_fixed(audio_chunk.memblock);
+ }
+
+ sink = sink->next;
+ }
+
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_GET_LATENCY: {
+ *((pa_usec_t *) data) = get_latency_us(sink->userdata, sink->channel_idx, sink->stream_idx, 0, &sink->ss);
+ return 0;
+ }
+
+ case PA_SINK_MESSAGE_SET_STATE:
+ switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) {
+ case PA_SINK_SUSPENDED:
+ sink->active = FALSE;
+ break;
+
+ case PA_SINK_IDLE:
+ case PA_SINK_RUNNING:
+ sink->active = TRUE;
+ break;
+
+ case PA_SINK_UNLINKED:
+ case PA_SINK_INIT:
+ case PA_SINK_INVALID_STATE:
+ ;
+ }
+
+ ca_device_check_device_state(sink->userdata);
+ break;
+ }
+
+ return pa_sink_process_msg(o, code, data, offset, chunk);
+}
+
+static int ca_device_create_sink(pa_module *m, AudioBuffer *buf, int channel_idx, int stream_idx) {
+ OSStatus err;
+ UInt32 size;
+ struct userdata *u = m->userdata;
+ pa_sink_new_data new_data;
+ pa_sink_flags_t flags = PA_SINK_LATENCY | PA_SINK_HARDWARE;
+ coreaudio_sink *ca_sink;
+ pa_sink *sink;
+ unsigned int i;
+ char tmp[255];
+ pa_strbuf *strbuf;
+
+ ca_sink = pa_xnew0(coreaudio_sink, 1);
+ ca_sink->map.channels = buf->mNumberChannels;
+ ca_sink->ss.channels = buf->mNumberChannels;
+ ca_sink->channel_idx = channel_idx;
+ ca_sink->stream_idx = stream_idx;
+
+ /* get current stream format */
+ size = sizeof(AudioStreamBasicDescription);
+ err = AudioDeviceGetProperty(u->device_id, channel_idx, 0, kAudioDevicePropertyStreamFormat, &size, &u->stream_description);
+ if (err) {
+ pa_log("Failed to get kAudioDevicePropertyStreamFormats.");
+ return -1;
+ }
+
+ if (u->stream_description.mFormatID != kAudioFormatLinearPCM) {
+ pa_log("Unsupported audio format '%c%c%c%c'",
+ (char) (u->stream_description.mFormatID >> 24),
+ (char) (u->stream_description.mFormatID >> 16) & 0xff,
+ (char) (u->stream_description.mFormatID >> 8) & 0xff,
+ (char) (u->stream_description.mFormatID & 0xff));
+ return -1;
+ }
+
+ pa_log_debug("Sample rate: %f", u->stream_description.mSampleRate);
+ pa_log_debug("%d bytes per packet", (unsigned int) u->stream_description.mBytesPerPacket);
+ pa_log_debug("%d frames per packet", (unsigned int) u->stream_description.mFramesPerPacket);
+ pa_log_debug("%d bytes per frame", (unsigned int) u->stream_description.mBytesPerFrame);
+ pa_log_debug("%d channels per frame", (unsigned int) u->stream_description.mChannelsPerFrame);
+ pa_log_debug("%d bits per channel", (unsigned int) u->stream_description.mBitsPerChannel);
+
+ /* build a name for this stream */
+ strbuf = pa_strbuf_new();
+
+ for (i = 0; i < buf->mNumberChannels; i++) {
+ size = sizeof(tmp);
+ err = AudioDeviceGetProperty(u->device_id, channel_idx + i + 1, 0, kAudioObjectPropertyElementName, &size, tmp);
+ if (err || !strlen(tmp))
+ snprintf(tmp, sizeof(tmp), "Channel %d", channel_idx + i + 1);
+
+ if (i > 0)
+ pa_strbuf_puts(strbuf, ", ");
+
+ pa_strbuf_puts(strbuf, tmp);
+ }
+
+ ca_sink->name = pa_strbuf_tostring_free(strbuf);
+
+ pa_log_debug("Stream name is >%s<", ca_sink->name);
+
+ /* default to mono streams */
+ for (i = 0; i < ca_sink->map.channels; i++)
+ ca_sink->map.map[i] = PA_CHANNEL_POSITION_MONO;
+
+ if (buf->mNumberChannels == 2) {
+ ca_sink->map.map[0] = PA_CHANNEL_POSITION_LEFT;
+ ca_sink->map.map[1] = PA_CHANNEL_POSITION_RIGHT;
+ }
+
+ ca_sink->ss.rate = u->stream_description.mSampleRate;
+ ca_sink->ss.format = PA_SAMPLE_FLOAT32LE;
+
+ pa_sink_new_data_init(&new_data);
+ new_data.card = u->card;
+ new_data.driver = __FILE__;
+ new_data.module = u->module;
+ new_data.namereg_fail = FALSE;
+ pa_sink_new_data_set_name(&new_data, ca_sink->name);
+ pa_sink_new_data_set_channel_map(&new_data, &ca_sink->map);
+ pa_sink_new_data_set_sample_spec(&new_data, &ca_sink->ss);
+ pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_STRING, u->card->name);
+ pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_PRODUCT_NAME, u->card->name);
+ pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_DESCRIPTION, u->card->name);
+ pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_ACCESS_MODE, "mmap");
+ pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_CLASS, "sound");
+ pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_API, "CoreAudio");
+ pa_proplist_setf(new_data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) buf->mDataByteSize);
+
+ if (u->vendor_name)
+ pa_proplist_sets(new_data.proplist, PA_PROP_DEVICE_VENDOR_NAME, u->vendor_name);
+
+ sink = pa_sink_new(m->core, &new_data, flags);
+ pa_sink_new_data_done(&new_data);
+
+ if (!sink) {
+ pa_log("unable to create sink.");
+ return -1;
+ }
+
+ sink->parent.process_msg = sink_process_msg;
+ sink->userdata = ca_sink;
+
+ pa_sink_set_asyncmsgq(sink, u->thread_mq.inq);
+ pa_sink_set_rtpoll(sink, u->rtpoll);
+
+ ca_sink->pa_sink = sink;
+ ca_sink->userdata = u;
+
+ PA_LLIST_PREPEND(coreaudio_sink, u->sinks, ca_sink);
+
+ return 0;
+}
+
+static int ca_device_create_streams(pa_module *m, bool direction_in) {
+ OSStatus err;
+ UInt32 size, i, channel_idx, stream_idx;
+ struct userdata *u = m->userdata;
+ int section = direction_in ? 1 : 0;
+ AudioBufferList *buffer_list;
+
+ size = 0;
+ err = AudioDeviceGetPropertyInfo(u->device_id, 0, section, kAudioDevicePropertyStreamConfiguration, &size, NULL);
+ if (err) {
+ pa_log("Failed to get kAudioDevicePropertyStreamConfiguration (%s).", direction_in ? "input" : "output");
+ return -1;
+ }
+
+ if (!size)
+ return 0;
+
+ buffer_list = (AudioBufferList *) pa_xmalloc(size);
+ err = AudioDeviceGetProperty(u->device_id, 0, section, kAudioDevicePropertyStreamConfiguration, &size, buffer_list);
+
+ if (!err) {
+ stream_idx = 0;
+
+ for (channel_idx = 0, i = 0; i < buffer_list->mNumberBuffers; i++) {
+ AudioBuffer *buf = buffer_list->mBuffers + i;
+
+ if (!direction_in)
+ ca_device_create_sink(m, buf, channel_idx, stream_idx++);
+
+ channel_idx += buf->mNumberChannels;
+ }
+ }
+
+ pa_xfree(buffer_list);
+ return 0;
+}
+
+static void thread_func(void *userdata) {
+ struct userdata *u = userdata;
+
+ pa_assert(u);
+ pa_assert(u->module);
+ pa_assert(u->module->core);
+
+ pa_log_debug("Thread starting up");
+
+ if (u->module->core->realtime_scheduling)
+ pa_make_realtime(u->module->core->realtime_priority);
+
+ pa_thread_mq_install(&u->thread_mq);
+
+ for (;;) {
+ int ret;
+
+ ret = pa_rtpoll_run(u->rtpoll, TRUE);
+
+ if (ret < 0)
+ goto fail;
+
+ if (ret == 0)
+ goto finish;
+ }
+
+fail:
+ /* If this was no regular exit from the loop we have to continue
+ * processing messages until we received PA_MESSAGE_SHUTDOWN */
+ pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->module->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL);
+ pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN);
+
+finish:
+ pa_log_debug("Thread shutting down");
+}
+
+int pa__init(pa_module *m) {
+ OSStatus err;
+ UInt32 size, frames;
+ struct userdata *u = NULL;
+ pa_modargs *ma = NULL;
+ char tmp[64];
+ pa_card_new_data card_new_data;
+ coreaudio_sink *ca_sink;
+
+ pa_assert(m);
+
+ if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+ pa_log("Failed to parse module arguments.");
+ goto fail;
+ }
+
+ u = pa_xnew0(struct userdata, 1);
+ u->module = m;
+ m->userdata = u;
+
+ if (pa_modargs_get_value_u32(ma, "device_id", (unsigned int *) &u->device_id) != 0) {
+ pa_log("Failed to parse device_id argument.");
+ goto fail;
+ }
+
+ /* get device product name */
+ size = sizeof(tmp);
+ err = AudioDeviceGetProperty(u->device_id, 0, 0, kAudioDevicePropertyDeviceName, &size, &tmp);
+ if (err) {
+ pa_log("Failed to get kAudioDevicePropertyDeviceName (err = %08x).", (int) err);
+ goto fail;
+ }
+
+ pa_card_new_data_init(&card_new_data);
+ pa_card_new_data_set_name(&card_new_data, tmp);
+ pa_log_info("Initializing module for CoreAudio device '%s' (id %d)", tmp, (unsigned int) u->device_id);
+
+ /* get device vendor name (may fail) */
+ size = sizeof(tmp);
+ err = AudioDeviceGetProperty(u->device_id, 0, 0, kAudioDevicePropertyDeviceManufacturer, &size, &tmp);
+ if (!err)
+ u->vendor_name = pa_xstrdup(tmp);
+
+ /* create the card object */
+ u->card = pa_card_new(m->core, &card_new_data);
+ if (!u->card) {
+ pa_log("Unable to create card.\n");
+ goto fail;
+ }
+
+ pa_card_new_data_done(&card_new_data);
+ u->card->userdata = u;
+
+ u->rtpoll = pa_rtpoll_new();
+ pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
+ u->async_msgq = pa_asyncmsgq_new(0);
+ pa_rtpoll_item_new_asyncmsgq_read(u->rtpoll, PA_RTPOLL_EARLY-1, u->async_msgq);
+
+ PA_LLIST_HEAD_INIT(coreaudio_sink, u->sinks);
+
+ /* create sinks */
+ ca_device_create_streams(m, FALSE);
+
+ /* create the message thread */
+ if (!(u->thread = pa_thread_new(thread_func, u))) {
+ pa_log("Failed to create thread.");
+ goto fail;
+ }
+
+ /* register notification callback for stream format changes */
+ AudioDeviceAddPropertyListener(u->device_id, 0, 0, kAudioDevicePropertyStreamFormat, ca_stream_format_changed, u);
+
+ /* set number of frames in IOProc */
+ if (pa_modargs_get_value_u32(ma, "sample_frames", (unsigned int *) &frames) != 0)
+ frames = 4096;
+
+ AudioDeviceSetProperty(u->device_id, NULL, 0, 0, kAudioDevicePropertyBufferFrameSize, sizeof(frames), &frames);
+
+ /* get the device latency */
+ size = sizeof(u->device_latency);
+ AudioDeviceGetProperty(u->device_id, 0, 0, kAudioDevicePropertyLatency, &size, &u->device_latency);
+
+ /* create one ioproc for both directions */
+ err = AudioDeviceCreateIOProcID(u->device_id, io_render_proc, u, &u->proc_id);
+ if (err) {
+ pa_log("AudioDeviceCreateIOProcID() failed (err = %08x\n).", (int) err);
+ goto fail;
+ }
+
+ for (ca_sink = u->sinks; ca_sink; ca_sink = ca_sink->next)
+ pa_sink_put(ca_sink->pa_sink);
+
+ pa_modargs_free(ma);
+
+ return 0;
+
+fail:
+ if (u)
+ pa__done(m);
+
+ if (ma)
+ pa_modargs_free(ma);
+
+ return -1;
+}
+
+void pa__done(pa_module *m) {
+ struct userdata *u;
+ coreaudio_sink *ca_sink;
+
+ pa_assert(m);
+
+ u = m->userdata;
+ pa_assert(u);
+
+ /* unlink sinks */
+ for (ca_sink = u->sinks; ca_sink; ca_sink = ca_sink->next)
+ if (ca_sink->pa_sink)
+ pa_sink_unlink(ca_sink->pa_sink);
+
+ if (u->thread) {
+ pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL);
+ pa_thread_free(u->thread);
+ }
+
+ pa_thread_mq_done(&u->thread_mq);
+ pa_asyncmsgq_unref(u->async_msgq);
+
+ /* free sinks */
+ for (ca_sink = u->sinks; ca_sink;) {
+ coreaudio_sink *next = ca_sink->next;
+
+ if (ca_sink->pa_sink)
+ pa_sink_unref(ca_sink->pa_sink);
+
+ if (ca_sink->name)
+ pa_xfree(ca_sink->name);
+
+ pa_xfree(ca_sink);
+ ca_sink = next;
+ }
+
+ if (u->proc_id) {
+ AudioDeviceStop(u->device_id, u->proc_id);
+ AudioDeviceDestroyIOProcID(u->device_id, u->proc_id);
+ }
+
+ AudioDeviceRemovePropertyListener(u->device_id, 0, 0, kAudioDevicePropertyStreamFormat, ca_stream_format_changed);
+
+ if (u->audio_buf)
+ pa_xfree(u->audio_buf);
+
+ if (u->vendor_name)
+ pa_xfree(u->vendor_name);
+
+ if (u->rtpoll)
+ pa_rtpoll_free(u->rtpoll);
+
+ if (u->card)
+ pa_card_free(u->card);
+
+ pa_xfree(u);
+}
--
1.6.3.3
More information about the pulseaudio-discuss
mailing list