You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
530 lines
13 KiB
530 lines
13 KiB
/*
|
|
** Copyright (c) 2002-2016, Erik de Castro Lopo <erikd@mega-nerd.com>
|
|
** All rights reserved.
|
|
**
|
|
** This code is released under 2-clause BSD license. Please see the
|
|
** file at : https://github.com/libsndfile/libsamplerate/blob/master/COPYING
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#if defined(_WIN32)
|
|
#define popen _popen
|
|
#define pclose _pclose
|
|
#endif
|
|
|
|
#if (HAVE_FFTW3 && HAVE_SNDFILE && HAVE_SYS_TIMES_H)
|
|
|
|
#include <time.h>
|
|
#include <sys/times.h>
|
|
|
|
#include <sndfile.h>
|
|
#include <math.h>
|
|
#include <sys/utsname.h>
|
|
|
|
#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 <number>\n\n", progname) ;
|
|
|
|
puts (" where <number> 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://libsndfile.github.io/libsamplerate/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 [262] ;
|
|
|
|
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) - 1) ;
|
|
version_string [sizeof (version_string) - 1] = 0 ;
|
|
} ;
|
|
|
|
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) */
|
|
|
|
|