From 9471fc8daede4bb8c7e0763efe226ba50ac593be Mon Sep 17 00:00:00 2001 From: philburk Date: Tue, 1 Mar 2011 22:48:50 +0000 Subject: [PATCH] Add check for clipping and noise in loopback test. --- qa/loopback/src/audio_analyzer.c | 66 +++++++++- qa/loopback/src/audio_analyzer.h | 16 +++ qa/loopback/src/paqa.c | 216 +++++++++++++++++++++++++------ 3 files changed, 258 insertions(+), 40 deletions(-) diff --git a/qa/loopback/src/audio_analyzer.c b/qa/loopback/src/audio_analyzer.c index 60299d0..1464e8d 100644 --- a/qa/loopback/src/audio_analyzer.c +++ b/qa/loopback/src/audio_analyzer.c @@ -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 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; istest; + + // 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; iinputDevice, 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; iinputDevice ); - 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 0 ) - { - PaQa_AnalyzeLoopbackConnection( userOptions, i, j, expectedAmplitude ); - numLoopbacks += 1; - } + + for( iIn=0; iIn 0) ); + QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) ); return numLoopbacks; error: -- 2.43.0