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 PeCoffLoaderRelocateImageEx(IN UINT16 * Reloc,IN OUT CHAR8 * Fixup,IN OUT CHAR8 ** FixupData,IN UINT64 Adjust)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 }; PeHotRelocateImageEx(IN UINT16 * Reloc,IN OUT CHAR8 * Fixup,IN OUT CHAR8 ** FixupData,IN UINT64 Adjust)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 } 1602