2 * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
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>
18 #include "write_cmd.lsg.h"
24 #include "buffer_tree.h"
29 #include <CoreServices/CoreServices.h>
30 #include <AudioUnit/AudioUnit.h>
31 #include <AudioToolbox/AudioToolbox.h>
33 /** Data specific to the osx writer. */
34 struct private_osx_write_data {
35 /** The main CoreAudio handle. */
37 /** True if we wrote some audio data. */
39 /** Sample rate of the current audio stream. */
41 /** Sample format of the current audio stream */
42 unsigned sample_format;
43 /** Number of channels of the current audio stream. */
46 * Serializes access to buffer tree nodes between the writer and
47 * the callback which runs in a different thread.
51 * The btr node of the callback.
53 * Although access to the btr node is serialized between the writer and
54 * the callback via the above mutex, this does not stop other buffer
55 * tree nodes, for example the decoder, to race against the osx
58 * However, since all operations on buffer tree nodes are local in the
59 * sense that they only affect one level in the buffer tree (i.e.
60 * parent or child nodes, but not the grandparent or the
61 * grandchildren), we may work around this problem by using another
62 * buffer tree node for the callback.
64 * The writer grabs the mutex in its post_select method and pushes down
65 * the buffers to the callback node.
67 struct btr_node *callback_btrn;
70 /* This function writes the address and the number of bytes to one end of the socket.
71 * The post_select() function then fills the buffer and notifies the callback also
74 static OSStatus osx_callback(void *cb_arg, __a_unused AudioUnitRenderActionFlags *af,
75 __a_unused const AudioTimeStamp *ts, __a_unused UInt32 bus_number,
76 __a_unused UInt32 num_frames, AudioBufferList *abl)
79 struct writer_node *wn = cb_arg;
80 struct private_osx_write_data *powd;
81 size_t samples_have, samples_want = 0;
83 powd = wn->private_data;
84 mutex_lock(powd->mutex);
85 powd = wn->private_data;
86 if (!powd || !wn->btrn)
89 * We fill with zeros if no data was yet written and we do not have
90 * enough to fill all buffers.
93 size_t want = 0, have =
94 btr_get_input_queue_size(powd->callback_btrn);
95 for (i = 0; i < abl->mNumberBuffers; i++)
96 want += abl->mBuffers[i].mDataByteSize;
98 PARA_DEBUG_LOG("deferring playback (have = %zu < %zu = want)\n",
100 for (i = 0; i < abl->mNumberBuffers; i++)
101 memset(abl->mBuffers[i].mData, 0,
102 abl->mBuffers[i].mDataByteSize);
105 powd->playing = true;
108 for (i = 0; i < abl->mNumberBuffers; i++) {
109 /* what we have to fill */
110 void *dest = abl->mBuffers[i].mData;
111 size_t sz = abl->mBuffers[i].mDataByteSize, samples, bytes;
113 samples_want = sz / wn->min_iqs;
114 while (samples_want > 0) {
116 btr_merge(powd->callback_btrn, wn->min_iqs);
117 samples_have = btr_next_buffer(powd->callback_btrn, &buf) / wn->min_iqs;
118 //PARA_INFO_LOG("i: %d want %zu samples to addr %p, have: %zu\n", i, samples_want,
119 // dest, samples_have);
120 samples = PARA_MIN(samples_have, samples_want);
123 bytes = samples * wn->min_iqs;
124 memcpy(dest, buf, bytes);
125 btr_consume(powd->callback_btrn, bytes);
126 samples_want -= samples;
129 if (samples_want == 0)
131 if (btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF) >= 0)
132 PARA_INFO_LOG("zero-padding (%zu samples)\n",
134 memset(dest, 0, samples_want * wn->min_iqs);
138 mutex_unlock(powd->mutex);
142 static int core_audio_init(struct writer_node *wn)
144 struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
148 AURenderCallbackStruct input_callback;
149 ComponentDescription desc = {
150 .componentType = kAudioUnitType_Output,
151 .componentSubType = kAudioUnitSubType_DefaultOutput,
152 .componentManufacturer = kAudioUnitManufacturer_Apple,
154 AudioStreamBasicDescription format = {
155 .mFormatID = kAudioFormatLinearPCM,
156 .mFramesPerPacket = 1,
158 struct btr_node *btrn = wn->btrn;
159 struct btr_node_description bnd;
161 PARA_INFO_LOG("wn: %p\n", wn);
162 ret = -E_DEFAULT_COMP;
163 comp = FindNextComponent(NULL, &desc);
167 if (OpenAComponent(comp, &powd->audio_unit))
170 if (AudioUnitInitialize(powd->audio_unit))
172 get_btr_sample_rate(btrn, &val);
173 powd->sample_rate = val;
174 get_btr_channels(btrn, &val);
175 powd->channels = val;
176 get_btr_sample_format(btrn, &val);
177 powd->sample_format = val;
179 * Choose PCM format. We tell the Output Unit what format we're going
180 * to supply data to it. This is necessary if you're providing data
181 * through an input callback AND you want the DefaultOutputUnit to do
182 * any format conversions necessary from your format to the device's
186 format.mSampleRate = powd->sample_rate;
187 format.mChannelsPerFrame = powd->channels;
189 switch (powd->sample_format) {
192 wn->min_iqs = powd->channels;
193 format.mBitsPerChannel = 8;
194 format.mBytesPerPacket = powd->channels;
195 format.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
198 wn->min_iqs = powd->channels * 2;
199 format.mBytesPerPacket = powd->channels * 2;
200 format.mBitsPerChannel = 16;
201 format.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
203 format.mBytesPerFrame = format.mBytesPerPacket;
205 if (powd->sample_format == SF_S16_BE || powd->sample_format == SF_U16_BE)
206 format.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
208 input_callback = (AURenderCallbackStruct){osx_callback, wn};
209 ret = -E_STREAM_FORMAT;
210 if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat,
211 kAudioUnitScope_Input, 0, &format, sizeof(format)))
213 ret = -E_ADD_CALLBACK;
214 if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
215 kAudioUnitScope_Input, 0, &input_callback,
216 sizeof(input_callback)) < 0)
223 /* set up callback btr node */
224 bnd.name = "cb_node";
229 powd->callback_btrn = btr_new_node(&bnd);
230 wn->private_data = powd;
233 AudioUnitUninitialize(powd->audio_unit);
235 CloseComponent(powd->audio_unit);
238 wn->private_data = NULL;
242 static void osx_write_close(struct writer_node *wn)
244 struct private_osx_write_data *powd = wn->private_data;
248 PARA_INFO_LOG("closing writer node %p\n", wn);
249 mutex_destroy(powd->mutex);
251 wn->private_data = NULL;
254 /* must be called with the mutex held */
255 static inline bool need_drain_delay(struct private_osx_write_data *powd)
259 return btr_get_input_queue_size(powd->callback_btrn) != 0;
262 static void osx_write_pre_select(struct sched *s, void *context)
264 struct writer_node *wn = context;
265 struct private_osx_write_data *powd = wn->private_data;
267 bool drain_delay_nec = false;
270 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
276 mutex_lock(powd->mutex);
277 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_INTERNAL);
279 drain_delay_nec = need_drain_delay(powd);
280 mutex_unlock(powd->mutex);
283 return sched_request_timeout_ms(50, s);
285 return sched_min_delay(s);
286 sched_request_timeout_ms(50, s);
289 static int osx_write_post_select(__a_unused struct sched *s, void *context)
291 struct writer_node *wn = context;
292 struct private_osx_write_data *powd = wn->private_data;
293 struct btr_node *btrn = wn->btrn;
296 ret = task_get_notification(wn->task);
300 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
305 ret = core_audio_init(wn);
308 powd = wn->private_data;
310 if (AudioOutputUnitStart(powd->audio_unit) != noErr) {
311 AudioUnitUninitialize(powd->audio_unit);
312 CloseComponent(powd->audio_unit);
313 btr_remove_node(&powd->callback_btrn);
317 mutex_lock(powd->mutex);
318 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_INTERNAL);
321 if (ret < 0 && need_drain_delay(powd))
323 mutex_unlock(powd->mutex);
328 if (powd && powd->callback_btrn) {
329 AudioOutputUnitStop(powd->audio_unit);
330 AudioUnitUninitialize(powd->audio_unit);
331 CloseComponent(powd->audio_unit);
332 btr_remove_node(&powd->callback_btrn);
334 btr_remove_node(&wn->btrn);
335 PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
339 struct writer lsg_write_cmd_com_osx_user_data = {
340 .close = osx_write_close,
341 .pre_select = osx_write_pre_select,
342 .post_select = osx_write_post_select,