]> Repos - portaudio/commitdiff
Add test for sample format conversion.
authorphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Wed, 2 Mar 2011 01:53:02 +0000 (01:53 +0000)
committerphilburk <philburk@0f58301d-fd10-0410-b4af-bbb618454e57>
Wed, 2 Mar 2011 01:53:02 +0000 (01:53 +0000)
qa/loopback/src/audio_analyzer.h
qa/loopback/src/paqa.c
qa/loopback/src/paqa_tools.c
qa/loopback/src/paqa_tools.h
qa/loopback/src/test_audio_analyzer.c

index 78911083c4a9c82fa4173ac601c2a24ec484f3cc..43271db84f231f1fed93b4c55ce3a508d3c31006 100644 (file)
@@ -75,6 +75,7 @@ typedef struct PaQaTestTone_s
 typedef struct PaQaAnalysisResult_s
 {
        int       valid;
+       /** Latency in samples from output to input. */
        double    latency;
        double    amplitudeRatio;
        double    popAmplitude;
@@ -125,7 +126,6 @@ double PaQa_MeasureCrossingSlope( float *buffer, int numFrames );
  */
 void PaQa_SetupSineGenerator( PaQaSineGenerator *generator, double frequency, double amplitude, double frameRate );
 
-
 /*================================================================*/
 /*================= Recordings ===================================*/
 /*================================================================*/
index 45ba1e578fb975d11a0b55d849a9efe02991283d..8c09b8d58f7fbc02d29c3e96eaf40ee91d1a2056 100644 (file)
@@ -96,9 +96,15 @@ typedef struct LoopbackContext_s
        PaQaSineGenerator  generators[MAX_NUM_GENERATORS];
        // Record each channel individually.
        PaQaRecording      recordings[MAX_NUM_RECORDINGS];
+       // Measured at runtime.
        int                callbackCount;
-       
+       int                inputUnderflowCount;
+       int                outputUnderflowCount;
+       int                inputOverflowCount;
+       int                outputOverflowCount;
+       int                primingCount;
        TestParameters    *test;
+       
 } LoopbackContext;
 
 typedef struct UserOptions_s
@@ -115,7 +121,8 @@ typedef struct UserOptions_s
 } UserOptions;
 
 #define BIG_BUFFER_SIZE  (sizeof(float) * 2 * 2048)
-static unsigned char g_BigBuffer[BIG_BUFFER_SIZE];
+static unsigned char g_ReadWriteBuffer[BIG_BUFFER_SIZE];
+static unsigned char g_ConversionBuffer[BIG_BUFFER_SIZE];
 
 /*******************************************************************/
 static int RecordAndPlaySinesCallback( const void *inputBuffer, void *outputBuffer,
@@ -125,40 +132,67 @@ static int RecordAndPlaySinesCallback( const void *inputBuffer, void *outputBuff
                                                void *userData )
 {
        int i;
-    float *in = (float *)inputBuffer;
-    float *out = (float *)outputBuffer;
     int done = paContinue;
+       LoopbackContext *loopbackContext = (LoopbackContext *) userData;
        
-       LoopbackContext *loopbackContext = (LoopbackContext *) userData;        
+       int channelsPerFrame = loopbackContext->test->inputParameters.channelCount;
+               
        loopbackContext->callbackCount += 1;
-       
+       if( statusFlags & paInputUnderflow ) loopbackContext->inputUnderflowCount += 1;
+       if( statusFlags & paInputOverflow ) loopbackContext->inputOverflowCount += 1;
+       if( statusFlags & paOutputUnderflow ) loopbackContext->outputUnderflowCount += 1;
+       if( statusFlags & paOutputOverflow ) loopbackContext->outputOverflowCount += 1;
+       if( statusFlags & paPrimingOutput ) loopbackContext->primingCount += 1;
+
     /* This may get called with NULL inputBuffer during initial setup.
         * We may also use the same callback with output only streams.
         */
-    if( in != NULL)
+    if( inputBuffer != NULL)
        {
-               // Read each channel from the buffer.
-               for( i=0; i<loopbackContext->test->inputParameters.channelCount; i++ )
+               float *in = (float *)inputBuffer;
+               
+               PaSampleFormat inFormat = loopbackContext->test->inputParameters.sampleFormat;
+               if( inFormat != paFloat32 )
                {
+                       in = (float *) g_ConversionBuffer;
+                       PaQa_ConvertToFloat( inputBuffer, framesPerBuffer * channelsPerFrame, inFormat, (float *) g_ConversionBuffer );
+               }
+               
+               // Read each channel from the buffer.
+               for( i=0; i<channelsPerFrame; i++ )
+               {                       
                        done |= PaQa_WriteRecording( &loopbackContext->recordings[i],
                                                                                in + i, 
                                                                                framesPerBuffer,
-                                                                               loopbackContext->test->inputParameters.channelCount );
+                                                                               channelsPerFrame );
                }
        }
        
-       if( out != NULL )
+       if( outputBuffer != NULL )
        {
-               PaQa_EraseBuffer( out, framesPerBuffer, loopbackContext->test->outputParameters.channelCount );
-               
+               float *out = (float *)outputBuffer;
                
-               for( i=0; i<loopbackContext->test->outputParameters.channelCount; i++ )
+               PaSampleFormat outFormat = loopbackContext->test->outputParameters.sampleFormat;
+               if( outFormat != paFloat32 )
+               {
+                       // If we need to convert then mix to the g_ConversionBuffer and then convert into the PA outputBuffer.
+                       out = (float *) g_ConversionBuffer;
+               }
+                       
+               PaQa_EraseBuffer( out, framesPerBuffer, loopbackContext->test->outputParameters.channelCount );
+               for( i=0; i<channelsPerFrame; i++ )
                {
                        PaQa_MixSine( &loopbackContext->generators[i],
                                                 out + i,
                                                 framesPerBuffer,
-                                                loopbackContext->test->outputParameters.channelCount );
+                                                channelsPerFrame );
                }
+               
+               if( outFormat != paFloat32 )
+               {
+                       PaQa_ConvertFromFloat( out, framesPerBuffer * channelsPerFrame, outFormat, outputBuffer );
+               }
+               
        }
     return done ? paComplete : paContinue;
 }
