1 /* $OpenBSD: ossaudio.c,v 1.21 2020/04/02 19:57:10 ratchov Exp $ */ 2 /* $NetBSD: ossaudio.c,v 1.14 2001/05/10 01:53:48 augustss Exp $ */ 3 4 /*- 5 * Copyright (c) 1997 The NetBSD Foundation, Inc. 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 18 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 19 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 20 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 21 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 * POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 /* 31 * This is an OSS (Linux) sound API emulator. 32 * It provides the essentials of the API. 33 */ 34 35 #include <stdarg.h> 36 #include <string.h> 37 #include <sys/types.h> 38 #include <sys/ioctl.h> 39 #include <errno.h> 40 #include <poll.h> 41 #include <sndio.h> 42 #include <stdlib.h> 43 #include <stdio.h> 44 #include "soundcard.h" 45 46 #ifdef DEBUG 47 #define DPRINTF(...) do { fprintf(stderr, __VA_ARGS__); } while (0) 48 #else 49 #define DPRINTF(...) do {} while (0) 50 #endif 51 52 #define GET_DEV(com) ((com) & 0xff) 53 #define INTARG (*(int*)argp) 54 55 struct control { 56 struct control *next; 57 int type; /* one of SOUND_MIXER_xxx */ 58 int chan; /* 0 -> left, 1 -> right, -1 -> mono */ 59 int addr; /* sioctl control id */ 60 int value; /* current value */ 61 int max; 62 }; 63 64 static int mixer_ioctl(int, unsigned long, void *); 65 66 static int initialized; 67 static struct control *controls; 68 static struct sioctl_hdl *hdl; 69 static char *dev_name = SIO_DEVANY; 70 static struct pollfd *pfds; 71 72 int 73 _oss_ioctl(int fd, unsigned long com, ...) 74 { 75 va_list ap; 76 void *argp; 77 78 va_start(ap, com); 79 argp = va_arg(ap, void *); 80 va_end(ap); 81 if (IOCGROUP(com) == 'P') 82 return ENOTTY; 83 else if (IOCGROUP(com) == 'M') 84 return mixer_ioctl(fd, com, argp); 85 else 86 return (ioctl)(fd, com, argp); 87 } 88 89 /* 90 * new control 91 */ 92 static void 93 mixer_ondesc(void *unused, struct sioctl_desc *d, int val) 94 { 95 struct control *i, **pi; 96 int type; 97 98 if (d == NULL) 99 return; 100 101 /* 102 * delete existing control with the same address 103 */ 104 for (pi = &controls; (i = *pi) != NULL; pi = &i->next) { 105 if (d->addr == i->addr) { 106 *pi = i->next; 107 free(i); 108 break; 109 } 110 } 111 112 /* 113 * we support only numeric "level" controls, first 2 channels 114 */ 115 if (d->type != SIOCTL_NUM || d->node0.unit >= 2 || 116 strcmp(d->func, "level") != 0) 117 return; 118 119 /* 120 * We expose top-level input.level and output.level as OSS 121 * volume and microphone knobs. By default sndiod exposes 122 * the underlying hardware knobs as hw/input.level and 123 * hw/output.level that we map to OSS gain controls. This 124 * ensures useful knobs are exposed no matter if sndiod 125 * is running or not. 126 */ 127 if (d->group[0] == 0) { 128 if (strcmp(d->node0.name, "output") == 0) 129 type = SOUND_MIXER_VOLUME; 130 else if (strcmp(d->node0.name, "input") == 0) 131 type = SOUND_MIXER_MIC; 132 else 133 return; 134 } else if (strcmp(d->group, "hw") == 0) { 135 if (strcmp(d->node0.name, "output") == 0) 136 type = SOUND_MIXER_OGAIN; 137 else if (strcmp(d->node0.name, "input") == 0) 138 type = SOUND_MIXER_IGAIN; 139 else 140 return; 141 } else 142 return; 143 144 i = malloc(sizeof(struct control)); 145 if (i == NULL) { 146 DPRINTF("%s: cannot allocate control\n", __func__); 147 return; 148 } 149 150 i->addr = d->addr; 151 i->chan = d->node0.unit; 152 i->max = d->maxval; 153 i->value = val; 154 i->type = type; 155 i->next = controls; 156 controls = i; 157 DPRINTF("%s: %d: used as %d, chan = %d, value = %d\n", __func__, 158 i->addr, i->type, i->chan, i->value); 159 } 160 161 /* 162 * control value changed 163 */ 164 static void 165 mixer_onval(void *unused, unsigned int addr, unsigned int value) 166 { 167 struct control *c; 168 169 for (c = controls; ; c = c->next) { 170 if (c == NULL) { 171 DPRINTF("%s: %d: change ignored\n", __func__, addr); 172 return; 173 } 174 if (c->addr == addr) 175 break; 176 } 177 178 DPRINTF("%s: %d: changed to %d\n", __func__, addr, value); 179 c->value = value; 180 } 181 182 static int 183 mixer_init(void) 184 { 185 if (initialized) 186 return hdl != NULL; 187 188 initialized = 1; 189 190 hdl = sioctl_open(dev_name, SIOCTL_READ | SIOCTL_WRITE, 0); 191 if (hdl == NULL) { 192 DPRINTF("%s: cannot open audio control device\n", __func__); 193 return 0; 194 } 195 196 pfds = calloc(sioctl_nfds(hdl), sizeof(struct pollfd)); 197 if (pfds == NULL) { 198 DPRINTF("%s: cannot allocate pfds\n", __func__); 199 goto bad_close; 200 } 201 202 if (!sioctl_ondesc(hdl, mixer_ondesc, NULL)) { 203 DPRINTF("%s: cannot get controls descriptions\n", __func__); 204 goto bad_free; 205 } 206 207 if (!sioctl_onval(hdl, mixer_onval, NULL)) { 208 DPRINTF("%s: cannot get controls values\n", __func__); 209 goto bad_free; 210 } 211 212 return 1; 213 214 bad_free: 215 free(pfds); 216 bad_close: 217 sioctl_close(hdl); 218 return 0; 219 } 220 221 static int 222 mixer_ioctl(int fd, unsigned long com, void *argp) 223 { 224 struct control *c; 225 struct mixer_info *omi; 226 int idat = 0; 227 int v, n; 228 229 if (!mixer_init()) { 230 DPRINTF("%s: not initialized\n", __func__); 231 errno = EIO; 232 return -1; 233 } 234 235 n = sioctl_pollfd(hdl, pfds, POLLIN); 236 if (n > 0) { 237 n = poll(pfds, n, 0); 238 if (n == -1) 239 return -1; 240 if (n > 0) 241 sioctl_revents(hdl, pfds); 242 } 243 244 switch (com) { 245 case OSS_GETVERSION: 246 idat = SOUND_VERSION; 247 break; 248 case SOUND_MIXER_INFO: 249 case SOUND_OLD_MIXER_INFO: 250 omi = argp; 251 if (com == SOUND_MIXER_INFO) 252 omi->modify_counter = 1; 253 strlcpy(omi->id, dev_name, sizeof omi->id); 254 strlcpy(omi->name, dev_name, sizeof omi->name); 255 return 0; 256 case SOUND_MIXER_READ_RECSRC: 257 case SOUND_MIXER_READ_RECMASK: 258 idat = 0; 259 for (c = controls; c != NULL; c = c->next) 260 idat |= 1 << c->type; 261 idat &= (1 << SOUND_MIXER_MIC) | (1 << SOUND_MIXER_IGAIN); 262 DPRINTF("%s: SOUND_MIXER_READ_RECSRC: %d\n", __func__, idat); 263 break; 264 case SOUND_MIXER_READ_DEVMASK: 265 idat = 0; 266 for (c = controls; c != NULL; c = c->next) 267 idat |= 1 << c->type; 268 DPRINTF("%s: SOUND_MIXER_READ_DEVMASK: %d\n", __func__, idat); 269 break; 270 case SOUND_MIXER_READ_STEREODEVS: 271 idat = 0; 272 for (c = controls; c != NULL; c = c->next) { 273 if (c->chan == 1) 274 idat |= 1 << c->type; 275 } 276 DPRINTF("%s: SOUND_MIXER_STEREODEVS: %d\n", __func__, idat); 277 break; 278 case SOUND_MIXER_READ_CAPS: 279 idat = 0; 280 DPRINTF("%s: SOUND_MIXER_READ_CAPS: %d\n", __func__, idat); 281 break; 282 case SOUND_MIXER_WRITE_RECSRC: 283 case SOUND_MIXER_WRITE_R_RECSRC: 284 DPRINTF("%s: SOUND_MIXER_WRITE_RECSRC\n", __func__); 285 errno = EINVAL; 286 return -1; 287 default: 288 if (MIXER_READ(SOUND_MIXER_FIRST) <= com && 289 com < MIXER_READ(SOUND_MIXER_NRDEVICES)) { 290 doread: 291 idat = 0; 292 n = GET_DEV(com); 293 for (c = controls; c != NULL; c = c->next) { 294 if (c->type != n) 295 continue; 296 v = (c->value * 100 + c->max / 2) / c->max; 297 if (c->chan == 1) 298 v <<= 8; 299 idat |= v; 300 } 301 DPRINTF("%s: MIXER_READ: %d: 0x%04x\n", 302 __func__, n, idat); 303 break; 304 } else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com && 305 com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) || 306 (MIXER_WRITE(SOUND_MIXER_FIRST) <= com && 307 com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) { 308 idat = INTARG; 309 n = GET_DEV(com); 310 for (c = controls; c != NULL; c = c->next) { 311 if (c->type != n) 312 continue; 313 v = idat; 314 if (c->chan == 1) 315 v >>= 8; 316 v &= 0xff; 317 if (v > 100) 318 v = 100; 319 v = (v * c->max + 50) / 100; 320 sioctl_setval(hdl, c->addr, v); 321 DPRINTF("%s: MIXER_WRITE: %d: %d\n", 322 __func__, n, v); 323 } 324 if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com && 325 com < MIXER_WRITE(SOUND_MIXER_NRDEVICES)) 326 return 0; 327 goto doread; 328 } else { 329 errno = EINVAL; 330 return -1; 331 } 332 } 333 334 INTARG = idat; 335 return 0; 336 } 337