Rewrite of the osx writer.
authorAndre Noll <maan@systemlinux.org>
Thu, 3 Feb 2011 16:15:39 +0000 (17:15 +0100)
committerAndre Noll <maan@systemlinux.org>
Thu, 7 Jul 2011 15:28:47 +0000 (17:28 +0200)
This replaces most of the code taken from mosx-mpg123 and should
make osx_write.c much more readable, shorter and more reliable.

Since coreaudio creates a new thread which periodically calls our
callback function, some kind of coordination between the two threads
of execution is necessary.

This implementation employs a new buffer tree node and a mutex to
serialize access to the data buffers.  The parent thread pushes
down all data to the child thread which consumes this data in the
callback function.  Both threads grab the new mutex whenever they
access or modify the data buffers.

configure.ac
osx_write.c

index 8904281..ed1d710 100644 (file)
@@ -423,11 +423,11 @@ if test ${have_core_audio} = yes; then
        f="$f1 $f2 $f3 $f4"
 
        all_errlist_objs="$all_errlist_objs osx_write"
-       audiod_errlist_objs="$audiod_errlist_objs osx_write"
+       audiod_errlist_objs="$audiod_errlist_objs osx_write ipc"
        audiod_cmdline_objs="$audiod_cmdline_objs osx_write.cmdline"
        audiod_ldflags="$audiod_ldflags $f"
 
-       write_errlist_objs="$write_errlist_objs osx_write"
+       write_errlist_objs="$write_errlist_objs osx_write ipc"
        write_cmdline_objs="$write_cmdline_objs osx_write.cmdline"
        write_ldflags="$write_ldflags $f"
        writers="$writers osx"
index 50f22be..429c713 100644 (file)
 #include "write.h"
 #include "write_common.h"
 #include "osx_write.cmdline.h"
+#include "ipc.h"
 #include "error.h"
 
 #include <CoreServices/CoreServices.h>
 #include <AudioUnit/AudioUnit.h>
 #include <AudioToolbox/AudioToolbox.h>
 
-/** describes one input buffer for the osx writer */
-struct osx_buffer {
-       /** pointer to the beginning of the buffer */
-       float *buffer;
-       /** the size of this buffer */
-       long size;
-       /** current position in the buffer */
-       float *ptr;
-       /** number of floats not yet consuned */
-       long remaining;
-       /** pointer to the next audio buffer */
-       struct osx_buffer *next;
-};
-
-/** data specific to the osx writer */
+/** Data specific to the osx writer. */
 struct private_osx_write_data {
-       /** the main control structure for audio data manipulation */
+       /** The main CoreAudio handle. */
        AudioUnit audio_unit;
-       /** non-zero if playback has started */
-       char play;
-       /** callback reads audio data from this buffer */
-       struct osx_buffer *from;
-       /** the post_select writes audio data here */
-       struct osx_buffer *to;
-       /** sample rate of the current audio stream */
+       /** True if we wrote some audio data. */
+       bool playing;
+       /** Sample rate of the current audio stream. */
        unsigned sample_rate;
        /** Sample format of the current audio stream */
        unsigned sample_format;
-       /** number of channels of the current audio stream */
+       /** Number of channels of the current audio stream. */
        unsigned channels;
+       /** Serializes access to buffer tree nodes. */
+       int mutex;
+       /** The btr node of the callback. */
+       struct btr_node *callback_btrn;
 };
 
