xref: /titanic_50/usr/src/cmd/audio/audiotest/audiotest.c (revision 49f9b365248ee858ee91baa36eab27c5200f6dca)
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 (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
25  */
26 /*
27  * This program is a general purpose test facility for audio output.
28  * It does not test record.
29  *
30  * The wavedata.c and wavedata.h files contain the actual samples compressed
31  * using the MS ADPCM algorithm.
32  */
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <fcntl.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <unistd.h>
41 #include <sys/time.h>
42 #include <sys/ioctl.h>
43 #include <sys/utsname.h>
44 #include <sys/soundcard.h>
45 #include <inttypes.h>
46 #include <locale.h>
47 
48 #if !defined(TEXT_DOMAIN)	/* Should be defined by cc -D */
49 #define	TEXT_DOMAIN "SYS_TEST"	/* Use this only if it weren't */
50 #endif
51 
52 #define	_(s)	gettext(s)
53 
54 /*
55  * Channel selectors
56  */
57 #define	CH_LEFT		(1 << 0)
58 #define	CH_RIGHT	(1 << 1)
59 #define	CH_LREAR4	(1 << 2)	/* quadraphonic */
60 #define	CH_RREAR4	(1 << 3)	/* quadraphonic */
61 #define	CH_CENTER	(1 << 2)
62 #define	CH_LFE		(1 << 3)
63 #define	CH_LSURR	(1 << 4)
64 #define	CH_RSURR	(1 << 5)
65 #define	CH_LREAR	(1 << 6)
66 #define	CH_RREAR	(1 << 7)
67 #define	CH_STEREO	(CH_LEFT|CH_RIGHT)
68 #define	CH_4		(CH_STEREO | CH_LREAR4 | CH_RREAR4)
69 #define	CH_5		(CH_STEREO | CH_CENTER | CH_LSURR | CH_RSURR)
70 #define	CH_7		(CH_5 | CH_LREAR | CH_RREAR)
71 
72 typedef struct chancfg {
73 	int		mask;
74 	const char	*name;
75 	unsigned	flags;
76 	int16_t		*data;
77 	int		len;
78 } chancfg_t;
79 
80 typedef struct testcfg {
81 	int		nchan;
82 	uint32_t	rate;
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, 48000, { &ch_left, &ch_right, &ch_stereo, NULL }
128 };
129 
130 testcfg_t test_quad = {
131 	4, 48000, { &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, 48000, { &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, 48000, { &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, j;
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 		int		rate_multiple;
192 
193 		src = sample_buf;
194 		rate_multiple = tcfg->rate / 48000;
195 
196 		if (ccfg->flags != CFLAG_LFE) {
197 			ccfg->len = nsamples * tcfg->nchan *
198 			    sizeof (int16_t) * rate_multiple;
199 			ccfg->data = malloc(ccfg->len);
200 			if ((dst = ccfg->data) == NULL) {
201 				perror("malloc");
202 				exit(-1);
203 			}
204 			for (samp = 0; samp < nsamples; samp++) {
205 				for (ch = 0; ch < tcfg->nchan; ch++) {
206 					for (j = 0; j < rate_multiple; j++) {
207 						*dst = ((1U << ch) & ccfg->mask)
208 						    ? *src : 0;
209 						dst++;
210 					}
211 				}
212 				src += 2;
213 			}
214 		} else {
215 			/* Skip LFE for now */
216 			ccfg->len = 0;
217 		}
218 	}
219 }
220 
221 /*
222  * The testdsp() routine checks the capabilities of a given audio device number
223  * (parameter n) and decides if the test sound needs to be played.
224  */
225 
226 /*ARGSUSED*/
227 int
228 testdsp(int hd, int flags, testcfg_t *tcfg)
229 {
230 	float ratio;
231 	struct timeval t1, t2;
232 	unsigned long t;
233 	int sample_rate;
234 	int delay;
235 	long long total_bytes = 0;
236 	unsigned int tmp, caps;
237 	int i;
238 	chancfg_t *ccfg;
239 
240 	caps = 0;
241 	if (ioctl(hd, SNDCTL_DSP_GETCAPS, &caps) == -1) {
242 		perror("SNDCTL_DSP_GETCAPS");
243 		return (-1);
244 	}
245 
246 	/*
247 	 * Setup the sample format. Since OSS will support AFMT_S16_NE
248 	 * regardless of the device we do not need to support any
249 	 * other formats.
250 	 */
251 	tmp = AFMT_S16_NE;
252 	if (ioctl(hd, SNDCTL_DSP_SETFMT, &tmp) == -1 || tmp != AFMT_S16_NE) {
253 		(void) printf(_("Device doesn't support native 16-bit PCM\n"));
254 		return (-1);
255 	}
256 
257 	/*
258 	 * Setup the device for channels. Once again we can simply
259 	 * assume that stereo will always work before OSS takes care
260 	 * of this by emulation if necessary.
261 	 */
262 	tmp = tcfg->nchan;
263 	if (ioctl(hd, SNDCTL_DSP_CHANNELS, &tmp) == -1 || tmp != tcfg->nchan) {
264 		(void) printf(_("The device doesn't support %d channels\n"),
265 		    tcfg->nchan);
266 		return (-2);
267 	}
268 
269 	/*
270 	 * Set up the sample rate.
271 	 */
272 	tmp = tcfg->rate;
273 	if (ioctl(hd, SNDCTL_DSP_SPEED, &tmp) == -1) {
274 		perror("SNDCTL_DSP_SPEED");
275 		return (-3);
276 	}
277 
278 	sample_rate = tmp;
279 	if (sample_rate != tcfg->rate) {
280 		(void) printf(_("The device doesn't support %d Hz\n"),
281 		    tcfg->rate);
282 		return (-3);
283 	}
284 	(void) printf("\n");
285 
286 	/*
287 	 * This program will measure the real sampling rate by
288 	 * computing the total time required to play the sample.
289 	 *
290 	 * This is not terribly presice with short test sounds but it
291 	 * can be used to detect if the sampling rate badly
292 	 * wrong. Errors of few percents is more likely to be caused
293 	 * by poor accuracy of the system clock rather than problems
294 	 * with the sampling rate.
295 	 */
296 	(void) gettimeofday(&t1, NULL);
297 
298 	for (i = 0; (ccfg = tcfg->tests[i]) != NULL; i++) {
299 		(void) fputs(_(ccfg->name), stdout);
300 		(void) fflush(stdout);
301 		if (ccfg->flags & CFLAG_LFE) {
302 			(void) printf(_("SKIPPED\n"));
303 			continue;
304 		}
305 
306 		if (write(hd, ccfg->data, ccfg->len) < 0) {
307 			(void) printf(_("ERROR: %s\n"),
308 			    strerror(errno));
309 			return (-3);
310 		}
311 		(void) printf(_("OK\n"));
312 		total_bytes += ccfg->len;
313 	}
314 
315 	(void) gettimeofday(&t2, NULL);
316 	delay = 0;
317 	(void) ioctl(hd, SNDCTL_DSP_GETODELAY, &delay);	/* Ignore errors */
318 
319 	/*
320 	 * Perform the time computations using milliseconds.
321 	 */
322 
323 	t = t2.tv_sec - t1.tv_sec;
324 	t *= 1000;
325 
326 	t += t2.tv_usec / 1000;
327 	t -= t1.tv_usec / 1000;
328 
329 	total_bytes -= delay;
330 	total_bytes *= 1000;
331 
332 	total_bytes /= t;
333 	total_bytes /= (tcfg->nchan * sizeof (int16_t));
334 
335 	ratio = ((float)total_bytes / (float)sample_rate) * 100.0;
336 	(void) printf(_("\t<measured sample rate %8.2f Hz (%4.2f%%)>\n"),
337 	    (float)sample_rate * ratio / 100.0, ratio - 100.0);
338 	num_devices_tested++;
339 
340 	return (1);
341 }
342 
343 static int
344 find_num_devices(void)
345 {
346 	oss_sysinfo info;
347 	struct utsname un;
348 	/*
349 	 * Find out the number of available audio devices by calling
350 	 * SNDCTL_SYSINFO.
351 	 */
352 
353 	if (ioctl(mixerfd, SNDCTL_SYSINFO, &info) == -1) {
354 		if (errno == ENXIO) {
355 			(void) fprintf(stderr,
356 			    _("No supported sound hardware detected.\n"));
357 			exit(-1);
358 		} else {
359 			perror("SNDCTL_SYSINFO");
360 			(void) printf(_("Cannot get system information.\n"));
361 			exit(-1);
362 		}
363 	}
364 	(void) printf(_("Sound subsystem and version: %s %s (0x%08X)\n"),
365 	    info.product, info.version, info.versionnum);
366 
367 	if (uname(&un) != -1)
368 		(void) printf(_("Platform: %s %s %s %s\n"),
369 		    un.sysname, un.release, un.version, un.machine);
370 
371 	return (info.numaudios);
372 }
373 
374 /*
375  * The test_device() routine checks certain information about the device
376  * and calls testdsp() to play the test sound.
377  */
378 
379 int
380 test_device(char *dn, int flags, testcfg_t *tcfg)
381 {
382 	oss_audioinfo ainfo;
383 	int code;
384 	int fd;
385 
386 	fd = open(dn, O_WRONLY, 0);
387 	if (fd == -1) {
388 		int err = errno;
389 		perror(dn);
390 		errno = err;
391 		describe_error(errno);
392 		return (-1);
393 	}
394 
395 	ainfo.dev = -1;
396 	if (ioctl(fd, SNDCTL_AUDIOINFO, &ainfo) == -1) {
397 		perror("SNDCTL_AUDIOINFO");
398 		(void) close(fd);
399 		return (-1);
400 	}
401 
402 	(void) printf(_("\n*** Scanning sound adapter #%d ***\n"),
403 	    ainfo.card_number);
404 
405 	(void) printf(_("%s (audio engine %d): %s\n"), ainfo.devnode, ainfo.dev,
406 	    ainfo.name);
407 
408 	if (!ainfo.enabled) {
409 		(void) printf(_("  - Device not present - Skipping\n"));
410 		(void) close(fd);
411 		return (0);
412 	}
413 
414 	if (!(ainfo.caps & PCM_CAP_OUTPUT)) {
415 		(void) printf(_("  - Skipping input only device\n"));
416 		(void) close(fd);
417 		return (0);
418 	}
419 
420 	(void) printf(_("  - Performing audio playback test... "));
421 	(void) fflush(stdout);
422 
423 	code = testdsp(fd, flags, tcfg);
424 	(void) close(fd);
425 	if (code < 0) {
426 		return (code);
427 	}
428 
429 	return (code == 1);
430 }
431 
432 void
433 describe_error(int err)
434 {
435 	switch (err) {
436 	case ENODEV:
437 		(void) fprintf(stderr,
438 		    _("The device file was found in /dev but\n"
439 		    "the driver was not loaded.\n"));
440 		break;
441 
442 	case ENXIO:
443 		(void) fprintf(stderr,
444 		    _("There are no sound devices available.\n"
445 		    "The most likely reason is that the device you have\n"
446 		    "is malfunctioning or it's not supported.\n"
447 		    "It's also possible that you are trying to use the wrong "
448 		    "device file.\n"));
449 		break;
450 
451 	case ENOSPC:
452 		(void) fprintf(stderr,
453 		    _("Your system cannot allocate memory for the device\n"
454 		    "buffers. Reboot your machine and try again.\n"));
455 		break;
456 
457 	case ENOENT:
458 		(void) fprintf(stderr,
459 		    _("The device file is missing from /dev.\n"));
460 		break;
461 
462 
463 	case EBUSY:
464 		(void) fprintf(stderr,
465 		    _("The device is busy. There is some other application\n"
466 		    "using it.\n"));
467 		break;
468 
469 	default:
470 		break;
471 	}
472 }
473 
474 int
475 main(int argc, char *argv[])
476 {
477 	int t, i;
478 	int maxdev;
479 	int flags = 0;
480 	int status = 0;
481 	int errors = 0;
482 	int numdev;
483 	extern int optind;
484 	testcfg_t	*tcfg;
485 
486 	(void) setlocale(LC_ALL, "");
487 	(void) textdomain(TEXT_DOMAIN);
488 
489 	tcfg = &test_stereo;
490 
491 	/*
492 	 * Simple command line switch handling.
493 	 */
494 
495 	while ((i = getopt(argc, argv, "l2457r:")) != EOF) {
496 		switch (i) {
497 		case 'l':
498 			flags |= TF_LOOP;
499 			break;
500 		case '2':
501 			tcfg = &test_stereo;
502 			break;
503 		case '4':
504 			tcfg = &test_quad;
505 			break;
506 		case '5':
507 			tcfg = &test_51;
508 			break;
509 		case '7':
510 			tcfg = &test_71;
511 			break;
512 		case 'r':
513 			tcfg->rate = atoi(optarg);
514 			break;
515 		default:
516 			(void) printf(_("Usage: %s [options...] [device]\n"
517 			    "	-2	Stereo test\n"
518 			    "	-4	Quadraphonic 4.0 test\n"
519 			    "	-5	Surround 5.1 test\n"
520 			    "	-7	Surround 7.1 test\n"
521 			    "	-r	Sample Rate (48000|96000|192000)\n"
522 			    "	-l	Loop test\n"), argv[0]);
523 			exit(-1);
524 		}
525 	}
526 
527 	/*
528 	 * Open the mixer device used for calling SNDCTL_SYSINFO and
529 	 * SNDCTL_AUDIOINFO.
530 	 */
531 	if ((mixerfd = open("/dev/mixer", O_RDWR, 0)) == -1) {
532 		int err = errno;
533 		perror("/dev/mixer");
534 		errno = err;
535 		describe_error(errno);
536 		exit(-1);
537 	}
538 
539 	prepare(tcfg);			/* Prepare the wave data */
540 
541 	/*
542 	 * Enumerate all devices and play the test sounds.
543 	 */
544 	maxdev = find_num_devices();
545 	if (maxdev < 1) {
546 		(void) printf(_("\n*** No audio hardware available ***\n"));
547 		exit(-1);
548 	}
549 
550 	numdev = (argc - optind);
551 	do {
552 		char *dn;
553 		oss_audioinfo	ainfo;
554 		int rv;
555 
556 		status = 0;
557 		if (numdev > 0) {
558 			for (t = 0; t < numdev; t++) {
559 				dn = argv[optind + t];
560 				rv = test_device(dn, flags, tcfg);
561 				if (rv < 0) {
562 					errors++;
563 				} else if (rv) {
564 					status++;
565 				}
566 			}
567 		} else {
568 			for (t = 0; t < maxdev; t++) {
569 				ainfo.dev = t;
570 				if (ioctl(mixerfd, SNDCTL_AUDIOINFO,
571 				    &ainfo) == -1) {
572 					perror("SNDCTL_AUDIOINFO");
573 					status++;
574 					continue;
575 				}
576 				dn = ainfo.devnode;
577 				rv = test_device(dn, flags, tcfg);
578 				if (rv < 0) {
579 					errors++;
580 				} else if (rv) {
581 					status++;
582 				}
583 			}
584 		}
585 
586 		if (errors == 0)
587 			(void) printf(_("\n*** All tests completed OK ***\n"));
588 		else
589 			(void) printf(_("\n*** Errors were detected ***\n"));
590 
591 	} while (status && (flags & TF_LOOP));
592 
593 	(void) close(mixerfd);
594 
595 	return (status);
596 }
597