From f7169a98e8e3c7fffce4be4d3b392a6f75bea0c3 Mon Sep 17 00:00:00 2001 From: nplourde Date: Sun, 5 Jun 2005 14:47:26 +0000 Subject: Mac OS X Audio with AudioUnits and AudioToolbox format converters, Patch by Chris Roccati +#include +#include +#include #include #include #include @@ -55,7 +57,7 @@ static ao_info_t info = { "Darwin/Mac OS X native audio output", "macosx", - "Timothy J. Wood & Dan Christiansen", + "Timothy J. Wood & Dan Christiansen & Chris Roccati", "" }; @@ -70,15 +72,17 @@ LIBAO_EXTERN(macosx) typedef struct ao_macosx_s { - /* CoreAudio */ - AudioDeviceID outputDeviceID; - AudioStreamBasicDescription outputStreamBasicDescription; + /* AudioUnit */ + AudioUnit theOutputUnit; + AudioConverterRef theConverter; + int packetSize; /* 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 char *chunk; unsigned int buffer_len; ///< must always be (num_chunks + 1) * chunk_size unsigned int num_chunks; unsigned int chunk_size; @@ -153,55 +157,61 @@ static int read_buffer(unsigned char* data,int len){ /* end ring buffer stuff */ -/* The function that the CoreAudio thread calls when it wants more data */ -static OSStatus audioDeviceIOProc(AudioDeviceID inDevice, const AudioTimeStamp *inNow, const AudioBufferList *inInputData, const AudioTimeStamp *inInputTime, AudioBufferList *outOutputData, const AudioTimeStamp *inOutputTime, void *inClientData) +OSStatus ACComplexInputProc(AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) { - outOutputData->mBuffers[0].mDataByteSize = - read_buffer((char *)outOutputData->mBuffers[0].mData, ao->chunk_size); +int amt=buf_used(); +int req=(*ioNumberDataPackets)*ao->packetSize; - return 0; + + ioData->mBuffers[0].mData = ao->chunk; + ioData->mBuffers[0].mDataByteSize = req; + +// fprintf(stderr, "##### req=%d amt=%d #####\n", req, amt); + + if(amt>req) + amt=req; + + if(amt) + read_buffer((unsigned char *)ioData->mBuffers[0].mData, amt); + + if(req-amt) + memset(ioData->mBuffers[0].mData+amt, 0, req-amt); + + return noErr; } +OSStatus theRenderProc(void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumFrames, AudioBufferList *ioData) +{ +OSStatus err = noErr; +void *inInputDataProcUserData = NULL; +AudioStreamPacketDescription *outPacketDescription = NULL; + + + err = AudioConverterFillComplexBuffer(ao->theConverter, ACComplexInputProc, inInputDataProcUserData, &inNumFrames, ioData, outPacketDescription); + + /*Parameters for AudioConverterFillComplexBuffer() + converter - the converter being used + ACComplexInputProc() - input procedure to supply data to the Audio Converter + inInputDataProcUserData - Used to hold any data that needs to be passed on. + inNumFrames - The amount of requested data. On output, this number is the amount actually received. + ioData - Buffer of the converted data recieved on return + outPacketDescription - contains the format of the returned data. + */ + + if(err) + ao_msg(MSGT_AO, MSGL_WARN, "AudioConverterFillComplexBuffer failed status %-8d\n", err); + + return err; +} static int control(int cmd,void *arg){ - OSStatus status; - UInt32 propertySize; - ao_control_vol_t* vol = (ao_control_vol_t*)arg; - UInt32 stereoChannels[2]; - static float volume=0.5; switch (cmd) { case AOCONTROL_SET_DEVICE: case AOCONTROL_GET_DEVICE: - /* unimplemented/meaningless */ - return CONTROL_FALSE; case AOCONTROL_GET_VOLUME: - propertySize=sizeof(stereoChannels); - status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, - kAudioDevicePropertyPreferredChannelsForStereo, &propertySize, - &stereoChannels); -// printf("OSX: stereochannels %d ; %d \n",stereoChannels[0],stereoChannels[1]); - propertySize=sizeof(volume); - status = AudioDeviceGetProperty(ao->outputDeviceID, stereoChannels[0], false, kAudioDevicePropertyVolumeScalar, &propertySize, &volume); -// printf("OSX: get volume=%5.3f status=%d \n",volume,status); - vol->left=(int)(volume*100.0); - status = AudioDeviceGetProperty(ao->outputDeviceID, stereoChannels[1], false, kAudioDevicePropertyVolumeScalar, &propertySize, &volume); - vol->right=(int)(volume*100.0); - return CONTROL_TRUE; case AOCONTROL_SET_VOLUME: - propertySize=sizeof(stereoChannels); - status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, - kAudioDevicePropertyPreferredChannelsForStereo, &propertySize, - &stereoChannels); -// printf("OSX: stereochannels %d ; %d \n",stereoChannels[0],stereoChannels[1]); - propertySize=sizeof(volume); - volume=vol->left/100.0; - status = AudioDeviceSetProperty(ao->outputDeviceID, 0, stereoChannels[0], 0, kAudioDevicePropertyVolumeScalar, propertySize, &volume); -// printf("OSX: set volume=%5.3f status=%d\n",volume,status); - volume=vol->right/100.0; - status = AudioDeviceSetProperty(ao->outputDeviceID, 0, stereoChannels[1], 0, kAudioDevicePropertyVolumeScalar, propertySize, &volume); - return CONTROL_TRUE; case AOCONTROL_QUERY_FORMAT: - /* stick with what CoreAudio requests */ + /* Everything is currently unimplemented */ return CONTROL_FALSE; default: return CONTROL_FALSE; @@ -239,145 +249,126 @@ static void print_format(const char* str,AudioStreamBasicDescription *f){ static int init(int rate,int channels,int format,int flags) { - OSStatus status; - UInt32 propertySize; - int rc; - int i; - - ao = (ao_macosx_t *)malloc(sizeof(ao_macosx_t)); +AudioStreamBasicDescription inDesc, outDesc; +ComponentDescription desc; +Component comp; +AURenderCallbackStruct renderCallback; +OSStatus err; +UInt32 size, maxFrames; + + ao = (ao_macosx_t *)malloc(sizeof(ao_macosx_t)); + + // Build Description for the input format + memset(&inDesc, 0, sizeof(AudioStreamBasicDescription)); + 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; + } - /* get default output device */ - propertySize = sizeof(ao->outputDeviceID); - status = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice, &propertySize, &(ao->outputDeviceID)); - if (status) { - ao_msg(MSGT_AO,MSGL_WARN, - "AudioHardwareGetProperty returned %d\n", - (int)status); - return CONTROL_FALSE; + if((format&AF_FORMAT_POINT_MASK)==AF_FORMAT_F) { + // float + inDesc.mFormatFlags = kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked; } - - if (ao->outputDeviceID == kAudioDeviceUnknown) { - ao_msg(MSGT_AO,MSGL_WARN, "AudioHardwareGetProperty: ao->outputDeviceID is kAudioDeviceUnknown\n"); - return CONTROL_FALSE; + 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); + ao_data.samplerate = inDesc.mSampleRate; + ao_data.channels = inDesc.mChannelsPerFrame; + ao_data.outburst = ao_data.buffersize = ao->chunk_size; + ao_data.bps = ao_data.samplerate * inDesc.mBytesPerFrame; + + 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; + } - propertySize = sizeof(ao->outputStreamBasicDescription); - status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &ao->outputStreamBasicDescription); - if(!status) print_format("default:",&ao->outputStreamBasicDescription); + // 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 = AudioUnitGetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &outDesc, &size); + print_format("destination: ", &outDesc); + err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &outDesc, size); -#if 1 -// dump supported format list: -{ AudioStreamBasicDescription* p; - Boolean ow; - int i; - propertySize=0; //sizeof(p); -// status = AudioDeviceGetPropertyInfo(ao->outputDeviceID, 0, false, kAudioStreamPropertyPhysicalFormats, &propertySize, &ow); - status = AudioDeviceGetPropertyInfo(ao->outputDeviceID, 0, false, kAudioDevicePropertyStreamFormats, &propertySize, &ow); - if (status) { - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceGetPropertyInfo returned 0x%X when getting kAudioDevicePropertyStreamFormats\n", (int)status); - } - p=malloc(propertySize); -// status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioStreamPropertyPhysicalFormats, &propertySize, p); - status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioDevicePropertyStreamFormats, &propertySize, p); - if (status) { - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceGetProperty returned 0x%X when getting kAudioDevicePropertyStreamFormats\n", (int)status); -// return CONTROL_FALSE; - } - for(i=0;ioutputStreamBasicDescription); - memset(&ao->outputStreamBasicDescription,0,propertySize); - ao->outputStreamBasicDescription.mSampleRate=rate; - ao->outputStreamBasicDescription.mFormatID=kAudioFormatLinearPCM; - ao->outputStreamBasicDescription.mChannelsPerFrame=channels; - - print_format("wanted: ",&ao->outputStreamBasicDescription); - - // try 1: ask if it accepts our specific requirements? - propertySize = sizeof(ao->outputStreamBasicDescription); -// status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioStreamPropertyPhysicalFormatMatch, &propertySize, &ao->outputStreamBasicDescription); - status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioDevicePropertyStreamFormatMatch, &propertySize, &ao->outputStreamBasicDescription); - if (status) { - ao_msg(MSGT_AO,MSGL_V, "AudioDeviceGetProperty returned 0x%X when getting kAudioDevicePropertyStreamFormatMatch\n", (int)status); + err = AudioConverterNew(&inDesc, &outDesc, &(ao->theConverter)); + if (err) { + ao_msg(MSGT_AO, MSGL_WARN, "Unable to create the AudioConverter component (err=%d)\n", err); return CONTROL_FALSE; - } + } -// propertySize = sizeof(ao->outputStreamBasicDescription); -// status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioDevicePropertyStreamFormatSupported, &propertySize, &ao->outputStreamBasicDescription); -// if (status) { -// ao_msg(MSGT_AO,MSGL_V, "AudioDeviceGetProperty returned 0x%X when getting kAudioDevicePropertyStreamFormatSupported\n", (int)status); -// } - - // ok, now try to set the new (default or matched) audio format: - print_format("best: ",&ao->outputStreamBasicDescription); - propertySize = sizeof(ao->outputStreamBasicDescription); - status = AudioDeviceSetProperty(ao->outputDeviceID, 0, 0, false, kAudioDevicePropertyStreamFormat, propertySize, &ao->outputStreamBasicDescription); - if(status) - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceSetProperty returned 0x%X when getting kAudioDevicePropertyStreamFormat\n", (int)status); - - // see what did we get finally... we'll be forced to use this anyway :( - propertySize = sizeof(ao->outputStreamBasicDescription); - status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioDevicePropertyStreamFormat, &propertySize, &ao->outputStreamBasicDescription); - print_format("final: ",&ao->outputStreamBasicDescription); - - /* get requested buffer length */ - // TODO: set NUM_BUFS dinamically, based on buffer size! - propertySize = sizeof(ao->chunk_size); - status = AudioDeviceGetProperty(ao->outputDeviceID, 0, false, kAudioDevicePropertyBufferSize, &propertySize, &ao->chunk_size); - if (status) { - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceGetProperty returned %d when getting kAudioDevicePropertyBufferSize\n", (int)status); - return CONTROL_FALSE; - } - ao_msg(MSGT_AO,MSGL_V, "%5d chunk size\n", (int)ao->chunk_size); + size=sizeof(UInt32); + maxFrames=8192; // This was calculated empirically. On MY system almost everything works more or less the same... + err = AudioUnitSetProperty(ao->theOutputUnit, kAudioUnitProperty_MaximumFramesPerSlice, kAudioUnitScope_Input, 0, &maxFrames, size); + + if(err) { + ao_msg(MSGT_AO, MSGL_WARN, "Unable to set the maximum number of frames per slice!! (err=%d)\n", err); + return CONTROL_FALSE; + } + + ao_msg(MSGT_AO, MSGL_DBG2, "Maximum number of frames per request %d (that is %d bytes)", err, maxFrames, maxFrames*inDesc.mBytesPerFrame); - ao_data.samplerate = ao->outputStreamBasicDescription.mSampleRate; - ao_data.channels = channels; - ao_data.outburst = ao_data.buffersize = ao->chunk_size; - ao_data.bps = - ao_data.samplerate * ao->outputStreamBasicDescription.mBytesPerFrame; - - if (ao->outputStreamBasicDescription.mFormatID == kAudioFormatLinearPCM) { - uint32_t flags = ao->outputStreamBasicDescription.mFormatFlags; - if (flags & kAudioFormatFlagIsFloat) { - ao_data.format = (flags&kAudioFormatFlagIsBigEndian) ? AF_FORMAT_FLOAT_BE : AF_FORMAT_FLOAT_LE; - } else { - ao_msg(MSGT_AO,MSGL_WARN, "Unsupported audio output " - "format 0x%X. Please report this to the developer\n", format); - return CONTROL_FALSE; - } - - } else { - /* TODO: handle AFMT_AC3, AFMT_MPEG & friends */ - ao_msg(MSGT_AO,MSGL_WARN, "Default Audio Device doesn't " - "support Linear PCM!\n"); - return CONTROL_FALSE; - } - - /* Allocate ring-buffer memory */ + ao->chunk_size = maxFrames*inDesc.mBytesPerFrame; ao->num_chunks = NUM_BUFS; ao->buffer_len = (ao->num_chunks + 1) * ao->chunk_size; - ao->buffer = (unsigned char *)malloc(ao->buffer_len); - - - /* Prepare for playback */ + ao->buffer = (unsigned char *)calloc(ao->num_chunks + 1, ao->chunk_size); + ao->chunk = (unsigned char*)calloc(1, ao->chunk_size); + + memset(&renderCallback, 0, sizeof(AURenderCallbackStruct)); + 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; + } - /* Set the IO proc that CoreAudio will call when it needs data */ - status = AudioDeviceAddIOProc(ao->outputDeviceID, audioDeviceIOProc, NULL); - if (status) { - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceAddIOProc returned %d\n", (int)status); - return CONTROL_FALSE; - } - - /* Start callback */ reset(); return CONTROL_OK; @@ -426,11 +417,12 @@ static void uninit(int immed) reset(); - status = AudioDeviceRemoveIOProc(ao->outputDeviceID, audioDeviceIOProc); - if (status) - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceRemoveIOProc " - "returned %d\n", (int)status); + AudioConverterDispose(ao->theConverter); + AudioOutputUnitStop(ao->theOutputUnit); + AudioUnitUninitialize(ao->theOutputUnit); + CloseComponent(ao->theOutputUnit); + free(ao->chunk); free(ao->buffer); free(ao); } @@ -439,12 +431,12 @@ static void uninit(int immed) /* stop playing, keep buffers (for pause) */ static void audio_pause() { - OSErr status; + OSErr status=noErr; /* stop callback */ - status = AudioDeviceStop(ao->outputDeviceID, audioDeviceIOProc); + status=AudioOutputUnitStop(ao->theOutputUnit); if (status) - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceStop returned %d\n", + ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStop returned %d\n", (int)status); } @@ -452,8 +444,10 @@ static void audio_pause() /* resume playing, after audio_pause() */ static void audio_resume() { - OSErr status = AudioDeviceStart(ao->outputDeviceID, audioDeviceIOProc); + OSErr status=noErr; + + status=AudioOutputUnitStart(ao->theOutputUnit); if (status) - ao_msg(MSGT_AO,MSGL_WARN, "AudioDeviceStart returned %d\n", + ao_msg(MSGT_AO,MSGL_WARN, "AudioOutputUnitStart returned %d\n", (int)status); } -- cgit v1.2.3