-/*
- * Copyright (C) 2006-2012 Andre Noll <maan@systemlinux.org>
- *
- * Licensed under the GPL v2. For licencing details see COPYING.
- */
-
-/** \file osx_write.c paraslash's output plugin for MacOs */
-
-/*
- * based on mosx-mpg123, by Guillaume Outters and Steven A. Kortze
- * <skortze@sourceforge.net>
- */
-
-#include <regex.h>
-#include <sys/types.h>
-
-#include "para.h"
-#include "fd.h"
-#include "string.h"
-#include "list.h"
-#include "sched.h"
-#include "ggo.h"
-#include "buffer_tree.h"
-#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>
-
-/** Data specific to the osx writer. */
-struct private_osx_write_data {
- /** The main CoreAudio handle. */
- AudioUnit audio_unit;
- /** 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. */
- unsigned channels;
- /** Serializes access to buffer tree nodes. */
- int mutex;
- /** The btr node of the callback. */
- struct btr_node *callback_btrn;
-};
-
-/* 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)
-{
- int i;
- struct writer_node *wn = cb_arg;
- struct private_osx_write_data *powd = wn->private_data;
- size_t samples_have, samples_want = 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;
- }
- powd->playing = true;
- }
-
- for (i = 0; i < abl->mNumberBuffers; i++) {
- /* what we have to fill */
- 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;
- }
-out:
- mutex_unlock(powd->mutex);
- return noErr;
-}
-
-static int core_audio_init(struct writer_node *wn)
-{
- struct private_osx_write_data *powd = para_calloc(sizeof(*powd));
- Component comp;
- int ret;
- 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;
-
- PARA_INFO_LOG("wn: %p\n", wn);
- ret = -E_DEFAULT_COMP;
- comp = FindNextComponent(NULL, &desc);
- if (!comp)
- goto e0;
- ret = -E_OPEN_COMP;
- if (OpenAComponent(comp, &powd->audio_unit))
- goto e0;
- ret = -E_UNIT_INIT;
- if (AudioUnitInitialize(powd->audio_unit))
- goto e1;
- get_btr_sample_rate(btrn, &val);
- powd->sample_rate = val;
- get_btr_channels(btrn, &val);
- powd->channels = val;
- get_btr_sample_format(btrn, &val);
- powd->sample_format = val;
- /*
- * Choose PCM format. We tell the Output Unit what format we're going
- * to supply data to it. This is necessary if you're providing data
- * through an input callback AND you want the DefaultOutputUnit to do
- * any format conversions necessary from your format to the device's
- * format.
- */
-
- format.mSampleRate = powd->sample_rate;
- 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.mBytesPerFrame = format.mBytesPerPacket;
-
- 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(format)))
- goto e2;
- ret = -E_ADD_CALLBACK;
- if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback,
- 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;
-e2:
- AudioUnitUninitialize(powd->audio_unit);
-e1:
- CloseComponent(powd->audio_unit);
-e0:
- free(powd);
- wn->private_data = NULL;
- return ret;
-}
-
-__malloc static void *osx_write_parse_config_or_die(const char *options)
-{
- struct osx_write_args_info *conf = para_calloc(sizeof(*conf));
-
- /* exits on errors */
- osx_cmdline_parser_string(options, conf, "osx_write");
- return conf;
-}
-
-static void osx_free_config(void *conf)
-{
- osx_cmdline_parser_free(conf);
-}
-
-static void osx_write_close(struct writer_node *wn)
-{
- struct private_osx_write_data *powd = wn->private_data;
-
- if (!powd)
- return;
- PARA_INFO_LOG("closing writer node %p\n", wn);
- mutex_destroy(powd->mutex);
- free(powd);
- wn->private_data = NULL;
-}
-
-/* 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;
- int ret;
- bool drain_delay_nec = false;
-
- if (!powd) {
- ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF);
- 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_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 btr_node *btrn = wn->btrn;
- int ret;
-
- 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.
- *
- * \param w Filled in by the function.
- */
-void osx_write_init(struct writer *w)
-{
- struct osx_write_args_info dummy;
-
- osx_cmdline_parser_init(&dummy);
- w->close = osx_write_close;
- w->pre_select = osx_write_pre_select;
- w->post_select = osx_write_post_select;
- w->parse_config_or_die = osx_write_parse_config_or_die;
- w->free_config = osx_free_config;
- w->help = (struct ggo_help) {
- .short_help = osx_write_args_info_help,
- .detailed_help = osx_write_args_info_detailed_help
- };
- osx_cmdline_parser_free(&dummy);
-}