-static void destroy_buffers(struct private_osx_write_data *powd)
-{
-       struct osx_buffer *ptr;
-       struct osx_buffer *ptr2;
-       ptr = powd->to->next;
-       powd->to->next = NULL;
-       while (ptr) {
-               ptr2 = ptr->next;
-               free(ptr->buffer);
-               free(ptr);
-               ptr = ptr2;
-       }
-}
-
-static void init_buffers(struct writer_node *wn)
+/* This function writes the address and the number of bytes to one end of the socket.
+ * The post_select() function then fills the buffer and notifies the callback also
+ * through the socket.
+ */
+static OSStatus osx_callback(void *cb_arg, __a_unused AudioUnitRenderActionFlags *af,
+               __a_unused const AudioTimeStamp *ts, __a_unused  UInt32 bus_number,
+               __a_unused UInt32 num_frames, AudioBufferList *abl)
 {
-       struct private_osx_write_data *powd = wn->private_data;
-       struct osx_write_args_info *conf = wn->conf;
-       struct osx_buffer **ptrptr;
        int i;
+       struct writer_node *wn = cb_arg;
+       struct private_osx_write_data *powd = wn->private_data;
+       size_t samples_have, samples_want = 0;
 
-       ptrptr = &powd->to;
-       for (i = 0; i < conf->numbuffers_arg; i++) {
-               *ptrptr = para_malloc(sizeof(struct osx_buffer));
-               (*ptrptr)->size = 0;
-               (*ptrptr)->remaining = 0;
-               (*ptrptr)->buffer = NULL;
-               ptrptr = &(*ptrptr)->next;
-       }
-       *ptrptr = powd->from = powd->to;
-}
-
-static void fill_buffer(struct private_osx_write_data *powd, char *data, long bytes)
-{
-       struct osx_buffer *b = powd->to;
-       float *dest;
-       long samples;
-       enum sample_format sf = powd->sample_format;
-
-       samples = (sf == SF_S8 || sf == SF_U8)? bytes : bytes / 2;
-       assert(b->remaining == 0 || samples > 0);
-       if (b->size != samples) {
-               b->buffer = para_realloc(b->buffer, samples * sizeof(float));
-               b->size = samples;
-       }
-       dest = b->buffer;
-       switch (powd->sample_format) {
-       case SF_U8: {
-               uint8_t *src = (uint8_t *)data;
-               while (samples--) {
-                       *dest++ = (*src++) / 256.0;
+       mutex_lock(powd->mutex);
+       /*
+        * We fill with zeros if no data was yet written and we do not have
+        * enough to fill all buffers.
+        */
+       if (!powd->playing) {
+               size_t want = 0, have =
+                       btr_get_input_queue_size(powd->callback_btrn);
+               for (i = 0; i < abl->mNumberBuffers; i++)
+                       want += abl->mBuffers[i].mDataByteSize;
+               if (have < want) {
+                       PARA_DEBUG_LOG("deferring playback (have = %zu < %zu = want)\n",
+                               have, want);
+                       for (i = 0; i < abl->mNumberBuffers; i++)
+                               memset(abl->mBuffers[i].mData, 0,
+                                       abl->mBuffers[i].mDataByteSize);
+                       goto out;
                }
-               break;
+               powd->playing = true;
        }
-       case SF_S8: {
-               int8_t *src = (int8_t *)data;
-               while (samples--) {
-                       *dest++ = ((*src++) + 128) / 256.0;
-               }
-               break;
-       }
-       default: {
-               short *src = (short *)data;
-               while (samples--)
-                       *dest++ = (*src++) / 32768.0;
-       }
-       }
-       b->ptr = b->buffer;
-       b->remaining = b->size;
-}
-
-static OSStatus osx_callback(void * inClientData,
-       __a_unused AudioUnitRenderActionFlags *inActionFlags,
-       __a_unused const AudioTimeStamp *inTimeStamp,
-       __a_unused  UInt32 inBusNumber,
-       __a_unused UInt32 inNumFrames,
-       AudioBufferList *outOutputData)
-{
-       long m, n;
-       float *dest;
-       int i;
-       struct private_osx_write_data *powd = inClientData;
 
-//     PARA_INFO_LOG("%p\n", powd);
-       for (i = 0; i < outOutputData->mNumberBuffers; ++i) {
+       for (i = 0; i < abl->mNumberBuffers; i++) {
                /* what we have to fill */
-               m = outOutputData->mBuffers[i].mDataByteSize / sizeof(float);
-               dest = outOutputData->mBuffers[i].mData;
-               while (m > 0) {
-                       n = powd->from->remaining;
-                       if (n <= 0) {
-                               n = powd->from->next->remaining;
-                               if (n <= 0) {
-                                       PARA_INFO_LOG("buffer underrun\n");
-                                       return 0;
-                               }
-                               powd->from = powd->from->next;
-                       }
-//                     PARA_INFO_LOG("buf %p: n = %ld, m= %ld\n", powd->from->buffer, n, m);
-                       /*
-                        * we dump what we can. In fact, just the necessary
-                        * should be sufficient
-                        */
-                       if (n > m)
-                               n = m;
-                       memcpy(dest, powd->from->ptr, n * sizeof(float));
-                       dest += n;
-                       /* remember all done work */
-                       m -= n;
-                       powd->from->ptr += n;
-                       if ((powd->from->remaining -= n) <= 0)
-                               powd->from = powd->from->next;
+               void *dest = abl->mBuffers[i].mData;
+               size_t sz = abl->mBuffers[i].mDataByteSize, samples, bytes;
+
+               samples_want = sz / wn->min_iqs;
+               while (samples_want > 0) {
+                       char *buf;
+                       btr_merge(powd->callback_btrn, wn->min_iqs);
+                       samples_have = btr_next_buffer(powd->callback_btrn, &buf) / wn->min_iqs;
+                       //PARA_INFO_LOG("i: %d want %zu samples to addr %p, have: %zu\n", i, samples_want,
+                       //      dest, samples_have);
+                       samples = PARA_MIN(samples_have, samples_want);
+                       if (samples == 0)
+                               break;
+                       bytes = samples * wn->min_iqs;
+                       memcpy(dest, buf, bytes);
+                       btr_consume(powd->callback_btrn, bytes);
+                       samples_want -= samples;
+                       dest += bytes;
                }
+               if (samples_want == 0)
+                       continue;
+               if (btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF) >= 0)
+                       PARA_INFO_LOG("zero-padding (%zu samples)\n",
+                               samples_want);
+               memset(dest, 0, samples_want * wn->min_iqs);
+               break;
        }
-       return 0;
+out:
+       mutex_unlock(powd->mutex);
+       return noErr;
 }
 
-#ifdef WORDS_BIGENDIAN /* ppc */
-#define ENDIAN_FLAGS kLinearPCMFormatFlagIsBigEndian
-#else
-#define ENDIAN_FLAGS 0
-#endif
-
 static int core_audio_init(struct writer_node *wn)
 {
        struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
-       ComponentDescription desc;
        Component comp;
-       AURenderCallbackStruct inputCallback = {osx_callback, powd};
-       AudioStreamBasicDescription format;
        int ret;
-       struct btr_node *btrn = wn->btrn;
        int32_t val;
+       AURenderCallbackStruct input_callback;
+       ComponentDescription desc = {
+               .componentType = kAudioUnitType_Output,
+               .componentSubType = kAudioUnitSubType_DefaultOutput,
+               .componentManufacturer = kAudioUnitManufacturer_Apple,
+       };
+       AudioStreamBasicDescription format = {
+               .mFormatID = kAudioFormatLinearPCM,
+               .mFramesPerPacket = 1,
+       };
+       struct btr_node *btrn = wn->btrn;
+       struct btr_node_description bnd;
 
-       wn->private_data = powd;
-       init_buffers(wn);
-       /* where did that default audio output go? */
-       desc.componentType = kAudioUnitType_Output;
-       desc.componentSubType = kAudioUnitSubType_DefaultOutput;
-       /* NOTE: and if default output isn't Apple? */
-       desc.componentManufacturer = kAudioUnitManufacturer_Apple;
-       desc.componentFlags = 0;
-       desc.componentFlagsMask = 0;
+       PARA_INFO_LOG("wn: %p\n", wn);
        ret = -E_DEFAULT_COMP;
        comp = FindNextComponent(NULL, &desc);
        if (!comp)
@@ -216,7 +148,6 @@ static int core_audio_init(struct writer_node *wn)
        ret = -E_UNIT_INIT;
        if (AudioUnitInitialize(powd->audio_unit))
                goto e1;
-       powd->play = 0;
        get_btr_sample_rate(btrn, &val);
        powd->sample_rate = val;
        get_btr_channels(btrn, &val);
@@ -230,39 +161,53 @@ static int core_audio_init(struct writer_node *wn)
         * any format conversions necessary from your format to the device's
         * format.
         */
-       format.mFormatID = kAudioFormatLinearPCM;
-       format.mFramesPerPacket = 1;
+
        format.mSampleRate = powd->sample_rate;
-       /* flags specific to each format */
-       format.mFormatFlags = kLinearPCMFormatFlagIsFloat
-               | kLinearPCMFormatFlagIsPacked
-               | ENDIAN_FLAGS;
+       format.mChannelsPerFrame = powd->channels;
+
        switch (powd->sample_format) {
        case SF_S8:
        case SF_U8:
                wn->min_iqs = powd->channels;
+               format.mBitsPerChannel = 8;
+               format.mBytesPerPacket = powd->channels;
+               format.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
                break;
        default:
                wn->min_iqs = powd->channels * 2;
+               format.mBytesPerPacket = powd->channels * 2;
+               format.mBitsPerChannel = 16;
+               format.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
        }
-       format.mBitsPerChannel = 8 * sizeof(float);
-       format.mBytesPerPacket = powd->channels * sizeof(float);
        format.mBytesPerFrame = format.mBytesPerPacket;
-       format.mChannelsPerFrame = powd->channels;
 
+       if (powd->sample_format == SF_S16_BE || powd->sample_format == SF_U16_BE)
+               format.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian;
+
+       input_callback = (AURenderCallbackStruct){osx_callback, wn};
        ret = -E_STREAM_FORMAT;
        if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat,
-                       kAudioUnitScope_Input, 0, &format,
-                       sizeof(AudioStreamBasicDescription)))
+                       kAudioUnitScope_Input, 0, &format, sizeof(format)))
                goto e2;
        ret = -E_ADD_CALLBACK;
        if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