@@ -330,8 +364,8 @@ static int RecordAndPlayBlockingIO( PaStream *inStream,
                                                                          )
 {      
        int i;
-       float *in = (float *)g_BigBuffer;
-       float *out = (float *)g_BigBuffer;
+       float *in = (float *)g_ReadWriteBuffer;
+       float *out = (float *)g_ReadWriteBuffer;
        PaError err;
        int done = 0;
        long available;
@@ -351,6 +385,11 @@ static int RecordAndPlayBlockingIO( PaStream *inStream,
        {
                QA_ASSERT_EQUALS( "Pa_ReadStream failed", paNoError, err );
        }
+       else
+       {
+               loopbackContext->inputOverflowCount += 1;
+       }
+
        
        // Save in a recording.
        for( i=0; i<loopbackContext->test->inputParameters.channelCount; i++ )
@@ -381,6 +420,11 @@ static int RecordAndPlayBlockingIO( PaStream *inStream,
        {
                QA_ASSERT_EQUALS( "Pa_WriteStream failed", paNoError, err );
        }
+       else
+       {
+               loopbackContext->outputUnderflowCount += 1;
+       }
+       
                
        loopbackContext->callbackCount += 1;
        
@@ -664,7 +708,7 @@ static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *te
        err = PaQa_RunLoopback( &loopbackContext );
        QA_ASSERT_TRUE("loopback did not run", (loopbackContext.callbackCount > 1) );
        
-       // Analyse recording to to detect glitches.
+       // Analyse recording to detect glitches.
        for( i=0; i<testParams->samplesPerFrame; i++ )
        {
                double freq = PaQa_GetNthFrequency( testParams->baseFrequency, i );
@@ -674,7 +718,8 @@ static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *te
                
                if( i==0 )
                {
-                       printf("%7.1f | ", analysisResult.latency );
+                       double latencyMSec = 1000.0 * analysisResult.latency / testParams->sampleRate;
+                       printf("%7.2f | ", latencyMSec );
                }
                
                if( analysisResult.valid )
@@ -708,6 +753,13 @@ static int PaQa_SingleLoopBackTest( UserOptions *userOptions, TestParameters *te
        {
                printf( "OK" );
        }
+       
+       printf( ", %d/%d/%d/%d, ",
+                  loopbackContext.inputOverflowCount,
+                  loopbackContext.inputUnderflowCount,
+                  loopbackContext.outputOverflowCount,
+                  loopbackContext.outputUnderflowCount );
+       
        printf( "\n" );
        
                        
@@ -757,6 +809,7 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
        int iFlags;
        int iRate;
        int iSize;
+       int iFormat;
        int totalBadChannels = 0;
        TestParameters testParams;
     const   PaDeviceInfo *inputDeviceInfo;     
@@ -764,27 +817,37 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
        inputDeviceInfo = Pa_GetDeviceInfo( inputDevice );
        outputDeviceInfo = Pa_GetDeviceInfo( outputDevice );
        
+       double bestSampleRate = 44100.0;
+       int bestBufferSize = 32;
+       
+       
        printf( "=============== Analysing Loopback %d to %d ====================\n", outputDevice, inputDevice  );
        printf( "    Devices: %s => %s\n", outputDeviceInfo->name, inputDeviceInfo->name);
        
-       int flagSettings[] = { 0, 1 };
+       // test half duplex first because it is more likely to work.
+       int flagSettings[] = { PAQA_FLAG_TWO_STREAMS, 0 };
        int numFlagSettings = (sizeof(flagSettings)/sizeof(int));
        
-       double sampleRates[] = { 44100.0, 48000.0, 8000.0, 11025.0, 16000.0, 22050.0, 32000.0, 96000.0 };
+       double sampleRates[] = { 8000.0, 11025.0, 16000.0, 22050.0, 32000.0, 44100.0, 48000.0, 96000.0 };
        int numRates = (sizeof(sampleRates)/sizeof(double));
        
-       int framesPerBuffers[] = { 256, 16, 32, 40, 64, 100, 128, 512, 1024 };
+       int framesPerBuffers[] = { 16, 32, 40, 64, 100, 128, 256, 512, 1024 };
        int numBufferSizes = (sizeof(framesPerBuffers)/sizeof(int));
-               
+       
+       PaSampleFormat sampleFormats[] = { paUInt8, paInt16, paInt32 };
+       int numSampleFormats = (sizeof(sampleFormats)/sizeof(PaSampleFormat));
+       
        // Check to see if a specific value was requested.
        if( userOptions->sampleRate > 0 )
        {
                sampleRates[0] = userOptions->sampleRate;
+               bestSampleRate = userOptions->sampleRate;
                numRates = 1;
        }
        if( userOptions->framesPerBuffer > 0 )
        {
                framesPerBuffers[0] = userOptions->framesPerBuffer;
+               bestBufferSize = userOptions->framesPerBuffer;
                numBufferSizes = 1;
        }
        
@@ -796,11 +859,11 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
        {
                testParams.flags = flagSettings[iFlags];
                printf( "************ Mode = %s ************\n",
-                          (( iFlags & 1 ) ? s_FlagOnNames[0] : s_FlagOffNames[0]) );
+                          (( testParams.flags & 1 ) ? s_FlagOnNames[0] : s_FlagOffNames[0]) );
                printf("|-sRate-|-buffer-|-latency-|-channel results--------------------|\n");
 
                // Loop though combinations of audio parameters.
-               testParams.framesPerBuffer = framesPerBuffers[0];
+               testParams.framesPerBuffer = bestBufferSize;
                for( iRate=0; iRate<numRates; iRate++ )
                {
                        // SAMPLE RATE
@@ -812,7 +875,7 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
                }
                printf( "\n" );
                
-               testParams.sampleRate = sampleRates[0];
+               testParams.sampleRate = bestSampleRate;
                testParams.maxFrames = (int) (1.2 * testParams.sampleRate);
                for( iSize=0; iSize<numBufferSizes; iSize++ )
                {       
@@ -822,10 +885,25 @@ static int PaQa_AnalyzeLoopbackConnection( UserOptions *userOptions, PaDeviceInd
                        int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
                        totalBadChannels += numBadChannels;                     
                }
-               printf( "\n" );
-               
-               
+               printf( "\n" );         
+       }
+       
+       printf("Test Sample Formats using Half Duplex IO -----\n" );
+       testParams.flags = PAQA_FLAG_TWO_STREAMS;
+       testParams.sampleRate = bestSampleRate;
+       testParams.framesPerBuffer = bestBufferSize;
+       
+       for( iFormat=0; iFormat<numSampleFormats; iFormat++ )
+       {       
+               PaSampleFormat format = sampleFormats[ iFormat ];
+               testParams.inputParameters.sampleFormat = format;
+               testParams.outputParameters.sampleFormat = format;
+               printf("Sample format = %d\n", (int) format );
+               int numBadChannels = PaQa_SingleLoopBackTest( userOptions, &testParams );
+               totalBadChannels += numBadChannels;                     
        }
+       printf( "\n" );
+       
        return totalBadChannels;
 }
 
@@ -1062,6 +1140,73 @@ error:
        return -1;
 }
 
+/*==========================================================================================*/
+int TestSampleFormatConversion( void )
+{
+       int i;
+       const float floatInput[] = { 1.0, 0.5, -0.5, -1.0 };
+
+       const unsigned char ucharInput[] = { 255, 128+64, 64, 0 };
+       const short shortInput[] = { 32767, 32768/2, -32768/2, -32768 };
+       const int intInput[] = { 2147483647, 2147483647/2, -2147483648/2, -2147483648 };
+       
+       float floatOutput[4];
+       short shortOutput[4];
+       int intOutput[4];       
+       unsigned char ucharOutput[4];
+       
+       QA_ASSERT_EQUALS("int must be 32-bit", 4, (int) sizeof(int) );
+       QA_ASSERT_EQUALS("short must be 16-bit", 2, (int) sizeof(short) );
+       
+       // from Float ======
+       PaQa_ConvertFromFloat( floatInput, 4, paUInt8, ucharOutput );
+       for( i=0; i<4; i++ )
+       {
+               QA_ASSERT_CLOSE( "paFloat32 -> paUint8 -> error", ucharInput[i], ucharOutput[i], 1 );
+       }
+       
+       PaQa_ConvertFromFloat( floatInput, 4, paInt16, shortOutput );
+       for( i=0; i<4; i++ )
+       {
+               QA_ASSERT_CLOSE( "paFloat32 -> paInt16 error", shortInput[i], shortOutput[i], 1 );
+       }
+               
+       PaQa_ConvertFromFloat( floatInput, 4, paInt32, intOutput );
+       for( i=0; i<4; i++ )
+       {
+               QA_ASSERT_CLOSE( "paFloat32 -> paInt32 error", intInput[i], intOutput[i], 0x00010000 );
+       }
+       
+       
+       // to Float ======
+       memset( floatOutput, 0, sizeof(floatOutput) );
+       PaQa_ConvertToFloat( ucharInput, 4, paUInt8, floatOutput );
+       for( i=0; i<4; i++ )
+       {
+               QA_ASSERT_CLOSE( "paUInt8 -> paFloat32 error", floatInput[i], floatOutput[i], 0.01 );
+       }
+       
+       memset( floatOutput, 0, sizeof(floatOutput) );
+       PaQa_ConvertToFloat( shortInput, 4, paInt16, floatOutput );
+       for( i=0; i<4; i++ )
+       {
+               QA_ASSERT_CLOSE( "paInt16 -> paFloat32 error", floatInput[i], floatOutput[i], 0.001 );
+       }
+       
+       memset( floatOutput, 0, sizeof(floatOutput) );
+       PaQa_ConvertToFloat( intInput, 4, paInt32, floatOutput );
+       for( i=0; i<4; i++ )
+       {
+               QA_ASSERT_CLOSE( "paInt32 -> paFloat32 error", floatInput[i], floatOutput[i], 0.00001 );
+       }
+       
+       return 0;
+       
+error:
+       return -1;
+}
+
+
 /*******************************************************************/
 void usage( const char *name )
 {
@@ -1151,6 +1296,9 @@ int main( int argc, char **argv )
                
        result = PaQa_TestAnalyzer();
        
+       // Test sample format conversion tool.
+       result = TestSampleFormatConversion();
+       
        if( (result == 0) && (justMath == 0) )
        {
                Pa_Initialize();
index fc989341ef71f05e7ccd7f6a94b4a625e84926f9..2dc80a5997717321cad6cecd2189516c13abd5af 100644 (file)
@@ -38,6 +38,7 @@
 
 #include "paqa_tools.h"
 
+
 /*******************************************************************/
 void PaQa_ListAudioDevices(void)
 {
@@ -54,3 +55,95 @@ void PaQa_ListAudioDevices(void)
         printf( ", on %s\n",  Pa_GetHostApiInfo( deviceInfo->hostApi )->name );
        }
 }
+
+/*******************************************************************/
+void PaQa_ConvertToFloat( const void *input, int numSamples, PaSampleFormat inFormat, float *output )
+{
+       int i;
+       switch( inFormat )
+       {
+               case paUInt8:
+               {
+                       unsigned char *data = (unsigned char *)input;
+                       for( i=0; i<numSamples; i++ )
+                       {
+                               int value = *data++;
+                               value -= 128;
+                               *output++ = value / 128.0f;
+                       }
+               }
+                       break;
+                       
+               case paInt16:
+               {
+                       short *data = (short *)input;
+                       for( i=0; i<numSamples; i++ )
+                       {
+                               *output++ = *data++ / 32768.0f;
+                       }
+               }
+                       break;
+                       
+               case paInt32:
+               {
+                       int *data = (int *)input;
+                       for( i=0; i<numSamples; i++ )
+                       {
+                               int value = *data++;
+                               value = value >> 8;
+                               float fval = (float) (value / ((double) 0x00800000));
+                               *output++ = fval;
+                       }
+               }
+                       break;
+       }
+       
+}
+
+/*******************************************************************/
+void PaQa_ConvertFromFloat( const float *input, int numSamples, PaSampleFormat outFormat, void *output )
+{
+       int i;
+       switch( outFormat )
+       {
+               case paUInt8:
+               {
+                       unsigned char *data = (unsigned char *)output;
+                       for( i=0; i<numSamples; i++ )
+                       {
+                               float value = *input++;
+                               int byte = ((int) (value * 127)) + 128;
+                               *data++ = (unsigned char) byte;
+                       }
+               }
+                       break;
+                       
+               case paInt16:
+               {
+                       short *data = (short *)output;
+                       for( i=0; i<numSamples; i++ )
+                       {
+                               float value = *input++;
+                               // Use assymmetric conversion to avoid clipping.
+                               short sval = value * 32767.0;
+                               *data++ = sval;
+                       }
+               }
+                       break;
+                       
+               case paInt32:
+               {
+                       int *data = (int *)output;
+                       for( i=0; i<numSamples; i++ )
+                       {
+                               float value = *input++;
+                               // Use assymmetric conversion to avoid clipping.
+                               int ival = value * ((double) 0x007FFFF0);
+                               ival = ival << 8;
+                               *data++ = ival;
+                       }
+               }
+                       break;
+       }
+       
+}
index 9990597cf72aec3b0565e206034ad41cbef9afb5..f6b807f2cffeb37244f53794113649573ab61a86 100644 (file)
@@ -45,4 +45,8 @@
 
 void PaQa_ListAudioDevices(void);
 
+void PaQa_ConvertToFloat( const void *input, int numSamples, PaSampleFormat inFormat, float *output );
+
+void PaQa_ConvertFromFloat( const float *input, int numSamples, PaSampleFormat outFormat, void *output );
+
 #endif /* _PAQA_TOOLS_H */
index 147f1a3329ef59f5da8e8a0390a854e6d56cd3b3..5ba01f745975c2a79c62c1f51a4b1bc6f3a5fc25 100644 (file)
@@ -555,7 +555,6 @@ error:
        
 }
 
-
 /*==========================================================================================*/
 /**
  */