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