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