Home | History | Annotate | Line # | Download | only in libossaudio
oss_dsp.c revision 1.1
      1 /*	$NetBSD: oss_dsp.c,v 1.1 2021/06/08 18:43:54 nia Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 1997-2021 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #include <sys/cdefs.h>
     30 __RCSID("$NetBSD: oss_dsp.c,v 1.1 2021/06/08 18:43:54 nia Exp $");
     31 
     32 #include <sys/audioio.h>
     33 #include <stdbool.h>
     34 #include <errno.h>
     35 #include "internal.h"
     36 
     37 #define GETPRINFO(info, name)	\
     38 	(((info)->mode == AUMODE_RECORD) \
     39 	    ? (info)->record.name : (info)->play.name)
     40 
     41 static int get_vol(u_int, u_char);
     42 static void set_vol(int, int, bool);
     43 
     44 static void set_channels(int, int, int);
     45 
     46 oss_private int
     47 _oss_dsp_ioctl(int fd, unsigned long com, void *argp)
     48 {
     49 
     50 	struct audio_info tmpinfo, hwfmt;
     51 	struct audio_offset tmpoffs;
     52 	struct audio_buf_info bufinfo;
     53 	struct audio_errinfo *tmperrinfo;
     54 	struct count_info cntinfo;
     55 	struct audio_encoding tmpenc;
     56 	u_int u;
     57 	u_int encoding;
     58 	u_int precision;
     59 	int perrors, rerrors;
     60 	static int totalperrors = 0;
     61 	static int totalrerrors = 0;
     62 	oss_mixer_enuminfo *ei;
     63 	oss_count_t osscount;
     64 	int idat;
     65 	int retval;
     66 
     67 	idat = 0;
     68 
     69 	switch (com) {
     70 	case SNDCTL_DSP_HALT_INPUT:
     71 	case SNDCTL_DSP_HALT_OUTPUT:
     72 	case SNDCTL_DSP_RESET:
     73 		retval = ioctl(fd, AUDIO_FLUSH, 0);
     74 		if (retval < 0)
     75 			return retval;
     76 		break;
     77 	case SNDCTL_DSP_SYNC:
     78 		retval = ioctl(fd, AUDIO_DRAIN, 0);
     79 		if (retval < 0)
     80 			return retval;
     81 		break;
     82 	case SNDCTL_DSP_GETERROR:
     83 		tmperrinfo = (struct audio_errinfo *)argp;
     84 		if (tmperrinfo == NULL) {
     85 			errno = EINVAL;
     86 			return -1;
     87 		}
     88 		memset(tmperrinfo, 0, sizeof(struct audio_errinfo));
     89 		if ((retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo)) < 0)
     90 			return retval;
     91 		/*
     92 		 * OSS requires that we return counters that are relative to
     93 		 * the last call. We must maintain state here...
     94 		 */
     95 		if (ioctl(fd, AUDIO_PERROR, &perrors) != -1) {
     96 			perrors /= ((tmpinfo.play.precision / NBBY) *
     97 			    tmpinfo.play.channels);
     98 			tmperrinfo->play_underruns =
     99 			    (perrors / tmpinfo.blocksize) - totalperrors;
    100 			totalperrors += tmperrinfo->play_underruns;
    101 		}
    102 		if (ioctl(fd, AUDIO_RERROR, &rerrors) != -1) {
    103 			rerrors /= ((tmpinfo.record.precision / NBBY) *
    104 			    tmpinfo.record.channels);
    105 			tmperrinfo->rec_overruns =
    106 			    (rerrors / tmpinfo.blocksize) - totalrerrors;
    107 			totalrerrors += tmperrinfo->rec_overruns;
    108 		}
    109 		break;
    110 	case SNDCTL_DSP_COOKEDMODE:
    111 		/*
    112 		 * NetBSD is always running in "cooked mode" - the kernel
    113 		 * always performs format conversions.
    114 		 */
    115 		INTARG = 1;
    116 		break;
    117 	case SNDCTL_DSP_POST:
    118 		/* This call is merely advisory, and may be a nop. */
    119 		break;
    120 	case SNDCTL_DSP_SPEED:
    121 		/*
    122 		 * In Solaris, 0 is used a special value to query the
    123 		 * current rate. This seems useful to support.
    124 		 */
    125 		if (INTARG == 0) {
    126 			retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    127 			if (retval < 0)
    128 				return retval;
    129 			retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt);
    130 			if (retval < 0)
    131 				return retval;
    132 			INTARG = (tmpinfo.mode == AUMODE_RECORD) ?
    133 			    hwfmt.record.sample_rate :
    134 			    hwfmt.play.sample_rate;
    135 		}
    136 		/*
    137 		 * Conform to kernel limits.
    138 		 * NetBSD will reject unsupported sample rates, but OSS
    139 		 * applications need to be able to negotiate a supported one.
    140 		 */
    141 		if (INTARG < 1000)
    142 			INTARG = 1000;
    143 		if (INTARG > 192000)
    144 			INTARG = 192000;
    145 		AUDIO_INITINFO(&tmpinfo);
    146 		tmpinfo.play.sample_rate =
    147 		tmpinfo.record.sample_rate = INTARG;
    148 		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    149 		if (retval < 0)
    150 			return retval;
    151 		/* FALLTHRU */
    152 	case SOUND_PCM_READ_RATE:
    153 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    154 		if (retval < 0)
    155 			return retval;
    156 		INTARG = GETPRINFO(&tmpinfo, sample_rate);
    157 		break;
    158 	case SNDCTL_DSP_STEREO:
    159 		AUDIO_INITINFO(&tmpinfo);
    160 		tmpinfo.play.channels =
    161 		tmpinfo.record.channels = INTARG ? 2 : 1;
    162 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    163 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    164 		if (retval < 0)
    165 			return retval;
    166 		INTARG = GETPRINFO(&tmpinfo, channels) - 1;
    167 		break;
    168 	case SNDCTL_DSP_GETBLKSIZE:
    169 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    170 		if (retval < 0)
    171 			return retval;
    172 		INTARG = tmpinfo.blocksize;
    173 		break;
    174 	case SNDCTL_DSP_SETFMT:
    175 		AUDIO_INITINFO(&tmpinfo);
    176 		switch (INTARG) {
    177 		case AFMT_MU_LAW:
    178 			tmpinfo.play.precision =
    179 			tmpinfo.record.precision = 8;
    180 			tmpinfo.play.encoding =
    181 			tmpinfo.record.encoding = AUDIO_ENCODING_ULAW;
    182 			break;
    183 		case AFMT_A_LAW:
    184 			tmpinfo.play.precision =
    185 			tmpinfo.record.precision = 8;
    186 			tmpinfo.play.encoding =
    187 			tmpinfo.record.encoding = AUDIO_ENCODING_ALAW;
    188 			break;
    189 		case AFMT_U8:
    190 			tmpinfo.play.precision =
    191 			tmpinfo.record.precision = 8;
    192 			tmpinfo.play.encoding =
    193 			tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR;
    194 			break;
    195 		case AFMT_S8:
    196 			tmpinfo.play.precision =
    197 			tmpinfo.record.precision = 8;
    198 			tmpinfo.play.encoding =
    199 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR;
    200 			break;
    201 		case AFMT_S16_LE:
    202 			tmpinfo.play.precision =
    203 			tmpinfo.record.precision = 16;
    204 			tmpinfo.play.encoding =
    205 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
    206 			break;
    207 		case AFMT_S16_BE:
    208 			tmpinfo.play.precision =
    209 			tmpinfo.record.precision = 16;
    210 			tmpinfo.play.encoding =
    211 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_BE;
    212 			break;
    213 		case AFMT_U16_LE:
    214 			tmpinfo.play.precision =
    215 			tmpinfo.record.precision = 16;
    216 			tmpinfo.play.encoding =
    217 			tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR_LE;
    218 			break;
    219 		case AFMT_U16_BE:
    220 			tmpinfo.play.precision =
    221 			tmpinfo.record.precision = 16;
    222 			tmpinfo.play.encoding =
    223 			tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR_BE;
    224 			break;
    225 		/*
    226 		 * XXX: When the kernel supports 24-bit LPCM by default,
    227 		 * the 24-bit formats should be handled properly instead
    228 		 * of falling back to 32 bits.
    229 		 */
    230 		case AFMT_S24_PACKED:
    231 		case AFMT_S24_LE:
    232 		case AFMT_S32_LE:
    233 			tmpinfo.play.precision =
    234 			tmpinfo.record.precision = 32;
    235 			tmpinfo.play.encoding =
    236 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE;
    237 			break;
    238 		case AFMT_S24_BE:
    239 		case AFMT_S32_BE:
    240 			tmpinfo.play.precision =
    241 			tmpinfo.record.precision = 32;
    242 			tmpinfo.play.encoding =
    243 			tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_BE;
    244 			break;
    245 		case AFMT_AC3:
    246 			tmpinfo.play.precision =
    247 			tmpinfo.record.precision = 16;
    248 			tmpinfo.play.encoding =
    249 			tmpinfo.record.encoding = AUDIO_ENCODING_AC3;
    250 			break;
    251 		default:
    252 			/*
    253 			 * OSSv4 specifies that if an invalid format is chosen
    254 			 * by an application then a sensible format supported
    255 			 * by the hardware is returned.
    256 			 *
    257 			 * In this case, we pick the current hardware format.
    258 			 */
    259 			retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt);
    260 			if (retval < 0)
    261 				return retval;
    262 			retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
    263 			if (retval < 0)
    264 				return retval;
    265 			tmpinfo.play.encoding =
    266 			tmpinfo.record.encoding =
    267 			    (tmpinfo.mode == AUMODE_RECORD) ?
    268 			    hwfmt.record.encoding : hwfmt.play.encoding;
    269 			tmpinfo.play.precision =
    270 			tmpinfo.record.precision =
    271 			    (tmpinfo.mode == AUMODE_RECORD) ?
    272 			    hwfmt.record.precision : hwfmt.play.precision ;
    273 			break;
    274 		}
    275 		/*
    276 		 * In the post-kernel-mixer world, assume that any error means
    277 		 * it's fatal rather than an unsupported format being selected.
    278 		 */
    279 		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    280 		if (retval < 0)
    281 			return retval;
    282 		/* FALLTHRU */
    283 	case SOUND_PCM_READ_BITS:
    284 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    285 		if (retval < 0)
    286 			return retval;
    287 		encoding = GETPRINFO(&tmpinfo, encoding);
    288 		precision = GETPRINFO(&tmpinfo, precision);
    289 		switch (encoding) {
    290 		case AUDIO_ENCODING_ULAW:
    291 			idat = AFMT_MU_LAW;
    292 			break;
    293 		case AUDIO_ENCODING_ALAW:
    294 			idat = AFMT_A_LAW;
    295 			break;
    296 		case AUDIO_ENCODING_SLINEAR_LE:
    297 			if (precision == 32)
    298 				idat = AFMT_S32_LE;
    299 			else if (precision == 24)
    300 				idat = AFMT_S24_LE;
    301 			else if (precision == 16)
    302 				idat = AFMT_S16_LE;
    303 			else
    304 				idat = AFMT_S8;
    305 			break;
    306 		case AUDIO_ENCODING_SLINEAR_BE:
    307 			if (precision == 32)
    308 				idat = AFMT_S32_BE;
    309 			else if (precision == 24)
    310 				idat = AFMT_S24_BE;
    311 			else if (precision == 16)
    312 				idat = AFMT_S16_BE;
    313 			else
    314 				idat = AFMT_S8;
    315 			break;
    316 		case AUDIO_ENCODING_ULINEAR_LE:
    317 			if (precision == 16)
    318 				idat = AFMT_U16_LE;
    319 			else
    320 				idat = AFMT_U8;
    321 			break;
    322 		case AUDIO_ENCODING_ULINEAR_BE:
    323 			if (precision == 16)
    324 				idat = AFMT_U16_BE;
    325 			else
    326 				idat = AFMT_U8;
    327 			break;
    328 		case AUDIO_ENCODING_ADPCM:
    329 			idat = AFMT_IMA_ADPCM;
    330 			break;
    331 		case AUDIO_ENCODING_AC3:
    332 			idat = AFMT_AC3;
    333 			break;
    334 		}
    335 		INTARG = idat;
    336 		break;
    337 	case SNDCTL_DSP_CHANNELS:
    338 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    339 		if (retval < 0)
    340 			return retval;
    341 		set_channels(fd, tmpinfo.mode, INTARG);
    342 		/* FALLTHRU */
    343 	case SOUND_PCM_READ_CHANNELS:
    344 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    345 		if (retval < 0)
    346 			return retval;
    347 		INTARG = GETPRINFO(&tmpinfo, channels);
    348 		break;
    349 	case SOUND_PCM_WRITE_FILTER:
    350 	case SOUND_PCM_READ_FILTER:
    351 		errno = EINVAL;
    352 		return -1; /* XXX unimplemented */
    353 	case SNDCTL_DSP_SUBDIVIDE:
    354 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    355 		if (retval < 0)
    356 			return retval;
    357 		idat = INTARG;
    358 		if (idat == 0)
    359 			idat = tmpinfo.play.buffer_size / tmpinfo.blocksize;
    360 		idat = (tmpinfo.play.buffer_size / idat) & -4;
    361 		AUDIO_INITINFO(&tmpinfo);
    362 		tmpinfo.blocksize = idat;
    363 		retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    364 		if (retval < 0)
    365 			return retval;
    366 		INTARG = tmpinfo.play.buffer_size / tmpinfo.blocksize;
    367 		break;
    368 	case SNDCTL_DSP_SETFRAGMENT:
    369 		AUDIO_INITINFO(&tmpinfo);
    370 		idat = INTARG;
    371 		tmpinfo.blocksize = 1 << (idat & 0xffff);
    372 		tmpinfo.hiwat = ((unsigned)idat >> 16) & 0x7fff;
    373 		if (tmpinfo.hiwat == 0)	/* 0 means set to max */
    374 			tmpinfo.hiwat = 65536;
    375 		(void) ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    376 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    377 		if (retval < 0)
    378 			return retval;
    379 		u = tmpinfo.blocksize;
    380 		for(idat = 0; u > 1; idat++, u >>= 1)
    381 			;
    382 		idat |= (tmpinfo.hiwat & 0x7fff) << 16;
    383 		INTARG = idat;
    384 		break;
    385 	case SNDCTL_DSP_GETFMTS:
    386 		for(idat = 0, tmpenc.index = 0;
    387 		    ioctl(fd, AUDIO_GETENC, &tmpenc) == 0;
    388 		    tmpenc.index++) {
    389 			switch(tmpenc.encoding) {
    390 			case AUDIO_ENCODING_ULAW:
    391 				idat |= AFMT_MU_LAW;
    392 				break;
    393 			case AUDIO_ENCODING_ALAW:
    394 				idat |= AFMT_A_LAW;
    395 				break;
    396 			case AUDIO_ENCODING_SLINEAR:
    397 				idat |= AFMT_S8;
    398 				break;
    399 			case AUDIO_ENCODING_SLINEAR_LE:
    400 				if (tmpenc.precision == 32)
    401 					idat |= AFMT_S32_LE;
    402 				else if (tmpenc.precision == 24)
    403 					idat |= AFMT_S24_LE;
    404 				else if (tmpenc.precision == 16)
    405 					idat |= AFMT_S16_LE;
    406 				else
    407 					idat |= AFMT_S8;
    408 				break;
    409 			case AUDIO_ENCODING_SLINEAR_BE:
    410 				if (tmpenc.precision == 32)
    411 					idat |= AFMT_S32_BE;
    412 				else if (tmpenc.precision == 24)
    413 					idat |= AFMT_S24_BE;
    414 				else if (tmpenc.precision == 16)
    415 					idat |= AFMT_S16_BE;
    416 				else
    417 					idat |= AFMT_S8;
    418 				break;
    419 			case AUDIO_ENCODING_ULINEAR:
    420 				idat |= AFMT_U8;
    421 				break;
    422 			case AUDIO_ENCODING_ULINEAR_LE:
    423 				if (tmpenc.precision == 16)
    424 					idat |= AFMT_U16_LE;
    425 				else
    426 					idat |= AFMT_U8;
    427 				break;
    428 			case AUDIO_ENCODING_ULINEAR_BE:
    429 				if (tmpenc.precision == 16)
    430 					idat |= AFMT_U16_BE;
    431 				else
    432 					idat |= AFMT_U8;
    433 				break;
    434 			case AUDIO_ENCODING_ADPCM:
    435 				idat |= AFMT_IMA_ADPCM;
    436 				break;
    437 			case AUDIO_ENCODING_AC3:
    438 				idat |= AFMT_AC3;
    439 				break;
    440 			default:
    441 				break;
    442 			}
    443 		}
    444 		INTARG = idat;
    445 		break;
    446 	case SNDCTL_DSP_GETOSPACE:
    447 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    448 		if (retval < 0)
    449 			return retval;
    450 		bufinfo.fragsize = tmpinfo.blocksize;
    451 		bufinfo.fragments = tmpinfo.hiwat - (tmpinfo.play.seek
    452 		    + tmpinfo.blocksize - 1) / tmpinfo.blocksize;
    453 		bufinfo.fragstotal = tmpinfo.hiwat;
    454 		bufinfo.bytes = tmpinfo.hiwat * tmpinfo.blocksize
    455 		    - tmpinfo.play.seek;
    456 		*(struct audio_buf_info *)argp = bufinfo;
    457 		break;
    458 	case SNDCTL_DSP_GETISPACE:
    459 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    460 		if (retval < 0)
    461 			return retval;
    462 		bufinfo.fragsize = tmpinfo.blocksize;
    463 		bufinfo.fragments = tmpinfo.record.seek / tmpinfo.blocksize;
    464 		bufinfo.fragstotal =
    465 		    tmpinfo.record.buffer_size / tmpinfo.blocksize;
    466 		bufinfo.bytes = tmpinfo.record.seek;
    467 		*(struct audio_buf_info *)argp = bufinfo;
    468 		break;
    469 	case SNDCTL_DSP_NONBLOCK:
    470 		idat = 1;
    471 		retval = ioctl(fd, FIONBIO, &idat);
    472 		if (retval < 0)
    473 			return retval;
    474 		break;
    475 	case SNDCTL_DSP_GETCAPS:
    476 		retval = _oss_get_caps(fd, (int *)argp);
    477 		if (retval < 0)
    478 			return retval;
    479 		break;
    480 	case SNDCTL_DSP_SETTRIGGER:
    481 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    482 		if (retval < 0)
    483 			return retval;
    484 		AUDIO_INITINFO(&tmpinfo);
    485 		if (tmpinfo.mode & AUMODE_PLAY)
    486 			tmpinfo.play.pause = (INTARG & PCM_ENABLE_OUTPUT) == 0;
    487 		if (tmpinfo.mode & AUMODE_RECORD)
    488 			tmpinfo.record.pause = (INTARG & PCM_ENABLE_INPUT) == 0;
    489 		(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    490 		/* FALLTHRU */
    491 	case SNDCTL_DSP_GETTRIGGER:
    492 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    493 		if (retval < 0)
    494 			return retval;
    495 		idat = 0;
    496 		if ((tmpinfo.mode & AUMODE_PLAY) && !tmpinfo.play.pause)
    497 			idat |= PCM_ENABLE_OUTPUT;
    498 		if ((tmpinfo.mode & AUMODE_RECORD) && !tmpinfo.record.pause)
    499 			idat |= PCM_ENABLE_INPUT;
    500 		INTARG = idat;
    501 		break;
    502 	case SNDCTL_DSP_GETIPTR:
    503 		retval = ioctl(fd, AUDIO_GETIOFFS, &tmpoffs);
    504 		if (retval < 0)
    505 			return retval;
    506 		cntinfo.bytes = tmpoffs.samples;
    507 		cntinfo.blocks = tmpoffs.deltablks;
    508 		cntinfo.ptr = tmpoffs.offset;
    509 		*(struct count_info *)argp = cntinfo;
    510 		break;
    511 	case SNDCTL_DSP_CURRENT_IPTR:
    512 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    513 		if (retval < 0)
    514 			return retval;
    515 		/* XXX: 'samples' may wrap */
    516 		memset(osscount.filler, 0, sizeof(osscount.filler));
    517 		osscount.samples = tmpinfo.record.samples /
    518 		    ((tmpinfo.record.precision / NBBY) *
    519 			tmpinfo.record.channels);
    520 		osscount.fifo_samples = tmpinfo.record.seek /
    521 		    ((tmpinfo.record.precision / NBBY) *
    522 			tmpinfo.record.channels);
    523 		*(oss_count_t *)argp = osscount;
    524 		break;
    525 	case SNDCTL_DSP_GETOPTR:
    526 		retval = ioctl(fd, AUDIO_GETOOFFS, &tmpoffs);
    527 		if (retval < 0)
    528 			return retval;
    529 		cntinfo.bytes = tmpoffs.samples;
    530 		cntinfo.blocks = tmpoffs.deltablks;
    531 		cntinfo.ptr = tmpoffs.offset;
    532 		*(struct count_info *)argp = cntinfo;
    533 		break;
    534 	case SNDCTL_DSP_CURRENT_OPTR:
    535 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    536 		if (retval < 0)
    537 			return retval;
    538 		/* XXX: 'samples' may wrap */
    539 		memset(osscount.filler, 0, sizeof(osscount.filler));
    540 		osscount.samples = tmpinfo.play.samples /
    541 		    ((tmpinfo.play.precision / NBBY) *
    542 			tmpinfo.play.channels);
    543 		osscount.fifo_samples = tmpinfo.play.seek /
    544 		    ((tmpinfo.play.precision / NBBY) *
    545 			tmpinfo.play.channels);
    546 		*(oss_count_t *)argp = osscount;
    547 		break;
    548 	case SNDCTL_DSP_SETPLAYVOL:
    549 		set_vol(fd, INTARG, false);
    550 		/* FALLTHRU */
    551 	case SNDCTL_DSP_GETPLAYVOL:
    552 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
    553 		if (retval < 0)
    554 			return retval;
    555 		INTARG = get_vol(tmpinfo.play.gain, tmpinfo.play.balance);
    556 		break;
    557 	case SNDCTL_DSP_SETRECVOL:
    558 		set_vol(fd, INTARG, true);
    559 		/* FALLTHRU */
    560 	case SNDCTL_DSP_GETRECVOL:
    561 		retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo);
    562 		if (retval < 0)
    563 			return retval;
    564 		INTARG = get_vol(tmpinfo.record.gain, tmpinfo.record.balance);
    565 		break;
    566 	case SNDCTL_DSP_SKIP:
    567 	case SNDCTL_DSP_SILENCE:
    568 		errno = EINVAL;
    569 		return -1;
    570 	case SNDCTL_DSP_SETDUPLEX:
    571 		idat = 1;
    572 		retval = ioctl(fd, AUDIO_SETFD, &idat);
    573 		if (retval < 0)
    574 			return retval;
    575 		break;
    576 	case SNDCTL_DSP_GETODELAY:
    577 		retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo);
    578 		if (retval < 0)
    579 			return retval;
    580 		idat = tmpinfo.play.seek + tmpinfo.blocksize / 2;
    581 		INTARG = idat;
    582 		break;
    583 	case SNDCTL_DSP_PROFILE:
    584 		/* This gives just a hint to the driver,
    585 		 * implementing it as a NOP is ok
    586 		 */
    587 		break;
    588 	case SNDCTL_DSP_MAPINBUF:
    589 	case SNDCTL_DSP_MAPOUTBUF:
    590 	case SNDCTL_DSP_SETSYNCRO:
    591 		errno = EINVAL;
    592 		return -1; /* XXX unimplemented */
    593 	case SNDCTL_DSP_GET_PLAYTGT_NAMES:
    594 	case SNDCTL_DSP_GET_RECSRC_NAMES:
    595 		ei = (oss_mixer_enuminfo *)argp;
    596 		ei->nvalues = 1;
    597 		ei->version = 0;
    598 		ei->strindex[0] = 0;
    599 		strlcpy(ei->strings, "primary", OSS_ENUM_STRINGSIZE);
    600 		break;
    601 	case SNDCTL_DSP_SET_PLAYTGT:
    602 	case SNDCTL_DSP_SET_RECSRC:
    603 	case SNDCTL_DSP_GET_PLAYTGT:
    604 	case SNDCTL_DSP_GET_RECSRC:
    605 		/* We have one recording source and play target. */
    606 		INTARG = 0;
    607 		break;
    608 	default:
    609 		errno = EINVAL;
    610 		return -1;
    611 	}
    612 
    613 	return 0;
    614 }
    615 
    616 static int
    617 get_vol(u_int gain, u_char balance)
    618 {
    619 	u_int l, r;
    620 
    621 	if (balance == AUDIO_MID_BALANCE) {
    622 		l = r = gain;
    623 	} else if (balance < AUDIO_MID_BALANCE) {
    624 		l = gain;
    625 		r = (balance * gain) / AUDIO_MID_BALANCE;
    626 	} else {
    627 		r = gain;
    628 		l = ((AUDIO_RIGHT_BALANCE - balance) * gain)
    629 		    / AUDIO_MID_BALANCE;
    630 	}
    631 
    632 	return TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
    633 }
    634 
    635 static void
    636 set_vol(int fd, int volume, bool record)
    637 {
    638 	u_int lgain, rgain;
    639 	struct audio_info tmpinfo;
    640 	struct audio_prinfo *prinfo;
    641 
    642 	AUDIO_INITINFO(&tmpinfo);
    643 	prinfo = record ? &tmpinfo.record : &tmpinfo.play;
    644 
    645 	lgain = FROM_OSSVOL((volume >> 0) & 0xff);
    646 	rgain = FROM_OSSVOL((volume >> 8) & 0xff);
    647 
    648 	if (lgain == rgain) {
    649 		prinfo->gain = lgain;
    650 		prinfo->balance = AUDIO_MID_BALANCE;
    651 	} else if (lgain < rgain) {
    652 		prinfo->gain = rgain;
    653 		prinfo->balance = AUDIO_RIGHT_BALANCE -
    654 		    (AUDIO_MID_BALANCE * lgain) / rgain;
    655 	} else {
    656 		prinfo->gain = lgain;
    657 		prinfo->balance = (AUDIO_MID_BALANCE * rgain) / lgain;
    658 	}
    659 
    660 	(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    661 }
    662 
    663 /*
    664  * When AUDIO_SETINFO fails to set a channel count, the application's chosen
    665  * number is out of range of what the kernel allows.
    666  *
    667  * When this happens, we use the current hardware settings. This is just in
    668  * case an application is abusing SNDCTL_DSP_CHANNELS - OSSv4 always sets and
    669  * returns a reasonable value, even if it wasn't what the user requested.
    670  *
    671  * Solaris guarantees this behaviour if nchannels = 0.
    672  *
    673  * XXX: If a device is opened for both playback and recording, and supports
    674  * fewer channels for recording than playback, applications that do both will
    675  * behave very strangely. OSS doesn't allow for reporting separate channel
    676  * counts for recording and playback. This could be worked around by always
    677  * mixing recorded data up to the same number of channels as is being used
    678  * for playback.
    679  */
    680 static void
    681 set_channels(int fd, int mode, int nchannels)
    682 {
    683 	struct audio_info tmpinfo, hwfmt;
    684 
    685 	if (ioctl(fd, AUDIO_GETFORMAT, &hwfmt) < 0) {
    686 		errno = 0;
    687 		hwfmt.record.channels = hwfmt.play.channels = 2;
    688 	}
    689 
    690 	if (mode & AUMODE_PLAY) {
    691 		AUDIO_INITINFO(&tmpinfo);
    692 		tmpinfo.play.channels = nchannels;
    693 		if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) {
    694 			errno = 0;
    695 			AUDIO_INITINFO(&tmpinfo);
    696 			tmpinfo.play.channels = hwfmt.play.channels;
    697 			(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    698 		}
    699 	}
    700 
    701 	if (mode & AUMODE_RECORD) {
    702 		AUDIO_INITINFO(&tmpinfo);
    703 		tmpinfo.record.channels = nchannels;
    704 		if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) {
    705 			errno = 0;
    706 			AUDIO_INITINFO(&tmpinfo);
    707 			tmpinfo.record.channels = hwfmt.record.channels;
    708 			(void)ioctl(fd, AUDIO_SETINFO, &tmpinfo);
    709 		}
    710 	}
    711 }
    712