blob: 7fe3478e3f5351e22dde3c06a1fbd1eb3664205d [file] [log] [blame]
Emeric Vigiereebea672012-08-06 17:36:30 -04001/*
2** Copyright (C) 2002-2011 Erik de Castro Lopo <erikd@mega-nerd.com>
3**
4** This program is free software; you can redistribute it and/or modify
5** it under the terms of the GNU General Public License as published by
6** the Free Software Foundation; either version 2 of the License, or
7** (at your option) any later version.
8**
9** This program is distributed in the hope that it will be useful,
10** but WITHOUT ANY WARRANTY; without even the implied warranty of
11** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12** GNU General Public License for more details.
13**
14** You should have received a copy of the GNU General Public License
15** along with this program; if not, write to the Free Software
16** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
17*/
18
19#include <stdio.h>
20#include <stdlib.h>
21#include <unistd.h>
22#include <string.h>
23#include <ctype.h>
24
25#include "config.h"
26
27#if (HAVE_FFTW3 && HAVE_SNDFILE && HAVE_SYS_TIMES_H)
28
29#include <time.h>
30#include <sys/times.h>
31
32#include <sndfile.h>
33#include <math.h>
34#include <sys/utsname.h>
35
36#include "util.h"
37
38#define MAX_FREQS 4
39#define BUFFER_LEN 80000
40
41#define SAFE_STRNCAT(dest,src,len) \
42 { int safe_strncat_count ; \
43 safe_strncat_count = (len) - strlen (dest) - 1 ; \
44 strncat ((dest), (src), safe_strncat_count) ; \
45 (dest) [(len) - 1] = 0 ; \
46 } ;
47
48typedef struct
49{ int freq_count ;
50 double freqs [MAX_FREQS] ;
51
52 int output_samplerate ;
53 int pass_band_peaks ;
54
55 double peak_value ;
56} SNR_TEST ;
57
58typedef struct
59{ const char *progname ;
60 const char *version_cmd ;
61 const char *version_start ;
62 const char *convert_cmd ;
63 int format ;
64} RESAMPLE_PROG ;
65
66static char *get_progname (char *) ;
67static void usage_exit (const char *, const RESAMPLE_PROG *prog, int count) ;
68static void measure_program (const RESAMPLE_PROG *prog, int verbose) ;
69static void generate_source_wav (const char *filename, const double *freqs, int freq_count, int format) ;
70static const char* get_machine_details (void) ;
71
72static char version_string [512] ;
73
74int
75main (int argc, char *argv [])
76{ static RESAMPLE_PROG resample_progs [] =
77 { { "sndfile-resample",
78 "examples/sndfile-resample --version",
79 "libsamplerate",
80 "examples/sndfile-resample --max-speed -c 0 -to %d source.wav destination.wav",
81 SF_FORMAT_WAV | SF_FORMAT_PCM_32
82 },
83 { "sox",
84 "sox -h 2>&1",
85 "sox",
86 "sox source.wav -r %d destination.wav resample 0.835",
87 SF_FORMAT_WAV | SF_FORMAT_PCM_32
88 },
89 { "ResampAudio",
90 "ResampAudio --version",
91 "ResampAudio",
92 "ResampAudio -f cutoff=0.41,atten=100,ratio=128 -s %d source.wav destination.wav",
93 SF_FORMAT_WAV | SF_FORMAT_PCM_32
94 },
95
96 /*-
97 { /+*
98 ** The Shibatch converter doesn't work for all combinations of
99 ** source and destination sample rates. Therefore it can't be
100 ** included in this test.
101 *+/
102 "shibatch",
103 "ssrc",
104 "Shibatch",
105 "ssrc --rate %d source.wav destination.wav",
106 SF_FORMAT_WAV | SF_FORMAT_PCM_32
107 },-*/
108
109 /*-
110 { /+*
111 ** The resample program is not able to match the bandwidth and SNR
112 ** specs or sndfile-resample and hence will not be tested.
113 *+/
114 "resample",
115 "resample -version",
116 "resample",
117 "resample -to %d source.wav destination.wav",
118 SF_FORMAT_WAV | SF_FORMAT_FLOAT
119 },-*/
120
121 /*-
122 { "mplayer",
123 "mplayer -v 2>&1",
124 "MPlayer ",
125 "mplayer -ao pcm -srate %d source.wav >/dev/null 2>&1 && mv audiodump.wav destination.wav",
126 SF_FORMAT_WAV | SF_FORMAT_PCM_32
127 },-*/
128
129 } ; /* resample_progs */
130
131 char *progname ;
132 int prog = 0, verbose = 0 ;
133
134 progname = get_progname (argv [0]) ;
135
136 printf ("\n %s : evaluate a sample rate converter.\n", progname) ;
137
138 if (argc == 3 && strcmp ("--verbose", argv [1]) == 0)
139 { verbose = 1 ;
140 prog = atoi (argv [2]) ;
141 }
142 else if (argc == 2)
143 { verbose = 0 ;
144 prog = atoi (argv [1]) ;
145 }
146 else
147 usage_exit (progname, resample_progs, ARRAY_LEN (resample_progs)) ;
148
149 if (prog < 0 || prog >= ARRAY_LEN (resample_progs))
150 usage_exit (progname, resample_progs, ARRAY_LEN (resample_progs)) ;
151
152 measure_program (& (resample_progs [prog]), verbose) ;
153
154 puts ("") ;
155
156 return 0 ;
157} /* main */
158
159/*==============================================================================
160*/
161
162static char *
163get_progname (char *progname)
164{ char *cptr ;
165
166 if ((cptr = strrchr (progname, '/')) != NULL)
167 progname = cptr + 1 ;
168
169 if ((cptr = strrchr (progname, '\\')) != NULL)
170 progname = cptr + 1 ;
171
172 return progname ;
173} /* get_progname */
174
175static void
176usage_exit (const char *progname, const RESAMPLE_PROG *prog, int count)
177{ int k ;
178
179 printf ("\n Usage : %s <number>\n\n", progname) ;
180
181 puts (" where <number> specifies the program to test:\n") ;
182
183 for (k = 0 ; k < count ; k++)
184 printf (" %d : %s\n", k, prog [k].progname) ;
185
186 puts ("\n"
187 " Obviously to test a given program you have to have it available on\n"
188 " your system. See http://www.mega-nerd.com/SRC/quality.html for\n"
189 " the download location of these programs.\n") ;
190
191 exit (1) ;
192} /* usage_exit */
193
194static const char*
195get_machine_details (void)
196{ static char namestr [256] ;
197
198 struct utsname name ;
199
200 if (uname (&name) != 0)
201 { snprintf (namestr, sizeof (namestr), "Unknown") ;
202 return namestr ;
203 } ;
204
205 snprintf (namestr, sizeof (namestr), "%s (%s %s %s)", name.nodename,
206 name.machine, name.sysname, name.release) ;
207
208 return namestr ;
209} /* get_machine_details */
210
211
212/*==============================================================================
213*/
214
215static void
216get_version_string (const RESAMPLE_PROG *prog)
217{ FILE *file ;
218 char *cptr ;
219
220 /* Default. */
221 snprintf (version_string, sizeof (version_string), "no version") ;
222
223 if (prog->version_cmd == NULL)
224 return ;
225
226 if ((file = popen (prog->version_cmd, "r")) == NULL)
227 return ;
228
229 while ((cptr = fgets (version_string, sizeof (version_string), file)) != NULL)
230 {
231 if (strstr (cptr, prog->version_start) != NULL)
232 break ;
233
234 version_string [0] = 0 ;
235 } ;
236
237 pclose (file) ;
238
239 /* Remove trailing newline. */
240 if ((cptr = strchr (version_string, '\n')) != NULL)
241 cptr [0] = 0 ;
242
243 /* Remove leading whitespace from version string. */
244 cptr = version_string ;
245 while (cptr [0] != 0 && isspace (cptr [0]))
246 cptr ++ ;
247
248 if (cptr != version_string)
249 strncpy (version_string, cptr, sizeof (version_string)) ;
250
251 return ;
252} /* get_version_string */
253
254static void
255generate_source_wav (const char *filename, const double *freqs, int freq_count, int format)
256{ static float buffer [BUFFER_LEN] ;
257
258 SNDFILE *sndfile ;
259 SF_INFO sfinfo ;
260
261 sfinfo.channels = 1 ;
262 sfinfo.samplerate = 44100 ;
263 sfinfo.format = format ;
264
265 if ((sndfile = sf_open (filename, SFM_WRITE, &sfinfo)) == NULL)
266 { printf ("Line %d : cound not open '%s' : %s\n", __LINE__, filename, sf_strerror (NULL)) ;
267 exit (1) ;
268 } ;
269
270 sf_command (sndfile, SFC_SET_ADD_PEAK_CHUNK, NULL, SF_FALSE) ;
271
272 gen_windowed_sines (freq_count, freqs, 0.9, buffer, ARRAY_LEN (buffer)) ;
273
274 if (sf_write_float (sndfile, buffer, ARRAY_LEN (buffer)) != ARRAY_LEN (buffer))
275 { printf ("Line %d : sf_write_float short write.\n", __LINE__) ;
276 exit (1) ;
277 } ;
278
279 sf_close (sndfile) ;
280} /* generate_source_wav */
281
282static double
283measure_destination_wav (char *filename, int *output_samples, int expected_peaks)
284{ static float buffer [250000] ;
285
286 SNDFILE *sndfile ;
287 SF_INFO sfinfo ;
288 double snr ;
289
290 if ((sndfile = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
291 { printf ("Line %d : Cound not open '%s' : %s\n", __LINE__, filename, sf_strerror (NULL)) ;
292 exit (1) ;
293 } ;
294
295 if (sfinfo.channels != 1)
296 { printf ("Line %d : Bad channel count (%d). Should be 1.\n", __LINE__, sfinfo.channels) ;
297 exit (1) ;
298 } ;
299
300 if (sfinfo.frames > ARRAY_LEN (buffer))
301 { printf ("Line %d : Too many frames (%ld) of data in file.\n", __LINE__, (long) sfinfo.frames) ;
302 exit (1) ;
303 } ;
304
305 *output_samples = (int) sfinfo.frames ;
306
307 if (sf_read_float (sndfile, buffer, sfinfo.frames) != sfinfo.frames)
308 { printf ("Line %d : Bad read.\n", __LINE__) ;
309 exit (1) ;
310 } ;
311
312 sf_close (sndfile) ;
313
314 snr = calculate_snr (buffer, sfinfo.frames, expected_peaks) ;
315
316 return snr ;
317} /* measure_desination_wav */
318
319static double
320measure_snr (const RESAMPLE_PROG *prog, int *output_samples, int verbose)
321{ static SNR_TEST snr_test [] =
322 {
323 { 1, { 0.211111111111 }, 48000, 1, 1.0 },
324 { 1, { 0.011111111111 }, 132301, 1, 1.0 },
325 { 1, { 0.111111111111 }, 92301, 1, 1.0 },
326 { 1, { 0.011111111111 }, 26461, 1, 1.0 },
327 { 1, { 0.011111111111 }, 13231, 1, 1.0 },
328 { 1, { 0.011111111111 }, 44101, 1, 1.0 },
329 { 2, { 0.311111, 0.49 }, 78199, 2, 1.0 },
330 { 2, { 0.011111, 0.49 }, 12345, 1, 0.5 },
331 { 2, { 0.0123456, 0.4 }, 20143, 1, 0.5 },
332 { 2, { 0.0111111, 0.4 }, 26461, 1, 0.5 },
333 { 1, { 0.381111111111 }, 58661, 1, 1.0 }
334 } ; /* snr_test */
335 static char command [256] ;
336
337 double snr, worst_snr = 500.0 ;
338 int k , retval, sample_count ;
339
340 *output_samples = 0 ;
341
342 for (k = 0 ; k < ARRAY_LEN (snr_test) ; k++)
343 { remove ("source.wav") ;
344 remove ("destination.wav") ;
345
346 if (verbose)
347 printf (" SNR test #%d : ", k) ;
348 fflush (stdout) ;
349 generate_source_wav ("source.wav", snr_test [k].freqs, snr_test [k].freq_count, prog->format) ;
350
351 snprintf (command, sizeof (command), prog->convert_cmd, snr_test [k].output_samplerate) ;
352 SAFE_STRNCAT (command, " >/dev/null 2>&1", sizeof (command)) ;
353 if ((retval = system (command)) != 0)
354 printf ("system returned %d\n", retval) ;
355
356 snr = measure_destination_wav ("destination.wav", &sample_count, snr_test->pass_band_peaks) ;
357
358 *output_samples += sample_count ;
359
360 if (fabs (snr) < fabs (worst_snr))
361 worst_snr = fabs (snr) ;
362
363 if (verbose)
364 printf ("%6.2f dB\n", snr) ;
365 } ;
366
367 return worst_snr ;
368} /* measure_snr */
369
370/*------------------------------------------------------------------------------
371*/
372
373static double
374measure_destination_peak (const char *filename)
375{ static float data [2 * BUFFER_LEN] ;
376 SNDFILE *sndfile ;
377 SF_INFO sfinfo ;
378 double peak = 0.0 ;
379 int k = 0 ;
380
381 if ((sndfile = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
382 { printf ("Line %d : failed to open file %s\n", __LINE__, filename) ;
383 exit (1) ;
384 } ;
385
386 if (sfinfo.channels != 1)
387 { printf ("Line %d : bad channel count.\n", __LINE__) ;
388 exit (1) ;
389 } ;
390
391 if (sfinfo.frames > ARRAY_LEN (data) + 4 || sfinfo.frames < ARRAY_LEN (data) - 100)
392 { printf ("Line %d : bad frame count (got %d, expected %d).\n", __LINE__, (int) sfinfo.frames, ARRAY_LEN (data)) ;
393 exit (1) ;
394 } ;
395
396 if (sf_read_float (sndfile, data, sfinfo.frames) != sfinfo.frames)
397 { printf ("Line %d : bad read.\n", __LINE__) ;
398 exit (1) ;
399 } ;
400
401 sf_close (sndfile) ;
402
403 for (k = 0 ; k < (int) sfinfo.frames ; k++)
404 if (fabs (data [k]) > peak)
405 peak = fabs (data [k]) ;
406
407 return peak ;
408} /* measure_destination_peak */
409
410static double
411find_attenuation (double freq, const RESAMPLE_PROG *prog, int verbose)
412{ static char command [256] ;
413 double output_peak ;
414 int retval ;
415 char *filename ;
416
417 filename = "destination.wav" ;
418
419 generate_source_wav ("source.wav", &freq, 1, prog->format) ;
420
421 remove (filename) ;
422
423 snprintf (command, sizeof (command), prog->convert_cmd, 88189) ;
424 SAFE_STRNCAT (command, " >/dev/null 2>&1", sizeof (command)) ;
425 if ((retval = system (command)) != 0)
426 printf ("system returned %d\n", retval) ;
427
428 output_peak = measure_destination_peak (filename) ;
429
430 if (verbose)
431 printf (" freq : %f peak : %f\n", freq, output_peak) ;
432
433 return fabs (20.0 * log10 (output_peak)) ;
434} /* find_attenuation */
435
436static double
437bandwidth_test (const RESAMPLE_PROG *prog, int verbose)
438{ double f1, f2, a1, a2 ;
439 double freq, atten ;
440
441 f1 = 0.35 ;
442 a1 = find_attenuation (f1, prog, verbose) ;
443
444 f2 = 0.49999 ;
445 a2 = find_attenuation (f2, prog, verbose) ;
446
447
448 if (fabs (a1) < 1e-2 && a2 < 3.0)
449 return -1.0 ;
450
451 if (a1 > 3.0 || a2 < 3.0)
452 { printf ("\n\nLine %d : cannot bracket 3dB point.\n\n", __LINE__) ;
453 exit (1) ;
454 } ;
455
456 while (a2 - a1 > 1.0)
457 { freq = f1 + 0.5 * (f2 - f1) ;
458 atten = find_attenuation (freq, prog, verbose) ;
459
460 if (atten < 3.0)
461 { f1 = freq ;
462 a1 = atten ;
463 }
464 else
465 { f2 = freq ;
466 a2 = atten ;
467 } ;
468 } ;
469
470 freq = f1 + (3.0 - a1) * (f2 - f1) / (a2 - a1) ;
471
472 return 200.0 * freq ;
473} /* bandwidth_test */
474
475static void
476measure_program (const RESAMPLE_PROG *prog, int verbose)
477{ double snr, bandwidth, conversion_rate ;
478 int output_samples ;
479 struct tms time_data ;
480 time_t time_now ;
481
482 printf ("\n Machine : %s\n", get_machine_details ()) ;
483 time_now = time (NULL) ;
484 printf (" Date : %s", ctime (&time_now)) ;
485
486 get_version_string (prog) ;
487 printf (" Program : %s\n", version_string) ;
488 printf (" Command : %s\n\n", prog->convert_cmd) ;
489
490 snr = measure_snr (prog, &output_samples, verbose) ;
491
492 printf (" Worst case SNR : %6.2f dB\n", snr) ;
493
494 times (&time_data) ;
495
496 conversion_rate = (1.0 * output_samples * sysconf (_SC_CLK_TCK)) / time_data.tms_cutime ;
497
498 printf (" Conversion rate : %5.0f samples/sec\n", conversion_rate) ;
499
500 bandwidth = bandwidth_test (prog, verbose) ;
501
502 if (bandwidth > 0.0)
503 printf (" Measured bandwidth : %5.2f %%\n", bandwidth) ;
504 else
505 printf (" Could not measure bandwidth (no -3dB point found).\n") ;
506
507 return ;
508} /* measure_program */
509
510/*##############################################################################
511*/
512
513#else
514
515int
516main (void)
517{ puts ("\n"
518 "****************************************************************\n"
519 " This program has been compiled without :\n"
520 " 1) FFTW (http://www.fftw.org/).\n"
521 " 2) libsndfile (http://www.zip.com.au/~erikd/libsndfile/).\n"
522 " Without these two libraries there is not much it can do.\n"
523 "****************************************************************\n") ;
524
525 return 0 ;
526} /* main */
527
528#endif /* (HAVE_FFTW3 && HAVE_SNDFILE) */
529