cannam@126: /* cannam@126: ** Copyright (c) 2002-2016, Erik de Castro Lopo cannam@126: ** All rights reserved. cannam@126: ** cannam@126: ** This code is released under 2-clause BSD license. Please see the cannam@126: ** file at : https://github.com/erikd/libsamplerate/blob/master/COPYING cannam@126: */ cannam@126: cannam@126: #include cannam@126: #include cannam@126: #include cannam@126: #include cannam@126: #include cannam@126: cannam@126: #include "config.h" cannam@126: cannam@126: #if (HAVE_FFTW3 && HAVE_SNDFILE && HAVE_SYS_TIMES_H) cannam@126: cannam@126: #include cannam@126: #include cannam@126: cannam@126: #include cannam@126: #include cannam@126: #include cannam@126: cannam@126: #include "util.h" cannam@126: cannam@126: #define MAX_FREQS 4 cannam@126: #define BUFFER_LEN 80000 cannam@126: cannam@126: #define SAFE_STRNCAT(dest,src,len) \ cannam@126: { int safe_strncat_count ; \ cannam@126: safe_strncat_count = (len) - strlen (dest) - 1 ; \ cannam@126: strncat ((dest), (src), safe_strncat_count) ; \ cannam@126: (dest) [(len) - 1] = 0 ; \ cannam@126: } ; cannam@126: cannam@126: typedef struct cannam@126: { int freq_count ; cannam@126: double freqs [MAX_FREQS] ; cannam@126: cannam@126: int output_samplerate ; cannam@126: int pass_band_peaks ; cannam@126: cannam@126: double peak_value ; cannam@126: } SNR_TEST ; cannam@126: cannam@126: typedef struct cannam@126: { const char *progname ; cannam@126: const char *version_cmd ; cannam@126: const char *version_start ; cannam@126: const char *convert_cmd ; cannam@126: int format ; cannam@126: } RESAMPLE_PROG ; cannam@126: cannam@126: static char *get_progname (char *) ; cannam@126: static void usage_exit (const char *, const RESAMPLE_PROG *prog, int count) ; cannam@126: static void measure_program (const RESAMPLE_PROG *prog, int verbose) ; cannam@126: static void generate_source_wav (const char *filename, const double *freqs, int freq_count, int format) ; cannam@126: static const char* get_machine_details (void) ; cannam@126: cannam@126: static char version_string [512] ; cannam@126: cannam@126: int cannam@126: main (int argc, char *argv []) cannam@126: { static RESAMPLE_PROG resample_progs [] = cannam@126: { { "sndfile-resample", cannam@126: "examples/sndfile-resample --version", cannam@126: "libsamplerate", cannam@126: "examples/sndfile-resample --max-speed -c 0 -to %d source.wav destination.wav", cannam@126: SF_FORMAT_WAV | SF_FORMAT_PCM_32 cannam@126: }, cannam@126: { "sox", cannam@126: "sox -h 2>&1", cannam@126: "sox", cannam@126: "sox source.wav -r %d destination.wav resample 0.835", cannam@126: SF_FORMAT_WAV | SF_FORMAT_PCM_32 cannam@126: }, cannam@126: { "ResampAudio", cannam@126: "ResampAudio --version", cannam@126: "ResampAudio", cannam@126: "ResampAudio -f cutoff=0.41,atten=100,ratio=128 -s %d source.wav destination.wav", cannam@126: SF_FORMAT_WAV | SF_FORMAT_PCM_32 cannam@126: }, cannam@126: cannam@126: /*- cannam@126: { /+* cannam@126: ** The Shibatch converter doesn't work for all combinations of cannam@126: ** source and destination sample rates. Therefore it can't be cannam@126: ** included in this test. cannam@126: *+/ cannam@126: "shibatch", cannam@126: "ssrc", cannam@126: "Shibatch", cannam@126: "ssrc --rate %d source.wav destination.wav", cannam@126: SF_FORMAT_WAV | SF_FORMAT_PCM_32 cannam@126: },-*/ cannam@126: cannam@126: /*- cannam@126: { /+* cannam@126: ** The resample program is not able to match the bandwidth and SNR cannam@126: ** specs or sndfile-resample and hence will not be tested. cannam@126: *+/ cannam@126: "resample", cannam@126: "resample -version", cannam@126: "resample", cannam@126: "resample -to %d source.wav destination.wav", cannam@126: SF_FORMAT_WAV | SF_FORMAT_FLOAT cannam@126: },-*/ cannam@126: cannam@126: /*- cannam@126: { "mplayer", cannam@126: "mplayer -v 2>&1", cannam@126: "MPlayer ", cannam@126: "mplayer -ao pcm -srate %d source.wav >/dev/null 2>&1 && mv audiodump.wav destination.wav", cannam@126: SF_FORMAT_WAV | SF_FORMAT_PCM_32 cannam@126: },-*/ cannam@126: cannam@126: } ; /* resample_progs */ cannam@126: cannam@126: char *progname ; cannam@126: int prog = 0, verbose = 0 ; cannam@126: cannam@126: progname = get_progname (argv [0]) ; cannam@126: cannam@126: printf ("\n %s : evaluate a sample rate converter.\n", progname) ; cannam@126: cannam@126: if (argc == 3 && strcmp ("--verbose", argv [1]) == 0) cannam@126: { verbose = 1 ; cannam@126: prog = atoi (argv [2]) ; cannam@126: } cannam@126: else if (argc == 2) cannam@126: { verbose = 0 ; cannam@126: prog = atoi (argv [1]) ; cannam@126: } cannam@126: else cannam@126: usage_exit (progname, resample_progs, ARRAY_LEN (resample_progs)) ; cannam@126: cannam@126: if (prog < 0 || prog >= ARRAY_LEN (resample_progs)) cannam@126: usage_exit (progname, resample_progs, ARRAY_LEN (resample_progs)) ; cannam@126: cannam@126: measure_program (& (resample_progs [prog]), verbose) ; cannam@126: cannam@126: puts ("") ; cannam@126: cannam@126: return 0 ; cannam@126: } /* main */ cannam@126: cannam@126: /*============================================================================== cannam@126: */ cannam@126: cannam@126: static char * cannam@126: get_progname (char *progname) cannam@126: { char *cptr ; cannam@126: cannam@126: if ((cptr = strrchr (progname, '/')) != NULL) cannam@126: progname = cptr + 1 ; cannam@126: cannam@126: if ((cptr = strrchr (progname, '\\')) != NULL) cannam@126: progname = cptr + 1 ; cannam@126: cannam@126: return progname ; cannam@126: } /* get_progname */ cannam@126: cannam@126: static void cannam@126: usage_exit (const char *progname, const RESAMPLE_PROG *prog, int count) cannam@126: { int k ; cannam@126: cannam@126: printf ("\n Usage : %s \n\n", progname) ; cannam@126: cannam@126: puts (" where specifies the program to test:\n") ; cannam@126: cannam@126: for (k = 0 ; k < count ; k++) cannam@126: printf (" %d : %s\n", k, prog [k].progname) ; cannam@126: cannam@126: puts ("\n" cannam@126: " Obviously to test a given program you have to have it available on\n" cannam@126: " your system. See http://www.mega-nerd.com/SRC/quality.html for\n" cannam@126: " the download location of these programs.\n") ; cannam@126: cannam@126: exit (1) ; cannam@126: } /* usage_exit */ cannam@126: cannam@126: static const char* cannam@126: get_machine_details (void) cannam@126: { static char namestr [256] ; cannam@126: cannam@126: struct utsname name ; cannam@126: cannam@126: if (uname (&name) != 0) cannam@126: { snprintf (namestr, sizeof (namestr), "Unknown") ; cannam@126: return namestr ; cannam@126: } ; cannam@126: cannam@126: snprintf (namestr, sizeof (namestr), "%s (%s %s %s)", name.nodename, cannam@126: name.machine, name.sysname, name.release) ; cannam@126: cannam@126: return namestr ; cannam@126: } /* get_machine_details */ cannam@126: cannam@126: cannam@126: /*============================================================================== cannam@126: */ cannam@126: cannam@126: static void cannam@126: get_version_string (const RESAMPLE_PROG *prog) cannam@126: { FILE *file ; cannam@126: char *cptr ; cannam@126: cannam@126: /* Default. */ cannam@126: snprintf (version_string, sizeof (version_string), "no version") ; cannam@126: cannam@126: if (prog->version_cmd == NULL) cannam@126: return ; cannam@126: cannam@126: if ((file = popen (prog->version_cmd, "r")) == NULL) cannam@126: return ; cannam@126: cannam@126: while ((cptr = fgets (version_string, sizeof (version_string), file)) != NULL) cannam@126: { cannam@126: if (strstr (cptr, prog->version_start) != NULL) cannam@126: break ; cannam@126: cannam@126: version_string [0] = 0 ; cannam@126: } ; cannam@126: cannam@126: pclose (file) ; cannam@126: cannam@126: /* Remove trailing newline. */ cannam@126: if ((cptr = strchr (version_string, '\n')) != NULL) cannam@126: cptr [0] = 0 ; cannam@126: cannam@126: /* Remove leading whitespace from version string. */ cannam@126: cptr = version_string ; cannam@126: while (cptr [0] != 0 && isspace (cptr [0])) cannam@126: cptr ++ ; cannam@126: cannam@126: if (cptr != version_string) cannam@126: strncpy (version_string, cptr, sizeof (version_string)) ; cannam@126: cannam@126: return ; cannam@126: } /* get_version_string */ cannam@126: cannam@126: static void cannam@126: generate_source_wav (const char *filename, const double *freqs, int freq_count, int format) cannam@126: { static float buffer [BUFFER_LEN] ; cannam@126: cannam@126: SNDFILE *sndfile ; cannam@126: SF_INFO sfinfo ; cannam@126: cannam@126: sfinfo.channels = 1 ; cannam@126: sfinfo.samplerate = 44100 ; cannam@126: sfinfo.format = format ; cannam@126: cannam@126: if ((sndfile = sf_open (filename, SFM_WRITE, &sfinfo)) == NULL) cannam@126: { printf ("Line %d : cound not open '%s' : %s\n", __LINE__, filename, sf_strerror (NULL)) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: sf_command (sndfile, SFC_SET_ADD_PEAK_CHUNK, NULL, SF_FALSE) ; cannam@126: cannam@126: gen_windowed_sines (freq_count, freqs, 0.9, buffer, ARRAY_LEN (buffer)) ; cannam@126: cannam@126: if (sf_write_float (sndfile, buffer, ARRAY_LEN (buffer)) != ARRAY_LEN (buffer)) cannam@126: { printf ("Line %d : sf_write_float short write.\n", __LINE__) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: sf_close (sndfile) ; cannam@126: } /* generate_source_wav */ cannam@126: cannam@126: static double cannam@126: measure_destination_wav (char *filename, int *output_samples, int expected_peaks) cannam@126: { static float buffer [250000] ; cannam@126: cannam@126: SNDFILE *sndfile ; cannam@126: SF_INFO sfinfo ; cannam@126: double snr ; cannam@126: cannam@126: if ((sndfile = sf_open (filename, SFM_READ, &sfinfo)) == NULL) cannam@126: { printf ("Line %d : Cound not open '%s' : %s\n", __LINE__, filename, sf_strerror (NULL)) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: if (sfinfo.channels != 1) cannam@126: { printf ("Line %d : Bad channel count (%d). Should be 1.\n", __LINE__, sfinfo.channels) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: if (sfinfo.frames > ARRAY_LEN (buffer)) cannam@126: { printf ("Line %d : Too many frames (%ld) of data in file.\n", __LINE__, (long) sfinfo.frames) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: *output_samples = (int) sfinfo.frames ; cannam@126: cannam@126: if (sf_read_float (sndfile, buffer, sfinfo.frames) != sfinfo.frames) cannam@126: { printf ("Line %d : Bad read.\n", __LINE__) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: sf_close (sndfile) ; cannam@126: cannam@126: snr = calculate_snr (buffer, sfinfo.frames, expected_peaks) ; cannam@126: cannam@126: return snr ; cannam@126: } /* measure_desination_wav */ cannam@126: cannam@126: static double cannam@126: measure_snr (const RESAMPLE_PROG *prog, int *output_samples, int verbose) cannam@126: { static SNR_TEST snr_test [] = cannam@126: { cannam@126: { 1, { 0.211111111111 }, 48000, 1, 1.0 }, cannam@126: { 1, { 0.011111111111 }, 132301, 1, 1.0 }, cannam@126: { 1, { 0.111111111111 }, 92301, 1, 1.0 }, cannam@126: { 1, { 0.011111111111 }, 26461, 1, 1.0 }, cannam@126: { 1, { 0.011111111111 }, 13231, 1, 1.0 }, cannam@126: { 1, { 0.011111111111 }, 44101, 1, 1.0 }, cannam@126: { 2, { 0.311111, 0.49 }, 78199, 2, 1.0 }, cannam@126: { 2, { 0.011111, 0.49 }, 12345, 1, 0.5 }, cannam@126: { 2, { 0.0123456, 0.4 }, 20143, 1, 0.5 }, cannam@126: { 2, { 0.0111111, 0.4 }, 26461, 1, 0.5 }, cannam@126: { 1, { 0.381111111111 }, 58661, 1, 1.0 } cannam@126: } ; /* snr_test */ cannam@126: static char command [256] ; cannam@126: cannam@126: double snr, worst_snr = 500.0 ; cannam@126: int k , retval, sample_count ; cannam@126: cannam@126: *output_samples = 0 ; cannam@126: cannam@126: for (k = 0 ; k < ARRAY_LEN (snr_test) ; k++) cannam@126: { remove ("source.wav") ; cannam@126: remove ("destination.wav") ; cannam@126: cannam@126: if (verbose) cannam@126: printf (" SNR test #%d : ", k) ; cannam@126: fflush (stdout) ; cannam@126: generate_source_wav ("source.wav", snr_test [k].freqs, snr_test [k].freq_count, prog->format) ; cannam@126: cannam@126: snprintf (command, sizeof (command), prog->convert_cmd, snr_test [k].output_samplerate) ; cannam@126: SAFE_STRNCAT (command, " >/dev/null 2>&1", sizeof (command)) ; cannam@126: if ((retval = system (command)) != 0) cannam@126: printf ("system returned %d\n", retval) ; cannam@126: cannam@126: snr = measure_destination_wav ("destination.wav", &sample_count, snr_test->pass_band_peaks) ; cannam@126: cannam@126: *output_samples += sample_count ; cannam@126: cannam@126: if (fabs (snr) < fabs (worst_snr)) cannam@126: worst_snr = fabs (snr) ; cannam@126: cannam@126: if (verbose) cannam@126: printf ("%6.2f dB\n", snr) ; cannam@126: } ; cannam@126: cannam@126: return worst_snr ; cannam@126: } /* measure_snr */ cannam@126: cannam@126: /*------------------------------------------------------------------------------ cannam@126: */ cannam@126: cannam@126: static double cannam@126: measure_destination_peak (const char *filename) cannam@126: { static float data [2 * BUFFER_LEN] ; cannam@126: SNDFILE *sndfile ; cannam@126: SF_INFO sfinfo ; cannam@126: double peak = 0.0 ; cannam@126: int k = 0 ; cannam@126: cannam@126: if ((sndfile = sf_open (filename, SFM_READ, &sfinfo)) == NULL) cannam@126: { printf ("Line %d : failed to open file %s\n", __LINE__, filename) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: if (sfinfo.channels != 1) cannam@126: { printf ("Line %d : bad channel count.\n", __LINE__) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: if (sfinfo.frames > ARRAY_LEN (data) + 4 || sfinfo.frames < ARRAY_LEN (data) - 100) cannam@126: { printf ("Line %d : bad frame count (got %d, expected %d).\n", __LINE__, (int) sfinfo.frames, ARRAY_LEN (data)) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: if (sf_read_float (sndfile, data, sfinfo.frames) != sfinfo.frames) cannam@126: { printf ("Line %d : bad read.\n", __LINE__) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: sf_close (sndfile) ; cannam@126: cannam@126: for (k = 0 ; k < (int) sfinfo.frames ; k++) cannam@126: if (fabs (data [k]) > peak) cannam@126: peak = fabs (data [k]) ; cannam@126: cannam@126: return peak ; cannam@126: } /* measure_destination_peak */ cannam@126: cannam@126: static double cannam@126: find_attenuation (double freq, const RESAMPLE_PROG *prog, int verbose) cannam@126: { static char command [256] ; cannam@126: double output_peak ; cannam@126: int retval ; cannam@126: char *filename ; cannam@126: cannam@126: filename = "destination.wav" ; cannam@126: cannam@126: generate_source_wav ("source.wav", &freq, 1, prog->format) ; cannam@126: cannam@126: remove (filename) ; cannam@126: cannam@126: snprintf (command, sizeof (command), prog->convert_cmd, 88189) ; cannam@126: SAFE_STRNCAT (command, " >/dev/null 2>&1", sizeof (command)) ; cannam@126: if ((retval = system (command)) != 0) cannam@126: printf ("system returned %d\n", retval) ; cannam@126: cannam@126: output_peak = measure_destination_peak (filename) ; cannam@126: cannam@126: if (verbose) cannam@126: printf (" freq : %f peak : %f\n", freq, output_peak) ; cannam@126: cannam@126: return fabs (20.0 * log10 (output_peak)) ; cannam@126: } /* find_attenuation */ cannam@126: cannam@126: static double cannam@126: bandwidth_test (const RESAMPLE_PROG *prog, int verbose) cannam@126: { double f1, f2, a1, a2 ; cannam@126: double freq, atten ; cannam@126: cannam@126: f1 = 0.35 ; cannam@126: a1 = find_attenuation (f1, prog, verbose) ; cannam@126: cannam@126: f2 = 0.49999 ; cannam@126: a2 = find_attenuation (f2, prog, verbose) ; cannam@126: cannam@126: cannam@126: if (fabs (a1) < 1e-2 && a2 < 3.0) cannam@126: return -1.0 ; cannam@126: cannam@126: if (a1 > 3.0 || a2 < 3.0) cannam@126: { printf ("\n\nLine %d : cannot bracket 3dB point.\n\n", __LINE__) ; cannam@126: exit (1) ; cannam@126: } ; cannam@126: cannam@126: while (a2 - a1 > 1.0) cannam@126: { freq = f1 + 0.5 * (f2 - f1) ; cannam@126: atten = find_attenuation (freq, prog, verbose) ; cannam@126: cannam@126: if (atten < 3.0) cannam@126: { f1 = freq ; cannam@126: a1 = atten ; cannam@126: } cannam@126: else cannam@126: { f2 = freq ; cannam@126: a2 = atten ; cannam@126: } ; cannam@126: } ; cannam@126: cannam@126: freq = f1 + (3.0 - a1) * (f2 - f1) / (a2 - a1) ; cannam@126: cannam@126: return 200.0 * freq ; cannam@126: } /* bandwidth_test */ cannam@126: cannam@126: static void cannam@126: measure_program (const RESAMPLE_PROG *prog, int verbose) cannam@126: { double snr, bandwidth, conversion_rate ; cannam@126: int output_samples ; cannam@126: struct tms time_data ; cannam@126: time_t time_now ; cannam@126: cannam@126: printf ("\n Machine : %s\n", get_machine_details ()) ; cannam@126: time_now = time (NULL) ; cannam@126: printf (" Date : %s", ctime (&time_now)) ; cannam@126: cannam@126: get_version_string (prog) ; cannam@126: printf (" Program : %s\n", version_string) ; cannam@126: printf (" Command : %s\n\n", prog->convert_cmd) ; cannam@126: cannam@126: snr = measure_snr (prog, &output_samples, verbose) ; cannam@126: cannam@126: printf (" Worst case SNR : %6.2f dB\n", snr) ; cannam@126: cannam@126: times (&time_data) ; cannam@126: cannam@126: conversion_rate = (1.0 * output_samples * sysconf (_SC_CLK_TCK)) / time_data.tms_cutime ; cannam@126: cannam@126: printf (" Conversion rate : %5.0f samples/sec\n", conversion_rate) ; cannam@126: cannam@126: bandwidth = bandwidth_test (prog, verbose) ; cannam@126: cannam@126: if (bandwidth > 0.0) cannam@126: printf (" Measured bandwidth : %5.2f %%\n", bandwidth) ; cannam@126: else cannam@126: printf (" Could not measure bandwidth (no -3dB point found).\n") ; cannam@126: cannam@126: return ; cannam@126: } /* measure_program */ cannam@126: cannam@126: /*############################################################################## cannam@126: */ cannam@126: cannam@126: #else cannam@126: cannam@126: int cannam@126: main (void) cannam@126: { puts ("\n" cannam@126: "****************************************************************\n" cannam@126: " This program has been compiled without :\n" cannam@126: " 1) FFTW (http://www.fftw.org/).\n" cannam@126: " 2) libsndfile (http://www.zip.com.au/~erikd/libsndfile/).\n" cannam@126: " Without these two libraries there is not much it can do.\n" cannam@126: "****************************************************************\n") ; cannam@126: cannam@126: return 0 ; cannam@126: } /* main */ cannam@126: cannam@126: #endif /* (HAVE_FFTW3 && HAVE_SNDFILE) */ cannam@126: