]> Repos - portaudio/commitdiff
Improve calculation of default latencies on Mac CoreAudio.
authorphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 21 Jul 2011 08:29:26 +0000 (08:29 +0000)
committerphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Thu, 21 Jul 2011 08:29:26 +0000 (08:29 +0000)
Allow very low buffer sizes if suggestedLatency is low.
Set actual latencies in StreamInfo.

src/hostapi/coreaudio/pa_mac_core.c
test/pa_devs.c
test/paqa_devs.c

index 88bb478c68ee30fc158b90643e5c3288eaa8e160..7a3c282efb36a415f73fa7bf86d6ae3723f640af 100644 (file)
 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 <seb@plogue.com> 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;
index 4862e6a54a7e844d5911fd8ed6755c5e4e13230c..97454d2b1b64f737b28c6aee9b7d89aed4a21456 100644 (file)
@@ -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
index 2a9ce54da04bb16c754f140a3f5d910d002e6e4c..3374829ee8911db11e40d982c21178b10bb314cd 100644 (file)
@@ -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;