xref: /illumos-gate/usr/src/cmd/audio/audiotest/audiotest.c (revision 8883f1c270cc8e33c18dd088e744840092b47bbb)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright (C) 4Front Technologies 1996-2008.
23  *
24  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 /*
28  * This program is a general purpose test facility for audio output.
29  * It does not test record.
30  *
31  * The wavedata.c and wavedata.h files contain the actual samples compressed
32  * using the MS ADPCM algorithm.
33  */
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <unistd.h>
38 #include <fcntl.h>
39 #include <string.h>
40 #include <errno.h>
41 #include <unistd.h>
42 #include <sys/time.h>
43 #include <sys/ioctl.h>
44 #include <sys/utsname.h>
45 #include <sys/soundcard.h>
46 #include <inttypes.h>
47 #include <locale.h>
48 
49 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
50 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
51 #endif
52 
53 #define	_(s)	gettext(s)
54 
55 /*
56  * Channel selectors
57  */
58 #define	CH_LEFT		(1 << 0)
59 #define	CH_RIGHT	(1 << 1)
60 #define	CH_LREAR4	(1 << 2)	/* quadraphonic */
61 #define	CH_RREAR4	(1 << 3)	/* quadraphonic */
62 #define	CH_CENTER	(1 << 2)
63 #define	CH_LFE		(1 << 3)
64 #define	CH_LSURR	(1 << 4)
65 #define	CH_RSURR	(1 << 5)
66 #define	CH_LREAR	(1 << 6)
67 #define	CH_RREAR	(1 << 7)
68 #define	CH_STEREO	(CH_LEFT|CH_RIGHT)
69 #define	CH_4		(CH_STEREO | CH_LREAR4 | CH_RREAR4)
70 #define	CH_5		(CH_STEREO | CH_CENTER | CH_LSURR | CH_RSURR)
71 #define	CH_7		(CH_5 | CH_LREAR | CH_RREAR)
72 
73 typedef struct chancfg {
74 	int		mask;
75 	const char	*name;
76 	unsigned	flags;
77 	int16_t		*data;
78 	int		len;
79 } chancfg_t;
80 
81 typedef struct testcfg {
82 	int		nchan;
83 	chancfg_t	*tests[16];
84 } testcfg_t;
85 
86 #define	CFLAG_LFE	0x1	/* lfe channel - not full range */
87 
88 /*
89  * TRANSLATION_NOTE : The following strings are displayed during progress.
90  * Its important for alignment that they have the same displayed length.
91  */
92 #define	NM_LEFT		"\t<left> ................"
93 #define	NM_RIGHT	"\t<right> ..............."
94 #define	NM_LREAR	"\t<left rear> ..........."
95 #define	NM_RREAR	"\t<right rear> .........."
96 #define	NM_LSIDE	"\t<left side> ..........."
97 #define	NM_RSIDE	"\t<right side> .........."
98 #define	NM_CENTER	"\t<center> .............."
99 #define	NM_LFE		"\t<lfe> ................."
100 #define	NM_STEREO	"\t<stereo> .............."
101 #define	NM_40		"\t<4.0 surround> ........"
102 #define	NM_50		"\t<5.0 surround> ........"
103 #define	NM_70		"\t<7.0 surround> ........"
104 
105 chancfg_t ch_left = { CH_LEFT, NM_LEFT, 0 };
106 chancfg_t ch_right = { CH_RIGHT, NM_RIGHT, 0 };
107 chancfg_t ch_stereo = { CH_STEREO, NM_STEREO, 0 };
108 
109 chancfg_t ch_center = { CH_CENTER, NM_CENTER, 0 };
110 chancfg_t ch_lfe = { CH_LFE, NM_LFE, CFLAG_LFE };
111 
112 chancfg_t ch_lsurr_4 = { (1 << 2), NM_LREAR, 0 };
113 chancfg_t ch_rsurr_4 = { (1 << 3), NM_RREAR, 0 };
114 chancfg_t ch_4 = { CH_4, NM_40, 0 };
115 
116 chancfg_t ch_lsurr_5 = { CH_LSURR, NM_LREAR, 0 };
117 chancfg_t ch_rsurr_5 = { CH_RSURR, NM_RREAR, 0 };
118 chancfg_t ch_5 = { CH_5, NM_50, 0 };
119 
120 chancfg_t ch_lsurr_7 = { CH_LSURR, NM_LSIDE, 0 };
121 chancfg_t ch_rsurr_7 = { CH_RSURR, NM_RSIDE, 0 };
122 chancfg_t ch_lrear_7 = { CH_LREAR, NM_LREAR, 0 };
123 chancfg_t ch_rrear_7 = { CH_RREAR, NM_RREAR, 0 };
124 chancfg_t ch_7 = { CH_7, NM_70, 0 };
125 
126 testcfg_t test_stereo = {
127 	2, { &ch_left, &ch_right, &ch_stereo, NULL }
128 };
129 
130 testcfg_t test_quad = {
131 	4, { &ch_left, &ch_right, &ch_stereo,
132 	&ch_lsurr_4, &ch_rsurr_4, &ch_4, NULL }
133 };
134 
135 testcfg_t test_51 = {
136 	6, { &ch_left, &ch_right, &ch_stereo,
137 	&ch_lsurr_5, &ch_rsurr_5, &ch_center, &ch_lfe, &ch_5, NULL }
138 };
139 
140 testcfg_t test_71 = {
141 	8, { &ch_left, &ch_right, &ch_stereo,
142 	&ch_lsurr_7, &ch_rsurr_7, &ch_lrear_7, &ch_rrear_7,
143 	&ch_center, &ch_lfe, &ch_7, NULL }
144 };
145 
146 /*
147  * uncompress_wave() is defined in wavedata.c. It expands the audio
148  * samples stored in wavedata.h and returns the lenghth of the
149  * uncompressed version in bytes.
150  *
151  * The uncompressed wave data format is 16 bit (native) stereo
152  * recorded at 48000 Hz.
153  */
154 extern int uncompress_wave(short *outbuf);
155 
156 static int data_len;
157 
158 #define	MAXDEVICE   64
159 extern void describe_error(int);
160 
161 #define	SAMPLE_RATE 48000
162 
163 /*
164  * Operating mode flags (set from the command line).
165  */
166 #define	TF_LOOP		0x00000010	/* Loop until interrupted */
167 
168 static int mixerfd;
169 static int num_devices_tested = 0;
170 
171 static short *sample_buf;
172 
173 void
174 prepare(testcfg_t *tcfg)
175 {
176 	int	nsamples;
177 	int	i;
178 	chancfg_t	*ccfg;
179 	if ((sample_buf = malloc(2000000)) == NULL) {
180 		perror("malloc");
181 		exit(-1);
182 	}
183 
184 	data_len = uncompress_wave(sample_buf);
185 	nsamples = (data_len / sizeof (int16_t)) / 2;
186 
187 	for (i = 0; (ccfg = tcfg->tests[i]) != NULL; i++) {
188 		int16_t		*src, *dst;
189 		int		ch;
190 		int		samp;
191 
192 		src = sample_buf;
193 
194 		if (ccfg->flags != CFLAG_LFE) {
195 			ccfg->len = nsamples * tcfg->nchan * sizeof (int16_t);
196 			ccfg->data = malloc(ccfg->len);
197 			if ((dst = ccfg->data) == NULL) {
198 				perror("malloc");
199 				exit(-1);
200 			}
201 			for (samp = 0; samp < nsamples; samp++) {
202 				for (ch = 0; ch < tcfg->nchan; ch++) {
203 					*dst = ((1U << ch) & ccfg->mask) ?
204 					    *src : 0;
205 					dst++;
206 				}
207 				src += 2;
208 			}
209 		} else {
210 			/* Skip LFE for now */
211 			ccfg->len = 0;
212 		}
213 	}
214 }
215 
216 /*
217  * The testdsp() routine checks the capabilities of a given audio device number
218  * (parameter n) and decides if the test sound needs to be played.
219  */
220 
221 /*ARGSUSED*/
222 int
223 testdsp(int hd, int flags, testcfg_t *tcfg)
224 {
225 	float ratio;
226 	struct timeval t1, t2;
227 	unsigned long t;
228 	int sample_rate;
229 	int delay;
230 	long long total_bytes = 0;
231 	unsigned int tmp, caps;
232 	int i;
233 	chancfg_t *ccfg;
234 
235 	caps = 0;
236 	if (ioctl(hd, SNDCTL_DSP_GETCAPS, &caps) == -1) {
237 		perror("SNDCTL_DSP_GETCAPS");
238 		return (-1);
239 	}
240 
241 	/*
242 	 * Setup the sample format. Since OSS will support AFMT_S16_NE
243 	 * regardless of the device we do not need to support any
244 	 * other formats.
245 	 */
246 
247 	tmp = AFMT_S16_NE;
248 	if (ioctl(hd, SNDCTL_DSP_SETFMT, &tmp) == -1 || tmp != AFMT_S16_NE) {
249 		(void) printf(_("Device doesn't support native 16-bit PCM\n"));
250 		return (-1);
251 	}
252 
253 	/*
254 	 * Setup the device for channels. Once again we can simply
255 	 * assume that stereo will always work before OSS takes care
256 	 * of this by emulation if necessary.
257 	 */
258 	tmp = tcfg->nchan;
259 	if (ioctl(hd, SNDCTL_DSP_CHANNELS, &tmp) == -1 || tmp != tcfg->nchan) {
260 		(void) printf(_("The device doesn't support %d channels\n"),
261 		    tcfg->nchan);
262 		return (-2);
263 	}
264 
265 	/*
266 	 * Set up the sample rate.
267 	 */
268 
269 	tmp = SAMPLE_RATE;
270 	if (ioctl(hd, SNDCTL_DSP_SPEED, &tmp) == -1) {
271 		perror("SNDCTL_DSP_SPEED");
272 		return (-3);
273 	}
274 
275 	sample_rate = tmp;
276 	if (sample_rate != SAMPLE_RATE) {
277 		(void) printf(_("The device doesn't support %d Hz\n"),
278 		    SAMPLE_RATE);
279 		return (-3);
280 	}
281 	(void) printf("\n");
282 
283 	/*
284 	 * This program will measure the real sampling rate by
285 	 * computing the total time required to play the sample.
286 	 *
287 	 * This is not terribly presice with short test sounds but it
288 	 * can be used to detect if the sampling rate badly
289 	 * wrong. Errors of few percents is more likely to be caused
290 	 * by poor accuracy of the system clock rather than problems
291 	 * with the sampling rate.
292 	 */
293 	(void) gettimeofday(&t1, NULL);
294 
295 	for (i = 0; (ccfg = tcfg->tests[i]) != NULL; i++) {
296 		(void) fputs(_(ccfg->name), stdout);
297 		(void) fflush(stdout);
298 		if (ccfg->flags & CFLAG_LFE) {
299 			(void) printf(_("SKIPPED\n"));
300 			continue;
301 		}
302 
303 		if (write(hd, ccfg->data, ccfg->len) < 0) {
304 			(void) printf(_("ERROR: %s\n"),
305 			    strerror(errno));
306 			return (-3);
307 		}
308 		(void) printf(_("OK\n"));
309 		total_bytes += ccfg->len;
310 	}
311 
312 	(void) gettimeofday(&t2, NULL);
313 	delay = 0;
314 	(void) ioctl(hd, SNDCTL_DSP_GETODELAY, &delay);	/* Ignore errors */
315 
316 	/*
317 	 * Perform the time computations using milliseconds.
318 	 */
319 
320 	t = t2.tv_sec - t1.tv_sec;
321 	t *= 1000;
322 
323 	t += t2.tv_usec / 1000;
324 	t -= t1.tv_usec / 1000;
325 
326 	total_bytes -= delay;
327 	total_bytes *= 1000;
328 
329 	total_bytes /= t;
330 	total_bytes /= (tcfg->nchan * sizeof (int16_t));
331 
332 	ratio = ((float)total_bytes / (float)sample_rate) * 100.0;
333 	(void) printf(_("\t<measured sample rate %8.2f Hz (%4.2f%%)>\n"),
334 	    (float)sample_rate * ratio / 100.0, ratio - 100.0);
335 	num_devices_tested++;
336 
337 	return (1);
338 }
339 
340 static int
341 find_num_devices(void)
342 {
343 	oss_sysinfo info;
344 	struct utsname un;
345 	/*
346 	 * Find out the number of available audio devices by calling
347 	 * SNDCTL_SYSINFO.
348 	 */
349 
350 	if (ioctl(mixerfd, SNDCTL_SYSINFO, &info) == -1) {
351 		if (errno == ENXIO) {
352 			(void) fprintf(stderr,
353 			    _("No supported sound hardware detected.\n"));
354 			exit(-1);
355 		} else {
356 			perror("SNDCTL_SYSINFO");
357 			(void) printf(_("Cannot get system information.\n"));
358 			exit(-1);
359 		}
360 	}
361 	(void) printf(_("Sound subsystem and version: %s %s (0x%08X)\n"),
362 	    info.product, info.version, info.versionnum);
363 
364 	if (uname(&un) != -1)
365 		(void) printf(_("Platform: %s %s %s %s\n"),
366 		    un.sysname, un.release, un.version, un.machine);
367 
368 	return (info.numaudios);
369 }
370 
371 /*
372  * The test_device() routine checks certain information about the device
373  * and calls testdsp() to play the test sound.
374  */
375 
376 int
377 test_device(char *dn, int flags, testcfg_t *tcfg)
378 {
379 	oss_audioinfo ainfo;
380 	int code;
381 	int fd;
382 
383 	fd = open(dn, O_WRONLY, 0);
384 	if (fd == -1) {
385 		int err = errno;
386 		perror(dn);
387 		errno = err;
388 		describe_error(errno);
389 		return (0);
390 	}
391 
392 	ainfo.dev = -1;
393 	if (ioctl(fd, SNDCTL_AUDIOINFO, &ainfo) == -1) {
394 		perror("SNDCTL_AUDIOINFO");
395 		(void) close(fd);
396 		return (1);
397 	}
398 
399 	(void) printf(_("\n*** Scanning sound adapter #%d ***\n"),
400 	    ainfo.card_number);
401 
402 	(void) printf(_("%s (audio engine %d): %s\n"), ainfo.devnode, ainfo.dev,
403 	    ainfo.name);
404 
405 	if (!ainfo.enabled) {
406 		(void) printf(_("  - Device not present - Skipping\n"));
407 		(void) close(fd);
408 		return (1);
409 	}
410 
411 	if (!(ainfo.caps & PCM_CAP_OUTPUT)) {
412 		(void) printf(_("  - Skipping input only device\n"));
413 		(void) close(fd);
414 		return (1);
415 	}
416 
417 	(void) printf(_("  - Performing audio playback test... "));
418 	(void) fflush(stdout);
419 
420 	code = testdsp(fd, flags, tcfg);
421 	(void) close(fd);
422 
423 	return (code == 1);
424 }
425 
426 void
427 describe_error(int err)
428 {
429 	switch (err) {
430 	case ENODEV:
431 		(void) fprintf(stderr,
432 		    _("The device file was found in /dev but\n"
433 		    "the driver was not loaded.\n"));
434 		break;
435 
436 	case ENXIO:
437 		(void) fprintf(stderr,
438 		    _("There are no sound devices available.\n"
439 		    "The most likely reason is that the device you have\n"
440 		    "is malfunctioning or it's not supported.\n"
441 		    "It's also possible that you are trying to use the wrong "
442 		    "device file.\n"));
443 		break;
444 
445 	case ENOSPC:
446 		(void) fprintf(stderr,
447 		    _("Your system cannot allocate memory for the device\n"
448 		    "buffers. Reboot your machine and try again.\n"));
449 		break;
450 
451 	case ENOENT:
452 		(void) fprintf(stderr,
453 		    _("The device file is missing from /dev.\n"));
454 		break;
455 
456 
457 	case EBUSY:
458 		(void) fprintf(stderr,
459 		    _("The device is busy. There is some other application\n"
460 		    "using it.\n"));
461 		break;
462 
463 	default:
464 		break;
465 	}
466 }
467 
468 int
469 main(int argc, char *argv[])
470 {
471 	int t, i;
472 	int maxdev;
473 	int flags = 0;
474 	int status = 0;
475 	int numdev;
476 	extern int optind;
477 	testcfg_t	*tcfg;
478 
479 	(void) setlocale(LC_ALL, "");
480 	(void) textdomain(TEXT_DOMAIN);
481 
482 	tcfg = &test_stereo;
483 
484 	/*
485 	 * Simple command line switch handling.
486 	 */
487 
488 	while ((i = getopt(argc, argv, "l2457")) != EOF) {
489 		switch (i) {
490 		case 'l':
491 			flags |= TF_LOOP;
492 			break;
493 		case '2':
494 			tcfg = &test_stereo;
495 			break;
496 		case '4':
497 			tcfg = &test_quad;
498 			break;
499 		case '5':
500 			tcfg = &test_51;
501 			break;
502 		case '7':
503 			tcfg = &test_71;
504 			break;
505 		default:
506 			(void) printf(_("Usage: %s [options...] [device]\n"
507 			    "	-2	Stereo test\n"
508 			    "	-4	Quadraphonic 4.0 test\n"
509 			    "	-5	Surround 5.1 test\n"
510 			    "	-7	Surround 7.1 test\n"
511 			    "	-l	Loop test\n"), argv[0]);
512 			exit(-1);
513 		}
514 	}
515 
516 	/*
517 	 * Open the mixer device used for calling SNDCTL_SYSINFO and
518 	 * SNDCTL_AUDIOINFO.
519 	 */
520 	if ((mixerfd = open("/dev/mixer", O_RDWR, 0)) == -1) {
521 		int err = errno;
522 		perror("/dev/mixer");
523 		errno = err;
524 		describe_error(errno);
525 		exit(-1);
526 	}
527 
528 	prepare(tcfg);			/* Prepare the wave data */
529 
530 	/*
531 	 * Enumerate all devices and play the test sounds.
532 	 */
533 	maxdev = find_num_devices();
534 	if (maxdev < 1) {
535 		(void) printf(_("\n*** No audio hardware available ***\n"));
536 		exit(-1);
537 	}
538 
539 	numdev = (argc - optind);
540 	do {
541 		char *dn;
542 		oss_audioinfo	ainfo;
543 
544 		if (numdev > 0) {
545 			for (t = 0; t < numdev; t++) {
546 				dn = argv[optind + t];
547 				if (!test_device(dn, flags, tcfg))
548 					status++;
549 			}
550 		} else {
551 			for (t = 0; t < maxdev; t++) {
552 				ainfo.dev = t;
553 				if (ioctl(mixerfd, SNDCTL_AUDIOINFO,
554 				    &ainfo) == -1) {
555 					perror("SNDCTL_AUDIOINFO");
556 					status++;
557 					continue;
558 				}
559 				dn = ainfo.devnode;
560 				if (!test_device(dn, flags, tcfg))
561 					status++;
562 			}
563 		}
564 
565 		if (status == 0)
566 			(void) printf(_("\n*** All tests completed OK ***\n"));
567 		else
568 			(void) printf(_("\n*** Errors were detected ***\n"));
569 
570 	} while (flags & TF_LOOP);
571 
572 	(void) close(mixerfd);
573 
574 	return (status);
575 }
576