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.

520 lines
13KB

  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/erikd/libsamplerate/blob/master/COPYING
  7. */
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <unistd.h>
  11. #include <string.h>
  12. #include <ctype.h>
  13. #include "config.h"
  14. #if (HAVE_FFTW3 && HAVE_SNDFILE && HAVE_SYS_TIMES_H)
  15. #include <time.h>
  16. #include <sys/times.h>
  17. #include <sndfile.h>
  18. #include <math.h>
  19. #include <sys/utsname.h>
  20. #include "util.h"
  21. #define MAX_FREQS 4
  22. #define BUFFER_LEN 80000
  23. #define SAFE_STRNCAT(dest,src,len) \
  24. { int safe_strncat_count ; \
  25. safe_strncat_count = (len) - strlen (dest) - 1 ; \
  26. strncat ((dest), (src), safe_strncat_count) ; \
  27. (dest) [(len) - 1] = 0 ; \
  28. } ;
  29. typedef struct
  30. { int freq_count ;
  31. double freqs [MAX_FREQS] ;
  32. int output_samplerate ;
  33. int pass_band_peaks ;
  34. double peak_value ;
  35. } SNR_TEST ;
  36. typedef struct
  37. { const char *progname ;
  38. const char *version_cmd ;
  39. const char *version_start ;
  40. const char *convert_cmd ;
  41. int format ;
  42. } RESAMPLE_PROG ;
  43. static char *get_progname (char *) ;
  44. static void usage_exit (const char *, const RESAMPLE_PROG *prog, int count) ;
  45. static void measure_program (const RESAMPLE_PROG *prog, int verbose) ;
  46. static void generate_source_wav (const char *filename, const double *freqs, int freq_count, int format) ;
  47. static const char* get_machine_details (void) ;
  48. static char version_string [512] ;
  49. int
  50. main (int argc, char *argv [])
  51. { static RESAMPLE_PROG resample_progs [] =
  52. { { "sndfile-resample",
  53. "examples/sndfile-resample --version",
  54. "libsamplerate",
  55. "examples/sndfile-resample --max-speed -c 0 -to %d source.wav destination.wav",
  56. SF_FORMAT_WAV | SF_FORMAT_PCM_32
  57. },
  58. { "sox",
  59. "sox -h 2>&1",
  60. "sox",
  61. "sox source.wav -r %d destination.wav resample 0.835",
  62. SF_FORMAT_WAV | SF_FORMAT_PCM_32
  63. },
  64. { "ResampAudio",
  65. "ResampAudio --version",
  66. "ResampAudio",
  67. "ResampAudio -f cutoff=0.41,atten=100,ratio=128 -s %d source.wav destination.wav",
  68. SF_FORMAT_WAV | SF_FORMAT_PCM_32
  69. },
  70. /*-
  71. { /+*
  72. ** The Shibatch converter doesn't work for all combinations of
  73. ** source and destination sample rates. Therefore it can't be
  74. ** included in this test.
  75. *+/
  76. "shibatch",
  77. "ssrc",
  78. "Shibatch",
  79. "ssrc --rate %d source.wav destination.wav",
  80. SF_FORMAT_WAV | SF_FORMAT_PCM_32
  81. },-*/
  82. /*-
  83. { /+*
  84. ** The resample program is not able to match the bandwidth and SNR
  85. ** specs or sndfile-resample and hence will not be tested.
  86. *+/
  87. "resample",
  88. "resample -version",
  89. "resample",
  90. "resample -to %d source.wav destination.wav",
  91. SF_FORMAT_WAV | SF_FORMAT_FLOAT
  92. },-*/
  93. /*-
  94. { "mplayer",
  95. "mplayer -v 2>&1",
  96. "MPlayer ",
  97. "mplayer -ao pcm -srate %d source.wav >/dev/null 2>&1 && mv audiodump.wav destination.wav",
  98. SF_FORMAT_WAV | SF_FORMAT_PCM_32
  99. },-*/
  100. } ; /* resample_progs */
  101. char *progname ;
  102. int prog = 0, verbose = 0 ;
  103. progname = get_progname (argv [0]) ;
  104. printf ("\n %s : evaluate a sample rate converter.\n", progname) ;
  105. if (argc == 3 && strcmp ("--verbose", argv [1]) == 0)
  106. { verbose = 1 ;
  107. prog = atoi (argv [2]) ;
  108. }
  109. else if (argc == 2)
  110. { verbose = 0 ;
  111. prog = atoi (argv [1]) ;
  112. }
  113. else
  114. usage_exit (progname, resample_progs, ARRAY_LEN (resample_progs)) ;
  115. if (prog < 0 || prog >= ARRAY_LEN (resample_progs))
  116. usage_exit (progname, resample_progs, ARRAY_LEN (resample_progs)) ;
  117. measure_program (& (resample_progs [prog]), verbose) ;
  118. puts ("") ;
  119. return 0 ;
  120. } /* main */
  121. /*==============================================================================
  122. */
  123. static char *
  124. get_progname (char *progname)
  125. { char *cptr ;
  126. if ((cptr = strrchr (progname, '/')) != NULL)
  127. progname = cptr + 1 ;
  128. if ((cptr = strrchr (progname, '\\')) != NULL)
  129. progname = cptr + 1 ;
  130. return progname ;
  131. } /* get_progname */
  132. static void
  133. usage_exit (const char *progname, const RESAMPLE_PROG *prog, int count)
  134. { int k ;
  135. printf ("\n Usage : %s <number>\n\n", progname) ;
  136. puts (" where <number> specifies the program to test:\n") ;
  137. for (k = 0 ; k < count ; k++)
  138. printf (" %d : %s\n", k, prog [k].progname) ;
  139. puts ("\n"
  140. " Obviously to test a given program you have to have it available on\n"
  141. " your system. See http://www.mega-nerd.com/SRC/quality.html for\n"
  142. " the download location of these programs.\n") ;
  143. exit (1) ;
  144. } /* usage_exit */
  145. static const char*
  146. get_machine_details (void)
  147. { static char namestr [256] ;
  148. struct utsname name ;
  149. if (uname (&name) != 0)
  150. { snprintf (namestr, sizeof (namestr), "Unknown") ;
  151. return namestr ;
  152. } ;
  153. snprintf (namestr, sizeof (namestr), "%s (%s %s %s)", name.nodename,
  154. name.machine, name.sysname, name.release) ;
  155. return namestr ;
  156. } /* get_machine_details */
  157. /*==============================================================================
  158. */
  159. static void
  160. get_version_string (const RESAMPLE_PROG *prog)
  161. { FILE *file ;
  162. char *cptr ;
  163. /* Default. */
  164. snprintf (version_string, sizeof (version_string), "no version") ;
  165. if (prog->version_cmd == NULL)
  166. return ;
  167. if ((file = popen (prog->version_cmd, "r")) == NULL)
  168. return ;
  169. while ((cptr = fgets (version_string, sizeof (version_string), file)) != NULL)
  170. {
  171. if (strstr (cptr, prog->version_start) != NULL)
  172. break ;
  173. version_string [0] = 0 ;
  174. } ;
  175. pclose (file) ;
  176. /* Remove trailing newline. */
  177. if ((cptr = strchr (version_string, '\n')) != NULL)
  178. cptr [0] = 0 ;
  179. /* Remove leading whitespace from version string. */
  180. cptr = version_string ;
  181. while (cptr [0] != 0 && isspace (cptr [0]))
  182. cptr ++ ;
  183. if (cptr != version_string)
  184. strncpy (version_string, cptr, sizeof (version_string)) ;
  185. return ;
  186. } /* get_version_string */
  187. static void
  188. generate_source_wav (const char *filename, const double *freqs, int freq_count, int format)
  189. { static float buffer [BUFFER_LEN] ;
  190. SNDFILE *sndfile ;
  191. SF_INFO sfinfo ;
  192. sfinfo.channels = 1 ;
  193. sfinfo.samplerate = 44100 ;
  194. sfinfo.format = format ;
  195. if ((sndfile = sf_open (filename, SFM_WRITE, &sfinfo)) == NULL)
  196. { printf ("Line %d : cound not open '%s' : %s\n", __LINE__, filename, sf_strerror (NULL)) ;
  197. exit (1) ;
  198. } ;
  199. sf_command (sndfile, SFC_SET_ADD_PEAK_CHUNK, NULL, SF_FALSE) ;
  200. gen_windowed_sines (freq_count, freqs, 0.9, buffer, ARRAY_LEN (buffer)) ;
  201. if (sf_write_float (sndfile, buffer, ARRAY_LEN (buffer)) != ARRAY_LEN (buffer))
  202. { printf ("Line %d : sf_write_float short write.\n", __LINE__) ;
  203. exit (1) ;
  204. } ;
  205. sf_close (sndfile) ;
  206. } /* generate_source_wav */
  207. static double
  208. measure_destination_wav (char *filename, int *output_samples, int expected_peaks)
  209. { static float buffer [250000] ;
  210. SNDFILE *sndfile ;
  211. SF_INFO sfinfo ;
  212. double snr ;
  213. if ((sndfile = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
  214. { printf ("Line %d : Cound not open '%s' : %s\n", __LINE__, filename, sf_strerror (NULL)) ;
  215. exit (1) ;
  216. } ;
  217. if (sfinfo.channels != 1)
  218. { printf ("Line %d : Bad channel count (%d). Should be 1.\n", __LINE__, sfinfo.channels) ;
  219. exit (1) ;
  220. } ;
  221. if (sfinfo.frames > ARRAY_LEN (buffer))
  222. { printf ("Line %d : Too many frames (%ld) of data in file.\n", __LINE__, (long) sfinfo.frames) ;
  223. exit (1) ;
  224. } ;
  225. *output_samples = (int) sfinfo.frames ;
  226. if (sf_read_float (sndfile, buffer, sfinfo.frames) != sfinfo.frames)
  227. { printf ("Line %d : Bad read.\n", __LINE__) ;
  228. exit (1) ;
  229. } ;
  230. sf_close (sndfile) ;
  231. snr = calculate_snr (buffer, sfinfo.frames, expected_peaks) ;
  232. return snr ;
  233. } /* measure_desination_wav */
  234. static double
  235. measure_snr (const RESAMPLE_PROG *prog, int *output_samples, int verbose)
  236. { static SNR_TEST snr_test [] =
  237. {
  238. { 1, { 0.211111111111 }, 48000, 1, 1.0 },
  239. { 1, { 0.011111111111 }, 132301, 1, 1.0 },
  240. { 1, { 0.111111111111 }, 92301, 1, 1.0 },
  241. { 1, { 0.011111111111 }, 26461, 1, 1.0 },
  242. { 1, { 0.011111111111 }, 13231, 1, 1.0 },
  243. { 1, { 0.011111111111 }, 44101, 1, 1.0 },
  244. { 2, { 0.311111, 0.49 }, 78199, 2, 1.0 },
  245. { 2, { 0.011111, 0.49 }, 12345, 1, 0.5 },
  246. { 2, { 0.0123456, 0.4 }, 20143, 1, 0.5 },
  247. { 2, { 0.0111111, 0.4 }, 26461, 1, 0.5 },
  248. { 1, { 0.381111111111 }, 58661, 1, 1.0 }
  249. } ; /* snr_test */
  250. static char command [256] ;
  251. double snr, worst_snr = 500.0 ;
  252. int k , retval, sample_count ;
  253. *output_samples = 0 ;
  254. for (k = 0 ; k < ARRAY_LEN (snr_test) ; k++)
  255. { remove ("source.wav") ;
  256. remove ("destination.wav") ;
  257. if (verbose)
  258. printf (" SNR test #%d : ", k) ;
  259. fflush (stdout) ;
  260. generate_source_wav ("source.wav", snr_test [k].freqs, snr_test [k].freq_count, prog->format) ;
  261. snprintf (command, sizeof (command), prog->convert_cmd, snr_test [k].output_samplerate) ;
  262. SAFE_STRNCAT (command, " >/dev/null 2>&1", sizeof (command)) ;
  263. if ((retval = system (command)) != 0)
  264. printf ("system returned %d\n", retval) ;
  265. snr = measure_destination_wav ("destination.wav", &sample_count, snr_test->pass_band_peaks) ;
  266. *output_samples += sample_count ;
  267. if (fabs (snr) < fabs (worst_snr))
  268. worst_snr = fabs (snr) ;
  269. if (verbose)
  270. printf ("%6.2f dB\n", snr) ;
  271. } ;
  272. return worst_snr ;
  273. } /* measure_snr */
  274. /*------------------------------------------------------------------------------
  275. */
  276. static double
  277. measure_destination_peak (const char *filename)
  278. { static float data [2 * BUFFER_LEN] ;
  279. SNDFILE *sndfile ;
  280. SF_INFO sfinfo ;
  281. double peak = 0.0 ;
  282. int k = 0 ;
  283. if ((sndfile = sf_open (filename, SFM_READ, &sfinfo)) == NULL)
  284. { printf ("Line %d : failed to open file %s\n", __LINE__, filename) ;
  285. exit (1) ;
  286. } ;
  287. if (sfinfo.channels != 1)
  288. { printf ("Line %d : bad channel count.\n", __LINE__) ;
  289. exit (1) ;
  290. } ;
  291. if (sfinfo.frames > ARRAY_LEN (data) + 4 || sfinfo.frames < ARRAY_LEN (data) - 100)
  292. { printf ("Line %d : bad frame count (got %d, expected %d).\n", __LINE__, (int) sfinfo.frames, ARRAY_LEN (data)) ;
  293. exit (1) ;
  294. } ;
  295. if (sf_read_float (sndfile, data, sfinfo.frames) != sfinfo.frames)
  296. { printf ("Line %d : bad read.\n", __LINE__) ;
  297. exit (1) ;
  298. } ;
  299. sf_close (sndfile) ;
  300. for (k = 0 ; k < (int) sfinfo.frames ; k++)
  301. if (fabs (data [k]) > peak)
  302. peak = fabs (data [k]) ;
  303. return peak ;
  304. } /* measure_destination_peak */
  305. static double
  306. find_attenuation (double freq, const RESAMPLE_PROG *prog, int verbose)
  307. { static char command [256] ;
  308. double output_peak ;
  309. int retval ;
  310. char *filename ;
  311. filename = "destination.wav" ;
  312. generate_source_wav ("source.wav", &freq, 1, prog->format) ;
  313. remove (filename) ;
  314. snprintf (command, sizeof (command), prog->convert_cmd, 88189) ;
  315. SAFE_STRNCAT (command, " >/dev/null 2>&1", sizeof (command)) ;
  316. if ((retval = system (command)) != 0)
  317. printf ("system returned %d\n", retval) ;
  318. output_peak = measure_destination_peak (filename) ;
  319. if (verbose)
  320. printf (" freq : %f peak : %f\n", freq, output_peak) ;
  321. return fabs (20.0 * log10 (output_peak)) ;
  322. } /* find_attenuation */
  323. static double
  324. bandwidth_test (const RESAMPLE_PROG *prog, int verbose)
  325. { double f1, f2, a1, a2 ;
  326. double freq, atten ;
  327. f1 = 0.35 ;
  328. a1 = find_attenuation (f1, prog, verbose) ;
  329. f2 = 0.49999 ;
  330. a2 = find_attenuation (f2, prog, verbose) ;
  331. if (fabs (a1) < 1e-2 && a2 < 3.0)
  332. return -1.0 ;
  333. if (a1 > 3.0 || a2 < 3.0)
  334. { printf ("\n\nLine %d : cannot bracket 3dB point.\n\n", __LINE__) ;
  335. exit (1) ;
  336. } ;
  337. while (a2 - a1 > 1.0)
  338. { freq = f1 + 0.5 * (f2 - f1) ;
  339. atten = find_attenuation (freq, prog, verbose) ;
  340. if (atten < 3.0)
  341. { f1 = freq ;
  342. a1 = atten ;
  343. }
  344. else
  345. { f2 = freq ;
  346. a2 = atten ;
  347. } ;
  348. } ;
  349. freq = f1 + (3.0 - a1) * (f2 - f1) / (a2 - a1) ;
  350. return 200.0 * freq ;
  351. } /* bandwidth_test */
  352. static void
  353. measure_program (const RESAMPLE_PROG *prog, int verbose)
  354. { double snr, bandwidth, conversion_rate ;
  355. int output_samples ;
  356. struct tms time_data ;
  357. time_t time_now ;
  358. printf ("\n Machine : %s\n", get_machine_details ()) ;
  359. time_now = time (NULL) ;
  360. printf (" Date : %s", ctime (&time_now)) ;
  361. get_version_string (prog) ;
  362. printf (" Program : %s\n", version_string) ;
  363. printf (" Command : %s\n\n", prog->convert_cmd) ;
  364. snr = measure_snr (prog, &output_samples, verbose) ;
  365. printf (" Worst case SNR : %6.2f dB\n", snr) ;
  366. times (&time_data) ;
  367. conversion_rate = (1.0 * output_samples * sysconf (_SC_CLK_TCK)) / time_data.tms_cutime ;
  368. printf (" Conversion rate : %5.0f samples/sec\n", conversion_rate) ;
  369. bandwidth = bandwidth_test (prog, verbose) ;
  370. if (bandwidth > 0.0)
  371. printf (" Measured bandwidth : %5.2f %%\n", bandwidth) ;
  372. else
  373. printf (" Could not measure bandwidth (no -3dB point found).\n") ;
  374. return ;
  375. } /* measure_program */
  376. /*##############################################################################
  377. */
  378. #else
  379. int
  380. main (void)
  381. { puts ("\n"
  382. "****************************************************************\n"
  383. " This program has been compiled without :\n"
  384. " 1) FFTW (http://www.fftw.org/).\n"
  385. " 2) libsndfile (http://www.zip.com.au/~erikd/libsndfile/).\n"
  386. " Without these two libraries there is not much it can do.\n"
  387. "****************************************************************\n") ;
  388. return 0 ;
  389. } /* main */
  390. #endif /* (HAVE_FFTW3 && HAVE_SNDFILE) */