• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2000-2004 James Courtier-Dutton
3  * Copyright (C) 2005 Nathan Hurst
4  *
5  * This file is part of the speaker-test tool.
6  *
7  * This small program sends a simple sinusoidal wave to your speakers.
8  *
9  * speaker-test is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * speaker-test is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
22  *
23  *
24  * Main program by James Courtier-Dutton (including some source code fragments from the alsa project.)
25  * Some cleanup from Daniel Caujolle-Bert <segfault@club-internet.fr>
26  * Pink noise option added Nathan Hurst,
27  *   based on generator by Phil Burk (pink.c)
28  *
29  * Changelog:
30  *   0.0.8 Added support for pink noise output.
31  * Changelog:
32  *   0.0.7 Added support for more than 6 channels.
33  * Changelog:
34  *   0.0.6 Added support for different sample formats.
35  *
36  * $Id: speaker_test.c,v 1.00 2003/11/26 19:43:38 jcdutton Exp $
37  */
38 
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <sched.h>
43 #include <errno.h>
44 #include <getopt.h>
45 #include <inttypes.h>
46 #include <ctype.h>
47 #include <byteswap.h>
48 #include <signal.h>
49 
50 #define ALSA_PCM_NEW_HW_PARAMS_API
51 #define ALSA_PCM_NEW_SW_PARAMS_API
52 #include <alsa/asoundlib.h>
53 #include <sys/time.h>
54 #include <math.h>
55 #include "pink.h"
56 #include "aconfig.h"
57 #include "gettext.h"
58 #include "version.h"
59 
60 #ifdef ENABLE_NLS
61 #include <locale.h>
62 #endif
63 
64 #ifdef SND_CHMAP_API_VERSION
65 #define CONFIG_SUPPORT_CHMAP	1
66 #endif
67 
68 enum {
69   TEST_PINK_NOISE = 1,
70   TEST_SINE,
71   TEST_WAV,
72   TEST_PATTERN,
73 };
74 
75 #define MAX_CHANNELS	16
76 
77 #if __BYTE_ORDER == __LITTLE_ENDIAN
78 #define COMPOSE_ID(a,b,c,d)	((a) | ((b)<<8) | ((c)<<16) | ((d)<<24))
79 #define LE_SHORT(v)		(v)
80 #define LE_INT(v)		(v)
81 #define BE_SHORT(v)		bswap_16(v)
82 #define BE_INT(v)		bswap_32(v)
83 #else /* __BIG_ENDIAN */
84 #define COMPOSE_ID(a,b,c,d)	((d) | ((c)<<8) | ((b)<<16) | ((a)<<24))
85 #define LE_SHORT(v)		bswap_16(v)
86 #define LE_INT(v)		bswap_32(v)
87 #define BE_SHORT(v)		(v)
88 #define BE_INT(v)		(v)
89 #endif
90 
91 #define ARRAY_SIZE(x) (int)(sizeof(x)/sizeof(x[0]))
92 
93 static char              *device      = "default";       /* playback device */
94 static snd_pcm_format_t   format      = SND_PCM_FORMAT_S16; /* sample format */
95 static unsigned int       rate        = 48000;	            /* stream rate */
96 static unsigned int       channels    = 1;	            /* count of channels */
97 static unsigned int       speaker     = 0;	            /* count of channels */
98 static unsigned int       buffer_time = 0;	            /* ring buffer length in us */
99 static unsigned int       period_time = 0;	            /* period time in us */
100 static unsigned int       nperiods    = 4;                  /* number of periods */
101 static double             freq        = 440.0;              /* sinusoidal wave frequency in Hz */
102 static int                test_type   = TEST_PINK_NOISE;    /* Test type. 1 = noise, 2 = sine wave */
103 static float              generator_scale  = 0.8;           /* Scale to use for sine volume */
104 static snd_pcm_uframes_t  buffer_size;
105 static snd_pcm_uframes_t  period_size;
106 static const char *given_test_wav_file = NULL;
107 static char *wav_file_dir = SOUNDSDIR;
108 static int debug = 0;
109 static int force_frequency = 0;
110 static int in_aborting = 0;
111 static snd_pcm_t *pcm_handle = NULL;
112 
113 #ifdef CONFIG_SUPPORT_CHMAP
114 static snd_pcm_chmap_t *channel_map;
115 static int channel_map_set;
116 static int *ordered_channels;
117 #endif
118 
119 static const char *const channel_name[MAX_CHANNELS] = {
120   /*  0 */ N_("Front Left"),
121   /*  1 */ N_("Front Right"),
122   /*  2 */ N_("Rear Left"),
123   /*  3 */ N_("Rear Right"),
124   /*  4 */ N_("Center"),
125   /*  5 */ N_("LFE"),
126   /*  6 */ N_("Side Left"),
127   /*  7 */ N_("Side Right"),
128   /*  8 */ N_("Channel 9"),
129   /*  9 */ N_("Channel 10"),
130   /* 10 */ N_("Channel 11"),
131   /* 11 */ N_("Channel 12"),
132   /* 12 */ N_("Channel 13"),
133   /* 13 */ N_("Channel 14"),
134   /* 14 */ N_("Channel 15"),
135   /* 15 */ N_("Channel 16")
136 };
137 
138 static const int	channels4[] = {
139   0, /* Front Left  */
140   1, /* Front Right */
141   3, /* Rear Right  */
142   2, /* Rear Left   */
143 };
144 static const int	channels6[] = {
145   0, /* Front Left  */
146   4, /* Center      */
147   1, /* Front Right */
148   3, /* Rear Right  */
149   2, /* Rear Left   */
150   5, /* LFE         */
151 };
152 static const int	channels8[] = {
153   0, /* Front Left  */
154   4, /* Center      */
155   1, /* Front Right */
156   7, /* Side Right  */
157   3, /* Rear Right  */
158   2, /* Rear Left   */
159   6, /* Side Left   */
160   5, /* LFE         */
161 };
162 
163 #ifdef CONFIG_SUPPORT_CHMAP
164 /* circular clockwise and bottom-to-top order */
165 static const int channel_order[] = {
166   [SND_CHMAP_FLW]  =  10,
167   [SND_CHMAP_FL]   =  20,
168   [SND_CHMAP_TFL]  =  30,
169   [SND_CHMAP_FLC]  =  40,
170   [SND_CHMAP_TFLC] =  50,
171   [SND_CHMAP_FC]   =  60,
172   [SND_CHMAP_TFC]  =  70,
173   [SND_CHMAP_FRC]  =  80,
174   [SND_CHMAP_TFRC] =  90,
175   [SND_CHMAP_FR]   = 100,
176   [SND_CHMAP_TFR]  = 110,
177   [SND_CHMAP_FRW]  = 120,
178   [SND_CHMAP_SR]   = 130,
179   [SND_CHMAP_TSR]  = 140,
180   [SND_CHMAP_RR]   = 150,
181   [SND_CHMAP_TRR]  = 160,
182   [SND_CHMAP_RRC]  = 170,
183   [SND_CHMAP_RC]   = 180,
184   [SND_CHMAP_TRC]  = 190,
185   [SND_CHMAP_RLC]  = 200,
186   [SND_CHMAP_RL]   = 210,
187   [SND_CHMAP_TRL]  = 220,
188   [SND_CHMAP_SL]   = 230,
189   [SND_CHMAP_TSL]  = 240,
190   [SND_CHMAP_BC]   = 250,
191   [SND_CHMAP_TC]   = 260,
192   [SND_CHMAP_LLFE] = 270,
193   [SND_CHMAP_LFE]  = 280,
194   [SND_CHMAP_RLFE] = 290,
195   /* not in table  = 10000 */
196   [SND_CHMAP_UNKNOWN] = 20000,
197   [SND_CHMAP_NA]      = 30000,
198 };
199 
chpos_cmp(const void * chnum1p,const void * chnum2p)200 static int chpos_cmp(const void *chnum1p, const void *chnum2p)
201 {
202   int chnum1 = *(int *)chnum1p;
203   int chnum2 = *(int *)chnum2p;
204   int chpos1 = channel_map->pos[chnum1];
205   int chpos2 = channel_map->pos[chnum2];
206   int weight1 = 10000;
207   int weight2 = 10000;
208 
209   if (chpos1 < ARRAY_SIZE(channel_order) && channel_order[chpos1])
210     weight1 = channel_order[chpos1];
211   if (chpos2 < ARRAY_SIZE(channel_order) && channel_order[chpos2])
212     weight2 = channel_order[chpos2];
213 
214   if (weight1 == weight2) {
215     /* order by channel number if both have the same position (e.g. UNKNOWN)
216      * or if neither is in channel_order[] */
217     return chnum1 - chnum2;
218   }
219 
220   /* order according to channel_order[] */
221   return weight1 - weight2;
222 }
223 
order_channels(void)224 static int *order_channels(void)
225 {
226   /* create a (playback order => channel number) table with channels ordered
227    * according to channel_order[] values */
228   int i;
229   int *ordered_chs;
230 
231   ordered_chs = calloc(channel_map->channels, sizeof(*ordered_chs));
232   if (!ordered_chs)
233     return NULL;
234 
235   for (i = 0; i < channel_map->channels; i++)
236     ordered_chs[i] = i;
237 
238   qsort(ordered_chs, channel_map->channels, sizeof(*ordered_chs), chpos_cmp);
239 
240   return ordered_chs;
241 }
242 #endif
243 
get_speaker_channel(int chn)244 static int get_speaker_channel(int chn)
245 {
246 #ifdef CONFIG_SUPPORT_CHMAP
247   if (channel_map_set || (ordered_channels && chn >= channel_map->channels))
248     return chn;
249   if (ordered_channels)
250     return ordered_channels[chn];
251 #endif
252 
253   switch (channels) {
254   case 4:
255     chn = channels4[chn];
256     break;
257   case 6:
258     chn = channels6[chn];
259     break;
260   case 8:
261     chn = channels8[chn];
262     break;
263   }
264 
265   return chn;
266 }
267 
get_channel_name(int chn)268 static const char *get_channel_name(int chn)
269 {
270 #ifdef CONFIG_SUPPORT_CHMAP
271   if (channel_map) {
272     const char *name = NULL;
273     if (chn < channel_map->channels)
274       name = snd_pcm_chmap_long_name(channel_map->pos[chn]);
275     return name ? name : "Unknown";
276   }
277 #endif
278   return gettext(channel_name[chn]);
279 }
280 
281 static const int	supported_formats[] = {
282   SND_PCM_FORMAT_S8,
283   SND_PCM_FORMAT_S16_LE,
284   SND_PCM_FORMAT_S16_BE,
285   SND_PCM_FORMAT_FLOAT_LE,
286   SND_PCM_FORMAT_S24_3LE,
287   SND_PCM_FORMAT_S24_3BE,
288   SND_PCM_FORMAT_S24_LE,
289   SND_PCM_FORMAT_S24_BE,
290   SND_PCM_FORMAT_S32_LE,
291   SND_PCM_FORMAT_S32_BE,
292   -1
293 };
294 
295 typedef union {
296   float f;
297   int32_t i;
298 } value_t;
299 
do_generate(uint8_t * frames,int channel,int count,value_t (* generate)(void *),void * arg)300 static void do_generate(uint8_t *frames, int channel, int count,
301 			value_t (*generate)(void *), void *arg)
302 {
303   value_t res;
304   int    chn;
305   int8_t *samp8 = (int8_t*) frames;
306   int16_t *samp16 = (int16_t*) frames;
307   int32_t *samp32 = (int32_t*) frames;
308   float   *samp_f = (float*) frames;
309 
310   while (count-- > 0) {
311     for(chn=0;chn<channels;chn++) {
312       if (chn==channel) {
313 	res = generate(arg);
314       } else {
315 	res.i = 0;
316       }
317 
318       switch (format) {
319       case SND_PCM_FORMAT_S8:
320 	*samp8++ = res.i >> 24;
321         break;
322       case SND_PCM_FORMAT_S16_LE:
323 	*samp16++ = LE_SHORT(res.i >> 16);
324         break;
325       case SND_PCM_FORMAT_S16_BE:
326 	*samp16++ = BE_SHORT(res.i >> 16);
327         break;
328       case SND_PCM_FORMAT_FLOAT_LE:
329 	*samp_f++ = res.f;
330         break;
331       case SND_PCM_FORMAT_S24_3LE:
332         res.i >>= 8;
333         *samp8++ = LE_INT(res.i);
334         *samp8++ = LE_INT(res.i) >> 8;
335         *samp8++ = LE_INT(res.i) >> 16;
336         break;
337       case SND_PCM_FORMAT_S24_3BE:
338         res.i >>= 8;
339         *samp8++ = BE_INT(res.i);
340         *samp8++ = BE_INT(res.i) >> 8;
341         *samp8++ = BE_INT(res.i) >> 16;
342         break;
343       case SND_PCM_FORMAT_S24_LE:
344         res.i >>= 8;
345         *samp8++ = LE_INT(res.i);
346         *samp8++ = LE_INT(res.i) >> 8;
347         *samp8++ = LE_INT(res.i) >> 16;
348         *samp8++ = 0;
349         break;
350       case SND_PCM_FORMAT_S24_BE:
351         res.i >>= 8;
352         *samp8++ = 0;
353         *samp8++ = BE_INT(res.i);
354         *samp8++ = BE_INT(res.i) >> 8;
355         *samp8++ = BE_INT(res.i) >> 16;
356         break;
357       case SND_PCM_FORMAT_S32_LE:
358 	*samp32++ = LE_INT(res.i);
359         break;
360       case SND_PCM_FORMAT_S32_BE:
361 	*samp32++ = BE_INT(res.i);
362         break;
363       default:
364         ;
365       }
366     }
367   }
368 }
369 
370 /*
371  * Sine generator
372  */
373 typedef struct {
374   double phase;
375   double max_phase;
376   double step;
377 } sine_t;
378 
init_sine(sine_t * sine)379 static void init_sine(sine_t *sine)
380 {
381   sine->phase = 0;
382   sine->max_phase = 1.0 / freq;
383   sine->step = 1.0 / (double)rate;
384 }
385 
generate_sine(void * arg)386 static value_t generate_sine(void *arg)
387 {
388   sine_t *sine = arg;
389   value_t res;
390 
391   res.f = sin((sine->phase * 2 * M_PI) / sine->max_phase - M_PI);
392   res.f *= generator_scale;
393   if (format != SND_PCM_FORMAT_FLOAT_LE)
394     res.i = res.f * INT32_MAX;
395   sine->phase += sine->step;
396   if (sine->phase >= sine->max_phase)
397     sine->phase -= sine->max_phase;
398   return res;
399 }
400 
401 /* Pink noise is a better test than sine wave because we can tell
402  * where pink noise is coming from more easily that a sine wave.
403  */
generate_pink_noise(void * arg)404 static value_t generate_pink_noise(void *arg)
405 {
406   pink_noise_t *pink = arg;
407   value_t res;
408 
409   res.f = generate_pink_noise_sample(pink) * generator_scale;
410   if (format != SND_PCM_FORMAT_FLOAT_LE)
411     res.i = res.f * INT32_MAX;
412   return res;
413 }
414 
415 /*
416  * useful for tests
417  */
generate_pattern(void * arg)418 static value_t generate_pattern(void *arg)
419 {
420   value_t res;
421 
422   res.i = *(int *)arg;
423   *(int *)arg = res.i + 1;
424   if (format != SND_PCM_FORMAT_FLOAT_LE)
425     res.f = (float)res.i / (float)INT32_MAX;
426   return res;
427 }
428 
set_hwparams(snd_pcm_t * handle,snd_pcm_hw_params_t * params,snd_pcm_access_t access)429 static int set_hwparams(snd_pcm_t *handle, snd_pcm_hw_params_t *params, snd_pcm_access_t access) {
430   unsigned int rrate;
431   int          err;
432   snd_pcm_uframes_t     period_size_min;
433   snd_pcm_uframes_t     period_size_max;
434   snd_pcm_uframes_t     buffer_size_min;
435   snd_pcm_uframes_t     buffer_size_max;
436 
437   /* choose all parameters */
438   err = snd_pcm_hw_params_any(handle, params);
439   if (err < 0) {
440     fprintf(stderr, _("Broken configuration for playback: no configurations available: %s\n"), snd_strerror(err));
441     return err;
442   }
443 
444   /* set the interleaved read/write format */
445   err = snd_pcm_hw_params_set_access(handle, params, access);
446   if (err < 0) {
447     fprintf(stderr, _("Access type not available for playback: %s\n"), snd_strerror(err));
448     return err;
449   }
450 
451   /* set the sample format */
452   err = snd_pcm_hw_params_set_format(handle, params, format);
453   if (err < 0) {
454     fprintf(stderr, _("Sample format not available for playback: %s\n"), snd_strerror(err));
455     return err;
456   }
457 
458   /* set the count of channels */
459   err = snd_pcm_hw_params_set_channels(handle, params, channels);
460   if (err < 0) {
461     fprintf(stderr, _("Channels count (%i) not available for playbacks: %s\n"), channels, snd_strerror(err));
462     return err;
463   }
464 
465   /* set the stream rate */
466   rrate = rate;
467   err = snd_pcm_hw_params_set_rate(handle, params, rate, 0);
468   if (err < 0) {
469     fprintf(stderr, _("Rate %iHz not available for playback: %s\n"), rate, snd_strerror(err));
470     return err;
471   }
472 
473   if (rrate != rate) {
474     fprintf(stderr, _("Rate doesn't match (requested %iHz, get %iHz, err %d)\n"), rate, rrate, err);
475     return -EINVAL;
476   }
477 
478   printf(_("Rate set to %iHz (requested %iHz)\n"), rrate, rate);
479   /* set the buffer time */
480   err = snd_pcm_hw_params_get_buffer_size_min(params, &buffer_size_min);
481   err = snd_pcm_hw_params_get_buffer_size_max(params, &buffer_size_max);
482   err = snd_pcm_hw_params_get_period_size_min(params, &period_size_min, NULL);
483   err = snd_pcm_hw_params_get_period_size_max(params, &period_size_max, NULL);
484   printf(_("Buffer size range from %lu to %lu\n"),buffer_size_min, buffer_size_max);
485   printf(_("Period size range from %lu to %lu\n"),period_size_min, period_size_max);
486   if (period_time > 0) {
487     printf(_("Requested period time %u us\n"), period_time);
488     err = snd_pcm_hw_params_set_period_time_near(handle, params, &period_time, NULL);
489     if (err < 0) {
490       fprintf(stderr, _("Unable to set period time %u us for playback: %s\n"),
491 	     period_time, snd_strerror(err));
492       return err;
493     }
494   }
495   if (buffer_time > 0) {
496     printf(_("Requested buffer time %u us\n"), buffer_time);
497     err = snd_pcm_hw_params_set_buffer_time_near(handle, params, &buffer_time, NULL);
498     if (err < 0) {
499       fprintf(stderr, _("Unable to set buffer time %u us for playback: %s\n"),
500 	     buffer_time, snd_strerror(err));
501       return err;
502     }
503   }
504   if (! buffer_time && ! period_time) {
505     buffer_size = buffer_size_max;
506     if (! period_time)
507       buffer_size = (buffer_size / nperiods) * nperiods;
508     printf(_("Using max buffer size %lu\n"), buffer_size);
509     err = snd_pcm_hw_params_set_buffer_size_near(handle, params, &buffer_size);
510     if (err < 0) {
511       fprintf(stderr, _("Unable to set buffer size %lu for playback: %s\n"),
512 	     buffer_size, snd_strerror(err));
513       return err;
514     }
515   }
516   if (! buffer_time || ! period_time) {
517     printf(_("Periods = %u\n"), nperiods);
518     err = snd_pcm_hw_params_set_periods_near(handle, params, &nperiods, NULL);
519     if (err < 0) {
520       fprintf(stderr, _("Unable to set nperiods %u for playback: %s\n"),
521 	     nperiods, snd_strerror(err));
522       return err;
523     }
524   }
525 
526   /* write the parameters to device */
527   err = snd_pcm_hw_params(handle, params);
528   if (err < 0) {
529     fprintf(stderr, _("Unable to set hw params for playback: %s\n"), snd_strerror(err));
530     return err;
531   }
532 
533   snd_pcm_hw_params_get_buffer_size(params, &buffer_size);
534   snd_pcm_hw_params_get_period_size(params, &period_size, NULL);
535   printf(_("was set period_size = %lu\n"),period_size);
536   printf(_("was set buffer_size = %lu\n"),buffer_size);
537   if (2*period_size > buffer_size) {
538     fprintf(stderr, _("buffer to small, could not use\n"));
539     return -EINVAL;
540   }
541 
542   return 0;
543 }
544 
set_swparams(snd_pcm_t * handle,snd_pcm_sw_params_t * swparams)545 static int set_swparams(snd_pcm_t *handle, snd_pcm_sw_params_t *swparams) {
546   int err;
547 
548   /* get the current swparams */
549   err = snd_pcm_sw_params_current(handle, swparams);
550   if (err < 0) {
551     fprintf(stderr, _("Unable to determine current swparams for playback: %s\n"), snd_strerror(err));
552     return err;
553   }
554 
555   /* start the transfer when a buffer is full */
556   err = snd_pcm_sw_params_set_start_threshold(handle, swparams, buffer_size);
557   if (err < 0) {
558     fprintf(stderr, _("Unable to set start threshold mode for playback: %s\n"), snd_strerror(err));
559     return err;
560   }
561 
562   /* allow the transfer when at least period_size frames can be processed */
563   err = snd_pcm_sw_params_set_avail_min(handle, swparams, period_size);
564   if (err < 0) {
565     fprintf(stderr, _("Unable to set avail min for playback: %s\n"), snd_strerror(err));
566     return err;
567   }
568 
569   /* write the parameters to the playback device */
570   err = snd_pcm_sw_params(handle, swparams);
571   if (err < 0) {
572     fprintf(stderr, _("Unable to set sw params for playback: %s\n"), snd_strerror(err));
573     return err;
574   }
575 
576   return 0;
577 }
578 
579 #ifdef CONFIG_SUPPORT_CHMAP
config_chmap(snd_pcm_t * handle,const char * mapstr)580 static int config_chmap(snd_pcm_t *handle, const char *mapstr)
581 {
582   int err;
583 
584   if (mapstr) {
585     channel_map = snd_pcm_chmap_parse_string(mapstr);
586     if (!channel_map) {
587       fprintf(stderr, _("Unable to parse channel map string: %s\n"), mapstr);
588       return -EINVAL;
589     }
590     err = snd_pcm_set_chmap(handle, channel_map);
591     if (err < 0) {
592       fprintf(stderr, _("Unable to set channel map: %s\n"), mapstr);
593       return err;
594     }
595     channel_map_set = 1;
596     return 0;
597   }
598 
599   channel_map = snd_pcm_get_chmap(handle);
600 
601   /* create a channel order table for default layouts */
602   if (channel_map)
603     ordered_channels = order_channels();
604 
605   return 0;
606 }
607 #endif
608 
609 /*
610  *   Underrun and suspend recovery
611  */
612 
xrun_recovery(snd_pcm_t * handle,int err)613 static int xrun_recovery(snd_pcm_t *handle, int err) {
614   if (err == -EPIPE) {	/* under-run */
615     err = snd_pcm_prepare(handle);
616     if (err < 0)
617       fprintf(stderr, _("Can't recovery from underrun, prepare failed: %s\n"), snd_strerror(err));
618     return 0;
619   }
620   else if (err == -ESTRPIPE) {
621 
622     while ((err = snd_pcm_resume(handle)) == -EAGAIN)
623       sleep(1);	/* wait until the suspend flag is released */
624 
625     if (err < 0) {
626       err = snd_pcm_prepare(handle);
627       if (err < 0)
628         fprintf(stderr, _("Can't recovery from suspend, prepare failed: %s\n"), snd_strerror(err));
629     }
630 
631     return 0;
632   }
633 
634   return err;
635 }
636 
637 /*
638  * Handle WAV files
639  */
640 
641 static const char *wav_file[MAX_CHANNELS];
642 static int wav_file_size[MAX_CHANNELS];
643 
644 struct wave_header {
645   struct {
646     uint32_t magic;
647     uint32_t length;
648     uint32_t type;
649   } hdr;
650   struct {
651     uint32_t type;
652     uint32_t length;
653   } chunk1;
654   struct {
655     uint16_t format;
656     uint16_t channels;
657     uint32_t rate;
658     uint32_t bytes_per_sec;
659     uint16_t sample_size;
660     uint16_t sample_bits;
661   } body;
662   struct {
663     uint32_t type;
664     uint32_t length;
665   } chunk;
666 };
667 
668 #define WAV_RIFF		COMPOSE_ID('R','I','F','F')
669 #define WAV_WAVE		COMPOSE_ID('W','A','V','E')
670 #define WAV_FMT			COMPOSE_ID('f','m','t',' ')
671 #define WAV_DATA		COMPOSE_ID('d','a','t','a')
672 #define WAV_PCM_CODE		1
673 
search_for_file(const char * name)674 static const char *search_for_file(const char *name)
675 {
676   char *file;
677   if (*name == '/')
678     return strdup(name);
679   file = malloc(strlen(wav_file_dir) + strlen(name) + 2);
680   if (file)
681     sprintf(file, "%s/%s", wav_file_dir, name);
682   return file;
683 }
684 
check_wav_file(int channel,const char * name)685 static int check_wav_file(int channel, const char *name)
686 {
687   struct wave_header header;
688   int fd;
689 
690   wav_file[channel] = search_for_file(name);
691   if (! wav_file[channel]) {
692     fprintf(stderr, _("No enough memory\n"));
693     return -ENOMEM;
694   }
695 
696   if ((fd = open(wav_file[channel], O_RDONLY)) < 0) {
697     fprintf(stderr, _("Cannot open WAV file %s\n"), wav_file[channel]);
698     return -EINVAL;
699   }
700   if (read(fd, &header, sizeof(header)) < (int)sizeof(header)) {
701     fprintf(stderr, _("Invalid WAV file %s\n"), wav_file[channel]);
702     goto error;
703   }
704 
705   if (header.hdr.magic != WAV_RIFF || header.hdr.type != WAV_WAVE) {
706     fprintf(stderr, _("Not a WAV file: %s\n"), wav_file[channel]);
707     goto error;
708   }
709   if (header.body.format != LE_SHORT(WAV_PCM_CODE)) {
710     fprintf(stderr, _("Unsupported WAV format %d for %s\n"),
711 	    LE_SHORT(header.body.format), wav_file[channel]);
712     goto error;
713   }
714   if (header.body.channels != LE_SHORT(1)) {
715     fprintf(stderr, _("%s is not a mono stream (%d channels)\n"),
716 	    wav_file[channel], LE_SHORT(header.body.channels));
717     goto error;
718   }
719   if (header.body.rate != LE_INT(rate)) {
720     fprintf(stderr, _("Sample rate doesn't match (%d) for %s\n"),
721 	    LE_INT(header.body.rate), wav_file[channel]);
722     goto error;
723   }
724   if (header.body.sample_bits != LE_SHORT(16)) {
725     fprintf(stderr, _("Unsupported sample format bits %d for %s\n"),
726 	    LE_SHORT(header.body.sample_bits), wav_file[channel]);
727     goto error;
728   }
729   if (header.chunk.type != WAV_DATA) {
730     fprintf(stderr, _("Invalid WAV file %s\n"), wav_file[channel]);
731     goto error;
732   }
733   wav_file_size[channel] = LE_INT(header.chunk.length);
734   close(fd);
735   return 0;
736 
737  error:
738   close(fd);
739   return -EINVAL;
740 }
741 
setup_wav_file(int chn)742 static int setup_wav_file(int chn)
743 {
744   static const char *const wavs[MAX_CHANNELS] = {
745     "Front_Left.wav",
746     "Front_Right.wav",
747     "Rear_Left.wav",
748     "Rear_Right.wav",
749     "Front_Center.wav",
750     "Rear_Center.wav", /* FIXME: should be "Bass" or so */
751     "Side_Left.wav",
752     "Side_Right.wav",
753     "Channel_9.wav",
754     "Channel_10.wav",
755     "Channel_11.wav",
756     "Channel_12.wav",
757     "Channel_13.wav",
758     "Channel_14.wav",
759     "Channel_15.wav",
760     "Channel_16.wav"
761   };
762 
763   if (given_test_wav_file)
764     return check_wav_file(chn, given_test_wav_file);
765 
766 #ifdef CONFIG_SUPPORT_CHMAP
767   if (channel_map && chn < channel_map->channels) {
768     int channel = channel_map->pos[chn] - SND_CHMAP_FL;
769     if (channel >= 0 && channel < MAX_CHANNELS)
770       return check_wav_file(chn, wavs[channel]);
771   }
772 #endif
773 
774   return check_wav_file(chn, wavs[chn]);
775 }
776 
read_wav(uint16_t * buf,int channel,int offset,int bufsize)777 static int read_wav(uint16_t *buf, int channel, int offset, int bufsize)
778 {
779   static FILE *wavfp = NULL;
780   int size;
781 
782   if (in_aborting)
783     return -EFAULT;
784 
785   if (! wav_file[channel]) {
786     fprintf(stderr, _("Undefined channel %d\n"), channel);
787     return -EINVAL;
788   }
789 
790   if (offset >= wav_file_size[channel])
791    return 0; /* finished */
792 
793   if (! offset) {
794     if (wavfp)
795       fclose(wavfp);
796     wavfp = fopen(wav_file[channel], "r");
797     if (! wavfp)
798       return -errno;
799     if (fseek(wavfp, sizeof(struct wave_header), SEEK_SET) < 0)
800       return -errno;
801   }
802   if (offset + bufsize > wav_file_size[channel])
803     bufsize = wav_file_size[channel] - offset;
804   bufsize /= channels;
805   for (size = 0; size < bufsize; size += 2) {
806     int chn;
807     for (chn = 0; chn < channels; chn++) {
808       if (chn == channel) {
809 	if (fread(buf, 2, 1, wavfp) != 1)
810 	  return size;
811       }
812       else
813 	*buf = 0;
814       buf++;
815     }
816   }
817   return size;
818 }
819 
820 
821 /*
822  *   Transfer method - write only
823  */
824 
write_buffer(snd_pcm_t * handle,uint8_t * ptr,int cptr)825 static int write_buffer(snd_pcm_t *handle, uint8_t *ptr, int cptr)
826 {
827   int err;
828 
829   while (cptr > 0 && !in_aborting) {
830 
831     err = snd_pcm_writei(handle, ptr, cptr);
832 
833     if (err == -EAGAIN)
834       continue;
835 
836     if (err < 0) {
837       fprintf(stderr, _("Write error: %d,%s\n"), err, snd_strerror(err));
838       if ((err = xrun_recovery(handle, err)) < 0) {
839 	fprintf(stderr, _("xrun_recovery failed: %d,%s\n"), err, snd_strerror(err));
840 	return err;
841       }
842       break;	/* skip one period */
843     }
844 
845     ptr += snd_pcm_frames_to_bytes(handle, err);
846     cptr -= err;
847   }
848   return 0;
849 }
850 
851 static int pattern;
852 static sine_t sine;
853 static pink_noise_t pink;
854 
init_loop(void)855 static void init_loop(void)
856 {
857   switch (test_type) {
858   case TEST_PINK_NOISE:
859     initialize_pink_noise(&pink, 16);
860     break;
861   case TEST_SINE:
862     init_sine(&sine);
863     break;
864   case TEST_PATTERN:
865     pattern = 0;
866     break;
867   }
868 }
869 
write_loop(snd_pcm_t * handle,int channel,int periods,uint8_t * frames)870 static int write_loop(snd_pcm_t *handle, int channel, int periods, uint8_t *frames)
871 {
872   int    err, n;
873 
874   fflush(stdout);
875   if (test_type == TEST_WAV) {
876     int bufsize = snd_pcm_frames_to_bytes(handle, period_size);
877     n = 0;
878     while ((err = read_wav((uint16_t *)frames, channel, n, bufsize)) > 0 && !in_aborting) {
879       n += err;
880       if ((err = write_buffer(handle, frames,
881 			      snd_pcm_bytes_to_frames(handle, err * channels))) < 0)
882 	break;
883     }
884     if (buffer_size > n && !in_aborting) {
885       snd_pcm_drain(handle);
886       snd_pcm_prepare(handle);
887     }
888     return err;
889   }
890 
891 
892   if (periods <= 0)
893     periods = 1;
894 
895   for(n = 0; n < periods && !in_aborting; n++) {
896     if (test_type == TEST_PINK_NOISE)
897       do_generate(frames, channel, period_size, generate_pink_noise, &pink);
898     else if (test_type == TEST_PATTERN)
899       do_generate(frames, channel, period_size, generate_pattern, &pattern);
900     else
901       do_generate(frames, channel, period_size, generate_sine, &sine);
902 
903     if ((err = write_buffer(handle, frames, period_size)) < 0)
904       return err;
905   }
906   if (buffer_size > n * period_size && !in_aborting) {
907     snd_pcm_drain(handle);
908     snd_pcm_prepare(handle);
909   }
910   return 0;
911 }
912 
prg_exit(int code)913 static int prg_exit(int code)
914 {
915   if (pcm_handle)
916     snd_pcm_close(pcm_handle);
917   exit(code);
918   return code;
919 }
920 
signal_handler(int sig)921 static void signal_handler(int sig)
922 {
923   if (in_aborting)
924     return;
925 
926   in_aborting = 1;
927 
928   if (pcm_handle)
929     snd_pcm_abort(pcm_handle);
930   if (sig == SIGABRT) {
931     pcm_handle = NULL;
932     prg_exit(EXIT_FAILURE);
933   }
934   signal(sig, signal_handler);
935 }
936 
help(void)937 static void help(void)
938 {
939   const int *fmt;
940 
941   printf(
942 	 _("Usage: speaker-test [OPTION]... \n"
943 	   "-h,--help	help\n"
944 	   "-D,--device	playback device\n"
945 	   "-r,--rate	stream rate in Hz\n"
946 	   "-c,--channels	count of channels in stream\n"
947 	   "-f,--frequency	sine wave frequency in Hz\n"
948 	   "-F,--format	sample format\n"
949 	   "-b,--buffer	ring buffer size in us\n"
950 	   "-p,--period	period size in us\n"
951 	   "-P,--nperiods	number of periods\n"
952 	   "-t,--test	pink=use pink noise, sine=use sine wave, wav=WAV file\n"
953 	   "-l,--nloops	specify number of loops to test, 0 = infinite\n"
954 	   "-s,--speaker	single speaker test. Values 1=Left, 2=right, etc\n"
955 	   "-w,--wavfile	Use the given WAV file as a test sound\n"
956 	   "-W,--wavdir	Specify the directory containing WAV files\n"
957 	   "-m,--chmap	Specify the channel map to override\n"
958 	   "-X,--force-frequency	force frequencies outside the 30-8000hz range\n"
959 	   "-S,--scale	Scale of generated test tones in percent (default=80)\n"
960 	   "\n"));
961   printf(_("Recognized sample formats are:"));
962   for (fmt = supported_formats; *fmt >= 0; fmt++) {
963     const char *s = snd_pcm_format_name(*fmt);
964     if (s)
965       printf(" %s", s);
966   }
967 
968   printf("\n\n");
969 }
970 
main(int argc,char * argv[])971 int main(int argc, char *argv[]) {
972   snd_pcm_t            *handle;
973   int                   err, morehelp;
974   snd_pcm_hw_params_t  *hwparams;
975   snd_pcm_sw_params_t  *swparams;
976   uint8_t              *frames;
977   int                   chn;
978   const int	       *fmt;
979   double		time1,time2,time3;
980   unsigned int		n, nloops;
981   struct   timeval	tv1,tv2;
982   int			speakeroptset = 0;
983 #ifdef CONFIG_SUPPORT_CHMAP
984   const char *chmap = NULL;
985 #endif
986 
987   static const struct option long_option[] = {
988     {"help",      0, NULL, 'h'},
989     {"device",    1, NULL, 'D'},
990     {"rate",      1, NULL, 'r'},
991     {"channels",  1, NULL, 'c'},
992     {"frequency", 1, NULL, 'f'},
993     {"format",    1, NULL, 'F'},
994     {"buffer",    1, NULL, 'b'},
995     {"period",    1, NULL, 'p'},
996     {"nperiods",  1, NULL, 'P'},
997     {"test",      1, NULL, 't'},
998     {"nloops",    1, NULL, 'l'},
999     {"speaker",   1, NULL, 's'},
1000     {"wavfile",   1, NULL, 'w'},
1001     {"wavdir",    1, NULL, 'W'},
1002     {"debug",	  0, NULL, 'd'},
1003     {"force-frequency",	  0, NULL, 'X'},
1004     {"scale",	  1, NULL, 'S'},
1005 #ifdef CONFIG_SUPPORT_CHMAP
1006     {"chmap",	  1, NULL, 'm'},
1007 #endif
1008     {NULL,        0, NULL, 0  },
1009   };
1010 
1011 #ifdef ENABLE_NLS
1012   setlocale(LC_ALL, "");
1013   textdomain(PACKAGE);
1014 #endif
1015 
1016   snd_pcm_hw_params_alloca(&hwparams);
1017   snd_pcm_sw_params_alloca(&swparams);
1018 
1019   nloops = 0;
1020   morehelp = 0;
1021 
1022   printf("\nspeaker-test %s\n\n", SND_UTIL_VERSION_STR);
1023   while (1) {
1024     int c;
1025 
1026     if ((c = getopt_long(argc, argv, "hD:r:c:f:F:b:p:P:t:l:s:w:W:d:XS:"
1027 #ifdef CONFIG_SUPPORT_CHMAP
1028 			 "m:"
1029 #endif
1030 			 , long_option, NULL)) < 0)
1031       break;
1032 
1033     switch (c) {
1034     case 'h':
1035       morehelp++;
1036       break;
1037     case 'D':
1038       device = strdup(optarg);
1039       break;
1040     case 'F':
1041       format = snd_pcm_format_value(optarg);
1042       for (fmt = supported_formats; *fmt >= 0; fmt++)
1043         if (*fmt == format)
1044           break;
1045       if (*fmt < 0) {
1046         fprintf(stderr, "Format %s is not supported...\n", snd_pcm_format_name(format));
1047         exit(EXIT_FAILURE);
1048       }
1049       break;
1050     case 'r':
1051       rate = atoi(optarg);
1052       rate = rate < 4000 ? 4000 : rate;
1053       rate = rate > 768000 ? 768000 : rate;
1054       break;
1055     case 'c':
1056       channels = atoi(optarg);
1057       channels = channels < 1 ? 1 : channels;
1058       channels = channels > 1024 ? 1024 : channels;
1059       break;
1060     case 'f':
1061       freq = atof(optarg);
1062       break;
1063     case 'b':
1064       buffer_time = atoi(optarg);
1065       buffer_time = buffer_time > 1000000 ? 1000000 : buffer_time;
1066       break;
1067     case 'p':
1068       period_time = atoi(optarg);
1069       period_time = period_time > 1000000 ? 1000000 : period_time;
1070       break;
1071     case 'P':
1072       nperiods = atoi(optarg);
1073       if (nperiods < 2 || nperiods > 1024) {
1074 	fprintf(stderr, _("Invalid number of periods %d\n"), nperiods);
1075 	exit(1);
1076       }
1077       break;
1078     case 't':
1079       if (*optarg == 'p')
1080 	test_type = TEST_PINK_NOISE;
1081       else if (*optarg == 's')
1082 	test_type = TEST_SINE;
1083       else if (*optarg == 'w')
1084 	test_type = TEST_WAV;
1085       else if (*optarg == 't')
1086 	test_type = TEST_PATTERN;
1087       else if (isdigit(*optarg)) {
1088 	test_type = atoi(optarg);
1089 	if (test_type < TEST_PINK_NOISE || test_type > TEST_PATTERN) {
1090 	  fprintf(stderr, _("Invalid test type %s\n"), optarg);
1091 	  exit(1);
1092 	}
1093       } else {
1094 	fprintf(stderr, _("Invalid test type %s\n"), optarg);
1095 	exit(1);
1096       }
1097       break;
1098     case 'l':
1099       nloops = atoi(optarg);
1100       break;
1101     case 's':
1102       speaker = atoi(optarg);
1103       speaker = speaker < 1 ? 0 : speaker;
1104       speakeroptset = 1;
1105       break;
1106     case 'w':
1107       given_test_wav_file = optarg;
1108       break;
1109     case 'W':
1110       wav_file_dir = optarg;
1111       break;
1112     case 'd':
1113       debug = 1;
1114       break;
1115     case 'X':
1116       force_frequency = 1;
1117       break;
1118 #ifdef CONFIG_SUPPORT_CHMAP
1119     case 'm':
1120       chmap = optarg;
1121       break;
1122 #endif
1123     case 'S':
1124       generator_scale = atoi(optarg) / 100.0;
1125       break;
1126     default:
1127       fprintf(stderr, _("Unknown option '%c'\n"), c);
1128       exit(EXIT_FAILURE);
1129       break;
1130     }
1131   }
1132 
1133   if (morehelp) {
1134     help();
1135     exit(EXIT_SUCCESS);
1136   }
1137 
1138   if (speakeroptset) {
1139     speaker = speaker > channels ? 0 : speaker;
1140     if (speaker==0) {
1141       fprintf(stderr, _("Invalid parameter for -s option.\n"));
1142       exit(EXIT_FAILURE);
1143     }
1144   }
1145 
1146   if (!force_frequency) {
1147     freq = freq < 30.0 ? 30.0 : freq;
1148     freq = freq > 8000.0 ? 8000.0 : freq;
1149   } else {
1150     freq = freq < 1.0 ? 1.0 : freq;
1151   }
1152 
1153   if (test_type == TEST_WAV)
1154     format = SND_PCM_FORMAT_S16_LE; /* fixed format */
1155 
1156   printf(_("Playback device is %s\n"), device);
1157   printf(_("Stream parameters are %iHz, %s, %i channels\n"), rate, snd_pcm_format_name(format), channels);
1158   switch (test_type) {
1159   case TEST_PINK_NOISE:
1160     printf(_("Using 16 octaves of pink noise\n"));
1161     break;
1162   case TEST_SINE:
1163     printf(_("Sine wave rate is %.4fHz\n"), freq);
1164     break;
1165   case TEST_WAV:
1166     printf(_("WAV file(s)\n"));
1167     break;
1168 
1169   }
1170 
1171   signal(SIGINT, signal_handler);
1172   signal(SIGTERM, signal_handler);
1173   signal(SIGABRT, signal_handler);
1174 
1175   if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
1176     printf(_("Playback open error: %d,%s\n"), err,snd_strerror(err));
1177     prg_exit(EXIT_FAILURE);
1178   }
1179   pcm_handle = handle;
1180 
1181   if ((err = set_hwparams(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
1182     printf(_("Setting of hwparams failed: %s\n"), snd_strerror(err));
1183     prg_exit(EXIT_FAILURE);
1184   }
1185   if ((err = set_swparams(handle, swparams)) < 0) {
1186     printf(_("Setting of swparams failed: %s\n"), snd_strerror(err));
1187     prg_exit(EXIT_FAILURE);
1188   }
1189 
1190 #ifdef CONFIG_SUPPORT_CHMAP
1191   err = config_chmap(handle, chmap);
1192   if (err < 0)
1193     prg_exit(EXIT_FAILURE);
1194 #endif
1195 
1196   if (debug) {
1197     snd_output_t *log;
1198     err = snd_output_stdio_attach(&log, stderr, 0);
1199     if (err >= 0) {
1200       snd_pcm_dump(handle, log);
1201       snd_output_close(log);
1202     }
1203   }
1204 
1205   frames = malloc(snd_pcm_frames_to_bytes(handle, period_size));
1206   if (frames == NULL) {
1207     fprintf(stderr, _("No enough memory\n"));
1208     prg_exit(EXIT_FAILURE);
1209   }
1210 
1211   init_loop();
1212 
1213   if (speaker==0) {
1214 
1215     if (test_type == TEST_WAV) {
1216       for (chn = 0; chn < channels; chn++) {
1217 	if (setup_wav_file(get_speaker_channel(chn)) < 0)
1218 	  prg_exit(EXIT_FAILURE);
1219       }
1220     }
1221 
1222     for (n = 0; (! nloops || n < nloops) && !in_aborting; n++) {
1223 
1224       gettimeofday(&tv1, NULL);
1225       for(chn = 0; chn < channels; chn++) {
1226 	int channel = get_speaker_channel(chn);
1227         printf(" %d - %s\n", channel, get_channel_name(channel));
1228 
1229         err = write_loop(handle, channel, ((rate*3)/period_size), frames);
1230 
1231         if (err < 0) {
1232           fprintf(stderr, _("Transfer failed: %s\n"), snd_strerror(err));
1233           prg_exit(EXIT_SUCCESS);
1234         }
1235       }
1236       gettimeofday(&tv2, NULL);
1237       time1 = (double)tv1.tv_sec + ((double)tv1.tv_usec / 1000000.0);
1238       time2 = (double)tv2.tv_sec + ((double)tv2.tv_usec / 1000000.0);
1239       time3 = time2 - time1;
1240       printf(_("Time per period = %lf\n"), time3 );
1241     }
1242   } else {
1243     chn = get_speaker_channel(speaker - 1);
1244 
1245     if (test_type == TEST_WAV) {
1246       if (setup_wav_file(chn) < 0)
1247 	prg_exit(EXIT_FAILURE);
1248     }
1249 
1250     printf("  - %s\n", get_channel_name(chn));
1251     err = write_loop(handle, chn, ((rate*5)/period_size), frames);
1252 
1253     if (err < 0) {
1254       fprintf(stderr, _("Transfer failed: %s\n"), snd_strerror(err));
1255     }
1256   }
1257 
1258   snd_pcm_drain(handle);
1259 
1260   free(frames);
1261 #ifdef CONFIG_SUPPORT_CHMAP
1262   free(ordered_channels);
1263 #endif
1264 
1265   return prg_exit(EXIT_SUCCESS);
1266 }
1267