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 /*******************************************************************/
RecordAndPlaySinesCallback(const void * inputBuffer,void * outputBuffer,unsigned long framesPerBuffer,const PaStreamCallbackTimeInfo * timeInfo,PaStreamCallbackFlags statusFlags,void * userData)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
CopyStreamInfoToLoopbackContext(LoopbackContext * loopbackContext,PaStream * inputStream,PaStream * outputStream)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 */
PaQa_RunLoopbackFullDuplex(LoopbackContext * loopbackContext)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
PaQa_WaitForStream(LoopbackContext * loopbackContext)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 */
PaQa_RunLoopbackHalfDuplex(LoopbackContext * loopbackContext)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 */
PaQa_RunInputOnly(LoopbackContext * loopbackContext)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 /*******************************************************************/
RecordAndPlayBlockingIO(PaStream * inStream,PaStream * outStream,LoopbackContext * loopbackContext)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 */
PaQa_RunLoopbackHalfDuplexBlockingIO(LoopbackContext * loopbackContext)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 */
PaQa_RunLoopbackFullDuplexBlockingIO(LoopbackContext * loopbackContext)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 */
PaQa_RunLoopback(LoopbackContext * loopbackContext)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 /*******************************************************************/
PaQa_SaveTestResultToWaveFile(UserOptions * userOptions,PaQaRecording * recording)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 /*******************************************************************/
PaQa_SetupLoopbackContext(LoopbackContext * loopbackContextPtr,TestParameters * testParams)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 /*******************************************************************/
PaQa_TeardownLoopbackContext(LoopbackContext * loopbackContextPtr)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 /*******************************************************************/
PaQa_PrintShortErrorReport(PaQaAnalysisResult * analysisResultPtr,int channel)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 /*******************************************************************/
PaQa_PrintFullErrorReport(PaQaAnalysisResult * analysisResultPtr,int channel)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 */
PaQa_SingleLoopBackTest(UserOptions * userOptions,TestParameters * testParams)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 /*******************************************************************/
PaQa_SetDefaultTestParameters(TestParameters * testParamsPtr,PaDeviceIndex inputDevice,PaDeviceIndex outputDevice)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 /*******************************************************************/
PaQa_OverrideTestParameters(TestParameters * testParamsPtr,UserOptions * userOptions)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 */
PaQa_AnalyzeLoopbackConnection(UserOptions * userOptions,PaDeviceIndex inputDevice,PaDeviceIndex outputDevice)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 /*******************************************************************/
PaQa_CheckForClippedLoopback(LoopbackContext * loopbackContextPtr)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 /*******************************************************************/
PaQa_MeasureBackgroundNoise(LoopbackContext * loopbackContextPtr,double * rmsPtr)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 */
PaQa_CheckForLoopBack(UserOptions * userOptions,PaDeviceIndex inputDevice,PaDeviceIndex outputDevice)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 */
CheckLoopbackAndScan(UserOptions * userOptions,PaDeviceIndex iIn,PaDeviceIndex iOut)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 */
ScanForLoopback(UserOptions * userOptions)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 /*==========================================================================================*/
TestSampleFormatConversion(void)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 /*******************************************************************/
usage(const char * name)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 /*******************************************************************/
main(int argc,char ** argv)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