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