audiotest.c revision 9484:fbd5ddc28e96
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
73typedef struct chancfg {
74	int		mask;
75	const char	*name;
76	unsigned	flags;
77	int16_t		*data;
78	int		len;
79} chancfg_t;
80
81typedef 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
105chancfg_t ch_left = { CH_LEFT, NM_LEFT, 0 };
106chancfg_t ch_right = { CH_RIGHT, NM_RIGHT, 0 };
107chancfg_t ch_stereo = { CH_STEREO, NM_STEREO, 0 };
108
109chancfg_t ch_center = { CH_CENTER, NM_CENTER, 0 };
110chancfg_t ch_lfe = { CH_LFE, NM_LFE, CFLAG_LFE };
111
112chancfg_t ch_lsurr_4 = { (1 << 2), NM_LREAR, 0 };
113chancfg_t ch_rsurr_4 = { (1 << 3), NM_RREAR, 0 };
114chancfg_t ch_4 = { CH_4, NM_40, 0 };
115
116chancfg_t ch_lsurr_5 = { CH_LSURR, NM_LREAR, 0 };
117chancfg_t ch_rsurr_5 = { CH_RSURR, NM_RREAR, 0 };
118chancfg_t ch_5 = { CH_5, NM_50, 0 };
119
120chancfg_t ch_lsurr_7 = { CH_LSURR, NM_LSIDE, 0 };
121chancfg_t ch_rsurr_7 = { CH_RSURR, NM_RSIDE, 0 };
122chancfg_t ch_lrear_7 = { CH_LREAR, NM_LREAR, 0 };
123chancfg_t ch_rrear_7 = { CH_RREAR, NM_RREAR, 0 };
124chancfg_t ch_7 = { CH_7, NM_70, 0 };
125
126testcfg_t test_stereo = {
127	2, { &ch_left, &ch_right, &ch_stereo, NULL }
128};
129
130testcfg_t test_quad = {
131	4, { &ch_left, &ch_right, &ch_stereo,
132	&ch_lsurr_4, &ch_rsurr_4, &ch_4, NULL }
133};
134
135testcfg_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
140testcfg_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 */
154extern int uncompress_wave(short *outbuf);
155
156static int data_len;
157
158#define	MAXDEVICE   64
159extern 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
168static int mixerfd;
169static int num_devices_tested = 0;
170
171static short *sample_buf;
172
173void
174prepare(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*/
222int
223testdsp(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
340static int
341find_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
376int
377test_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
426void
427describe_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
468int
469main(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