Home | History | Annotate | Line # | Download | only in libossaudio
      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
     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
    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
    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
    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