/* * * ao_macosx.c * * Original Copyright (C) Timothy J. Wood - Aug 2000 * * This file is part of libao, a cross-platform library. See * README for a history of this source code. * * libao 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, or (at your option) * any later version. * * libao 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 GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * The MacOS X CoreAudio framework doesn't mesh as simply as some * simpler frameworks do. This is due to the fact that CoreAudio pulls * audio samples rather than having them pushed at it (which is nice * when you are wanting to do good buffering of audio). */ /* Change log: * * 14/5-2003: Ported to MPlayer libao2 by Dan Christiansen * * AC-3 and MPEG audio passthrough is possible, but I don't have * access to a sound card that supports it. */ #include #include #include #include #include #include #include #include #include "config.h" #include "mp_msg.h" #include "audio_out.h" #include "audio_out_internal.h" #include "libaf/af_format.h" static ao_info_t info = { "Darwin/Mac OS X native audio output", "macosx", "Timothy J. Wood & Dan Christiansen & Chris Roccati", "" }; LIBAO_EXTERN(macosx) /* Prefix for all mp_msg() calls */ #define ao_msg(a, b, c...) mp_msg(a, b, "AO: [macosx] " c) /* This is large, but best (maybe it should be even larger). * CoreAudio supposedly has an internal latency in the order of 2ms */ #define NUM_BUFS 32 typedef struct ao_macosx_s { /* AudioUnit */ AudioUnit theOutputUnit; int packetSize; int paused; /* Ring-buffer */ /* does not need explicit synchronization, but needs to allocate * (num_chunks + 1) * chunk_size memory to store num_chunks * chunk_size * data */ unsigned char *buffer; unsigned int buffer_len; ///< must always be (num_chunks + 1) * chunk_size unsigned int num_chunks; unsigned int chunk_size; unsigned int buf_read_pos; unsigned int buf_write_pos; } ao_macosx_t; static ao_macosx_t *ao = NULL; /** * \brief return number of free bytes in the buffer * may only be called by mplayer's thread * \return minimum number of free bytes in buffer, value may change between * two immediately following calls, and the real number of free bytes * might actually be larger! */ static int buf_free(void) { int free = ao->buf_read_pos - ao->buf_write_pos - ao->chunk_size; if (free < 0) free += ao->buffer_len; return free; } /** * \brief return number of buffered bytes * may only be called by playback thread * \return minimum number of buffered bytes, value may change between * two immediately following calls, and the real number of buffered bytes * might actually be larger! */ static int buf_used(void) { int used = ao->buf_write_pos - ao->buf_read_pos; if (used < 0) used += ao->buffer_len; return used; } /** * \brief add data to ringbuffer */ static int write_buffer(unsigned char* data, int len){ int first_len = ao->buffer_len - ao->buf_write_pos; int free = buf_free(); if (len > free) len = free; if (first_len > len) first_len = len; // till end of buffer memcpy (&ao->buffer[ao->buf_write_pos], data, first_len); if (len > first_len) { // we have to wrap around // remaining part from beginning of buffer memcpy (ao->buffer, &data[first_len], len - first_len); } ao->buf_write_pos = (ao->buf_write_pos + len) % ao->buffer_len; return len; } /** * \brief remove data from ringbuffer */ static int read_buffer(unsigned char* data,int len){ int first_len = ao->buffer_len - ao->buf_read_pos; int buffered = buf_used(); if (len > buffered) len = buffered; if (first_len > len) first_len = len; // till end of buffer memcpy (data, &ao->buffer[ao->buf_read_pos], first_len); if (len > first_len) { // we have to wrap around // remaining part from beginning of buffer memcpy (&data[first_len], ao->buffer, len - first_len); } ao->buf_read_pos = (ao->buf_read_pos + len) % ao->buffer_len; return len; } OSStatus theRenderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) { int amt=buf_used(); int req=(inNumFrames)*ao->packetSize; if(amt>req) amt=req; if(amt) read_buffer((unsigned char *)ioData->mBuffers[0].mData, amt); else audio_pause(); ioData->mBuffers[0].mDataByteSize = amt; return noErr; } static int control(int cmd,void *arg){ switch (cmd) { case AOCONTROL_SET_DEVICE: case AOCONTROL_GET_DEVICE: case AOCONTROL_GET_VOLUME: case AOCONTROL_SET_VOLUME: /* Everything is currently unimplemented */ return CONTROL_FALSE; default: return CONTROL_FALSE; } } static void print_format(const char* str,AudioStreamBasicDescription *f){ uint32_t flags=(uint32_t) f->mFormatFlags; ao_msg(MSGT_AO,MSGL_V, "%s %7.1fHz %dbit [%c%c%c%c] %s %s %s%s%s%s\n", str, f->mSampleRate, f->mBitsPerChannel, (int)(f->mFormatID & 0xff000000) >> 24, (int)(f->mFormatID & 0x00ff0000) >> 16, (int)(f->mFormatID & 0x0000ff00) >> 8, (int)(f->mFormatID & 0x000000ff) >> 0, (flags&kAudioFormatFlagIsFloat) ? "float" : "int", (flags&kAudioFormatFlagIsBigEndian) ? "BE" : "LE", (flags&kAudioFormatFlagIsSignedInteger) ? "S" : "U", (flags&kAudioFormatFlagIsPacked) ? " packed" : "", (flags&kAudioFormatFlagIsAlignedHigh) ? " aligned" : "", (flags&kAudioFormatFlagIsNonInterleaved) ? " ni" : "" ); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mBytesPerPacket\n", (int)f->mBytesPerPacket); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mFramesPerPacket\n", (int)f->mFramesPerPacket); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mBytesPerFrame\n", (int)f->mBytesPerFrame); ao_msg(MSGT_AO,MSGL_DBG2, "%5d mChannelsPerFrame\n", (int)f->mChannelsPerFrame); } static int init(int rate,int channels,int format,int flags) { AudioStreamBasicDescription inDesc, outDesc; ComponentDescription desc; Component comp; AURenderCallbackStruct renderCallback; OSStatus err; UInt32 size, maxFrames; int aoIsCreated = ao != NULL; if (!aoIsCreated) ao = malloc(sizeof(ao_macosx_t)); // Build Description for the input format inDesc.mSampleRate=rate; inDesc.mFormatID=kAudioFormatLinearPCM; inDesc.mChannelsPerFrame=channels; switch(format&AF_FORMAT_BITS_MASK){ case AF_FORMAT_8BIT: inDesc.mBitsPerChannel=8; break; case AF_FORMAT_16BIT: inDesc.mBitsPerChannel=16; break; case AF_FORMAT_24BIT: inDesc.mBitsPerChannel=24; break; case AF_FORMAT_32BIT: inDesc.mBitsPerChannel=32; break; default: ao_msg(MSGT_AO, MSGL_WARN, "Unsupported format (0x%08x)\n", format); return CONTROL_FALSE; break; } if((format&AF_FORMAT_POINT_MASK)==AF_FORMAT_F) { // float inDesc.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked; } else if((format&AF_FORMAT_SIGN_MASK)==AF_FORMAT_SI) { // signed int inDesc.mFormatFlags = kAudioFormatFlagIsSignedInteger|kAudioFormatFlagIsPacked; } else { // unsigned int inDesc.mFormatFlags = kAudioFormatFlagIsPacked; } if((format&AF_FORMAT_END_MASK)==AF_FORMAT_BE) inDesc.mFormatFlags |= kAudioFormatFlagIsBigEndian; inDesc.mFramesPerPacket = 1; ao->packetSize = inDesc.mBytesPerPacket = inDesc.mBytesPerFrame = inDesc.mFramesPerPacket*channels*(inDesc.mBitsPerChannel/8); print_format("source: ",&inDesc); if (!aoIsCreated) { desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; desc.componentManufacturer = kAudioUnitManufacturer_Apple; desc.componentFlags = 0; desc.componentFlagsMask = 0; comp = FindNextComponent(NULL, &desc); //Finds an component that meets the desc spec's if (comp == NULL) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to find Output Unit component\n"); return CONTROL_FALSE; } err = OpenAComponent(comp, &(ao->theOutputUnit)); //gains access to the services provided by the component if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to open Output Unit component (err=%d)\n", err); return CONTROL_FALSE; } // Initialize AudioUnit err = AudioUnitInitialize(ao->theOutputUnit); if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to initialize Output Unit component (err=%d)\n", err); return CONTROL_FALSE; } } size = sizeof(AudioStreamBasicDescription); err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &inDesc, size); if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the input format (err=%d)\n", err); return CONTROL_FALSE; } /*Initialized the device, set the charecteristics for the input stream, now examine the output stream to get bytes per frame */ size = sizeof(AudioStreamBasicDescription); err = AudioUnitGetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &outDesc, &size); if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to get the output format (err=%d)\n", err); return CONTROL_FALSE; } /* Now lets query the number of frames the output device has got on hold */ size = sizeof(UInt32); maxFrames = 0; err = AudioUnitGetProperty(ao->theOutputUnit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Input, 0, &maxFrames, &size); if (err) { ao_msg(MSGT_AO,MSGL_WARN, "AudioUnitGetProperty returned %d when getting kAudioDevicePropertyBufferFrameSize\n", (int)err); return CONTROL_FALSE; } /* Now combine the two to produce the chunk size */ ao->chunk_size = maxFrames * outDesc.mBytesPerFrame; //debug. Comment or erase this out later ao_msg(MSGT_AO,MSGL_WARN, "AudioUnitGetProperty succeeded. ao->chunk_size = %d maxFrames = %d outDesc.mBytesPerFrame = %d", (int)ao->chunk_size, (int)maxFrames, (int)outDesc.mBytesPerFrame); //debug ao_data.samplerate = inDesc.mSampleRate; ao_data.channels = inDesc.mChannelsPerFrame; ao_data.bps = ao_data.samplerate * inDesc.mBytesPerFrame; ao_data.outburst = ao->chunk_size; ao_data.buffersize = ao_data.bps; ao->num_chunks = (ao_data.bps+ao->chunk_size-1)/ao->chunk_size; ao->buffer_len = (ao->num_chunks + 1) * ao->chunk_size; ao->buffer = aoIsCreated ? realloc(ao->buffer,(ao->num_chunks + 1)*ao->chunk_size) : calloc(ao->num_chunks + 1, ao->chunk_size); ao_msg(MSGT_AO,MSGL_V, "using %5d chunks of %d bytes (buffer len %d bytes)\n", (int)ao->num_chunks, (int)ao->chunk_size, (int)ao->buffer_len); renderCallback.inputProc = theRenderProc; renderCallback.inputProcRefCon = 0; err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &renderCallback, sizeof(AURenderCallbackStruct)); if (err) { ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the render callback (err=%d)\n", err); return CONTROL_FALSE; } reset(); return CONTROL_OK; } static int play(void* output_samples,int num_bytes,int flags) { int wrote=write_buffer(output_samples, num_bytes); audio_resume(); return wrote; } /* set variables and buffer to initial state */ static void reset(void) { audio_pause(); /* reset ring-buffer state */ ao->buf_read_pos=0; ao->buf_write_pos=0; return; } /* return available space */ static int get_space(void) { return buf_free(); } /* return delay until audio is played */ static float get_delay(void) { int buffered = ao->buffer_len - ao->chunk_size - buf_free(); // could be less // inaccurate, should also contain the data buffered e.g. by the OS return (float)(buffered)/(float)ao_data.bps; } /* unload plugin and deregister from coreaudio */ static void uninit(int immed) { int i; OSErr status; if (!immed) { long long timeleft=(1000000LL*buf_used())/ao_data.bps; ao_msg(MSGT_AO,MSGL_DBG2, "%d bytes left @%d bps (%ld usec)\n", buf_used(), ao_data.bps, (int)timeleft); usec_sleep((int)timeleft); } AudioOutputUnitStop(ao->theOutputUnit); AudioUnitUninitialize(ao->theOutputUnit); CloseComponent(ao->theOutputUnit); free(ao->buffer); free(ao); ao = NULL; } /* stop playing, keep buffers (for pause) */ static void audio_pause(void) { OSErr status=noErr; /* stop callback */ status=AudioOutputUnitStop(ao->theOutputUnit); if (status) ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStop returned %d\n", (int)status); ao->paused=1; } /* resume playing, after audio_pause() */ static void audio_resume(void) { if(ao->paused) { OSErr status=noErr; /* start callback */ status=AudioOutputUnitStart(ao->theOutputUnit); if (status) ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStart returned %d\n", (int)status); ao->paused=0; } }