xref: /netbsd-src/lib/libossaudio/oss4_mixer.c (revision 8170080d8e472ec3eaf475abefc490c1db8644ab)
1 /*	$NetBSD: oss4_mixer.c,v 1.1 2021/06/08 18:43:54 nia Exp $	*/
2 
3 /*-
4  * Copyright (c) 2020-2021 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Nia Alarie.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 #include <sys/audioio.h>
32 #include <sys/fcntl.h>
33 #include <sys/stat.h>
34 #include <errno.h>
35 #include <limits.h>
36 #include <stdio.h>
37 #include <unistd.h>
38 #include "internal.h"
39 
40 static int get_audio_count(void);
41 static int get_mixer_count(void);
42 static int get_mixer_control_count(int);
43 
44 oss_private int
_oss4_mixer_ioctl(int fd,unsigned long com,void * argp)45 _oss4_mixer_ioctl(int fd, unsigned long com, void *argp)
46 {
47 	oss_audioinfo *tmpai;
48 	oss_card_info *cardinfo;
49 	oss_mixext *ext;
50 	oss_mixext_root root;
51 	oss_mixer_enuminfo *ei;
52 	oss_mixer_value *mv;
53 	oss_mixerinfo *mi;
54 	oss_sysinfo sysinfo;
55 	dev_t devno;
56 	struct stat tmpstat;
57 	struct audio_device dev;
58 	struct audio_format_query fmtq;
59 	struct mixer_devinfo mdi;
60 	struct mixer_ctrl mc;
61 	char devname[32];
62 	size_t len;
63 	int newfd = -1, tmperrno;
64 	int i, noffs;
65 	int retval;
66 
67 	/*
68 	 * Note: it is difficult to translate the NetBSD concept of a "set"
69 	 * mixer control type to the OSSv4 API, as far as I can tell.
70 	 *
71 	 * This means they are treated like enums, i.e. only one entry in the
72 	 * set can be selected at a time.
73 	 */
74 
75 	switch (com) {
76 	case SNDCTL_AUDIOINFO:
77 	/*
78 	 * SNDCTL_AUDIOINFO_EX is intended for underlying hardware devices
79 	 * that are to be opened in "exclusive mode" (bypassing the normal
80 	 * kernel mixer for exclusive control). NetBSD does not support
81 	 * bypassing the kernel mixer, so it's an alias of SNDCTL_AUDIOINFO.
82 	 */
83 	case SNDCTL_AUDIOINFO_EX:
84 	case SNDCTL_ENGINEINFO:
85 		devno = 0;
86 		tmpai = (struct oss_audioinfo*)argp;
87 		if (tmpai == NULL) {
88 			errno = EINVAL;
89 			return -1;
90 		}
91 
92 		/*
93 		 * If the input device is -1, guess the device related to
94 		 * the open mixer device.
95 		 */
96 		if (tmpai->dev < 0) {
97 			fstat(fd, &tmpstat);
98 			if ((tmpstat.st_rdev & 0xff00) == 0x2a00)
99 				devno = tmpstat.st_rdev & 0xff;
100 			if (devno >= 0x80)
101 				tmpai->dev = devno & 0x7f;
102 		}
103 		if (tmpai->dev < 0)
104 			tmpai->dev = 0;
105 
106 		snprintf(tmpai->devnode, sizeof(tmpai->devnode),
107 		    "/dev/audio%d", tmpai->dev);
108 
109 		if ((newfd = open(tmpai->devnode, O_WRONLY)) < 0) {
110 			if ((newfd = open(tmpai->devnode, O_RDONLY)) < 0) {
111 				return newfd;
112 			}
113 		}
114 
115 		retval = ioctl(newfd, AUDIO_GETDEV, &dev);
116 		if (retval < 0) {
117 			tmperrno = errno;
118 			close(newfd);
119 			errno = tmperrno;
120 			return retval;
121 		}
122 		if (_oss_get_caps(newfd, &tmpai->caps) < 0) {
123 			tmperrno = errno;
124 			close(newfd);
125 			errno = tmperrno;
126 			return retval;
127 		}
128 		snprintf(tmpai->name, sizeof(tmpai->name),
129 		    "%s %s", dev.name, dev.version);
130 		tmpai->busy = 0;
131 		tmpai->pid = -1;
132 		_oss_dsp_ioctl(newfd, SNDCTL_DSP_GETFMTS, &tmpai->iformats);
133 		tmpai->oformats = tmpai->iformats;
134 		tmpai->magic = -1; /* reserved for "internal use" */
135 		memset(tmpai->cmd, 0, sizeof(tmpai->cmd));
136 		tmpai->card_number = -1;
137 		memset(tmpai->song_name, 0,
138 		    sizeof(tmpai->song_name));
139 		memset(tmpai->label, 0, sizeof(tmpai->label));
140 		tmpai->port_number = 0;
141 		tmpai->mixer_dev = tmpai->dev;
142 		tmpai->legacy_device = tmpai->dev;
143 		tmpai->enabled = 1;
144 		tmpai->flags = -1; /* reserved for "future versions" */
145 		tmpai->min_rate = 1000;
146 		tmpai->max_rate = 192000;
147 		tmpai->nrates = 0;
148 		tmpai->min_channels = 1;
149 		tmpai->max_channels = 2;
150 		for (fmtq.index = 0;
151 		    ioctl(newfd, AUDIO_QUERYFORMAT, &fmtq) != -1; ++fmtq.index) {
152 			if (fmtq.fmt.channels > (unsigned)tmpai->max_channels)
153 				tmpai->max_channels = fmtq.fmt.channels;
154 		}
155 		tmpai->binding = -1; /* reserved for "future versions" */
156 		tmpai->rate_source = -1;
157 		/*
158 		 * 'handle' is supposed to be globally unique. The closest
159 		 * we have to that is probably device nodes.
160 		 */
161 		strlcpy(tmpai->handle, tmpai->devnode,
162 		    sizeof(tmpai->handle));
163 		tmpai->next_play_engine = 0;
164 		tmpai->next_rec_engine = 0;
165 		argp = tmpai;
166 		close(newfd);
167 		break;
168 	case SNDCTL_CARDINFO:
169 		cardinfo = (oss_card_info *)argp;
170 		if (cardinfo == NULL) {
171 			errno = EINVAL;
172 			return -1;
173 		}
174 		if (cardinfo->card != -1) {
175 			snprintf(devname, sizeof(devname),
176 			    "/dev/audio%d", cardinfo->card);
177 			newfd = open(devname, O_RDONLY);
178 			if (newfd < 0)
179 				return newfd;
180 		} else {
181 			newfd = fd;
182 		}
183 		retval = ioctl(newfd, AUDIO_GETDEV, &dev);
184 		tmperrno = errno;
185 		if (newfd != fd)
186 			close(newfd);
187 		if (retval < 0) {
188 			errno = tmperrno;
189 			return retval;
190 		}
191 		strlcpy(cardinfo->shortname, dev.name,
192 		    sizeof(cardinfo->shortname));
193 		snprintf(cardinfo->longname, sizeof(cardinfo->longname),
194 		    "%s %s %s", dev.name, dev.version, dev.config);
195 		memset(cardinfo->hw_info, 0, sizeof(cardinfo->hw_info));
196 		/*
197 		 * OSSv4 does not document this ioctl, and claims it should
198 		 * not be used by applications and is provided for "utiltiy
199 		 * programs included in OSS". We follow the Solaris
200 		 * implementation (which is documented) and leave these fields
201 		 * unset.
202 		 */
203 		cardinfo->flags = 0;
204 		cardinfo->intr_count = 0;
205 		cardinfo->ack_count = 0;
206 		break;
207 	case SNDCTL_SYSINFO:
208 		memset(&sysinfo, 0, sizeof(sysinfo));
209 		strlcpy(sysinfo.product,
210 		    "OSS/NetBSD", sizeof(sysinfo.product));
211 		strlcpy(sysinfo.version,
212 		    "4.01", sizeof(sysinfo.version));
213 		strlcpy(sysinfo.license,
214 		    "BSD", sizeof(sysinfo.license));
215 		sysinfo.versionnum = SOUND_VERSION;
216 		sysinfo.numaudios =
217 		    sysinfo.numcards =
218 			get_audio_count();
219 		sysinfo.numaudioengines = 1;
220 		sysinfo.numsynths = 1;
221 		sysinfo.nummidis = -1;
222 		sysinfo.numtimers = -1;
223 		sysinfo.nummixers = get_mixer_count();
224 		*(struct oss_sysinfo *)argp = sysinfo;
225 		break;
226 	case SNDCTL_MIXERINFO:
227 		mi = (oss_mixerinfo *)argp;
228 		if (mi == NULL) {
229 			errno = EINVAL;
230 			return -1;
231 		}
232 		snprintf(devname, sizeof(devname), "/dev/mixer%d", mi->dev);
233 		if ((newfd = open(devname, O_RDONLY)) < 0)
234 			return newfd;
235 		retval = ioctl(newfd, AUDIO_GETDEV, &dev);
236 		if (retval < 0) {
237 			tmperrno = errno;
238 			close(newfd);
239 			errno = tmperrno;
240 			return retval;
241 		}
242 		strlcpy(mi->id, devname, sizeof(mi->id));
243 		strlcpy(mi->handle, devname, sizeof(mi->handle));
244 		snprintf(mi->name, sizeof(mi->name),
245 		    "%s %s", dev.name, dev.version);
246 		mi->card_number = mi->dev;
247 		mi->port_number = 0;
248 		mi->magic = 0;
249 		mi->enabled = 1;
250 		mi->caps = 0;
251 		mi->flags = 0;
252 		mi->nrext = get_mixer_control_count(newfd) + 1;
253 		mi->priority = UCHAR_MAX - mi->dev;
254 		strlcpy(mi->devnode, devname, sizeof(mi->devnode));
255 		mi->legacy_device = mi->dev;
256 		break;
257 	case SNDCTL_MIX_DESCRIPTION:
258 		/* No description available. */
259 		errno = ENOSYS;
260 		return -1;
261 	case SNDCTL_MIX_NRMIX:
262 		INTARG = get_mixer_count();
263 		break;
264 	case SNDCTL_MIX_NREXT:
265 		snprintf(devname, sizeof(devname), "/dev/mixer%d", INTARG);
266 		if ((newfd = open(devname, O_RDONLY)) < 0)
267 			return newfd;
268 		INTARG = get_mixer_control_count(newfd) + 1;
269 		close(newfd);
270 		break;
271 	case SNDCTL_MIX_EXTINFO:
272 		ext = (oss_mixext *)argp;
273 		snprintf(devname, sizeof(devname), "/dev/mixer%d", ext->dev);
274 		if ((newfd = open(devname, O_RDONLY)) < 0)
275 			return newfd;
276 		if (ext->ctrl == 0) {
277 			/*
278 			 * NetBSD has no concept of a "root mixer control", but
279  			 * OSSv4 requires one to work. We fake one at 0 and
280 			 * simply add 1 to all real control indexes.
281 			 */
282 			retval = ioctl(newfd, AUDIO_GETDEV, &dev);
283 			tmperrno = errno;
284 			close(newfd);
285 			if (retval < 0) {
286 				errno = tmperrno;
287 				return -1;
288 			}
289 			memset(&root, 0, sizeof(root));
290 			strlcpy(root.id, devname, sizeof(root.id));
291 			snprintf(root.name, sizeof(root.name),
292 			    "%s %s", dev.name, dev.version);
293 			strlcpy(ext->id, devname, sizeof(ext->id));
294 			snprintf(ext->extname, sizeof(ext->extname),
295 			    "%s %s", dev.name, dev.version);
296 			strlcpy(ext->extname, "root", sizeof(ext->extname));
297 			ext->type = MIXT_DEVROOT;
298 			ext->minvalue = 0;
299 			ext->maxvalue = 0;
300 			ext->flags = 0;
301 			ext->parent = -1;
302 			ext->control_no = -1;
303 			ext->update_counter = 0;
304 			ext->rgbcolor = 0;
305 			memcpy(&ext->data, &root,
306 			    sizeof(root) > sizeof(ext->data) ?
307 			    sizeof(ext->data) : sizeof(root));
308 			return 0;
309 		}
310 		mdi.index = ext->ctrl - 1;
311 		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
312 		if (retval < 0) {
313 			tmperrno = errno;
314 			close(newfd);
315 			errno = tmperrno;
316 			return retval;
317 		}
318 		ext->flags = MIXF_READABLE | MIXF_WRITEABLE | MIXF_POLL;
319 		ext->parent = mdi.mixer_class + 1;
320 		strlcpy(ext->id, mdi.label.name, sizeof(ext->id));
321 		strlcpy(ext->extname, mdi.label.name, sizeof(ext->extname));
322 		len = strlen(ext->extname);
323 		memset(ext->data, 0, sizeof(ext->data));
324 		ext->control_no = -1;
325 		ext->update_counter = 0;
326 		ext->rgbcolor = 0;
327 		switch (mdi.type) {
328 		case AUDIO_MIXER_CLASS:
329 			ext->type = MIXT_GROUP;
330 			ext->parent = 0;
331 			ext->minvalue = 0;
332 			ext->maxvalue = 0;
333 			break;
334 		case AUDIO_MIXER_ENUM:
335 			ext->maxvalue = mdi.un.e.num_mem;
336 			ext->minvalue = 0;
337 			for (i = 0; i < mdi.un.e.num_mem; ++i) {
338 				ext->enum_present[i / 8] |= (1 << (i % 8));
339 			}
340 			if (mdi.un.e.num_mem == 2) {
341 				if (!strcmp(mdi.un.e.member[0].label.name, AudioNoff) &&
342 				    !strcmp(mdi.un.e.member[1].label.name, AudioNon)) {
343 					ext->type = MIXT_MUTE;
344 				} else {
345 					ext->type = MIXT_ENUM;
346 				}
347 			} else {
348 				ext->type = MIXT_ENUM;
349 			}
350 			break;
351 		case AUDIO_MIXER_SET:
352 			ext->maxvalue = mdi.un.s.num_mem;
353 			ext->minvalue = 0;
354 #ifdef notyet
355 			/*
356 			 * XXX: This is actually the correct type for "set"
357 			 * controls, but it seems no real world software
358 			 * supports it. The only documentation exists in
359 			 * the OSSv4 headers and describes it as "reserved
360 			 * for Sun's implementation".
361 			 */
362 			ext->type = MIXT_ENUM_MULTI;
363 #else
364 			ext->type = MIXT_ENUM;
365 #endif
366 			for (i = 0; i < mdi.un.s.num_mem; ++i) {
367 				ext->enum_present[i / 8] |= (1 << (i % 8));
368 			}
369 			break;
370 		case AUDIO_MIXER_VALUE:
371 			ext->maxvalue = UCHAR_MAX + 1;
372 			ext->minvalue = 0;
373 			if (mdi.un.v.num_channels == 2) {
374 				ext->type = MIXT_STEREOSLIDER;
375 			} else {
376 				ext->type = MIXT_MONOSLIDER;
377 			}
378 			break;
379 		}
380 		close(newfd);
381 		break;
382 	case SNDCTL_MIX_ENUMINFO:
383 		ei = (oss_mixer_enuminfo *)argp;
384 		if (ei == NULL) {
385 			errno = EINVAL;
386 			return -1;
387 		}
388 		if (ei->ctrl == 0) {
389 			errno = EINVAL;
390 			return -1;
391 		}
392 		snprintf(devname, sizeof(devname), "/dev/mixer%d", ei->dev);
393 		if ((newfd = open(devname, O_RDONLY)) < 0)
394 			return newfd;
395 		mdi.index = ei->ctrl - 1;
396 		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
397 		tmperrno = errno;
398 		close(newfd);
399 		if (retval < 0) {
400 			errno = tmperrno;
401 			return retval;
402 		}
403 		ei->version = 0;
404 		switch (mdi.type) {
405 		case AUDIO_MIXER_ENUM:
406 			ei->nvalues = mdi.un.e.num_mem;
407 			noffs = 0;
408 			for (i = 0; i < ei->nvalues; ++i) {
409 				ei->strindex[i] = noffs;
410 				len = strlen(mdi.un.e.member[i].label.name) + 1;
411 				if ((noffs + len) >= sizeof(ei->strings)) {
412 				    errno = ENOMEM;
413 				    return -1;
414 				}
415 				memcpy(ei->strings + noffs,
416 				    mdi.un.e.member[i].label.name, len);
417 				noffs += len;
418 			}
419 			break;
420 		case AUDIO_MIXER_SET:
421 			ei->nvalues = mdi.un.s.num_mem;
422 			noffs = 0;
423 			for (i = 0; i < ei->nvalues; ++i) {
424 				ei->strindex[i] = noffs;
425 				len = strlen(mdi.un.s.member[i].label.name) + 1;
426 				if ((noffs + len) >= sizeof(ei->strings)) {
427 				    errno = ENOMEM;
428 				    return -1;
429 				}
430 				memcpy(ei->strings + noffs,
431 				    mdi.un.s.member[i].label.name, len);
432 				noffs += len;
433 			}
434 			break;
435 		default:
436 			errno = EINVAL;
437 			return -1;
438 		}
439 		break;
440 	case SNDCTL_MIX_WRITE:
441 		mv = (oss_mixer_value *)argp;
442 		if (mv == NULL) {
443 			errno = EINVAL;
444 			return -1;
445 		}
446 		if (mv->ctrl == 0) {
447 			errno = EINVAL;
448 			return -1;
449 		}
450 		snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev);
451 		if ((newfd = open(devname, O_RDWR)) < 0)
452 			return newfd;
453 		mdi.index = mc.dev = mv->ctrl - 1;
454 		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
455 		if (retval < 0) {
456 			tmperrno = errno;
457 			close(newfd);
458 			errno = tmperrno;
459 			return retval;
460 		}
461 		mc.type = mdi.type;
462 		switch (mdi.type) {
463 		case AUDIO_MIXER_ENUM:
464 			if (mv->value >= mdi.un.e.num_mem) {
465 				close(newfd);
466 				errno = EINVAL;
467 				return -1;
468 			}
469 			mc.un.ord = mdi.un.e.member[mv->value].ord;
470 			break;
471 		case AUDIO_MIXER_SET:
472 			if (mv->value >= mdi.un.s.num_mem) {
473 				close(newfd);
474 				errno = EINVAL;
475 				return -1;
476 			}
477 #ifdef notyet
478 			mc.un.mask = 0;
479 			for (i = 0; i < mdi.un.s.num_mem; ++i) {
480 				if (mv->value & (1 << i)) {
481 					mc.un.mask |= mdi.un.s.member[mv->value].mask;
482 				}
483 			}
484 #else
485 			mc.un.mask = mdi.un.s.member[mv->value].mask;
486 #endif
487 			break;
488 		case AUDIO_MIXER_VALUE:
489 			mc.un.value.num_channels = mdi.un.v.num_channels;
490 			if (mdi.un.v.num_channels != 2) {
491 				for (i = 0; i < mdi.un.v.num_channels; ++i) {
492 					mc.un.value.level[i] = mv->value;
493 				}
494 			} else {
495 			    mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] =
496 				(mv->value >> 0) & 0xFF;
497 			    mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] =
498 				(mv->value >> 8) & 0xFF;
499 			}
500 			break;
501 		}
502 		retval = ioctl(newfd, AUDIO_MIXER_WRITE, &mc);
503 		if (retval < 0) {
504 			tmperrno = errno;
505 			close(newfd);
506 			errno = tmperrno;
507 			return retval;
508 		}
509 		close(newfd);
510 		break;
511 	case SNDCTL_MIX_READ:
512 		mv = (oss_mixer_value *)argp;
513 		if (mv == NULL) {
514 			errno = EINVAL;
515 			return -1;
516 		}
517 		if (mv->ctrl == 0) {
518 			errno = EINVAL;
519 			return -1;
520 		}
521 		snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev);
522 		if ((newfd = open(devname, O_RDWR)) < 0)
523 			return newfd;
524 		mdi.index = mc.dev = (mv->ctrl - 1);
525 		retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi);
526 		if (retval < 0) {
527 			tmperrno = errno;
528 			close(newfd);
529 			errno = tmperrno;
530 			return retval;
531 		}
532 		mc.dev = mdi.index;
533 		mc.type = mdi.type;
534 		if (mdi.type == AUDIO_MIXER_VALUE)
535 			mc.un.value.num_channels = mdi.un.v.num_channels;
536 		retval = ioctl(newfd, AUDIO_MIXER_READ, &mc);
537 		if (retval < 0) {
538 			tmperrno = errno;
539 			close(newfd);
540 			errno = tmperrno;
541 			return retval;
542 		}
543 		close(newfd);
544 		mv->value = 0;
545 		switch (mdi.type) {
546 		case AUDIO_MIXER_ENUM:
547 			for (i = 0; i < mdi.un.e.num_mem; ++i) {
548 				if (mc.un.ord == mdi.un.e.member[i].ord) {
549 					mv->value = i;
550 					break;
551 				}
552 			}
553 			break;
554 		case AUDIO_MIXER_SET:
555 			for (i = 0; i < mdi.un.s.num_mem; ++i) {
556 #ifdef notyet
557 				if (mc.un.mask & mdi.un.s.member[i].mask)
558 					mv->value |= (1 << i);
559 #else
560 				if (mc.un.mask == mdi.un.s.member[i].mask) {
561 					mv->value = i;
562 					break;
563 				}
564 #endif
565 			}
566 			break;
567 		case AUDIO_MIXER_VALUE:
568 			if (mdi.un.v.num_channels != 2) {
569 				mv->value = mc.un.value.level[0];
570 			} else {
571 				mv->value = \
572 				    ((mc.un.value.level[1] & 0xFF) << 8) |
573 				    ((mc.un.value.level[0] & 0xFF) << 0);
574 			}
575 			break;
576 		default:
577 			errno = EINVAL;
578 			return -1;
579 		}
580 		break;
581 	default:
582 		errno = EINVAL;
583 		return -1;
584 	}
585 	return 0;
586 }
587 
588 static int
get_audio_count(void)589 get_audio_count(void)
590 {
591 	char devname[32];
592 	int ndevs = 0;
593 	int tmpfd;
594 	int tmperrno = errno;
595 
596 	do {
597 		snprintf(devname, sizeof(devname),
598 		    "/dev/audio%d", ndevs);
599 		if ((tmpfd = open(devname, O_RDONLY)) != -1 ||
600 		    (tmpfd = open(devname, O_WRONLY)) != -1) {
601 			ndevs++;
602 			close(tmpfd);
603 		}
604 	} while (tmpfd != -1);
605 	errno = tmperrno;
606 	return ndevs;
607 }
608 
609 static int
get_mixer_count(void)610 get_mixer_count(void)
611 {
612 	char devname[32];
613 	int ndevs = 0;
614 	int tmpfd;
615 	int tmperrno = errno;
616 
617 	do {
618 		snprintf(devname, sizeof(devname),
619 		    "/dev/mixer%d", ndevs);
620 		if ((tmpfd = open(devname, O_RDONLY)) != -1) {
621 			ndevs++;
622 			close(tmpfd);
623 		}
624 	} while (tmpfd != -1);
625 	errno = tmperrno;
626 	return ndevs;
627 }
628 
629 static int
get_mixer_control_count(int fd)630 get_mixer_control_count(int fd)
631 {
632 	struct mixer_devinfo mdi;
633 	int ndevs = 0;
634 
635 	do {
636 		mdi.index = ndevs++;
637 	} while (ioctl(fd, AUDIO_MIXER_DEVINFO, &mdi) != -1);
638 
639 	return ndevs > 0 ? ndevs - 1 : 0;
640 }
641