1 /** @file patest_converters.c
2 	@ingroup test_src
3 	@brief Tests the converter functions in pa_converters.c
4 	@author Ross Bencina <rossb@audiomulch.com>
5 
6     Link with pa_dither.c and pa_converters.c
7 
8     see http://www.portaudio.com/trac/wiki/V19ConvertersStatus for a discussion of this.
9 */
10 /*
11  * $Id: $
12  *
13  * This program uses the PortAudio Portable Audio Library.
14  * For more information see: http://www.portaudio.com/
15  * Copyright (c) 1999-2008 Ross Bencina and Phil Burk
16  *
17  * Permission is hereby granted, free of charge, to any person obtaining
18  * a copy of this software and associated documentation files
19  * (the "Software"), to deal in the Software without restriction,
20  * including without limitation the rights to use, copy, modify, merge,
21  * publish, distribute, sublicense, and/or sell copies of the Software,
22  * and to permit persons to whom the Software is furnished to do so,
23  * subject to the following conditions:
24  *
25  * The above copyright notice and this permission notice shall be
26  * included in all copies or substantial portions of the Software.
27  *
28  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
29  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
30  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
31  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
32  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
33  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
34  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35  */
36 
37 /*
38  * The text above constitutes the entire PortAudio license; however,
39  * the PortAudio community also makes the following non-binding requests:
40  *
41  * Any person wishing to distribute modifications to the Software is
42  * requested to send the modifications to the original developer so that
43  * they can be incorporated into the canonical version. It is also
44  * requested that these non-binding requests be included along with the
45  * license above.
46  */
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <math.h>
51 
52 #include "portaudio.h"
53 #include "pa_converters.h"
54 #include "pa_dither.h"
55 #include "pa_types.h"
56 #include "pa_endianness.h"
57 
58 #ifndef M_PI
59 #define M_PI  (3.14159265)
60 #endif
61 
62 #define MAX_PER_CHANNEL_FRAME_COUNT     (2048)
63 #define MAX_CHANNEL_COUNT               (8)
64 
65 
66 #define SAMPLE_FORMAT_COUNT (6)
67 
68 static PaSampleFormat sampleFormats_[ SAMPLE_FORMAT_COUNT ] =
69     { paFloat32, paInt32, paInt24, paInt16, paInt8, paUInt8 }; /* all standard PA sample formats */
70 
71 static const char* sampleFormatNames_[SAMPLE_FORMAT_COUNT] =
72     { "paFloat32", "paInt32", "paInt24", "paInt16", "paInt8", "paUInt8" };
73 
74 
75 static const char* abbreviatedSampleFormatNames_[SAMPLE_FORMAT_COUNT] =
76     { "f32", "i32", "i24", "i16", " i8", "ui8" };
77 
78 
79 PaError My_Pa_GetSampleSize( PaSampleFormat format );
80 
81 /*
82     available flags are paClipOff and paDitherOff
83     clipping is usually applied for float -> int conversions
84     dither is usually applied for all downconversions (ie anything but 8bit->8bit conversions
85 */
86 
CanClip(PaSampleFormat sourceFormat,PaSampleFormat destinationFormat)87 static int CanClip( PaSampleFormat sourceFormat, PaSampleFormat destinationFormat )
88 {
89     if( sourceFormat == paFloat32 && destinationFormat != sourceFormat )
90         return 1;
91     else
92         return 0;
93 }
94 
CanDither(PaSampleFormat sourceFormat,PaSampleFormat destinationFormat)95 static int CanDither( PaSampleFormat sourceFormat, PaSampleFormat destinationFormat )
96 {
97     if( sourceFormat < destinationFormat && sourceFormat != paInt8 )
98         return 1;
99     else
100         return 0;
101 }
102 
GenerateOneCycleSineReference(double * out,int frameCount,int strideFrames)103 static void GenerateOneCycleSineReference( double *out, int frameCount, int strideFrames )
104 {
105     int i;
106     for( i=0; i < frameCount; ++i ){
107         *out = sin( ((double)i/(double)frameCount) * 2. * M_PI );
108         out += strideFrames;
109     }
110 }
111 
112 
GenerateOneCycleSine(PaSampleFormat format,void * buffer,int frameCount,int strideFrames)113 static void GenerateOneCycleSine( PaSampleFormat format, void *buffer, int frameCount, int strideFrames )
114 {
115     switch( format ){
116 
117         case paFloat32:
118             {
119                 int i;
120                 float *out = (float*)buffer;
121                 for( i=0; i < frameCount; ++i ){
122                     *out = (float).9 * sin( ((double)i/(double)frameCount) * 2. * M_PI );
123                     out += strideFrames;
124                 }
125             }
126             break;
127         case paInt32:
128             {
129                 int i;
130                 PaInt32 *out = (PaInt32*)buffer;
131                 for( i=0; i < frameCount; ++i ){
132                     *out = (PaInt32)(.9 * sin( ((double)i/(double)frameCount) * 2. * M_PI ) * 0x7FFFFFFF);
133                     out += strideFrames;
134                 }
135             }
136             break;
137         case paInt24:
138             {
139                 int i;
140                 unsigned char *out = (unsigned char*)buffer;
141                 for( i=0; i < frameCount; ++i ){
142                     signed long temp = (PaInt32)(.9 * sin( ((double)i/(double)frameCount) * 2. * M_PI ) * 0x7FFFFFFF);
143 
144                     #if defined(PA_LITTLE_ENDIAN)
145                             out[0] = (unsigned char)(temp >> 8) & 0xFF;
146                             out[1] = (unsigned char)(temp >> 16) & 0xFF;
147                             out[2] = (unsigned char)(temp >> 24) & 0xFF;
148                     #elif defined(PA_BIG_ENDIAN)
149                             out[0] = (unsigned char)(temp >> 24) & 0xFF;
150                             out[1] = (unsigned char)(temp >> 16) & 0xFF;
151                             out[2] = (unsigned char)(temp >> 8) & 0xFF;
152                     #endif
153                     out += 3;
154                 }
155             }
156             break;
157         case paInt16:
158             {
159                 int i;
160                 PaInt16 *out = (PaInt16*)buffer;
161                 for( i=0; i < frameCount; ++i ){
162                     *out = (PaInt16)(.9 * sin( ((double)i/(double)frameCount) * 2. * M_PI ) * 0x7FFF );
163                     out += strideFrames;
164                 }
165             }
166             break;
167         case paInt8:
168             {
169                 int i;
170                 signed char *out = (signed char*)buffer;
171                 for( i=0; i < frameCount; ++i ){
172                     *out = (signed char)(.9 * sin( ((double)i/(double)frameCount) * 2. * M_PI ) * 0x7F );
173                     out += strideFrames;
174                 }
175             }
176             break;
177         case paUInt8:
178             {
179                 int i;
180                 unsigned char *out = (unsigned char*)buffer;
181                 for( i=0; i < frameCount; ++i ){
182                     *out = (unsigned char)( .5 * (1. + (.9 * sin( ((double)i/(double)frameCount) * 2. * M_PI ))) * 0xFF  );
183                     out += strideFrames;
184                 }
185             }
186             break;
187     }
188 }
189 
TestNonZeroPresent(void * buffer,int size)190 int TestNonZeroPresent( void *buffer, int size )
191 {
192     char *p = (char*)buffer;
193     int i;
194 
195     for( i=0; i < size; ++i ){
196 
197         if( *p != 0 )
198             return 1;
199         ++p;
200     }
201 
202     return 0;
203 }
204 
MaximumAbsDifference(float * sourceBuffer,float * referenceBuffer,int count)205 float MaximumAbsDifference( float* sourceBuffer, float* referenceBuffer, int count )
206 {
207     float result = 0;
208     float difference;
209     while( count-- ){
210         difference = fabs( *sourceBuffer++ - *referenceBuffer++ );
211         if( difference > result )
212             result = difference;
213     }
214 
215     return result;
216 }
217 
main(const char ** argv,int argc)218 int main( const char **argv, int argc )
219 {
220     PaUtilTriangularDitherGenerator ditherState;
221     PaUtilConverter *converter;
222     void *destinationBuffer, *sourceBuffer;
223     double *referenceBuffer;
224     int sourceFormatIndex, destinationFormatIndex;
225     PaSampleFormat sourceFormat, destinationFormat;
226     PaStreamFlags flags;
227     int passFailMatrix[SAMPLE_FORMAT_COUNT][SAMPLE_FORMAT_COUNT]; // [source][destination]
228     float noiseAmplitudeMatrix[SAMPLE_FORMAT_COUNT][SAMPLE_FORMAT_COUNT]; // [source][destination]
229     float amp;
230 
231 #define FLAG_COMBINATION_COUNT (4)
232     PaStreamFlags flagCombinations[FLAG_COMBINATION_COUNT] = { paNoFlag, paClipOff, paDitherOff, paClipOff | paDitherOff };
233     const char *flagCombinationNames[FLAG_COMBINATION_COUNT] = { "paNoFlag", "paClipOff", "paDitherOff", "paClipOff | paDitherOff" };
234     int flagCombinationIndex;
235 
236     PaUtil_InitializeTriangularDitherState( &ditherState );
237 
238     /* allocate more than enough space, we use sizeof(float) but we need to fit any 32 bit datum */
239 
240     destinationBuffer = (void*)malloc( MAX_PER_CHANNEL_FRAME_COUNT * MAX_CHANNEL_COUNT * sizeof(float) );
241     sourceBuffer = (void*)malloc( MAX_PER_CHANNEL_FRAME_COUNT * MAX_CHANNEL_COUNT * sizeof(float) );
242     referenceBuffer = (void*)malloc( MAX_PER_CHANNEL_FRAME_COUNT * MAX_CHANNEL_COUNT * sizeof(float) );
243 
244 
245     /* the first round of tests simply iterates through the buffer combinations testing
246         that putting something in gives something out */
247 
248     printf( "= Sine wave in, something out =\n" );
249 
250     printf( "\n" );
251 
252     GenerateOneCycleSine( paFloat32, referenceBuffer, MAX_PER_CHANNEL_FRAME_COUNT, 1 );
253 
254     for( flagCombinationIndex = 0; flagCombinationIndex < FLAG_COMBINATION_COUNT; ++flagCombinationIndex ){
255         flags = flagCombinations[flagCombinationIndex];
256 
257         printf( "\n" );
258         printf( "== flags = %s ==\n", flagCombinationNames[flagCombinationIndex] );
259 
260         for( sourceFormatIndex = 0; sourceFormatIndex < SAMPLE_FORMAT_COUNT; ++sourceFormatIndex ){
261             for( destinationFormatIndex = 0; destinationFormatIndex < SAMPLE_FORMAT_COUNT; ++destinationFormatIndex ){
262                 sourceFormat = sampleFormats_[sourceFormatIndex];
263                 destinationFormat = sampleFormats_[destinationFormatIndex];
264                 //printf( "%s -> %s ", sampleFormatNames_[ sourceFormatIndex ], sampleFormatNames_[ destinationFormatIndex ] );
265 
266                 converter = PaUtil_SelectConverter( sourceFormat, destinationFormat, flags );
267 
268                 /* source is a sinewave */
269                 GenerateOneCycleSine( sourceFormat, sourceBuffer, MAX_PER_CHANNEL_FRAME_COUNT, 1 );
270 
271                 /* zero destination */
272                 memset( destinationBuffer, 0, MAX_PER_CHANNEL_FRAME_COUNT * My_Pa_GetSampleSize( destinationFormat ) );
273 
274                 (*converter)( destinationBuffer, 1, sourceBuffer, 1, MAX_PER_CHANNEL_FRAME_COUNT, &ditherState );
275 
276     /*
277     Other ways we could test this would be:
278         - pass a constant, check for a constant (wouldn't work with dither)
279         - pass alternating +/-, check for the same...
280     */
281                 if( TestNonZeroPresent( destinationBuffer, MAX_PER_CHANNEL_FRAME_COUNT * My_Pa_GetSampleSize( destinationFormat ) ) ){
282                     //printf( "PASSED\n" );
283                     passFailMatrix[sourceFormatIndex][destinationFormatIndex] = 1;
284                 }else{
285                     //printf( "FAILED\n" );
286                     passFailMatrix[sourceFormatIndex][destinationFormatIndex] = 0;
287                 }
288 
289 
290                 /* try to measure the noise floor (comparing output signal to a float32 sine wave) */
291 
292                 if( passFailMatrix[sourceFormatIndex][destinationFormatIndex] ){
293 
294                     /* convert destination back to paFloat32 into source */
295                     converter = PaUtil_SelectConverter( destinationFormat, paFloat32, paNoFlag );
296 
297                     memset( sourceBuffer, 0, MAX_PER_CHANNEL_FRAME_COUNT * My_Pa_GetSampleSize( paFloat32 ) );
298                     (*converter)( sourceBuffer, 1, destinationBuffer, 1, MAX_PER_CHANNEL_FRAME_COUNT, &ditherState );
299 
300                     if( TestNonZeroPresent( sourceBuffer, MAX_PER_CHANNEL_FRAME_COUNT * My_Pa_GetSampleSize( paFloat32 ) ) ){
301 
302                         noiseAmplitudeMatrix[sourceFormatIndex][destinationFormatIndex] = MaximumAbsDifference( (float*)sourceBuffer, (float*)referenceBuffer, MAX_PER_CHANNEL_FRAME_COUNT );
303 
304                     }else{
305                         /* can't test noise floor because there is no conversion from dest format to float available */
306                         noiseAmplitudeMatrix[sourceFormatIndex][destinationFormatIndex] = -1; // mark as failed
307                     }
308                 }else{
309                     noiseAmplitudeMatrix[sourceFormatIndex][destinationFormatIndex] = -1; // mark as failed
310                 }
311             }
312         }
313 
314         printf( "\n" );
315         printf( "=== Output contains non-zero data ===\n" );
316         printf( "Key: . - pass, X - fail\n" );
317         printf( "{{{\n" ); // trac preformated text tag
318         printf( "in|  out:    " );
319         for( destinationFormatIndex = 0; destinationFormatIndex < SAMPLE_FORMAT_COUNT; ++destinationFormatIndex ){
320             printf( "  %s   ", abbreviatedSampleFormatNames_[destinationFormatIndex] );
321         }
322         printf( "\n" );
323 
324         for( sourceFormatIndex = 0; sourceFormatIndex < SAMPLE_FORMAT_COUNT; ++sourceFormatIndex ){
325             printf( "%s         ", abbreviatedSampleFormatNames_[sourceFormatIndex] );
326             for( destinationFormatIndex = 0; destinationFormatIndex < SAMPLE_FORMAT_COUNT; ++destinationFormatIndex ){
327                 printf( "   %s   ", (passFailMatrix[sourceFormatIndex][destinationFormatIndex])? " ." : " X" );
328             }
329             printf( "\n" );
330         }
331         printf( "}}}\n" ); // trac preformated text tag
332 
333         printf( "\n" );
334         printf( "=== Combined dynamic range (src->dest->float32) ===\n" );
335         printf( "Key: Noise amplitude in dBfs, X - fail (either above failed or dest->float32 failed)\n" );
336         printf( "{{{\n" ); // trac preformated text tag
337         printf( "in|  out:    " );
338         for( destinationFormatIndex = 0; destinationFormatIndex < SAMPLE_FORMAT_COUNT; ++destinationFormatIndex ){
339             printf( "  %s   ", abbreviatedSampleFormatNames_[destinationFormatIndex] );
340         }
341         printf( "\n" );
342 
343         for( sourceFormatIndex = 0; sourceFormatIndex < SAMPLE_FORMAT_COUNT; ++sourceFormatIndex ){
344             printf( " %s       ", abbreviatedSampleFormatNames_[sourceFormatIndex] );
345             for( destinationFormatIndex = 0; destinationFormatIndex < SAMPLE_FORMAT_COUNT; ++destinationFormatIndex ){
346                 amp = noiseAmplitudeMatrix[sourceFormatIndex][destinationFormatIndex];
347                 if( amp < 0. )
348                     printf( "    X   " );
349                 else
350                     printf( " % 6.1f ", 20.*log10(amp) );
351             }
352             printf( "\n" );
353         }
354         printf( "}}}\n" ); // trac preformated text tag
355     }
356 
357 
358     free( destinationBuffer );
359     free( sourceBuffer );
360     free( referenceBuffer );
361 }
362 
363 // copied here for now otherwise we need to include the world just for this function.
My_Pa_GetSampleSize(PaSampleFormat format)364 PaError My_Pa_GetSampleSize( PaSampleFormat format )
365 {
366     int result;
367 
368     switch( format & ~paNonInterleaved )
369     {
370 
371     case paUInt8:
372     case paInt8:
373         result = 1;
374         break;
375 
376     case paInt16:
377         result = 2;
378         break;
379 
380     case paInt24:
381         result = 3;
382         break;
383 
384     case paFloat32:
385     case paInt32:
386         result = 4;
387         break;
388 
389     default:
390         result = paSampleFormatNotSupported;
391         break;
392     }
393 
394     return (PaError) result;
395 }
396