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