]> Repos - portaudio/commitdiff
Add check for clipping and noise in loopback test.
authorphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Tue, 1 Mar 2011 22:48:50 +0000 (22:48 +0000)
committerphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Tue, 1 Mar 2011 22:48:50 +0000 (22:48 +0000)
qa/loopback/src/audio_analyzer.c
qa/loopback/src/audio_analyzer.h
qa/loopback/src/paqa.c

index 60299d0d79dd7a71f151ae0f315b8c0387d151ac..1464e8ddb22059657985d3afa4c2d7b871b1aac6 100644 (file)
@@ -236,6 +236,54 @@ error:
 #undef NUM_SAMPLES
 }
 
+/*==========================================================================================*/
+
+double PaQa_MeasureCrossingSlope( float *buffer, int numFrames )
+{
+       int i;
+       double slopeTotal = 0.0;
+       int slopeCount = 0;
+       float previous;
+       double averageSlope = 0.0;
+       
+       previous = buffer[0];
+       for( i=1; i<numFrames; i++ )
+       {
+               float current = buffer[i];
+               if( (current > 0.0) && (previous < 0.0) )
+               {
+                       double delta = current - previous;
+                       slopeTotal += delta;
+                       slopeCount += 1;
+               }
+               previous = current;
+       }
+       if( slopeCount > 0 )
+       {
+               averageSlope = slopeTotal / slopeCount;
+       }
+       return averageSlope;
+}
+
+/*==========================================================================================*/
+/*
+ * We can't just measure the peaks cuz they may be clipped.
+ * But the zero crossing should be intact.
+ * The measured slope of a sine wave at zero should be:
+ *
+ *   slope = sin( 2PI * frequency / sampleRate )
+ *
+ */
+double PaQa_MeasureSineAmplitudeBySlope( PaQaRecording *recording,
+                                                 double frequency, double frameRate,
+                                               int startFrame, int numFrames )
+{
+       float *buffer = &recording->buffer[startFrame];
+       double measuredSlope = PaQa_MeasureCrossingSlope( buffer, numFrames );
+       double unitySlope = sin( MATH_TWO_PI * frequency / frameRate );
+       double estimatedAmplitude = measuredSlope / unitySlope;
+       return estimatedAmplitude;
+}
 
 /*==========================================================================================*/
 double PaQa_CorrelateSine( PaQaRecording *recording, double frequency, double frameRate,
@@ -339,6 +387,22 @@ double PaQa_MeasureArea( float *buffer, int numFrames, int stride  )
        return area;
 }
 
+/*==========================================================================================*/
+// Measure the area under the curve by summing absolute value of each value.
+double PaQa_MeasureRootMeanSquare( float *buffer, int numFrames )
+{
+       int is;
+       double area = 0.0;
+       double root;
+       for( is=0; is<numFrames; is++ )
+       {
+               float value = *buffer++;
+               area += value * value;
+       }
+       root = sqrt( area );
+       return root / numFrames;
+}
+
 
 /*==========================================================================================*/
 // Compare the amplitudes of these two signals.
@@ -425,7 +489,7 @@ void PaQa_FadeInRecording( PaQaRecording *recording, int startFrame, int count )
 
 
 /*==========================================================================================*/
-/** Notch filter and high pass filter then detect remaining energy.
+/** Apply notch filter and high pass filter then detect remaining energy.
  */
 int PaQa_DetectPop( PaQaRecording *recording, PaQaTestTone *testTone, PaQaAnalysisResult *analysisResult )
 {      
index b3bec86d0161b26577f1ae9018b778686f5f4b03..78911083c4a9c82fa4173ac601c2a24ec484f3cc 100644 (file)
@@ -114,6 +114,12 @@ double PaQa_ComputePhaseDifference( double phase1, double phase2 );
  */
 double PaQa_MeasureArea( float *buffer, int numFrames, int stride  );
 
+/**
+ * Measure slope of the positive zero crossings.
+ */
+double PaQa_MeasureCrossingSlope( float *buffer, int numFrames );
+
+
 /**
  * Prepare an oscillator that can generate a sine tone for testing.
  */
@@ -156,6 +162,16 @@ double PaQa_CorrelateSine( PaQaRecording *recording, double frequency, double fr
 
 double PaQa_FindFirstMatch( PaQaRecording *recording, float *buffer, int numSamples, double tolerance  );
 
+/** 
+ * Estimate the original amplitude of a clipped sine wave by measuring
+ * its average slope at the zero crossings.
+ */
+double PaQa_MeasureSineAmplitudeBySlope( PaQaRecording *recording,
+                                                                               double frequency, double frameRate,
+                                                                               int startFrame, int numFrames );
+
+double PaQa_MeasureRootMeanSquare( float *buffer, int numFrames );
+
 /**
  * Compare the amplitudes of these two signals.
  * Return ratio of recorded signal over buffer signal.
index 4bf9e2f5661d2afcced9d4c85057d8882abe5cdf..f542b532a17e9c23fab7e4146e1a1157d197a1bb 100644 (file)
@@ -54,6 +54,7 @@ int g_testsFailed = 0;
 
 #define MAX_NUM_GENERATORS  (8)
 #define MAX_NUM_RECORDINGS  (8)
+#define MAX_BACKGROUND_NOISE_RMS (0.00001)
 #define LOOPBACK_DETECTION_DURATION_SECONDS  (0.5)
 
 // Use two separate streams instead of one full duplex stream.
@@ -276,6 +277,51 @@ error:
 }
 
 
+/*******************************************************************/
+/** 
+ * Open one audio streams, just for input.
+ * Record background level.
+ * Then close the stream.
+ * @return 0 if OK or negative error.
+ */
+int PaQa_RunInputOnly( LoopbackContext *loopbackContext )
+{
+       PaStream *inStream = NULL;
+       PaError err = 0;
+       TestParameters *test = loopbackContext->test;
+       
+       // Just open an input stream.
+       err = Pa_OpenStream(
+                                               &inStream,
+                                               &test->inputParameters,
+                                               NULL,
+                                               test->sampleRate,
+                                               test->framesPerBuffer,
+                                               paClipOff, /* We won't output out of range samples so don't bother clipping them. */
+                                               RecordAndPlaySinesCallback,
+                                               loopbackContext );
+       if( err != paNoError ) goto error;
+       
+       err = Pa_StartStream( inStream );
+       if( err != paNoError ) goto error;
+       
+       // Wait for stream to finish.
+       while( Pa_IsStreamActive( inStream ) )
+       {
+               Pa_Sleep(50);
+       }
+       
+       err = Pa_StopStream( inStream );
+       if( err != paNoError ) goto error;
+       
+       err = Pa_CloseStream( inStream );
+       if( err != paNoError ) goto error;
+       
+       return 0;
+       
+error:
+       return err;     
+}
 
 /*******************************************************************/
 static int RecordAndPlayBlockingIO( PaStream *inStream,
@@ -682,19 +728,23 @@ error:
 static void PaQa_SetDefaultTestParameters( TestParameters *testParamsPtr, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
 {
        memset( testParamsPtr, 0, sizeof(TestParameters) );
-       testParamsPtr->inputParameters.device = inputDevice;
-       testParamsPtr->outputParameters.device = outputDevice;
-       testParamsPtr->inputParameters.sampleFormat = paFloat32;
-       testParamsPtr->outputParameters.sampleFormat = paFloat32;
+       
+       
        testParamsPtr->samplesPerFrame = 2;
-       testParamsPtr->inputParameters.channelCount = testParamsPtr->samplesPerFrame;
-       testParamsPtr->outputParameters.channelCount = testParamsPtr->samplesPerFrame;
        testParamsPtr->amplitude = 0.5;
        testParamsPtr->sampleRate = 44100;
        testParamsPtr->maxFrames = (int) (1.0 * testParamsPtr->sampleRate);
        testParamsPtr->framesPerBuffer = DEFAULT_FRAMES_PER_BUFFER;
        testParamsPtr->baseFrequency = 200.0;
        testParamsPtr->flags = PAQA_FLAG_TWO_STREAMS;
+       
+       testParamsPtr->inputParameters.device = inputDevice;
+       testParamsPtr->inputParameters.sampleFormat = paFloat32;
+       testParamsPtr->inputParameters.channelCount = testParamsPtr->samplesPerFrame;
+       
+       testParamsPtr->outputParameters.device = outputDevice;
+       testParamsPtr->outputParameters.sampleFormat = paFloat32;
+       testParamsPtr->outputParameters.channelCount = testParamsPtr->samplesPerFrame;
 }
 
 /*******************************************************************/
@@ -779,6 +829,56 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
        return totalBadChannels;
 }
 
+/*******************************************************************/
+int PaQa_CheckForClippedLoopback( LoopbackContext *loopbackContextPtr )
+{
+       int clipped = 0;
+       TestParameters *testParamsPtr = loopbackContextPtr->test;
+       
+       // Start in the middle assuming past latency.
+       int startFrame = testParamsPtr->maxFrames/2;
+       int numFrames = testParamsPtr->maxFrames/2;
+       
+       // Check to see if the signal is clipped.
+       double amplitudeLeft = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[0],
+                                                                                                                       testParamsPtr->baseFrequency, testParamsPtr->sampleRate,
+                                                                                                                       startFrame, numFrames );
+       double gainLeft = amplitudeLeft / testParamsPtr->amplitude;
+       double amplitudeRight = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[1],
+                                                                                                                        testParamsPtr->baseFrequency, testParamsPtr->sampleRate,
+                                                                                                                        startFrame, numFrames );
+       double gainRight = amplitudeLeft / testParamsPtr->amplitude;
+       printf("   Loop gain: left = %f, right = %f\n", gainLeft, gainRight );
+
+       if( (amplitudeLeft > 1.0 ) || (amplitudeRight > 1.0) )
+       {
+               printf("ERROR - loop gain is too high. Should be around than 1.0. Please lower output level and/or input gain.\n" );
+               clipped = 1;
+       }
+       return clipped;
+}
+
+/*******************************************************************/
+int PaQa_MeasureBackgroundNoise( LoopbackContext *loopbackContextPtr, double *rmsPtr )
+{
+       int result = 0;
+       TestParameters *testParamsPtr = loopbackContextPtr->test;
+       *rmsPtr = 0.0;
+       // Rewind so we can record some input.
+       loopbackContextPtr->recordings[0].numFrames = 0;
+       loopbackContextPtr->recordings[1].numFrames = 0;
+       result = PaQa_RunInputOnly( loopbackContextPtr );
+       if( result == 0 )
+       {
+               double leftRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[0].buffer,
+                                                                                                       loopbackContextPtr->recordings[0].numFrames );
+               double rightRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[1].buffer,
+                                                                                                        loopbackContextPtr->recordings[1].numFrames );
+               *rmsPtr = (leftRMS + rightRMS) / 2.0;
+       }
+       return result;
+}
+
 /*******************************************************************/
 /** 
  * Output a sine wave then try to detect it on input.
@@ -792,7 +892,7 @@ int PaQa_CheckForLoopBack( PaDeviceIndex inputDevice, PaDeviceIndex outputDevice
     const   PaDeviceInfo *inputDeviceInfo;     
     const   PaDeviceInfo *outputDeviceInfo;            
        PaError err = paNoError;
-       double minAmplitude = 0.3;
+       double minAmplitude;
        
        inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );
        if( inputDeviceInfo->maxInputChannels < 2 )
@@ -809,6 +909,19 @@ int PaQa_CheckForLoopBack( PaDeviceIndex inputDevice, PaDeviceIndex outputDevice
        
        PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
        testParams.maxFrames = (int) (LOOPBACK_DETECTION_DURATION_SECONDS * testParams.sampleRate);     
+       minAmplitude = testParams.amplitude / 2.0;
+       
+       // Check to see if the selected formats are supported.
+       if( Pa_IsFormatSupported( &testParams.inputParameters, NULL, testParams.sampleRate ) != paFormatIsSupported )
+       {
+               printf( "Input not supported for this format!\n" );
+               return 0;
+       }
+       if( Pa_IsFormatSupported( NULL, &testParams.outputParameters, testParams.sampleRate ) != paFormatIsSupported )
+       {
+               printf( "Output not supported for this format!\n" );
+               return 0;
+       }
        
        PaQa_SetupLoopbackContext( &loopbackContext, &testParams );
                        
@@ -839,17 +952,39 @@ int PaQa_CheckForLoopBack( PaDeviceIndex inputDevice, PaDeviceIndex outputDevice
                                                                                                   testParams.sampleRate,
                                                                                                   startFrame, numFrames, NULL );
                
-               double magRightReverse = PaQa_CorrelateSine( &loopbackContext.recordings[1],
-                         loopbackContext.generators[0].frequency,
-                         testParams.sampleRate,
-                         startFrame, numFrames, NULL );
+               double magRightReverse = PaQa_CorrelateSine( &loopbackContext.recordings[1], 
+                                                                                                       loopbackContext.generators[0].frequency,
+                                                                                                       testParams.sampleRate,
+                                                                                                       startFrame, numFrames, NULL );
                
-               if ((magLeftReverse > 0.1) && (magRightReverse>minAmplitude))
+               if ((magLeftReverse > minAmplitude) && (magRightReverse>minAmplitude))
                {
-                       printf("WARNING - you seem to have the left and right channels swapped on the loopback cable!\n");
+                       printf("ERROR - You seem to have the left and right channels swapped on the loopback cable!\n");
                }
        }
-       
+       else
+       {
+               double rms = 0.0;
+               if( PaQa_CheckForClippedLoopback( &loopbackContext ) )
+               {
+                       // Clipped so don't use this loopback.
+                       loopbackConnected = 0;
+               }
+               
+               err = PaQa_MeasureBackgroundNoise( &loopbackContext, &rms );
+               printf("   Background noise = %f\n", rms );
+               if( err )
+               {
+                       printf("ERROR - Could not measure background noise on this input!\n");
+                       loopbackConnected = 0;
+               }
+               else if( rms > MAX_BACKGROUND_NOISE_RMS )
+               {                       printf("ERROR - There is too much background noise on this input!\n");
+                       loopbackConnected = 0;
+               }
+               
+                       
+       }
        
        PaQa_TeardownLoopbackContext( &loopbackContext );
        return loopbackConnected;       
@@ -859,6 +994,23 @@ error:
        return err;     
 }
 
+/*******************************************************************/
+/**
+ * If there is a loopback connection then run the analysis.
+ */
+static int CheckLoopbackAndScan( UserOptions *userOptions,
+                                                               PaDeviceIndex iIn, PaDeviceIndex iOut,
+                                                               double expectedAmplitude )
+{
+       int loopbackConnected = PaQa_CheckForLoopBack( iIn, iOut );
+       if( loopbackConnected > 0 )
+       {
+               PaQa_AnalyzeLoopbackConnection( userOptions, iIn, iOut, expectedAmplitude );
+               return 1;
+       }
+       return 0;
+}
+                                                               
 /*******************************************************************/
 /**
  * Scan every combination of output to input device.
@@ -867,7 +1019,7 @@ error:
  */
 static int ScanForLoopback(UserOptions *userOptions)
 {
-       PaDeviceIndex i,j;
+       PaDeviceIndex iIn,iOut;
        int  numLoopbacks = 0;
     int  numDevices;
     numDevices = Pa_GetDeviceCount();    
@@ -883,46 +1035,32 @@ static int ScanForLoopback(UserOptions *userOptions)
        else if (userOptions->inputDevice >= 0)
        {
                // Just scan for output.
-               for( i=0; i<numDevices; i++ )
+               for( iOut=0; iOut<numDevices; iOut++ )
                {                                       
-                       int loopbackConnected = PaQa_CheckForLoopBack( userOptions->inputDevice, i );
-                       if( loopbackConnected > 0 )
-                       {
-                               PaQa_AnalyzeLoopbackConnection( userOptions, userOptions->inputDevice, i, expectedAmplitude );
-                               numLoopbacks += 1;
-                       }
+                       numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, iOut, expectedAmplitude );
                }
        }
        else if (userOptions->outputDevice >= 0)
        {
                // Just scan for input.
-               for( i=0; i<numDevices; i++ )
+               for( iIn=0; iIn<numDevices; iIn++ )
                {                                       
-                       int loopbackConnected = PaQa_CheckForLoopBack( i, userOptions->inputDevice );
-                       if( loopbackConnected > 0 )
-                       {
-                               PaQa_AnalyzeLoopbackConnection( userOptions, i, userOptions->inputDevice, expectedAmplitude );
-                               numLoopbacks += 1;
-                       }
+                       numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, userOptions->outputDevice, expectedAmplitude );
                }
        }
        else 
        {       
                // Scan both.
-               for( i=0; i<numDevices; i++ )
+               for( iOut=0; iOut<numDevices; iOut++ )
                {
-                       for( j=0; j<numDevices; j++ )
-                       {
-                               int loopbackConnected = PaQa_CheckForLoopBack( i, j );
-                               if( loopbackConnected > 0 )
-                               {
-                                       PaQa_AnalyzeLoopbackConnection( userOptions, i, j, expectedAmplitude );
-                                       numLoopbacks += 1;
-                               }
+                       
+                       for( iIn=0; iIn<numDevices; iIn++ )
+                       {                               
+                               numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, iOut, expectedAmplitude );
                        }
                }
        }
-       QA_ASSERT_TRUE( "No loopback cables found or volumes too low.", (numLoopbacks > 0) );
+       QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) );
        return numLoopbacks;
        
 error: