mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-09-18 19:26:34 -04:00
8d353a5b3b
git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/trunk@189 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
467 lines
17 KiB
C
467 lines
17 KiB
C
/*
|
|
*
|
|
* 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 <pthread.h>
|
|
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<propsize/sizeof(AudioValueRange); ++i )
|
|
VDBUG( ("\t%g-%g\n",
|
|
(float) ranges[i].mMinimum,
|
|
(float) ranges[i].mMaximum ) );
|
|
#endif
|
|
VDBUG(("-----\n"));
|
|
|
|
/* -- now pick the best available sample rate -- */
|
|
for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
|
|
{
|
|
if( ranges[i].mMaximum > 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<propsize/sizeof(AudioValueRange); ++i )
|
|
VDBUG( ("\t%g-%g\n",
|
|
(float) ranges[i].mMinimum,
|
|
(float) ranges[i].mMaximum ) );
|
|
#endif
|
|
VDBUG(("-----\n"));
|
|
|
|
/* --- now pick the best available framesPerBuffer -- */
|
|
for( i=0; i<propsize/sizeof(AudioValueRange); ++i )
|
|
{
|
|
if( min == -1 || ranges[i].mMinimum < min ) min = ranges[i].mMinimum;
|
|
if( ranges[i].mMaximum < requestedFramesPerBuffer ) {
|
|
if( best < 0 )
|
|
best = ranges[i].mMaximum;
|
|
else if( ranges[i].mMaximum > 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;
|
|
}
|