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