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