Spaces:
Runtime error
Runtime error
/* | |
* PortAudio Portable Real-Time Audio Library | |
* Latest Version at: http://www.portaudio.com | |
* | |
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining | |
* a copy of this software and associated documentation files | |
* (the "Software"), to deal in the Software without restriction, | |
* including without limitation the rights to use, copy, modify, merge, | |
* publish, distribute, sublicense, and/or sell copies of the Software, | |
* and to permit persons to whom the Software is furnished to do so, | |
* subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be | |
* included in all copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. | |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR | |
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | |
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
/* | |
* The text above constitutes the entire PortAudio license; however, | |
* the PortAudio community also makes the following non-binding requests: | |
* | |
* Any person wishing to distribute modifications to the Software is | |
* requested to send the modifications to the original developer so that | |
* they can be incorporated into the canonical version. It is also | |
* requested that these non-binding requests be included along with the | |
* license above. | |
*/ | |
/** Accumulate counts for how many tests pass or fail. */ | |
int g_testsPassed = 0; | |
int g_testsFailed = 0; | |
// Use two separate streams instead of one full duplex stream. | |
// Use bloching read/write for loopback. | |
const char * s_FlagOnNames[] = | |
{ | |
"Two Streams (Half Duplex)", | |
"Blocking Read/Write" | |
}; | |
const char * s_FlagOffNames[] = | |
{ | |
"One Stream (Full Duplex)", | |
"Callback" | |
}; | |
/** Parameters that describe a single test run. */ | |
typedef struct TestParameters_s | |
{ | |
PaStreamParameters inputParameters; | |
PaStreamParameters outputParameters; | |
double sampleRate; | |
int samplesPerFrame; | |
int framesPerBuffer; | |
int maxFrames; | |
double baseFrequency; | |
double amplitude; | |
PaStreamFlags streamFlags; // paClipOff, etc | |
int flags; // PAQA_FLAG_TWO_STREAMS, PAQA_FLAG_USE_BLOCKING_IO | |
} TestParameters; | |
typedef struct LoopbackContext_s | |
{ | |
// Generate a unique signal on each channel. | |
PaQaSineGenerator generators[MAX_NUM_GENERATORS]; | |
// Record each channel individually. | |
PaQaRecording recordings[MAX_NUM_RECORDINGS]; | |
// Reported by the stream after it's opened | |
PaTime streamInfoInputLatency; | |
PaTime streamInfoOutputLatency; | |
// Measured at runtime. | |
volatile int callbackCount; // incremented for each callback | |
volatile int inputBufferCount; // incremented if input buffer not NULL | |
int inputUnderflowCount; | |
int inputOverflowCount; | |
volatile int outputBufferCount; // incremented if output buffer not NULL | |
int outputOverflowCount; | |
int outputUnderflowCount; | |
// Measure whether input or output is lagging behind. | |
volatile int minInputOutputDelta; | |
volatile int maxInputOutputDelta; | |
int minFramesPerBuffer; | |
int maxFramesPerBuffer; | |
int primingCount; | |
TestParameters *test; | |
volatile int done; | |
} LoopbackContext; | |
typedef struct UserOptions_s | |
{ | |
int sampleRate; | |
int framesPerBuffer; | |
int inputLatency; | |
int outputLatency; | |
int saveBadWaves; | |
int verbose; | |
int waveFileCount; | |
const char *waveFilePath; | |
PaDeviceIndex inputDevice; | |
PaDeviceIndex outputDevice; | |
} UserOptions; | |
static unsigned char g_ReadWriteBuffer[BIG_BUFFER_SIZE]; | |
static unsigned char g_ConversionBuffer[CONVERSION_BUFFER_SIZE]; | |
/*******************************************************************/ | |
static int RecordAndPlaySinesCallback( const void *inputBuffer, void *outputBuffer, | |
unsigned long framesPerBuffer, | |
const PaStreamCallbackTimeInfo* timeInfo, | |
PaStreamCallbackFlags statusFlags, | |
void *userData ) | |
{ | |
int i; | |
LoopbackContext *loopbackContext = (LoopbackContext *) userData; | |
loopbackContext->callbackCount += 1; | |
if( statusFlags & paInputUnderflow ) loopbackContext->inputUnderflowCount += 1; | |
if( statusFlags & paInputOverflow ) loopbackContext->inputOverflowCount += 1; | |
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. | |
*/ | |
if( inputBuffer != NULL) | |
{ | |
int channelsPerFrame = loopbackContext->test->inputParameters.channelCount; | |
float *in = (float *)inputBuffer; | |
PaSampleFormat inFormat = loopbackContext->test->inputParameters.sampleFormat; | |
loopbackContext->inputBufferCount += 1; | |
if( inFormat != paFloat32 ) | |
{ | |
int samplesToConvert = framesPerBuffer * channelsPerFrame; | |
in = (float *) g_ConversionBuffer; | |
if( samplesToConvert > MAX_CONVERSION_SAMPLES ) | |
{ | |
// Hack to prevent buffer overflow. | |
// @todo Loop with small buffer instead of failing. | |
printf("Format conversion buffer too small!\n"); | |
return paComplete; | |
} | |
PaQa_ConvertToFloat( inputBuffer, samplesToConvert, inFormat, (float *) g_ConversionBuffer ); | |
} | |
// Read each channel from the buffer. | |
for( i=0; i<channelsPerFrame; i++ ) | |
{ | |
loopbackContext->done |= PaQa_WriteRecording( &loopbackContext->recordings[i], | |
in + i, | |
framesPerBuffer, | |
channelsPerFrame ); | |
} | |
} | |
if( outputBuffer != NULL ) | |
{ | |
int channelsPerFrame = loopbackContext->test->outputParameters.channelCount; | |
float *out = (float *)outputBuffer; | |
PaSampleFormat outFormat = loopbackContext->test->outputParameters.sampleFormat; | |
loopbackContext->outputBufferCount += 1; | |
if( outFormat != paFloat32 ) | |
{ | |
// If we need to convert then mix to the g_ConversionBuffer and then convert into the PA outputBuffer. | |
out = (float *) g_ConversionBuffer; | |
} | |
PaQa_EraseBuffer( out, framesPerBuffer, channelsPerFrame ); | |
for( i=0; i<channelsPerFrame; i++ ) | |
{ | |
PaQa_MixSine( &loopbackContext->generators[i], | |
out + i, | |
framesPerBuffer, | |
channelsPerFrame ); | |
} | |
if( outFormat != paFloat32 ) | |
{ | |
int samplesToConvert = framesPerBuffer * channelsPerFrame; | |
if( samplesToConvert > MAX_CONVERSION_SAMPLES ) | |
{ | |
printf("Format conversion buffer too small!\n"); | |
return paComplete; | |
} | |
PaQa_ConvertFromFloat( out, framesPerBuffer * channelsPerFrame, outFormat, outputBuffer ); | |
} | |
} | |
// Measure whether the input or output are lagging behind. | |
// Don't measure lag at end. | |
if( !loopbackContext->done ) | |
{ | |
int inputOutputDelta = loopbackContext->inputBufferCount - loopbackContext->outputBufferCount; | |
if( loopbackContext->maxInputOutputDelta < inputOutputDelta ) | |
{ | |
loopbackContext->maxInputOutputDelta = inputOutputDelta; | |
} | |
if( loopbackContext->minInputOutputDelta > inputOutputDelta ) | |
{ | |
loopbackContext->minInputOutputDelta = inputOutputDelta; | |
} | |
} | |
return loopbackContext->done ? paComplete : paContinue; | |
} | |
static void CopyStreamInfoToLoopbackContext( LoopbackContext *loopbackContext, PaStream *inputStream, PaStream *outputStream ) | |
{ | |
const PaStreamInfo *inputStreamInfo = Pa_GetStreamInfo( inputStream ); | |
const PaStreamInfo *outputStreamInfo = Pa_GetStreamInfo( outputStream ); | |
loopbackContext->streamInfoInputLatency = inputStreamInfo ? inputStreamInfo->inputLatency : -1; | |
loopbackContext->streamInfoOutputLatency = outputStreamInfo ? outputStreamInfo->outputLatency : -1; | |
} | |
/*******************************************************************/ | |
/** | |
* Open a full duplex audio stream. | |
* Generate sine waves on the output channels and record the input channels. | |
* Then close the stream. | |
* @return 0 if OK or negative error. | |
*/ | |
int PaQa_RunLoopbackFullDuplex( LoopbackContext *loopbackContext ) | |
{ | |
PaStream *stream = NULL; | |
PaError err = 0; | |
TestParameters *test = loopbackContext->test; | |
loopbackContext->done = 0; | |
// Use one full duplex stream. | |
err = Pa_OpenStream( | |
&stream, | |
&test->inputParameters, | |
&test->outputParameters, | |
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; | |
CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream ); | |
err = Pa_StartStream( stream ); | |
if( err != paNoError ) goto error; | |
// Wait for stream to finish. | |
while( loopbackContext->done == 0 ) | |
{ | |
Pa_Sleep(PAQA_WAIT_STREAM_MSEC); | |
} | |
err = Pa_StopStream( stream ); | |
if( err != paNoError ) goto error; | |
err = Pa_CloseStream( stream ); | |
if( err != paNoError ) goto error; | |
return 0; | |
error: | |
return err; | |
} | |
/*******************************************************************/ | |
/** | |
* Open two audio streams, one for input and one for output. | |
* Generate sine waves on the output channels and record the input channels. | |
* Then close the stream. | |
* @return 0 if OK or paTimedOut. | |
*/ | |
int PaQa_WaitForStream( LoopbackContext *loopbackContext ) | |
{ | |
int timeoutMSec = 1000 * PAQA_TEST_DURATION * 2; | |
// Wait for stream to finish or timeout. | |
while( (loopbackContext->done == 0) && (timeoutMSec > 0) ) | |
{ | |
Pa_Sleep(PAQA_WAIT_STREAM_MSEC); | |
timeoutMSec -= PAQA_WAIT_STREAM_MSEC; | |
} | |
if( loopbackContext->done == 0 ) | |
{ | |
printf("ERROR - stream completion timed out!"); | |
return paTimedOut; | |
} | |
return 0; | |
} | |
/*******************************************************************/ | |
/** | |
* Open two audio streams, one for input and one for output. | |
* Generate sine waves on the output channels and record the input channels. | |
* Then close the stream. | |
* @return 0 if OK or negative error. | |
*/ | |
int PaQa_RunLoopbackHalfDuplex( LoopbackContext *loopbackContext ) | |
{ | |
PaStream *inStream = NULL; | |
PaStream *outStream = NULL; | |
PaError err = 0; | |
int timedOut = 0; | |
TestParameters *test = loopbackContext->test; | |
loopbackContext->done = 0; | |
// Use two half duplex streams. | |
err = Pa_OpenStream( | |
&inStream, | |
&test->inputParameters, | |
NULL, | |
test->sampleRate, | |
test->framesPerBuffer, | |
test->streamFlags, | |
RecordAndPlaySinesCallback, | |
loopbackContext ); | |
if( err != paNoError ) goto error; | |
err = Pa_OpenStream( | |
&outStream, | |
NULL, | |
&test->outputParameters, | |
test->sampleRate, | |
test->framesPerBuffer, | |
test->streamFlags, | |
RecordAndPlaySinesCallback, | |
loopbackContext ); | |
if( err != paNoError ) goto error; | |
CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream ); | |
err = Pa_StartStream( inStream ); | |
if( err != paNoError ) goto error; | |
// Start output later so we catch the beginning of the waveform. | |
err = Pa_StartStream( outStream ); | |
if( err != paNoError ) goto error; | |
timedOut = PaQa_WaitForStream( loopbackContext ); | |
err = Pa_StopStream( inStream ); | |
if( err != paNoError ) goto error; | |
err = Pa_StopStream( outStream ); | |
if( err != paNoError ) goto error; | |
err = Pa_CloseStream( inStream ); | |
if( err != paNoError ) goto error; | |
err = Pa_CloseStream( outStream ); | |
if( err != paNoError ) goto error; | |
return timedOut; | |
error: | |
return err; | |
} | |
/*******************************************************************/ | |
/** | |
* 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; | |
int timedOut = 0; | |
TestParameters *test = loopbackContext->test; | |
loopbackContext->done = 0; | |
// 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; | |
timedOut = PaQa_WaitForStream( loopbackContext ); | |
err = Pa_StopStream( inStream ); | |
if( err != paNoError ) goto error; | |
err = Pa_CloseStream( inStream ); | |
if( err != paNoError ) goto error; | |
return timedOut; | |
error: | |
return err; | |
} | |
/*******************************************************************/ | |
static int RecordAndPlayBlockingIO( PaStream *inStream, | |
PaStream *outStream, | |
LoopbackContext *loopbackContext | |
) | |
{ | |
int i; | |
float *in = (float *)g_ReadWriteBuffer; | |
float *out = (float *)g_ReadWriteBuffer; | |
PaError err; | |
int done = 0; | |
long available; | |
const long maxPerBuffer = 64; | |
TestParameters *test = loopbackContext->test; | |
long framesPerBuffer = test->framesPerBuffer; | |
if( framesPerBuffer <= 0 ) | |
{ | |
framesPerBuffer = maxPerBuffer; // bigger values might run past end of recording | |
} | |
// Read in audio. | |
err = Pa_ReadStream( inStream, in, framesPerBuffer ); | |
// Ignore an overflow on the first read. | |
//if( !((loopbackContext->callbackCount == 0) && (err == paInputOverflowed)) ) | |
if( err != paInputOverflowed ) | |
{ | |
QA_ASSERT_EQUALS( "Pa_ReadStream failed", paNoError, err ); | |
} | |
else | |
{ | |
loopbackContext->inputOverflowCount += 1; | |
} | |
// Save in a recording. | |
for( i=0; i<loopbackContext->test->inputParameters.channelCount; i++ ) | |
{ | |
done |= PaQa_WriteRecording( &loopbackContext->recordings[i], | |
in + i, | |
framesPerBuffer, | |
loopbackContext->test->inputParameters.channelCount ); | |
} | |
// Synthesize audio. | |
available = Pa_GetStreamWriteAvailable( outStream ); | |
if( available > (2*framesPerBuffer) ) available = (2*framesPerBuffer); | |
PaQa_EraseBuffer( out, available, loopbackContext->test->outputParameters.channelCount ); | |
for( i=0; i<loopbackContext->test->outputParameters.channelCount; i++ ) | |
{ | |
PaQa_MixSine( &loopbackContext->generators[i], | |
out + i, | |
available, | |
loopbackContext->test->outputParameters.channelCount ); | |
} | |
// Write out audio. | |
err = Pa_WriteStream( outStream, out, available ); | |
// Ignore an underflow on the first write. | |
//if( !((loopbackContext->callbackCount == 0) && (err == paOutputUnderflowed)) ) | |
if( err != paOutputUnderflowed ) | |
{ | |
QA_ASSERT_EQUALS( "Pa_WriteStream failed", paNoError, err ); | |
} | |
else | |
{ | |
loopbackContext->outputUnderflowCount += 1; | |
} | |
loopbackContext->callbackCount += 1; | |
return done; | |
error: | |
return err; | |
} | |
/*******************************************************************/ | |
/** | |
* Open two audio streams with non-blocking IO. | |
* Generate sine waves on the output channels and record the input channels. | |
* Then close the stream. | |
* @return 0 if OK or negative error. | |
*/ | |
int PaQa_RunLoopbackHalfDuplexBlockingIO( LoopbackContext *loopbackContext ) | |
{ | |
PaStream *inStream = NULL; | |
PaStream *outStream = NULL; | |
PaError err = 0; | |
TestParameters *test = loopbackContext->test; | |
// Use two half duplex streams. | |
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 */ | |
NULL, // causes non-blocking IO | |
NULL ); | |
if( err != paNoError ) goto error1; | |
err = Pa_OpenStream( | |
&outStream, | |
NULL, | |
&test->outputParameters, | |
test->sampleRate, | |
test->framesPerBuffer, | |
paClipOff, /* we won't output out of range samples so don't bother clipping them */ | |
NULL, // causes non-blocking IO | |
NULL ); | |
if( err != paNoError ) goto error2; | |
CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream ); | |
err = Pa_StartStream( outStream ); | |
if( err != paNoError ) goto error3; | |
err = Pa_StartStream( inStream ); | |
if( err != paNoError ) goto error3; | |
while( err == 0 ) | |
{ | |
err = RecordAndPlayBlockingIO( inStream, outStream, loopbackContext ); | |
if( err < 0 ) goto error3; | |
} | |
err = Pa_StopStream( inStream ); | |
if( err != paNoError ) goto error3; | |
err = Pa_StopStream( outStream ); | |
if( err != paNoError ) goto error3; | |
err = Pa_CloseStream( outStream ); | |
if( err != paNoError ) goto error2; | |
err = Pa_CloseStream( inStream ); | |
if( err != paNoError ) goto error1; | |
return 0; | |
error3: | |
Pa_CloseStream( outStream ); | |
error2: | |
Pa_CloseStream( inStream ); | |
error1: | |
return err; | |
} | |
/*******************************************************************/ | |
/** | |
* Open one audio stream with non-blocking IO. | |
* Generate sine waves on the output channels and record the input channels. | |
* Then close the stream. | |
* @return 0 if OK or negative error. | |
*/ | |
int PaQa_RunLoopbackFullDuplexBlockingIO( LoopbackContext *loopbackContext ) | |
{ | |
PaStream *stream = NULL; | |
PaError err = 0; | |
TestParameters *test = loopbackContext->test; | |
// Use one full duplex stream. | |
err = Pa_OpenStream( | |
&stream, | |
&test->inputParameters, | |
&test->outputParameters, | |
test->sampleRate, | |
test->framesPerBuffer, | |
paClipOff, /* we won't output out of range samples so don't bother clipping them */ | |
NULL, // causes non-blocking IO | |
NULL ); | |
if( err != paNoError ) goto error1; | |
CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream ); | |
err = Pa_StartStream( stream ); | |
if( err != paNoError ) goto error2; | |
while( err == 0 ) | |
{ | |
err = RecordAndPlayBlockingIO( stream, stream, loopbackContext ); | |
if( err < 0 ) goto error2; | |
} | |
err = Pa_StopStream( stream ); | |
if( err != paNoError ) goto error2; | |
err = Pa_CloseStream( stream ); | |
if( err != paNoError ) goto error1; | |
return 0; | |
error2: | |
Pa_CloseStream( stream ); | |
error1: | |
return err; | |
} | |
/*******************************************************************/ | |
/** | |
* Run some kind of loopback test. | |
* @return 0 if OK or negative error. | |
*/ | |
int PaQa_RunLoopback( LoopbackContext *loopbackContext ) | |
{ | |
PaError err = 0; | |
TestParameters *test = loopbackContext->test; | |
if( test->flags & PAQA_FLAG_TWO_STREAMS ) | |
{ | |
if( test->flags & PAQA_FLAG_USE_BLOCKING_IO ) | |
{ | |
err = PaQa_RunLoopbackHalfDuplexBlockingIO( loopbackContext ); | |
} | |
else | |
{ | |
err = PaQa_RunLoopbackHalfDuplex( loopbackContext ); | |
} | |
} | |
else | |
{ | |
if( test->flags & PAQA_FLAG_USE_BLOCKING_IO ) | |
{ | |
err = PaQa_RunLoopbackFullDuplexBlockingIO( loopbackContext ); | |
} | |
else | |
{ | |
err = PaQa_RunLoopbackFullDuplex( loopbackContext ); | |
} | |
} | |
if( err != paNoError ) | |
{ | |
printf("PortAudio error = %s\n", Pa_GetErrorText( err ) ); | |
} | |
return err; | |
} | |
/*******************************************************************/ | |
static int PaQa_SaveTestResultToWaveFile( UserOptions *userOptions, PaQaRecording *recording ) | |
{ | |
if( userOptions->saveBadWaves ) | |
{ | |
char filename[256]; | |
_snprintf( filename, sizeof(filename), "%s\\paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ ); | |
snprintf( filename, sizeof(filename), "%s/paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ ); | |
printf( "\"%s\", ", filename ); | |
return PaQa_SaveRecordingToWaveFile( recording, filename ); | |
} | |
return 0; | |
} | |
/*******************************************************************/ | |
static int PaQa_SetupLoopbackContext( LoopbackContext *loopbackContextPtr, TestParameters *testParams ) | |
{ | |
int i; | |
// Setup loopback context. | |
memset( loopbackContextPtr, 0, sizeof(LoopbackContext) ); | |
loopbackContextPtr->test = testParams; | |
for( i=0; i<testParams->samplesPerFrame; i++ ) | |
{ | |
int err = PaQa_InitializeRecording( &loopbackContextPtr->recordings[i], testParams->maxFrames, testParams->sampleRate ); | |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", paNoError, err ); | |
} | |
for( i=0; i<testParams->samplesPerFrame; i++ ) | |
{ | |
PaQa_SetupSineGenerator( &loopbackContextPtr->generators[i], PaQa_GetNthFrequency( testParams->baseFrequency, i ), | |
testParams->amplitude, testParams->sampleRate ); | |
} | |
loopbackContextPtr->minFramesPerBuffer = 0x0FFFFFFF; | |
return 0; | |
error: | |
return -1; | |
} | |
/*******************************************************************/ | |
static void PaQa_TeardownLoopbackContext( LoopbackContext *loopbackContextPtr ) | |
{ | |
int i; | |
if( loopbackContextPtr->test != NULL ) | |
{ | |
for( i=0; i<loopbackContextPtr->test->samplesPerFrame; i++ ) | |
{ | |
PaQa_TerminateRecording( &loopbackContextPtr->recordings[i] ); | |
} | |
} | |
} | |
/*******************************************************************/ | |
static void PaQa_PrintShortErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel ) | |
{ | |
printf("channel %d ", channel); | |
if( analysisResultPtr->popPosition > 0 ) | |
{ | |
printf("POP %0.3f at %d, ", (double)analysisResultPtr->popAmplitude, (int)analysisResultPtr->popPosition ); | |
} | |
else | |
{ | |
if( analysisResultPtr->addedFramesPosition > 0 ) | |
{ | |
printf("ADD %d at %d ", (int)analysisResultPtr->numAddedFrames, (int)analysisResultPtr->addedFramesPosition ); | |
} | |
if( analysisResultPtr->droppedFramesPosition > 0 ) | |
{ | |
printf("DROP %d at %d ", (int)analysisResultPtr->numDroppedFrames, (int)analysisResultPtr->droppedFramesPosition ); | |
} | |
} | |
} | |
/*******************************************************************/ | |
static void PaQa_PrintFullErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel ) | |
{ | |
printf("\n=== Loopback Analysis ===================\n"); | |
printf(" channel: %d\n", channel ); | |
printf(" latency: %10.3f\n", analysisResultPtr->latency ); | |
printf(" amplitudeRatio: %10.3f\n", (double)analysisResultPtr->amplitudeRatio ); | |
printf(" popPosition: %10.3f\n", (double)analysisResultPtr->popPosition ); | |
printf(" popAmplitude: %10.3f\n", (double)analysisResultPtr->popAmplitude ); | |
printf(" num added frames: %10.3f\n", analysisResultPtr->numAddedFrames ); | |
printf(" added frames at: %10.3f\n", analysisResultPtr->addedFramesPosition ); | |
printf(" num dropped frames: %10.3f\n", analysisResultPtr->numDroppedFrames ); | |
printf(" dropped frames at: %10.3f\n", analysisResultPtr->droppedFramesPosition ); | |
} | |
/*******************************************************************/ | |
/** | |
* Test loopback connection using the given parameters. | |
* @return number of channels with glitches, or negative error. | |
*/ | |
static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *testParams ) | |
{ | |
int i; | |
LoopbackContext loopbackContext; | |
PaError err = paNoError; | |
PaQaTestTone testTone; | |
PaQaAnalysisResult analysisResult; | |
int numBadChannels = 0; | |
printf("| %5d | %6d | ", ((int)(testParams->sampleRate+0.5)), testParams->framesPerBuffer ); | |
fflush(stdout); | |
testTone.samplesPerFrame = testParams->samplesPerFrame; | |
testTone.sampleRate = testParams->sampleRate; | |
testTone.amplitude = testParams->amplitude; | |
testTone.startDelay = 0; | |
err = PaQa_SetupLoopbackContext( &loopbackContext, testParams ); | |
if( err ) return err; | |
err = PaQa_RunLoopback( &loopbackContext ); | |
QA_ASSERT_TRUE("loopback did not run", (loopbackContext.callbackCount > 1) ); | |
printf( "%7.2f %7.2f %7.2f | ", | |
loopbackContext.streamInfoInputLatency * 1000.0, | |
loopbackContext.streamInfoOutputLatency * 1000.0, | |
(loopbackContext.streamInfoInputLatency + loopbackContext.streamInfoOutputLatency) * 1000.0 | |
); | |
printf( "%4d/%4d/%4d, %4d/%4d/%4d | ", | |
loopbackContext.inputOverflowCount, | |
loopbackContext.inputUnderflowCount, | |
loopbackContext.inputBufferCount, | |
loopbackContext.outputOverflowCount, | |
loopbackContext.outputUnderflowCount, | |
loopbackContext.outputBufferCount | |
); | |
// Analyse recording to detect glitches. | |
for( i=0; i<testParams->samplesPerFrame; i++ ) | |
{ | |
double freq = PaQa_GetNthFrequency( testParams->baseFrequency, i ); | |
testTone.frequency = freq; | |
PaQa_AnalyseRecording( &loopbackContext.recordings[i], &testTone, &analysisResult ); | |
if( i==0 ) | |
{ | |
double latencyMSec; | |
printf( "%4d-%4d | ", | |
loopbackContext.minFramesPerBuffer, | |
loopbackContext.maxFramesPerBuffer | |
); | |
latencyMSec = 1000.0 * analysisResult.latency / testParams->sampleRate; | |
printf("%7.2f | ", latencyMSec ); | |
} | |
if( analysisResult.valid ) | |
{ | |
int badChannel = ( (analysisResult.popPosition > 0) | |
|| (analysisResult.addedFramesPosition > 0) | |
|| (analysisResult.droppedFramesPosition > 0) ); | |
if( badChannel ) | |
{ | |
if( userOptions->verbose ) | |
{ | |
PaQa_PrintFullErrorReport( &analysisResult, i ); | |
} | |
else | |
{ | |
PaQa_PrintShortErrorReport( &analysisResult, i ); | |
} | |
PaQa_SaveTestResultToWaveFile( userOptions, &loopbackContext.recordings[i] ); | |
} | |
numBadChannels += badChannel; | |
} | |
else | |
{ | |
printf( "[%d] No or low signal, ampRatio = %f", i, analysisResult.amplitudeRatio ); | |
numBadChannels += 1; | |
} | |
} | |
if( numBadChannels == 0 ) | |
{ | |
printf( "OK" ); | |
} | |
// Print the # errors so far to make it easier to see where the error occurred. | |
printf( " - #errs = %d\n", g_testsFailed ); | |
PaQa_TeardownLoopbackContext( &loopbackContext ); | |
if( numBadChannels > 0 ) | |
{ | |
g_testsFailed += 1; | |
} | |
return numBadChannels; | |
error: | |
PaQa_TeardownLoopbackContext( &loopbackContext ); | |
printf( "\n" ); | |
g_testsFailed += 1; | |
return err; | |
} | |
/*******************************************************************/ | |
static void PaQa_SetDefaultTestParameters( TestParameters *testParamsPtr, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice ) | |
{ | |
memset( testParamsPtr, 0, sizeof(TestParameters) ); | |
testParamsPtr->samplesPerFrame = 2; | |
testParamsPtr->amplitude = 0.5; | |
testParamsPtr->sampleRate = 44100; | |
testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate); | |
testParamsPtr->framesPerBuffer = DEFAULT_FRAMES_PER_BUFFER; | |
testParamsPtr->baseFrequency = 200.0; | |
testParamsPtr->flags = PAQA_FLAG_TWO_STREAMS; | |
testParamsPtr->streamFlags = paClipOff; /* we won't output out of range samples so don't bother clipping them */ | |
testParamsPtr->inputParameters.device = inputDevice; | |
testParamsPtr->inputParameters.sampleFormat = paFloat32; | |
testParamsPtr->inputParameters.channelCount = testParamsPtr->samplesPerFrame; | |
testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultLowInputLatency; | |
//testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultHighInputLatency; | |
testParamsPtr->outputParameters.device = outputDevice; | |
testParamsPtr->outputParameters.sampleFormat = paFloat32; | |
testParamsPtr->outputParameters.channelCount = testParamsPtr->samplesPerFrame; | |
testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultLowOutputLatency; | |
//testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultHighOutputLatency; | |
} | |
/*******************************************************************/ | |
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; | |
testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate); | |
} | |
if( userOptions->framesPerBuffer >= 0 ) | |
{ | |
testParamsPtr->framesPerBuffer = userOptions->framesPerBuffer; | |
} | |
if( userOptions->inputLatency >= 0 ) | |
{ | |
testParamsPtr->inputParameters.suggestedLatency = userOptions->inputLatency * 0.001; | |
} | |
if( userOptions->outputLatency >= 0 ) | |
{ | |
testParamsPtr->outputParameters.suggestedLatency = userOptions->outputLatency * 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) ); | |
} | |
/*******************************************************************/ | |
/** | |
* Run a series of tests on this loopback connection. | |
* @return number of bad channel results | |
*/ | |
static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice ) | |
{ | |
int iFlags; | |
int iRate; | |
int iSize; | |
int iFormat; | |
int savedValue; | |
TestParameters testParams; | |
const PaDeviceInfo *inputDeviceInfo = Pa_GetDeviceInfo( inputDevice ); | |
const PaDeviceInfo *outputDeviceInfo = Pa_GetDeviceInfo( outputDevice ); | |
int totalBadChannels = 0; | |
// 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)); | |
// framesPerBuffer==0 means PA decides on the buffer size. | |
int framesPerBuffers[] = { 0, 16, 32, 40, 64, 100, 128, 256, 512, 1024 }; | |
int numBufferSizes = (sizeof(framesPerBuffers)/sizeof(int)); | |
PaSampleFormat sampleFormats[] = { paFloat32, paUInt8, paInt8, paInt16, paInt32 }; | |
const char *sampleFormatNames[] = { "paFloat32", "paUInt8", "paInt8", "paInt16", "paInt32" }; | |
int numSampleFormats = (sizeof(sampleFormats)/sizeof(PaSampleFormat)); | |
printf( "=============== Analysing Loopback %d to %d =====================\n", outputDevice, inputDevice ); | |
printf( " Devices: %s => %s\n", outputDeviceInfo->name, inputDeviceInfo->name); | |
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice ); | |
PaQa_OverrideTestParameters( &testParams, userOptions ); | |
// Loop though combinations of audio parameters. | |
for( iFlags=0; iFlags<numFlagSettings; iFlags++ ) | |
{ | |
int numRuns = 0; | |
testParams.flags = flagSettings[iFlags]; | |
printf( "\n************ Mode = %s ************\n", | |
(( testParams.flags & 1 ) ? s_FlagOnNames[0] : s_FlagOffNames[0]) ); | |
printf("|- requested -|- stream info latency -|- measured ------------------------------\n"); | |
printf("|-sRate-|-fr/buf-|- in - out - total -|- over/under/calls for in, out -|- frm/buf -|-latency-|- channel results -\n"); | |
// Loop though various sample rates. | |
if( userOptions->sampleRate < 0 ) | |
{ | |
savedValue = testParams.sampleRate; | |
for( iRate=0; iRate<numRates; iRate++ ) | |
{ | |
int numBadChannels; | |
// SAMPLE RATE | |
testParams.sampleRate = sampleRates[iRate]; | |
testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate); | |
numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams ); | |
totalBadChannels += numBadChannels; | |
} | |
testParams.sampleRate = savedValue; | |
testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate); | |
printf( "\n" ); | |
numRuns += 1; | |
} | |
// Loop through various buffer sizes. | |
if( userOptions->framesPerBuffer < 0 ) | |
{ | |
savedValue = testParams.framesPerBuffer; | |
for( iSize=0; iSize<numBufferSizes; iSize++ ) | |
{ | |
int numBadChannels; | |
// BUFFER SIZE | |
testParams.framesPerBuffer = framesPerBuffers[iSize]; | |
numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams ); | |
totalBadChannels += numBadChannels; | |
} | |
testParams.framesPerBuffer = savedValue; | |
printf( "\n" ); | |
numRuns += 1; | |
} | |
// Run one with single parameters in case we did not do a series. | |
if( numRuns == 0 ) | |
{ | |
int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams ); | |
totalBadChannels += numBadChannels; | |
} | |
} | |
printf("\nTest Sample Formats using Half Duplex IO -----\n" ); | |
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice ); | |
testParams.flags = PAQA_FLAG_TWO_STREAMS; | |
for( iFlags= 0; iFlags<4; iFlags++ ) | |
{ | |
// Cycle through combinations of flags. | |
testParams.streamFlags = 0; | |
if( iFlags & 1 ) testParams.streamFlags |= paClipOff; | |
if( iFlags & 2 ) testParams.streamFlags |= paDitherOff; | |
for( iFormat=0; iFormat<numSampleFormats; iFormat++ ) | |
{ | |
int numBadChannels; | |
PaSampleFormat format = sampleFormats[ iFormat ]; | |
testParams.inputParameters.sampleFormat = format; | |
testParams.outputParameters.sampleFormat = format; | |
printf("Sample format = %d = %s, PaStreamFlags = 0x%02X\n", (int) format, sampleFormatNames[iFormat], (unsigned int) testParams.streamFlags ); | |
numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams ); | |
totalBadChannels += numBadChannels; | |
} | |
} | |
printf( "\n" ); | |
printf( "****************************************\n"); | |
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; | |
*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. | |
* | |
* @return 1 if loopback connected, 0 if not, or negative error. | |
*/ | |
int PaQa_CheckForLoopBack( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice ) | |
{ | |
TestParameters testParams; | |
LoopbackContext loopbackContext; | |
const PaDeviceInfo *inputDeviceInfo; | |
const PaDeviceInfo *outputDeviceInfo; | |
PaError err = paNoError; | |
double minAmplitude; | |
int loopbackIsConnected; | |
int startFrame, numFrames; | |
double magLeft, magRight; | |
inputDeviceInfo = Pa_GetDeviceInfo( inputDevice ); | |
if( inputDeviceInfo == NULL ) | |
{ | |
printf("ERROR - Pa_GetDeviceInfo for input returned NULL.\n"); | |
return paInvalidDevice; | |
} | |
if( inputDeviceInfo->maxInputChannels < 2 ) | |
{ | |
return 0; | |
} | |
outputDeviceInfo = Pa_GetDeviceInfo( outputDevice ); | |
if( outputDeviceInfo == NULL ) | |
{ | |
printf("ERROR - Pa_GetDeviceInfo for output returned NULL.\n"); | |
return paInvalidDevice; | |
} | |
if( outputDeviceInfo->maxOutputChannels < 2 ) | |
{ | |
return 0; | |
} | |
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->defaultLowOutputLatency * 1000.0), | |
(outputDeviceInfo->defaultHighOutputLatency * 1000.0) ); | |
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice ); | |
PaQa_OverrideTestParameters( &testParams, userOptions ); | |
testParams.maxFrames = (int) (LOOPBACK_DETECTION_DURATION_SECONDS * testParams.sampleRate); | |
minAmplitude = testParams.amplitude / 4.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 ); | |
if( inputDevice == outputDevice ) | |
{ | |
// Use full duplex if checking for loopback on one device. | |
testParams.flags &= ~PAQA_FLAG_TWO_STREAMS; | |
} | |
else | |
{ | |
// Use half duplex if checking for loopback on two different device. | |
testParams.flags = PAQA_FLAG_TWO_STREAMS; | |
} | |
err = PaQa_RunLoopback( &loopbackContext ); | |
QA_ASSERT_TRUE("loopback detection callback did not run", (loopbackContext.callbackCount > 1) ); | |
// Analyse recording to see if we captured the output. | |
// Start in the middle assuming past latency. | |
startFrame = testParams.maxFrames/2; | |
numFrames = testParams.maxFrames/2; | |
magLeft = PaQa_CorrelateSine( &loopbackContext.recordings[0], | |
loopbackContext.generators[0].frequency, | |
testParams.sampleRate, | |
startFrame, numFrames, NULL ); | |
magRight = PaQa_CorrelateSine( &loopbackContext.recordings[1], | |
loopbackContext.generators[1].frequency, | |
testParams.sampleRate, | |
startFrame, numFrames, NULL ); | |
printf(" Amplitudes: left = %f, right = %f\n", magLeft, magRight ); | |
// Check for backwards cable. | |
loopbackIsConnected = ((magLeft > minAmplitude) && (magRight > minAmplitude)); | |
if( !loopbackIsConnected ) | |
{ | |
double magLeftReverse = PaQa_CorrelateSine( &loopbackContext.recordings[0], | |
loopbackContext.generators[1].frequency, | |
testParams.sampleRate, | |
startFrame, numFrames, NULL ); | |
double magRightReverse = PaQa_CorrelateSine( &loopbackContext.recordings[1], | |
loopbackContext.generators[0].frequency, | |
testParams.sampleRate, | |
startFrame, numFrames, NULL ); | |
if ((magLeftReverse > minAmplitude) && (magRightReverse>minAmplitude)) | |
{ | |
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. | |
loopbackIsConnected = 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"); | |
loopbackIsConnected = 0; | |
} | |
else if( rms > MAX_BACKGROUND_NOISE_RMS ) | |
{ | |
printf("ERROR - There is too much background noise on this input!\n"); | |
loopbackIsConnected = 0; | |
} | |
} | |
PaQa_TeardownLoopbackContext( &loopbackContext ); | |
return loopbackIsConnected; | |
error: | |
PaQa_TeardownLoopbackContext( &loopbackContext ); | |
return err; | |
} | |
/*******************************************************************/ | |
/** | |
* If there is a loopback connection then run the analysis. | |
*/ | |
static int CheckLoopbackAndScan( UserOptions *userOptions, | |
PaDeviceIndex iIn, PaDeviceIndex iOut ) | |
{ | |
int loopbackConnected = PaQa_CheckForLoopBack( userOptions, iIn, iOut ); | |
if( loopbackConnected > 0 ) | |
{ | |
PaQa_AnalyzeLoopbackConnection( userOptions, iIn, iOut ); | |
return 1; | |
} | |
return 0; | |
} | |
/*******************************************************************/ | |
/** | |
* Scan every combination of output to input device. | |
* If a loopback is found the analyse the combination. | |
* The scan can be overridden using the -i and -o command line options. | |
*/ | |
static int ScanForLoopback(UserOptions *userOptions) | |
{ | |
PaDeviceIndex iIn,iOut; | |
int numLoopbacks = 0; | |
int numDevices; | |
numDevices = Pa_GetDeviceCount(); | |
// If both devices are specified then just use that combination. | |
if ((userOptions->inputDevice >= 0) && (userOptions->outputDevice >= 0)) | |
{ | |
numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, userOptions->outputDevice ); | |
} | |
else if (userOptions->inputDevice >= 0) | |
{ | |
// Just scan for output. | |
for( iOut=0; iOut<numDevices; iOut++ ) | |
{ | |
numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, iOut ); | |
} | |
} | |
else if (userOptions->outputDevice >= 0) | |
{ | |
// Just scan for input. | |
for( iIn=0; iIn<numDevices; iIn++ ) | |
{ | |
numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, userOptions->outputDevice ); | |
} | |
} | |
else | |
{ | |
// Scan both. | |
for( iOut=0; iOut<numDevices; iOut++ ) | |
{ | |
for( iIn=0; iIn<numDevices; iIn++ ) | |
{ | |
numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, iOut ); | |
} | |
} | |
} | |
QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) ); | |
return numLoopbacks; | |
error: | |
return -1; | |
} | |
/*==========================================================================================*/ | |
int TestSampleFormatConversion( void ) | |
{ | |
int i; | |
const float floatInput[] = { 1.0, 0.5, -0.5, -1.0 }; | |
const char charInput[] = { 127, 64, -64, -128 }; | |
const unsigned char ucharInput[] = { 255, 128+64, 64, 0 }; | |
const short shortInput[] = { 32767, 32768/2, -32768/2, -32768 }; | |
const int intInput[] = { 2147483647, 2147483647/2, -1073741824 /*-2147483648/2 doesn't work in msvc*/, -2147483648 }; | |
float floatOutput[4]; | |
short shortOutput[4]; | |
int intOutput[4]; | |
unsigned char ucharOutput[4]; | |
char charOutput[4]; | |
QA_ASSERT_EQUALS("int must be 32-bit", 4, (int) sizeof(int) ); | |
QA_ASSERT_EQUALS("short must be 16-bit", 2, (int) sizeof(short) ); | |
// from Float ====== | |
PaQa_ConvertFromFloat( floatInput, 4, paUInt8, ucharOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paUInt8 -> error", ucharInput[i], ucharOutput[i], 1 ); | |
} | |
PaQa_ConvertFromFloat( floatInput, 4, paInt8, charOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt8 -> error", charInput[i], charOutput[i], 1 ); | |
} | |
PaQa_ConvertFromFloat( floatInput, 4, paInt16, shortOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt16 error", shortInput[i], shortOutput[i], 1 ); | |
} | |
PaQa_ConvertFromFloat( floatInput, 4, paInt32, intOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt32 error", intInput[i], intOutput[i], 0x00010000 ); | |
} | |
// to Float ====== | |
memset( floatOutput, 0, sizeof(floatOutput) ); | |
PaQa_ConvertToFloat( ucharInput, 4, paUInt8, floatOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE( "paUInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 ); | |
} | |
memset( floatOutput, 0, sizeof(floatOutput) ); | |
PaQa_ConvertToFloat( charInput, 4, paInt8, floatOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE( "paInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 ); | |
} | |
memset( floatOutput, 0, sizeof(floatOutput) ); | |
PaQa_ConvertToFloat( shortInput, 4, paInt16, floatOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE( "paInt16 -> paFloat32 error", floatInput[i], floatOutput[i], 0.001 ); | |
} | |
memset( floatOutput, 0, sizeof(floatOutput) ); | |
PaQa_ConvertToFloat( intInput, 4, paInt32, floatOutput ); | |
for( i=0; i<4; i++ ) | |
{ | |
QA_ASSERT_CLOSE( "paInt32 -> paFloat32 error", floatInput[i], floatOutput[i], 0.00001 ); | |
} | |
return 0; | |
error: | |
return -1; | |
} | |
/*******************************************************************/ | |
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 for both input and output in milliseconds.\n"); | |
printf(" --inputLatency # Input latency in milliseconds.\n"); | |
printf(" --outputLatency # Output 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"); | |
printf(" -dDir - Path for Directory for WAV files. Default is current directory.\n"); | |
printf(" -m - Just test the DSP Math code and not the audio devices.\n"); | |
printf(" -v - Verbose reports.\n"); | |
} | |
/*******************************************************************/ | |
int main( int argc, char **argv ) | |
{ | |
int i; | |
UserOptions userOptions; | |
int result = 0; | |
int justMath = 0; | |
char *executableName = argv[0]; | |
printf("PortAudio LoopBack Test built " __DATE__ " at " __TIME__ "\n"); | |
if( argc > 1 ){ | |
printf("running with arguments:"); | |
for(i=1; i < argc; ++i ) | |
printf(" %s", argv[i] ); | |
printf("\n"); | |
}else{ | |
printf("running with no arguments\n"); | |
} | |
memset(&userOptions, 0, sizeof(userOptions)); | |
userOptions.inputDevice = paNoDevice; | |
userOptions.outputDevice = paNoDevice; | |
userOptions.sampleRate = -1; | |
userOptions.framesPerBuffer = -1; | |
userOptions.inputLatency = -1; | |
userOptions.outputLatency = -1; | |
userOptions.waveFilePath = "."; | |
// Process arguments. Skip name of executable. | |
i = 1; | |
while( i<argc ) | |
{ | |
char *arg = argv[i]; | |
if( arg[0] == '-' ) | |
{ | |
switch(arg[1]) | |
{ | |
case 'i': | |
userOptions.inputDevice = atoi(&arg[2]); | |
break; | |
case 'o': | |
userOptions.outputDevice = atoi(&arg[2]); | |
break; | |
case 'l': | |
userOptions.inputLatency = userOptions.outputLatency = atoi(&arg[2]); | |
break; | |
case 'r': | |
userOptions.sampleRate = atoi(&arg[2]); | |
break; | |
case 's': | |
userOptions.framesPerBuffer = atoi(&arg[2]); | |
break; | |
case 'm': | |
printf("Option -m set so just testing math and not the audio devices.\n"); | |
justMath = 1; | |
break; | |
case 'w': | |
userOptions.saveBadWaves = 1; | |
break; | |
case 'd': | |
userOptions.waveFilePath = &arg[2]; | |
break; | |
case 'v': | |
userOptions.verbose = 1; | |
break; | |
case 'h': | |
usage( executableName ); | |
exit(0); | |
break; | |
case '-': | |
{ | |
if( strcmp( &arg[2], "inputLatency" ) == 0 ) | |
{ | |
i += 1; | |
userOptions.inputLatency = atoi(argv[i]); | |
} | |
else if( strcmp( &arg[2], "outputLatency" ) == 0 ) | |
{ | |
i += 1; | |
userOptions.outputLatency = atoi(argv[i]); | |
} | |
else | |
{ | |
printf("Illegal option: %s\n", arg); | |
usage( executableName ); | |
exit(1); | |
} | |
} | |
break; | |
default: | |
printf("Illegal option: %s\n", arg); | |
usage( executableName ); | |
exit(1); | |
break; | |
} | |
} | |
else | |
{ | |
printf("Illegal argument: %s\n", arg); | |
usage( executableName ); | |
exit(1); | |
} | |
i += 1; | |
} | |
result = PaQa_TestAnalyzer(); | |
// Test sample format conversion tool. | |
result = TestSampleFormatConversion(); | |
if( (result == 0) && (justMath == 0) ) | |
{ | |
Pa_Initialize(); | |
printf( "PortAudio version number = %d\nPortAudio version text = '%s'\n", | |
Pa_GetVersion(), Pa_GetVersionText() ); | |
printf( "=============== PortAudio Devices ========================\n" ); | |
PaQa_ListAudioDevices(); | |
if( Pa_GetDeviceCount() == 0 ) | |
printf( "no devices found.\n" ); | |
printf( "=============== Detect Loopback ==========================\n" ); | |
ScanForLoopback(&userOptions); | |
Pa_Terminate(); | |
} | |
if (g_testsFailed == 0) | |
{ | |
printf("PortAudio QA SUCCEEDED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed ); | |
return 0; | |
} | |
else | |
{ | |
printf("PortAudio QA FAILED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed ); | |
return 1; | |
} | |
} | |