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