From 1e2990131a4c26662aa89df52f1ffeba922bf46d Mon Sep 17 00:00:00 2001 From: maan Date: Thu, 8 Jun 2006 02:39:47 +0200 Subject: [PATCH] first version of the osx writer taken from the osxplay thingy. Untested, but compiles. --- configure.ac | 25 ++++ error.h | 11 ++ osx_writer.c | 325 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 osx_writer.c diff --git a/configure.ac b/configure.ac index 3c2f4ec1..b50665aa 100644 --- a/configure.ac +++ b/configure.ac @@ -149,6 +149,31 @@ if test ${have_ucred} = yes; then AC_DEFINE(HAVE_UCRED, 1, define to 1 you have struct ucred) fi +########################################################################### osx + +AC_MSG_CHECKING(for CoreAudio (MacOs)) +AC_TRY_LINK([ + #include +],[ + AudioDeviceID id; +],[have_core_audio=yes],[have_core_audio=no]) +AC_MSG_RESULT($have_core_audio) +if test ${have_core_audio} = yes; then + f1="-framework CoreAudio" + f2="-framework AudioToolbox" + f3="-framework AudioUnit" + f4="-framework CoreServices" + f="$f1 $f2 $f3 $f4" + audiod_errlist_objs="$audiod_errlist_objs osx_writer" + audiod_cmdline_objs="$audiod_cmdline_objs osx_write.cmdline" + audiod_ldflags="$audiod_ldflags $f" + + write_errlist_objs="$write_errlist_objs osx_writer" + write_cmdline_objs="$write_cmdline_objs osx_write.cmdline" + write_ldflags="$write_ldflags $f" + write_writers="$write_writers osx" + AC_DEFINE(HAVE_CORE_AUDIO, 1, define to 1 on MacOs) +fi ########################################################################### gtk2 pkg_modules="gtk+-2.0 >= 2.0.0" diff --git a/error.h b/error.h index 983f653b..477baadf 100644 --- a/error.h +++ b/error.h @@ -71,6 +71,7 @@ enum para_subsystem { SS_WRITE_COMMON, SS_ALSA_WRITER, SS_FILE_WRITER, + SS_OSX_WRITER, NUM_SS }; @@ -90,6 +91,15 @@ enum para_subsystem { extern const char **para_errlist[]; /** \endcond */ +#define OSX_WRITER_ERRORS \ + PARA_ERROR(STREAM_FORMAT, "could not set stream format"), \ + PARA_ERROR(ADD_CALLBACK, "can not add callback"), \ + PARA_ERROR(READ_STDIN, "failed to read from stdin"), \ + PARA_ERROR(OPEN_COMP, "OpenAComponent() error"), \ + PARA_ERROR(UNIT_INIT, "AudioUnitInitialize() error"), \ + PARA_ERROR(DEFAULT_COMP, "can not find default audio output component"), \ + + #define CLIENT_ERRORS \ PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \ PARA_ERROR(INVALID_CHALLENGE, "did not receive valid challenge"), \ @@ -536,6 +546,7 @@ SS_ENUM(WRITE); SS_ENUM(WRITE_COMMON); SS_ENUM(ALSA_WRITER); SS_ENUM(FILE_WRITER); +SS_ENUM(OSX_WRITER); SS_ENUM(RINGBUFFER); SS_ENUM(CLIENT); /** \endcond */ diff --git a/osx_writer.c b/osx_writer.c new file mode 100644 index 00000000..e7fecc82 --- /dev/null +++ b/osx_writer.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2006 Andre Noll + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ + +/** \file osx_writer.c paraslash's output plugin for MacOs */ + +#include +#include "para.h" +#include "fd.h" +#include "string.h" +#include "list.h" +#include "sched.h" +#include "write.h" +#include "osx_write.cmdline.h" +#include "error.h" + + +#include +#include +#include +#include +struct osx_buffer { + float *buffer; + long size; + float *ptr; /* Where in the buffer are we? */ + long remaining; + struct osx_buffer *next; +}; +typedef struct osx_buffer osx_buffer; + +struct private_osx_writer_data { + long size; + short *ptr; + AudioUnit output; + char play; + sem_t *semaphore; + osx_buffer *from; /* Current buffers */ + osx_buffer *to; +}; + + +/* + * Tried with 3 buffers, but then any little window move is sufficient to + * stop the sound (OK, on a G3 400 with a Public Beta. Perhaps now we can + * go down to 2 buffers). With 16 buffers we have 1.5 seconds music + * buffered (or, if you're pessimistic, 1.5 seconds latency). Note 0 + * buffers don't work much further than the Bus error. + */ +#define NUMBER_BUFFERS 2 + +static void destroy_buffers(struct private_osx_writer_data *powd) +{ + osx_buffer *ptr; + osx_buffer *ptr2; + ptr = powd->to->next; + powd->to->next = NULL; + while (ptr) { + ptr2 = ptr->next; + if (ptr->buffer) + free(ptr->buffer); + free(ptr); + ptr = ptr2; + } +} + +static void init_buffers(struct private_osx_writer_data *powd) +{ + int i; + + osx_buffer ** ptrptr; + ptrptr = &powd->to; + for (i = 0; i < NUMBER_BUFFERS; i++) { + *ptrptr = malloc(sizeof(osx_buffer)); + (*ptrptr)->size = 0; + (*ptrptr)->remaining = 0; + (*ptrptr)->buffer = NULL; + ptrptr = &(*ptrptr)->next; + /* This buffer is ready for filling (of course, it is empty!) */ + sem_post(powd->semaphore); + } + *ptrptr = powd->from = powd->to; +} + +static void fill_buffer(osx_buffer *b, short *source, long size) +{ + float *dest; + + PARA_INFO_LOG("%ld\n", size); + if (b->remaining) /* Non empty buffer, must still be playing */ + return; + if (b->size != size) { + /* + * Hey! What's that? Coudn't this buffer size be fixed + * once (well, perhaps we just didn't allocate it yet) + */ + if (b->buffer) + free(b->buffer); + b->buffer = malloc(size * sizeof(float)); + b->size = size; + } + dest = b->buffer; + while (size--) + /* *dest++ = ((*source++) + 32768) / 65536.0; */ + *dest++ = (*source++) / 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_writer_data *powd = inClientData; + +// PARA_INFO_LOG("%p\n", powd); + for (i = 0; i < outOutputData->mNumberBuffers; ++i) { + /* what we have to fill */ + m = outOutputData->mBuffers[i].mDataByteSize / sizeof(float); + dest = outOutputData->mBuffers[i].mData; + while (m > 0) { + if ((n = powd->from->remaining) <= 0) { + /* no more bytes in the current read buffer! */ + while ((n = powd->from->remaining) <= 0) + /* wait for the results */ + usleep(2000); + } + /* + * 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) { + /* tell that there's a buffer to fill */ + sem_post(powd->semaphore); + powd->from = powd->from->next; + } + } + } + return 0; +} + +static int osx_writer_open(struct writer_node *wn) +{ + struct private_osx_writer_data *powd = para_calloc( + sizeof(struct private_osx_writer_data)); + ComponentDescription desc; + Component comp; + AURenderCallbackStruct inputCallback = {osx_callback, powd}; + AudioStreamBasicDescription format; + char s[10]; + int m, ret; + + wn->private_data = powd; + /* 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; + ret = -E_DEFAULT_COMP; + comp = FindNextComponent(NULL, &desc); + if (!comp) + goto e0; + ret = -E_OPEN_COMP; + if (OpenAComponent(comp, &powd->output)) + goto e0; + ret = -E_UNIT_INIT; + if (AudioUnitInitialize(powd->output)) + goto e1; + powd->size = 0; + powd->ptr = NULL; + powd->play = 0; + /* Hmmm, let's 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 = 44100.0; /* The sample rate of the audio stream */ + /* The specific encoding type of audio stream*/ + format.mFormatID = kAudioFormatLinearPCM; + /* flags specific to each format */ + format.mFormatFlags = kLinearPCMFormatFlagIsFloat + | kLinearPCMFormatFlagIsBigEndian + | kLinearPCMFormatFlagIsPacked; + /* + * We produce 2-channel audio. Now if we have a mega-super-hyper card for our + * audio, it is its problem to convert it to 8-, 16-, 32- or 1024-channel data. + */ + format.mBytesPerFrame = (format.mFramesPerPacket = 1) + * (format.mBytesPerPacket = (format.mChannelsPerFrame = 2) * sizeof(float)); + /* one of the most constant constants of the whole computer history */ + format.mBitsPerChannel = sizeof(float) * 8; + ret = -E_STREAM_FORMAT; + if (AudioUnitSetProperty(powd->output, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, &format, + sizeof(AudioStreamBasicDescription))) + goto e2; + /* init the semaphore */ + strcpy(s, "/mpg123-0000"); + do { + for (m = 10;; m--) + if( (s[m]++) <= '9') + break; + else + s[m] = '0'; + } while ((powd->semaphore = sem_open(s, O_CREAT | O_EXCL, 0644, 0)) + == (sem_t *)SEM_FAILED); + init_buffers(powd); + ret = -E_ADD_CALLBACK; + if (AudioUnitSetProperty(powd->output, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, &inputCallback, + sizeof(inputCallback)) < 0) + goto e3; + return 0; +e3: + destroy_buffers(powd); +e2: + AudioUnitUninitialize(powd->output); +e1: + CloseComponent(powd->output); +e0: + return ret; +} + +__malloc void *osx_write_parse_config(char *options) +{ + struct osx_write_args_info *conf + = para_calloc(sizeof(struct osx_write_args_info)); + PARA_INFO_LOG("options: %s\n", options); + int ret = osx_cmdline_parser_string(options, conf, "osx_write"); + if (ret) + goto err_out; + return conf; +err_out: + free(conf); + return NULL; + +} + +static void osx_writer_close(struct writer_node *wn) +{ + struct private_osx_writer_data *powd = wn->private_data; + + PARA_INFO_LOG("closing writer node %p\n", wn); + AudioOutputUnitStop(powd->output); + AudioUnitUninitialize(powd->output); + CloseComponent(powd->output); + destroy_buffers(powd); + sem_close(powd->semaphore); + free(powd); +} + +static int osx_write_post_select(__a_unused struct sched *s, + struct writer_node *wn) +{ + struct private_osx_writer_data *powd = wn->private_data; + struct writer_node_group *wng = wn->wng; + short *data = (short*)wng->buf + wn->written; + + if (!*wng->loaded) + return 1; + if (powd->to->remaining) /* Non empty buffer, must still be playing */ + return 1; + fill_buffer(powd->to, data, (*wng->loaded - wn->written) / sizeof(short)); + powd->to = powd->to->next; + wn->written += (*wng->loaded - wn->written); + if (!powd->play) { + if (AudioOutputUnitStart(powd->output)) + return -1; + powd->play = 1; + } + return 1; +} + +static int osx_write_pre_select(struct sched *s, struct writer_node *wn) +{ + struct writer_node_group *wng = wn->wng; + struct private_osx_writer_data *powd = wn->private_data; + +// if (!*wng->loaded) +// return 1; +// if (powd->to->remaining) /* Non empty buffer, must still be playing */ +// return 1; + s->timeout.tv_sec = 0; + s->timeout.tv_usec = 20; + return 1; +} + +void osx_writer_init(struct writer *w) +{ + w->open = osx_writer_open; + w->close = osx_writer_close; + w->pre_select = osx_write_pre_select; + w->post_select = osx_write_post_select; + w->parse_config = osx_write_parse_config; + w->shutdown = NULL; /* nothing to do */ +} -- 2.39.5