view src/libsamplerate-0.1.8/tests/src-evaluate.c @ 83:ae30d91d2ffe

Replace these with versions built using an older toolset (so as to avoid ABI compatibilities when linking on Ubuntu 14.04 for packaging purposes)
author Chris Cannam
date Fri, 07 Feb 2020 11:51:13 +0000
parents c7265573341e
children
line wrap: on
line source
/*
** Copyright (C) 2002-2011 Erik de Castro Lopo <erikd@mega-nerd.com>
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>

#include "config.h"

#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://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) */