xref: /openbsd/lib/libossaudio/ossaudio.c (revision 905646f0)
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