diff options
author | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2020-04-02 19:57:11 +0000 |
---|---|---|
committer | Alexandre Ratchov <ratchov@cvs.openbsd.org> | 2020-04-02 19:57:11 +0000 |
commit | bae9dc7ea6180f0f48ab3d0fa9d84071a6d849ca (patch) | |
tree | 79569ff32760e0de743dddd711d281ccbcc57c25 /lib/libossaudio | |
parent | 985e302384b3dae70817212fec405b7f90ba764a (diff) |
Makes libossaudio use sndio instead of the kernel mixer(4) interface.
Programs will see the device handled by sndiod (instead of the first
one) and will always get access at least the sndiod master level
control. If present, hardware master input and output volumes are
exposed as well.
feedback from landry and sthen, "do it" deraadt
Diffstat (limited to 'lib/libossaudio')
-rw-r--r-- | lib/libossaudio/Makefile | 6 | ||||
-rw-r--r-- | lib/libossaudio/ossaudio.c | 473 |
2 files changed, 207 insertions, 272 deletions
diff --git a/lib/libossaudio/Makefile b/lib/libossaudio/Makefile index de1d0e7deeb..ee99b60db10 100644 --- a/lib/libossaudio/Makefile +++ b/lib/libossaudio/Makefile @@ -1,13 +1,15 @@ -# $OpenBSD: Makefile,v 1.5 2014/07/16 20:02:17 okan Exp $ +# $OpenBSD: Makefile,v 1.6 2020/04/02 19:57:10 ratchov Exp $ # $NetBSD: Makefile,v 1.5 1998/04/13 14:18:45 lukem Exp $ LIB= ossaudio MAN= ossaudio.3 -SRCS= ossaudio.c +SRCS= ossaudio.c aucat.c debug.c sioctl.c sioctl_aucat.c sioctl_sun.c CPPFLAGS+= -I${.CURDIR} +.PATH: ${.CURDIR}/../libsndio + includes: @cd ${.CURDIR}; cmp -s soundcard.h ${DESTDIR}/usr/include/soundcard.h || \ ${INSTALL} ${INSTALL_COPY} -m 444 -o $(BINOWN) -g $(BINGRP) soundcard.h \ diff --git a/lib/libossaudio/ossaudio.c b/lib/libossaudio/ossaudio.c index 8374af54830..bad73ac0c33 100644 --- a/lib/libossaudio/ossaudio.c +++ b/lib/libossaudio/ossaudio.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ossaudio.c,v 1.20 2019/06/28 13:32:42 deraadt Exp $ */ +/* $OpenBSD: ossaudio.c,v 1.21 2020/04/02 19:57:10 ratchov Exp $ */ /* $NetBSD: ossaudio.c,v 1.14 2001/05/10 01:53:48 augustss Exp $ */ /*- @@ -36,26 +36,38 @@ #include <string.h> #include <sys/types.h> #include <sys/ioctl.h> -#include <sys/audioio.h> -#include <sys/stat.h> #include <errno.h> - +#include <poll.h> +#include <sndio.h> +#include <stdlib.h> +#include <stdio.h> #include "soundcard.h" -#undef ioctl -#define GET_DEV(com) ((com) & 0xff) +#ifdef DEBUG +#define DPRINTF(...) do { fprintf(stderr, __VA_ARGS__); } while (0) +#else +#define DPRINTF(...) do {} while (0) +#endif -#define TO_OSSVOL(x) (((x) * 100 + 127) / 255) -#define FROM_OSSVOL(x) ((((x) > 100 ? 100 : (x)) * 255 + 50) / 100) +#define GET_DEV(com) ((com) & 0xff) +#define INTARG (*(int*)argp) -static struct audiodevinfo *getdevinfo(int); +struct control { + struct control *next; + int type; /* one of SOUND_MIXER_xxx */ + int chan; /* 0 -> left, 1 -> right, -1 -> mono */ + int addr; /* sioctl control id */ + int value; /* current value */ + int max; +}; static int mixer_ioctl(int, unsigned long, void *); -static int opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq); -static int enum_to_ord(struct audiodevinfo *di, int enm); -static int enum_to_mask(struct audiodevinfo *di, int enm); -#define INTARG (*(int*)argp) +static int initialized; +static struct control *controls; +static struct sioctl_hdl *hdl; +static char *dev_name = SIO_DEVANY; +static struct pollfd *pfds; int _oss_ioctl(int fd, unsigned long com, ...) @@ -71,201 +83,163 @@ _oss_ioctl(int fd, unsigned long com, ...) else if (IOCGROUP(com) == 'M') return mixer_ioctl(fd, com, argp); else - return ioctl(fd, com, argp); + return (ioctl)(fd, com, argp); } -/* If the mixer device should have more than MAX_MIXER_DEVS devices - * some will not be available to Linux */ -#define MAX_MIXER_DEVS 64 -struct audiodevinfo { - int done; - dev_t dev; - ino_t ino; - int16_t devmap[SOUND_MIXER_NRDEVICES], - rdevmap[MAX_MIXER_DEVS]; - char names[MAX_MIXER_DEVS][MAX_AUDIO_DEV_LEN]; - int enum2opaque[MAX_MIXER_DEVS]; - u_long devmask, recmask, stereomask; - u_long caps, recsource; -}; - -static int -opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq) +/* + * new control + */ +static void +mixer_ondesc(void *unused, struct sioctl_desc *d, int val) { - int i, o; + struct control *i, **pi; + int type; - for (i = 0; i < MAX_MIXER_DEVS; 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; + if (d == NULL) + return; + + /* + * delete existing control with the same address + */ + for (pi = &controls; (i = *pi) != NULL; pi = &i->next) { + if (d->addr == i->addr) { + *pi = i->next; + free(i); break; } } - if (i >= MAX_MIXER_DEVS) - i = -1; - /*printf("opq_to_enum %s %d -> %d\n", label->name, opq, i);*/ - return (i); + + /* + * we support only numeric "level" controls, first 2 channels + */ + if (d->type != SIOCTL_NUM || d->node0.unit >= 2 || + strcmp(d->func, "level") != 0) + return; + + /* + * We expose top-level input.level and output.level as OSS + * volume and microphone knobs. By default sndiod exposes + * the underlying hardware knobs as hw/input.level and + * hw/output.level that we map to OSS gain controls. This + * ensures useful knobs are exposed no matter if sndiod + * is running or not. + */ + if (d->group[0] == 0) { + if (strcmp(d->node0.name, "output") == 0) + type = SOUND_MIXER_VOLUME; + else if (strcmp(d->node0.name, "input") == 0) + type = SOUND_MIXER_MIC; + else + return; + } else if (strcmp(d->group, "hw") == 0) { + if (strcmp(d->node0.name, "output") == 0) + type = SOUND_MIXER_OGAIN; + else if (strcmp(d->node0.name, "input") == 0) + type = SOUND_MIXER_IGAIN; + else + return; + } else + return; + + i = malloc(sizeof(struct control)); + if (i == NULL) { + DPRINTF("%s: cannot allocate control\n", __func__); + return; + } + + i->addr = d->addr; + i->chan = d->node0.unit; + i->max = d->maxval; + i->value = val; + i->type = type; + i->next = controls; + controls = i; + DPRINTF("%s: %d: used as %d, chan = %d, value = %d\n", __func__, + i->addr, i->type, i->chan, i->value); } -static int -enum_to_ord(struct audiodevinfo *di, int enm) +/* + * control value changed + */ +static void +mixer_onval(void *unused, unsigned int addr, unsigned int value) { - if (enm >= MAX_MIXER_DEVS) - return (-1); + struct control *c; + + for (c = controls; ; c = c->next) { + if (c == NULL) { + DPRINTF("%s: %d: change ignored\n", __func__, addr); + return; + } + if (c->addr == addr) + break; + } - /*printf("enum_to_ord %d -> %d\n", enm, di->enum2opaque[enm]);*/ - return (di->enum2opaque[enm]); + DPRINTF("%s: %d: changed to %d\n", __func__, addr, value); + c->value = value; } static int -enum_to_mask(struct audiodevinfo *di, int enm) +mixer_init(void) { - int m; - if (enm >= MAX_MIXER_DEVS) - return (0); - - m = di->enum2opaque[enm]; - if (m == -1) - m = 0; - /*printf("enum_to_mask %d -> %d\n", enm, di->enum2opaque[enm]);*/ - return (m); -} + if (initialized) + return hdl != NULL; -/* - * Collect the audio device information to allow faster - * emulation of the Linux 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, cl; - int i, j, e; - static struct { - 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 }, - { AudioNoutput, SOUND_MIXER_OGAIN }, - { AudioNinput, SOUND_MIXER_IGAIN }, - { AudioNfmsynth, SOUND_MIXER_SYNTH }, - { AudioNmidi, SOUND_MIXER_SYNTH }, - { 0, -1 } - }; - static struct audiodevinfo devcache = { 0 }; - struct audiodevinfo *di = &devcache; - struct stat sb; - - /* Figure out what device it is so we can check if the - * cached data is valid. - */ - if (fstat(fd, &sb) < 0) + initialized = 1; + + hdl = sioctl_open(dev_name, SIOCTL_READ | SIOCTL_WRITE, 0); + if (hdl == NULL) { + DPRINTF("%s: cannot open audio control device\n", __func__); return 0; - if (di->done && (di->dev == sb.st_dev && di->ino == sb.st_ino)) - return di; - - di->done = 1; - di->dev = sb.st_dev; - di->ino = sb.st_ino; - di->devmask = 0; - di->recmask = 0; - di->stereomask = 0; - di->recsource = ~0; - di->caps = 0; - for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) - di->devmap[i] = -1; - for(i = 0; i < MAX_MIXER_DEVS; i++) { - di->rdevmap[i] = -1; - di->names[i][0] = '\0'; - di->enum2opaque[i] = -1; } - for(i = 0; i < MAX_MIXER_DEVS; i++) { - mi.index = i; - if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1) - break; - switch(mi.type) { - case AUDIO_MIXER_VALUE: - for(dp = devs; dp->name; dp++) - if (strcmp(dp->name, mi.label.name) == 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; - strncpy(di->names[i], mi.label.name, - sizeof di->names[i]); - } - break; - } + + pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd)); + if (pfds == NULL) { + DPRINTF("%s: cannot allocate pfds\n", __func__); + goto bad_close; } - for(i = 0; i < MAX_MIXER_DEVS; i++) { - mi.index = i; - if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1) - break; - if (strcmp(mi.label.name, AudioNsource) != 0) - continue; - cl.index = mi.mixer_class; - if (ioctl(fd, AUDIO_MIXER_DEVINFO, &cl) == -1) - break; - if ((cl.type != AUDIO_MIXER_CLASS) || - (strcmp(cl.label.name, AudioCrecord) != 0)) - continue; - di->recsource = 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; - } + + if (!sioctl_ondesc(hdl, mixer_ondesc, NULL)) { + DPRINTF("%s: cannot get controls descriptions\n", __func__); + goto bad_free; } - return di; + + if (!sioctl_onval(hdl, mixer_onval, NULL)) { + DPRINTF("%s: cannot get controls values\n", __func__); + goto bad_free; + } + + return 1; + +bad_free: + free(pfds); +bad_close: + sioctl_close(hdl); + return 0; } -int +static int mixer_ioctl(int fd, unsigned long com, void *argp) { - struct audiodevinfo *di; + struct control *c; struct mixer_info *omi; - struct audio_device adev; - mixer_ctrl_t mc; int idat = 0; - int i; - int retval; - int l, r, n, error, e; + int v, n; - di = getdevinfo(fd); - if (di == 0) + if (!mixer_init()) { + DPRINTF("%s: not initialized\n", __func__); + errno = EIO; return -1; + } + + n = sioctl_pollfd(hdl, pfds, POLLIN); + if (n > 0) { + n = poll(pfds, n, 0); + if (n == -1) + return -1; + if (n > 0) + sioctl_revents(hdl, pfds); + } switch (com) { case OSS_GETVERSION: @@ -273,122 +247,80 @@ mixer_ioctl(int fd, unsigned long com, void *argp) break; case SOUND_MIXER_INFO: case SOUND_OLD_MIXER_INFO: - error = ioctl(fd, AUDIO_GETDEV, &adev); - if (error == -1) - return (error); omi = argp; if (com == SOUND_MIXER_INFO) omi->modify_counter = 1; - strncpy(omi->id, adev.name, sizeof omi->id); - strncpy(omi->name, adev.name, sizeof omi->name); + strlcpy(omi->id, dev_name, sizeof omi->id); + strlcpy(omi->name, dev_name, sizeof omi->name); return 0; case SOUND_MIXER_READ_RECSRC: - if (di->recsource == -1) - return EINVAL; - mc.dev = di->recsource; - if (di->caps & SOUND_CAP_EXCL_INPUT) { - mc.type = AUDIO_MIXER_ENUM; - retval = ioctl(fd, AUDIO_MIXER_READ, &mc); - if (retval == -1) - 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 == -1) - return retval; - e = opaque_to_enum(di, NULL, mc.un.mask); - if (e >= 0) - idat = 1 << di->rdevmap[e]; - } + case SOUND_MIXER_READ_RECMASK: + idat = 0; + for (c = controls; c != NULL; c = c->next) + idat |= 1 << c->type; + idat &= (1 << SOUND_MIXER_MIC) | (1 << SOUND_MIXER_IGAIN); + DPRINTF("%s: SOUND_MIXER_READ_RECSRC: %d\n", __func__, idat); break; case SOUND_MIXER_READ_DEVMASK: - idat = di->devmask; - break; - case SOUND_MIXER_READ_RECMASK: - idat = di->recmask; + idat = 0; + for (c = controls; c != NULL; c = c->next) + idat |= 1 << c->type; + DPRINTF("%s: SOUND_MIXER_READ_DEVMASK: %d\n", __func__, idat); break; case SOUND_MIXER_READ_STEREODEVS: - idat = di->stereomask; + idat = 0; + for (c = controls; c != NULL; c = c->next) { + if (c->chan == 1) + idat |= 1 << c->type; + } + DPRINTF("%s: SOUND_MIXER_STEREODEVS: %d\n", __func__, idat); break; case SOUND_MIXER_READ_CAPS: - idat = di->caps; + idat = 0; + DPRINTF("%s: SOUND_MIXER_READ_CAPS: %d\n", __func__, idat); break; case SOUND_MIXER_WRITE_RECSRC: case SOUND_MIXER_WRITE_R_RECSRC: - if (di->recsource == -1) - return EINVAL; - mc.dev = di->recsource; - 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) - return EINVAL; - 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) - return EINVAL; - mc.un.mask |= enum_to_mask(di, di->devmap[i]); - } - } - } - return ioctl(fd, AUDIO_MIXER_WRITE, &mc); + DPRINTF("%s: SOUND_MIXER_WRITE_RECSRC\n", __func__); + errno = EINVAL; + return -1; default: if (MIXER_READ(SOUND_MIXER_FIRST) <= com && com < MIXER_READ(SOUND_MIXER_NRDEVICES)) { + doread: + idat = 0; n = GET_DEV(com); - if (di->devmap[n] == -1) - return EINVAL; - mc.dev = di->devmap[n]; - mc.type = AUDIO_MIXER_VALUE; - doread: - mc.un.value.num_channels = di->stereomask & (1<<n) ? 2 : 1; - retval = ioctl(fd, AUDIO_MIXER_READ, &mc); - if (retval == -1) - return retval; - if (mc.type != AUDIO_MIXER_VALUE) - return EINVAL; - 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]; + for (c = controls; c != NULL; c = c->next) { + if (c->type != n) + continue; + v = (c->value * 100 + c->max / 2) / c->max; + if (c->chan == 1) + v <<= 8; + idat |= v; } - idat = TO_OSSVOL(l) | (TO_OSSVOL(r) << 8); + DPRINTF("%s: MIXER_READ: %d: 0x%04x\n", + __func__, n, idat); 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) - return EINVAL; idat = INTARG; - l = FROM_OSSVOL( idat & 0xff); - r = FROM_OSSVOL((idat >> 8) & 0xff); - mc.dev = di->devmap[n]; - mc.type = AUDIO_MIXER_VALUE; - if (di->stereomask & (1<<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; + n = GET_DEV(com); + for (c = controls; c != NULL; c = c->next) { + if (c->type != n) + continue; + v = idat; + if (c->chan == 1) + v >>= 8; + v &= 0xff; + if (v > 100) + v = 100; + v = (v * c->max + 50) / 100; + sioctl_setval(hdl, c->addr, v); + DPRINTF("%s: MIXER_WRITE: %d: %d\n", + __func__, n, v); } - retval = ioctl(fd, AUDIO_MIXER_WRITE, &mc); - if (retval == -1) - return retval; if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com && com < MIXER_WRITE(SOUND_MIXER_NRDEVICES)) return 0; @@ -398,6 +330,7 @@ mixer_ioctl(int fd, unsigned long com, void *argp) return -1; } } + INTARG = idat; return 0; } |