From e3387a98a734f1cdde1d03ab7abebd236c9b8e94 Mon Sep 17 00:00:00 2001 From: philburk Date: Thu, 21 Jul 2011 08:29:26 +0000 Subject: [PATCH] Improve calculation of default latencies on Mac CoreAudio. Allow very low buffer sizes if suggestedLatency is low. Set actual latencies in StreamInfo. --- src/hostapi/coreaudio/pa_mac_core.c | 366 ++++++++++++++++++++-------- test/pa_devs.c | 9 +- test/paqa_devs.c | 2 +- 3 files changed, 266 insertions(+), 111 deletions(-) diff --git a/src/hostapi/coreaudio/pa_mac_core.c b/src/hostapi/coreaudio/pa_mac_core.c index 88bb478..7a3c282 100644 --- a/src/hostapi/coreaudio/pa_mac_core.c +++ b/src/hostapi/coreaudio/pa_mac_core.c @@ -76,9 +76,11 @@ extern "C" { #endif /* __cplusplus */ - + +/* This is a reasonable size for a small buffer based on experience. */ +#define PA_MAC_SMALL_BUFFER_SIZE (64) + /* prototypes for functions declared in this file */ - PaError PaMacCore_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); /* @@ -397,6 +399,132 @@ static PaError gatherDeviceInfo(PaMacAUHAL *auhalHostApi) return paNoError; } +/* =================================================================================================== */ +/** + * @internal + * @brief Clip the desired size against the allowed IO buffer size range for the device. + */ +static PaError ClipToDeviceBufferSize( AudioDeviceID macCoreDeviceId, + int isInput, UInt32 desiredSize, UInt32 *allowedSize ) +{ + UInt32 resultSize = desiredSize; + AudioValueRange audioRange; + UInt32 propSize = sizeof( audioRange ); + PaError err = WARNING(AudioDeviceGetProperty( macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSizeRange, &propSize, &audioRange ) ); + //printf("kAudioDevicePropertyBufferFrameSizeRange: err = %d, propSize = %d, minimum = %g\n", err, propSize, audioRange.mMinimum); + //printf("kAudioDevicePropertyBufferFrameSizeRange: err = %d, propSize = %d, meximum = %g\n", err, propSize, audioRange.mMaximum ); + resultSize = MAX( resultSize, audioRange.mMinimum ); + resultSize = MIN( resultSize, audioRange.mMaximum ); + *allowedSize = resultSize; + return err; +} + +/* =================================================================================================== */ +#if 0 +void DumpDeviceProperties( AudioDeviceID macCoreDeviceId, + int isInput ) +{ + UInt32 propSize; + UInt32 deviceLatency; + UInt32 streamLatency; + UInt32 bufferFrames; + UInt32 safetyOffset; + + printf(" ---- latency query : macCoreDeviceId = %d, isInput %d ----\n", macCoreDeviceId, isInput ); + + propSize = sizeof(UInt32); + PaError err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioStreamPropertyLatency, &propSize, &streamLatency)); + printf("kAudioStreamPropertyLatency: err = %d, propSize = %d, value = %d\n", err, propSize, streamLatency ); + + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &bufferFrames)); + printf("kAudioDevicePropertyBufferFrameSize: err = %d, propSize = %d, value = %d\n", err, propSize, bufferFrames ); + + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertySafetyOffset, &propSize, &safetyOffset)); + printf("kAudioDevicePropertySafetyOffset: err = %d, propSize = %d, value = %d\n", err, propSize, safetyOffset ); + + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &deviceLatency)); + printf("kAudioDevicePropertyLatency: err = %d, propSize = %d, value = %d\n", err, propSize, deviceLatency ); + + AudioValueRange audioRange; + propSize = sizeof( audioRange ); + err = WARNING(AudioDeviceGetProperty( macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSizeRange, &propSize, &audioRange ) ); + printf("kAudioDevicePropertyBufferFrameSizeRange: err = %d, propSize = %d, minimum = %g\n", err, propSize, audioRange.mMinimum); + printf("kAudioDevicePropertyBufferFrameSizeRange: err = %d, propSize = %d, maximum = %g\n", err, propSize, audioRange.mMaximum ); +} +#endif + +/* =================================================================================================== */ +/** + * @internal + * Calculate the fixed latency from the system and the device. + * Sum of kAudioStreamPropertyLatency + + * kAudioDevicePropertySafetyOffset + + * kAudioDevicePropertyLatency + * + * Some useful info from Jeff Moore on latency. + * http://osdir.com/ml/coreaudio-api/2010-01/msg00046.html + * http://osdir.com/ml/coreaudio-api/2009-07/msg00140.html + */ +static PaError CalculateFixedDeviceLatency( AudioDeviceID macCoreDeviceId, int isInput, UInt32 *fixedLatencyPtr ) +{ + UInt32 propSize; + UInt32 deviceLatency; + UInt32 streamLatency; + UInt32 safetyOffset; + + propSize = sizeof(UInt32); + PaError err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioStreamPropertyLatency, &propSize, &streamLatency)); + if( err != paNoError ) goto error; + + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertySafetyOffset, &propSize, &safetyOffset)); + if( err != paNoError ) goto error; + + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &deviceLatency)); + if( err != paNoError ) goto error; + + *fixedLatencyPtr = deviceLatency + streamLatency + safetyOffset; + return err; +error: + return err; +} + +/* =================================================================================================== */ +static PaError CalculateDefaultDeviceLatencies( AudioDeviceID macCoreDeviceId, + int isInput, UInt32 *lowLatencyFramesPtr, + UInt32 *highLatencyFramesPtr ) +{ + UInt32 propSize; + UInt32 bufferFrames = 0; + UInt32 fixedLatency = 0; + UInt32 clippedMinBufferSize = 0; + + PaError err = CalculateFixedDeviceLatency( macCoreDeviceId, isInput, &fixedLatency ); + if( err != paNoError ) goto error; + + // For low latency use a small fixed size buffer clipped to the device range. + err = ClipToDeviceBufferSize( macCoreDeviceId, isInput, PA_MAC_SMALL_BUFFER_SIZE, &clippedMinBufferSize ); + if( err != paNoError ) goto error; + + // For high latency use the default device buffer size. + propSize = sizeof(UInt32); + err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyBufferFrameSize, &propSize, &bufferFrames)); + if( err != paNoError ) goto error; + + *lowLatencyFramesPtr = fixedLatency + clippedMinBufferSize; + *highLatencyFramesPtr = fixedLatency + bufferFrames; + + return err; +error: + return err; +} + +/* =================================================================================================== */ + static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, PaDeviceInfo *deviceInfo, AudioDeviceID macCoreDeviceId, @@ -407,8 +535,7 @@ static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, UInt32 i; int numChannels = 0; AudioBufferList *buflist = NULL; - UInt32 frameLatency; - + VVDBUG(("GetChannelInfo()\n")); /* Get the number of channels from the stream configuration. @@ -433,39 +560,33 @@ static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, else deviceInfo->maxOutputChannels = numChannels; - if (numChannels > 0) /* do not try to retrieve the latency if there is no channels. */ + if (numChannels > 0) /* do not try to retrieve the latency if there are no channels. */ { /* Get the latency. Don't fail if we can't get this. */ /* default to something reasonable */ deviceInfo->defaultLowInputLatency = .01; deviceInfo->defaultHighInputLatency = .10; deviceInfo->defaultLowOutputLatency = .01; - deviceInfo->defaultHighOutputLatency = .10; - propSize = sizeof(UInt32); - err = WARNING(AudioDeviceGetProperty(macCoreDeviceId, 0, isInput, kAudioDevicePropertyLatency, &propSize, &frameLatency)); - if (!err) - { - /** FEEDBACK: - * This code was arrived at by trial and error, and some extentive, but not exhaustive - * testing. Sebastien Beaulieu has suggested using - * kAudioDevicePropertyLatency + kAudioDevicePropertySafetyOffset + buffer size instead. - * At the time this code was written, many users were reporting dropouts with audio - * programs that probably used this formula. This was probably - * around 10.4.4, and the problem is probably fixed now. So perhaps - * his formula should be reviewed and used. - * */ - double secondLatency = frameLatency / deviceInfo->defaultSampleRate; - if (isInput) - { - deviceInfo->defaultLowInputLatency = 3 * secondLatency; - deviceInfo->defaultHighInputLatency = 3 * 10 * secondLatency; - } - else - { - deviceInfo->defaultLowOutputLatency = 3 * secondLatency; - deviceInfo->defaultHighOutputLatency = 3 * 10 * secondLatency; - } - } + deviceInfo->defaultHighOutputLatency = .10; + UInt32 lowLatencyFrames = 0; + UInt32 highLatencyFrames = 0; + err = CalculateDefaultDeviceLatencies( macCoreDeviceId, isInput, &lowLatencyFrames, &highLatencyFrames ); + if( err == 0 ) + { + + double lowLatencySeconds = lowLatencyFrames / deviceInfo->defaultSampleRate; + double highLatencySeconds = highLatencyFrames / deviceInfo->defaultSampleRate; + if (isInput) + { + deviceInfo->defaultLowInputLatency = lowLatencySeconds; + deviceInfo->defaultHighInputLatency = highLatencySeconds; + } + else + { + deviceInfo->defaultLowOutputLatency = lowLatencySeconds; + deviceInfo->defaultHighOutputLatency = highLatencySeconds; + } + } } PaUtil_FreeMemory( buflist ); return paNoError; @@ -474,6 +595,7 @@ static PaError GetChannelInfo( PaMacAUHAL *auhalHostApi, return err; } +/* =================================================================================================== */ static PaError InitializeDeviceInfo( PaMacAUHAL *auhalHostApi, PaDeviceInfo *deviceInfo, AudioDeviceID macCoreDeviceId, @@ -1315,6 +1437,56 @@ static PaError OpenAndSetupOneAudioUnit( return paResult; } +/* =================================================================================================== */ + +UInt32 CalculateOptimalBufferSize( PaMacAUHAL *auhalHostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + UInt32 fixedInputLatency, + UInt32 fixedOutputLatency, + double sampleRate ) +{ + UInt32 requested = 0; + // Use maximum of suggested input and output latencies. + if( inputParameters ) + { + UInt32 suggestedLatencyFrames = inputParameters->suggestedLatency * sampleRate; + // Calculate a buffer size assuming we are double buffered. + SInt32 variableLatencyFrames = suggestedLatencyFrames - fixedInputLatency; + // Prevent negative latency. + variableLatencyFrames = MAX( variableLatencyFrames, 0 ); + requested = MAX( requested, (UInt32) variableLatencyFrames ); + } + if( outputParameters ) + { + UInt32 suggestedLatencyFrames = outputParameters->suggestedLatency * sampleRate; + SInt32 variableLatencyFrames = suggestedLatencyFrames - fixedOutputLatency; + variableLatencyFrames = MAX( variableLatencyFrames, 0 ); + requested = MAX( requested, (UInt32) variableLatencyFrames ); + } + + VDBUG( ("Block Size unspecified. Based on Latency, the user wants a Block Size near: %ld.\n", + requested ) ); + + // Clip to the capabilities of the device. + if( inputParameters ) + { + ClipToDeviceBufferSize( auhalHostApi->devIds[inputParameters->device], + true, // In the old code isInput was false! + requested, &requested ); + } + if( outputParameters ) + { + ClipToDeviceBufferSize( auhalHostApi->devIds[outputParameters->device], + false, requested, &requested ); + } + VDBUG(("After querying hardware, setting block size to %ld.\n", requested)); + +error: + return requested; +} + +/* =================================================================================================== */ /* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaStream** s, @@ -1332,6 +1504,12 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, int inputChannelCount, outputChannelCount; PaSampleFormat inputSampleFormat, outputSampleFormat; PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + UInt32 fixedInputLatency = 0; + UInt32 fixedOutputLatency = 0; + // Accumulate contributions to latency in these variables. + UInt32 inputLatencyFrames = 0; + UInt32 outputLatencyFrames = 0; + VVDBUG(("OpenStream(): in chan=%d, in fmt=%ld, out chan=%d, out fmt=%ld SR=%g, FPB=%ld\n", inputParameters ? inputParameters->channelCount : -1, inputParameters ? inputParameters->sampleFormat : -1, @@ -1454,71 +1632,28 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); - /* -- handle paFramesPerBufferUnspecified -- */ - if( framesPerBuffer == paFramesPerBufferUnspecified ) { - long requested = 64; - if( inputParameters ) - requested = MAX( requested, inputParameters->suggestedLatency * sampleRate / 2 ); - if( outputParameters ) - requested = MAX( requested, outputParameters->suggestedLatency *sampleRate / 2 ); - VDBUG( ("Block Size unspecified. Based on Latency, the user wants a Block Size near: %ld.\n", - requested ) ); - if( requested <= 64 ) { - /*requested a realtively low latency. make sure this is in range of devices */ - /*try to get the device's min natural buffer size and use that (but no smaller than 64).*/ - AudioValueRange audioRange; - UInt32 size = sizeof( audioRange ); - if( inputParameters ) { - WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device], - 0, - false, - kAudioDevicePropertyBufferFrameSizeRange, - &size, &audioRange ) ); - if( result ) - requested = MAX( requested, audioRange.mMinimum ); - } - size = sizeof( audioRange ); - if( outputParameters ) { - WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device], - 0, - false, - kAudioDevicePropertyBufferFrameSizeRange, - &size, &audioRange ) ); - if( result ) - requested = MAX( requested, audioRange.mMinimum ); - } - } else { - /* requested a realtively high latency. make sure this is in range of devices */ - /*try to get the device's max natural buffer size and use that (but no larger than 1024).*/ - AudioValueRange audioRange; - UInt32 size = sizeof( audioRange ); - requested = MIN( requested, 1024 ); - if( inputParameters ) { - WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[inputParameters->device], - 0, - false, - kAudioDevicePropertyBufferFrameSizeRange, - &size, &audioRange ) ); - if( result ) - requested = MIN( requested, audioRange.mMaximum ); - } - size = sizeof( audioRange ); - if( outputParameters ) { - WARNING( result = AudioDeviceGetProperty( auhalHostApi->devIds[outputParameters->device], - 0, - false, - kAudioDevicePropertyBufferFrameSizeRange, - &size, &audioRange ) ); - if( result ) - requested = MIN( requested, audioRange.mMaximum ); - } - } - /* -- double check ranges -- */ - if( requested > 1024 ) requested = 1024; - if( requested < 64 ) requested = 64; - VDBUG(("After querying hardware, setting block size to %ld.\n", requested)); - framesPerBuffer = requested; + + if( inputParameters ) + { + CalculateFixedDeviceLatency( auhalHostApi->devIds[inputParameters->device], true, &fixedInputLatency ); + inputLatencyFrames += fixedInputLatency; + } + if( outputParameters ) + { + CalculateFixedDeviceLatency( auhalHostApi->devIds[outputParameters->device], false, &fixedOutputLatency ); + outputLatencyFrames += fixedOutputLatency; + } + + if( framesPerBuffer == paFramesPerBufferUnspecified ) + { + framesPerBuffer = CalculateOptimalBufferSize( auhalHostApi, inputParameters, outputParameters, + fixedInputLatency, fixedOutputLatency, + sampleRate ); + } + + inputLatencyFrames += framesPerBuffer; + outputLatencyFrames += framesPerBuffer; /* -- Now we actually open and setup streams. -- */ if( inputParameters && outputParameters && outputParameters->device == inputParameters->device ) @@ -1605,7 +1740,7 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, * ring buffer to store inpt data while waiting for output * data. */ - if( (stream->outputUnit && stream->inputUnit != stream->outputUnit) + if( (stream->outputUnit && (stream->inputUnit != stream->outputUnit)) || stream->inputSRConverter ) { /* May want the ringSize ot initial position in @@ -1636,6 +1771,9 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, middle of the buffer */ if( stream->outputUnit ) PaUtil_AdvanceRingBufferWriteIndex( &stream->inputRingBuffer, ringSize / RING_BUFFER_ADVANCE_DENOMINATOR ); + + // Just adds to input latency between input device and PA full duplex callback. + inputLatencyFrames += ringSize; } } @@ -1658,6 +1796,10 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, outputParameters?outputChannelCount:0 ) ; if( result != paNoError ) goto error; + + inputLatencyFrames += ringSize; + outputLatencyFrames += ringSize; + } /* -- initialize Buffer Processor -- */ @@ -1686,16 +1828,28 @@ static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, } stream->bufferProcessorIsInitialized = TRUE; - /* - IMPLEMENT ME: initialise the following fields with estimated or actual - values. - I think this is okay the way it is br 12/1/05 - maybe need to change input latency estimate if IO devs differ - */ - stream->streamRepresentation.streamInfo.inputLatency = - PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor)/sampleRate; - stream->streamRepresentation.streamInfo.outputLatency = - PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor)/sampleRate; + // Calculate actual latency from the sum of individual latencies. + if( inputParameters ) + { + inputLatencyFrames += PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.inputLatency = inputLatencyFrames / sampleRate; + } + else + { + stream->streamRepresentation.streamInfo.inputLatency = 0.0; + } + + if( outputParameters ) + { + + outputLatencyFrames += PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor); + stream->streamRepresentation.streamInfo.outputLatency = outputLatencyFrames / sampleRate; + } + else + { + stream->streamRepresentation.streamInfo.outputLatency = 0.0; + } + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; stream->sampleRate = sampleRate; diff --git a/test/pa_devs.c b/test/pa_devs.c index 4862e6a..97454d2 100644 --- a/test/pa_devs.c +++ b/test/pa_devs.c @@ -94,6 +94,7 @@ static void PrintSupportedStandardSampleRates( else printf( "\n" ); } + /*******************************************************************/ int main(void); int main(void) @@ -161,10 +162,10 @@ int main(void) printf( "Max inputs = %d", deviceInfo->maxInputChannels ); printf( ", Max outputs = %d\n", deviceInfo->maxOutputChannels ); - printf( "Default low input latency = %8.3f\n", deviceInfo->defaultLowInputLatency ); - printf( "Default low output latency = %8.3f\n", deviceInfo->defaultLowOutputLatency ); - printf( "Default high input latency = %8.3f\n", deviceInfo->defaultHighInputLatency ); - printf( "Default high output latency = %8.3f\n", deviceInfo->defaultHighOutputLatency ); + printf( "Default low input latency = %8.4f\n", deviceInfo->defaultLowInputLatency ); + printf( "Default low output latency = %8.4f\n", deviceInfo->defaultLowOutputLatency ); + printf( "Default high input latency = %8.4f\n", deviceInfo->defaultHighInputLatency ); + printf( "Default high output latency = %8.4f\n", deviceInfo->defaultHighOutputLatency ); #ifdef WIN32 #if PA_USE_ASIO diff --git a/test/paqa_devs.c b/test/paqa_devs.c index 2a9ce54..3374829 100644 --- a/test/paqa_devs.c +++ b/test/paqa_devs.c @@ -122,7 +122,7 @@ static int QaCallback( const void *inputBuffer, void *outputBuffer, PaQaData *data = (PaQaData *) userData; (void) inputBuffer; - /* Play simle sawtooth wave. */ + /* Play simple sawtooth wave. */ if( data->mode == MODE_OUTPUT ) { phase = data->sawPhase; -- 2.43.0