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 2010 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 (-1);
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 (0);
409 	}
410 
411 	if (!(ainfo.caps & PCM_CAP_OUTPUT)) {
412 		(void) printf(_("  - Skipping input only device\n"));
413 		(void) close(fd);
414 		return (0);
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 	if (code < 0) {
423 		return (code);
424 	}
425 
426 	return (code == 1);
427 }
428 
429 void
430 describe_error(int err)
431 {
432 	switch (err) {
433 	case ENODEV:
434 		(void) fprintf(stderr,
435 		    _("The device file was found in /dev but\n"
436 		    "the driver was not loaded.\n"));
437 		break;
438 
439 	case ENXIO:
440 		(void) fprintf(stderr,
441 		    _("There are no sound devices available.\n"
442 		    "The most likely reason is that the device you have\n"
443 		    "is malfunctioning or it's not supported.\n"
444 		    "It's also possible that you are trying to use the wrong "
445 		    "device file.\n"));
446 		break;
447 
448 	case ENOSPC:
449 		(void) fprintf(stderr,
450 		    _("Your system cannot allocate memory for the device\n"
451 		    "buffers. Reboot your machine and try again.\n"));
452 		break;
453 
454 	case ENOENT:
455 		(void) fprintf(stderr,
456 		    _("The device file is missing from /dev.\n"));
457 		break;
458 
459 
460 	case EBUSY:
461 		(void) fprintf(stderr,
462 		    _("The device is busy. There is some other application\n"
463 		    "using it.\n"));
464 		break;
465 
466 	default:
467 		break;
468 	}
469 }
470 
471 int
472 main(int argc, char *argv[])
473 {
474 	int t, i;
475 	int maxdev;
476 	int flags = 0;
477 	int status = 0;
478 	int errors = 0;
479 	int numdev;
480 	extern int optind;
481 	testcfg_t	*tcfg;
482 
483 	(void) setlocale(LC_ALL, "");
484 	(void) textdomain(TEXT_DOMAIN);
485 
486 	tcfg = &test_stereo;
487 
488 	/*
489 	 * Simple command line switch handling.
490 	 */
491 
492 	while ((i = getopt(argc, argv, "l2457")) != EOF) {
493 		switch (i) {
494 		case 'l':
495 			flags |= TF_LOOP;
496 			break;
497 		case '2':
498 			tcfg = &test_stereo;
499 			break;
500 		case '4':
501 			tcfg = &test_quad;
502 			break;
503 		case '5':
504 			tcfg = &test_51;
505 			break;
506 		case '7':
507 			tcfg = &test_71;
508 			break;
509 		default:
510 			(void) printf(_("Usage: %s [options...] [device]\n"
511 			    "	-2	Stereo test\n"
512 			    "	-4	Quadraphonic 4.0 test\n"
513 			    "	-5	Surround 5.1 test\n"
514 			    "	-7	Surround 7.1 test\n"
515 			    "	-l	Loop test\n"), argv[0]);
516 			exit(-1);
517 		}
518 	}
519 
520 	/*
521 	 * Open the mixer device used for calling SNDCTL_SYSINFO and
522 	 * SNDCTL_AUDIOINFO.
523 	 */
524 	if ((mixerfd = open("/dev/mixer", O_RDWR, 0)) == -1) {
525 		int err = errno;
526 		perror("/dev/mixer");
527 		errno = err;
528 		describe_error(errno);
529 		exit(-1);
530 	}
531 
532 	prepare(tcfg);			/* Prepare the wave data */
533 
534 	/*
535 	 * Enumerate all devices and play the test sounds.
536 	 */
537 	maxdev = find_num_devices();
538 	if (maxdev < 1) {
539 		(void) printf(_("\n*** No audio hardware available ***\n"));
540 		exit(-1);
541 	}
542 
543 	numdev = (argc - optind);
544 	do {
545 		char *dn;
546 		oss_audioinfo	ainfo;
547 		int rv;
548 
549 		status = 0;
550 		if (numdev > 0) {
551 			for (t = 0; t < numdev; t++) {
552 				dn = argv[optind + t];
553 				rv = test_device(dn, flags, tcfg);
554 				if (rv < 0) {
555 					errors++;
556 				} else if (rv) {
557 					status++;
558 				}
559 			}
560 		} else {
561 			for (t = 0; t < maxdev; t++) {
562 				ainfo.dev = t;
563 				if (ioctl(mixerfd, SNDCTL_AUDIOINFO,
564 				    &ainfo) == -1) {
565 					perror("SNDCTL_AUDIOINFO");
566 					status++;
567 					continue;
568 				}
569 				dn = ainfo.devnode;
570 				rv = test_device(dn, flags, tcfg);
571 				if (rv < 0) {
572 					errors++;
573 				} else if (rv) {
574 					status++;
575 				}
576 			}
577 		}
578 
579 		if (errors == 0)
580 			(void) printf(_("\n*** All tests completed OK ***\n"));
581 		else
582 			(void) printf(_("\n*** Errors were detected ***\n"));
583 
584 	} while (status && (flags & TF_LOOP));
585 
586 	(void) close(mixerfd);
587 
588 	return (status);
589 }
590