Add latency control in loopback to fix Linux latency problems.

This commit is contained in:
philburk 2011-03-02 21:03:45 +00:00
commit 0c00ae8845

View file

@ -52,12 +52,13 @@
int g_testsPassed = 0;
int g_testsFailed = 0;
#define MAX_NUM_GENERATORS (8)
#define MAX_NUM_RECORDINGS (8)
#define MAX_BACKGROUND_NOISE_RMS (0.0004)
#define MAX_NUM_GENERATORS (8)
#define MAX_NUM_RECORDINGS (8)
#define MAX_BACKGROUND_NOISE_RMS (0.0004)
#define LOOPBACK_DETECTION_DURATION_SECONDS (0.8)
#define DEFAULT_FRAMES_PER_BUFFER (64)
#define PAQA_WAIT_STREAM_MSEC (100)
#define DEFAULT_FRAMES_PER_BUFFER (128)
#define DEFAULT_HIGH_LATENCY_MSEC (100)
#define PAQA_WAIT_STREAM_MSEC (100)
// Use two separate streams instead of one full duplex stream.
#define PAQA_FLAG_TWO_STREAMS (1<<0)
@ -103,6 +104,8 @@ typedef struct LoopbackContext_s
int outputUnderflowCount;
int inputOverflowCount;
int outputOverflowCount;
int minFramesPerBuffer;
int maxFramesPerBuffer;
int primingCount;
TestParameters *test;
volatile int done;
@ -142,7 +145,15 @@ static int RecordAndPlaySinesCallback( const void *inputBuffer, void *outputBuff
if( statusFlags & paOutputUnderflow ) loopbackContext->outputUnderflowCount += 1;
if( statusFlags & paOutputOverflow ) loopbackContext->outputOverflowCount += 1;
if( statusFlags & paPrimingOutput ) loopbackContext->primingCount += 1;
if( framesPerBuffer > loopbackContext->maxFramesPerBuffer )
{
loopbackContext->maxFramesPerBuffer = framesPerBuffer;
}
if( framesPerBuffer < loopbackContext->minFramesPerBuffer )
{
loopbackContext->minFramesPerBuffer = framesPerBuffer;
}
/* This may get called with NULL inputBuffer during initial setup.
* We may also use the same callback with output only streams.
*/
@ -292,7 +303,7 @@ int PaQa_RunLoopbackHalfDuplex( LoopbackContext *loopbackContext )
// Wait for stream to finish.
while( loopbackContext->done == 0 )
{
Pa_Sleep(50);
Pa_Sleep(PAQA_WAIT_STREAM_MSEC);
}
err = Pa_StopStream( inStream );
@ -346,7 +357,7 @@ int PaQa_RunInputOnly( LoopbackContext *loopbackContext )
// Wait for stream to finish.
while( loopbackContext->done == 0 )
{
Pa_Sleep(50);
Pa_Sleep(PAQA_WAIT_STREAM_MSEC);
}
err = Pa_StopStream( inStream );
@ -632,6 +643,7 @@ static int PaQa_SetupLoopbackContext( LoopbackContext *loopbackContextPtr, TestP
PaQa_SetupSineGenerator( &loopbackContextPtr->generators[i], PaQa_GetNthFrequency( testParams->baseFrequency, i ),
testParams->amplitude, testParams->sampleRate );
}
loopbackContextPtr->minFramesPerBuffer = 0x0FFFFFFF;
return 0;
error:
return -1;
@ -641,10 +653,13 @@ error:
static void PaQa_TeardownLoopbackContext( LoopbackContext *loopbackContextPtr )
{
int i;
for( i=0; i<loopbackContextPtr->test->samplesPerFrame; i++ )
if( loopbackContextPtr->test != NULL )
{
PaQa_TerminateRecording( &loopbackContextPtr->recordings[i] );
}
for( i=0; i<loopbackContextPtr->test->samplesPerFrame; i++ )
{
PaQa_TerminateRecording( &loopbackContextPtr->recordings[i] );
}
}
}
/*******************************************************************/
@ -766,6 +781,7 @@ static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *te
printf( "\n" );
// Sleep to see if this lets the audio drivers stabilize.
Pa_Sleep( 500 );
PaQa_TeardownLoopbackContext( &loopbackContext );
@ -778,6 +794,7 @@ static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *te
error:
PaQa_TeardownLoopbackContext( &loopbackContext );
printf( "\n" );
g_testsFailed += 1;
return err;
}
@ -786,7 +803,6 @@ static void PaQa_SetDefaultTestParameters( TestParameters *testParamsPtr, PaDevi
{
memset( testParamsPtr, 0, sizeof(TestParameters) );
testParamsPtr->samplesPerFrame = 2;
testParamsPtr->amplitude = 0.5;
testParamsPtr->sampleRate = 44100;
@ -798,14 +814,34 @@ static void PaQa_SetDefaultTestParameters( TestParameters *testParamsPtr, PaDevi
testParamsPtr->inputParameters.device = inputDevice;
testParamsPtr->inputParameters.sampleFormat = paFloat32;
testParamsPtr->inputParameters.channelCount = testParamsPtr->samplesPerFrame;
testParamsPtr->inputParameters.suggestedLatency =
Pa_GetDeviceInfo( inputDevice )->defaultLowInputLatency;
testParamsPtr->inputParameters.suggestedLatency = DEFAULT_HIGH_LATENCY_MSEC * 0.001;
testParamsPtr->outputParameters.device = outputDevice;
testParamsPtr->outputParameters.sampleFormat = paFloat32;
testParamsPtr->outputParameters.channelCount = testParamsPtr->samplesPerFrame;
testParamsPtr->outputParameters.suggestedLatency =
Pa_GetDeviceInfo( outputDevice )->defaultLowOutputLatency;
testParamsPtr->outputParameters.suggestedLatency = DEFAULT_HIGH_LATENCY_MSEC * 0.001;
}
/*******************************************************************/
static void PaQa_OverrideTestParameters( TestParameters *testParamsPtr, UserOptions *userOptions )
{
// Check to see if a specific value was requested.
if( userOptions->sampleRate >= 0 )
{
testParamsPtr->sampleRate = userOptions->sampleRate;
}
if( userOptions->framesPerBuffer >= 0 )
{
testParamsPtr->framesPerBuffer = userOptions->framesPerBuffer;
}
if( userOptions->latency >= 0 )
{
testParamsPtr->inputParameters.suggestedLatency = userOptions->latency * 0.001;
testParamsPtr->outputParameters.suggestedLatency = userOptions->latency * 0.001;
}
printf( " running with suggested latency (msec): input = %5.2f, out = %5.2f\n",
(testParamsPtr->inputParameters.suggestedLatency * 1000.0),
(testParamsPtr->outputParameters.suggestedLatency * 1000.0) );
}
/*******************************************************************/
@ -820,22 +856,20 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
int iSize;
int iFormat;
int totalBadChannels = 0;
int savedValue;
TestParameters testParams;
const PaDeviceInfo *inputDeviceInfo;
const PaDeviceInfo *outputDeviceInfo;
inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );
outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );
double bestSampleRate = 44100.0;
int bestBufferSize = DEFAULT_FRAMES_PER_BUFFER;
printf( "=============== Analysing Loopback %d to %d ====================\n", outputDevice, inputDevice );
printf( " Devices: %s => %s\n", outputDeviceInfo->name, inputDeviceInfo->name);
// test half duplex first because it is more likely to work.
int flagSettings[] = { PAQA_FLAG_TWO_STREAMS, 0 };
int numFlagSettings = (sizeof(flagSettings)/sizeof(int));
double sampleRates[] = { 8000.0, 11025.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0, 96000.0 };
int numRates = (sizeof(sampleRates)/sizeof(double));
@ -846,22 +880,9 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
const char *sampleFormatNames[] = { "paUInt8", "paInt16", "paInt32" };
int numSampleFormats = (sizeof(sampleFormats)/sizeof(PaSampleFormat));
// Check to see if a specific value was requested.
if( userOptions->sampleRate > 0 )
{
sampleRates[0] = userOptions->sampleRate;
bestSampleRate = userOptions->sampleRate;
numRates = 1;
}
if( userOptions->framesPerBuffer > 0 )
{
framesPerBuffers[0] = userOptions->framesPerBuffer;
bestBufferSize = userOptions->framesPerBuffer;
numBufferSizes = 1;
}
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
testParams.maxFrames = (int) (0.5 * testParams.sampleRate);
PaQa_OverrideTestParameters( &testParams, userOptions );
// Loop though combinations of audio parameters.
for( iFlags=0; iFlags<numFlagSettings; iFlags++ )
@ -871,8 +892,8 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
(( testParams.flags & 1 ) ? s_FlagOnNames[0] : s_FlagOffNames[0]) );
printf("|-sRate-|-buffer-|-latency-|-channel results--------------------|\n");
// Loop though combinations of audio parameters.
testParams.framesPerBuffer = bestBufferSize;
// Loop though various sample rates.
savedValue = testParams.sampleRate;
for( iRate=0; iRate<numRates; iRate++ )
{
// SAMPLE RATE
@ -882,10 +903,12 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
totalBadChannels += numBadChannels;
}
testParams.sampleRate = savedValue;
testParams.maxFrames = (int) (1.2 * testParams.sampleRate);
printf( "\n" );
testParams.sampleRate = bestSampleRate;
testParams.maxFrames = (int) (1.2 * testParams.sampleRate);
// Loop through various buffer sizes.
savedValue = testParams.framesPerBuffer;
for( iSize=0; iSize<numBufferSizes; iSize++ )
{
// BUFFER SIZE
@ -894,13 +917,12 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
totalBadChannels += numBadChannels;
}
testParams.framesPerBuffer = savedValue;
printf( "\n" );
}
printf("Test Sample Formats using Half Duplex IO -----\n" );
testParams.flags = PAQA_FLAG_TWO_STREAMS;
testParams.sampleRate = bestSampleRate;
testParams.framesPerBuffer = bestBufferSize;
for( iFormat=0; iFormat<numSampleFormats; iFormat++ )
{
@ -971,21 +993,23 @@ int PaQa_MeasureBackgroundNoise( LoopbackContext *loopbackContextPtr, double *rm
*
* @return 1 if loopback connected, 0 if not, or negative error.
*/
int PaQa_CheckForLoopBack( PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
int PaQa_CheckForLoopBack( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
{
TestParameters testParams;
LoopbackContext loopbackContext;
LoopbackContext loopbackContext = { 0 };
const PaDeviceInfo *inputDeviceInfo;
const PaDeviceInfo *outputDeviceInfo;
PaError err = paNoError;
double minAmplitude;
inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );
QA_ASSERT_TRUE( "Pa_GetDeviceInfo for input returned NULL.", (inputDeviceInfo != NULL) );
if( inputDeviceInfo->maxInputChannels < 2 )
{
return 0;
}
outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );
QA_ASSERT_TRUE( "Pa_GetDeviceInfo for output returned NULL.", (outputDeviceInfo != NULL) );
if( outputDeviceInfo->maxOutputChannels < 2 )
{
return 0;
@ -993,7 +1017,17 @@ int PaQa_CheckForLoopBack( PaDeviceIndex inputDevice, PaDeviceIndex outputDevice
printf( "Look for loopback cable between \"%s\" => \"%s\"\n", outputDeviceInfo->name, inputDeviceInfo->name);
printf( " default suggested input latency (msec): low = %5.2f, high = %5.2f\n",
(inputDeviceInfo->defaultLowInputLatency * 1000.0),
(inputDeviceInfo->defaultHighInputLatency * 1000.0) );
printf( " default suggested output latency (msec): low = %5.2f, high = %5.2f\n",
(outputDeviceInfo->defaultLowInputLatency * 1000.0),
(outputDeviceInfo->defaultHighInputLatency * 1000.0) );
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
PaQa_OverrideTestParameters( &testParams, userOptions );
testParams.maxFrames = (int) (LOOPBACK_DETECTION_DURATION_SECONDS * testParams.sampleRate);
minAmplitude = testParams.amplitude / 2.0;
@ -1007,7 +1041,7 @@ int PaQa_CheckForLoopBack( PaDeviceIndex inputDevice, PaDeviceIndex outputDevice
{
printf( "Output not supported for this format!\n" );
return 0;
}
}!
PaQa_SetupLoopbackContext( &loopbackContext, &testParams );
@ -1065,11 +1099,10 @@ int PaQa_CheckForLoopBack( PaDeviceIndex inputDevice, PaDeviceIndex outputDevice
loopbackConnected = 0;
}
else if( rms > MAX_BACKGROUND_NOISE_RMS )
{ printf("ERROR - There is too much background noise on this input!\n");
{
printf("ERROR - There is too much background noise on this input!\n");
loopbackConnected = 0;
}
}
PaQa_TeardownLoopbackContext( &loopbackContext );
@ -1087,7 +1120,7 @@ error:
static int CheckLoopbackAndScan( UserOptions *userOptions,
PaDeviceIndex iIn, PaDeviceIndex iOut )
{
int loopbackConnected = PaQa_CheckForLoopBack( iIn, iOut );
int loopbackConnected = PaQa_CheckForLoopBack( userOptions, iIn, iOut );
if( loopbackConnected > 0 )
{
PaQa_AnalyzeLoopbackConnection( userOptions, iIn, iOut );
@ -1125,6 +1158,7 @@ static int ScanForLoopback(UserOptions *userOptions)
else if (userOptions->outputDevice >= 0)
{
// Just scan for input.
QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) );
for( iIn=0; iIn<numDevices; iIn++ )
{
numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, userOptions->outputDevice );
@ -1136,6 +1170,7 @@ static int ScanForLoopback(UserOptions *userOptions)
for( iOut=0; iOut<numDevices; iOut++ )
{
QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) );
for( iIn=0; iIn<numDevices; iIn++ )
{
numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, iOut );
@ -1222,7 +1257,7 @@ void usage( const char *name )
printf("%s [-i# -o# -l# -r# -s# -m -w -dDir]\n", name);
printf(" -i# Input device ID. Will scan for loopback cable if not specified.\n");
printf(" -o# Output device ID. Will scan for loopback if not specified.\n");
// printf(" -l# Latency in milliseconds.\n");
printf(" -l# Latency in milliseconds.\n");
printf(" -r# Sample Rate in Hz. Will use multiple common rates if not specified.\n");
printf(" -s# Size of callback buffer in frames, framesPerBuffer. Will use common values if not specified.\n");
printf(" -w Save bad recordings in a WAV file.\n");
@ -1243,6 +1278,9 @@ int main( int argc, char **argv )
memset(&userOptions, 0, sizeof(userOptions));
userOptions.inputDevice = paNoDevice;
userOptions.outputDevice = paNoDevice;
userOptions.sampleRate = -1;
userOptions.framesPerBuffer = -1;
userOptions.latency = -1;
userOptions.waveFilePath = ".";
// Process arguments. Skip name of executable.
@ -1271,6 +1309,7 @@ int main( int argc, char **argv )
break;
case 'm':
userOptions.framesPerBuffer = -1;
printf("Option -m set so just testing math and not the audio devices.\n");
justMath = 1;
break;