-                       kAudioUnitScope_Input, 0, &inputCallback,
-                       sizeof(inputCallback)) < 0)
-               goto e3;
+                       kAudioUnitScope_Input, 0, &input_callback,
+                       sizeof(input_callback)) < 0)
+               goto e2;
+
+       ret = mutex_new();
+       if (ret < 0)
+               goto e2;
+       powd->mutex = ret;
+       /* set up callback btr node */
+       bnd.name = "cb_node";
+       bnd.parent = btrn;
+       bnd.child = NULL;
+       bnd.handler = NULL;
+       bnd.context = powd;
+       powd->callback_btrn = btr_new_node(&bnd);
+       wn->private_data = powd;
        return 1;
-e3:
-       destroy_buffers(powd);
 e2:
        AudioUnitUninitialize(powd->audio_unit);
 e1:
@@ -294,73 +239,91 @@ static void osx_write_close(struct writer_node *wn)
        if (!powd)
                return;
        PARA_INFO_LOG("closing writer node %p\n", wn);
-       AudioOutputUnitStop(powd->audio_unit);
-       AudioUnitUninitialize(powd->audio_unit);
-       CloseComponent(powd->audio_unit);
-       destroy_buffers(powd);
+       mutex_destroy(powd->mutex);
        free(powd);
