aft.c: Silence scan-build warning.
[paraslash.git] / osx_write.c
1 /*
2 * Copyright (C) 2006-2012 Andre Noll <maan@systemlinux.org>
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
17 #include "para.h"
18 #include "fd.h"
19 #include "string.h"
20 #include "list.h"
21 #include "sched.h"
22 #include "ggo.h"
23 #include "buffer_tree.h"
24 #include "write.h"
25 #include "write_common.h"
26 #include "osx_write.cmdline.h"
27 #include "ipc.h"
28 #include "error.h"
29
30 #include <CoreServices/CoreServices.h>
31 #include <AudioUnit/AudioUnit.h>
32 #include <AudioToolbox/AudioToolbox.h>
33
34 /** Data specific to the osx writer. */
35 struct private_osx_write_data {
36 /** The main CoreAudio handle. */
37 AudioUnit audio_unit;
38 /** True if we wrote some audio data. */
39 bool playing;
40 /** Sample rate of the current audio stream. */
41 unsigned sample_rate;
42 /** Sample format of the current audio stream */
43 unsigned sample_format;
44 /** Number of channels of the current audio stream. */
45 unsigned channels;
46 /** Serializes access to buffer tree nodes. */
47 int mutex;
48 /** The btr node of the callback. */
49 struct btr_node *callback_btrn;
50 };
51
52 /* This function writes the address and the number of bytes to one end of the socket.
53 * The post_select() function then fills the buffer and notifies the callback also
54 * through the socket.
55 */
56 static OSStatus osx_callback(void *cb_arg, __a_unused AudioUnitRenderActionFlags *af,
57 __a_unused const AudioTimeStamp *ts, __a_unused UInt32 bus_number,
58 __a_unused UInt32 num_frames, AudioBufferList *abl)
59 {
60 int i;
61 struct writer_node *wn = cb_arg;
62 struct private_osx_write_data *powd = wn->private_data;
63 size_t samples_have, samples_want = 0;
64
65 mutex_lock(powd->mutex);
66 /*
67 * We fill with zeros if no data was yet written and we do not have
68 * enough to fill all buffers.
69 */
70 if (!powd->playing) {
71 size_t want = 0, have =
72 btr_get_input_queue_size(powd->callback_btrn);
73 for (i = 0; i < abl->mNumberBuffers; i++)
74 want += abl->mBuffers[i].mDataByteSize;
75 if (have < want) {
76 PARA_DEBUG_LOG("deferring playback (have = %zu < %zu = want)\n",
77 have, want);
78 for (i = 0; i < abl->mNumberBuffers; i++)
79 memset(abl->mBuffers[i].mData, 0,
80 abl->mBuffers[i].mDataByteSize);
81 goto out;
82 }
83 powd->playing = true;
84 }
85
86 for (i = 0; i < abl->mNumberBuffers; i++) {
87 /* what we have to fill */
88 void *dest = abl->mBuffers[i].mData;
89 size_t sz = abl->mBuffers[i].mDataByteSize, samples, bytes;
90
91 samples_want = sz / wn->min_iqs;
92 while (samples_want > 0) {
93 char *buf;
94 btr_merge(powd->callback_btrn, wn->min_iqs);
95 samples_have = btr_next_buffer(powd->callback_btrn, &buf) / wn->min_iqs;
96 //PARA_INFO_LOG("i: %d want %zu samples to addr %p, have: %zu\n", i, samples_want,
97 // dest, samples_have);
98 samples = PARA_MIN(samples_have, samples_want);
99 if (samples == 0)
100 break;
101 bytes = samples * wn->min_iqs;
102 memcpy(dest, buf, bytes);
103 btr_consume(powd->callback_btrn, bytes);
104 samples_want -= samples;
105 dest += bytes;
106 }
107 if (samples_want == 0)
108 continue;
109 if (btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF) >= 0)
110 PARA_INFO_LOG("zero-padding (%zu samples)\n",
111 samples_want);
112 memset(dest, 0, samples_want * wn->min_iqs);
113 break;
114 }
115 out:
116 mutex_unlock(powd->mutex);
117 return noErr;
118 }
119
120 static int core_audio_init(struct writer_node *wn)
121 {
122 struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
123 Component comp;
124 int ret;
125 int32_t val;
126 AURenderCallbackStruct input_callback;
127 ComponentDescription desc = {
128 .componentType = kAudioUnitType_Output,
129 .componentSubType = kAudioUnitSubType_DefaultOutput,
130 .componentManufacturer = kAudioUnitManufacturer_Apple,
131 };
132 AudioStreamBasicDescription format = {
133 .mFormatID = kAudioFormatLinearPCM,
134 .mFramesPerPacket = 1,
135 };
136 struct btr_node *btrn = wn->btrn;
137 struct btr_node_description bnd;
138
139 PARA_INFO_LOG("wn: %p\n", wn);
140 ret = -E_DEFAULT_COMP;
141 comp = FindNextComponent(NULL, &desc);
142 if (!comp)
143 goto e0;
144 ret = -E_OPEN_COMP;
145 if (OpenAComponent(comp, &powd->audio_unit))
146 goto e0;
147 ret = -E_UNIT_INIT;
148 if (AudioUnitInitialize(powd->audio_unit))
149 goto e1;
150 get_btr_sample_rate(btrn, &val);
151 powd->sample_rate = val;
152 get_btr_channels(btrn, &val);
153 powd->channels = val;
154 get_btr_sample_format(btrn, &val);
155 powd->sample_format = val;
156 /*
157 * Choose PCM format. We tell the Output Unit what format we're going
158 * to supply data to it. This is necessary if you're providing data
159 * through an input callback AND you want the DefaultOutputUnit to do
160 * any format conversions necessary from your format to the device's
161 * format.
162 */
163
164 format.mSampleRate = powd->sample_rate;
165 format.mChannelsPerFrame = powd->channels;
166
167 switch (powd->sample_format) {
168 case SF_S8:
169 case SF_U8:
170 wn->min_iqs = powd->channels;
171 format.mBitsPerChannel = 8;
172 format.mBytesPerPacket = powd->channels;
173 format.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
174 break;
175 default:
176 wn->min_iqs = powd->channels * 2;
177 format.mBytesPerPacket = powd->channels * 2;
178 format.mBitsPerChannel = 16;
179 format.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
180 }
181 format.mBytesPerFrame = format.mBytesPerPacket;
182
183 if (powd->sample_format == SF_S16_BE || powd->sample_format == SF_U16_BE)
184 format.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
185
186 input_callback = (AURenderCallbackStruct){osx_callback, wn};
187 ret = -E_STREAM_FORMAT;
188 if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat,
189 kAudioUnitScope_Input, 0, &format, sizeof(format)))
190 goto e2;
191 ret = -E_ADD_CALLBACK;
192 if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
193 kAudioUnitScope_Input, 0, &input_callback,
194 sizeof(input_callback)) < 0)
195 goto e2;
196
197 ret = mutex_new();
198 if (ret < 0)
199 goto e2;
200 powd->mutex = ret;
201 /* set up callback btr node */
202 bnd.name = "cb_node";
203 bnd.parent = btrn;
204 bnd.child = NULL;
205 bnd.handler = NULL;
206 bnd.context = powd;
207 powd->callback_btrn = btr_new_node(&bnd);
208 wn->private_data = powd;
209 return 1;
210 e2:
211 AudioUnitUninitialize(powd->audio_unit);
212 e1:
213 CloseComponent(powd->audio_unit);
214 e0:
215 free(powd);
216 wn->private_data = NULL;
217 return ret;
218 }
219
220 __malloc static void *osx_write_parse_config_or_die(const char *options)
221 {
222 struct osx_write_args_info *conf = para_calloc(sizeof(*conf));
223
224 /* exits on errors */
225 osx_cmdline_parser_string(options, conf, "osx_write");
226 return conf;
227 }
228
229 static void osx_free_config(void *conf)
230 {
231 osx_cmdline_parser_free(conf);
232 }
233
234 static void osx_write_close(struct writer_node *wn)
235 {
236 struct private_osx_write_data *powd = wn->private_data;
237
238 if (!powd)
239 return;
240 PARA_INFO_LOG("closing writer node %p\n", wn);
241 mutex_destroy(powd->mutex);
242 free(powd);
243 wn->private_data = NULL;
244 }
245
246 /* must be called with the mutex held */
247 static inline bool need_drain_delay(struct private_osx_write_data *powd)
248 {
249 if (!powd->playing)
250 return false;
251 return btr_get_input_queue_size(powd->callback_btrn) != 0;
252 }
253
254 static void osx_write_pre_select(struct sched *s, struct task *t)
255 {
256 struct writer_node *wn = container_of(t, struct writer_node, task);
257 struct private_osx_write_data *powd = wn->private_data;
258 int ret;
259 bool drain_delay_nec = false;
260
261 if (!powd) {
262 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
263 if (ret != 0)
264 sched_min_delay(s);
265 return;
266 }
267
268 mutex_lock(powd->mutex);
269 ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
270 if (ret < 0)
271 drain_delay_nec = need_drain_delay(powd);
272 mutex_unlock(powd->mutex);
273
274 if (drain_delay_nec)
275 return sched_request_timeout_ms(50, s);
276 if (ret != 0)
277 return sched_min_delay(s);
278 sched_request_timeout_ms(50, s);
279 }
280
281 static void osx_write_post_select(__a_unused struct sched *s, struct task *t)
282 {
283 struct writer_node *wn = container_of(t, struct writer_node, task);
284 struct private_osx_write_data *powd = wn->private_data;
285 struct btr_node *btrn = wn->btrn;
286 int ret;
287
288 if (!powd) {
289 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
290 if (ret == 0)
291 return;
292 if (ret < 0)
293 goto remove_btrn;
294 ret = core_audio_init(wn);
295 if (ret < 0)
296 goto remove_btrn;
297 powd = wn->private_data;
298 AudioOutputUnitStart(powd->audio_unit);
299 }
300 mutex_lock(powd->mutex);
301 btr_pushdown(btrn);
302 ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
303 if (ret < 0 && need_drain_delay(powd))
304 ret = 0;
305 mutex_unlock(powd->mutex);
306
307 if (ret >= 0)
308 goto out;
309 AudioOutputUnitStop(powd->audio_unit);
310 AudioUnitUninitialize(powd->audio_unit);
311 CloseComponent(powd->audio_unit);
312 btr_remove_node(powd->callback_btrn);
313 btr_free_node(powd->callback_btrn);
314 remove_btrn:
315 btr_remove_node(btrn);
316 PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
317 out:
318 t->error = ret;
319 }
320
321 /**
322 * The init function of the osx writer.
323 *
324 * \param w Filled in by the function.
325 */
326 void osx_write_init(struct writer *w)
327 {
328 struct osx_write_args_info dummy;
329
330 osx_cmdline_parser_init(&dummy);
331 w->close = osx_write_close;
332 w->pre_select = osx_write_pre_select;
333 w->post_select = osx_write_post_select;
334 w->parse_config_or_die = osx_write_parse_config_or_die;
335 w->free_config = osx_free_config;
336 w->shutdown = NULL; /* nothing to do */
337 w->help = (struct ggo_help) {
338 .short_help = osx_write_args_info_help,
339 .detailed_help = osx_write_args_info_detailed_help
340 };
341 osx_cmdline_parser_free(&dummy);
342 }