xref: /openbsd-src/lib/libossaudio/ossaudio.c (revision 99fd087599a8791921855f21bd7e36130f39aadc)
1 /*	$OpenBSD: ossaudio.c,v 1.20 2019/06/28 13:32:42 deraadt 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 <sys/audioio.h>
40 #include <sys/stat.h>
41 #include <errno.h>
42 
43 #include "soundcard.h"
44 #undef ioctl
45 
46 #define GET_DEV(com) ((com) & 0xff)
47 
48 #define TO_OSSVOL(x)	(((x) * 100 + 127) / 255)
49 #define FROM_OSSVOL(x)	((((x) > 100 ? 100 : (x)) * 255 + 50) / 100)
50 
51 static struct audiodevinfo *getdevinfo(int);
52 
53 static int mixer_ioctl(int, unsigned long, void *);
54 static int opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq);
55 static int enum_to_ord(struct audiodevinfo *di, int enm);
56 static int enum_to_mask(struct audiodevinfo *di, int enm);
57 
58 #define INTARG (*(int*)argp)
59 
60 int
61 _oss_ioctl(int fd, unsigned long com, ...)
62 {
63 	va_list ap;
64 	void *argp;
65 
66 	va_start(ap, com);
67 	argp = va_arg(ap, void *);
68 	va_end(ap);
69 	if (IOCGROUP(com) == 'P')
70 		return ENOTTY;
71 	else if (IOCGROUP(com) == 'M')
72 		return mixer_ioctl(fd, com, argp);
73 	else
74 		return ioctl(fd, com, argp);
75 }
76 
77 /* If the mixer device should have more than MAX_MIXER_DEVS devices
78  * some will not be available to Linux */
79 #define MAX_MIXER_DEVS 64
80 struct audiodevinfo {
81 	int done;
82 	dev_t dev;
83 	ino_t ino;
84 	int16_t devmap[SOUND_MIXER_NRDEVICES],
85 	        rdevmap[MAX_MIXER_DEVS];
86 	char names[MAX_MIXER_DEVS][MAX_AUDIO_DEV_LEN];
87 	int enum2opaque[MAX_MIXER_DEVS];
88         u_long devmask, recmask, stereomask;
89 	u_long caps, recsource;
90 };
91 
92 static int
93 opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, int opq)
94 {
95 	int i, o;
96 
97 	for (i = 0; i < MAX_MIXER_DEVS; i++) {
98 		o = di->enum2opaque[i];
99 		if (o == opq)
100 			break;
101 		if (o == -1 && label != NULL &&
102 		    !strncmp(di->names[i], label->name, sizeof di->names[i])) {
103 			di->enum2opaque[i] = opq;
104 			break;
105 		}
106 	}
107 	if (i >= MAX_MIXER_DEVS)
108 		i = -1;
109 	/*printf("opq_to_enum %s %d -> %d\n", label->name, opq, i);*/
110 	return (i);
111 }
112 
113 static int
114 enum_to_ord(struct audiodevinfo *di, int enm)
115 {
116 	if (enm >= MAX_MIXER_DEVS)
117 		return (-1);
118 
119 	/*printf("enum_to_ord %d -> %d\n", enm, di->enum2opaque[enm]);*/
120 	return (di->enum2opaque[enm]);
121 }
122 
123 static int
124 enum_to_mask(struct audiodevinfo *di, int enm)
125 {
126 	int m;
127 	if (enm >= MAX_MIXER_DEVS)
128 		return (0);
129 
130 	m = di->enum2opaque[enm];
131 	if (m == -1)
132 		m = 0;
133 	/*printf("enum_to_mask %d -> %d\n", enm, di->enum2opaque[enm]);*/
134 	return (m);
135 }
136 
137 /*
138  * Collect the audio device information to allow faster
139  * emulation of the Linux mixer ioctls.  Cache the information
140  * to eliminate the overhead of repeating all the ioctls needed
141  * to collect the information.
142  */
143 static struct audiodevinfo *
144 getdevinfo(int fd)
145 {
146 	mixer_devinfo_t mi, cl;
147 	int i, j, e;
148 	static struct {
149 		char *name;
150 		int code;
151 	} *dp, devs[] = {
152 		{ AudioNmicrophone,	SOUND_MIXER_MIC },
153 		{ AudioNline,		SOUND_MIXER_LINE },
154 		{ AudioNcd,		SOUND_MIXER_CD },
155 		{ AudioNdac,		SOUND_MIXER_PCM },
156 		{ AudioNaux,		SOUND_MIXER_LINE1 },
157 		{ AudioNrecord,		SOUND_MIXER_IMIX },
158 		{ AudioNmaster,		SOUND_MIXER_VOLUME },
159 		{ AudioNtreble,		SOUND_MIXER_TREBLE },
160 		{ AudioNbass,		SOUND_MIXER_BASS },
161 		{ AudioNspeaker,	SOUND_MIXER_SPEAKER },
162 		{ AudioNoutput,		SOUND_MIXER_OGAIN },
163 		{ AudioNinput,		SOUND_MIXER_IGAIN },
164 		{ AudioNfmsynth,	SOUND_MIXER_SYNTH },
165 		{ AudioNmidi,		SOUND_MIXER_SYNTH },
166 		{ 0, -1 }
167 	};
168 	static struct audiodevinfo devcache = { 0 };
169 	struct audiodevinfo *di = &devcache;
170 	struct stat sb;
171 
172 	/* Figure out what device it is so we can check if the
173 	 * cached data is valid.
174 	 */
175 	if (fstat(fd, &sb) < 0)
176 		return 0;
177 	if (di->done && (di->dev == sb.st_dev && di->ino == sb.st_ino))
178 		return di;
179 
180 	di->done = 1;
181 	di->dev = sb.st_dev;
182 	di->ino = sb.st_ino;
183 	di->devmask = 0;
184 	di->recmask = 0;
185 	di->stereomask = 0;
186 	di->recsource = ~0;
187 	di->caps = 0;
188 	for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
189 		di->devmap[i] = -1;
190 	for(i = 0; i < MAX_MIXER_DEVS; i++) {
191 		di->rdevmap[i] = -1;
192 		di->names[i][0] = '\0';
193 		di->enum2opaque[i] = -1;
194 	}
195 	for(i = 0; i < MAX_MIXER_DEVS; i++) {
196 		mi.index = i;
197 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1)
198 			break;
199 		switch(mi.type) {
200 		case AUDIO_MIXER_VALUE:
201 			for(dp = devs; dp->name; dp++)
202 		    		if (strcmp(dp->name, mi.label.name) == 0)
203 					break;
204 			if (dp->code >= 0) {
205 				di->devmap[dp->code] = i;
206 				di->rdevmap[i] = dp->code;
207 				di->devmask |= 1 << dp->code;
208 				if (mi.un.v.num_channels == 2)
209 					di->stereomask |= 1 << dp->code;
210 				strncpy(di->names[i], mi.label.name,
211 					sizeof di->names[i]);
212 			}
213 			break;
214 		}
215 	}
216 	for(i = 0; i < MAX_MIXER_DEVS; i++) {
217 		mi.index = i;
218 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &mi) == -1)
219 			break;
220 		if (strcmp(mi.label.name, AudioNsource) != 0)
221 			continue;
222 		cl.index = mi.mixer_class;
223 		if (ioctl(fd, AUDIO_MIXER_DEVINFO, &cl) == -1)
224 			break;
225 		if ((cl.type != AUDIO_MIXER_CLASS) ||
226 		    (strcmp(cl.label.name, AudioCrecord) != 0))
227 			continue;
228 		di->recsource = i;
229 		switch(mi.type) {
230 		case AUDIO_MIXER_ENUM:
231 			for(j = 0; j < mi.un.e.num_mem; j++) {
232 				e = opaque_to_enum(di,
233 						   &mi.un.e.member[j].label,
234 						   mi.un.e.member[j].ord);
235 				if (e >= 0)
236 					di->recmask |= 1 << di->rdevmap[e];
237 			}
238 			di->caps = SOUND_CAP_EXCL_INPUT;
239 			break;
240 		case AUDIO_MIXER_SET:
241 			for(j = 0; j < mi.un.s.num_mem; j++) {
242 				e = opaque_to_enum(di,
243 						   &mi.un.s.member[j].label,
244 						   mi.un.s.member[j].mask);
245 				if (e >= 0)
246 					di->recmask |= 1 << di->rdevmap[e];
247 			}
248 			break;
249 		}
250 	}
251 	return di;
252 }
253 
254 int
255 mixer_ioctl(int fd, unsigned long com, void *argp)
256 {
257 	struct audiodevinfo *di;
258 	struct mixer_info *omi;
259 	struct audio_device adev;
260 	mixer_ctrl_t mc;
261 	int idat = 0;
262 	int i;
263 	int retval;
264 	int l, r, n, error, e;
265 
266 	di = getdevinfo(fd);
267 	if (di == 0)
268 		return -1;
269 
270 	switch (com) {
271 	case OSS_GETVERSION:
272 		idat = SOUND_VERSION;
273 		break;
274 	case SOUND_MIXER_INFO:
275 	case SOUND_OLD_MIXER_INFO:
276 		error = ioctl(fd, AUDIO_GETDEV, &adev);
277 		if (error == -1)
278 			return (error);
279 		omi = argp;
280 		if (com == SOUND_MIXER_INFO)
281 			omi->modify_counter = 1;
282 		strncpy(omi->id, adev.name, sizeof omi->id);
283 		strncpy(omi->name, adev.name, sizeof omi->name);
284 		return 0;
285 	case SOUND_MIXER_READ_RECSRC:
286 		if (di->recsource == -1)
287 			return EINVAL;
288 		mc.dev = di->recsource;
289 		if (di->caps & SOUND_CAP_EXCL_INPUT) {
290 			mc.type = AUDIO_MIXER_ENUM;
291 			retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
292 			if (retval == -1)
293 				return retval;
294 			e = opaque_to_enum(di, NULL, mc.un.ord);
295 			if (e >= 0)
296 				idat = 1 << di->rdevmap[e];
297 		} else {
298 			mc.type = AUDIO_MIXER_SET;
299 			retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
300 			if (retval == -1)
301 				return retval;
302 			e = opaque_to_enum(di, NULL, mc.un.mask);
303 			if (e >= 0)
304 				idat = 1 << di->rdevmap[e];
305 		}
306 		break;
307 	case SOUND_MIXER_READ_DEVMASK:
308 		idat = di->devmask;
309 		break;
310 	case SOUND_MIXER_READ_RECMASK:
311 		idat = di->recmask;
312 		break;
313 	case SOUND_MIXER_READ_STEREODEVS:
314 		idat = di->stereomask;
315 		break;
316 	case SOUND_MIXER_READ_CAPS:
317 		idat = di->caps;
318 		break;
319 	case SOUND_MIXER_WRITE_RECSRC:
320 	case SOUND_MIXER_WRITE_R_RECSRC:
321 		if (di->recsource == -1)
322 			return EINVAL;
323 		mc.dev = di->recsource;
324 		idat = INTARG;
325 		if (di->caps & SOUND_CAP_EXCL_INPUT) {
326 			mc.type = AUDIO_MIXER_ENUM;
327 			for(i = 0; i < SOUND_MIXER_NRDEVICES; i++)
328 				if (idat & (1 << i))
329 					break;
330 			if (i >= SOUND_MIXER_NRDEVICES ||
331 			    di->devmap[i] == -1)
332 				return EINVAL;
333 			mc.un.ord = enum_to_ord(di, di->devmap[i]);
334 		} else {
335 			mc.type = AUDIO_MIXER_SET;
336 			mc.un.mask = 0;
337 			for(i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
338 				if (idat & (1 << i)) {
339 					if (di->devmap[i] == -1)
340 						return EINVAL;
341 					mc.un.mask |= enum_to_mask(di, di->devmap[i]);
342 				}
343 			}
344 		}
345 		return ioctl(fd, AUDIO_MIXER_WRITE, &mc);
346 	default:
347 		if (MIXER_READ(SOUND_MIXER_FIRST) <= com &&
348 		    com < MIXER_READ(SOUND_MIXER_NRDEVICES)) {
349 			n = GET_DEV(com);
350 			if (di->devmap[n] == -1)
351 				return EINVAL;
352 			mc.dev = di->devmap[n];
353 			mc.type = AUDIO_MIXER_VALUE;
354 		    doread:
355 			mc.un.value.num_channels = di->stereomask & (1<<n) ? 2 : 1;
356 			retval = ioctl(fd, AUDIO_MIXER_READ, &mc);
357 			if (retval == -1)
358 				return retval;
359 			if (mc.type != AUDIO_MIXER_VALUE)
360 				return EINVAL;
361 			if (mc.un.value.num_channels != 2) {
362 				l = r = mc.un.value.level[AUDIO_MIXER_LEVEL_MONO];
363 			} else {
364 				l = mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
365 				r = mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
366 			}
367 			idat = TO_OSSVOL(l) | (TO_OSSVOL(r) << 8);
368 			break;
369 		} else if ((MIXER_WRITE_R(SOUND_MIXER_FIRST) <= com &&
370 			   com < MIXER_WRITE_R(SOUND_MIXER_NRDEVICES)) ||
371 			   (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
372 			   com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))) {
373 			n = GET_DEV(com);
374 			if (di->devmap[n] == -1)
375 				return EINVAL;
376 			idat = INTARG;
377 			l = FROM_OSSVOL( idat       & 0xff);
378 			r = FROM_OSSVOL((idat >> 8) & 0xff);
379 			mc.dev = di->devmap[n];
380 			mc.type = AUDIO_MIXER_VALUE;
381 			if (di->stereomask & (1<<n)) {
382 				mc.un.value.num_channels = 2;
383 				mc.un.value.level[AUDIO_MIXER_LEVEL_LEFT] = l;
384 				mc.un.value.level[AUDIO_MIXER_LEVEL_RIGHT] = r;
385 			} else {
386 				mc.un.value.num_channels = 1;
387 				mc.un.value.level[AUDIO_MIXER_LEVEL_MONO] = (l+r)/2;
388 			}
389 			retval = ioctl(fd, AUDIO_MIXER_WRITE, &mc);
390 			if (retval == -1)
391 				return retval;
392 			if (MIXER_WRITE(SOUND_MIXER_FIRST) <= com &&
393 			   com < MIXER_WRITE(SOUND_MIXER_NRDEVICES))
394 				return 0;
395 			goto doread;
396 		} else {
397 			errno = EINVAL;
398 			return -1;
399 		}
400 	}
401 	INTARG = idat;
402 	return 0;
403 }
404