Module Name: src Committed By: nia Date: Tue Jun 8 18:43:54 UTC 2021
Modified Files: src/lib/libossaudio: Makefile Added Files: src/lib/libossaudio: internal.h oss3_mixer.c oss4_global.c oss4_mixer.c oss_caps.c oss_dsp.c oss_ioctl.c Removed Files: src/lib/libossaudio: ossaudio.c Log Message: ossaudio(3): refactor library into separate files To generate a diff of this commit: cvs rdiff -u -r1.10 -r1.11 src/lib/libossaudio/Makefile cvs rdiff -u -r0 -r1.1 src/lib/libossaudio/internal.h \ src/lib/libossaudio/oss3_mixer.c src/lib/libossaudio/oss4_global.c \ src/lib/libossaudio/oss4_mixer.c src/lib/libossaudio/oss_caps.c \ src/lib/libossaudio/oss_dsp.c src/lib/libossaudio/oss_ioctl.c cvs rdiff -u -r1.68 -r0 src/lib/libossaudio/ossaudio.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/lib/libossaudio/Makefile diff -u src/lib/libossaudio/Makefile:1.10 src/lib/libossaudio/Makefile:1.11 --- src/lib/libossaudio/Makefile:1.10 Sat May 5 15:57:45 2012 +++ src/lib/libossaudio/Makefile Tue Jun 8 18:43:54 2021 @@ -1,11 +1,13 @@ -# $NetBSD: Makefile,v 1.10 2012/05/05 15:57:45 christos Exp $ +# $NetBSD: Makefile,v 1.11 2021/06/08 18:43:54 nia Exp $ WARNS= 5 LIB= ossaudio MAN= ossaudio.3 -SRCS= ossaudio.c +SRCS= oss_caps.c oss_dsp.c oss_ioctl.c +SRCS+= oss3_mixer.c oss4_mixer.c +SRCS+= oss4_global.c CPPFLAGS+= -I${.CURDIR} Added files: Index: src/lib/libossaudio/internal.h diff -u /dev/null src/lib/libossaudio/internal.h:1.1 --- /dev/null Tue Jun 8 18:43:54 2021 +++ src/lib/libossaudio/internal.h Tue Jun 8 18:43:54 2021 @@ -0,0 +1,53 @@ +/* $NetBSD: internal.h,v 1.1 2021/06/08 18:43:54 nia Exp $ */ + +/*- + * Copyright (c) 1997-2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef OSSAUDIO_INTERNAL_H +#define OSSAUDIO_INTERNAL_H + +#include <sys/ioctl.h> +#include "soundcard.h" +#undef ioctl + +#define TO_OSSVOL(x) (((x) * 100 + 127) / 255) +#define FROM_OSSVOL(x) ((((x) > 100 ? 100 : (x)) * 255 + 50) / 100) + +#define INTARG (*(int*)argp) + +#define GET_DEV(com) ((com) & 0xff) + +#define oss_private __attribute__((__visibility__("hidden"))) + +int _oss_ioctl(int, unsigned long, ...); + +oss_private int _oss_dsp_ioctl(int, unsigned long, void *); +oss_private int _oss_get_caps(int, int *); +oss_private int _oss3_mixer_ioctl(int, unsigned long, void *); +oss_private int _oss4_mixer_ioctl(int, unsigned long, void *); +oss_private int _oss4_global_ioctl(int, unsigned long, void *); + +#endif Index: src/lib/libossaudio/oss3_mixer.c diff -u /dev/null src/lib/libossaudio/oss3_mixer.c:1.1 --- /dev/null Tue Jun 8 18:43:54 2021 +++ src/lib/libossaudio/oss3_mixer.c Tue Jun 8 18:43:54 2021 @@ -0,0 +1,393 @@ +/* $NetBSD: oss3_mixer.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */ + +/*- + * Copyright (c) 1997-2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/audioio.h> +#include <sys/stat.h> +#include <errno.h> +#include "internal.h" + +/* If the NetBSD mixer device should have more than NETBSD_MAXDEVS devices + * some will not be available to OSS applications */ +#define NETBSD_MAXDEVS 64 + +struct audiodevinfo { + int done; + dev_t dev; + int16_t devmap[SOUND_MIXER_NRDEVICES], + rdevmap[NETBSD_MAXDEVS]; + char names[NETBSD_MAXDEVS][MAX_AUDIO_DEV_LEN]; + int enum2opaque[NETBSD_MAXDEVS]; + u_long devmask, recmask, stereomask; + u_long caps; + int source; +}; + +static struct audiodevinfo *getdevinfo(int); +static int opaque_to_enum(struct audiodevinfo *, audio_mixer_name_t *, int); +static int enum_to_ord(struct audiodevinfo *, int); +static int enum_to_mask(struct audiodevinfo *, int); + +oss_private int +_oss3_mixer_ioctl(int fd, unsigned long com, void *argp) +{ + struct audiodevinfo *di; + struct mixer_info *omi; + struct audio_device adev; + mixer_ctrl_t mc; + u_long idat, n; + int i; + int retval; + int l, r, error, e; + + idat = 0; + di = getdevinfo(fd); + if (di == 0) + return -1; + + switch (com) { + case OSS_GETVERSION: + idat = SOUND_VERSION; + break; + case SOUND_MIXER_INFO: + case SOUND_OLD_MIXER_INFO: + error = ioctl(fd, AUDIO_GETDEV, &adev); + if (error) + return (error); + omi = argp; + if (com == SOUND_MIXER_INFO) + omi->modify_counter = 1; + strlcpy(omi->id, adev.name, sizeof omi->id); + strlcpy(omi->name, adev.name, sizeof omi->name); + return 0; + case SOUND_MIXER_READ_RECSRC: + if (di->source == -1) { + errno = EINVAL; + return -1; + } + mc.dev = di->source; + if (di->caps & SOUND_CAP_EXCL_INPUT) { + mc.type = AUDIO_MIXER_ENUM; + retval = ioctl(fd, AUDIO_MIXER_READ, &mc); + if (retval < 0) + return retval; + e = opaque_to_enum(di, NULL, mc.un.ord); + if (e >= 0) + idat = 1 << di->rdevmap[e]; + } else { + mc.type = AUDIO_MIXER_SET; + retval = ioctl(fd, AUDIO_MIXER_READ, &mc); + if (retval < 0) + return retval; + e = opaque_to_enum(di, NULL, mc.un.mask); + if (e >= 0) + idat = 1 << di->rdevmap[e]; + } + break; + case SOUND_MIXER_READ_DEVMASK: + idat = di->devmask; + break; + case SOUND_MIXER_READ_RECMASK: + idat = di->recmask; + break; + case SOUND_MIXER_READ_STEREODEVS: + idat = di->stereomask; + break; + case SOUND_MIXER_READ_CAPS: + idat = di->caps; + break; + case SOUND_MIXER_WRITE_RECSRC: + case SOUND_MIXER_WRITE_R_RECSRC: + if (di->source == -1) { + errno = EINVAL; + return -1; + } + mc.dev = di->source; + idat = INTARG; + if (di->caps & SOUND_CAP_EXCL_INPUT) { + mc.type = AUDIO_MIXER_ENUM; + for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) + if (idat & (1 << i)) + break; + if (i >= SOUND_MIXER_NRDEVICES || + di->devmap[i] == -1) { + errno = EINVAL; + return -1; + } + mc.un.ord = enum_to_ord(di, di->devmap[i]); + } else { + mc.type = AUDIO_MIXER_SET; + mc.un.mask = 0; + for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (idat & (1 << i)) { + if (di->devmap[i] == -1) { + errno = EINVAL; + return -1; + } + mc.un.mask |= + enum_to_mask(di, di->devmap[i]); + } + } + } + return ioctl(fd, AUDIO_MIXER_WRITE, &mc); + default: + if (MIXER_READ(SOUND_MIXER_FIRST) <= com && + com < MIXER_READ(SOUND_MIXER_NRDEVICES)) { + n = GET_DEV(com); + if (di->devmap[n] == -1) { + errno = EINVAL; + return -1; + } + mc.dev = di->devmap[n]; + mc.type = AUDIO_MIXER_VALUE; + doread: + mc.un.value.num_channels = + di->stereomask & (1 << (u_int)n) ? 2 : 1; + retval = ioctl(fd, AUDIO_MIXER_READ, &mc); + if (retval < 0) + return retval; + if (mc.type != AUDIO_MIXER_VALUE) { + errno = EINVAL; + return -1; + } + if (mc.un.value.num_channels != 2) { + l = r = + mc.un.value.level[AUDIO_MIXER_LEVEL_MONO]; + } else { + l = mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT]; + r = mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT]; + } + idat = TO_OSSVOL(l) | (TO_OSSVOL(r) << 8); + break; + } else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com && + com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) || + (MIXER_WRITE(SOUND_MIXER_FIRST) <= com && + com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) { + n = GET_DEV(com); + if (di->devmap[n] == -1) { + errno = EINVAL; + return -1; + } + idat = INTARG; + l = FROM_OSSVOL((u_int)idat & 0xff); + r = FROM_OSSVOL(((u_int)idat >> 8) & 0xff); + mc.dev = di->devmap[n]; + mc.type = AUDIO_MIXER_VALUE; + if (di->stereomask & (1 << (u_int)n)) { + mc.un.value.num_channels = 2; + mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l; + mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r; + } else { + mc.un.value.num_channels = 1; + mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] = + (l + r) / 2; + } + retval = ioctl(fd, AUDIO_MIXER_WRITE, &mc); + if (retval < 0) + return retval; + if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com && + com < MIXER_WRITE(SOUND_MIXER_NRDEVICES)) + return 0; + goto doread; + } else { + errno = EINVAL; + return -1; + } + } + INTARG = (int)idat; + return 0; +} + +/* + * Collect the audio device information to allow faster + * emulation of the OSSv3 mixer ioctls. Cache the information + * to eliminate the overhead of repeating all the ioctls needed + * to collect the information. + */ +static struct audiodevinfo * +getdevinfo(int fd) +{ + mixer_devinfo_t mi; + int i, j, e; + static struct { + const char *name; + int code; + } *dp, devs[] = { + { AudioNmicrophone, SOUND_MIXER_MIC }, + { AudioNline, SOUND_MIXER_LINE }, + { AudioNcd, SOUND_MIXER_CD }, + { AudioNdac, SOUND_MIXER_PCM }, + { AudioNaux, SOUND_MIXER_LINE1 }, + { AudioNrecord, SOUND_MIXER_IMIX }, + { AudioNmaster, SOUND_MIXER_VOLUME }, + { AudioNtreble, SOUND_MIXER_TREBLE }, + { AudioNbass, SOUND_MIXER_BASS }, + { AudioNspeaker, SOUND_MIXER_SPEAKER }, +/* { AudioNheadphone, ?? },*/ + { AudioNoutput, SOUND_MIXER_OGAIN }, + { AudioNinput, SOUND_MIXER_IGAIN }, +/* { AudioNmaster, SOUND_MIXER_SPEAKER },*/ +/* { AudioNstereo, ?? },*/ +/* { AudioNmono, ?? },*/ + { AudioNfmsynth, SOUND_MIXER_SYNTH }, +/* { AudioNwave, SOUND_MIXER_PCM },*/ + { AudioNmidi, SOUND_MIXER_SYNTH }, +/* { AudioNmixerout, ?? },*/ + { 0, -1 } + }; + static struct audiodevinfo devcache = { .done = 0 }; + struct audiodevinfo *di = &devcache; + struct stat sb; + size_t mlen, dlen; + + /* Figure out what device it is so we can check if the + * cached data is valid. + */ + if (fstat(fd, &sb) < 0) + return 0; + if (di->done && di->dev == sb.st_dev) + return di; + + di->done = 1; + di->dev = sb.st_dev; + di->devmask = 0; + di->recmask = 0; + di->stereomask = 0; + di->source = ~0; + di->caps = 0; + for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) + di->devmap[i] = -1; + for(i = 0; i < NETBSD_MAXDEVS; i++) { + di->rdevmap[i] = -1; + di->names[i][0] = '\0'; + di->enum2opaque[i] = -1; + } + for(i = 0; i < NETBSD_MAXDEVS; i++) { + mi.index = i; + if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) < 0) + break; + switch(mi.type) { + case AUDIO_MIXER_VALUE: + for(dp = devs; dp->name; dp++) { + if (strcmp(dp->name, mi.label.name) == 0) + break; + dlen = strlen(dp->name); + mlen = strlen(mi.label.name); + if (dlen < mlen + && mi.label.name[mlen-dlen-1] == '.' + && strcmp(dp->name, + mi.label.name + mlen - dlen) == 0) + break; + } + if (dp->code >= 0) { + di->devmap[dp->code] = i; + di->rdevmap[i] = dp->code; + di->devmask |= 1 << dp->code; + if (mi.un.v.num_channels == 2) + di->stereomask |= 1 << dp->code; + strlcpy(di->names[i], mi.label.name, + sizeof di->names[i]); + } + break; + } + } + for(i = 0; i < NETBSD_MAXDEVS; i++) { + mi.index = i; + if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) < 0) + break; + if (strcmp(mi.label.name, AudioNsource) != 0) + continue; + di->source = i; + switch(mi.type) { + case AUDIO_MIXER_ENUM: + for(j = 0; j < mi.un.e.num_mem; j++) { + e = opaque_to_enum(di, + &mi.un.e.member[j].label, + mi.un.e.member[j].ord); + if (e >= 0) + di->recmask |= 1 << di->rdevmap[e]; + } + di->caps = SOUND_CAP_EXCL_INPUT; + break; + case AUDIO_MIXER_SET: + for(j = 0; j < mi.un.s.num_mem; j++) { + e = opaque_to_enum(di, + &mi.un.s.member[j].label, + mi.un.s.member[j].mask); + if (e >= 0) + di->recmask |= 1 << di->rdevmap[e]; + } + break; + } + } + return di; +} + +static int +opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq) +{ + int i, o; + + for (i = 0; i < NETBSD_MAXDEVS; i++) { + o = di->enum2opaque[i]; + if (o == opq) + break; + if (o == -1 && label != NULL && + !strncmp(di->names[i], label->name, sizeof di->names[i])) { + di->enum2opaque[i] = opq; + break; + } + } + if (i >= NETBSD_MAXDEVS) + i = -1; + /*printf("opq_to_enum %s %d -> %d\n", label->name, opq, i);*/ + return (i); +} + +static int +enum_to_ord(struct audiodevinfo *di, int enm) +{ + if (enm >= NETBSD_MAXDEVS) + return (-1); + + /*printf("enum_to_ord %d -> %d\n", enm, di->enum2opaque[enm]);*/ + return (di->enum2opaque[enm]); +} + +static int +enum_to_mask(struct audiodevinfo *di, int enm) +{ + int m; + if (enm >= NETBSD_MAXDEVS) + return (0); + + m = di->enum2opaque[enm]; + if (m == -1) + m = 0; + /*printf("enum_to_mask %d -> %d\n", enm, di->enum2opaque[enm]);*/ + return (m); +} Index: src/lib/libossaudio/oss4_global.c diff -u /dev/null src/lib/libossaudio/oss4_global.c:1.1 --- /dev/null Tue Jun 8 18:43:54 2021 +++ src/lib/libossaudio/oss4_global.c Tue Jun 8 18:43:54 2021 @@ -0,0 +1,63 @@ +/* $NetBSD: oss4_global.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */ + +/*- + * Copyright (c) 2020-2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Nia Alarie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include <errno.h> +#include "internal.h" + +oss_private int +_oss4_global_ioctl(int fd, unsigned long com, void *argp) +{ + int retval = 0; + + switch (com) { + /* + * These ioctls were added in OSSv4 with the idea that + * applications could apply strings to audio devices to + * display what they are using them for (e.g. with song + * names) in mixer applications. In practice, the popular + * implementations of the API in FreeBSD and Solaris treat + * these as a no-op and return EINVAL, and no software in the + * wild seems to use them. + */ + case SNDCTL_SETSONG: + case SNDCTL_GETSONG: + case SNDCTL_SETNAME: + case SNDCTL_SETLABEL: + case SNDCTL_GETLABEL: + errno = EINVAL; + retval = -1; + break; + default: + errno = EINVAL; + retval = -1; + break; + } + return retval; +} Index: src/lib/libossaudio/oss4_mixer.c diff -u /dev/null src/lib/libossaudio/oss4_mixer.c:1.1 --- /dev/null Tue Jun 8 18:43:54 2021 +++ src/lib/libossaudio/oss4_mixer.c Tue Jun 8 18:43:54 2021 @@ -0,0 +1,640 @@ +/* $NetBSD: oss4_mixer.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */ + +/*- + * Copyright (c) 2020-2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Nia Alarie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include <sys/audioio.h> +#include <sys/fcntl.h> +#include <sys/stat.h> +#include <errno.h> +#include <limits.h> +#include <stdio.h> +#include <unistd.h> +#include "internal.h" + +static int get_audio_count(void); +static int get_mixer_count(void); +static int get_mixer_control_count(int); + +oss_private int +_oss4_mixer_ioctl(int fd, unsigned long com, void *argp) +{ + oss_audioinfo *tmpai; + oss_card_info *cardinfo; + oss_mixext *ext; + oss_mixext_root root; + oss_mixer_enuminfo *ei; + oss_mixer_value *mv; + oss_mixerinfo *mi; + oss_sysinfo sysinfo; + dev_t devno; + struct stat tmpstat; + struct audio_device dev; + struct audio_format_query fmtq; + struct mixer_devinfo mdi; + struct mixer_ctrl mc; + char devname[32]; + size_t len; + int newfd = -1, tmperrno; + int i, noffs; + int retval; + + /* + * Note: it is difficult to translate the NetBSD concept of a "set" + * mixer control type to the OSSv4 API, as far as I can tell. + * + * This means they are treated like enums, i.e. only one entry in the + * set can be selected at a time. + */ + + switch (com) { + case SNDCTL_AUDIOINFO: + /* + * SNDCTL_AUDIOINFO_EX is intended for underlying hardware devices + * that are to be opened in "exclusive mode" (bypassing the normal + * kernel mixer for exclusive control). NetBSD does not support + * bypassing the kernel mixer, so it's an alias of SNDCTL_AUDIOINFO. + */ + case SNDCTL_AUDIOINFO_EX: + case SNDCTL_ENGINEINFO: + devno = 0; + tmpai = (struct oss_audioinfo*)argp; + if (tmpai == NULL) { + errno = EINVAL; + return -1; + } + + /* + * If the input device is -1, guess the device related to + * the open mixer device. + */ + if (tmpai->dev < 0) { + fstat(fd, &tmpstat); + if ((tmpstat.st_rdev & 0xff00) == 0x2a00) + devno = tmpstat.st_rdev & 0xff; + if (devno >= 0x80) + tmpai->dev = devno & 0x7f; + } + if (tmpai->dev < 0) + tmpai->dev = 0; + + snprintf(tmpai->devnode, sizeof(tmpai->devnode), + "/dev/audio%d", tmpai->dev); + + if ((newfd = open(tmpai->devnode, O_WRONLY)) < 0) { + if ((newfd = open(tmpai->devnode, O_RDONLY)) < 0) { + return newfd; + } + } + + retval = ioctl(newfd, AUDIO_GETDEV, &dev); + if (retval < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + if (_oss_get_caps(newfd, &tmpai->caps) < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + snprintf(tmpai->name, sizeof(tmpai->name), + "%s %s", dev.name, dev.version); + tmpai->busy = 0; + tmpai->pid = -1; + _oss_dsp_ioctl(newfd, SNDCTL_DSP_GETFMTS, &tmpai->iformats); + tmpai->oformats = tmpai->iformats; + tmpai->magic = -1; /* reserved for "internal use" */ + memset(tmpai->cmd, 0, sizeof(tmpai->cmd)); + tmpai->card_number = -1; + memset(tmpai->song_name, 0, + sizeof(tmpai->song_name)); + memset(tmpai->label, 0, sizeof(tmpai->label)); + tmpai->port_number = 0; + tmpai->mixer_dev = tmpai->dev; + tmpai->legacy_device = tmpai->dev; + tmpai->enabled = 1; + tmpai->flags = -1; /* reserved for "future versions" */ + tmpai->min_rate = 1000; + tmpai->max_rate = 192000; + tmpai->nrates = 0; + tmpai->min_channels = 1; + tmpai->max_channels = 2; + for (fmtq.index = 0; + ioctl(newfd, AUDIO_QUERYFORMAT, &fmtq) != -1; ++fmtq.index) { + if (fmtq.fmt.channels > (unsigned)tmpai->max_channels) + tmpai->max_channels = fmtq.fmt.channels; + } + tmpai->binding = -1; /* reserved for "future versions" */ + tmpai->rate_source = -1; + /* + * 'handle' is supposed to be globally unique. The closest + * we have to that is probably device nodes. + */ + strlcpy(tmpai->handle, tmpai->devnode, + sizeof(tmpai->handle)); + tmpai->next_play_engine = 0; + tmpai->next_rec_engine = 0; + argp = tmpai; + close(newfd); + break; + case SNDCTL_CARDINFO: + cardinfo = (oss_card_info *)argp; + if (cardinfo == NULL) { + errno = EINVAL; + return -1; + } + if (cardinfo->card != -1) { + snprintf(devname, sizeof(devname), + "/dev/audio%d", cardinfo->card); + newfd = open(devname, O_RDONLY); + if (newfd < 0) + return newfd; + } else { + newfd = fd; + } + retval = ioctl(newfd, AUDIO_GETDEV, &dev); + tmperrno = errno; + if (newfd != fd) + close(newfd); + if (retval < 0) { + errno = tmperrno; + return retval; + } + strlcpy(cardinfo->shortname, dev.name, + sizeof(cardinfo->shortname)); + snprintf(cardinfo->longname, sizeof(cardinfo->longname), + "%s %s %s", dev.name, dev.version, dev.config); + memset(cardinfo->hw_info, 0, sizeof(cardinfo->hw_info)); + /* + * OSSv4 does not document this ioctl, and claims it should + * not be used by applications and is provided for "utiltiy + * programs included in OSS". We follow the Solaris + * implementation (which is documented) and leave these fields + * unset. + */ + cardinfo->flags = 0; + cardinfo->intr_count = 0; + cardinfo->ack_count = 0; + break; + case SNDCTL_SYSINFO: + memset(&sysinfo, 0, sizeof(sysinfo)); + strlcpy(sysinfo.product, + "OSS/NetBSD", sizeof(sysinfo.product)); + strlcpy(sysinfo.version, + "4.01", sizeof(sysinfo.version)); + strlcpy(sysinfo.license, + "BSD", sizeof(sysinfo.license)); + sysinfo.versionnum = SOUND_VERSION; + sysinfo.numaudios = + sysinfo.numcards = + get_audio_count(); + sysinfo.numaudioengines = 1; + sysinfo.numsynths = 1; + sysinfo.nummidis = -1; + sysinfo.numtimers = -1; + sysinfo.nummixers = get_mixer_count(); + *(struct oss_sysinfo *)argp = sysinfo; + break; + case SNDCTL_MIXERINFO: + mi = (oss_mixerinfo *)argp; + if (mi == NULL) { + errno = EINVAL; + return -1; + } + snprintf(devname, sizeof(devname), "/dev/mixer%d", mi->dev); + if ((newfd = open(devname, O_RDONLY)) < 0) + return newfd; + retval = ioctl(newfd, AUDIO_GETDEV, &dev); + if (retval < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + strlcpy(mi->id, devname, sizeof(mi->id)); + strlcpy(mi->handle, devname, sizeof(mi->handle)); + snprintf(mi->name, sizeof(mi->name), + "%s %s", dev.name, dev.version); + mi->card_number = mi->dev; + mi->port_number = 0; + mi->magic = 0; + mi->enabled = 1; + mi->caps = 0; + mi->flags = 0; + mi->nrext = get_mixer_control_count(newfd) + 1; + mi->priority = UCHAR_MAX - mi->dev; + strlcpy(mi->devnode, devname, sizeof(mi->devnode)); + mi->legacy_device = mi->dev; + break; + case SNDCTL_MIX_DESCRIPTION: + /* No description available. */ + errno = ENOSYS; + return -1; + case SNDCTL_MIX_NRMIX: + INTARG = get_mixer_count(); + break; + case SNDCTL_MIX_NREXT: + snprintf(devname, sizeof(devname), "/dev/mixer%d", INTARG); + if ((newfd = open(devname, O_RDONLY)) < 0) + return newfd; + INTARG = get_mixer_control_count(newfd) + 1; + close(newfd); + break; + case SNDCTL_MIX_EXTINFO: + ext = (oss_mixext *)argp; + snprintf(devname, sizeof(devname), "/dev/mixer%d", ext->dev); + if ((newfd = open(devname, O_RDONLY)) < 0) + return newfd; + if (ext->ctrl == 0) { + /* + * NetBSD has no concept of a "root mixer control", but + * OSSv4 requires one to work. We fake one at 0 and + * simply add 1 to all real control indexes. + */ + retval = ioctl(newfd, AUDIO_GETDEV, &dev); + tmperrno = errno; + close(newfd); + if (retval < 0) { + errno = tmperrno; + return -1; + } + memset(&root, 0, sizeof(root)); + strlcpy(root.id, devname, sizeof(root.id)); + snprintf(root.name, sizeof(root.name), + "%s %s", dev.name, dev.version); + strlcpy(ext->id, devname, sizeof(ext->id)); + snprintf(ext->extname, sizeof(ext->extname), + "%s %s", dev.name, dev.version); + strlcpy(ext->extname, "root", sizeof(ext->extname)); + ext->type = MIXT_DEVROOT; + ext->minvalue = 0; + ext->maxvalue = 0; + ext->flags = 0; + ext->parent = -1; + ext->control_no = -1; + ext->update_counter = 0; + ext->rgbcolor = 0; + memcpy(&ext->data, &root, + sizeof(root) > sizeof(ext->data) ? + sizeof(ext->data) : sizeof(root)); + return 0; + } + mdi.index = ext->ctrl - 1; + retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); + if (retval < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + ext->flags = MIXF_READABLE | MIXF_WRITEABLE | MIXF_POLL; + ext->parent = mdi.mixer_class + 1; + strlcpy(ext->id, mdi.label.name, sizeof(ext->id)); + strlcpy(ext->extname, mdi.label.name, sizeof(ext->extname)); + len = strlen(ext->extname); + memset(ext->data, 0, sizeof(ext->data)); + ext->control_no = -1; + ext->update_counter = 0; + ext->rgbcolor = 0; + switch (mdi.type) { + case AUDIO_MIXER_CLASS: + ext->type = MIXT_GROUP; + ext->parent = 0; + ext->minvalue = 0; + ext->maxvalue = 0; + break; + case AUDIO_MIXER_ENUM: + ext->maxvalue = mdi.un.e.num_mem; + ext->minvalue = 0; + for (i = 0; i < mdi.un.e.num_mem; ++i) { + ext->enum_present[i / 8] |= (1 << (i % 8)); + } + if (mdi.un.e.num_mem == 2) { + if (!strcmp(mdi.un.e.member[0].label.name, AudioNoff) && + !strcmp(mdi.un.e.member[1].label.name, AudioNon)) { + ext->type = MIXT_MUTE; + } else { + ext->type = MIXT_ENUM; + } + } else { + ext->type = MIXT_ENUM; + } + break; + case AUDIO_MIXER_SET: + ext->maxvalue = mdi.un.s.num_mem; + ext->minvalue = 0; +#ifdef notyet + /* + * XXX: This is actually the correct type for "set" + * controls, but it seems no real world software + * supports it. The only documentation exists in + * the OSSv4 headers and describes it as "reserved + * for Sun's implementation". + */ + ext->type = MIXT_ENUM_MULTI; +#else + ext->type = MIXT_ENUM; +#endif + for (i = 0; i < mdi.un.s.num_mem; ++i) { + ext->enum_present[i / 8] |= (1 << (i % 8)); + } + break; + case AUDIO_MIXER_VALUE: + ext->maxvalue = UCHAR_MAX + 1; + ext->minvalue = 0; + if (mdi.un.v.num_channels == 2) { + ext->type = MIXT_STEREOSLIDER; + } else { + ext->type = MIXT_MONOSLIDER; + } + break; + } + close(newfd); + break; + case SNDCTL_MIX_ENUMINFO: + ei = (oss_mixer_enuminfo *)argp; + if (ei == NULL) { + errno = EINVAL; + return -1; + } + if (ei->ctrl == 0) { + errno = EINVAL; + return -1; + } + snprintf(devname, sizeof(devname), "/dev/mixer%d", ei->dev); + if ((newfd = open(devname, O_RDONLY)) < 0) + return newfd; + mdi.index = ei->ctrl - 1; + retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); + tmperrno = errno; + close(newfd); + if (retval < 0) { + errno = tmperrno; + return retval; + } + ei->version = 0; + switch (mdi.type) { + case AUDIO_MIXER_ENUM: + ei->nvalues = mdi.un.e.num_mem; + noffs = 0; + for (i = 0; i < ei->nvalues; ++i) { + ei->strindex[i] = noffs; + len = strlen(mdi.un.e.member[i].label.name) + 1; + if ((noffs + len) >= sizeof(ei->strings)) { + errno = ENOMEM; + return -1; + } + memcpy(ei->strings + noffs, + mdi.un.e.member[i].label.name, len); + noffs += len; + } + break; + case AUDIO_MIXER_SET: + ei->nvalues = mdi.un.s.num_mem; + noffs = 0; + for (i = 0; i < ei->nvalues; ++i) { + ei->strindex[i] = noffs; + len = strlen(mdi.un.s.member[i].label.name) + 1; + if ((noffs + len) >= sizeof(ei->strings)) { + errno = ENOMEM; + return -1; + } + memcpy(ei->strings + noffs, + mdi.un.s.member[i].label.name, len); + noffs += len; + } + break; + default: + errno = EINVAL; + return -1; + } + break; + case SNDCTL_MIX_WRITE: + mv = (oss_mixer_value *)argp; + if (mv == NULL) { + errno = EINVAL; + return -1; + } + if (mv->ctrl == 0) { + errno = EINVAL; + return -1; + } + snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev); + if ((newfd = open(devname, O_RDWR)) < 0) + return newfd; + mdi.index = mc.dev = mv->ctrl - 1; + retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); + if (retval < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + mc.type = mdi.type; + switch (mdi.type) { + case AUDIO_MIXER_ENUM: + if (mv->value >= mdi.un.e.num_mem) { + close(newfd); + errno = EINVAL; + return -1; + } + mc.un.ord = mdi.un.e.member[mv->value].ord; + break; + case AUDIO_MIXER_SET: + if (mv->value >= mdi.un.s.num_mem) { + close(newfd); + errno = EINVAL; + return -1; + } +#ifdef notyet + mc.un.mask = 0; + for (i = 0; i < mdi.un.s.num_mem; ++i) { + if (mv->value & (1 << i)) { + mc.un.mask |= mdi.un.s.member[mv->value].mask; + } + } +#else + mc.un.mask = mdi.un.s.member[mv->value].mask; +#endif + break; + case AUDIO_MIXER_VALUE: + mc.un.value.num_channels = mdi.un.v.num_channels; + if (mdi.un.v.num_channels != 2) { + for (i = 0; i < mdi.un.v.num_channels; ++i) { + mc.un.value.level[i] = mv->value; + } + } else { + mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = + (mv->value >> 0) & 0xFF; + mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = + (mv->value >> 8) & 0xFF; + } + break; + } + retval = ioctl(newfd, AUDIO_MIXER_WRITE, &mc); + if (retval < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + close(newfd); + break; + case SNDCTL_MIX_READ: + mv = (oss_mixer_value *)argp; + if (mv == NULL) { + errno = EINVAL; + return -1; + } + if (mv->ctrl == 0) { + errno = EINVAL; + return -1; + } + snprintf(devname, sizeof(devname), "/dev/mixer%d", mv->dev); + if ((newfd = open(devname, O_RDWR)) < 0) + return newfd; + mdi.index = mc.dev = (mv->ctrl - 1); + retval = ioctl(newfd, AUDIO_MIXER_DEVINFO, &mdi); + if (retval < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + mc.dev = mdi.index; + mc.type = mdi.type; + if (mdi.type == AUDIO_MIXER_VALUE) + mc.un.value.num_channels = mdi.un.v.num_channels; + retval = ioctl(newfd, AUDIO_MIXER_READ, &mc); + if (retval < 0) { + tmperrno = errno; + close(newfd); + errno = tmperrno; + return retval; + } + close(newfd); + mv->value = 0; + switch (mdi.type) { + case AUDIO_MIXER_ENUM: + for (i = 0; i < mdi.un.e.num_mem; ++i) { + if (mc.un.ord == mdi.un.e.member[i].ord) { + mv->value = i; + break; + } + } + break; + case AUDIO_MIXER_SET: + for (i = 0; i < mdi.un.s.num_mem; ++i) { +#ifdef notyet + if (mc.un.mask & mdi.un.s.member[i].mask) + mv->value |= (1 << i); +#else + if (mc.un.mask == mdi.un.s.member[i].mask) { + mv->value = i; + break; + } +#endif + } + break; + case AUDIO_MIXER_VALUE: + if (mdi.un.v.num_channels != 2) { + mv->value = mc.un.value.level[0]; + } else { + mv->value = \ + ((mc.un.value.level[1] & 0xFF) << 8) | + ((mc.un.value.level[0] & 0xFF) << 0); + } + break; + default: + errno = EINVAL; + return -1; + } + break; + default: + errno = EINVAL; + return -1; + } + return 0; +} + +static int +get_audio_count(void) +{ + char devname[32]; + int ndevs = 0; + int tmpfd; + int tmperrno = errno; + + do { + snprintf(devname, sizeof(devname), + "/dev/audio%d", ndevs); + if ((tmpfd = open(devname, O_RDONLY)) != -1 || + (tmpfd = open(devname, O_WRONLY)) != -1) { + ndevs++; + close(tmpfd); + } + } while (tmpfd != -1); + errno = tmperrno; + return ndevs; +} + +static int +get_mixer_count(void) +{ + char devname[32]; + int ndevs = 0; + int tmpfd; + int tmperrno = errno; + + do { + snprintf(devname, sizeof(devname), + "/dev/mixer%d", ndevs); + if ((tmpfd = open(devname, O_RDONLY)) != -1) { + ndevs++; + close(tmpfd); + } + } while (tmpfd != -1); + errno = tmperrno; + return ndevs; +} + +static int +get_mixer_control_count(int fd) +{ + struct mixer_devinfo mdi; + int ndevs = 0; + + do { + mdi.index = ndevs++; + } while (ioctl(fd, AUDIO_MIXER_DEVINFO, &mdi) != -1); + + return ndevs > 0 ? ndevs - 1 : 0; +} Index: src/lib/libossaudio/oss_caps.c diff -u /dev/null src/lib/libossaudio/oss_caps.c:1.1 --- /dev/null Tue Jun 8 18:43:54 2021 +++ src/lib/libossaudio/oss_caps.c Tue Jun 8 18:43:54 2021 @@ -0,0 +1,78 @@ +/* $NetBSD: oss_caps.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */ + +/*- + * Copyright (c) 2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Nia Alarie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include <sys/audioio.h> +#include "internal.h" + +oss_private int +_oss_get_caps(int fd, int *out) +{ + struct audio_info info; + int props, caps; + int nchannels; + + if (ioctl(fd, AUDIO_GETPROPS, &props) < 0) + return -1; + + if (ioctl(fd, AUDIO_GETFORMAT, &info) < 0) + return -1; + + caps = 0; + caps |= PCM_CAP_TRIGGER; + caps |= PCM_CAP_MULTI; + caps |= PCM_CAP_FREERATE; + + nchannels = (props & AUDIO_PROP_PLAYBACK) ? + info.play.channels : info.record.channels; + + switch (nchannels) { + case 2: + caps |= DSP_CH_STEREO; + break; + case 1: + caps |= DSP_CH_MONO; + break; + default: + caps |= DSP_CH_MULTI; + break; + } + + if (props & AUDIO_PROP_FULLDUPLEX) + caps |= PCM_CAP_DUPLEX; + if (props & AUDIO_PROP_MMAP) + caps |= PCM_CAP_MMAP; + if (props & AUDIO_PROP_CAPTURE) + caps |= PCM_CAP_INPUT; + if (props & AUDIO_PROP_PLAYBACK) + caps |= PCM_CAP_OUTPUT; + + *out = caps; + return 0; +} Index: src/lib/libossaudio/oss_dsp.c diff -u /dev/null src/lib/libossaudio/oss_dsp.c:1.1 --- /dev/null Tue Jun 8 18:43:54 2021 +++ src/lib/libossaudio/oss_dsp.c Tue Jun 8 18:43:54 2021 @@ -0,0 +1,711 @@ +/* $NetBSD: oss_dsp.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */ + +/*- + * Copyright (c) 1997-2021 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: oss_dsp.c,v 1.1 2021/06/08 18:43:54 nia Exp $"); + +#include <sys/audioio.h> +#include <stdbool.h> +#include <errno.h> +#include "internal.h" + +#define GETPRINFO(info, name) \ + (((info)->mode == AUMODE_RECORD) \ + ? (info)->record.name : (info)->play.name) + +static int get_vol(u_int, u_char); +static void set_vol(int, int, bool); + +static void set_channels(int, int, int); + +oss_private int +_oss_dsp_ioctl(int fd, unsigned long com, void *argp) +{ + + struct audio_info tmpinfo, hwfmt; + struct audio_offset tmpoffs; + struct audio_buf_info bufinfo; + struct audio_errinfo *tmperrinfo; + struct count_info cntinfo; + struct audio_encoding tmpenc; + u_int u; + u_int encoding; + u_int precision; + int perrors, rerrors; + static int totalperrors = 0; + static int totalrerrors = 0; + oss_mixer_enuminfo *ei; + oss_count_t osscount; + int idat; + int retval; + + idat = 0; + + switch (com) { + case SNDCTL_DSP_HALT_INPUT: + case SNDCTL_DSP_HALT_OUTPUT: + case SNDCTL_DSP_RESET: + retval = ioctl(fd, AUDIO_FLUSH, 0); + if (retval < 0) + return retval; + break; + case SNDCTL_DSP_SYNC: + retval = ioctl(fd, AUDIO_DRAIN, 0); + if (retval < 0) + return retval; + break; + case SNDCTL_DSP_GETERROR: + tmperrinfo = (struct audio_errinfo *)argp; + if (tmperrinfo == NULL) { + errno = EINVAL; + return -1; + } + memset(tmperrinfo, 0, sizeof(struct audio_errinfo)); + if ((retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo)) < 0) + return retval; + /* + * OSS requires that we return counters that are relative to + * the last call. We must maintain state here... + */ + if (ioctl(fd, AUDIO_PERROR, &perrors) != -1) { + perrors /= ((tmpinfo.play.precision / NBBY) * + tmpinfo.play.channels); + tmperrinfo->play_underruns = + (perrors / tmpinfo.blocksize) - totalperrors; + totalperrors += tmperrinfo->play_underruns; + } + if (ioctl(fd, AUDIO_RERROR, &rerrors) != -1) { + rerrors /= ((tmpinfo.record.precision / NBBY) * + tmpinfo.record.channels); + tmperrinfo->rec_overruns = + (rerrors / tmpinfo.blocksize) - totalrerrors; + totalrerrors += tmperrinfo->rec_overruns; + } + break; + case SNDCTL_DSP_COOKEDMODE: + /* + * NetBSD is always running in "cooked mode" - the kernel + * always performs format conversions. + */ + INTARG = 1; + break; + case SNDCTL_DSP_POST: + /* This call is merely advisory, and may be a nop. */ + break; + case SNDCTL_DSP_SPEED: + /* + * In Solaris, 0 is used a special value to query the + * current rate. This seems useful to support. + */ + if (INTARG == 0) { + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt); + if (retval < 0) + return retval; + INTARG = (tmpinfo.mode == AUMODE_RECORD) ? + hwfmt.record.sample_rate : + hwfmt.play.sample_rate; + } + /* + * Conform to kernel limits. + * NetBSD will reject unsupported sample rates, but OSS + * applications need to be able to negotiate a supported one. + */ + if (INTARG < 1000) + INTARG = 1000; + if (INTARG > 192000) + INTARG = 192000; + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.sample_rate = + tmpinfo.record.sample_rate = INTARG; + retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo); + if (retval < 0) + return retval; + /* FALLTHRU */ + case SOUND_PCM_READ_RATE: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + INTARG = GETPRINFO(&tmpinfo, sample_rate); + break; + case SNDCTL_DSP_STEREO: + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.channels = + tmpinfo.record.channels = INTARG ? 2 : 1; + (void) ioctl(fd, AUDIO_SETINFO, &tmpinfo); + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + INTARG = GETPRINFO(&tmpinfo, channels) - 1; + break; + case SNDCTL_DSP_GETBLKSIZE: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + INTARG = tmpinfo.blocksize; + break; + case SNDCTL_DSP_SETFMT: + AUDIO_INITINFO(&tmpinfo); + switch (INTARG) { + case AFMT_MU_LAW: + tmpinfo.play.precision = + tmpinfo.record.precision = 8; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_ULAW; + break; + case AFMT_A_LAW: + tmpinfo.play.precision = + tmpinfo.record.precision = 8; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_ALAW; + break; + case AFMT_U8: + tmpinfo.play.precision = + tmpinfo.record.precision = 8; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR; + break; + case AFMT_S8: + tmpinfo.play.precision = + tmpinfo.record.precision = 8; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR; + break; + case AFMT_S16_LE: + tmpinfo.play.precision = + tmpinfo.record.precision = 16; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE; + break; + case AFMT_S16_BE: + tmpinfo.play.precision = + tmpinfo.record.precision = 16; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_BE; + break; + case AFMT_U16_LE: + tmpinfo.play.precision = + tmpinfo.record.precision = 16; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR_LE; + break; + case AFMT_U16_BE: + tmpinfo.play.precision = + tmpinfo.record.precision = 16; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_ULINEAR_BE; + break; + /* + * XXX: When the kernel supports 24-bit LPCM by default, + * the 24-bit formats should be handled properly instead + * of falling back to 32 bits. + */ + case AFMT_S24_PACKED: + case AFMT_S24_LE: + case AFMT_S32_LE: + tmpinfo.play.precision = + tmpinfo.record.precision = 32; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_LE; + break; + case AFMT_S24_BE: + case AFMT_S32_BE: + tmpinfo.play.precision = + tmpinfo.record.precision = 32; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_SLINEAR_BE; + break; + case AFMT_AC3: + tmpinfo.play.precision = + tmpinfo.record.precision = 16; + tmpinfo.play.encoding = + tmpinfo.record.encoding = AUDIO_ENCODING_AC3; + break; + default: + /* + * OSSv4 specifies that if an invalid format is chosen + * by an application then a sensible format supported + * by the hardware is returned. + * + * In this case, we pick the current hardware format. + */ + retval = ioctl(fd, AUDIO_GETFORMAT, &hwfmt); + if (retval < 0) + return retval; + retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo); + if (retval < 0) + return retval; + tmpinfo.play.encoding = + tmpinfo.record.encoding = + (tmpinfo.mode == AUMODE_RECORD) ? + hwfmt.record.encoding : hwfmt.play.encoding; + tmpinfo.play.precision = + tmpinfo.record.precision = + (tmpinfo.mode == AUMODE_RECORD) ? + hwfmt.record.precision : hwfmt.play.precision ; + break; + } + /* + * In the post-kernel-mixer world, assume that any error means + * it's fatal rather than an unsupported format being selected. + */ + retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo); + if (retval < 0) + return retval; + /* FALLTHRU */ + case SOUND_PCM_READ_BITS: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + encoding = GETPRINFO(&tmpinfo, encoding); + precision = GETPRINFO(&tmpinfo, precision); + switch (encoding) { + case AUDIO_ENCODING_ULAW: + idat = AFMT_MU_LAW; + break; + case AUDIO_ENCODING_ALAW: + idat = AFMT_A_LAW; + break; + case AUDIO_ENCODING_SLINEAR_LE: + if (precision == 32) + idat = AFMT_S32_LE; + else if (precision == 24) + idat = AFMT_S24_LE; + else if (precision == 16) + idat = AFMT_S16_LE; + else + idat = AFMT_S8; + break; + case AUDIO_ENCODING_SLINEAR_BE: + if (precision == 32) + idat = AFMT_S32_BE; + else if (precision == 24) + idat = AFMT_S24_BE; + else if (precision == 16) + idat = AFMT_S16_BE; + else + idat = AFMT_S8; + break; + case AUDIO_ENCODING_ULINEAR_LE: + if (precision == 16) + idat = AFMT_U16_LE; + else + idat = AFMT_U8; + break; + case AUDIO_ENCODING_ULINEAR_BE: + if (precision == 16) + idat = AFMT_U16_BE; + else + idat = AFMT_U8; + break; + case AUDIO_ENCODING_ADPCM: + idat = AFMT_IMA_ADPCM; + break; + case AUDIO_ENCODING_AC3: + idat = AFMT_AC3; + break; + } + INTARG = idat; + break; + case SNDCTL_DSP_CHANNELS: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + set_channels(fd, tmpinfo.mode, INTARG); + /* FALLTHRU */ + case SOUND_PCM_READ_CHANNELS: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + INTARG = GETPRINFO(&tmpinfo, channels); + break; + case SOUND_PCM_WRITE_FILTER: + case SOUND_PCM_READ_FILTER: + errno = EINVAL; + return -1; /* XXX unimplemented */ + case SNDCTL_DSP_SUBDIVIDE: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + idat = INTARG; + if (idat == 0) + idat = tmpinfo.play.buffer_size / tmpinfo.blocksize; + idat = (tmpinfo.play.buffer_size / idat) & -4; + AUDIO_INITINFO(&tmpinfo); + tmpinfo.blocksize = idat; + retval = ioctl(fd, AUDIO_SETINFO, &tmpinfo); + if (retval < 0) + return retval; + INTARG = tmpinfo.play.buffer_size / tmpinfo.blocksize; + break; + case SNDCTL_DSP_SETFRAGMENT: + AUDIO_INITINFO(&tmpinfo); + idat = INTARG; + tmpinfo.blocksize = 1 << (idat & 0xffff); + tmpinfo.hiwat = ((unsigned)idat >> 16) & 0x7fff; + if (tmpinfo.hiwat == 0) /* 0 means set to max */ + tmpinfo.hiwat = 65536; + (void) ioctl(fd, AUDIO_SETINFO, &tmpinfo); + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + u = tmpinfo.blocksize; + for(idat = 0; u > 1; idat++, u >>= 1) + ; + idat |= (tmpinfo.hiwat & 0x7fff) << 16; + INTARG = idat; + break; + case SNDCTL_DSP_GETFMTS: + for(idat = 0, tmpenc.index = 0; + ioctl(fd, AUDIO_GETENC, &tmpenc) == 0; + tmpenc.index++) { + switch(tmpenc.encoding) { + case AUDIO_ENCODING_ULAW: + idat |= AFMT_MU_LAW; + break; + case AUDIO_ENCODING_ALAW: + idat |= AFMT_A_LAW; + break; + case AUDIO_ENCODING_SLINEAR: + idat |= AFMT_S8; + break; + case AUDIO_ENCODING_SLINEAR_LE: + if (tmpenc.precision == 32) + idat |= AFMT_S32_LE; + else if (tmpenc.precision == 24) + idat |= AFMT_S24_LE; + else if (tmpenc.precision == 16) + idat |= AFMT_S16_LE; + else + idat |= AFMT_S8; + break; + case AUDIO_ENCODING_SLINEAR_BE: + if (tmpenc.precision == 32) + idat |= AFMT_S32_BE; + else if (tmpenc.precision == 24) + idat |= AFMT_S24_BE; + else if (tmpenc.precision == 16) + idat |= AFMT_S16_BE; + else + idat |= AFMT_S8; + break; + case AUDIO_ENCODING_ULINEAR: + idat |= AFMT_U8; + break; + case AUDIO_ENCODING_ULINEAR_LE: + if (tmpenc.precision == 16) + idat |= AFMT_U16_LE; + else + idat |= AFMT_U8; + break; + case AUDIO_ENCODING_ULINEAR_BE: + if (tmpenc.precision == 16) + idat |= AFMT_U16_BE; + else + idat |= AFMT_U8; + break; + case AUDIO_ENCODING_ADPCM: + idat |= AFMT_IMA_ADPCM; + break; + case AUDIO_ENCODING_AC3: + idat |= AFMT_AC3; + break; + default: + break; + } + } + INTARG = idat; + break; + case SNDCTL_DSP_GETOSPACE: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + bufinfo.fragsize = tmpinfo.blocksize; + bufinfo.fragments = tmpinfo.hiwat - (tmpinfo.play.seek + + tmpinfo.blocksize - 1) / tmpinfo.blocksize; + bufinfo.fragstotal = tmpinfo.hiwat; + bufinfo.bytes = tmpinfo.hiwat * tmpinfo.blocksize + - tmpinfo.play.seek; + *(struct audio_buf_info *)argp = bufinfo; + break; + case SNDCTL_DSP_GETISPACE: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + bufinfo.fragsize = tmpinfo.blocksize; + bufinfo.fragments = tmpinfo.record.seek / tmpinfo.blocksize; + bufinfo.fragstotal = + tmpinfo.record.buffer_size / tmpinfo.blocksize; + bufinfo.bytes = tmpinfo.record.seek; + *(struct audio_buf_info *)argp = bufinfo; + break; + case SNDCTL_DSP_NONBLOCK: + idat = 1; + retval = ioctl(fd, FIONBIO, &idat); + if (retval < 0) + return retval; + break; + case SNDCTL_DSP_GETCAPS: + retval = _oss_get_caps(fd, (int *)argp); + if (retval < 0) + return retval; + break; + case SNDCTL_DSP_SETTRIGGER: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + AUDIO_INITINFO(&tmpinfo); + if (tmpinfo.mode & AUMODE_PLAY) + tmpinfo.play.pause = (INTARG & PCM_ENABLE_OUTPUT) == 0; + if (tmpinfo.mode & AUMODE_RECORD) + tmpinfo.record.pause = (INTARG & PCM_ENABLE_INPUT) == 0; + (void)ioctl(fd, AUDIO_SETINFO, &tmpinfo); + /* FALLTHRU */ + case SNDCTL_DSP_GETTRIGGER: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + idat = 0; + if ((tmpinfo.mode & AUMODE_PLAY) && !tmpinfo.play.pause) + idat |= PCM_ENABLE_OUTPUT; + if ((tmpinfo.mode & AUMODE_RECORD) && !tmpinfo.record.pause) + idat |= PCM_ENABLE_INPUT; + INTARG = idat; + break; + case SNDCTL_DSP_GETIPTR: + retval = ioctl(fd, AUDIO_GETIOFFS, &tmpoffs); + if (retval < 0) + return retval; + cntinfo.bytes = tmpoffs.samples; + cntinfo.blocks = tmpoffs.deltablks; + cntinfo.ptr = tmpoffs.offset; + *(struct count_info *)argp = cntinfo; + break; + case SNDCTL_DSP_CURRENT_IPTR: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + /* XXX: 'samples' may wrap */ + memset(osscount.filler, 0, sizeof(osscount.filler)); + osscount.samples = tmpinfo.record.samples / + ((tmpinfo.record.precision / NBBY) * + tmpinfo.record.channels); + osscount.fifo_samples = tmpinfo.record.seek / + ((tmpinfo.record.precision / NBBY) * + tmpinfo.record.channels); + *(oss_count_t *)argp = osscount; + break; + case SNDCTL_DSP_GETOPTR: + retval = ioctl(fd, AUDIO_GETOOFFS, &tmpoffs); + if (retval < 0) + return retval; + cntinfo.bytes = tmpoffs.samples; + cntinfo.blocks = tmpoffs.deltablks; + cntinfo.ptr = tmpoffs.offset; + *(struct count_info *)argp = cntinfo; + break; + case SNDCTL_DSP_CURRENT_OPTR: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + /* XXX: 'samples' may wrap */ + memset(osscount.filler, 0, sizeof(osscount.filler)); + osscount.samples = tmpinfo.play.samples / + ((tmpinfo.play.precision / NBBY) * + tmpinfo.play.channels); + osscount.fifo_samples = tmpinfo.play.seek / + ((tmpinfo.play.precision / NBBY) * + tmpinfo.play.channels); + *(oss_count_t *)argp = osscount; + break; + case SNDCTL_DSP_SETPLAYVOL: + set_vol(fd, INTARG, false); + /* FALLTHRU */ + case SNDCTL_DSP_GETPLAYVOL: + retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo); + if (retval < 0) + return retval; + INTARG = get_vol(tmpinfo.play.gain, tmpinfo.play.balance); + break; + case SNDCTL_DSP_SETRECVOL: + set_vol(fd, INTARG, true); + /* FALLTHRU */ + case SNDCTL_DSP_GETRECVOL: + retval = ioctl(fd, AUDIO_GETINFO, &tmpinfo); + if (retval < 0) + return retval; + INTARG = get_vol(tmpinfo.record.gain, tmpinfo.record.balance); + break; + case SNDCTL_DSP_SKIP: + case SNDCTL_DSP_SILENCE: + errno = EINVAL; + return -1; + case SNDCTL_DSP_SETDUPLEX: + idat = 1; + retval = ioctl(fd, AUDIO_SETFD, &idat); + if (retval < 0) + return retval; + break; + case SNDCTL_DSP_GETODELAY: + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + idat = tmpinfo.play.seek + tmpinfo.blocksize / 2; + INTARG = idat; + break; + case SNDCTL_DSP_PROFILE: + /* This gives just a hint to the driver, + * implementing it as a NOP is ok + */ + break; + case SNDCTL_DSP_MAPINBUF: + case SNDCTL_DSP_MAPOUTBUF: + case SNDCTL_DSP_SETSYNCRO: + errno = EINVAL; + return -1; /* XXX unimplemented */ + case SNDCTL_DSP_GET_PLAYTGT_NAMES: + case SNDCTL_DSP_GET_RECSRC_NAMES: + ei = (oss_mixer_enuminfo *)argp; + ei->nvalues = 1; + ei->version = 0; + ei->strindex[0] = 0; + strlcpy(ei->strings, "primary", OSS_ENUM_STRINGSIZE); + break; + case SNDCTL_DSP_SET_PLAYTGT: + case SNDCTL_DSP_SET_RECSRC: + case SNDCTL_DSP_GET_PLAYTGT: + case SNDCTL_DSP_GET_RECSRC: + /* We have one recording source and play target. */ + INTARG = 0; + break; + default: + errno = EINVAL; + return -1; + } + + return 0; +} + +static int +get_vol(u_int gain, u_char balance) +{ + u_int l, r; + + if (balance == AUDIO_MID_BALANCE) { + l = r = gain; + } else if (balance < AUDIO_MID_BALANCE) { + l = gain; + r = (balance * gain) / AUDIO_MID_BALANCE; + } else { + r = gain; + l = ((AUDIO_RIGHT_BALANCE - balance) * gain) + / AUDIO_MID_BALANCE; + } + + return TO_OSSVOL(l) | (TO_OSSVOL(r) << 8); +} + +static void +set_vol(int fd, int volume, bool record) +{ + u_int lgain, rgain; + struct audio_info tmpinfo; + struct audio_prinfo *prinfo; + + AUDIO_INITINFO(&tmpinfo); + prinfo = record ? &tmpinfo.record : &tmpinfo.play; + + lgain = FROM_OSSVOL((volume >> 0) & 0xff); + rgain = FROM_OSSVOL((volume >> 8) & 0xff); + + if (lgain == rgain) { + prinfo->gain = lgain; + prinfo->balance = AUDIO_MID_BALANCE; + } else if (lgain < rgain) { + prinfo->gain = rgain; + prinfo->balance = AUDIO_RIGHT_BALANCE - + (AUDIO_MID_BALANCE * lgain) / rgain; + } else { + prinfo->gain = lgain; + prinfo->balance = (AUDIO_MID_BALANCE * rgain) / lgain; + } + + (void)ioctl(fd, AUDIO_SETINFO, &tmpinfo); +} + +/* + * When AUDIO_SETINFO fails to set a channel count, the application's chosen + * number is out of range of what the kernel allows. + * + * When this happens, we use the current hardware settings. This is just in + * case an application is abusing SNDCTL_DSP_CHANNELS - OSSv4 always sets and + * returns a reasonable value, even if it wasn't what the user requested. + * + * Solaris guarantees this behaviour if nchannels = 0. + * + * XXX: If a device is opened for both playback and recording, and supports + * fewer channels for recording than playback, applications that do both will + * behave very strangely. OSS doesn't allow for reporting separate channel + * counts for recording and playback. This could be worked around by always + * mixing recorded data up to the same number of channels as is being used + * for playback. + */ +static void +set_channels(int fd, int mode, int nchannels) +{ + struct audio_info tmpinfo, hwfmt; + + if (ioctl(fd, AUDIO_GETFORMAT, &hwfmt) < 0) { + errno = 0; + hwfmt.record.channels = hwfmt.play.channels = 2; + } + + if (mode & AUMODE_PLAY) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.channels = nchannels; + if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) { + errno = 0; + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.channels = hwfmt.play.channels; + (void)ioctl(fd, AUDIO_SETINFO, &tmpinfo); + } + } + + if (mode & AUMODE_RECORD) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.record.channels = nchannels; + if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) { + errno = 0; + AUDIO_INITINFO(&tmpinfo); + tmpinfo.record.channels = hwfmt.record.channels; + (void)ioctl(fd, AUDIO_SETINFO, &tmpinfo); + } + } +} Index: src/lib/libossaudio/oss_ioctl.c diff -u /dev/null src/lib/libossaudio/oss_ioctl.c:1.1 --- /dev/null Tue Jun 8 18:43:54 2021 +++ src/lib/libossaudio/oss_ioctl.c Tue Jun 8 18:43:54 2021 @@ -0,0 +1,26 @@ +/* $NetBSD: oss_ioctl.c,v 1.1 2021/06/08 18:43:54 nia Exp $ */ + +#include <stdarg.h> +#include "internal.h" + +int +_oss_ioctl(int fd, unsigned long com, ...) +{ + va_list ap; + void *argp; + + va_start(ap, com); + argp = va_arg(ap, void *); + va_end(ap); + + if (IOCGROUP(com) == 'P') + return _oss_dsp_ioctl(fd, com, argp); + else if (IOCGROUP(com) == 'M') + return _oss3_mixer_ioctl(fd, com, argp); + else if (IOCGROUP(com) == 'X') + return _oss4_mixer_ioctl(fd, com, argp); + else if (IOCGROUP(com) == 'Y') + return _oss4_global_ioctl(fd, com, argp); + else + return ioctl(fd, com, argp); +}