Add check for clipping and noise in loopback test.
This commit is contained in:
parent
33a9013437
commit
9471fc8dae
3 changed files with 258 additions and 40 deletions
|
|
@ -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 )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Reference in a new issue