]> Repos - portaudio/commitdiff
Improve latency measurement in loopback so it handles DC steps better.
authorphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Tue, 8 Mar 2011 00:37:50 +0000 (00:37 +0000)
committerphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Tue, 8 Mar 2011 00:37:50 +0000 (00:37 +0000)
qa/loopback/src/audio_analyzer.c
qa/loopback/src/test_audio_analyzer.c

index f027f6373b8c43abff1a1e17996bd0b77cb0a385..6fa2f76da665d464298659e86d7778235fbb553e 100644 (file)
@@ -345,21 +345,28 @@ void PaQa_FilterRecording( PaQaRecording *input, PaQaRecording *output, BiquadFi
 }
 
 /*==========================================================================================*/
-// Scan until we get a correlation that goes over the tolerance level,
-// peaks then drops to half the peak.
-double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numFrames, double tolerance  )
+/** Scan until we get a correlation of a single that goes over the tolerance level,
+ * peaks then drops to half the peak.
+ * Look for inverse correlation as well.
+ */
+double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numFrames, double threshold  )
 {
        int ic,is;
        // How many buffers will fit in the recording?
        int maxCorrelations = recording->numFrames - numFrames;
        double maxSum = 0.0;
        int peakIndex = -1;
+       double inverseMaxSum = 0.0;
+       int inversePeakIndex = -1;
        double location = -1.0;
 
     QA_ASSERT_TRUE( "numFrames out of bounds", (numFrames < recording->numFrames) );
 
        for( ic=0; ic<maxCorrelations; ic++ )
        {
+               int pastPeak;
+               int inversePastPeak;
+               
                double sum = 0.0;
                // Correlate buffer against the recording.
                float *recorded = &recording->buffer[ ic ];
@@ -374,13 +381,29 @@ double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numFram
                        maxSum = sum;
                        peakIndex = ic;
                }
-               if( (maxSum > tolerance) && (sum < 0.5*maxSum) )
+               if( ((-sum) > inverseMaxSum) )
                {
-                       location = peakIndex;
+                       inverseMaxSum = -sum;
+                       inversePeakIndex = ic;
+               }
+               pastPeak = (maxSum > threshold) && (sum < 0.5*maxSum);
+               inversePastPeak = (inverseMaxSum > threshold) && ((-sum) < 0.5*inverseMaxSum);
+               //printf("PaQa_FindFirstMatch: ic = %4d, sum = %8f, maxSum = %8f, inverseMaxSum = %8f\n", ic, sum, maxSum, inverseMaxSum );                                                                                                               
+               if( pastPeak && inversePastPeak )
+               {
+                       if( maxSum > inverseMaxSum )
+                       {
+                               location = peakIndex;
+                       }
+                       else
+                       {
+                               location = inversePeakIndex;
+                       }
                        break;
                }
                
        }
+       //printf("PaQa_FindFirstMatch: location = %4d\n", (int)location );
        return location;
 error:
        return -1.0;
@@ -454,6 +477,7 @@ double PaQa_ComputePhaseDifference( double phase1, double phase2 )
 /*==========================================================================================*/
 int PaQa_MeasureLatency( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
 {
+       double threshold;
        PaQaSineGenerator generator;
 #define MAX_BUFFER_SIZE 2048
        float buffer[MAX_BUFFER_SIZE];
@@ -470,7 +494,8 @@ int PaQa_MeasureLatency( PaQaRecording *recording, PaQaTestTone *testTone, PaQaA
        PaQa_EraseBuffer( buffer, cycleSize, testTone->samplesPerFrame );
        PaQa_MixSine( &generator, buffer, cycleSize, testTone->samplesPerFrame );
 
-       analysisResult->latency = PaQa_FindFirstMatch( recording, buffer, cycleSize, /* tolerance= */ 0.1 );    
+       threshold = cycleSize * 0.01;
+       analysisResult->latency = PaQa_FindFirstMatch( recording, buffer, cycleSize, threshold );       
        QA_ASSERT_TRUE( "Could not find the start of the signal.", (analysisResult->latency >= 0) );
        analysisResult->amplitudeRatio = PaQa_CompareAmplitudes( recording, analysisResult->latency, buffer, cycleSize );
        return 0;
@@ -478,8 +503,6 @@ error:
        return -1;
 }
 
-
-
 /*==========================================================================================*/
 // Apply cosine squared window.
 void PaQa_FadeInRecording( PaQaRecording *recording, int startFrame, int count )
@@ -551,7 +574,7 @@ int PaQa_DetectPop( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalys
        // Scan remaining signal looking for peak.
        maxAmplitude = 0.0;
        maxPosition = -1;
-       for( i=0; i<hipassOutput.numFrames; i++ )
+       for( i=(int) analysisResult->latency; i<hipassOutput.numFrames; i++ )
        {
                float x = hipassOutput.buffer[i];
                float mag = fabs( x );
index eb523a9b9b5dc564cb03f476fc38e21a80b3ce2c..990d8d8033390220c32d24c4b6c015916dc96cc6 100644 (file)
@@ -222,12 +222,11 @@ static void MakeRecordingWithAddedFrames( PaQaRecording *recording, PaQaTestTone
 
 /*==========================================================================================*/
 /**
- * Generate a recording with pop.
+ * Generate a clean recording.
  */
 
-static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTone, int popPosition, int popWidth, double popAmplitude )
+static void MakeCleanRecording( PaQaRecording *recording, PaQaTestTone *testTone )
 {
-       int i;
        PaQaSineGenerator generator;
 #define BUFFER_SIZE 512
        float buffer[BUFFER_SIZE];
@@ -246,6 +245,18 @@ static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTo
                PaQa_MixSine( &generator, buffer, BUFFER_SIZE, stride );
                done = PaQa_WriteRecording( recording, buffer, BUFFER_SIZE, testTone->samplesPerFrame );
        }
+}
+
+/*==========================================================================================*/
+/**
+ * Generate a recording with pop.
+ */
+
+static void MakeRecordingWithPop( PaQaRecording *recording, PaQaTestTone *testTone, int popPosition, int popWidth, double popAmplitude )
+{
+       int i;
+       
+       MakeCleanRecording( recording, testTone );
        
        // Apply glitch to good recording.
        if( (popPosition + popWidth) >= recording->numFrames )
@@ -324,6 +335,9 @@ static int TestDetectPhaseErrors( void )
 {
        int result;
        
+       result = TestDetectSinglePhaseError( 44100, 200, 477, -1, 0 );
+       if( result < 0 ) return result;
+/*
        result = TestDetectSinglePhaseError( 44100, 200, 77, -1, 0 );
        if( result < 0 ) return result;
        
@@ -345,7 +359,7 @@ static int TestDetectPhaseErrors( void )
        // Note that if the frequency is too high then it is hard to detect single dropped frames.
        result = TestDetectSinglePhaseError( 44100, 200, 500, 4251, -1 );
        if( result < 0 ) return result;
-               
+*/
        return 0;
 }
 
@@ -371,7 +385,7 @@ static int TestDetectSinglePop( double sampleRate, int cycleSize, int latencyFra
        QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
        
        MakeRecordingWithPop( &recording, &testTone, popPosition, popWidth, popAmplitude );
-       
+               
        PaQa_AnalyseRecording( &recording, &testTone, &analysisResult );
        
 #if PRINT_REPORTS
@@ -379,7 +393,7 @@ static int TestDetectSinglePop( double sampleRate, int cycleSize, int latencyFra
        printf("                        expected      actual\n");
        printf("             latency: %10.3f  %10.3f\n", (double)latencyFrames, analysisResult.latency );
        printf("         popPosition: %10.3f  %10.3f\n", (double)popPosition, analysisResult.popPosition );     
-       printf("        popAmplitude: %10.3f  %10.3f\n", (double)popAmplitude, analysisResult.popAmplitude );   
+       printf("        popAmplitude: %10.3f  %10.3f\n", popAmplitude, analysisResult.popAmplitude );   
        printf("           cycleSize: %6d\n", cycleSize );      
        printf("    num added frames: %10.3f\n", analysisResult.numAddedFrames );
        printf("     added frames at: %10.3f\n", analysisResult.addedFramesPosition );
@@ -403,6 +417,80 @@ error:
        return 1;
 }      
 
+/*==========================================================================================*/
+/**
+ * Analyse recording with a DC offset.
+ */
+static int TestSingleInitialSpike( double sampleRate, int stepPosition, int cycleSize, int latencyFrames, double stepAmplitude )
+{
+       int i;
+       int result = 0;
+       // Account for highpass filter offset.
+       int expectedLatency = latencyFrames + 1;
+       PaQaRecording     recording;    
+       
+       PaQaRecording     hipassOutput = { 0 };
+       BiquadFilter      hipassFilter;
+       
+       PaQaTestTone testTone;
+       PaQaAnalysisResult analysisResult = { 0.0 };
+       int maxFrames = ((int)sampleRate) * 2;
+       
+       testTone.samplesPerFrame = 1;
+       testTone.sampleRate = sampleRate;
+       testTone.frequency = sampleRate / cycleSize;
+       testTone.amplitude = -0.5;
+       testTone.startDelay = latencyFrames;
+       
+       result = PaQa_InitializeRecording( &recording, maxFrames, (int) sampleRate );
+       QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
+       
+       result = PaQa_InitializeRecording( &hipassOutput, maxFrames, (int) sampleRate );
+       QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", 0, result );
+       
+       MakeCleanRecording( &recording, &testTone );
+       
+       // Apply DC step.
+       for( i=stepPosition; i<recording.numFrames; i++ )
+       {
+               recording.buffer[i] += stepAmplitude;
+       }
+       
+       // Use high pass as a DC blocker!
+       BiquadFilter_SetupHighPass( &hipassFilter, 10.0 / sampleRate, 0.5 );
+       PaQa_FilterRecording( &recording, &hipassOutput, &hipassFilter );
+       
+       testTone.amplitude = 0.5;
+       PaQa_AnalyseRecording( &hipassOutput, &testTone, &analysisResult );
+       
+#if PRINT_REPORTS
+       printf("\n=== InitialSpike Analysis ===================\n");
+       printf("                        expected      actual\n");
+       printf("             latency: %10.3f  %10.3f\n", (double)expectedLatency, analysisResult.latency );
+       printf("         popPosition: %10.3f\n", analysisResult.popPosition );  
+       printf("        popAmplitude: %10.3f\n", analysisResult.popAmplitude ); 
+       printf("      amplitudeRatio: %10.3f\n", analysisResult.amplitudeRatio );       
+       printf("           cycleSize: %6d\n", cycleSize );      
+       printf("    num added frames: %10.3f\n", analysisResult.numAddedFrames );
+       printf("     added frames at: %10.3f\n", analysisResult.addedFramesPosition );
+       printf("  num dropped frames: %10.3f\n", analysisResult.numDroppedFrames );
+       printf("   dropped frames at: %10.3f\n", analysisResult.droppedFramesPosition );
+#endif
+       
+       QA_ASSERT_CLOSE( "PaQa_AnalyseRecording latency", expectedLatency, analysisResult.latency, 4.0 );
+       QA_ASSERT_EQUALS( "PaQa_AnalyseRecording no pop from step", -1, (int) analysisResult.popPosition );     
+       PaQa_TerminateRecording( &recording );
+       PaQa_TerminateRecording( &hipassOutput );
+       return 0;
+       
+error:
+       PaQa_SaveRecordingToWaveFile( &recording, "bad_step_original.wav" );
+       PaQa_SaveRecordingToWaveFile( &hipassOutput, "bad_step_hipass.wav" );
+       PaQa_TerminateRecording( &recording);   
+       PaQa_TerminateRecording( &hipassOutput );
+       return 1;
+}      
+
 /*==========================================================================================*/
 /**
  * Test various dropped sample scenarios.
@@ -431,6 +519,39 @@ static int TestDetectPops( void )
        return 0;
 }
 
+/*==========================================================================================*/
+/**
+ * Test analysis when there is a DC offset step before the sine signal.
+ */
+static int TestInitialSpike( void )
+{
+       int result;
+       
+       // No spike.
+       result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.0 );
+       if( result < 0 ) return result;
+       
+       // Small spike.
+       result = TestSingleInitialSpike( 44100, 32, 100, 537, 0.1 );
+       if( result < 0 ) return result;
+       
+       // short pop like Ross's error.
+       result = TestSingleInitialSpike( 8000, 32, 42, 2000, 0.1 );
+       if( result < 0 ) return result;
+       
+       // Medium spike.
+       result = TestSingleInitialSpike( 44100, 40, 190, 3000, 0.5 );
+       if( result < 0 ) return result;
+       
+       // Spike near sine.
+       //result = TestSingleInitialSpike( 44100, 2900, 140, 3000, 0.1 );
+       if( result < 0 ) return result;
+       
+       
+       return 0;
+}
+
+
 #if TEST_SAVED_WAVE
 /*==========================================================================================*/
 /**
@@ -588,5 +709,9 @@ int PaQa_TestAnalyzer( void )
        // Detect pops that get back in phase.
        if ((result = TestDetectPops()) != 0) return result;
        
+       // Test to see if the latency detector can be tricked like it was on Ross' Windows machine.
+       if ((result = TestInitialSpike()) != 0) return result;
+       
+
        return 0;
 }