diff options
author | sanine <sanine.not@pm.me> | 2022-08-25 14:54:53 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2022-08-25 14:54:53 -0500 |
commit | 37c97e345d12f95dde44e1d1a4c2f2aadd4615bc (patch) | |
tree | e1bb25bc855883062bdd7847ff2c04290f71c840 /portaudio/qa/loopback/src/paqa.c | |
parent | 5634c7b04da619669f2f29f6798c03982be05180 (diff) |
add initial structure
Diffstat (limited to 'portaudio/qa/loopback/src/paqa.c')
-rw-r--r-- | portaudio/qa/loopback/src/paqa.c | 1601 |
1 files changed, 1601 insertions, 0 deletions
diff --git a/portaudio/qa/loopback/src/paqa.c b/portaudio/qa/loopback/src/paqa.c new file mode 100644 index 0000000..5eb6283 --- /dev/null +++ b/portaudio/qa/loopback/src/paqa.c @@ -0,0 +1,1601 @@ + +/* + * 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. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <memory.h> +#include <math.h> +#include <string.h> + +#include "portaudio.h" + +#include "qa_tools.h" + +#include "paqa_tools.h" +#include "audio_analyzer.h" +#include "test_audio_analyzer.h" + +/** Accumulate counts for how many tests pass or fail. */ +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 LOOPBACK_DETECTION_DURATION_SECONDS (0.8) +#define DEFAULT_FRAMES_PER_BUFFER (0) +#define PAQA_WAIT_STREAM_MSEC (100) +#define PAQA_TEST_DURATION (1.2) + +// Use two separate streams instead of one full duplex stream. +#define PAQA_FLAG_TWO_STREAMS (1<<0) +// Use bloching read/write for loopback. +#define PAQA_FLAG_USE_BLOCKING_IO (1<<1) + +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; + +#define BIG_BUFFER_SIZE (sizeof(float) * 2 * 2 * 1024) +static unsigned char g_ReadWriteBuffer[BIG_BUFFER_SIZE]; + +#define MAX_CONVERSION_SAMPLES (2 * 32 * 1024) +#define CONVERSION_BUFFER_SIZE (sizeof(float) * 2 * MAX_CONVERSION_SAMPLES) +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]; +#ifdef WIN32 + _snprintf( filename, sizeof(filename), "%s\\paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ ); +#else + snprintf( filename, sizeof(filename), "%s/paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ ); +#endif + 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; + } +} |