+       wn->private_data = NULL;
 }
 
-static void osx_write_post_select(__a_unused struct sched *s, struct task *t)
+/* must be called with the mutex held */
+static inline bool need_drain_delay(struct private_osx_write_data *powd)
+{
+       if (!powd->playing)
+               return false;
+       return btr_get_input_queue_size(powd->callback_btrn) != 0;
+}
+
+static void osx_write_pre_select(struct sched *s, struct task *t)
 {
        struct writer_node *wn = container_of(t, struct writer_node, task);
        struct private_osx_write_data *powd = wn->private_data;
-       struct btr_node *btrn = wn->btrn;
-       char *data;
-       size_t bytes;
-       int ret = 0;
+       int ret;
+       bool drain_delay_nec = false;
 
-       while (!powd || powd->to->remaining <= 0) {
+       if (!powd) {
                ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
-               if (ret <= 0)
-                       break;
-               if (!powd) {
-                       ret = core_audio_init(wn);
-                       if (ret < 0)
-                               break;
-                       powd = wn->private_data;
-               }
-               btr_merge(btrn, 8192);
-               bytes = btr_next_buffer(btrn, &data);
-               //PARA_CRIT_LOG("have: %zu\n", bytes);
-               fill_buffer(powd, data, bytes);
-               btr_consume(btrn, bytes);
-               if (!powd->play) {
-                       ret = -E_UNIT_START;
-                       if (AudioOutputUnitStart(powd->audio_unit))
-                               break;
-                       powd->play = 1;
-               }
-               powd->to = powd->to->next;
-       }
-       if (ret < 0 && (!powd || powd->from->remaining <= 0)) {
-               btr_remove_node(btrn);
-               t->error = ret;
+               if (ret != 0)
+                       sched_min_delay(s);
+               return;
        }
+
+       mutex_lock(powd->mutex);
+       ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
+       if (ret < 0)
+               drain_delay_nec = need_drain_delay(powd);
+       mutex_unlock(powd->mutex);
+
+       if (drain_delay_nec)
+               return sched_request_timeout_ms(50, s);
+       if (ret != 0)
+               return sched_min_delay(s);
+       sched_request_timeout_ms(50, s);
 }
 
-static void osx_write_pre_select(struct sched *s, struct task *t)
+static void osx_write_post_select(__a_unused struct sched *s, struct task *t)
 {
        struct writer_node *wn = container_of(t, struct writer_node, task);
        struct private_osx_write_data *powd = wn->private_data;
-       struct timeval tmp = {.tv_sec = 1, .tv_usec = 0}, delay = tmp;
-       unsigned long factor;
-       size_t numbytes;
-       int ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
+       struct btr_node *btrn = wn->btrn;
+       int ret;
 
-       if (ret == 0)
-               return;
-       if (ret < 0 || !powd)
-               return sched_min_delay(s);
-       assert(powd->sample_rate > 0);
-       assert(wn->min_iqs > 0);
-       numbytes = powd->to->remaining * sizeof(short);
-       factor = numbytes / powd->sample_rate / wn->min_iqs;
-       tv_scale(factor, &tmp, &delay);
-       sched_request_timeout(&delay, s);
+       if (!powd) {
+               ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+               if (ret == 0)
+                       return;
+               if (ret < 0)
+                       goto remove_btrn;
+               ret = core_audio_init(wn);
+               if (ret < 0)
+                       goto remove_btrn;
+               powd = wn->private_data;
+               AudioOutputUnitStart(powd->audio_unit);
+       }
+       mutex_lock(powd->mutex);
+       btr_pushdown(btrn);
+       ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF);
+       if (ret < 0 && need_drain_delay(powd))
+               ret = 0;
+       mutex_unlock(powd->mutex);
+
+       if (ret >= 0)
+               goto out;
+       AudioOutputUnitStop(powd->audio_unit);
+       AudioUnitUninitialize(powd->audio_unit);
+       CloseComponent(powd->audio_unit);
+       btr_remove_node(powd->callback_btrn);
+       btr_free_node(powd->callback_btrn);
+remove_btrn:
+       btr_remove_node(btrn);
+       PARA_NOTICE_LOG("%s\n", para_strerror(-ret));
+out:
+       t->error = ret;
 }
 
-/** the init function of the osx writer */
+/**
+ * The init function of the osx writer.
+ *
+ * \param w Filled in by the function.
+ */
 void osx_write_init(struct writer *w)
 {
        struct osx_write_args_info dummy;