Convert writers to lopsub.
[paraslash.git] / osx_write.c
1 /*
2 * Copyright (C) 2006 Andre Noll <maan@tuebingen.mpg.de>
3 *
4 * Licensed under the GPL v2. For licencing details see COPYING.
5 */
6
7 /** \file osx_write.c paraslash's output plugin for MacOs */
8
9 /*
10 * based on mosx-mpg123, by Guillaume Outters and Steven A. Kortze
11 * <skortze@sourceforge.net>
12 */
13
14 #include <regex.h>
15 #include <sys/types.h>
16 #include <lopsub.h>
17
18 #include "write_cmd.lsg.h"
19 #include "para.h"
20 #include "fd.h"
21 #include "string.h"
22 #include "list.h"
23 #include "sched.h"
24 #include "buffer_tree.h"
25 #include "write.h"
26 #include "ipc.h"
27 #include "error.h"
28
29 #include <CoreServices/CoreServices.h>
30 #include <AudioUnit/AudioUnit.h>
31 #include <AudioToolbox/AudioToolbox.h>
32
33 /** Data specific to the osx writer. */
34 struct private_osx_write_data {
35 /** The main CoreAudio handle. */
36 AudioUnit audio_unit;
37 /** True if we wrote some audio data. */
38 bool playing;
39 /** Sample rate of the current audio stream. */
40 unsigned sample_rate;
41 /** Sample format of the current audio stream */
42 unsigned sample_format;
43 /** Number of channels of the current audio stream. */
44 unsigned channels;
45 /**
46 * Serializes access to buffer tree nodes between the writer and
47 * the callback which runs in a different thread.
48 */
49 int mutex;
50 /**
51 * The btr node of the callback.
52 *
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
56 * callback.
57 *
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.
63 *
64 * The writer grabs the mutex in its post_select method and pushes down
65 * the buffers to the callback node.
66 */
67 struct btr_node *callback_btrn;
68 };
69
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
72 * through the socket.
73 */
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)
77 {
78 int i;
79 struct writer_node *wn = cb_arg;
80 struct private_osx_write_data *powd;
81 size_t samples_have, samples_want = 0;
82
83 powd = wn->private_data;
84 mutex_lock(powd->mutex);
85 powd = wn->private_data;
86 if (!powd || !wn->btrn)
87 goto out;
88 /*
89 * We fill with zeros if no data was yet written and we do not have
90 * enough to fill all buffers.
91 */
92 if (!powd->playing) {
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;
97 if (have < want) {
98 PARA_DEBUG_LOG("deferring playback (have = %zu < %zu = want)\n",
99 have, want);
100 for (i = 0; i < abl->mNumberBuffers; i++)
101 memset(abl->mBuffers[i].mData, 0,
102 abl->mBuffers[i].mDataByteSize);
103 goto out;
104 }
105 powd->playing = true;
106 }
107
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;
112
113 samples_want = sz / wn->min_iqs;
114 while (samples_want > 0) {
115 char *buf;
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);
121 if (samples == 0)
122 break;
123 bytes = samples * wn->min_iqs;
124 memcpy(dest, buf, bytes);
125 btr_consume(powd->callback_btrn, bytes);
126 samples_want -= samples;
127 dest += bytes;
128 }
129 if (samples_want == 0)
130 continue;
131 if (btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF) >= 0)
132 PARA_INFO_LOG("zero-padding (%zu samples)\n",
133 samples_want);
134 memset(dest, 0, samples_want * wn->min_iqs);
135 break;
136 }
137 out:
138 mutex_unlock(powd->mutex);
139 return noErr;
140 }
141
142 static int core_audio_init(struct writer_node *wn)
143 {
144 struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
145 Component comp;
146 int ret;
147 int32_t val;
148 AURenderCallbackStruct input_callback;
149 ComponentDescription desc = {
150 .componentType = kAudioUnitType_Output,
151 .componentSubType = kAudioUnitSubType_DefaultOutput,
152 .componentManufacturer = kAudioUnitManufacturer_Apple,
153 };
154 AudioStreamBasicDescription format = {
155 .mFormatID = kAudioFormatLinearPCM,
156 .mFramesPerPacket = 1,
157 };
158 struct btr_node *btrn = wn->btrn;
159 struct btr_node_description bnd;
160
161 PARA_INFO_LOG("wn: %p\n", wn);
162 ret = -E_DEFAULT_COMP;
163 comp = FindNextComponent(NULL, &desc);
164 if (!comp)
165 goto e0;
166 ret = -E_OPEN_COMP;
167 if (OpenAComponent(comp, &powd->audio_unit))
168 goto e0;
169 ret = -E_UNIT_INIT;
170 if (AudioUnitInitialize(powd->audio_unit))
171 goto e1;
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;
178 /*
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
183 * format.
184 */
185
186 format.mSampleRate = powd->sample_rate;
187 format.mChannelsPerFrame = powd->channels;
188
189 switch (powd->sample_format) {
190 case SF_S8:
191 case SF_U8:
192 wn->min_iqs = powd->channels;
193 format.mBitsPerChannel = 8;
194 format.mBytesPerPacket = powd->channels;
195 format.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
196 break;
197 default:
198 wn->min_iqs = powd->channels * 2;
199 format.mBytesPerPacket = powd->channels * 2;
200 format.mBitsPerChannel = 16;
201 format.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
202 }
203 format.mBytesPerFrame = format.mBytesPerPacket;
204
205 if (powd->sample_format == SF_S16_BE || powd->sample_format == SF_U16_BE)
206 format.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
207
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)))
212 goto e2;
213 ret = -E_ADD_CALLBACK;
214 if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
215 kAudioUnitScope_Input, 0, &input_callback,
216 sizeof(input_callback)) < 0)
217 goto e2;
218
219 ret = mutex_new();
220 if (ret < 0)
221 goto e2;
222 powd->mutex = ret;
223 /* set up callback btr node */
224 bnd.name = "cb_node";
225 bnd.parent = btrn;
226 bnd.child = NULL;
227 bnd.handler = NULL;
228 bnd.context = powd;
229 powd->callback_btrn = btr_new_node(&bnd);
230 wn->private_data = powd;
231 return 1;
232 e2:
233 AudioUnitUninitialize(powd->audio_unit);
234 e1:
235 CloseComponent(powd->audio_unit);
236 e0:
237 free(powd);
238 wn->private_data = NULL;
239 return ret;
240 }
241
242 static void osx_write_close(struct writer_node *wn)
243 {
244 struct private_osx_write_data *powd = wn->private_data;
245
246 if (!powd)
247 return;
248 PARA_INFO_LOG("closing writer node %p\n", wn);
249 mutex_destroy(powd->mutex);
250 free(powd);
251 wn->private_data = NULL;
252 }
253
254 /* must be called with the mutex held */
255 static inline bool need_drain_delay(struct private_osx_write_data *powd)
256 {
257 if (!powd->playing)
258 return false;
259 return btr_get_input_queue_size(powd->callback_btrn) != 0;
260 }
261
262 static void osx_write_pre_select(struct sched *s, void *context)
263 {
264 struct writer_node *wn = context;
265 struct private_osx_write_data *powd = wn->private_data;
266 int ret;
267 bool drain_delay_nec = false;
268
269 if (!powd) {
270 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
271 if (ret != 0)
272 sched_min_delay(s);
273 return;
274 }
275
276 mutex_lock(powd->mutex);
277 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_INTERNAL);
278 if (ret < 0)
279 drain_delay_nec = need_drain_delay(powd);
280 mutex_unlock(powd->mutex);
281
282 if (drain_delay_nec)
283 return sched_request_timeout_ms(50, s);
284 if (ret != 0)
285 return sched_min_delay(s);
286 sched_request_timeout_ms(50, s);
287 }
288
289 static int osx_write_post_select(__a_unused struct sched *s, void *context)
290 {
291 struct writer_node *wn = context;
292 struct private_osx_write_data *powd = wn->private_data;
293 struct btr_node *btrn = wn->btrn;
294 int ret;
295
296 ret = task_get_notification(wn->task);
297 if (ret < 0)
298 goto fail;
299 if (!powd) {
300 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
301 if (ret == 0)
302 return 0;
303 if (ret < 0)
304 goto fail;
305 ret = core_audio_init(wn);
306 if (ret < 0)
307 goto fail;
308 powd = wn->private_data;
309 ret = -E_UNIT_START;
310 if (AudioOutputUnitStart(powd->audio_unit) != noErr) {
311 AudioUnitUninitialize(powd->audio_unit);
312 CloseComponent(powd->audio_unit);
313 btr_remove_node(&powd->callback_btrn);
314 goto fail;
315 }
316 }
317 mutex_lock(powd->mutex);
318 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_INTERNAL);
319 if (ret > 0)
320 btr_pushdown(btrn);
321 if (ret < 0 && need_drain_delay(powd))
322 ret = 0;
323 mutex_unlock(powd->mutex);
324 if (ret >= 0)
325 return 0;
326 fail:
327 assert(ret < 0);
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);
333 }
334 btr_remove_node(&wn->btrn);
335 PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
336 return ret;
337 }
338
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,
343 };