xref: /freebsd/lib/libmixer/mixer.c (revision e5f5ca7fee26179725ab2d66b5500d51fe8ae113)
1903873ceSHans Petter Selasky /*-
2903873ceSHans Petter Selasky  * Copyright (c) 2021 Christos Margiolis <christos@FreeBSD.org>
3903873ceSHans Petter Selasky  *
4903873ceSHans Petter Selasky  * Permission is hereby granted, free of charge, to any person obtaining a copy
5903873ceSHans Petter Selasky  * of this software and associated documentation files (the "Software"), to deal
6903873ceSHans Petter Selasky  * in the Software without restriction, including without limitation the rights
7903873ceSHans Petter Selasky  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8903873ceSHans Petter Selasky  * copies of the Software, and to permit persons to whom the Software is
9903873ceSHans Petter Selasky  * furnished to do so, subject to the following conditions:
10903873ceSHans Petter Selasky  *
11903873ceSHans Petter Selasky  * The above copyright notice and this permission notice shall be included in
12903873ceSHans Petter Selasky  * all copies or substantial portions of the Software.
13903873ceSHans Petter Selasky  *
14903873ceSHans Petter Selasky  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15903873ceSHans Petter Selasky  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16903873ceSHans Petter Selasky  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17903873ceSHans Petter Selasky  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18903873ceSHans Petter Selasky  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19903873ceSHans Petter Selasky  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20903873ceSHans Petter Selasky  * THE SOFTWARE.
21903873ceSHans Petter Selasky  *
22903873ceSHans Petter Selasky  * $FreeBSD$
23903873ceSHans Petter Selasky  */
24903873ceSHans Petter Selasky 
25903873ceSHans Petter Selasky #include <sys/types.h>
26903873ceSHans Petter Selasky #include <sys/ioctl.h>
27903873ceSHans Petter Selasky #include <sys/sysctl.h>
28903873ceSHans Petter Selasky 
29903873ceSHans Petter Selasky #include <errno.h>
30903873ceSHans Petter Selasky #include <fcntl.h>
31903873ceSHans Petter Selasky #include <stdio.h>
32903873ceSHans Petter Selasky #include <stdlib.h>
33903873ceSHans Petter Selasky #include <string.h>
34903873ceSHans Petter Selasky #include <unistd.h>
35903873ceSHans Petter Selasky 
36903873ceSHans Petter Selasky #include "mixer.h"
37903873ceSHans Petter Selasky 
38903873ceSHans Petter Selasky #define	BASEPATH "/dev/mixer"
39903873ceSHans Petter Selasky 
40*e5f5ca7fSChristos Margiolis static int _mixer_readvol(struct mix_dev *);
41903873ceSHans Petter Selasky 
42903873ceSHans Petter Selasky /*
43903873ceSHans Petter Selasky  * Fetch volume from the device.
44903873ceSHans Petter Selasky  */
45903873ceSHans Petter Selasky static int
46*e5f5ca7fSChristos Margiolis _mixer_readvol(struct mix_dev *dev)
47903873ceSHans Petter Selasky {
48903873ceSHans Petter Selasky 	int v;
49903873ceSHans Petter Selasky 
50*e5f5ca7fSChristos Margiolis 	if (ioctl(dev->parent_mixer->fd, MIXER_READ(dev->devno), &v) < 0)
51903873ceSHans Petter Selasky 		return (-1);
52903873ceSHans Petter Selasky 	dev->vol.left = MIX_VOLNORM(v & 0x00ff);
53903873ceSHans Petter Selasky 	dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff);
54903873ceSHans Petter Selasky 
55903873ceSHans Petter Selasky 	return (0);
56903873ceSHans Petter Selasky }
57903873ceSHans Petter Selasky 
58903873ceSHans Petter Selasky /*
59903873ceSHans Petter Selasky  * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer.
60903873ceSHans Petter Selasky  * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the
61903873ceSHans Petter Selasky  * mixer for pcm0, and so on.
62903873ceSHans Petter Selasky  *
63903873ceSHans Petter Selasky  * @param name		path to mixer device. NULL or "/dev/mixer" for the
64903873ceSHans Petter Selasky  *			the default mixer (i.e `hw.snd.default_unit`).
65903873ceSHans Petter Selasky  */
66903873ceSHans Petter Selasky struct mixer *
67903873ceSHans Petter Selasky mixer_open(const char *name)
68903873ceSHans Petter Selasky {
69903873ceSHans Petter Selasky 	struct mixer *m = NULL;
70903873ceSHans Petter Selasky 	struct mix_dev *dp;
71903873ceSHans Petter Selasky 	const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
72903873ceSHans Petter Selasky 	int i;
73903873ceSHans Petter Selasky 
74903873ceSHans Petter Selasky 	if ((m = calloc(1, sizeof(struct mixer))) == NULL)
75903873ceSHans Petter Selasky 		goto fail;
76903873ceSHans Petter Selasky 
77903873ceSHans Petter Selasky 	if (name != NULL) {
78903873ceSHans Petter Selasky 		/* `name` does not start with "/dev/mixer". */
79903873ceSHans Petter Selasky 		if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) {
8039844001SHans Petter Selasky 			m->unit = -1;
8139844001SHans Petter Selasky 		} else {
82903873ceSHans Petter Selasky 			/* `name` is "/dev/mixer" so, we'll use the default unit. */
83903873ceSHans Petter Selasky 			if (strncmp(name, BASEPATH, strlen(name)) == 0)
84903873ceSHans Petter Selasky 				goto dunit;
85903873ceSHans Petter Selasky 			m->unit = strtol(name + strlen(BASEPATH), NULL, 10);
8639844001SHans Petter Selasky 		}
87903873ceSHans Petter Selasky 		(void)strlcpy(m->name, name, sizeof(m->name));
88903873ceSHans Petter Selasky 	} else {
89903873ceSHans Petter Selasky dunit:
90903873ceSHans Petter Selasky 		if ((m->unit = mixer_get_dunit()) < 0)
91903873ceSHans Petter Selasky 			goto fail;
92903873ceSHans Petter Selasky 		(void)snprintf(m->name, sizeof(m->name), "/dev/mixer%d", m->unit);
93903873ceSHans Petter Selasky 	}
94903873ceSHans Petter Selasky 
95903873ceSHans Petter Selasky 	if ((m->fd = open(m->name, O_RDWR)) < 0)
96903873ceSHans Petter Selasky 		goto fail;
97903873ceSHans Petter Selasky 
98903873ceSHans Petter Selasky 	m->devmask = m->recmask = m->recsrc = 0;
99903873ceSHans Petter Selasky 	m->f_default = m->unit == mixer_get_dunit();
100903873ceSHans Petter Selasky 	m->mode = mixer_get_mode(m->unit);
101903873ceSHans Petter Selasky 	/* The unit number _must_ be set before the ioctl. */
102903873ceSHans Petter Selasky 	m->mi.dev = m->unit;
103903873ceSHans Petter Selasky 	m->ci.card = m->unit;
10439844001SHans Petter Selasky 	if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0) {
10539844001SHans Petter Selasky 		memset(&m->mi, 0, sizeof(m->mi));
10639844001SHans Petter Selasky 		strlcpy(m->mi.name, m->name, sizeof(m->mi.name));
10739844001SHans Petter Selasky 	}
10839844001SHans Petter Selasky 	if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0)
10939844001SHans Petter Selasky 		memset(&m->ci, 0, sizeof(m->ci));
11039844001SHans Petter Selasky 	if (ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 ||
111903873ceSHans Petter Selasky 	    ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 ||
112903873ceSHans Petter Selasky 	    ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 ||
113903873ceSHans Petter Selasky 	    ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
114903873ceSHans Petter Selasky 		goto fail;
115903873ceSHans Petter Selasky 
116903873ceSHans Petter Selasky 	TAILQ_INIT(&m->devs);
117903873ceSHans Petter Selasky 	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
118903873ceSHans Petter Selasky 		if (!MIX_ISDEV(m, i))
119903873ceSHans Petter Selasky 			continue;
120903873ceSHans Petter Selasky 		if ((dp = calloc(1, sizeof(struct mix_dev))) == NULL)
121903873ceSHans Petter Selasky 			goto fail;
122903873ceSHans Petter Selasky 		dp->parent_mixer = m;
123903873ceSHans Petter Selasky 		dp->devno = i;
124903873ceSHans Petter Selasky 		dp->nctl = 0;
125*e5f5ca7fSChristos Margiolis 		if (_mixer_readvol(dp) < 0)
126903873ceSHans Petter Selasky 			goto fail;
127903873ceSHans Petter Selasky 		(void)strlcpy(dp->name, names[i], sizeof(dp->name));
128903873ceSHans Petter Selasky 		TAILQ_INIT(&dp->ctls);
129903873ceSHans Petter Selasky 		TAILQ_INSERT_TAIL(&m->devs, dp, devs);
130903873ceSHans Petter Selasky 		m->ndev++;
131903873ceSHans Petter Selasky 	}
132903873ceSHans Petter Selasky 
133903873ceSHans Petter Selasky 	/* The default device is always "vol". */
134903873ceSHans Petter Selasky 	m->dev = TAILQ_FIRST(&m->devs);
135903873ceSHans Petter Selasky 
136903873ceSHans Petter Selasky 	return (m);
137903873ceSHans Petter Selasky fail:
138903873ceSHans Petter Selasky 	if (m != NULL)
139903873ceSHans Petter Selasky 		(void)mixer_close(m);
140903873ceSHans Petter Selasky 
141903873ceSHans Petter Selasky 	return (NULL);
142903873ceSHans Petter Selasky }
143903873ceSHans Petter Selasky 
144903873ceSHans Petter Selasky /*
145903873ceSHans Petter Selasky  * Free resources and close the mixer.
146903873ceSHans Petter Selasky  */
147903873ceSHans Petter Selasky int
148903873ceSHans Petter Selasky mixer_close(struct mixer *m)
149903873ceSHans Petter Selasky {
150903873ceSHans Petter Selasky 	struct mix_dev *dp;
151903873ceSHans Petter Selasky 	int r;
152903873ceSHans Petter Selasky 
153903873ceSHans Petter Selasky 	r = close(m->fd);
154903873ceSHans Petter Selasky 	while (!TAILQ_EMPTY(&m->devs)) {
155903873ceSHans Petter Selasky 		dp = TAILQ_FIRST(&m->devs);
156903873ceSHans Petter Selasky 		TAILQ_REMOVE(&m->devs, dp, devs);
157903873ceSHans Petter Selasky 		while (!TAILQ_EMPTY(&dp->ctls))
158903873ceSHans Petter Selasky 			(void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls));
159903873ceSHans Petter Selasky 		free(dp);
160903873ceSHans Petter Selasky 	}
161903873ceSHans Petter Selasky 	free(m);
162903873ceSHans Petter Selasky 
163903873ceSHans Petter Selasky 	return (r);
164903873ceSHans Petter Selasky }
165903873ceSHans Petter Selasky 
166903873ceSHans Petter Selasky /*
167903873ceSHans Petter Selasky  * Select a mixer device. The mixer structure keeps a list of all the devices
168903873ceSHans Petter Selasky  * the mixer has, but only one can be manipulated at a time -- this is what
169903873ceSHans Petter Selasky  * the `dev` in the mixer structure field is for. Each time a device is to be
170903873ceSHans Petter Selasky  * manipulated, `dev` has to point to it first.
171903873ceSHans Petter Selasky  *
172903873ceSHans Petter Selasky  * The caller must manually assign the return value to `m->dev`.
173903873ceSHans Petter Selasky  */
174903873ceSHans Petter Selasky struct mix_dev *
175903873ceSHans Petter Selasky mixer_get_dev(struct mixer *m, int dev)
176903873ceSHans Petter Selasky {
177903873ceSHans Petter Selasky 	struct mix_dev *dp;
178903873ceSHans Petter Selasky 
179903873ceSHans Petter Selasky 	if (dev < 0 || dev >= m->ndev) {
180903873ceSHans Petter Selasky 		errno = ERANGE;
181903873ceSHans Petter Selasky 		return (NULL);
182903873ceSHans Petter Selasky 	}
183903873ceSHans Petter Selasky 	TAILQ_FOREACH(dp, &m->devs, devs) {
184903873ceSHans Petter Selasky 		if (dp->devno == dev)
185903873ceSHans Petter Selasky 			return (dp);
186903873ceSHans Petter Selasky 	}
187903873ceSHans Petter Selasky 	errno = EINVAL;
188903873ceSHans Petter Selasky 
189903873ceSHans Petter Selasky 	return (NULL);
190903873ceSHans Petter Selasky }
191903873ceSHans Petter Selasky 
192903873ceSHans Petter Selasky /*
193903873ceSHans Petter Selasky  * Select a device by name.
194903873ceSHans Petter Selasky  *
195903873ceSHans Petter Selasky  * @param name		device name (e.g vol, pcm, ...)
196903873ceSHans Petter Selasky  */
197903873ceSHans Petter Selasky struct mix_dev *
198903873ceSHans Petter Selasky mixer_get_dev_byname(struct mixer *m, const char *name)
199903873ceSHans Petter Selasky {
200903873ceSHans Petter Selasky 	struct mix_dev *dp;
201903873ceSHans Petter Selasky 
202903873ceSHans Petter Selasky 	TAILQ_FOREACH(dp, &m->devs, devs) {
203903873ceSHans Petter Selasky 		if (!strncmp(dp->name, name, sizeof(dp->name)))
204903873ceSHans Petter Selasky 			return (dp);
205903873ceSHans Petter Selasky 	}
206903873ceSHans Petter Selasky 	errno = EINVAL;
207903873ceSHans Petter Selasky 
208903873ceSHans Petter Selasky 	return (NULL);
209903873ceSHans Petter Selasky }
210903873ceSHans Petter Selasky 
211903873ceSHans Petter Selasky /*
212903873ceSHans Petter Selasky  * Add a mixer control to a device.
213903873ceSHans Petter Selasky  */
214903873ceSHans Petter Selasky int
215903873ceSHans Petter Selasky mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name,
216903873ceSHans Petter Selasky     int (*mod)(struct mix_dev *, void *),
217903873ceSHans Petter Selasky     int (*print)(struct mix_dev *, void *))
218903873ceSHans Petter Selasky {
219903873ceSHans Petter Selasky 	struct mix_dev *dp;
220903873ceSHans Petter Selasky 	mix_ctl_t *ctl, *cp;
221903873ceSHans Petter Selasky 
222903873ceSHans Petter Selasky 	/* XXX: should we accept NULL name? */
223903873ceSHans Petter Selasky 	if (parent_dev == NULL) {
224903873ceSHans Petter Selasky 		errno = EINVAL;
225903873ceSHans Petter Selasky 		return (-1);
226903873ceSHans Petter Selasky 	}
227903873ceSHans Petter Selasky 	if ((ctl = calloc(1, sizeof(mix_ctl_t))) == NULL)
228903873ceSHans Petter Selasky 		return (-1);
229903873ceSHans Petter Selasky 	ctl->parent_dev = parent_dev;
230903873ceSHans Petter Selasky 	ctl->id = id;
231903873ceSHans Petter Selasky 	if (name != NULL)
232903873ceSHans Petter Selasky 		(void)strlcpy(ctl->name, name, sizeof(ctl->name));
233903873ceSHans Petter Selasky 	ctl->mod = mod;
234903873ceSHans Petter Selasky 	ctl->print = print;
235903873ceSHans Petter Selasky 	dp = ctl->parent_dev;
236903873ceSHans Petter Selasky 	/* Make sure the same ID or name doesn't exist already. */
237903873ceSHans Petter Selasky 	TAILQ_FOREACH(cp, &dp->ctls, ctls) {
238903873ceSHans Petter Selasky 		if (!strncmp(cp->name, name, sizeof(cp->name)) || cp->id == id) {
239903873ceSHans Petter Selasky 			errno = EINVAL;
240903873ceSHans Petter Selasky 			return (-1);
241903873ceSHans Petter Selasky 		}
242903873ceSHans Petter Selasky 	}
243903873ceSHans Petter Selasky 	TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls);
244903873ceSHans Petter Selasky 	dp->nctl++;
245903873ceSHans Petter Selasky 
246903873ceSHans Petter Selasky 	return (0);
247903873ceSHans Petter Selasky }
248903873ceSHans Petter Selasky 
249903873ceSHans Petter Selasky /*
250903873ceSHans Petter Selasky  * Same as `mixer_add_ctl`.
251903873ceSHans Petter Selasky  */
252903873ceSHans Petter Selasky int
253903873ceSHans Petter Selasky mixer_add_ctl_s(mix_ctl_t *ctl)
254903873ceSHans Petter Selasky {
255903873ceSHans Petter Selasky 	if (ctl == NULL)
256903873ceSHans Petter Selasky 		return (-1);
257903873ceSHans Petter Selasky 
258903873ceSHans Petter Selasky 	return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name,
259903873ceSHans Petter Selasky 	    ctl->mod, ctl->print));
260903873ceSHans Petter Selasky }
261903873ceSHans Petter Selasky 
262903873ceSHans Petter Selasky /*
263903873ceSHans Petter Selasky  * Remove a mixer control from a device.
264903873ceSHans Petter Selasky  */
265903873ceSHans Petter Selasky int
266903873ceSHans Petter Selasky mixer_remove_ctl(mix_ctl_t *ctl)
267903873ceSHans Petter Selasky {
268903873ceSHans Petter Selasky 	struct mix_dev *p;
269903873ceSHans Petter Selasky 
270903873ceSHans Petter Selasky 	if (ctl == NULL) {
271903873ceSHans Petter Selasky 		errno = EINVAL;
272903873ceSHans Petter Selasky 		return (-1);
273903873ceSHans Petter Selasky 	}
274903873ceSHans Petter Selasky 	p = ctl->parent_dev;
275903873ceSHans Petter Selasky 	if (!TAILQ_EMPTY(&p->ctls)) {
276903873ceSHans Petter Selasky 		TAILQ_REMOVE(&p->ctls, ctl, ctls);
277903873ceSHans Petter Selasky 		free(ctl);
278903873ceSHans Petter Selasky 	}
279903873ceSHans Petter Selasky 
280903873ceSHans Petter Selasky 	return (0);
281903873ceSHans Petter Selasky }
282903873ceSHans Petter Selasky 
283903873ceSHans Petter Selasky /*
284903873ceSHans Petter Selasky  * Get a mixer control by id.
285903873ceSHans Petter Selasky  */
286903873ceSHans Petter Selasky mix_ctl_t *
287903873ceSHans Petter Selasky mixer_get_ctl(struct mix_dev *d, int id)
288903873ceSHans Petter Selasky {
289903873ceSHans Petter Selasky 	mix_ctl_t *cp;
290903873ceSHans Petter Selasky 
291903873ceSHans Petter Selasky 	TAILQ_FOREACH(cp, &d->ctls, ctls) {
292903873ceSHans Petter Selasky 		if (cp->id == id)
293903873ceSHans Petter Selasky 			return (cp);
294903873ceSHans Petter Selasky 	}
295903873ceSHans Petter Selasky 	errno = EINVAL;
296903873ceSHans Petter Selasky 
297903873ceSHans Petter Selasky 	return (NULL);
298903873ceSHans Petter Selasky }
299903873ceSHans Petter Selasky 
300903873ceSHans Petter Selasky /*
301903873ceSHans Petter Selasky  * Get a mixer control by name.
302903873ceSHans Petter Selasky  */
303903873ceSHans Petter Selasky mix_ctl_t *
304903873ceSHans Petter Selasky mixer_get_ctl_byname(struct mix_dev *d, const char *name)
305903873ceSHans Petter Selasky {
306903873ceSHans Petter Selasky 	mix_ctl_t *cp;
307903873ceSHans Petter Selasky 
308903873ceSHans Petter Selasky 	TAILQ_FOREACH(cp, &d->ctls, ctls) {
309903873ceSHans Petter Selasky 		if (!strncmp(cp->name, name, sizeof(cp->name)))
310903873ceSHans Petter Selasky 			return (cp);
311903873ceSHans Petter Selasky 	}
312903873ceSHans Petter Selasky 	errno = EINVAL;
313903873ceSHans Petter Selasky 
314903873ceSHans Petter Selasky 	return (NULL);
315903873ceSHans Petter Selasky }
316903873ceSHans Petter Selasky 
317903873ceSHans Petter Selasky /*
318903873ceSHans Petter Selasky  * Change the mixer's left and right volume. The allowed volume values are
319903873ceSHans Petter Selasky  * between MIX_VOLMIN and MIX_VOLMAX. The `ioctl` for volume change requires
320903873ceSHans Petter Selasky  * an integer value between 0 and 100 stored as `lvol | rvol << 8` --  for
321903873ceSHans Petter Selasky  * that reason, we de-normalize the 32-bit float volume value, before
322903873ceSHans Petter Selasky  * we pass it to the `ioctl`.
323903873ceSHans Petter Selasky  *
324903873ceSHans Petter Selasky  * Volume clumping should be done by the caller.
325903873ceSHans Petter Selasky  */
326903873ceSHans Petter Selasky int
327903873ceSHans Petter Selasky mixer_set_vol(struct mixer *m, mix_volume_t vol)
328903873ceSHans Petter Selasky {
329903873ceSHans Petter Selasky 	int v;
330903873ceSHans Petter Selasky 
331903873ceSHans Petter Selasky 	if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX ||
332903873ceSHans Petter Selasky 	    vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) {
333903873ceSHans Petter Selasky 		errno = ERANGE;
334903873ceSHans Petter Selasky 		return (-1);
335903873ceSHans Petter Selasky 	}
336903873ceSHans Petter Selasky 	v = MIX_VOLDENORM(vol.left) | MIX_VOLDENORM(vol.right) << 8;
337903873ceSHans Petter Selasky 	if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &v) < 0)
338903873ceSHans Petter Selasky 		return (-1);
339*e5f5ca7fSChristos Margiolis 	if (_mixer_readvol(m->dev) < 0)
340903873ceSHans Petter Selasky 		return (-1);
341903873ceSHans Petter Selasky 
342903873ceSHans Petter Selasky 	return (0);
343903873ceSHans Petter Selasky }
344903873ceSHans Petter Selasky 
345903873ceSHans Petter Selasky /*
346903873ceSHans Petter Selasky  * Manipulate a device's mute.
347903873ceSHans Petter Selasky  *
348903873ceSHans Petter Selasky  * @param opt		MIX_MUTE mute device
349903873ceSHans Petter Selasky  *			MIX_UNMUTE unmute device
350903873ceSHans Petter Selasky  *			MIX_TOGGLEMUTE toggle device's mute
351903873ceSHans Petter Selasky  */
352903873ceSHans Petter Selasky int
353903873ceSHans Petter Selasky mixer_set_mute(struct mixer *m, int opt)
354903873ceSHans Petter Selasky {
355903873ceSHans Petter Selasky 	switch (opt) {
356903873ceSHans Petter Selasky 	case MIX_MUTE:
357903873ceSHans Petter Selasky 		m->mutemask |= (1 << m->dev->devno);
358903873ceSHans Petter Selasky 		break;
359903873ceSHans Petter Selasky 	case MIX_UNMUTE:
360903873ceSHans Petter Selasky 		m->mutemask &= ~(1 << m->dev->devno);
361903873ceSHans Petter Selasky 		break;
362903873ceSHans Petter Selasky 	case MIX_TOGGLEMUTE:
363903873ceSHans Petter Selasky 		m->mutemask ^= (1 << m->dev->devno);
364903873ceSHans Petter Selasky 		break;
365903873ceSHans Petter Selasky 	default:
366903873ceSHans Petter Selasky 		errno = EINVAL;
367903873ceSHans Petter Selasky 		return (-1);
368903873ceSHans Petter Selasky 	}
369903873ceSHans Petter Selasky 	if (ioctl(m->fd, SOUND_MIXER_WRITE_MUTE, &m->mutemask) < 0)
370903873ceSHans Petter Selasky 		return (-1);
371903873ceSHans Petter Selasky 	if (ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0)
372903873ceSHans Petter Selasky 		return (-1);
373903873ceSHans Petter Selasky 
374903873ceSHans Petter Selasky 	return 0;
375903873ceSHans Petter Selasky }
376903873ceSHans Petter Selasky 
377903873ceSHans Petter Selasky /*
378903873ceSHans Petter Selasky  * Modify a recording device. The selected device has to be a recording device,
379903873ceSHans Petter Selasky  * otherwise the function will fail.
380903873ceSHans Petter Selasky  *
381903873ceSHans Petter Selasky  * @param opt		MIX_ADDRECSRC add device to recording sources
382903873ceSHans Petter Selasky  *			MIX_REMOVERECSRC remove device from recording sources
383903873ceSHans Petter Selasky  *			MIX_SETRECSRC set device as the only recording source
384903873ceSHans Petter Selasky  *			MIX_TOGGLERECSRC toggle device from recording sources
385903873ceSHans Petter Selasky  */
386903873ceSHans Petter Selasky int
387903873ceSHans Petter Selasky mixer_mod_recsrc(struct mixer *m, int opt)
388903873ceSHans Petter Selasky {
389903873ceSHans Petter Selasky 	if (!m->recmask || !MIX_ISREC(m, m->dev->devno)) {
390903873ceSHans Petter Selasky 		errno = ENODEV;
391903873ceSHans Petter Selasky 		return (-1);
392903873ceSHans Petter Selasky 	}
393903873ceSHans Petter Selasky 	switch (opt) {
394903873ceSHans Petter Selasky 	case MIX_ADDRECSRC:
395903873ceSHans Petter Selasky 		m->recsrc |= (1 << m->dev->devno);
396903873ceSHans Petter Selasky 		break;
397903873ceSHans Petter Selasky 	case MIX_REMOVERECSRC:
398903873ceSHans Petter Selasky 		m->recsrc &= ~(1 << m->dev->devno);
399903873ceSHans Petter Selasky 		break;
400903873ceSHans Petter Selasky 	case MIX_SETRECSRC:
401903873ceSHans Petter Selasky 		m->recsrc = (1 << m->dev->devno);
402903873ceSHans Petter Selasky 		break;
403903873ceSHans Petter Selasky 	case MIX_TOGGLERECSRC:
404903873ceSHans Petter Selasky 		m->recsrc ^= (1 << m->dev->devno);
405903873ceSHans Petter Selasky 		break;
406903873ceSHans Petter Selasky 	default:
407903873ceSHans Petter Selasky 		errno = EINVAL;
408903873ceSHans Petter Selasky 		return (-1);
409903873ceSHans Petter Selasky 	}
410903873ceSHans Petter Selasky 	if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0)
411903873ceSHans Petter Selasky 		return (-1);
412903873ceSHans Petter Selasky 	if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
413903873ceSHans Petter Selasky 		return (-1);
414903873ceSHans Petter Selasky 
415903873ceSHans Petter Selasky 	return (0);
416903873ceSHans Petter Selasky }
417903873ceSHans Petter Selasky 
418903873ceSHans Petter Selasky /*
419903873ceSHans Petter Selasky  * Get default audio card's number. This is used to open the default mixer
420903873ceSHans Petter Selasky  * and set the mixer structure's `f_default` flag.
421903873ceSHans Petter Selasky  */
422903873ceSHans Petter Selasky int
423903873ceSHans Petter Selasky mixer_get_dunit(void)
424903873ceSHans Petter Selasky {
425903873ceSHans Petter Selasky 	size_t size;
426903873ceSHans Petter Selasky 	int unit;
427903873ceSHans Petter Selasky 
428903873ceSHans Petter Selasky 	size = sizeof(int);
429903873ceSHans Petter Selasky 	if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0)
430903873ceSHans Petter Selasky 		return (-1);
431903873ceSHans Petter Selasky 
432903873ceSHans Petter Selasky 	return (unit);
433903873ceSHans Petter Selasky }
434903873ceSHans Petter Selasky 
435903873ceSHans Petter Selasky /*
436903873ceSHans Petter Selasky  * Change the default audio card. This is normally _not_ a mixer feature, but
437903873ceSHans Petter Selasky  * it's useful to have, so the caller can avoid having to manually use
438903873ceSHans Petter Selasky  * the sysctl API.
439903873ceSHans Petter Selasky  *
440903873ceSHans Petter Selasky  * @param unit		the audio card number (e.g pcm0, pcm1, ...).
441903873ceSHans Petter Selasky  */
442903873ceSHans Petter Selasky int
443903873ceSHans Petter Selasky mixer_set_dunit(struct mixer *m, int unit)
444903873ceSHans Petter Selasky {
445903873ceSHans Petter Selasky 	size_t size;
446903873ceSHans Petter Selasky 
447903873ceSHans Petter Selasky 	size = sizeof(int);
448903873ceSHans Petter Selasky 	if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0)
449903873ceSHans Petter Selasky 		return (-1);
450903873ceSHans Petter Selasky 	/* XXX: how will other mixers get updated? */
451903873ceSHans Petter Selasky 	m->f_default = m->unit == unit;
452903873ceSHans Petter Selasky 
453903873ceSHans Petter Selasky 	return (0);
454903873ceSHans Petter Selasky }
455903873ceSHans Petter Selasky 
456903873ceSHans Petter Selasky /*
457903873ceSHans Petter Selasky  * Get sound device mode (none, play, rec, play+rec). Userland programs can
458790b5264SHans Petter Selasky  * use the MIX_MODE_* flags to determine the mode of the device.
459903873ceSHans Petter Selasky  */
460903873ceSHans Petter Selasky int
461903873ceSHans Petter Selasky mixer_get_mode(int unit)
462903873ceSHans Petter Selasky {
463903873ceSHans Petter Selasky 	char buf[64];
464903873ceSHans Petter Selasky 	size_t size;
465903873ceSHans Petter Selasky 	unsigned int mode;
466903873ceSHans Petter Selasky 
467903873ceSHans Petter Selasky 	(void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit);
468903873ceSHans Petter Selasky 	size = sizeof(unsigned int);
469903873ceSHans Petter Selasky 	if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0)
47039844001SHans Petter Selasky 		return (0);
471903873ceSHans Petter Selasky 
472903873ceSHans Petter Selasky 	return (mode);
473903873ceSHans Petter Selasky }
474903873ceSHans Petter Selasky 
475903873ceSHans Petter Selasky /*
476903873ceSHans Petter Selasky  * Get the total number of mixers in the system.
477903873ceSHans Petter Selasky  */
478903873ceSHans Petter Selasky int
479903873ceSHans Petter Selasky mixer_get_nmixers(void)
480903873ceSHans Petter Selasky {
481903873ceSHans Petter Selasky 	struct mixer *m;
482903873ceSHans Petter Selasky 	oss_sysinfo si;
483903873ceSHans Petter Selasky 
484903873ceSHans Petter Selasky 	/*
485903873ceSHans Petter Selasky 	 * Open a dummy mixer because we need the `fd` field for the
486903873ceSHans Petter Selasky 	 * `ioctl` to work.
487903873ceSHans Petter Selasky 	 */
488903873ceSHans Petter Selasky 	if ((m = mixer_open(NULL)) == NULL)
489903873ceSHans Petter Selasky 		return (-1);
490903873ceSHans Petter Selasky 	if (ioctl(m->fd, OSS_SYSINFO, &si) < 0) {
491903873ceSHans Petter Selasky 		(void)mixer_close(m);
492903873ceSHans Petter Selasky 		return (-1);
493903873ceSHans Petter Selasky 	}
494903873ceSHans Petter Selasky 	(void)mixer_close(m);
495903873ceSHans Petter Selasky 
496903873ceSHans Petter Selasky 	return (si.nummixers);
497903873ceSHans Petter Selasky }
498