From 824f813f1dd0d6e452f98feadb3c6a86ee796d50 Mon Sep 17 00:00:00 2001 From: Miller Puckette Date: Mon, 31 Jul 2006 17:20:28 +0000 Subject: added missing portaudio files for mac core audio svn path=/trunk/; revision=5442 --- pd/portaudio/pa_mac_core/pa_mac_core.h | 69 ++++ pd/portaudio/pa_mac_core/pa_mac_core_utilities.c | 466 +++++++++++++++++++++++ 2 files changed, 535 insertions(+) create mode 100644 pd/portaudio/pa_mac_core/pa_mac_core.h create mode 100644 pd/portaudio/pa_mac_core/pa_mac_core_utilities.c (limited to 'pd/portaudio') diff --git a/pd/portaudio/pa_mac_core/pa_mac_core.h b/pd/portaudio/pa_mac_core/pa_mac_core.h new file mode 100644 index 00000000..5994294a --- /dev/null +++ b/pd/portaudio/pa_mac_core/pa_mac_core.h @@ -0,0 +1,69 @@ +/* + * Mac spcific flags for PA. + * portaudio.h should be included before this file. + */ + +/* + * A pointer to a paMacCoreStreamInfo may be passed as + * the hostApiSpecificStreamInfo in the PaStreamParameters struct + * when opening a stream. Use NULL, for the defaults. Note that for + * duplex streams, both infos should be the same or behaviour + * is undefined. + */ +typedef struct paMacCoreStreamInfo +{ + unsigned long size; /**size of whole structure including this header */ + PaHostApiTypeId hostApiType;/**host API for which this data is intended */ + unsigned long version; /**structure version */ + unsigned long flags; /* flags to modify behaviour */ +} paMacCoreStreamInfo; + +/* Use this function to initialize a paMacCoreStreamInfo struct + using the requested flags. */ +void paSetupMacCoreStreamInfo( paMacCoreStreamInfo *data, unsigned long flags ) +{ + bzero( data, sizeof( paMacCoreStreamInfo ) ); + data->size = sizeof( paMacCoreStreamInfo ); + data->hostApiType = paCoreAudio; + data->version = 0x01; + data->flags = flags; +} + +/* + * The following flags alter the behaviour of PA on the mac platform. + * they can be ORed together. These should work both for opening and + * checking a device. + */ +/* Allows PortAudio to change things like the device's frame size, + * which allows for much lower latency, but might disrupt the device + * if other programs are using it. */ +const unsigned long paMacCore_ChangeDeviceParameters = 0x01; + +/* In combination with the above flag, + * causes the stream opening to fail, unless the exact sample rates + * are supported by the device. */ +const unsigned long paMacCore_FailIfConversionRequired = 0x02; + +/* These flags set the SR conversion quality, if required. The wierd ordering + * allows Maximum Quality to be the default.*/ +const unsigned long paMacCore_ConversionQualityMin = 0x0100; +const unsigned long paMacCore_ConversionQualityMedium = 0x0200; +const unsigned long paMacCore_ConversionQualityLow = 0x0300; +const unsigned long paMacCore_ConversionQualityHigh = 0x0400; +const unsigned long paMacCore_ConversionQualityMax = 0x0000; + +/* + * Here are some "preset" combinations of flags (above) to get to some + * common configurations. THIS IS OVERKILL, but if more flags are added + * it won't be. + */ +/*This is the default setting: do as much sample rate conversion as possible + * and as little mucking with the device as possible. */ +const unsigned long paMacCorePlayNice = 0x00; +/*This setting is tuned for pro audio apps. It allows SR conversion on input + and output, but it tries to set the appropriate SR on the device.*/ +const unsigned long paMacCorePro = 0x01; +/*This is a setting to minimize CPU usage and still play nice.*/ +const unsigned long paMacCoreMinimizeCPUButPlayNice = 0x0100; +/*This is a setting to minimize CPU usage, even if that means interrupting the device. */ +const unsigned long paMacCoreMinimizeCPU = 0x0101; diff --git a/pd/portaudio/pa_mac_core/pa_mac_core_utilities.c b/pd/portaudio/pa_mac_core/pa_mac_core_utilities.c new file mode 100644 index 00000000..f2cbd29c --- /dev/null +++ b/pd/portaudio/pa_mac_core/pa_mac_core_utilities.c @@ -0,0 +1,466 @@ +/* + * + * pa_mac_core_utilities.c + * + * utilitiy functions for pa_mac_core.c + * + * This functions are more like helper functions than part of the core, + * so I moved them to a separate file so the core code would be cleaner. + * + * by Bjorn Roche. + */ + +/* + * Translates MacOS generated errors into PaErrors + */ +static PaError PaMacCore_SetError(OSStatus error, int line, int isError) +{ + /*FIXME: still need to handle possible ComponentResult values.*/ + /* unfortunately, they don't seem to be documented anywhere.*/ + PaError result; + const char *errorType; + const char *errorText; + + switch (error) { + case kAudioHardwareNoError: + return paNoError; + case kAudioHardwareNotRunningError: + errorText = "Audio Hardware Not Running"; + result = paInternalError; break; + case kAudioHardwareUnspecifiedError: + errorText = "Unspecified Audio Hardware Error"; + result = paInternalError; break; + case kAudioHardwareUnknownPropertyError: + errorText = "Audio Hardware: Unknown Property"; + result = paInternalError; break; + case kAudioHardwareBadPropertySizeError: + errorText = "Audio Hardware: Bad Property Size"; + result = paInternalError; break; + case kAudioHardwareIllegalOperationError: + errorText = "Audio Hardware: Illegal Operation"; + result = paInternalError; break; + case kAudioHardwareBadDeviceError: + errorText = "Audio Hardware: Bad Device"; + result = paInvalidDevice; break; + case kAudioHardwareBadStreamError: + errorText = "Audio Hardware: BadStream"; + result = paBadStreamPtr; break; + case kAudioHardwareUnsupportedOperationError: + errorText = "Audio Hardware: Unsupported Operation"; + result = paInternalError; break; + case kAudioDeviceUnsupportedFormatError: + errorText = "Audio Device: Unsupported Format"; + result = paSampleFormatNotSupported; break; + case kAudioDevicePermissionsError: + errorText = "Audio Device: Permissions Error"; + result = paDeviceUnavailable; break; + /* Audio Unit Errors: http://developer.apple.com/documentation/MusicAudio/Reference/CoreAudio/audio_units/chapter_5_section_3.html */ + case kAudioUnitErr_InvalidProperty: + errorText = "Audio Unit: Invalid Property"; + result = paInternalError; break; + case kAudioUnitErr_InvalidParameter: + errorText = "Audio Unit: Invalid Parameter"; + result = paInternalError; break; + case kAudioUnitErr_NoConnection: + errorText = "Audio Unit: No Connection"; + result = paInternalError; break; + case kAudioUnitErr_FailedInitialization: + errorText = "Audio Unit: Initialization Failed"; + result = paInternalError; break; + case kAudioUnitErr_TooManyFramesToProcess: + errorText = "Audio Unit: Too Many Frames"; + result = paInternalError; break; + case kAudioUnitErr_IllegalInstrument: + errorText = "Audio Unit: Illegal Instrument"; + result = paInternalError; break; + case kAudioUnitErr_InstrumentTypeNotFound: + errorText = "Audio Unit: Instrument Type Not Found"; + result = paInternalError; break; + case kAudioUnitErr_InvalidFile: + errorText = "Audio Unit: Invalid File"; + result = paInternalError; break; + case kAudioUnitErr_UnknownFileType: + errorText = "Audio Unit: Unknown File Type"; + result = paInternalError; break; + case kAudioUnitErr_FileNotSpecified: + errorText = "Audio Unit: File Not Specified"; + result = paInternalError; break; + case kAudioUnitErr_FormatNotSupported: + errorText = "Audio Unit: Format Not Supported"; + result = paInternalError; break; + case kAudioUnitErr_Uninitialized: + errorText = "Audio Unit: Unitialized"; + result = paInternalError; break; + case kAudioUnitErr_InvalidScope: + errorText = "Audio Unit: Invalid Scope"; + result = paInternalError; break; + case kAudioUnitErr_PropertyNotWritable: + errorText = "Audio Unit: PropertyNotWritable"; + result = paInternalError; break; + case kAudioUnitErr_InvalidPropertyValue: + errorText = "Audio Unit: Invalid Property Value"; + result = paInternalError; break; + case kAudioUnitErr_PropertyNotInUse: + errorText = "Audio Unit: Property Not In Use"; + result = paInternalError; break; + case kAudioUnitErr_Initialized: + errorText = "Audio Unit: Initialized"; + result = paInternalError; break; + case kAudioUnitErr_InvalidOfflineRender: + errorText = "Audio Unit: Invalid Offline Render"; + result = paInternalError; break; + case kAudioUnitErr_Unauthorized: + errorText = "Audio Unit: Unauthorized"; + result = paInternalError; break; + case kAudioUnitErr_CannotDoInCurrentContext: + errorText = "Audio Unit: cannot do in current context"; + result = paInternalError; break; + default: + errorText = "Unknown Error"; + result = paInternalError; + } + + if (isError) + errorType = "Error"; + else + errorType = "Warning"; + + if ((int)error < -99999 || (int)error > 99999) + DBUG(("%s on line %d: err='%4s', msg='%s'\n", errorType, line, (const char *)&error, errorText)); + else + DBUG(("%s on line %d: err=%d, 0x%x, msg='%s'\n", errorType, line, (int)error, (unsigned)error, errorText)); + + PaUtil_SetLastHostErrorInfo( paCoreAudio, error, errorText ); + + return result; +} + + + + +/* + * Durring testing of core audio, I found that serious crashes could occur + * if properties such as sample rate were changed multiple times in rapid + * succession. The function below has some fancy logic to make sure that changes + * are acknowledged before another is requested. That seems to help a lot. + */ + +#include +typedef struct { + bool once; /* I didn't end up using this. bdr */ + pthread_mutex_t mutex; +} MutexAndBool ; + +static OSStatus propertyProc( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData ) +{ + MutexAndBool *mab = (MutexAndBool *) inClientData; + mab->once = TRUE; + pthread_mutex_unlock( &(mab->mutex) ); + return noErr; +} + +/* sets the value of the given property and waits for the change to + be acknowledged, and returns the final value, which is not guaranteed + by this function to be the same as the desired value. Obviously, this + function can only be used for data whose input and output are the + same size and format, and their size and format are known in advance.*/ +PaError AudioDeviceSetPropertyNowAndWaitForChange( + AudioDeviceID inDevice, + UInt32 inChannel, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + UInt32 inPropertyDataSize, + const void *inPropertyData, + void *outPropertyData ) +{ + OSStatus macErr; + int unixErr; + MutexAndBool mab; + UInt32 outPropertyDataSize = inPropertyDataSize; + + /* First, see if it already has that value. If so, return. */ + macErr = AudioDeviceGetProperty( inDevice, inChannel, + isInput, inPropertyID, + &outPropertyDataSize, outPropertyData ); + if( macErr ) + goto failMac2; + if( inPropertyDataSize!=outPropertyDataSize ) + return paInternalError; + if( 0==memcmp( outPropertyData, inPropertyData, outPropertyDataSize ) ) + return paNoError; + + /* setup and lock mutex */ + mab.once = FALSE; + unixErr = pthread_mutex_init( &mab.mutex, NULL ); + if( unixErr ) + goto failUnix2; + unixErr = pthread_mutex_lock( &mab.mutex ); + if( unixErr ) + goto failUnix; + + /* add property listener */ + macErr = AudioDeviceAddPropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc, + &mab ); + if( macErr ) + goto failMac; + /* set property */ + macErr = AudioDeviceSetProperty( inDevice, NULL, inChannel, + isInput, inPropertyID, + inPropertyDataSize, inPropertyData ); + if( macErr ) { + /* we couldn't set the property, so we'll just unlock the mutex + and move on. */ + pthread_mutex_unlock( &mab.mutex ); + } + + /* wait for property to change */ + unixErr = pthread_mutex_lock( &mab.mutex ); + if( unixErr ) + goto failUnix; + + /* now read the property back out */ + macErr = AudioDeviceGetProperty( inDevice, inChannel, + isInput, inPropertyID, + &outPropertyDataSize, outPropertyData ); + if( macErr ) + goto failMac; + /* cleanup */ + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + unixErr = pthread_mutex_unlock( &mab.mutex ); + if( unixErr ) + goto failUnix2; + unixErr = pthread_mutex_destroy( &mab.mutex ); + if( unixErr ) + goto failUnix2; + + return paNoError; + + failUnix: + pthread_mutex_destroy( &mab.mutex ); + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + + failUnix2: + DBUG( ("Error #%d while setting a device property: %s\n", unixErr, strerror( unixErr ) ) ); + return paUnanticipatedHostError; + + failMac: + pthread_mutex_destroy( &mab.mutex ); + AudioDeviceRemovePropertyListener( inDevice, inChannel, isInput, + inPropertyID, propertyProc ); + failMac2: + return ERR( macErr ); +} + +/* + * Sets the sample rate the HAL device. + * if requireExact: set the sample rate or fail. + * + * otherwise : set the exact sample rate. + * If that fails, check for available sample rates, and choose one + * higher than the requested rate. If there isn't a higher one, + * just use the highest available. + */ +static PaError setBestSampleRateForDevice( const AudioDeviceID device, + const bool isOutput, + const bool requireExact, + const Float64 desiredSrate ) +{ + /*FIXME: changing the sample rate is disruptive to other programs using the + device, so it might be good to offer a custom flag to not change the + sample rate and just do conversion. (in my casual tests, there is + no disruption unless the sample rate really does need to change) */ + const bool isInput = isOutput ? 0 : 1; + Float64 srate; + UInt32 propsize = sizeof( Float64 ); + OSErr err; + AudioValueRange *ranges; + int i=0; + Float64 max = -1; /*the maximum rate available*/ + Float64 best = -1; /*the lowest sample rate still greater than desired rate*/ + VDBUG(("Setting sample rate for device %ld to %g.\n",device,(float)desiredSrate)); + + /* -- try setting the sample rate -- */ + err = AudioDeviceSetPropertyNowAndWaitForChange( + device, 0, isInput, + kAudioDevicePropertyNominalSampleRate, + propsize, &desiredSrate, &srate ); + if( err ) + return err; + + /* -- if the rate agrees, and we got no errors, we are done -- */ + if( !err && srate == desiredSrate ) + return paNoError; + /* -- we've failed if the rates disagree and we are setting input -- */ + if( requireExact ) + return paInvalidSampleRate; + + /* -- generate a list of available sample rates -- */ + err = AudioDeviceGetPropertyInfo( device, 0, isInput, + kAudioDevicePropertyAvailableNominalSampleRates, + &propsize, NULL ); + if( err ) + return ERR( err ); + ranges = (AudioValueRange *)calloc( 1, propsize ); + if( !ranges ) + return paInsufficientMemory; + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyAvailableNominalSampleRates, + &propsize, ranges ); + if( err ) + { + free( ranges ); + return ERR( err ); + } + VDBUG(("Requested sample rate of %g was not available.\n", (float)desiredSrate)); + VDBUG(("%lu Available Sample Rates are:\n",propsize/sizeof(AudioValueRange))); +#ifdef MAC_CORE_VERBOSE_DEBUG + for( i=0; i max ) max = ranges[i].mMaximum; + if( ranges[i].mMinimum > desiredSrate ) { + if( best < 0 ) + best = ranges[i].mMinimum; + else if( ranges[i].mMinimum < best ) + best = ranges[i].mMinimum; + } + } + if( best < 0 ) + best = max; + VDBUG( ("Maximum Rate %g. best is %g.\n", max, best ) ); + free( ranges ); + + /* -- set the sample rate -- */ + propsize = sizeof( best ); + err = AudioDeviceSetPropertyNowAndWaitForChange( + device, 0, isInput, + kAudioDevicePropertyNominalSampleRate, + propsize, &best, &srate ); + if( err ) + return err; + + if( err ) + return ERR( err ); + /* -- if the set rate matches, we are done -- */ + if( srate == best ) + return paNoError; + + /* -- otherwise, something wierd happened: we didn't set the rate, and we got no errors. Just bail. */ + return paInternalError; +} + + +/* + Attempts to set the requestedFramesPerBuffer. If it can't set the exact + value, it settles for something smaller if available. If nothing smaller + is available, it uses the smallest available size. + actualFramesPerBuffer will be set to the actual value on successful return. + OK to pass NULL to actualFramesPerBuffer. + The logic is very simmilar too setBestSampleRate only failure here is + not usually catastrophic. +*/ +static PaError setBestFramesPerBuffer( const AudioDeviceID device, + const bool isOutput, + unsigned long requestedFramesPerBuffer, + unsigned long *actualFramesPerBuffer ) +{ + UInt32 afpb; + const bool isInput = !isOutput; + UInt32 propsize = sizeof(UInt32); + OSErr err; + Float64 min = -1; /*the min blocksize*/ + Float64 best = -1; /*the best blocksize*/ + int i=0; + AudioValueRange *ranges; + + if( actualFramesPerBuffer == NULL ) + actualFramesPerBuffer = &afpb; + + + /* -- try and set exact FPB -- */ + err = AudioDeviceSetProperty( device, NULL, 0, isInput, + kAudioDevicePropertyBufferFrameSize, + propsize, &requestedFramesPerBuffer); + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferFrameSize, + &propsize, actualFramesPerBuffer); + if( err ) + return ERR( err ); + if( *actualFramesPerBuffer == requestedFramesPerBuffer ) + return paNoError; /* we are done */ + + /* -- fetch available block sizes -- */ + err = AudioDeviceGetPropertyInfo( device, 0, isInput, + kAudioDevicePropertyBufferSizeRange, + &propsize, NULL ); + if( err ) + return ERR( err ); + ranges = (AudioValueRange *)calloc( 1, propsize ); + if( !ranges ) + return paInsufficientMemory; + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferSizeRange, + &propsize, ranges ); + if( err ) + { + free( ranges ); + return ERR( err ); + } + VDBUG(("Requested block size of %lu was not available.\n", + requestedFramesPerBuffer )); + VDBUG(("%lu Available block sizes are:\n",propsize/sizeof(AudioValueRange))); +#ifdef MAC_CORE_VERBOSE_DEBUG + for( i=0; i best ) + best = ranges[i].mMaximum; + } + } + if( best == -1 ) + best = min; + VDBUG( ("Minimum FPB %g. best is %g.\n", min, best ) ); + free( ranges ); + + /* --- set the buffer size (ignore errors) -- */ + requestedFramesPerBuffer = (UInt32) best ; + propsize = sizeof( UInt32 ); + err = AudioDeviceSetProperty( device, NULL, 0, isInput, + kAudioDevicePropertyBufferSize, + propsize, &requestedFramesPerBuffer ); + /* --- read the property to check that it was set -- */ + err = AudioDeviceGetProperty( device, 0, isInput, + kAudioDevicePropertyBufferSize, + &propsize, actualFramesPerBuffer ); + + if( err ) + return ERR( err ); + + return paNoError; +} -- cgit v1.2.1