To check out this repository please hg clone the following URL, or open the URL using EasyMercurial or your preferred Mercurial client.

The primary repository for this project is hosted at https://github.com/sonic-visualiser/sv-dependency-builds .
This repository is a read-only copy which is updated automatically every hour.

Statistics Download as Zip
| Branch: | Tag: | Revision:

root / src / portaudio_20161030_catalina_patch / qa / loopback / src / paqa.c @ 162:d43aab368df9

History | View | Annotate | Download (48.6 KB)

1

    
2
/*
3
 * PortAudio Portable Real-Time Audio Library
4
 * Latest Version at: http://www.portaudio.com
5
 *
6
 * Copyright (c) 1999-2010 Phil Burk and Ross Bencina
7
 *
8
 * Permission is hereby granted, free of charge, to any person obtaining
9
 * a copy of this software and associated documentation files
10
 * (the "Software"), to deal in the Software without restriction,
11
 * including without limitation the rights to use, copy, modify, merge,
12
 * publish, distribute, sublicense, and/or sell copies of the Software,
13
 * and to permit persons to whom the Software is furnished to do so,
14
 * subject to the following conditions:
15
 *
16
 * The above copyright notice and this permission notice shall be
17
 * included in all copies or substantial portions of the Software.
18
 *
19
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
23
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
24
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26
 */
27

    
28
/*
29
 * The text above constitutes the entire PortAudio license; however, 
30
 * the PortAudio community also makes the following non-binding requests:
31
 *
32
 * Any person wishing to distribute modifications to the Software is
33
 * requested to send the modifications to the original developer so that
34
 * they can be incorporated into the canonical version. It is also 
35
 * requested that these non-binding requests be included along with the 
36
 * license above.
37
 */
38

    
39
#include <stdio.h>
40
#include <stdlib.h>
41
#include <memory.h>
42
#include <math.h>
43
#include <string.h>
44

    
45
#include "portaudio.h"
46

    
47
#include "qa_tools.h"
48

    
49
#include "paqa_tools.h"
50
#include "audio_analyzer.h"
51
#include "test_audio_analyzer.h"
52

    
53
/** Accumulate counts for how many tests pass or fail. */
54
int g_testsPassed = 0;
55
int g_testsFailed = 0;
56

    
57
#define MAX_NUM_GENERATORS                   (8)
58
#define MAX_NUM_RECORDINGS                   (8)
59
#define MAX_BACKGROUND_NOISE_RMS             (0.0004)
60
#define LOOPBACK_DETECTION_DURATION_SECONDS  (0.8)
61
#define DEFAULT_FRAMES_PER_BUFFER            (0)
62
#define PAQA_WAIT_STREAM_MSEC                (100)
63
#define PAQA_TEST_DURATION                   (1.2)
64

    
65
// Use two separate streams instead of one full duplex stream.
66
#define PAQA_FLAG_TWO_STREAMS       (1<<0)
67
// Use bloching read/write for loopback.
68
#define PAQA_FLAG_USE_BLOCKING_IO   (1<<1)
69

    
70
const char * s_FlagOnNames[] =
71
{
72
        "Two Streams (Half Duplex)",
73
        "Blocking Read/Write"
74
};
75

    
76
const char * s_FlagOffNames[] =
77
{
78
        "One Stream (Full Duplex)",
79
        "Callback"
80
};
81

    
82

    
83
/** Parameters that describe a single test run. */
84
typedef struct TestParameters_s
85
{
86
        PaStreamParameters inputParameters;
87
        PaStreamParameters outputParameters;
88
        double             sampleRate;
89
        int                samplesPerFrame;
90
        int                framesPerBuffer;
91
        int                maxFrames;
92
        double             baseFrequency;
93
        double             amplitude;
94
    PaStreamFlags      streamFlags;  // paClipOff, etc
95
        int                flags;        // PAQA_FLAG_TWO_STREAMS, PAQA_FLAG_USE_BLOCKING_IO
96
} TestParameters;
97

    
98
typedef struct LoopbackContext_s
99
{
100
        // Generate a unique signal on each channel.
101
        PaQaSineGenerator  generators[MAX_NUM_GENERATORS];
102
        // Record each channel individually.
103
        PaQaRecording      recordings[MAX_NUM_RECORDINGS];
104
        
105
        // Reported by the stream after it's opened
106
        PaTime             streamInfoInputLatency;
107
        PaTime             streamInfoOutputLatency;
108

    
109
        // Measured at runtime.
110
        volatile int       callbackCount; // incremented for each callback
111
        volatile int       inputBufferCount; // incremented if input buffer not NULL
112
        int                inputUnderflowCount;
113
        int                inputOverflowCount;
114
        
115
        volatile int       outputBufferCount; // incremented if output buffer not NULL
116
        int                outputOverflowCount;
117
        int                outputUnderflowCount;
118
        
119
    // Measure whether input or output is lagging behind.
120
    volatile int       minInputOutputDelta;
121
    volatile int       maxInputOutputDelta;
122
    
123
        int                minFramesPerBuffer;
124
        int                maxFramesPerBuffer;
125
        int                primingCount;
126
        TestParameters    *test;
127
        volatile int       done;
128
} LoopbackContext;
129

    
130
typedef struct UserOptions_s
131
{
132
        int           sampleRate;
133
        int           framesPerBuffer;
134
        int           inputLatency;
135
        int           outputLatency;
136
        int           saveBadWaves;
137
        int           verbose;
138
        int           waveFileCount;
139
        const char   *waveFilePath;
140
        PaDeviceIndex inputDevice;
141
        PaDeviceIndex outputDevice;
142
} UserOptions;
143

    
144
#define BIG_BUFFER_SIZE  (sizeof(float) * 2 * 2 * 1024)
145
static unsigned char g_ReadWriteBuffer[BIG_BUFFER_SIZE];
146

    
147
#define MAX_CONVERSION_SAMPLES   (2 * 32 * 1024)
148
#define CONVERSION_BUFFER_SIZE  (sizeof(float) * 2 * MAX_CONVERSION_SAMPLES)
149
static unsigned char g_ConversionBuffer[CONVERSION_BUFFER_SIZE];
150

    
151
/*******************************************************************/
152
static int RecordAndPlaySinesCallback( const void *inputBuffer, void *outputBuffer,
153
                                                unsigned long framesPerBuffer,
154
                                                const PaStreamCallbackTimeInfo* timeInfo,
155
                                                PaStreamCallbackFlags statusFlags,
156
                                                void *userData )
157
{
158
        int i;
159
        LoopbackContext *loopbackContext = (LoopbackContext *) userData;
160
        
161
        
162
        loopbackContext->callbackCount += 1;
163
        if( statusFlags & paInputUnderflow ) loopbackContext->inputUnderflowCount += 1;
164
        if( statusFlags & paInputOverflow ) loopbackContext->inputOverflowCount += 1;
165
        if( statusFlags & paOutputUnderflow ) loopbackContext->outputUnderflowCount += 1;
166
        if( statusFlags & paOutputOverflow ) loopbackContext->outputOverflowCount += 1;
167
        if( statusFlags & paPrimingOutput ) loopbackContext->primingCount += 1;
168
        if( framesPerBuffer > loopbackContext->maxFramesPerBuffer )
169
        {
170
                loopbackContext->maxFramesPerBuffer = framesPerBuffer;
171
        }
172
        if( framesPerBuffer < loopbackContext->minFramesPerBuffer )
173
        {
174
                loopbackContext->minFramesPerBuffer = framesPerBuffer;
175
        }
176
        
177
    /* This may get called with NULL inputBuffer during initial setup.
178
         * We may also use the same callback with output only streams.
179
         */
180
        if( inputBuffer != NULL)
181
        {
182
                int channelsPerFrame = loopbackContext->test->inputParameters.channelCount;
183
                float *in = (float *)inputBuffer;
184
                PaSampleFormat inFormat = loopbackContext->test->inputParameters.sampleFormat;
185
                
186
                loopbackContext->inputBufferCount += 1;
187
                
188
                if( inFormat != paFloat32 )
189
                {
190
                        int samplesToConvert = framesPerBuffer * channelsPerFrame;
191
                        in = (float *) g_ConversionBuffer;
192
                        if( samplesToConvert > MAX_CONVERSION_SAMPLES )
193
                        {
194
                                // Hack to prevent buffer overflow.
195
                                // @todo Loop with small buffer instead of failing.
196
                                printf("Format conversion buffer too small!\n");
197
                                return paComplete;
198
                        }
199
                        PaQa_ConvertToFloat( inputBuffer, samplesToConvert, inFormat, (float *) g_ConversionBuffer );
200
                }
201
                
202
                // Read each channel from the buffer.
203
                for( i=0; i<channelsPerFrame; i++ )
204
                {                        
205
                        loopbackContext->done |= PaQa_WriteRecording( &loopbackContext->recordings[i],
206
                                                                                in + i, 
207
                                                                                framesPerBuffer,
208
                                                                                channelsPerFrame );
209
                }
210
        }
211
        
212
        if( outputBuffer != NULL )
213
        {
214
                int channelsPerFrame = loopbackContext->test->outputParameters.channelCount;
215
                float *out = (float *)outputBuffer;
216
                PaSampleFormat outFormat = loopbackContext->test->outputParameters.sampleFormat;
217
                
218
                loopbackContext->outputBufferCount += 1;
219
                
220
                if( outFormat != paFloat32 )
221
                {
222
                        // If we need to convert then mix to the g_ConversionBuffer and then convert into the PA outputBuffer.
223
                        out = (float *) g_ConversionBuffer;
224
                }
225
                        
226
                PaQa_EraseBuffer( out, framesPerBuffer, channelsPerFrame );
227
                for( i=0; i<channelsPerFrame; i++ )
228
                {
229
                        PaQa_MixSine( &loopbackContext->generators[i],
230
                                                 out + i,
231
                                                 framesPerBuffer,
232
                                                 channelsPerFrame );
233
                }
234
                
235
                if( outFormat != paFloat32 )
236
                {
237
                        int samplesToConvert = framesPerBuffer * channelsPerFrame;
238
                        if( samplesToConvert > MAX_CONVERSION_SAMPLES )
239
                        {
240
                                printf("Format conversion buffer too small!\n");
241
                                return paComplete;
242
                        }                        
243
                        PaQa_ConvertFromFloat( out, framesPerBuffer * channelsPerFrame, outFormat, outputBuffer );
244
                }
245
                
246
        }
247
    
248
    // Measure whether the input or output are lagging behind.
249
    // Don't measure lag at end.
250
    if( !loopbackContext->done )
251
    {        
252
        int inputOutputDelta = loopbackContext->inputBufferCount - loopbackContext->outputBufferCount;
253
        if( loopbackContext->maxInputOutputDelta < inputOutputDelta )
254
        {
255
            loopbackContext->maxInputOutputDelta = inputOutputDelta;
256
        }
257
        if( loopbackContext->minInputOutputDelta > inputOutputDelta )
258
        {
259
            loopbackContext->minInputOutputDelta = inputOutputDelta;
260
        }
261
    }
262
    
263
        return loopbackContext->done ? paComplete : paContinue;
264
}
265

    
266
static void CopyStreamInfoToLoopbackContext( LoopbackContext *loopbackContext, PaStream *inputStream, PaStream *outputStream )
267
{
268
        const PaStreamInfo *inputStreamInfo = Pa_GetStreamInfo( inputStream );
269
        const PaStreamInfo *outputStreamInfo = Pa_GetStreamInfo( outputStream );
270

    
271
        loopbackContext->streamInfoInputLatency = inputStreamInfo ? inputStreamInfo->inputLatency : -1;
272
        loopbackContext->streamInfoOutputLatency = outputStreamInfo ? outputStreamInfo->outputLatency : -1;
273
}
274

    
275
/*******************************************************************/
276
/** 
277
 * Open a full duplex audio stream.
278
 * Generate sine waves on the output channels and record the input channels.
279
 * Then close the stream.
280
 * @return 0 if OK or negative error.
281
 */
282
int PaQa_RunLoopbackFullDuplex( LoopbackContext *loopbackContext )
283
{
284
        PaStream *stream = NULL;
285
        PaError err = 0;
286
        TestParameters *test = loopbackContext->test;
287
        loopbackContext->done = 0;
288
        // Use one full duplex stream.
289
        err = Pa_OpenStream(
290
                                        &stream,
291
                                        &test->inputParameters,
292
                                        &test->outputParameters,
293
                                        test->sampleRate,
294
                                        test->framesPerBuffer,
295
                                        paClipOff, /* we won't output out of range samples so don't bother clipping them */
296
                                        RecordAndPlaySinesCallback,
297
                                        loopbackContext );
298
        if( err != paNoError ) goto error;
299
        
300
        CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream );
301

    
302
        err = Pa_StartStream( stream );
303
        if( err != paNoError ) goto error;
304
                
305
        // Wait for stream to finish.
306
        while( loopbackContext->done == 0 )
307
        {
308
                Pa_Sleep(PAQA_WAIT_STREAM_MSEC);
309
        }
310
        
311
        err = Pa_StopStream( stream );
312
        if( err != paNoError ) goto error;
313

    
314
        err = Pa_CloseStream( stream );
315
        if( err != paNoError ) goto error;
316
                
317
        return 0;
318
        
319
error:
320
        return err;        
321
}
322

    
323
/*******************************************************************/
324
/** 
325
 * Open two audio streams, one for input and one for output.
326
 * Generate sine waves on the output channels and record the input channels.
327
 * Then close the stream.
328
 * @return 0 if OK or paTimedOut.
329
 */
330

    
331
int PaQa_WaitForStream( LoopbackContext *loopbackContext )
332
{
333
        int timeoutMSec = 1000 * PAQA_TEST_DURATION * 2;
334
        
335
        // Wait for stream to finish or timeout.
336
        while( (loopbackContext->done == 0) && (timeoutMSec > 0) )
337
        {
338
                Pa_Sleep(PAQA_WAIT_STREAM_MSEC);
339
                timeoutMSec -= PAQA_WAIT_STREAM_MSEC;
340
        }
341
        
342
        if( loopbackContext->done == 0 )
343
        {
344
                printf("ERROR - stream completion timed out!");
345
                return paTimedOut;
346
        }
347
        return 0;
348
}
349

    
350
/*******************************************************************/
351
/** 
352
 * Open two audio streams, one for input and one for output.
353
 * Generate sine waves on the output channels and record the input channels.
354
 * Then close the stream.
355
 * @return 0 if OK or negative error.
356
 */
357
int PaQa_RunLoopbackHalfDuplex( LoopbackContext *loopbackContext )
358
{
359
        PaStream *inStream = NULL;
360
        PaStream *outStream = NULL;
361
        PaError err = 0;
362
        int timedOut = 0;
363
        TestParameters *test = loopbackContext->test;
364
        loopbackContext->done = 0;
365
        
366
        // Use two half duplex streams.
367
        err = Pa_OpenStream(
368
                                                &inStream,
369
                                                &test->inputParameters,
370
                                                NULL,
371
                                                test->sampleRate,
372
                                                test->framesPerBuffer,
373
                                                test->streamFlags,
374
                                                RecordAndPlaySinesCallback,
375
                                                loopbackContext );
376
        if( err != paNoError ) goto error;
377
        err = Pa_OpenStream(
378
                                                &outStream,
379
                                                NULL,
380
                                                &test->outputParameters,
381
                                                test->sampleRate,
382
                                                test->framesPerBuffer,
383
                                                test->streamFlags,
384
                                                RecordAndPlaySinesCallback,
385
                                                loopbackContext );
386
        if( err != paNoError ) goto error;
387

    
388
        CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream );
389

    
390
        err = Pa_StartStream( inStream );
391
        if( err != paNoError ) goto error;
392
        
393
        // Start output later so we catch the beginning of the waveform.
394
        err = Pa_StartStream( outStream );
395
        if( err != paNoError ) goto error;
396
        
397
        timedOut = PaQa_WaitForStream( loopbackContext );
398
        
399
        err = Pa_StopStream( inStream );
400
        if( err != paNoError ) goto error;
401
                
402
        err = Pa_StopStream( outStream );
403
        if( err != paNoError ) goto error;
404
        
405
        err = Pa_CloseStream( inStream );
406
        if( err != paNoError ) goto error;
407
        
408
        err = Pa_CloseStream( outStream );
409
        if( err != paNoError ) goto error;
410
        
411
        return timedOut;
412
        
413
error:
414
        return err;        
415
}
416

    
417

    
418
/*******************************************************************/
419
/** 
420
 * Open one audio streams, just for input.
421
 * Record background level.
422
 * Then close the stream.
423
 * @return 0 if OK or negative error.
424
 */
425
int PaQa_RunInputOnly( LoopbackContext *loopbackContext )
426
{
427
        PaStream *inStream = NULL;
428
        PaError err = 0;
429
        int timedOut = 0;
430
        TestParameters *test = loopbackContext->test;
431
        loopbackContext->done = 0;
432
        
433
        // Just open an input stream.
434
        err = Pa_OpenStream(
435
                                                &inStream,
436
                                                &test->inputParameters,
437
                                                NULL,
438
                                                test->sampleRate,
439
                                                test->framesPerBuffer,
440
                                                paClipOff, /* We won't output out of range samples so don't bother clipping them. */
441
                                                RecordAndPlaySinesCallback,
442
                                                loopbackContext );
443
        if( err != paNoError ) goto error;
444

    
445
        err = Pa_StartStream( inStream );
446
        if( err != paNoError ) goto error;
447
        
448
        timedOut = PaQa_WaitForStream( loopbackContext );
449
        
450
        err = Pa_StopStream( inStream );
451
        if( err != paNoError ) goto error;
452
        
453
        err = Pa_CloseStream( inStream );
454
        if( err != paNoError ) goto error;
455
        
456
        return timedOut;
457
        
458
error:
459
        return err;        
460
}
461

    
462
/*******************************************************************/
463
static int RecordAndPlayBlockingIO( PaStream *inStream,
464
                                                                          PaStream *outStream,
465
                                                                          LoopbackContext *loopbackContext
466
                                                                          )
467
{        
468
        int i;
469
        float *in = (float *)g_ReadWriteBuffer;
470
        float *out = (float *)g_ReadWriteBuffer;
471
        PaError err;
472
        int done = 0;
473
        long available;
474
        const long maxPerBuffer = 64;
475
        TestParameters *test = loopbackContext->test;
476
        long framesPerBuffer = test->framesPerBuffer;
477
        if( framesPerBuffer <= 0 )
478
        {
479
                framesPerBuffer = maxPerBuffer; // bigger values might run past end of recording
480
        }
481
        
482
        // Read in audio.
483
        err = Pa_ReadStream( inStream, in, framesPerBuffer );
484
        // Ignore an overflow on the first read.
485
        //if( !((loopbackContext->callbackCount == 0) && (err == paInputOverflowed)) )
486
        if( err != paInputOverflowed )
487
        {
488
                QA_ASSERT_EQUALS( "Pa_ReadStream failed", paNoError, err );
489
        }
490
        else
491
        {
492
                loopbackContext->inputOverflowCount += 1;
493
        }
494

    
495
        
496
        // Save in a recording.
497
        for( i=0; i<loopbackContext->test->inputParameters.channelCount; i++ )
498
        {
499
                done |= PaQa_WriteRecording( &loopbackContext->recordings[i],
500
                         in + i,
501
                         framesPerBuffer,
502
                         loopbackContext->test->inputParameters.channelCount );
503
        }
504
        
505
        // Synthesize audio.
506
        available = Pa_GetStreamWriteAvailable( outStream );
507
        if( available > (2*framesPerBuffer) ) available = (2*framesPerBuffer);
508
        PaQa_EraseBuffer( out, available, loopbackContext->test->outputParameters.channelCount );
509
        for( i=0; i<loopbackContext->test->outputParameters.channelCount; i++ )
510
        {
511
                PaQa_MixSine( &loopbackContext->generators[i],
512
                          out + i,
513
                          available,
514
                          loopbackContext->test->outputParameters.channelCount );
515
        }
516
        
517
        // Write out audio.
518
        err = Pa_WriteStream( outStream, out, available );
519
        // Ignore an underflow on the first write.
520
        //if( !((loopbackContext->callbackCount == 0) && (err == paOutputUnderflowed)) )
521
        if( err != paOutputUnderflowed )
522
        {
523
                QA_ASSERT_EQUALS( "Pa_WriteStream failed", paNoError, err );
524
        }
525
        else
526
        {
527
                loopbackContext->outputUnderflowCount += 1;
528
        }
529
        
530
                
531
        loopbackContext->callbackCount += 1;
532
        
533
        return done;
534
error:
535
        return err;
536
}
537

    
538

    
539
/*******************************************************************/
540
/** 
541
 * Open two audio streams with non-blocking IO.
542
 * Generate sine waves on the output channels and record the input channels.
543
 * Then close the stream.
544
 * @return 0 if OK or negative error.
545
 */
546
int PaQa_RunLoopbackHalfDuplexBlockingIO( LoopbackContext *loopbackContext )
547
{
548
        PaStream *inStream = NULL;
549
        PaStream *outStream = NULL;
550
        PaError err = 0;
551
        TestParameters *test = loopbackContext->test;
552
        
553
        // Use two half duplex streams.
554
        err = Pa_OpenStream(
555
                                                &inStream,
556
                                                &test->inputParameters,
557
                                                NULL,
558
                                                test->sampleRate,
559
                                                test->framesPerBuffer,
560
                                                paClipOff, /* we won't output out of range samples so don't bother clipping them */
561
                                                NULL, // causes non-blocking IO
562
                                                NULL );
563
        if( err != paNoError ) goto error1;
564
        err = Pa_OpenStream(
565
                                                &outStream,
566
                                                NULL,
567
                                                &test->outputParameters,
568
                                                test->sampleRate,
569
                                                test->framesPerBuffer,
570
                                                paClipOff, /* we won't output out of range samples so don't bother clipping them */
571
                                                NULL, // causes non-blocking IO
572
                                                NULL );
573
        if( err != paNoError ) goto error2;
574
        
575
        CopyStreamInfoToLoopbackContext( loopbackContext, inStream, outStream );
576

    
577
        err = Pa_StartStream( outStream );
578
        if( err != paNoError ) goto error3;
579
        
580
        err = Pa_StartStream( inStream );
581
        if( err != paNoError ) goto error3;
582
        
583
        while( err == 0 )
584
        {
585
                err = RecordAndPlayBlockingIO( inStream, outStream, loopbackContext );
586
                if( err < 0 ) goto error3;
587
        }
588
        
589
        err = Pa_StopStream( inStream );
590
        if( err != paNoError ) goto error3;
591
        
592
        err = Pa_StopStream( outStream );
593
        if( err != paNoError ) goto error3;
594
        
595
        err = Pa_CloseStream( outStream );
596
        if( err != paNoError ) goto error2;
597
        
598
        err = Pa_CloseStream( inStream );
599
        if( err != paNoError ) goto error1;
600
        
601
        
602
        return 0;
603
        
604
error3:
605
        Pa_CloseStream( outStream );
606
error2:
607
        Pa_CloseStream( inStream );
608
error1:
609
        return err;        
610
}
611

    
612

    
613
/*******************************************************************/
614
/** 
615
 * Open one audio stream with non-blocking IO.
616
 * Generate sine waves on the output channels and record the input channels.
617
 * Then close the stream.
618
 * @return 0 if OK or negative error.
619
 */
620
int PaQa_RunLoopbackFullDuplexBlockingIO( LoopbackContext *loopbackContext )
621
{
622
        PaStream *stream = NULL;
623
        PaError err = 0;
624
        TestParameters *test = loopbackContext->test;
625
        
626
        // Use one full duplex stream.
627
        err = Pa_OpenStream(
628
                                                &stream,
629
                                                &test->inputParameters,
630
                                                &test->outputParameters,
631
                                                test->sampleRate,
632
                                                test->framesPerBuffer,
633
                                                paClipOff, /* we won't output out of range samples so don't bother clipping them */
634
                                                NULL, // causes non-blocking IO
635
                                                NULL );
636
        if( err != paNoError ) goto error1;
637
                
638
        CopyStreamInfoToLoopbackContext( loopbackContext, stream, stream );
639

    
640
        err = Pa_StartStream( stream );
641
        if( err != paNoError ) goto error2;
642
        
643
        while( err == 0 )
644
        {
645
                err = RecordAndPlayBlockingIO( stream, stream, loopbackContext );
646
                if( err < 0 ) goto error2;
647
        }
648
        
649
        err = Pa_StopStream( stream );
650
        if( err != paNoError ) goto error2;
651
        
652
        
653
        err = Pa_CloseStream( stream );
654
        if( err != paNoError ) goto error1;
655
        
656
        
657
        return 0;
658
        
659
error2:
660
        Pa_CloseStream( stream );
661
error1:
662
        return err;        
663
}
664

    
665

    
666
/*******************************************************************/
667
/** 
668
 * Run some kind of loopback test.
669
 * @return 0 if OK or negative error.
670
 */
671
int PaQa_RunLoopback( LoopbackContext *loopbackContext )
672
{
673
        PaError err = 0;
674
        TestParameters *test = loopbackContext->test;
675
        
676
        
677
        if( test->flags & PAQA_FLAG_TWO_STREAMS )
678
        {
679
                if( test->flags & PAQA_FLAG_USE_BLOCKING_IO )
680
                {
681
                        err = PaQa_RunLoopbackHalfDuplexBlockingIO( loopbackContext );
682
                }
683
                else
684
                {
685
                        err = PaQa_RunLoopbackHalfDuplex( loopbackContext );
686
                }
687
        }
688
        else
689
        {
690
                if( test->flags & PAQA_FLAG_USE_BLOCKING_IO )
691
                {
692
                        err = PaQa_RunLoopbackFullDuplexBlockingIO( loopbackContext );
693
                }
694
                else
695
                {
696
                        err = PaQa_RunLoopbackFullDuplex( loopbackContext );
697
                }
698
        }
699
        
700
        if( err != paNoError )
701
        {
702
                printf("PortAudio error = %s\n", Pa_GetErrorText( err ) );
703
        }
704
        return err;        
705
}
706

    
707
/*******************************************************************/
708
static int PaQa_SaveTestResultToWaveFile( UserOptions *userOptions, PaQaRecording *recording )
709
{
710
        if( userOptions->saveBadWaves )
711
        {
712
                char filename[256];
713
#ifdef WIN32
714
        _snprintf( filename, sizeof(filename), "%s\\paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ );
715
#else
716
                snprintf( filename, sizeof(filename), "%s/paloopback_%d.wav", userOptions->waveFilePath, userOptions->waveFileCount++ );
717
#endif   
718
                printf( "\"%s\", ", filename );
719
                return PaQa_SaveRecordingToWaveFile( recording, filename );
720
        }
721
        return 0;
722
}
723

    
724
/*******************************************************************/
725
static int PaQa_SetupLoopbackContext( LoopbackContext *loopbackContextPtr, TestParameters *testParams )
726
{
727
        int i;
728
        // Setup loopback context.
729
        memset( loopbackContextPtr, 0, sizeof(LoopbackContext) );        
730
        loopbackContextPtr->test = testParams;
731
        for( i=0; i<testParams->samplesPerFrame; i++ )
732
        {
733
                int err = PaQa_InitializeRecording( &loopbackContextPtr->recordings[i], testParams->maxFrames, testParams->sampleRate );
734
                QA_ASSERT_EQUALS( "PaQa_InitializeRecording failed", paNoError, err );
735
        }
736
        for( i=0; i<testParams->samplesPerFrame; i++ )
737
        {
738
                PaQa_SetupSineGenerator( &loopbackContextPtr->generators[i], PaQa_GetNthFrequency( testParams->baseFrequency, i ),
739
                                                                testParams->amplitude, testParams->sampleRate );
740
        }
741
        loopbackContextPtr->minFramesPerBuffer = 0x0FFFFFFF;
742
        return 0;
743
error:
744
        return -1;
745
}
746

    
747
/*******************************************************************/
748
static void PaQa_TeardownLoopbackContext( LoopbackContext *loopbackContextPtr )
749
{
750
        int i;
751
        if( loopbackContextPtr->test != NULL )
752
        {
753
                for( i=0; i<loopbackContextPtr->test->samplesPerFrame; i++ )
754
                {
755
                        PaQa_TerminateRecording( &loopbackContextPtr->recordings[i] );
756
                }
757
        }
758
}
759

    
760
/*******************************************************************/
761
static void PaQa_PrintShortErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel )
762
{
763
        printf("channel %d ", channel);
764
        if( analysisResultPtr->popPosition > 0 )
765
        {
766
                printf("POP %0.3f at %d, ", (double)analysisResultPtr->popAmplitude, (int)analysisResultPtr->popPosition );        
767
        }
768
        else
769
        {
770
                if( analysisResultPtr->addedFramesPosition > 0 )
771
                {
772
                        printf("ADD %d at %d ", (int)analysisResultPtr->numAddedFrames, (int)analysisResultPtr->addedFramesPosition );        
773
                }
774
                
775
                if( analysisResultPtr->droppedFramesPosition > 0 )
776
                {
777
                        printf("DROP %d at %d ", (int)analysisResultPtr->numDroppedFrames, (int)analysisResultPtr->droppedFramesPosition );        
778
                }
779
        }
780
}
781

    
782
/*******************************************************************/
783
static void PaQa_PrintFullErrorReport( PaQaAnalysisResult *analysisResultPtr, int channel )
784
{
785
        printf("\n=== Loopback Analysis ===================\n");
786
        printf("             channel: %d\n", channel );
787
        printf("             latency: %10.3f\n", analysisResultPtr->latency );
788
        printf("      amplitudeRatio: %10.3f\n", (double)analysisResultPtr->amplitudeRatio );        
789
        printf("         popPosition: %10.3f\n", (double)analysisResultPtr->popPosition );        
790
        printf("        popAmplitude: %10.3f\n", (double)analysisResultPtr->popAmplitude );
791
        printf("    num added frames: %10.3f\n", analysisResultPtr->numAddedFrames );
792
        printf("     added frames at: %10.3f\n", analysisResultPtr->addedFramesPosition );
793
        printf("  num dropped frames: %10.3f\n", analysisResultPtr->numDroppedFrames );
794
        printf("   dropped frames at: %10.3f\n", analysisResultPtr->droppedFramesPosition );
795
}
796

    
797
/*******************************************************************/
798
/** 
799
 * Test loopback connection using the given parameters.
800
 * @return number of channels with glitches, or negative error.
801
 */
802
static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *testParams )
803
{
804
        int i;
805
        LoopbackContext loopbackContext;
806
        PaError err = paNoError;
807
        PaQaTestTone testTone;
808
        PaQaAnalysisResult analysisResult;
809
        int numBadChannels = 0;
810
        
811
        printf("| %5d | %6d | ", ((int)(testParams->sampleRate+0.5)), testParams->framesPerBuffer );
812
        fflush(stdout);
813
        
814
        testTone.samplesPerFrame = testParams->samplesPerFrame;
815
        testTone.sampleRate = testParams->sampleRate;
816
        testTone.amplitude = testParams->amplitude;
817
        testTone.startDelay = 0;
818
        
819
        err = PaQa_SetupLoopbackContext( &loopbackContext, testParams );
820
        if( err ) return err;
821
        
822
        err = PaQa_RunLoopback( &loopbackContext );
823
        QA_ASSERT_TRUE("loopback did not run", (loopbackContext.callbackCount > 1) );
824

    
825
        printf( "%7.2f %7.2f %7.2f | ",
826
                   loopbackContext.streamInfoInputLatency * 1000.0,
827
                   loopbackContext.streamInfoOutputLatency * 1000.0,
828
                   (loopbackContext.streamInfoInputLatency + loopbackContext.streamInfoOutputLatency) * 1000.0
829
                   );
830

    
831
        printf( "%4d/%4d/%4d, %4d/%4d/%4d | ",
832
                   loopbackContext.inputOverflowCount,
833
                   loopbackContext.inputUnderflowCount,
834
                   loopbackContext.inputBufferCount,
835
                   loopbackContext.outputOverflowCount,
836
                   loopbackContext.outputUnderflowCount,
837
                   loopbackContext.outputBufferCount
838
                   );
839
        
840
        // Analyse recording to detect glitches.
841
        for( i=0; i<testParams->samplesPerFrame; i++ )
842
        {
843
                double freq = PaQa_GetNthFrequency( testParams->baseFrequency, i );
844
                testTone.frequency = freq;
845
                
846
                PaQa_AnalyseRecording(  &loopbackContext.recordings[i], &testTone, &analysisResult );
847
                
848
                if( i==0 )
849
                {
850
            double latencyMSec;
851

    
852
                        printf( "%4d-%4d | ",
853
                                   loopbackContext.minFramesPerBuffer,
854
                                   loopbackContext.maxFramesPerBuffer
855
                                   );
856
                        
857
                        latencyMSec = 1000.0 * analysisResult.latency / testParams->sampleRate;
858
                        printf("%7.2f | ", latencyMSec );
859
                                                
860
                }
861
                
862
                if( analysisResult.valid )
863
                {
864
                        int badChannel = ( (analysisResult.popPosition > 0)
865
                                           || (analysisResult.addedFramesPosition > 0)
866
                                           || (analysisResult.droppedFramesPosition > 0) );
867
                        
868
                        if( badChannel )
869
                        {        
870
                                if( userOptions->verbose )
871
                                {
872
                                        PaQa_PrintFullErrorReport( &analysisResult, i );
873
                                }
874
                                else
875
                                {
876
                                        PaQa_PrintShortErrorReport( &analysisResult, i );
877
                                }
878
                                PaQa_SaveTestResultToWaveFile( userOptions, &loopbackContext.recordings[i] );
879
                        }
880
                        numBadChannels += badChannel;
881
                }
882
                else
883
                {
884
                        printf( "[%d] No or low signal, ampRatio = %f", i, analysisResult.amplitudeRatio );
885
                        numBadChannels += 1;
886
                }
887

    
888
        }
889
        if( numBadChannels == 0 )
890
        {
891
                printf( "OK" );
892
        }
893

    
894
    // Print the # errors so far to make it easier to see where the error occured.
895
        printf( " - #errs = %d\n", g_testsFailed );
896

    
897
        PaQa_TeardownLoopbackContext( &loopbackContext );
898
        if( numBadChannels > 0 )
899
        {
900
                g_testsFailed += 1;
901
        }
902
        return numBadChannels;        
903
        
904
error:
905
        PaQa_TeardownLoopbackContext( &loopbackContext );
906
        printf( "\n" );
907
        g_testsFailed += 1;
908
        return err;        
909
}
910

    
911
/*******************************************************************/
912
static void PaQa_SetDefaultTestParameters( TestParameters *testParamsPtr, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
913
{
914
        memset( testParamsPtr, 0, sizeof(TestParameters) );
915
        
916
        testParamsPtr->samplesPerFrame = 2;
917
        testParamsPtr->amplitude = 0.5;
918
        testParamsPtr->sampleRate = 44100;
919
        testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate);
920
        testParamsPtr->framesPerBuffer = DEFAULT_FRAMES_PER_BUFFER;
921
        testParamsPtr->baseFrequency = 200.0;
922
        testParamsPtr->flags = PAQA_FLAG_TWO_STREAMS;
923
    testParamsPtr->streamFlags = paClipOff; /* we won't output out of range samples so don't bother clipping them */
924
        
925
        testParamsPtr->inputParameters.device = inputDevice;
926
        testParamsPtr->inputParameters.sampleFormat = paFloat32;
927
        testParamsPtr->inputParameters.channelCount = testParamsPtr->samplesPerFrame;
928
        testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultLowInputLatency;
929
        //testParamsPtr->inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputDevice )->defaultHighInputLatency;
930
        
931
        testParamsPtr->outputParameters.device = outputDevice;
932
        testParamsPtr->outputParameters.sampleFormat = paFloat32;
933
        testParamsPtr->outputParameters.channelCount = testParamsPtr->samplesPerFrame;
934
        testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultLowOutputLatency;
935
        //testParamsPtr->outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputDevice )->defaultHighOutputLatency;
936
}
937

    
938
/*******************************************************************/
939
static void PaQa_OverrideTestParameters( TestParameters *testParamsPtr,  UserOptions *userOptions )
940
{
941
        // Check to see if a specific value was requested.
942
        if( userOptions->sampleRate >= 0 )
943
        {
944
                testParamsPtr->sampleRate = userOptions->sampleRate;
945
                testParamsPtr->maxFrames = (int) (PAQA_TEST_DURATION * testParamsPtr->sampleRate);
946
        }
947
        if( userOptions->framesPerBuffer >= 0 )
948
        {
949
                testParamsPtr->framesPerBuffer = userOptions->framesPerBuffer;
950
        }
951
        if( userOptions->inputLatency >= 0 )
952
        {
953
                testParamsPtr->inputParameters.suggestedLatency = userOptions->inputLatency * 0.001;
954
        }
955
        if( userOptions->outputLatency >= 0 )
956
        {
957
                testParamsPtr->outputParameters.suggestedLatency = userOptions->outputLatency * 0.001;
958
        }
959
        printf( "   Running with suggested latency (msec): input = %5.2f, out = %5.2f\n",
960
                (testParamsPtr->inputParameters.suggestedLatency * 1000.0),
961
                (testParamsPtr->outputParameters.suggestedLatency * 1000.0) );
962
}
963

    
964
/*******************************************************************/
965
/** 
966
 * Run a series of tests on this loopback connection.
967
 * @return number of bad channel results
968
 */
969
static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
970
{
971
        int iFlags;
972
        int iRate;
973
        int iSize;
974
        int iFormat;
975
        int savedValue;
976
        TestParameters testParams;
977
        const PaDeviceInfo *inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );        
978
        const PaDeviceInfo *outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );                
979
    int totalBadChannels = 0;
980

    
981
        // test half duplex first because it is more likely to work.
982
        int flagSettings[] = { PAQA_FLAG_TWO_STREAMS, 0 };
983
        int numFlagSettings = (sizeof(flagSettings)/sizeof(int));
984

    
985
        double sampleRates[] = { 8000.0, 11025.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0, 96000.0 };
986
        int numRates = (sizeof(sampleRates)/sizeof(double));
987
        
988
        // framesPerBuffer==0 means PA decides on the buffer size.
989
        int framesPerBuffers[] = { 0, 16, 32, 40, 64, 100, 128, 256, 512, 1024 };
990
        int numBufferSizes = (sizeof(framesPerBuffers)/sizeof(int));
991
        
992
        PaSampleFormat sampleFormats[] = { paFloat32, paUInt8, paInt8, paInt16, paInt32 };
993
        const char *sampleFormatNames[] = { "paFloat32", "paUInt8", "paInt8", "paInt16", "paInt32" };
994
        int numSampleFormats = (sizeof(sampleFormats)/sizeof(PaSampleFormat));
995
        
996
    printf( "=============== Analysing Loopback %d to %d =====================\n", outputDevice, inputDevice  );
997
        printf( "   Devices: %s => %s\n", outputDeviceInfo->name, inputDeviceInfo->name);
998
        
999
        PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
1000
        
1001
        PaQa_OverrideTestParameters( &testParams, userOptions );
1002
        
1003
        // Loop though combinations of audio parameters.
1004
        for( iFlags=0; iFlags<numFlagSettings; iFlags++ )
1005
        {
1006
                int numRuns = 0;
1007

    
1008
                testParams.flags = flagSettings[iFlags];
1009
                printf( "\n************ Mode = %s ************\n",
1010
                           (( testParams.flags & 1 ) ? s_FlagOnNames[0] : s_FlagOffNames[0]) );
1011

    
1012
                printf("|-   requested  -|-  stream info latency  -|- measured ------------------------------\n");
1013
                printf("|-sRate-|-fr/buf-|- in    - out   - total -|- over/under/calls for in, out -|- frm/buf -|-latency-|- channel results -\n");
1014

    
1015
                // Loop though various sample rates.
1016
                if( userOptions->sampleRate < 0 )
1017
                {
1018
                        savedValue = testParams.sampleRate;
1019
                        for( iRate=0; iRate<numRates; iRate++ )
1020
                        {
1021
                int numBadChannels;
1022

    
1023
                                // SAMPLE RATE
1024
                                testParams.sampleRate = sampleRates[iRate];
1025
                                testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate);
1026
                                
1027
                                numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
1028
                                totalBadChannels += numBadChannels;
1029
                        }
1030
                        testParams.sampleRate = savedValue;
1031
                        testParams.maxFrames = (int) (PAQA_TEST_DURATION * testParams.sampleRate);
1032
                        printf( "\n" );
1033
                        numRuns += 1;
1034
                }
1035
                
1036
                // Loop through various buffer sizes.
1037
                if( userOptions->framesPerBuffer < 0 )
1038
                {
1039
                        savedValue = testParams.framesPerBuffer;
1040
                        for( iSize=0; iSize<numBufferSizes; iSize++ )
1041
                        {        
1042
                int numBadChannels;
1043

    
1044
                                // BUFFER SIZE
1045
                                testParams.framesPerBuffer = framesPerBuffers[iSize];
1046
                                
1047
                                numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
1048
                                totalBadChannels += numBadChannels;                        
1049
                        }
1050
                        testParams.framesPerBuffer = savedValue;
1051
                        printf( "\n" );                
1052
                        numRuns += 1;
1053
                }
1054
                // Run one with single parameters in case we did not do a series.
1055
                if( numRuns == 0 )
1056
                {
1057
                        int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
1058
                        totalBadChannels += numBadChannels;                                                
1059
                }
1060
        }
1061
                        
1062
        printf("\nTest Sample Formats using Half Duplex IO -----\n" );
1063
    
1064
        PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
1065
        testParams.flags = PAQA_FLAG_TWO_STREAMS;        
1066
    for( iFlags= 0; iFlags<4; iFlags++ )
1067
    {
1068
        // Cycle through combinations of flags.
1069
        testParams.streamFlags = 0;
1070
        if( iFlags & 1 ) testParams.streamFlags |= paClipOff;
1071
        if( iFlags & 2 ) testParams.streamFlags |= paDitherOff;
1072
        
1073
        for( iFormat=0; iFormat<numSampleFormats; iFormat++ )
1074
        {        
1075
            int numBadChannels;
1076
            PaSampleFormat format = sampleFormats[ iFormat ];
1077
            testParams.inputParameters.sampleFormat = format;
1078
            testParams.outputParameters.sampleFormat = format;
1079
            printf("Sample format = %d = %s, PaStreamFlags = 0x%02X\n", (int) format, sampleFormatNames[iFormat], (unsigned int) testParams.streamFlags );
1080
            numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
1081
            totalBadChannels += numBadChannels;                        
1082
        }
1083
    }
1084
        printf( "\n" );
1085
        printf( "****************************************\n");
1086
        
1087
        return totalBadChannels;
1088
}
1089

    
1090
/*******************************************************************/
1091
int PaQa_CheckForClippedLoopback( LoopbackContext *loopbackContextPtr )
1092
{
1093
        int clipped = 0;
1094
        TestParameters *testParamsPtr = loopbackContextPtr->test;
1095
        
1096
        // Start in the middle assuming past latency.
1097
        int startFrame = testParamsPtr->maxFrames/2;
1098
        int numFrames = testParamsPtr->maxFrames/2;
1099
        
1100
        // Check to see if the signal is clipped.
1101
        double amplitudeLeft = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[0],
1102
                                                                                                                        testParamsPtr->baseFrequency, testParamsPtr->sampleRate,
1103
                                                                                                                        startFrame, numFrames );
1104
        double gainLeft = amplitudeLeft / testParamsPtr->amplitude;
1105
        double amplitudeRight = PaQa_MeasureSineAmplitudeBySlope( &loopbackContextPtr->recordings[1],
1106
                                                                                                                         testParamsPtr->baseFrequency, testParamsPtr->sampleRate,
1107
                                                                                                                         startFrame, numFrames );
1108
        double gainRight = amplitudeLeft / testParamsPtr->amplitude;
1109
        printf("   Loop gain: left = %f, right = %f\n", gainLeft, gainRight );
1110

    
1111
        if( (amplitudeLeft > 1.0 ) || (amplitudeRight > 1.0) )
1112
        {
1113
                printf("ERROR - loop gain is too high. Should be around than 1.0. Please lower output level and/or input gain.\n" );
1114
                clipped = 1;
1115
        }
1116
        return clipped;
1117
}
1118

    
1119
/*******************************************************************/
1120
int PaQa_MeasureBackgroundNoise( LoopbackContext *loopbackContextPtr, double *rmsPtr )
1121
{
1122
        int result = 0;
1123
        *rmsPtr = 0.0;
1124
        // Rewind so we can record some input.
1125
        loopbackContextPtr->recordings[0].numFrames = 0;
1126
        loopbackContextPtr->recordings[1].numFrames = 0;
1127
        result = PaQa_RunInputOnly( loopbackContextPtr );
1128
        if( result == 0 )
1129
        {
1130
                double leftRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[0].buffer,
1131
                                                                                                        loopbackContextPtr->recordings[0].numFrames );
1132
                double rightRMS = PaQa_MeasureRootMeanSquare( loopbackContextPtr->recordings[1].buffer,
1133
                                                                                                         loopbackContextPtr->recordings[1].numFrames );
1134
                *rmsPtr = (leftRMS + rightRMS) / 2.0;
1135
        }
1136
        return result;
1137
}
1138

    
1139
/*******************************************************************/
1140
/** 
1141
 * Output a sine wave then try to detect it on input.
1142
 *
1143
 * @return 1 if loopback connected, 0 if not, or negative error.
1144
 */
1145
int PaQa_CheckForLoopBack( UserOptions *userOptions, PaDeviceIndex inputDevice, PaDeviceIndex outputDevice )
1146
{
1147
        TestParameters testParams;
1148
        LoopbackContext loopbackContext;
1149
    const PaDeviceInfo *inputDeviceInfo;        
1150
    const PaDeviceInfo *outputDeviceInfo;                
1151
        PaError err = paNoError;
1152
        double minAmplitude;
1153
        int loopbackIsConnected;
1154
    int startFrame, numFrames;
1155
    double magLeft, magRight;
1156

    
1157
        inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );
1158
        if( inputDeviceInfo == NULL )
1159
        {
1160
                printf("ERROR - Pa_GetDeviceInfo for input returned NULL.\n");
1161
                return paInvalidDevice;
1162
        }
1163
        if( inputDeviceInfo->maxInputChannels < 2 )
1164
        {
1165
                return 0;
1166
        }
1167
        
1168
        outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );
1169
        if( outputDeviceInfo == NULL )
1170
        {
1171
                printf("ERROR - Pa_GetDeviceInfo for output returned NULL.\n");
1172
                return paInvalidDevice;
1173
        }
1174
        if( outputDeviceInfo->maxOutputChannels < 2 )
1175
        {
1176
                return 0;
1177
        }
1178
        
1179
        printf( "Look for loopback cable between \"%s\" => \"%s\"\n", outputDeviceInfo->name, inputDeviceInfo->name);
1180
        
1181
        printf( "   Default suggested input latency (msec): low = %5.2f, high = %5.2f\n",
1182
                (inputDeviceInfo->defaultLowInputLatency * 1000.0),
1183
                (inputDeviceInfo->defaultHighInputLatency * 1000.0) );
1184
        printf( "   Default suggested output latency (msec): low = %5.2f, high = %5.2f\n",
1185
                (outputDeviceInfo->defaultLowOutputLatency * 1000.0),
1186
                (outputDeviceInfo->defaultHighOutputLatency * 1000.0) );
1187
                
1188
        PaQa_SetDefaultTestParameters( &testParams, inputDevice, outputDevice );
1189
        
1190
        PaQa_OverrideTestParameters( &testParams, userOptions );
1191
        
1192
        testParams.maxFrames = (int) (LOOPBACK_DETECTION_DURATION_SECONDS * testParams.sampleRate);        
1193
        minAmplitude = testParams.amplitude / 4.0;
1194
        
1195
        // Check to see if the selected formats are supported.
1196
        if( Pa_IsFormatSupported( &testParams.inputParameters, NULL, testParams.sampleRate ) != paFormatIsSupported )
1197
        {
1198
                printf( "Input not supported for this format!\n" );
1199
                return 0;
1200
        }
1201
        if( Pa_IsFormatSupported( NULL, &testParams.outputParameters, testParams.sampleRate ) != paFormatIsSupported )
1202
        {
1203
                printf( "Output not supported for this format!\n" );
1204
                return 0;
1205
        }
1206
        
1207
        PaQa_SetupLoopbackContext( &loopbackContext, &testParams );
1208
                        
1209
        if( inputDevice == outputDevice )
1210
        {
1211
                // Use full duplex if checking for loopback on one device.
1212
                testParams.flags &= ~PAQA_FLAG_TWO_STREAMS;
1213
        }
1214
        else
1215
        {
1216
                // Use half duplex if checking for loopback on two different device.
1217
                testParams.flags = PAQA_FLAG_TWO_STREAMS;
1218
        }
1219
        err = PaQa_RunLoopback( &loopbackContext );
1220
        QA_ASSERT_TRUE("loopback detection callback did not run", (loopbackContext.callbackCount > 1) );
1221
        
1222
        // Analyse recording to see if we captured the output.
1223
        // Start in the middle assuming past latency.
1224
        startFrame = testParams.maxFrames/2;
1225
        numFrames = testParams.maxFrames/2;
1226
        magLeft = PaQa_CorrelateSine( &loopbackContext.recordings[0],
1227
                                                                        loopbackContext.generators[0].frequency,
1228
                                                                        testParams.sampleRate,
1229
                                                                        startFrame, numFrames, NULL );
1230
        magRight = PaQa_CorrelateSine( &loopbackContext.recordings[1],
1231
                                                                        loopbackContext.generators[1].frequency,
1232
                                                                        testParams.sampleRate,
1233
                                                                        startFrame, numFrames, NULL );
1234
        printf("   Amplitudes: left = %f, right = %f\n", magLeft, magRight );
1235
        
1236
        // Check for backwards cable.
1237
    loopbackIsConnected = ((magLeft > minAmplitude) && (magRight > minAmplitude));
1238

    
1239
        if( !loopbackIsConnected )
1240
        {
1241
                double magLeftReverse = PaQa_CorrelateSine( &loopbackContext.recordings[0],
1242
                                                                                                   loopbackContext.generators[1].frequency,
1243
                                                                                                   testParams.sampleRate,
1244
                                                                                                   startFrame, numFrames, NULL );
1245
                
1246
                double magRightReverse = PaQa_CorrelateSine( &loopbackContext.recordings[1], 
1247
                                                                                                        loopbackContext.generators[0].frequency,
1248
                                                                                                        testParams.sampleRate,
1249
                                                                                                        startFrame, numFrames, NULL );
1250
                
1251
                if ((magLeftReverse > minAmplitude) && (magRightReverse>minAmplitude))
1252
                {
1253
                        printf("ERROR - You seem to have the left and right channels swapped on the loopback cable!\n");
1254
                }
1255
        }
1256
        else
1257
        {
1258
                double rms = 0.0;
1259
                if( PaQa_CheckForClippedLoopback( &loopbackContext ) )
1260
                {
1261
                        // Clipped so don't use this loopback.
1262
                        loopbackIsConnected = 0;
1263
                }
1264
                
1265
                err = PaQa_MeasureBackgroundNoise( &loopbackContext, &rms );
1266
                printf("   Background noise = %f\n", rms );
1267
                if( err )
1268
                {
1269
                        printf("ERROR - Could not measure background noise on this input!\n");
1270
                        loopbackIsConnected = 0;
1271
                }
1272
                else if( rms > MAX_BACKGROUND_NOISE_RMS )
1273
                {                        
1274
                        printf("ERROR - There is too much background noise on this input!\n");
1275
                        loopbackIsConnected = 0;
1276
                }
1277
        }
1278
        
1279
        PaQa_TeardownLoopbackContext( &loopbackContext );
1280
        return loopbackIsConnected;        
1281
        
1282
error:
1283
        PaQa_TeardownLoopbackContext( &loopbackContext );
1284
        return err;        
1285
}
1286

    
1287
/*******************************************************************/
1288
/**
1289
 * If there is a loopback connection then run the analysis.
1290
 */
1291
static int CheckLoopbackAndScan( UserOptions *userOptions,
1292
                                                                PaDeviceIndex iIn, PaDeviceIndex iOut )
1293
{
1294
        int loopbackConnected = PaQa_CheckForLoopBack( userOptions, iIn, iOut );
1295
        if( loopbackConnected > 0 )
1296
        {
1297
                PaQa_AnalyzeLoopbackConnection( userOptions, iIn, iOut );
1298
                return 1;
1299
        }
1300
        return 0;
1301
}
1302
                                                                
1303
/*******************************************************************/
1304
/**
1305
 * Scan every combination of output to input device.
1306
 * If a loopback is found the analyse the combination.
1307
 * The scan can be overriden using the -i and -o command line options.
1308
 */
1309
static int ScanForLoopback(UserOptions *userOptions)
1310
{
1311
        PaDeviceIndex iIn,iOut;
1312
        int  numLoopbacks = 0;
1313
    int  numDevices;
1314
    numDevices = Pa_GetDeviceCount();    
1315
                
1316
        // If both devices are specified then just use that combination.
1317
        if ((userOptions->inputDevice >= 0) && (userOptions->outputDevice >= 0))
1318
        {
1319
                numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, userOptions->outputDevice );
1320
        }
1321
        else if (userOptions->inputDevice >= 0)
1322
        {
1323
                // Just scan for output.
1324
                for( iOut=0; iOut<numDevices; iOut++ )
1325
                {                                        
1326
                        numLoopbacks += CheckLoopbackAndScan( userOptions, userOptions->inputDevice, iOut );
1327
                }
1328
        }
1329
        else if (userOptions->outputDevice >= 0)
1330
        {
1331
                // Just scan for input.
1332
                for( iIn=0; iIn<numDevices; iIn++ )
1333
                {                                        
1334
                        numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, userOptions->outputDevice );
1335
                }
1336
        }
1337
        else
1338
        {        
1339
                // Scan both.
1340
                for( iOut=0; iOut<numDevices; iOut++ )
1341
                {
1342
                        for( iIn=0; iIn<numDevices; iIn++ )
1343
                        {                                
1344
                                numLoopbacks += CheckLoopbackAndScan( userOptions, iIn, iOut );
1345
                        }
1346
                }
1347
        }
1348
        QA_ASSERT_TRUE( "No good loopback cable found.", (numLoopbacks > 0) );
1349
        return numLoopbacks;
1350
        
1351
error:
1352
        return -1;
1353
}
1354

    
1355
/*==========================================================================================*/
1356
int TestSampleFormatConversion( void )
1357
{
1358
        int i;
1359
        const float floatInput[] = { 1.0, 0.5, -0.5, -1.0 };
1360
        
1361
        const char charInput[] = { 127, 64, -64, -128 };
1362
        const unsigned char ucharInput[] = { 255, 128+64, 64, 0 };
1363
        const short shortInput[] = { 32767, 32768/2, -32768/2, -32768 };
1364
        const int intInput[] = { 2147483647, 2147483647/2, -1073741824 /*-2147483648/2 doesn't work in msvc*/, -2147483648 };
1365
        
1366
        float floatOutput[4];
1367
        short shortOutput[4];
1368
        int intOutput[4];        
1369
        unsigned char ucharOutput[4];
1370
        char charOutput[4];
1371
        
1372
        QA_ASSERT_EQUALS("int must be 32-bit", 4, (int) sizeof(int) );
1373
        QA_ASSERT_EQUALS("short must be 16-bit", 2, (int) sizeof(short) );
1374
        
1375
        // from Float ======
1376
        PaQa_ConvertFromFloat( floatInput, 4, paUInt8, ucharOutput );
1377
        for( i=0; i<4; i++ )
1378
        {
1379
                QA_ASSERT_CLOSE_INT( "paFloat32 -> paUInt8 -> error", ucharInput[i], ucharOutput[i], 1 );
1380
        }
1381
        
1382
        PaQa_ConvertFromFloat( floatInput, 4, paInt8, charOutput );
1383
        for( i=0; i<4; i++ )
1384
        {
1385
                QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt8 -> error", charInput[i], charOutput[i], 1 );
1386
        }
1387
        
1388
        PaQa_ConvertFromFloat( floatInput, 4, paInt16, shortOutput );
1389
        for( i=0; i<4; i++ )
1390
        {
1391
                QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt16 error", shortInput[i], shortOutput[i], 1 );
1392
        }
1393
                
1394
        PaQa_ConvertFromFloat( floatInput, 4, paInt32, intOutput );
1395
        for( i=0; i<4; i++ )
1396
        {
1397
                QA_ASSERT_CLOSE_INT( "paFloat32 -> paInt32 error", intInput[i], intOutput[i], 0x00010000 );
1398
        }
1399
        
1400
        
1401
        // to Float ======
1402
        memset( floatOutput, 0, sizeof(floatOutput) );
1403
        PaQa_ConvertToFloat( ucharInput, 4, paUInt8, floatOutput );
1404
        for( i=0; i<4; i++ )
1405
        {
1406
                QA_ASSERT_CLOSE( "paUInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 );
1407
        }
1408
        
1409
        memset( floatOutput, 0, sizeof(floatOutput) );
1410
        PaQa_ConvertToFloat( charInput, 4, paInt8, floatOutput );
1411
        for( i=0; i<4; i++ )
1412
        {
1413
                QA_ASSERT_CLOSE( "paInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 );
1414
        }
1415
        
1416
        memset( floatOutput, 0, sizeof(floatOutput) );
1417
        PaQa_ConvertToFloat( shortInput, 4, paInt16, floatOutput );
1418
        for( i=0; i<4; i++ )
1419
        {
1420
                QA_ASSERT_CLOSE( "paInt16 -> paFloat32 error", floatInput[i], floatOutput[i], 0.001 );
1421
        }
1422
        
1423
        memset( floatOutput, 0, sizeof(floatOutput) );
1424
        PaQa_ConvertToFloat( intInput, 4, paInt32, floatOutput );
1425
        for( i=0; i<4; i++ )
1426
        {
1427
                QA_ASSERT_CLOSE( "paInt32 -> paFloat32 error", floatInput[i], floatOutput[i], 0.00001 );
1428
        }
1429
        
1430
        return 0;
1431
        
1432
error:
1433
        return -1;
1434
}
1435

    
1436

    
1437
/*******************************************************************/
1438
void usage( const char *name )
1439
{
1440
        printf("%s [-i# -o# -l# -r# -s# -m -w -dDir]\n", name);
1441
        printf("  -i# - Input device ID. Will scan for loopback cable if not specified.\n");
1442
        printf("  -o# - Output device ID. Will scan for loopback if not specified.\n");
1443
        printf("  -l# - Latency for both input and output in milliseconds.\n");
1444
        printf("  --inputLatency # Input latency in milliseconds.\n");        
1445
        printf("  --outputLatency # Output latency in milliseconds.\n");
1446
        printf("  -r# - Sample Rate in Hz.  Will use multiple common rates if not specified.\n");
1447
        printf("  -s# - Size of callback buffer in frames, framesPerBuffer. Will use common values if not specified.\n");
1448
        printf("  -w  - Save bad recordings in a WAV file.\n");
1449
        printf("  -dDir - Path for Directory for WAV files. Default is current directory.\n");
1450
        printf("  -m  - Just test the DSP Math code and not the audio devices.\n");
1451
        printf("  -v  - Verbose reports.\n");
1452
}
1453

    
1454
/*******************************************************************/
1455
int main( int argc, char **argv )
1456
{
1457
    int i;
1458
        UserOptions userOptions;
1459
        int result = 0;
1460
        int justMath = 0;
1461
    char *executableName = argv[0];
1462

    
1463
        printf("PortAudio LoopBack Test built " __DATE__ " at " __TIME__ "\n");
1464

    
1465
        if( argc > 1 ){
1466
                printf("running with arguments:");
1467
                for(i=1; i < argc; ++i )
1468
                        printf(" %s", argv[i] );
1469
                printf("\n");
1470
        }else{
1471
                printf("running with no arguments\n");
1472
        }
1473
        
1474
        memset(&userOptions, 0, sizeof(userOptions));
1475
        userOptions.inputDevice = paNoDevice;
1476
        userOptions.outputDevice = paNoDevice;
1477
        userOptions.sampleRate = -1;
1478
        userOptions.framesPerBuffer = -1;
1479
        userOptions.inputLatency = -1;
1480
        userOptions.outputLatency = -1;
1481
        userOptions.waveFilePath = ".";
1482
        
1483
        // Process arguments. Skip name of executable.
1484
        i = 1;
1485
        while( i<argc )
1486
        {
1487
                char *arg = argv[i];
1488
                if( arg[0] == '-' )
1489
                {
1490
                        switch(arg[1])
1491
                        {
1492
                                case 'i':
1493
                                        userOptions.inputDevice = atoi(&arg[2]);
1494
                                        break;
1495
                                case 'o':
1496
                                        userOptions.outputDevice = atoi(&arg[2]);
1497
                                        break;
1498
                                case 'l':
1499
                                        userOptions.inputLatency = userOptions.outputLatency = atoi(&arg[2]);
1500
                                        break;
1501
                                case 'r':
1502
                                        userOptions.sampleRate = atoi(&arg[2]);
1503
                                        break;
1504
                                case 's':
1505
                                        userOptions.framesPerBuffer = atoi(&arg[2]);
1506
                                        break;
1507
                                        
1508
                                case 'm':
1509
                                        printf("Option -m set so just testing math and not the audio devices.\n");
1510
                                        justMath = 1;
1511
                                        break;
1512
                                        
1513
                                case 'w':
1514
                                        userOptions.saveBadWaves = 1;
1515
                                        break;
1516
                                case 'd':
1517
                                        userOptions.waveFilePath = &arg[2];
1518
                                        break;
1519
                                        
1520
                                case 'v':
1521
                                        userOptions.verbose = 1;
1522
                                        break;
1523
                                        
1524
                                case 'h':
1525
                                        usage( executableName );
1526
                                        exit(0);
1527
                                        break;
1528
                                        
1529
                                case '-':
1530
                                {
1531
                                        if( strcmp( &arg[2], "inputLatency" ) == 0 )
1532
                                        {
1533
                                                i += 1;
1534
                                                userOptions.inputLatency = atoi(argv[i]);
1535
                                        }
1536
                                        else if( strcmp( &arg[2], "outputLatency" ) == 0 )
1537
                                        {
1538
                                                i += 1;
1539
                                                userOptions.outputLatency = atoi(argv[i]);                                        
1540
                                        }
1541
                                        else
1542
                                        {
1543
                                                printf("Illegal option: %s\n", arg);
1544
                                                usage( executableName );
1545
                                                exit(1);
1546
                                        }
1547

    
1548
                                }
1549
                                        break;
1550
                                        
1551
                                        
1552
                                default:
1553
                                        printf("Illegal option: %s\n", arg);
1554
                                        usage( executableName );
1555
                                        exit(1);
1556
                                        break;
1557
                        }
1558
                }
1559
                else
1560
                {
1561
                        printf("Illegal argument: %s\n", arg);
1562
                        usage( executableName );
1563
                        exit(1);
1564

    
1565
                }
1566
                i += 1;
1567
        }
1568
                
1569
        result = PaQa_TestAnalyzer();
1570
        
1571
        // Test sample format conversion tool.
1572
        result = TestSampleFormatConversion();
1573
        
1574
        if( (result == 0) && (justMath == 0) )
1575
        {
1576
                Pa_Initialize();
1577
                printf( "PortAudio version number = %d\nPortAudio version text = '%s'\n",
1578
                           Pa_GetVersion(), Pa_GetVersionText() );
1579
                printf( "=============== PortAudio Devices ========================\n" );
1580
                PaQa_ListAudioDevices();
1581
        if( Pa_GetDeviceCount() == 0 )
1582
            printf( "no devices found.\n" );
1583
        
1584
                printf( "=============== Detect Loopback ==========================\n" );
1585
                ScanForLoopback(&userOptions);
1586
 
1587
                Pa_Terminate();
1588
        }
1589

    
1590
        if (g_testsFailed == 0)
1591
        {
1592
                printf("PortAudio QA SUCCEEDED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed );
1593
                return 0;
1594

    
1595
        }
1596
        else
1597
        {
1598
                printf("PortAudio QA FAILED! %d tests passed, %d tests failed\n", g_testsPassed, g_testsFailed );
1599
                return 1;
1600
        }        
1601
}