2 * Copyright (C) 2006-2013 Andre Noll <maan@systemlinux.org>
4 * Licensed under the GPL v2. For licencing details see COPYING.
7 /** \file osx_write.c paraslash's output plugin for MacOs */
10 * based on mosx-mpg123, by Guillaume Outters and Steven A. Kortze
11 * <skortze@sourceforge.net>
15 #include <sys/types.h>
23 #include "buffer_tree.h"
25 #include "write_common.h"
26 #include "osx_write.cmdline.h"
30 #include <CoreServices/CoreServices.h>
31 #include <AudioUnit/AudioUnit.h>
32 #include <AudioToolbox/AudioToolbox.h>
34 /** Data specific to the osx writer. */
35 struct private_osx_write_data {
36 /** The main CoreAudio handle. */
38 /** True if we wrote some audio data. */
40 /** Sample rate of the current audio stream. */
42 /** Sample format of the current audio stream */
43 unsigned sample_format;
44 /** Number of channels of the current audio stream. */
47 * Serializes access to buffer tree nodes between the writer and
48 * the callback which runs in a different thread.
52 * The btr node of the callback.
54 * Although access to the btr node is serialized between the writer and
55 * the callback via the above mutex, this does not stop other buffer
56 * tree nodes, for example the decoder, to race against the osx
59 * However, since all operations on buffer tree nodes are local in the
60 * sense that they only affect one level in the buffer tree (i.e.
61 * parent or child nodes, but not the grandparent or the
62 * grandchildren), we may work around this problem by using another
63 * buffer tree node for the callback.
65 * The writer grabs the mutex in its post_select method and pushes down
66 * the buffers to the callback node.
68 struct btr_node *callback_btrn;
71 /* This function writes the address and the number of bytes to one end of the socket.
72 * The post_select() function then fills the buffer and notifies the callback also
75 static OSStatus osx_callback(void *cb_arg, __a_unused AudioUnitRenderActionFlags *af,
76 __a_unused const AudioTimeStamp *ts, __a_unused UInt32 bus_number,
77 __a_unused UInt32 num_frames, AudioBufferList *abl)
80 struct writer_node *wn = cb_arg;
81 struct private_osx_write_data *powd;
82 size_t samples_have, samples_want = 0;
84 powd = wn->private_data;
85 mutex_lock(powd->mutex);
86 powd = wn->private_data;
87 if (!powd || !wn->btrn)
90 * We fill with zeros if no data was yet written and we do not have
91 * enough to fill all buffers.
94 size_t want = 0, have =
95 btr_get_input_queue_size(powd->callback_btrn);
96 for (i = 0; i < abl->mNumberBuffers; i++)
97 want += abl->mBuffers[i].mDataByteSize;
99 PARA_DEBUG_LOG("deferring playback (have = %zu < %zu = want)\n",
101 for (i = 0; i < abl->mNumberBuffers; i++)
102 memset(abl->mBuffers[i].mData, 0,
103 abl->mBuffers[i].mDataByteSize);
106 powd->playing = true;
109 for (i = 0; i < abl->mNumberBuffers; i++) {
110 /* what we have to fill */
111 void *dest = abl->mBuffers[i].mData;
112 size_t sz = abl->mBuffers[i].mDataByteSize, samples, bytes;
114 samples_want = sz / wn->min_iqs;
115 while (samples_want > 0) {
117 btr_merge(powd->callback_btrn, wn->min_iqs);
118 samples_have = btr_next_buffer(powd->callback_btrn, &buf) / wn->min_iqs;
119 //PARA_INFO_LOG("i: %d want %zu samples to addr %p, have: %zu\n", i, samples_want,
120 // dest, samples_have);
121 samples = PARA_MIN(samples_have, samples_want);
124 bytes = samples * wn->min_iqs;
125 memcpy(dest, buf, bytes);
126 btr_consume(powd->callback_btrn, bytes);
127 samples_want -= samples;
130 if (samples_want == 0)
132 if (btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF) >= 0)
133 PARA_INFO_LOG("zero-padding (%zu samples)\n",
135 memset(dest, 0, samples_want * wn->min_iqs);
139 mutex_unlock(powd->mutex);
143 static int core_audio_init(struct writer_node *wn)
145 struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
149 AURenderCallbackStruct input_callback;
150 ComponentDescription desc = {
151 .componentType = kAudioUnitType_Output,
152 .componentSubType = kAudioUnitSubType_DefaultOutput,
153 .componentManufacturer = kAudioUnitManufacturer_Apple,
155 AudioStreamBasicDescription format = {
156 .mFormatID = kAudioFormatLinearPCM,
157 .mFramesPerPacket = 1,
159 struct btr_node *btrn = wn->btrn;
160 struct btr_node_description bnd;
162 PARA_INFO_LOG("wn: %p\n", wn);
163 ret = -E_DEFAULT_COMP;
164 comp = FindNextComponent(NULL, &desc);
168 if (OpenAComponent(comp, &powd->audio_unit))
171 if (AudioUnitInitialize(powd->audio_unit))
173 get_btr_sample_rate(btrn, &val);
174 powd->sample_rate = val;
175 get_btr_channels(btrn, &val);
176 powd->channels = val;
177 get_btr_sample_format(btrn, &val);
178 powd->sample_format = val;
180 * Choose PCM format. We tell the Output Unit what format we're going
181 * to supply data to it. This is necessary if you're providing data
182 * through an input callback AND you want the DefaultOutputUnit to do
183 * any format conversions necessary from your format to the device's
187 format.mSampleRate = powd->sample_rate;
188 format.mChannelsPerFrame = powd->channels;
190 switch (powd->sample_format) {
193 wn->min_iqs = powd->channels;
194 format.mBitsPerChannel = 8;
195 format.mBytesPerPacket = powd->channels;
196 format.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
199 wn->min_iqs = powd->channels * 2;
200 format.mBytesPerPacket = powd->channels * 2;
201 format.mBitsPerChannel = 16;
202 format.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
204 format.mBytesPerFrame = format.mBytesPerPacket;
206 if (powd->sample_format == SF_S16_BE || powd->sample_format == SF_U16_BE)
207 format.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
209 input_callback = (AURenderCallbackStruct){osx_callback, wn};
210 ret = -E_STREAM_FORMAT;
211 if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat,
212 kAudioUnitScope_Input, 0, &format, sizeof(format)))
214 ret = -E_ADD_CALLBACK;
215 if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
216 kAudioUnitScope_Input, 0, &input_callback,
217 sizeof(input_callback)) < 0)
224 /* set up callback btr node */
225 bnd.name = "cb_node";
230 powd->callback_btrn = btr_new_node(&bnd);
231 wn->private_data = powd;
234 AudioUnitUninitialize(powd->audio_unit);
236 CloseComponent(powd->audio_unit);
239 wn->private_data = NULL;
243 __malloc static void *osx_write_parse_config_or_die(int argc, char **argv)
245 struct osx_write_args_info *conf = para_calloc(sizeof(*conf));
247 /* exits on errors */
248 osx_write_cmdline_parser(argc, argv, conf);
252 static void osx_free_config(void *conf)
254 osx_write_cmdline_parser_free(conf);
257 static void osx_write_close(struct writer_node *wn)
259 struct private_osx_write_data *powd = wn->private_data;
263 PARA_INFO_LOG("closing writer node %p\n", wn);
264 mutex_destroy(powd->mutex);
266 wn->private_data = NULL;
269 /* must be called with the mutex held */
270 static inline bool need_drain_delay(struct private_osx_write_data *powd)
274 return btr_get_input_queue_size(powd->callback_btrn) != 0;
277 static void osx_write_pre_select(struct sched *s, struct task *t)
279 struct writer_node *wn = container_of(t, struct writer_node, task);
280 struct private_osx_write_data *powd = wn->private_data;
282 bool drain_delay_nec = false;
285 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
291 mutex_lock(powd->mutex);
292 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_INTERNAL);
294 drain_delay_nec = need_drain_delay(powd);
295 mutex_unlock(powd->mutex);
298 return sched_request_timeout_ms(50, s);
300 return sched_min_delay(s);
301 sched_request_timeout_ms(50, s);
304 static int osx_write_post_select(__a_unused struct sched *s, struct task *t)
306 struct writer_node *wn = container_of(t, struct writer_node, task);
307 struct private_osx_write_data *powd = wn->private_data;
308 struct btr_node *btrn = wn->btrn;
311 ret = task_get_notification(t);
315 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
320 ret = core_audio_init(wn);
323 powd = wn->private_data;
325 if (AudioOutputUnitStart(powd->audio_unit) != noErr) {
326 AudioUnitUninitialize(powd->audio_unit);
327 CloseComponent(powd->audio_unit);
328 btr_remove_node(&powd->callback_btrn);
332 mutex_lock(powd->mutex);
333 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_INTERNAL);
336 if (ret < 0 && need_drain_delay(powd))
338 mutex_unlock(powd->mutex);
343 if (powd && powd->callback_btrn) {
344 AudioOutputUnitStop(powd->audio_unit);
345 AudioUnitUninitialize(powd->audio_unit);
346 CloseComponent(powd->audio_unit);
347 btr_remove_node(&powd->callback_btrn);
349 btr_remove_node(&wn->btrn);
350 PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
355 * The init function of the osx writer.
357 * \param w Filled in by the function.
359 void osx_write_init(struct writer *w)
361 struct osx_write_args_info dummy;
363 osx_write_cmdline_parser_init(&dummy);
364 w->close = osx_write_close;
365 w->pre_select = osx_write_pre_select;
366 w->post_select = osx_write_post_select;
367 w->parse_config_or_die = osx_write_parse_config_or_die;
368 w->free_config = osx_free_config;
369 w->help = (struct ggo_help)DEFINE_GGO_HELP(osx_write);
370 osx_write_cmdline_parser_free(&dummy);