To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.
The primary repository for this project is hosted at https://github.com/sonic-visualiser/sv-dependency-builds .
This repository is a read-only copy which is updated automatically every hour.
root / src / portaudio_20161030_catalina_patch / qa / loopback / src / paqa.c @ 162:d43aab368df9
History | View | Annotate | Download (48.6 KB)
| 1 |
|
|---|---|
| 2 |
/*
|
| 3 |
* PortAudio Portable Real-Time Audio Library
|
| 4 |
* Latest Version at: http://www.portaudio.com
|
| 5 |
*
|
| 6 |
* Copyright (c) 1999-2010 Phil Burk and Ross Bencina
|
| 7 |
*
|
| 8 |
* Permission is hereby granted, free of charge, to any person obtaining
|
| 9 |
* a copy of this software and associated documentation files
|
| 10 |
* (the "Software"), to deal in the Software without restriction,
|
| 11 |
* including without limitation the rights to use, copy, modify, merge,
|
| 12 |
* publish, distribute, sublicense, and/or sell copies of the Software,
|
| 13 |
* and to permit persons to whom the Software is furnished to do so,
|
| 14 |
* subject to the following conditions:
|
| 15 |
*
|
| 16 |
* The above copyright notice and this permission notice shall be
|
| 17 |
* included in all copies or substantial portions of the Software.
|
| 18 |
*
|
| 19 |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
| 20 |
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
| 21 |
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
| 22 |
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
| 23 |
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
| 24 |
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
| 25 |
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 26 |
*/
|
| 27 |
|
| 28 |
/*
|
| 29 |
* The text above constitutes the entire PortAudio license; however,
|
| 30 |
* the PortAudio community also makes the following non-binding requests:
|
| 31 |
*
|
| 32 |
* Any person wishing to distribute modifications to the Software is
|
| 33 |
* requested to send the modifications to the original developer so that
|
| 34 |
* they can be incorporated into the canonical version. It is also
|
| 35 |
* requested that these non-binding requests be included along with the
|
| 36 |
* license above.
|
| 37 |
*/
|
| 38 |
|
| 39 |
#include <stdio.h> |
| 40 |
#include <stdlib.h> |
| 41 |
#include <memory.h> |
| 42 |
#include <math.h> |
| 43 |
#include <string.h> |
| 44 |
|
| 45 |
#include "portaudio.h" |
| 46 |
|
| 47 |
#include "qa_tools.h" |
| 48 |
|
| 49 |
#include "paqa_tools.h" |
| 50 |
#include "audio_analyzer.h" |
| 51 |
#include "test_audio_analyzer.h" |
| 52 |
|
| 53 |
/** Accumulate counts for how many tests pass or fail. */
|
| 54 |
int g_testsPassed = 0; |
| 55 |
int g_testsFailed = 0; |
| 56 |
|
| 57 |
#define MAX_NUM_GENERATORS (8) |
| 58 |
#define MAX_NUM_RECORDINGS (8) |
| 59 |
#define MAX_BACKGROUND_NOISE_RMS (0.0004) |
| 60 |
#define LOOPBACK_DETECTION_DURATION_SECONDS (0.8) |
| 61 |
#define DEFAULT_FRAMES_PER_BUFFER (0) |
| 62 |
#define PAQA_WAIT_STREAM_MSEC (100) |
| 63 |
#define PAQA_TEST_DURATION (1.2) |
| 64 |
|
| 65 |
// Use two separate streams instead of one full duplex stream.
|
| 66 |
#define PAQA_FLAG_TWO_STREAMS (1<<0) |
| 67 |
// Use bloching read/write for loopback.
|
| 68 |
#define PAQA_FLAG_USE_BLOCKING_IO (1<<1) |
| 69 |
|
| 70 |
const char * s_FlagOnNames[] = |
| 71 |
{
|
| 72 |
"Two Streams (Half Duplex)",
|
| 73 |
"Blocking Read/Write"
|
| 74 |
}; |
| 75 |
|
| 76 |
const char * s_FlagOffNames[] = |
| 77 |
{
|
| 78 |
"One Stream (Full Duplex)",
|
| 79 |
"Callback"
|
| 80 |
}; |
| 81 |
|
| 82 |
|
| 83 |
/** Parameters that describe a single test run. */
|
| 84 |
typedef struct TestParameters_s |
| 85 |
{
|
| 86 |
PaStreamParameters inputParameters; |
| 87 |
PaStreamParameters outputParameters; |
| 88 |
double sampleRate;
|
| 89 |
int samplesPerFrame;
|
| 90 |
int framesPerBuffer;
|
| 91 |
int maxFrames;
|
| 92 |
double baseFrequency;
|
| 93 |
double amplitude;
|
| 94 |
PaStreamFlags streamFlags; // paClipOff, etc
|
| 95 |
int flags; // PAQA_FLAG_TWO_STREAMS, PAQA_FLAG_USE_BLOCKING_IO |
| 96 |
} TestParameters; |
| 97 |
|
| 98 |
typedef struct LoopbackContext_s |
| 99 |
{
|
| 100 |
// Generate a unique signal on each channel.
|
| 101 |
PaQaSineGenerator generators[MAX_NUM_GENERATORS]; |
| 102 |
// Record each channel individually.
|
| 103 |
PaQaRecording recordings[MAX_NUM_RECORDINGS]; |
| 104 |
|
| 105 |
// Reported by the stream after it's opened
|
| 106 |
PaTime streamInfoInputLatency; |
| 107 |
PaTime streamInfoOutputLatency; |
| 108 |
|
| 109 |
// Measured at runtime.
|
| 110 |
volatile int callbackCount; // incremented for each callback |
| 111 |
volatile int inputBufferCount; // incremented if input buffer not NULL |
| 112 |
int inputUnderflowCount;
|
| 113 |
int inputOverflowCount;
|
| 114 |
|
| 115 |
volatile int outputBufferCount; // incremented if output buffer not NULL |
| 116 |
int outputOverflowCount;
|
| 117 |
int outputUnderflowCount;
|
| 118 |
|
| 119 |
// Measure whether input or output is lagging behind.
|
| 120 |
volatile int minInputOutputDelta; |
| 121 |
volatile int maxInputOutputDelta; |
| 122 |
|
| 123 |
int minFramesPerBuffer;
|
| 124 |
int maxFramesPerBuffer;
|
| 125 |
int primingCount;
|
| 126 |
TestParameters *test; |
| 127 |
volatile int done; |
| 128 |
} LoopbackContext; |
| 129 |
|
| 130 |
typedef struct UserOptions_s |
| 131 |
{
|
| 132 |
int sampleRate;
|
| 133 |
int framesPerBuffer;
|
| 134 |
int inputLatency;
|
| 135 |
int outputLatency;
|
| 136 |
int saveBadWaves;
|
| 137 |
int verbose;
|
| 138 |
int waveFileCount;
|
| 139 |
const char *waveFilePath; |
| 140 |
PaDeviceIndex inputDevice; |
| 141 |
PaDeviceIndex outputDevice; |
| 142 |
} UserOptions; |
| 143 |
|
| 144 |
#define BIG_BUFFER_SIZE (sizeof(float) * 2 * 2 * 1024) |
| 145 |
static unsigned char g_ReadWriteBuffer[BIG_BUFFER_SIZE]; |
| 146 |
|
| 147 |
#define MAX_CONVERSION_SAMPLES (2 * 32 * 1024) |
| 148 |
#define CONVERSION_BUFFER_SIZE (sizeof(float) * 2 * MAX_CONVERSION_SAMPLES) |
| 149 |
static unsigned char g_ConversionBuffer[CONVERSION_BUFFER_SIZE]; |
| 150 |
|
| 151 |
/*******************************************************************/
|
| 152 |
static int RecordAndPlaySinesCallback( const void *inputBuffer, void *outputBuffer, |
| 153 |
unsigned long framesPerBuffer, |
| 154 |
const PaStreamCallbackTimeInfo* timeInfo,
|
| 155 |
PaStreamCallbackFlags statusFlags, |
| 156 |
void *userData )
|
| 157 |
{
|
| 158 |
int i;
|
| 159 |
LoopbackContext *loopbackContext = (LoopbackContext *) userData; |
| 160 |
|
| 161 |
|
| 162 |
loopbackContext->callbackCount += 1;
|
| 163 |
if( statusFlags & paInputUnderflow ) loopbackContext->inputUnderflowCount += 1; |
| 164 |
if( statusFlags & paInputOverflow ) loopbackContext->inputOverflowCount += 1; |
| 165 |
if( statusFlags & paOutputUnderflow ) loopbackContext->outputUnderflowCount += 1; |
| 166 |
if( statusFlags & paOutputOverflow ) loopbackContext->outputOverflowCount += 1; |
| 167 |
if( statusFlags & paPrimingOutput ) loopbackContext->primingCount += 1; |
| 168 |
if( framesPerBuffer > loopbackContext->maxFramesPerBuffer )
|
| 169 |
{
|
| 170 |
loopbackContext->maxFramesPerBuffer = framesPerBuffer; |
| 171 |
} |
| 172 |
if( framesPerBuffer < loopbackContext->minFramesPerBuffer )
|
| 173 |
{
|
| 174 |
loopbackContext->minFramesPerBuffer = framesPerBuffer; |
| 175 |
} |
| 176 |
|
| 177 |
/* This may get called with NULL inputBuffer during initial setup.
|
| 178 |
* We may also use the same callback with output only streams.
|
| 179 |
*/
|
| 180 |
if( inputBuffer != NULL) |
| 181 |
{
|
| 182 |
int channelsPerFrame = loopbackContext->test->inputParameters.channelCount;
|
| 183 |
float *in = (float *)inputBuffer; |
| 184 |
PaSampleFormat inFormat = loopbackContext->test->inputParameters.sampleFormat; |
| 185 |
|
| 186 |
loopbackContext->inputBufferCount += 1;
|
| 187 |
|
| 188 |
if( inFormat != paFloat32 )
|
| 189 |
{
|
| 190 |
int samplesToConvert = framesPerBuffer * channelsPerFrame;
|
| 191 |
in = (float *) g_ConversionBuffer;
|
| 192 |
if( samplesToConvert > MAX_CONVERSION_SAMPLES )
|
| 193 |
{
|
| 194 |
// Hack to prevent buffer overflow.
|
| 195 |
// @todo Loop with small buffer instead of failing.
|
| 196 |
printf("Format conversion buffer too small!\n");
|
| 197 |
return paComplete;
|
| 198 |
} |
| 199 |
PaQa_ConvertToFloat( inputBuffer, samplesToConvert, inFormat, (float *) g_ConversionBuffer );
|
| 200 |
} |
| 201 |
|
| 202 |
// Read each channel from the buffer.
|
| 203 |
for( i=0; i<channelsPerFrame; i++ ) |
| 204 |
{
|
| 205 |
loopbackContext->done |= PaQa_WriteRecording( &loopbackContext->recordings[i], |
| 206 |
in + i, |
| 207 |
framesPerBuffer, |
| 208 |
channelsPerFrame ); |
| 209 |
} |
| 210 |
} |
| 211 |
|
| 212 |
if( outputBuffer != NULL ) |
| 213 |
{
|
| 214 |
int channelsPerFrame = loopbackContext->test->outputParameters.channelCount;
|
| 215 |
float *out = (float *)outputBuffer; |
| 216 |
PaSampleFormat outFormat = loopbackContext->test->outputParameters.sampleFormat; |
| 217 |
|
| 218 |
loopbackContext->outputBufferCount += 1;
|
| 219 |
|
| 220 |
if( outFormat != paFloat32 )
|
| 221 |
{
|
| 222 |
// If we need to convert then mix to the g_ConversionBuffer and then convert into the PA outputBuffer.
|
| 223 |
out = (float *) g_ConversionBuffer;
|
| 224 |
} |
| 225 |
|
| 226 |
PaQa_EraseBuffer( out, framesPerBuffer, channelsPerFrame ); |
| 227 |
for( i=0; i<channelsPerFrame; i++ ) |
| 228 |
{
|
| 229 |
PaQa_MixSine( &loopbackContext->generators[i], |
| 230 |
out + i, |
| 231 |
framesPerBuffer, |
| 232 |
channelsPerFrame ); |
| 233 |
} |
| 234 |
|
| 235 |
if( outFormat != paFloat32 )
|
| 236 |
{
|
| 237 |
int samplesToConvert = framesPerBuffer * channelsPerFrame;
|
| 238 |
if( samplesToConvert > MAX_CONVERSION_SAMPLES )
|
| 239 |
{
|
| 240 |
printf("Format conversion buffer too small!\n");
|
| 241 |
return paComplete;
|
| 242 |
} |
| 243 |
PaQa_ConvertFromFloat( out, framesPerBuffer * channelsPerFrame, outFormat, outputBuffer ); |
| 244 |
} |
| 245 |
|
| 246 |
} |
| 247 |
|
| 248 |
// Measure whether the input or output are lagging behind.
|
| 249 |
// Don't measure lag at end.
|
| 250 |
if( !loopbackContext->done )
|
| 251 |
{
|
| 252 |
int inputOutputDelta = loopbackContext->inputBufferCount - loopbackContext->outputBufferCount;
|
| 253 |
if( loopbackContext->maxInputOutputDelta < inputOutputDelta )
|
| 254 |
{
|
| 255 |
loopbackContext->maxInputOutputDelta = inputOutputDelta; |
| 256 |
} |
| 257 |
if( loopbackContext->minInputOutputDelta > inputOutputDelta )
|
| 258 |
{
|
| 259 |
loopbackContext->minInputOutputDelta = inputOutputDelta; |
| 260 |
} |
| 261 |
} |
| 262 |
|
| 263 |
return loopbackContext->done ? paComplete : paContinue;
|
| 264 |
} |
| 265 |
|
| 266 |
static void CopyStreamInfoToLoopbackContext( LoopbackContext *loopbackContext, PaStream *inputStream, PaStream *outputStream ) |
| 267 |
{
|
| 268 |
const PaStreamInfo *inputStreamInfo = Pa_GetStreamInfo( inputStream );
|
| 269 |
const PaStreamInfo *outputStreamInfo = Pa_GetStreamInfo( outputStream );
|
| 270 |
|
| 271 |
loopbackContext->streamInfoInputLatency = inputStreamInfo ? inputStreamInfo->inputLatency : -1;
|
| 272 |
loopbackContext->streamInfoOutputLatency = outputStreamInfo ? outputStreamInfo->outputLatency : -1;
|
| 273 |
} |
| 274 |
|
| 275 |
/*******************************************************************/
|
| 276 |
/**
|
| 277 |
* Open a full duplex audio stream.
|
| 278 |
* Generate sine waves on the output channels and record the input channels.
|
| 279 |
* Then close the stream.
|
| 280 |
* @return 0 if OK or negative error.
|
| 281 |
*/
|
| 282 |
int PaQa_RunLoopbackFullDuplex( LoopbackContext *loopbackContext )
|
| 283 |
{
|
| 284 |
PaStream *stream = NULL;
|
| 285 |
PaError err = 0;
|
| 286 |
TestParameters *test = loopbackContext->test; |
| 287 |
loopbackContext->done = 0;
|
| 288 |
// Use one full duplex stream.
|
| 289 |
err = Pa_OpenStream( |
| 290 |
&stream, |
| 291 |
&test->inputParameters, |
| 292 |
&test->outputParameters, |
| 293 |
test->sampleRate, |
| 294 |
test->framesPerBuffer, |
| 295 |
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
| 296 |
RecordAndPlaySinesCallback, |
| 297 |
loopbackContext ); |
| 298 |
if( err != paNoError ) goto error; |
| 299 |
|
| 300 |
CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream ); |
| 301 |
|
| 302 |
err = Pa_StartStream( stream ); |
| 303 |
if( err != paNoError ) goto error; |
| 304 |
|
| 305 |
// Wait for stream to finish.
|
| 306 |
while( loopbackContext->done == 0 ) |
| 307 |
{
|
| 308 |
Pa_Sleep(PAQA_WAIT_STREAM_MSEC); |
| 309 |
} |
| 310 |
|
| 311 |
err = Pa_StopStream( stream ); |
| 312 |
if( err != paNoError ) goto error; |
| 313 |
|
| 314 |
err = Pa_CloseStream( stream ); |
| 315 |
if( err != paNoError ) goto error; |
| 316 |
|
| 317 |
return 0; |
| 318 |
|
| 319 |
error:
|
| 320 |
return err;
|
| 321 |
} |
| 322 |
|
| 323 |
/*******************************************************************/
|
| 324 |
/**
|
| 325 |
* Open two audio streams, one for input and one for output.
|
| 326 |
* Generate sine waves on the output channels and record the input channels.
|
| 327 |
* Then close the stream.
|
| 328 |
* @return 0 if OK or paTimedOut.
|
| 329 |
*/
|
| 330 |
|
| 331 |
int PaQa_WaitForStream( LoopbackContext *loopbackContext )
|
| 332 |
{
|
| 333 |
int timeoutMSec = 1000 * PAQA_TEST_DURATION * 2; |
| 334 |
|
| 335 |
// Wait for stream to finish or timeout.
|
| 336 |
while( (loopbackContext->done == 0) && (timeoutMSec > 0) ) |
| 337 |
{
|
| 338 |
Pa_Sleep(PAQA_WAIT_STREAM_MSEC); |
| 339 |
timeoutMSec -= PAQA_WAIT_STREAM_MSEC; |
| 340 |
} |
| 341 |
|
| 342 |
if( loopbackContext->done == 0 ) |
| 343 |
{
|
| 344 |
printf("ERROR - stream completion timed out!");
|
| 345 |
return paTimedOut;
|
| 346 |
} |
| 347 |
return 0; |
| 348 |
} |
| 349 |
|
| 350 |
/*******************************************************************/
|
| 351 |
/**
|
| 352 |
* Open two audio streams, one for input and one for output.
|
| 353 |
* Generate sine waves on the output channels and record the input channels.
|
| 354 |
* Then close the stream.
|
| 355 |
* @return 0 if OK or negative error.
|
| 356 |
*/
|
| 357 |
int PaQa_RunLoopbackHalfDuplex( LoopbackContext *loopbackContext )
|
| 358 |
{
|
| 359 |
PaStream *inStream = NULL;
|
| 360 |
PaStream *outStream = NULL;
|
| 361 |
PaError err = 0;
|
| 362 |
int timedOut = 0; |
| 363 |
TestParameters *test = loopbackContext->test; |
| 364 |
loopbackContext->done = 0;
|
| 365 |
|
| 366 |
// Use two half duplex streams.
|
| 367 |
err = Pa_OpenStream( |
| 368 |
&inStream, |
| 369 |
&test->inputParameters, |
| 370 |
NULL,
|
| 371 |
test->sampleRate, |
| 372 |
test->framesPerBuffer, |
| 373 |
test->streamFlags, |
| 374 |
RecordAndPlaySinesCallback, |
| 375 |
loopbackContext ); |
| 376 |
if( err != paNoError ) goto error; |
| 377 |
err = Pa_OpenStream( |
| 378 |
&outStream, |
| 379 |
NULL,
|
| 380 |
&test->outputParameters, |
| 381 |
test->sampleRate, |
| 382 |
test->framesPerBuffer, |
| 383 |
test->streamFlags, |
| 384 |
RecordAndPlaySinesCallback, |
| 385 |
loopbackContext ); |
| 386 |
if( err != paNoError ) goto error; |
| 387 |
|
| 388 |
CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream ); |
| 389 |
|
| 390 |
err = Pa_StartStream( inStream ); |
| 391 |
if( err != paNoError ) goto error; |
| 392 |
|
| 393 |
// Start output later so we catch the beginning of the waveform.
|
| 394 |
err = Pa_StartStream( outStream ); |
| 395 |
if( err != paNoError ) goto error; |
| 396 |
|
| 397 |
timedOut = PaQa_WaitForStream( loopbackContext ); |
| 398 |
|
| 399 |
err = Pa_StopStream( inStream ); |
| 400 |
if( err != paNoError ) goto error; |
| 401 |
|
| 402 |
err = Pa_StopStream( outStream ); |
| 403 |
if( err != paNoError ) goto error; |
| 404 |
|
| 405 |
err = Pa_CloseStream( inStream ); |
| 406 |
if( err != paNoError ) goto error; |
| 407 |
|
| 408 |
err = Pa_CloseStream( outStream ); |
| 409 |
if( err != paNoError ) goto error; |
| 410 |
|
| 411 |
return timedOut;
|
| 412 |
|
| 413 |
error:
|
| 414 |
return err;
|
| 415 |
} |
| 416 |
|
| 417 |
|
| 418 |
/*******************************************************************/
|
| 419 |
/**
|
| 420 |
* Open one audio streams, just for input.
|
| 421 |
* Record background level.
|
| 422 |
* Then close the stream.
|
| 423 |
* @return 0 if OK or negative error.
|
| 424 |
*/
|
| 425 |
int PaQa_RunInputOnly( LoopbackContext *loopbackContext )
|
| 426 |
{
|
| 427 |
PaStream *inStream = NULL;
|
| 428 |
PaError err = 0;
|
| 429 |
int timedOut = 0; |
| 430 |
TestParameters *test = loopbackContext->test; |
| 431 |
loopbackContext->done = 0;
|
| 432 |
|
| 433 |
// Just open an input stream.
|
| 434 |
err = Pa_OpenStream( |
| 435 |
&inStream, |
| 436 |
&test->inputParameters, |
| 437 |
NULL,
|
| 438 |
test->sampleRate, |
| 439 |
test->framesPerBuffer, |
| 440 |
paClipOff, /* We won't output out of range samples so don't bother clipping them. */
|
| 441 |
RecordAndPlaySinesCallback, |
| 442 |
loopbackContext ); |
| 443 |
if( err != paNoError ) goto error; |
| 444 |
|
| 445 |
err = Pa_StartStream( inStream ); |
| 446 |
if( err != paNoError ) goto error; |
| 447 |
|
| 448 |
timedOut = PaQa_WaitForStream( loopbackContext ); |
| 449 |
|
| 450 |
err = Pa_StopStream( inStream ); |
| 451 |
if( err != paNoError ) goto error; |
| 452 |
|
| 453 |
err = Pa_CloseStream( inStream ); |
| 454 |
if( err != paNoError ) goto error; |
| 455 |
|
| 456 |
return timedOut;
|
| 457 |
|
| 458 |
error:
|
| 459 |
return err;
|
| 460 |
} |
| 461 |
|
| 462 |
/*******************************************************************/
|
| 463 |
static int RecordAndPlayBlockingIO( PaStream *inStream, |
| 464 |
PaStream *outStream, |
| 465 |
LoopbackContext *loopbackContext |
| 466 |
) |
| 467 |
{
|
| 468 |
int i;
|
| 469 |
float *in = (float *)g_ReadWriteBuffer; |
| 470 |
float *out = (float *)g_ReadWriteBuffer; |
| 471 |
PaError err; |
| 472 |
int done = 0; |
| 473 |
long available;
|
| 474 |
const long maxPerBuffer = 64; |
| 475 |
TestParameters *test = loopbackContext->test; |
| 476 |
long framesPerBuffer = test->framesPerBuffer;
|
| 477 |
if( framesPerBuffer <= 0 ) |
| 478 |
{
|
| 479 |
framesPerBuffer = maxPerBuffer; // bigger values might run past end of recording
|
| 480 |
} |
| 481 |
|
| 482 |
// Read in audio.
|
| 483 |
err = Pa_ReadStream( inStream, in, framesPerBuffer ); |
| 484 |
// Ignore an overflow on the first read.
|
| 485 |
//if( !((loopbackContext->callbackCount == 0) && (err == paInputOverflowed)) )
|
| 486 |
if( err != paInputOverflowed )
|
| 487 |
{
|
| 488 |
QA_ASSERT_EQUALS( "Pa_ReadStream failed", paNoError, err );
|
| 489 |
} |
| 490 |
else
|
| 491 |
{
|
| 492 |
loopbackContext->inputOverflowCount += 1;
|
| 493 |
} |
| 494 |
|
| 495 |
|
| 496 |
// Save in a recording.
|
| 497 |
for( i=0; i<loopbackContext->test->inputParameters.channelCount; i++ ) |
| 498 |
{
|
| 499 |
done |= PaQa_WriteRecording( &loopbackContext->recordings[i], |
| 500 |
in + i, |
| 501 |
framesPerBuffer, |
| 502 |
loopbackContext->test->inputParameters.channelCount ); |
| 503 |
} |
| 504 |
|
| 505 |
// Synthesize audio.
|
| 506 |
available = Pa_GetStreamWriteAvailable( outStream ); |
| 507 |
if( available > (2*framesPerBuffer) ) available = (2*framesPerBuffer); |
| 508 |
PaQa_EraseBuffer( out, available, loopbackContext->test->outputParameters.channelCount ); |
| 509 |
for( i=0; i<loopbackContext->test->outputParameters.channelCount; i++ ) |
| 510 |
{
|
| 511 |
PaQa_MixSine( &loopbackContext->generators[i], |
| 512 |
out + i, |
| 513 |
available, |
| 514 |
loopbackContext->test->outputParameters.channelCount ); |
| 515 |
} |
| 516 |
|
| 517 |
// Write out audio.
|
| 518 |
err = Pa_WriteStream( outStream, out, available ); |
| 519 |
// Ignore an underflow on the first write.
|
| 520 |
//if( !((loopbackContext->callbackCount == 0) && (err == paOutputUnderflowed)) )
|
| 521 |
if( err != paOutputUnderflowed )
|
| 522 |
{
|
| 523 |
QA_ASSERT_EQUALS( "Pa_WriteStream failed", paNoError, err );
|
| 524 |
} |
| 525 |
else
|
| 526 |
{
|
| 527 |
loopbackContext->outputUnderflowCount += 1;
|
| 528 |
} |
| 529 |
|
| 530 |
|
| 531 |
loopbackContext->callbackCount += 1;
|
| 532 |
|
| 533 |
return done;
|
| 534 |
error:
|
| 535 |
return err;
|
| 536 |
} |
| 537 |
|
| 538 |
|
| 539 |
/*******************************************************************/
|
| 540 |
/**
|
| 541 |
* Open two audio streams with non-blocking IO.
|
| 542 |
* Generate sine waves on the output channels and record the input channels.
|
| 543 |
* Then close the stream.
|
| 544 |
* @return 0 if OK or negative error.
|
| 545 |
*/
|
| 546 |
int PaQa_RunLoopbackHalfDuplexBlockingIO( LoopbackContext *loopbackContext )
|
| 547 |
{
|
| 548 |
PaStream *inStream = NULL;
|
| 549 |
PaStream *outStream = NULL;
|
| 550 |
PaError err = 0;
|
| 551 |
TestParameters *test = loopbackContext->test; |
| 552 |
|
| 553 |
// Use two half duplex streams.
|
| 554 |
err = Pa_OpenStream( |
| 555 |
&inStream, |
| 556 |
&test->inputParameters, |
| 557 |
NULL,
|
| 558 |
test->sampleRate, |
| 559 |
test->framesPerBuffer, |
| 560 |
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
| 561 |
NULL, // causes non-blocking IO |
| 562 |
NULL );
|
| 563 |
if( err != paNoError ) goto error1; |
| 564 |
err = Pa_OpenStream( |
| 565 |
&outStream, |
| 566 |
NULL,
|
| 567 |
&test->outputParameters, |
| 568 |
test->sampleRate, |
| 569 |
test->framesPerBuffer, |
| 570 |
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
| 571 |
NULL, // causes non-blocking IO |
| 572 |
NULL );
|
| 573 |
if( err != paNoError ) goto error2; |
| 574 |
|
| 575 |
CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream ); |
| 576 |
|
| 577 |
err = Pa_StartStream( outStream ); |
| 578 |
if( err != paNoError ) goto error3; |
| 579 |
|
| 580 |
err = Pa_StartStream( inStream ); |
| 581 |
if( err != paNoError ) goto error3; |
| 582 |
|
| 583 |
while( err == 0 ) |
| 584 |
{
|
| 585 |
err = RecordAndPlayBlockingIO( inStream, outStream, loopbackContext ); |
| 586 |
if( err < 0 ) goto error3; |
| 587 |
} |
| 588 |
|
| 589 |
err = Pa_StopStream( inStream ); |
| 590 |
if( err != paNoError ) goto error3; |
| 591 |
|
| 592 |
err = Pa_StopStream( outStream ); |
| 593 |
if( err != paNoError ) goto error3; |
| 594 |
|
| 595 |
err = Pa_CloseStream( outStream ); |
| 596 |
if( err != paNoError ) goto error2; |
| 597 |
|
| 598 |
err = Pa_CloseStream( inStream ); |
| 599 |
if( err != paNoError ) goto error1; |
| 600 |
|
| 601 |
|
| 602 |
return 0; |
| 603 |
|
| 604 |
error3:
|
| 605 |
Pa_CloseStream( outStream ); |
| 606 |
error2:
|
| 607 |
Pa_CloseStream( inStream ); |
| 608 |
error1:
|
| 609 |
return err;
|
| 610 |
} |
| 611 |
|
| 612 |
|
| 613 |
/*******************************************************************/
|
| 614 |
/**
|
| 615 |
* Open one audio stream with non-blocking IO.
|
| 616 |
* Generate sine waves on the output channels and record the input channels.
|
| 617 |
* Then close the stream.
|
| 618 |
* @return 0 if OK or negative error.
|
| 619 |
*/
|
| 620 |
int PaQa_RunLoopbackFullDuplexBlockingIO( LoopbackContext *loopbackContext )
|
| 621 |
{
|
| 622 |
PaStream *stream = NULL;
|
| 623 |
PaError err = 0;
|
| 624 |
TestParameters *test = loopbackContext->test; |
| 625 |
|
| 626 |
// Use one full duplex stream.
|
| 627 |
err = Pa_OpenStream( |
| 628 |
&stream, |
| 629 |
&test->inputParameters, |
| 630 |
&test->outputParameters, |
| 631 |
test->sampleRate, |
| 632 |
test->framesPerBuffer, |
| 633 |
paClipOff, /* we won't output out of range samples so don't bother clipping them */
|
| 634 |
NULL, // causes non-blocking IO |
| 635 |
NULL );
|
| 636 |
if( err != paNoError ) goto error1; |
| 637 |
|
| 638 |
CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream ); |
| 639 |
|
| 640 |
err = Pa_StartStream( stream ); |
| 641 |
if( err != paNoError ) goto error2; |
| 642 |
|
| 643 |
while( err == 0 ) |
| 644 |
{
|
| 645 |
err = RecordAndPlayBlockingIO( stream, stream, loopbackContext ); |
| 646 |
if( err < 0 ) goto error2; |
| 647 |
} |
| 648 |
|
| 649 |
err = Pa_StopStream( stream ); |
| 650 |
if( err != paNoError ) goto error2; |
| 651 |
|
| 652 |
|
| 653 |
err = Pa_CloseStream( stream ); |
| 654 |
if( err != paNoError ) goto error1; |
| 655 |
|
| 656 |
|
| 657 |
return 0; |
| 658 |
|
| 659 |
error2:
|
| 660 |
Pa_CloseStream( stream ); |
| 661 |
error1:
|
| 662 |
return err;
|
| 663 |
} |
| 664 |
|
| 665 |
|
| 666 |
/*******************************************************************/
|
| 667 |
/**
|
| 668 |
* Run some kind of loopback test.
|
| 669 |
* @return 0 if OK or negative error.
|
| 670 |
*/
|
| 671 |
int PaQa_RunLoopback( LoopbackContext *loopbackContext )
|
| 672 |
{
|
| 673 |
PaError err = 0;
|
| 674 |
TestParameters *test = loopbackContext->test; |
| 675 |
|
| 676 |
|
| 677 |
if( test->flags & PAQA_FLAG_TWO_STREAMS )
|
| 678 |
{
|
| 679 |
if( test->flags & PAQA_FLAG_USE_BLOCKING_IO )
|
| 680 |
{
|
| 681 |
err = PaQa_RunLoopbackHalfDuplexBlockingIO( loopbackContext ); |
| 682 |
} |
| 683 |
else
|
| 684 |
{
|
| 685 |
err = PaQa_RunLoopbackHalfDuplex( loopbackContext ); |
| 686 |
} |
| 687 |
} |
| 688 |
else
|
| 689 |
{
|
| 690 |
if( test->flags & PAQA_FLAG_USE_BLOCKING_IO )
|
| 691 |
{
|
| 692 |
err = PaQa_RunLoopbackFullDuplexBlockingIO( loopbackContext ); |
| 693 |
} |
| 694 |
else
|
| 695 |
{
|
| 696 |
err = PaQa_RunLoopbackFullDuplex( loopbackContext ); |
| 697 |
} |
| 698 |
} |
| 699 |
|
| 700 |
if( err != paNoError )
|
| 701 |
{
|
| 702 |
printf("PortAudio error = %s\n", Pa_GetErrorText( err ) );
|
| 703 |
} |
| 704 |
return err;
|
| 705 |
} |
| 706 |
|
| 707 |
/*******************************************************************/
|
| 708 |
static int PaQa_SaveTestResultToWaveFile( UserOptions *userOptions, PaQaRecording *recording ) |
| 709 |
{
|
| 710 |
if( userOptions->saveBadWaves )
|
| 711 |
{
|
| 712 |
char filename[256]; |
| 713 |
#ifdef WIN32
|
| 714 |
_snprintf( filename, sizeof(filename), "%s\\paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ ); |
| 715 |
#else
|
| 716 |
snprintf( filename, sizeof(filename), "%s/paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ ); |
| 717 |
#endif
|
| 718 |
printf( "\"%s\", ", filename );
|
| 719 |
return PaQa_SaveRecordingToWaveFile( recording, filename );
|
| 720 |
} |
| 721 |
return 0; |
| 722 |
} |
| 723 |
|
| 724 |
/*******************************************************************/
|
| 725 |
static int PaQa_SetupLoopbackContext( LoopbackContext *loopbackContextPtr, TestParameters *testParams ) |
| 726 |
{
|
| 727 |
int i;
|
| 728 |
// Setup loopback context.
|
| 729 |
memset( loopbackContextPtr, 0, sizeof(LoopbackContext) ); |
| 730 |
loopbackContextPtr->test = testParams; |
| 731 |
for( i=0; i<testParams->samplesPerFrame; i++ ) |
| 732 |
{
|
| 733 |
int err = PaQa_InitializeRecording( &loopbackContextPtr->recordings[i], testParams->maxFrames, testParams->sampleRate );
|
| 734 |
QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", paNoError, err );
|
| 735 |
} |
| 736 |
for( i=0; i<testParams->samplesPerFrame; i++ ) |
| 737 |
{
|
| 738 |
PaQa_SetupSineGenerator( &loopbackContextPtr->generators[i], PaQa_GetNthFrequency( testParams->baseFrequency, i ), |
| 739 |
testParams->amplitude, testParams->sampleRate ); |
| 740 |
} |
| 741 |
loopbackContextPtr->minFramesPerBuffer = 0x0FFFFFFF;
|
| 742 |
return 0; |
| 743 |
error:
|
| 744 |
return -1; |
| 745 |
} |
| 746 |
|
| 747 |
/*******************************************************************/
|
| 748 |
static void PaQa_TeardownLoopbackContext( LoopbackContext *loopbackContextPtr ) |
| 749 |
{
|
| 750 |
int i;
|
| 751 |
if( loopbackContextPtr->test != NULL ) |
| 752 |
{
|
| 753 |
for( i=0; i<loopbackContextPtr->test->samplesPerFrame; i++ ) |
| 754 |
{
|
| 755 |
PaQa_TerminateRecording( &loopbackContextPtr->recordings[i] ); |
| 756 |
} |
| 757 |
} |
| 758 |
} |
| 759 |
|
| 760 |
/*******************************************************************/
|
| 761 |
static void PaQa_PrintShortErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel ) |
| 762 |
{
|
| 763 |
printf("channel %d ", channel);
|
| 764 |
if( analysisResultPtr->popPosition > 0 ) |
| 765 |
{
|
| 766 |
printf("POP %0.3f at %d, ", (double)analysisResultPtr->popAmplitude, (int)analysisResultPtr->popPosition ); |
| 767 |
} |
| 768 |
else
|
| 769 |
{
|
| 770 |
if( analysisResultPtr->addedFramesPosition > 0 ) |
| 771 |
{
|
| 772 |
printf("ADD %d at %d ", (int)analysisResultPtr->numAddedFrames, (int)analysisResultPtr->addedFramesPosition ); |
| 773 |
} |
| 774 |
|
| 775 |
if( analysisResultPtr->droppedFramesPosition > 0 ) |
| 776 |
{
|
| 777 |
printf("DROP %d at %d ", (int)analysisResultPtr->numDroppedFrames, (int)analysisResultPtr->droppedFramesPosition ); |
| 778 |
} |
| 779 |
} |
| 780 |
} |
| 781 |
|
| 782 |
/*******************************************************************/
|
| 783 |
static void PaQa_PrintFullErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel ) |
| 784 |
{
|
| 785 |
printf("\n=== Loopback Analysis ===================\n");
|
| 786 |
printf(" channel: %d\n", channel );
|
| 787 |
printf(" latency: %10.3f\n", analysisResultPtr->latency );
|
| 788 |
printf(" amplitudeRatio: %10.3f\n", (double)analysisResultPtr->amplitudeRatio ); |
| 789 |
printf(" popPosition: %10.3f\n", (double)analysisResultPtr->popPosition ); |
| 790 |
printf(" popAmplitude: %10.3f\n", (double)analysisResultPtr->popAmplitude ); |
| 791 |
printf(" num added frames: %10.3f\n", analysisResultPtr->numAddedFrames );
|
| 792 |
printf(" added frames at: %10.3f\n", analysisResultPtr->addedFramesPosition );
|
| 793 |
printf(" num dropped frames: %10.3f\n", analysisResultPtr->numDroppedFrames );
|
| 794 |
printf(" dropped frames at: %10.3f\n", analysisResultPtr->droppedFramesPosition );
|
| 795 |
} |
| 796 |
|
| 797 |
/*******************************************************************/
|
| 798 |
/**
|
| 799 |
* Test loopback connection using the given parameters.
|
| 800 |
* @return number of channels with glitches, or negative error.
|
| 801 |
*/
|
| 802 |
static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *testParams ) |
| 803 |
{
|
| 804 |
int i;
|
| 805 |
LoopbackContext loopbackContext; |
| 806 |
PaError err = paNoError; |
| 807 |
PaQaTestTone testTone; |
| 808 |
PaQaAnalysisResult analysisResult; |
| 809 |
int numBadChannels = 0; |
| 810 |
|
| 811 |
printf("| %5d | %6d | ", ((int)(testParams->sampleRate+0.5)), testParams->framesPerBuffer ); |
| 812 |
fflush(stdout); |
| 813 |
|
| 814 |
testTone.samplesPerFrame = testParams->samplesPerFrame; |
| 815 |
testTone.sampleRate = testParams->sampleRate; |
| 816 |
testTone.amplitude = testParams->amplitude; |
| 817 |
testTone.startDelay = 0;
|
| 818 |
|
| 819 |
err = PaQa_SetupLoopbackContext( &loopbackContext, testParams ); |
| 820 |
if( err ) return err; |
| 821 |
|
| 822 |
err = PaQa_RunLoopback( &loopbackContext ); |
| 823 |
QA_ASSERT_TRUE("loopback did not run", (loopbackContext.callbackCount > 1) ); |
| 824 |
|
| 825 |
printf( "%7.2f %7.2f %7.2f | ",
|
| 826 |
loopbackContext.streamInfoInputLatency * 1000.0, |
| 827 |
loopbackContext.streamInfoOutputLatency * 1000.0, |
| 828 |
(loopbackContext.streamInfoInputLatency + loopbackContext.streamInfoOutputLatency) * 1000.0 |
| 829 |
); |
| 830 |
|
| 831 |
printf( "%4d/%4d/%4d, %4d/%4d/%4d | ",
|
| 832 |
loopbackContext.inputOverflowCount, |
| 833 |
loopbackContext.inputUnderflowCount, |
| 834 |
loopbackContext.inputBufferCount, |
| 835 |
loopbackContext.outputOverflowCount, |
| 836 |
loopbackContext.outputUnderflowCount, |
| 837 |
loopbackContext.outputBufferCount |
| 838 |
); |
| 839 |
|
| 840 |
// Analyse recording to detect glitches.
|
| 841 |
for( i=0; i<testParams->samplesPerFrame; i++ ) |
| 842 |
{
|
| 843 |
double freq = PaQa_GetNthFrequency( testParams->baseFrequency, i );
|
| 844 |
testTone.frequency = freq; |
| 845 |
|
| 846 |
PaQa_AnalyseRecording( &loopbackContext.recordings[i], &testTone, &analysisResult ); |
| 847 |
|
| 848 |
if( i==0 ) |
| 849 |
{
|
| 850 |
double latencyMSec;
|
| 851 |
|
| 852 |
printf( "%4d-%4d | ",
|
| 853 |
loopbackContext.minFramesPerBuffer, |
| 854 |
loopbackContext.maxFramesPerBuffer |
| 855 |
); |
| 856 |
|
| 857 |
latencyMSec = 1000.0 * analysisResult.latency / testParams->sampleRate; |
| 858 |
printf("%7.2f | ", latencyMSec );
|
| 859 |
|
| 860 |
} |
| 861 |
|
| 862 |
if( analysisResult.valid )
|
| 863 |
{
|
| 864 |
int badChannel = ( (analysisResult.popPosition > 0) |
| 865 |
|| (analysisResult.addedFramesPosition > 0)
|
| 866 |
|| (analysisResult.droppedFramesPosition > 0) );
|
| 867 |
|
| 868 |
if( badChannel )
|
| 869 |
{
|
| 870 |
if( userOptions->verbose )
|
| 871 |
{
|
| 872 |
PaQa_PrintFullErrorReport( &analysisResult, i ); |
| 873 |
} |
| 874 |
else
|
| 875 |
{
|
| 876 |
PaQa_PrintShortErrorReport( &analysisResult, i ); |
| 877 |
} |
| 878 |
PaQa_SaveTestResultToWaveFile( userOptions, &loopbackContext.recordings[i] ); |
| 879 |
} |
| 880 |
numBadChannels += badChannel; |
| 881 |
} |
| 882 |
else
|
| 883 |
{
|
| 884 |
printf( "[%d] No or low signal, ampRatio = %f", i, analysisResult.amplitudeRatio );
|
| 885 |
numBadChannels += 1;
|
| 886 |
} |
| 887 |
|
| 888 |
} |
| 889 |
if( numBadChannels == 0 ) |
| 890 |
{
|
| 891 |
printf( "OK" );
|
| 892 |
} |
| 893 |
|
| 894 |
// Print the # errors so far to make it easier to see where the error occured.
|
| 895 |
printf( " - #errs = %d\n", g_testsFailed );
|
| 896 |
|
| 897 |
PaQa_TeardownLoopbackContext( &loopbackContext ); |
| 898 |
if( numBadChannels > 0 ) |
| 899 |
{
|
| 900 |
g_testsFailed += 1;
|
| 901 |
} |
| 902 |
return numBadChannels;
|
| 903 |
|
| 904 |
error:
|
| 905 |
PaQa_TeardownLoopbackContext( &loopbackContext ); |
| 906 |
printf( "\n" );
|
| 907 |
g_testsFailed += 1;
|
| 908 |
return err;
|
| 909 |
} |
| 910 |
|
| 911 |
/*******************************************************************/
|
| 912 |
static void PaQa_SetDefaultTestParameters( TestParameters *testParamsPtr, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice ) |
| 913 |
{
|
| 914 |
memset( testParamsPtr, 0, sizeof(TestParameters) ); |
| 915 |
|
| 916 |
testParamsPtr->samplesPerFrame = 2;
|
| 917 |
testParamsPtr->amplitude = 0.5; |
| 918 |
testParamsPtr->sampleRate = 44100;
|
| 919 |
testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate);
|
| 920 |
testParamsPtr->framesPerBuffer = DEFAULT_FRAMES_PER_BUFFER; |
| 921 |
testParamsPtr->baseFrequency = 200.0; |
| 922 |
testParamsPtr->flags = PAQA_FLAG_TWO_STREAMS; |
| 923 |
testParamsPtr->streamFlags = paClipOff; /* we won't output out of range samples so don't bother clipping them */
|
| 924 |
|
| 925 |
testParamsPtr->inputParameters.device = inputDevice; |
| 926 |
testParamsPtr->inputParameters.sampleFormat = paFloat32; |
| 927 |
testParamsPtr->inputParameters.channelCount = testParamsPtr->samplesPerFrame; |
| 928 |
testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultLowInputLatency; |
| 929 |
//testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultHighInputLatency;
|
| 930 |
|
| 931 |
testParamsPtr->outputParameters.device = outputDevice; |
| 932 |
testParamsPtr->outputParameters.sampleFormat = paFloat32; |
| 933 |
testParamsPtr->outputParameters.channelCount = testParamsPtr->samplesPerFrame; |
| 934 |
testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultLowOutputLatency; |
| 935 |
//testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultHighOutputLatency;
|
| 936 |
} |
| 937 |
|
| 938 |
/*******************************************************************/
|
| 939 |
static void PaQa_OverrideTestParameters( TestParameters *testParamsPtr, UserOptions *userOptions ) |
| 940 |
{
|
| 941 |
// Check to see if a specific value was requested.
|
| 942 |
if( userOptions->sampleRate >= 0 ) |
| 943 |
{
|
| 944 |
testParamsPtr->sampleRate = userOptions->sampleRate; |
| 945 |
testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate);
|
| 946 |
} |
| 947 |
if( userOptions->framesPerBuffer >= 0 ) |
| 948 |
{
|
| 949 |
testParamsPtr->framesPerBuffer = userOptions->framesPerBuffer; |
| 950 |
} |
| 951 |
if( userOptions->inputLatency >= 0 ) |
| 952 |
{
|
| 953 |
testParamsPtr->inputParameters.suggestedLatency = userOptions->inputLatency * 0.001; |
| 954 |
} |
| 955 |
if( userOptions->outputLatency >= 0 ) |
| 956 |
{
|
| 957 |
testParamsPtr->outputParameters.suggestedLatency = userOptions->outputLatency * 0.001; |
| 958 |
} |
| 959 |
printf( " Running with suggested latency (msec): input = %5.2f, out = %5.2f\n",
|
| 960 |
(testParamsPtr->inputParameters.suggestedLatency * 1000.0), |
| 961 |
(testParamsPtr->outputParameters.suggestedLatency * 1000.0) ); |
| 962 |
} |
| 963 |
|
| 964 |
/*******************************************************************/
|
| 965 |
/**
|
| 966 |
* Run a series of tests on this loopback connection.
|
| 967 |
* @return number of bad channel results
|
| 968 |
*/
|
| 969 |
static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice ) |
| 970 |
{
|
| 971 |
int iFlags;
|
| 972 |
int iRate;
|
| 973 |
int iSize;
|
| 974 |
int iFormat;
|
| 975 |
int savedValue;
|
| 976 |
TestParameters testParams; |
| 977 |
const PaDeviceInfo *inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );
|
| 978 |
const PaDeviceInfo *outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );
|
| 979 |
int totalBadChannels = 0; |
| 980 |
|
| 981 |
// test half duplex first because it is more likely to work.
|
| 982 |
int flagSettings[] = { PAQA_FLAG_TWO_STREAMS, 0 }; |
| 983 |
int numFlagSettings = (sizeof(flagSettings)/sizeof(int)); |
| 984 |
|
| 985 |
double sampleRates[] = { 8000.0, 11025.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0, 96000.0 }; |
| 986 |
int numRates = (sizeof(sampleRates)/sizeof(double)); |
| 987 |
|
| 988 |
// framesPerBuffer==0 means PA decides on the buffer size.
|
| 989 |
int framesPerBuffers[] = { 0, 16, 32, 40, 64, 100, 128, 256, 512, 1024 }; |
| 990 |
int numBufferSizes = (sizeof(framesPerBuffers)/sizeof(int)); |
| 991 |
|
| 992 |
PaSampleFormat sampleFormats[] = { paFloat32, paUInt8, paInt8, paInt16, paInt32 };
|
| 993 |
const char *sampleFormatNames[] = { "paFloat32", "paUInt8", "paInt8", "paInt16", "paInt32" }; |
| 994 |
int numSampleFormats = (sizeof(sampleFormats)/sizeof(PaSampleFormat)); |
| 995 |
|
| 996 |
printf( "=============== Analysing Loopback %d to %d =====================\n", outputDevice, inputDevice );
|
| 997 |
printf( " Devices: %s => %s\n", outputDeviceInfo->name, inputDeviceInfo->name);
|
| 998 |
|
| 999 |
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice ); |
| 1000 |
|
| 1001 |
PaQa_OverrideTestParameters( &testParams, userOptions ); |
| 1002 |
|
| 1003 |
// Loop though combinations of audio parameters.
|
| 1004 |
for( iFlags=0; iFlags<numFlagSettings; iFlags++ ) |
| 1005 |
{
|
| 1006 |
int numRuns = 0; |
| 1007 |
|
| 1008 |
testParams.flags = flagSettings[iFlags]; |
| 1009 |
printf( "\n************ Mode = %s ************\n",
|
| 1010 |
(( testParams.flags & 1 ) ? s_FlagOnNames[0] : s_FlagOffNames[0]) ); |
| 1011 |
|
| 1012 |
printf("|- requested -|- stream info latency -|- measured ------------------------------\n");
|
| 1013 |
printf("|-sRate-|-fr/buf-|- in - out - total -|- over/under/calls for in, out -|- frm/buf -|-latency-|- channel results -\n");
|
| 1014 |
|
| 1015 |
// Loop though various sample rates.
|
| 1016 |
if( userOptions->sampleRate < 0 ) |
| 1017 |
{
|
| 1018 |
savedValue = testParams.sampleRate; |
| 1019 |
for( iRate=0; iRate<numRates; iRate++ ) |
| 1020 |
{
|
| 1021 |
int numBadChannels;
|
| 1022 |
|
| 1023 |
// SAMPLE RATE
|
| 1024 |
testParams.sampleRate = sampleRates[iRate]; |
| 1025 |
testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate);
|
| 1026 |
|
| 1027 |
numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams ); |
| 1028 |
totalBadChannels += numBadChannels; |
| 1029 |
} |
| 1030 |
testParams.sampleRate = savedValue; |
| 1031 |
testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate);
|
| 1032 |
printf( "\n" );
|
| 1033 |
numRuns += 1;
|
| 1034 |
} |
| 1035 |
|
| 1036 |
// Loop through various buffer sizes.
|
| 1037 |
if( userOptions->framesPerBuffer < 0 ) |
| 1038 |
{
|
| 1039 |
savedValue = testParams.framesPerBuffer; |
| 1040 |
for( iSize=0; iSize<numBufferSizes; iSize++ ) |
| 1041 |
{
|
| 1042 |
int numBadChannels;
|
| 1043 |
|
| 1044 |
// BUFFER SIZE
|
| 1045 |
testParams.framesPerBuffer = framesPerBuffers[iSize]; |
| 1046 |
|
| 1047 |
numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams ); |
| 1048 |
totalBadChannels += numBadChannels; |
| 1049 |
} |
| 1050 |
testParams.framesPerBuffer = savedValue; |
| 1051 |
printf( "\n" );
|
| 1052 |
numRuns += 1;
|
| 1053 |
} |
| 1054 |
// Run one with single parameters in case we did not do a series.
|
| 1055 |
if( numRuns == 0 ) |
| 1056 |
{
|
| 1057 |
int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
|
| 1058 |
totalBadChannels += numBadChannels; |
| 1059 |
} |
| 1060 |
} |
| 1061 |
|
| 1062 |
printf("\nTest Sample Formats using Half Duplex IO -----\n" );
|
| 1063 |
|
| 1064 |
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice ); |
| 1065 |
testParams.flags = PAQA_FLAG_TWO_STREAMS; |
| 1066 |
for( iFlags= 0; iFlags<4; iFlags++ ) |
| 1067 |
{
|
| 1068 |
// Cycle through combinations of flags.
|
| 1069 |
testParams.streamFlags = 0;
|
| 1070 |
if( iFlags & 1 ) testParams.streamFlags |= paClipOff; |
| 1071 |
if( iFlags & 2 ) testParams.streamFlags |= paDitherOff; |
| 1072 |
|
| 1073 |
for( iFormat=0; iFormat<numSampleFormats; iFormat++ ) |
| 1074 |
{
|
| 1075 |
int numBadChannels;
|
| 1076 |
PaSampleFormat format = sampleFormats[ iFormat ]; |
| 1077 |
testParams.inputParameters.sampleFormat = format; |
| 1078 |
testParams.outputParameters.sampleFormat = format; |
| 1079 |
printf("Sample format = %d = %s, PaStreamFlags = 0x%02X\n", (int) format, sampleFormatNames[iFormat], (unsigned int) testParams.streamFlags ); |
| 1080 |
numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams ); |
| 1081 |
totalBadChannels += numBadChannels; |
| 1082 |
} |
| 1083 |
} |
| 1084 |
printf( "\n" );
|
| 1085 |
printf( "****************************************\n");
|
| 1086 |
|
| 1087 |
return totalBadChannels;
|
| 1088 |
} |
| 1089 |
|
| 1090 |
/*******************************************************************/
|
| 1091 |
int PaQa_CheckForClippedLoopback( LoopbackContext *loopbackContextPtr )
|
| 1092 |
{
|
| 1093 |
int clipped = 0; |
| 1094 |
TestParameters *testParamsPtr = loopbackContextPtr->test; |
| 1095 |
|
| 1096 |
// Start in the middle assuming past latency.
|
| 1097 |
int startFrame = testParamsPtr->maxFrames/2; |
| 1098 |
int numFrames = testParamsPtr->maxFrames/2; |
| 1099 |
|
| 1100 |
// Check to see if the signal is clipped.
|
| 1101 |
double amplitudeLeft = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[0], |
| 1102 |
testParamsPtr->baseFrequency, testParamsPtr->sampleRate, |
| 1103 |
startFrame, numFrames ); |
| 1104 |
double gainLeft = amplitudeLeft / testParamsPtr->amplitude;
|
| 1105 |
double amplitudeRight = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[1], |
| 1106 |
testParamsPtr->baseFrequency, testParamsPtr->sampleRate, |
| 1107 |
startFrame, numFrames ); |
| 1108 |
double gainRight = amplitudeLeft / testParamsPtr->amplitude;
|
| 1109 |
printf(" Loop gain: left = %f, right = %f\n", gainLeft, gainRight );
|
| 1110 |
|
| 1111 |
if( (amplitudeLeft > 1.0 ) || (amplitudeRight > 1.0) ) |
| 1112 |
{
|
| 1113 |
printf("ERROR - loop gain is too high. Should be around than 1.0. Please lower output level and/or input gain.\n" );
|
| 1114 |
clipped = 1;
|
| 1115 |
} |
| 1116 |
return clipped;
|
| 1117 |
} |
| 1118 |
|
| 1119 |
/*******************************************************************/
|
| 1120 |
int PaQa_MeasureBackgroundNoise( LoopbackContext *loopbackContextPtr, double *rmsPtr ) |
| 1121 |
{
|
| 1122 |
int result = 0; |
| 1123 |
*rmsPtr = 0.0; |
| 1124 |
// Rewind so we can record some input.
|
| 1125 |
loopbackContextPtr->recordings[0].numFrames = 0; |
| 1126 |
loopbackContextPtr->recordings[1].numFrames = 0; |
| 1127 |
result = PaQa_RunInputOnly( loopbackContextPtr ); |
| 1128 |
if( result == 0 ) |
| 1129 |
{
|
| 1130 |
double leftRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[0].buffer, |
| 1131 |
loopbackContextPtr->recordings[0].numFrames );
|
| 1132 |
double rightRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[1].buffer, |
| 1133 |
loopbackContextPtr->recordings[1].numFrames );
|
| 1134 |
*rmsPtr = (leftRMS + rightRMS) / 2.0; |
| 1135 |
} |
| 1136 |
return result;
|
| 1137 |
} |
| 1138 |
|
| 1139 |
/*******************************************************************/
|
| 1140 |
/**
|
| 1141 |
* Output a sine wave then try to detect it on input.
|
| 1142 |
*
|
| 1143 |
* @return 1 if loopback connected, 0 if not, or negative error.
|
| 1144 |
*/
|
| 1145 |
int PaQa_CheckForLoopBack( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
|
| 1146 |
{
|
| 1147 |
TestParameters testParams; |
| 1148 |
LoopbackContext loopbackContext; |
| 1149 |
const PaDeviceInfo *inputDeviceInfo;
|
| 1150 |
const PaDeviceInfo *outputDeviceInfo;
|
| 1151 |
PaError err = paNoError; |
| 1152 |
double minAmplitude;
|
| 1153 |
int loopbackIsConnected;
|
| 1154 |
int startFrame, numFrames;
|
| 1155 |
double magLeft, magRight;
|
| 1156 |
|
| 1157 |
inputDeviceInfo = Pa_GetDeviceInfo( inputDevice ); |
| 1158 |
if( inputDeviceInfo == NULL ) |
| 1159 |
{
|
| 1160 |
printf("ERROR - Pa_GetDeviceInfo for input returned NULL.\n");
|
| 1161 |
return paInvalidDevice;
|
| 1162 |
} |
| 1163 |
if( inputDeviceInfo->maxInputChannels < 2 ) |
| 1164 |
{
|
| 1165 |
return 0; |
| 1166 |
} |
| 1167 |
|
| 1168 |
outputDeviceInfo = Pa_GetDeviceInfo( outputDevice ); |
| 1169 |
if( outputDeviceInfo == NULL ) |
| 1170 |
{
|
| 1171 |
printf("ERROR - Pa_GetDeviceInfo for output returned NULL.\n");
|
| 1172 |
return paInvalidDevice;
|
| 1173 |
} |
| 1174 |
if( outputDeviceInfo->maxOutputChannels < 2 ) |
| 1175 |
{
|
| 1176 |
return 0; |
| 1177 |
} |
| 1178 |
|
| 1179 |
printf( "Look for loopback cable between \"%s\" => \"%s\"\n", outputDeviceInfo->name, inputDeviceInfo->name);
|
| 1180 |
|
| 1181 |
printf( " Default suggested input latency (msec): low = %5.2f, high = %5.2f\n",
|
| 1182 |
(inputDeviceInfo->defaultLowInputLatency * 1000.0), |
| 1183 |
(inputDeviceInfo->defaultHighInputLatency * 1000.0) ); |
| 1184 |
printf( " Default suggested output latency (msec): low = %5.2f, high = %5.2f\n",
|
| 1185 |
(outputDeviceInfo->defaultLowOutputLatency * 1000.0), |
| 1186 |
(outputDeviceInfo->defaultHighOutputLatency * 1000.0) ); |
| 1187 |
|
| 1188 |
PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice ); |
| 1189 |
|
| 1190 |
PaQa_OverrideTestParameters( &testParams, userOptions ); |
| 1191 |
|
| 1192 |
testParams.maxFrames = (int) (LOOPBACK_DETECTION_DURATION_SECONDS * testParams.sampleRate);
|
| 1193 |
minAmplitude = testParams.amplitude / 4.0; |
| 1194 |
|
| 1195 |
// Check to see if the selected formats are supported.
|
| 1196 |
if( Pa_IsFormatSupported( &testParams.inputParameters, NULL, testParams.sampleRate ) != paFormatIsSupported ) |
| 1197 |
{
|
| 1198 |
printf( "Input not supported for this format!\n" );
|
| 1199 |
return 0; |
| 1200 |
} |
| 1201 |
if( Pa_IsFormatSupported( NULL, &testParams.outputParameters, testParams.sampleRate ) != paFormatIsSupported ) |
| 1202 |
{
|
| 1203 |
printf( "Output not supported for this format!\n" );
|
| 1204 |
return 0; |
| 1205 |
} |
| 1206 |
|
| 1207 |
PaQa_SetupLoopbackContext( &loopbackContext, &testParams ); |
| 1208 |
|
| 1209 |
if( inputDevice == outputDevice )
|
| 1210 |
{
|
| 1211 |
// Use full duplex if checking for loopback on one device.
|
| 1212 |
testParams.flags &= ~PAQA_FLAG_TWO_STREAMS; |
| 1213 |
} |
| 1214 |
else
|
| 1215 |
{
|
| 1216 |
// Use half duplex if checking for loopback on two different device.
|
| 1217 |
testParams.flags = PAQA_FLAG_TWO_STREAMS; |
| 1218 |
} |
| 1219 |
err = PaQa_RunLoopback( &loopbackContext ); |
| 1220 |
QA_ASSERT_TRUE("loopback detection callback did not run", (loopbackContext.callbackCount > 1) ); |
| 1221 |
|
| 1222 |
// Analyse recording to see if we captured the output.
|
| 1223 |
// Start in the middle assuming past latency.
|
| 1224 |
startFrame = testParams.maxFrames/2;
|
| 1225 |
numFrames = testParams.maxFrames/2;
|
| 1226 |
magLeft = PaQa_CorrelateSine( &loopbackContext.recordings[0],
|
| 1227 |
loopbackContext.generators[0].frequency,
|
| 1228 |
testParams.sampleRate, |
| 1229 |
startFrame, numFrames, NULL );
|
| 1230 |
magRight = PaQa_CorrelateSine( &loopbackContext.recordings[1],
|
| 1231 |
loopbackContext.generators[1].frequency,
|
| 1232 |
testParams.sampleRate, |
| 1233 |
startFrame, numFrames, NULL );
|
| 1234 |
printf(" Amplitudes: left = %f, right = %f\n", magLeft, magRight );
|
| 1235 |
|
| 1236 |
// Check for backwards cable.
|
| 1237 |
loopbackIsConnected = ((magLeft > minAmplitude) && (magRight > minAmplitude)); |
| 1238 |
|
| 1239 |
if( !loopbackIsConnected )
|
| 1240 |
{
|
| 1241 |
double magLeftReverse = PaQa_CorrelateSine( &loopbackContext.recordings[0], |
| 1242 |
loopbackContext.generators[1].frequency,
|
| 1243 |
testParams.sampleRate, |
| 1244 |
startFrame, numFrames, NULL );
|
| 1245 |
|
| 1246 |
double magRightReverse = PaQa_CorrelateSine( &loopbackContext.recordings[1], |
| 1247 |
loopbackContext.generators[0].frequency,
|
| 1248 |
testParams.sampleRate, |
| 1249 |
startFrame, numFrames, NULL );
|
| 1250 |
|
| 1251 |
if ((magLeftReverse > minAmplitude) && (magRightReverse>minAmplitude))
|
| 1252 |
{
|
| 1253 |
printf("ERROR - You seem to have the left and right channels swapped on the loopback cable!\n");
|
| 1254 |
} |
| 1255 |
} |
| 1256 |
else
|
| 1257 |
{
|
| 1258 |
double rms = 0.0; |
| 1259 |
if( PaQa_CheckForClippedLoopback( &loopbackContext ) )
|
| 1260 |
{
|
| 1261 |
// Clipped so don't use this loopback.
|
| 1262 |
loopbackIsConnected = 0;
|
| 1263 |
} |
| 1264 |
|
| 1265 |
err = PaQa_MeasureBackgroundNoise( &loopbackContext, &rms ); |
| 1266 |
printf(" Background noise = %f\n", rms );
|
| 1267 |
if( err )
|
| 1268 |
{
|
| 1269 |
printf("ERROR - Could not measure background noise on this input!\n");
|
| 1270 |
loopbackIsConnected = 0;
|
| 1271 |
} |
| 1272 |
else if( rms > MAX_BACKGROUND_NOISE_RMS ) |
| 1273 |
{
|
| 1274 |
printf("ERROR - There is too much background noise on this input!\n");
|
| 1275 |
loopbackIsConnected = 0;
|
| 1276 |
} |
| 1277 |
} |
| 1278 |
|
| 1279 |
PaQa_TeardownLoopbackContext( &loopbackContext ); |
| 1280 |
return loopbackIsConnected;
|
| 1281 |
|
| 1282 |
error:
|
| 1283 |
PaQa_TeardownLoopbackContext( &loopbackContext ); |
| 1284 |
return err;
|
| 1285 |
} |
| 1286 |
|
| 1287 |
/*******************************************************************/
|
| 1288 |
/**
|
| 1289 |
* If there is a loopback connection then run the analysis.
|
| 1290 |
*/
|
| 1291 |
static int CheckLoopbackAndScan( UserOptions *userOptions, |
| 1292 |
PaDeviceIndex iIn, PaDeviceIndex iOut ) |
| 1293 |
{
|
| 1294 |
int loopbackConnected = PaQa_CheckForLoopBack( userOptions, iIn, iOut );
|
| 1295 |
if( loopbackConnected > 0 ) |
| 1296 |
{
|
| 1297 |
PaQa_AnalyzeLoopbackConnection( userOptions, iIn, iOut ); |
| 1298 |
return 1; |
| 1299 |
} |
| 1300 |
return 0; |
| 1301 |
} |
| 1302 |
|
| 1303 |
/*******************************************************************/
|
| 1304 |
/**
|
| 1305 |
* Scan every combination of output to input device.
|
| 1306 |
* If a loopback is found the analyse the combination.
|
| 1307 |
* The scan can be overriden using the -i and -o command line options.
|
| 1308 |
*/
|
| 1309 |
static int ScanForLoopback(UserOptions *userOptions) |
| 1310 |
{
|
| 1311 |
PaDeviceIndex iIn,iOut; |
| 1312 |
int numLoopbacks = 0; |
| 1313 |
int numDevices;
|
| 1314 |
numDevices = Pa_GetDeviceCount(); |
| 1315 |
|
| 1316 |
// If both devices are specified then just use that combination.
|
| 1317 |
if ((userOptions->inputDevice >= 0) && (userOptions->outputDevice >= 0)) |
| 1318 |
{
|
| 1319 |
numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, userOptions->outputDevice ); |
| 1320 |
} |
| 1321 |
else if (userOptions->inputDevice >= 0) |
| 1322 |
{
|
| 1323 |
// Just scan for output.
|
| 1324 |
for( iOut=0; iOut<numDevices; iOut++ ) |
| 1325 |
{
|
| 1326 |
numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, iOut ); |
| 1327 |
} |
| 1328 |
} |
| 1329 |
else if (userOptions->outputDevice >= 0) |
| 1330 |
{
|
| 1331 |
// Just scan for input.
|
| 1332 |
for( iIn=0; iIn<numDevices; iIn++ ) |
| 1333 |
{
|
| 1334 |
numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, userOptions->outputDevice ); |
| 1335 |
} |
| 1336 |
} |
| 1337 |
else
|
| 1338 |
{
|
| 1339 |
// Scan both.
|
| 1340 |
for( iOut=0; iOut<numDevices; iOut++ ) |
| 1341 |
{
|
| 1342 |
for( iIn=0; iIn<numDevices; iIn++ ) |
| 1343 |
{
|
| 1344 |
numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, iOut ); |
| 1345 |
} |
| 1346 |
} |
| 1347 |
} |
| 1348 |
QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) ); |
| 1349 |
return numLoopbacks;
|
| 1350 |
|
| 1351 |
error:
|
| 1352 |
return -1; |
| 1353 |
} |
| 1354 |
|
| 1355 |
/*==========================================================================================*/
|
| 1356 |
int TestSampleFormatConversion( void ) |
| 1357 |
{
|
| 1358 |
int i;
|
| 1359 |
const float floatInput[] = { 1.0, 0.5, -0.5, -1.0 }; |
| 1360 |
|
| 1361 |
const char charInput[] = { 127, 64, -64, -128 }; |
| 1362 |
const unsigned char ucharInput[] = { 255, 128+64, 64, 0 }; |
| 1363 |
const short shortInput[] = { 32767, 32768/2, -32768/2, -32768 }; |
| 1364 |
const int intInput[] = { 2147483647, 2147483647/2, -1073741824 /*-2147483648/2 doesn't work in msvc*/, -2147483648 }; |
| 1365 |
|
| 1366 |
float floatOutput[4]; |
| 1367 |
short shortOutput[4]; |
| 1368 |
int intOutput[4]; |
| 1369 |
unsigned char ucharOutput[4]; |
| 1370 |
char charOutput[4]; |
| 1371 |
|
| 1372 |
QA_ASSERT_EQUALS("int must be 32-bit", 4, (int) sizeof(int) ); |
| 1373 |
QA_ASSERT_EQUALS("short must be 16-bit", 2, (int) sizeof(short) ); |
| 1374 |
|
| 1375 |
// from Float ======
|
| 1376 |
PaQa_ConvertFromFloat( floatInput, 4, paUInt8, ucharOutput );
|
| 1377 |
for( i=0; i<4; i++ ) |
| 1378 |
{
|
| 1379 |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paUInt8 -> error", ucharInput[i], ucharOutput[i], 1 ); |
| 1380 |
} |
| 1381 |
|
| 1382 |
PaQa_ConvertFromFloat( floatInput, 4, paInt8, charOutput );
|
| 1383 |
for( i=0; i<4; i++ ) |
| 1384 |
{
|
| 1385 |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt8 -> error", charInput[i], charOutput[i], 1 ); |
| 1386 |
} |
| 1387 |
|
| 1388 |
PaQa_ConvertFromFloat( floatInput, 4, paInt16, shortOutput );
|
| 1389 |
for( i=0; i<4; i++ ) |
| 1390 |
{
|
| 1391 |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt16 error", shortInput[i], shortOutput[i], 1 ); |
| 1392 |
} |
| 1393 |
|
| 1394 |
PaQa_ConvertFromFloat( floatInput, 4, paInt32, intOutput );
|
| 1395 |
for( i=0; i<4; i++ ) |
| 1396 |
{
|
| 1397 |
QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt32 error", intInput[i], intOutput[i], 0x00010000 ); |
| 1398 |
} |
| 1399 |
|
| 1400 |
|
| 1401 |
// to Float ======
|
| 1402 |
memset( floatOutput, 0, sizeof(floatOutput) ); |
| 1403 |
PaQa_ConvertToFloat( ucharInput, 4, paUInt8, floatOutput );
|
| 1404 |
for( i=0; i<4; i++ ) |
| 1405 |
{
|
| 1406 |
QA_ASSERT_CLOSE( "paUInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 ); |
| 1407 |
} |
| 1408 |
|
| 1409 |
memset( floatOutput, 0, sizeof(floatOutput) ); |
| 1410 |
PaQa_ConvertToFloat( charInput, 4, paInt8, floatOutput );
|
| 1411 |
for( i=0; i<4; i++ ) |
| 1412 |
{
|
| 1413 |
QA_ASSERT_CLOSE( "paInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 ); |
| 1414 |
} |
| 1415 |
|
| 1416 |
memset( floatOutput, 0, sizeof(floatOutput) ); |
| 1417 |
PaQa_ConvertToFloat( shortInput, 4, paInt16, floatOutput );
|
| 1418 |
for( i=0; i<4; i++ ) |
| 1419 |
{
|
| 1420 |
QA_ASSERT_CLOSE( "paInt16 -> paFloat32 error", floatInput[i], floatOutput[i], 0.001 ); |
| 1421 |
} |
| 1422 |
|
| 1423 |
memset( floatOutput, 0, sizeof(floatOutput) ); |
| 1424 |
PaQa_ConvertToFloat( intInput, 4, paInt32, floatOutput );
|
| 1425 |
for( i=0; i<4; i++ ) |
| 1426 |
{
|
| 1427 |
QA_ASSERT_CLOSE( "paInt32 -> paFloat32 error", floatInput[i], floatOutput[i], 0.00001 ); |
| 1428 |
} |
| 1429 |
|
| 1430 |
return 0; |
| 1431 |
|
| 1432 |
error:
|
| 1433 |
return -1; |
| 1434 |
} |
| 1435 |
|
| 1436 |
|
| 1437 |
/*******************************************************************/
|
| 1438 |
void usage( const char *name ) |
| 1439 |
{
|
| 1440 |
printf("%s [-i# -o# -l# -r# -s# -m -w -dDir]\n", name);
|
| 1441 |
printf(" -i# - Input device ID. Will scan for loopback cable if not specified.\n");
|
| 1442 |
printf(" -o# - Output device ID. Will scan for loopback if not specified.\n");
|
| 1443 |
printf(" -l# - Latency for both input and output in milliseconds.\n");
|
| 1444 |
printf(" --inputLatency # Input latency in milliseconds.\n");
|
| 1445 |
printf(" --outputLatency # Output latency in milliseconds.\n");
|
| 1446 |
printf(" -r# - Sample Rate in Hz. Will use multiple common rates if not specified.\n");
|
| 1447 |
printf(" -s# - Size of callback buffer in frames, framesPerBuffer. Will use common values if not specified.\n");
|
| 1448 |
printf(" -w - Save bad recordings in a WAV file.\n");
|
| 1449 |
printf(" -dDir - Path for Directory for WAV files. Default is current directory.\n");
|
| 1450 |
printf(" -m - Just test the DSP Math code and not the audio devices.\n");
|
| 1451 |
printf(" -v - Verbose reports.\n");
|
| 1452 |
} |
| 1453 |
|
| 1454 |
/*******************************************************************/
|
| 1455 |
int main( int argc, char **argv ) |
| 1456 |
{
|
| 1457 |
int i;
|
| 1458 |
UserOptions userOptions; |
| 1459 |
int result = 0; |
| 1460 |
int justMath = 0; |
| 1461 |
char *executableName = argv[0]; |
| 1462 |
|
| 1463 |
printf("PortAudio LoopBack Test built " __DATE__ " at " __TIME__ "\n"); |
| 1464 |
|
| 1465 |
if( argc > 1 ){ |
| 1466 |
printf("running with arguments:");
|
| 1467 |
for(i=1; i < argc; ++i ) |
| 1468 |
printf(" %s", argv[i] );
|
| 1469 |
printf("\n");
|
| 1470 |
}else{
|
| 1471 |
printf("running with no arguments\n");
|
| 1472 |
} |
| 1473 |
|
| 1474 |
memset(&userOptions, 0, sizeof(userOptions)); |
| 1475 |
userOptions.inputDevice = paNoDevice; |
| 1476 |
userOptions.outputDevice = paNoDevice; |
| 1477 |
userOptions.sampleRate = -1;
|
| 1478 |
userOptions.framesPerBuffer = -1;
|
| 1479 |
userOptions.inputLatency = -1;
|
| 1480 |
userOptions.outputLatency = -1;
|
| 1481 |
userOptions.waveFilePath = ".";
|
| 1482 |
|
| 1483 |
// Process arguments. Skip name of executable.
|
| 1484 |
i = 1;
|
| 1485 |
while( i<argc )
|
| 1486 |
{
|
| 1487 |
char *arg = argv[i];
|
| 1488 |
if( arg[0] == '-' ) |
| 1489 |
{
|
| 1490 |
switch(arg[1]) |
| 1491 |
{
|
| 1492 |
case 'i': |
| 1493 |
userOptions.inputDevice = atoi(&arg[2]);
|
| 1494 |
break;
|
| 1495 |
case 'o': |
| 1496 |
userOptions.outputDevice = atoi(&arg[2]);
|
| 1497 |
break;
|
| 1498 |
case 'l': |
| 1499 |
userOptions.inputLatency = userOptions.outputLatency = atoi(&arg[2]);
|
| 1500 |
break;
|
| 1501 |
case 'r': |
| 1502 |
userOptions.sampleRate = atoi(&arg[2]);
|
| 1503 |
break;
|
| 1504 |
case 's': |
| 1505 |
userOptions.framesPerBuffer = atoi(&arg[2]);
|
| 1506 |
break;
|
| 1507 |
|
| 1508 |
case 'm': |
| 1509 |
printf("Option -m set so just testing math and not the audio devices.\n");
|
| 1510 |
justMath = 1;
|
| 1511 |
break;
|
| 1512 |
|
| 1513 |
case 'w': |
| 1514 |
userOptions.saveBadWaves = 1;
|
| 1515 |
break;
|
| 1516 |
case 'd': |
| 1517 |
userOptions.waveFilePath = &arg[2];
|
| 1518 |
break;
|
| 1519 |
|
| 1520 |
case 'v': |
| 1521 |
userOptions.verbose = 1;
|
| 1522 |
break;
|
| 1523 |
|
| 1524 |
case 'h': |
| 1525 |
usage( executableName ); |
| 1526 |
exit(0);
|
| 1527 |
break;
|
| 1528 |
|
| 1529 |
case '-': |
| 1530 |
{
|
| 1531 |
if( strcmp( &arg[2], "inputLatency" ) == 0 ) |
| 1532 |
{
|
| 1533 |
i += 1;
|
| 1534 |
userOptions.inputLatency = atoi(argv[i]); |
| 1535 |
} |
| 1536 |
else if( strcmp( &arg[2], "outputLatency" ) == 0 ) |
| 1537 |
{
|
| 1538 |
i += 1;
|
| 1539 |
userOptions.outputLatency = atoi(argv[i]); |
| 1540 |
} |
| 1541 |
else
|
| 1542 |
{
|
| 1543 |
printf("Illegal option: %s\n", arg);
|
| 1544 |
usage( executableName ); |
| 1545 |
exit(1);
|
| 1546 |
} |
| 1547 |
|
| 1548 |
} |
| 1549 |
break;
|
| 1550 |
|
| 1551 |
|
| 1552 |
default:
|
| 1553 |
printf("Illegal option: %s\n", arg);
|
| 1554 |
usage( executableName ); |
| 1555 |
exit(1);
|
| 1556 |
break;
|
| 1557 |
} |
| 1558 |
} |
| 1559 |
else
|
| 1560 |
{
|
| 1561 |
printf("Illegal argument: %s\n", arg);
|
| 1562 |
usage( executableName ); |
| 1563 |
exit(1);
|
| 1564 |
|
| 1565 |
} |
| 1566 |
i += 1;
|
| 1567 |
} |
| 1568 |
|
| 1569 |
result = PaQa_TestAnalyzer(); |
| 1570 |
|
| 1571 |
// Test sample format conversion tool.
|
| 1572 |
result = TestSampleFormatConversion(); |
| 1573 |
|
| 1574 |
if( (result == 0) && (justMath == 0) ) |
| 1575 |
{
|
| 1576 |
Pa_Initialize(); |
| 1577 |
printf( "PortAudio version number = %d\nPortAudio version text = '%s'\n",
|
| 1578 |
Pa_GetVersion(), Pa_GetVersionText() ); |
| 1579 |
printf( "=============== PortAudio Devices ========================\n" );
|
| 1580 |
PaQa_ListAudioDevices(); |
| 1581 |
if( Pa_GetDeviceCount() == 0 ) |
| 1582 |
printf( "no devices found.\n" );
|
| 1583 |
|
| 1584 |
printf( "=============== Detect Loopback ==========================\n" );
|
| 1585 |
ScanForLoopback(&userOptions); |
| 1586 |
|
| 1587 |
Pa_Terminate(); |
| 1588 |
} |
| 1589 |
|
| 1590 |
if (g_testsFailed == 0) |
| 1591 |
{
|
| 1592 |
printf("PortAudio QA SUCCEEDED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed );
|
| 1593 |
return 0; |
| 1594 |
|
| 1595 |
} |
| 1596 |
else
|
| 1597 |
{
|
| 1598 |
printf("PortAudio QA FAILED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed );
|
| 1599 |
return 1; |
| 1600 |
} |
| 1601 |
} |