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
23903873ceSHans Petter Selasky #include <sys/types.h>
24903873ceSHans Petter Selasky #include <sys/ioctl.h>
25903873ceSHans Petter Selasky #include <sys/sysctl.h>
26903873ceSHans Petter Selasky
27903873ceSHans Petter Selasky #include <errno.h>
28903873ceSHans Petter Selasky #include <fcntl.h>
29903873ceSHans Petter Selasky #include <stdio.h>
30903873ceSHans Petter Selasky #include <stdlib.h>
31903873ceSHans Petter Selasky #include <string.h>
32903873ceSHans Petter Selasky #include <unistd.h>
33903873ceSHans Petter Selasky
34903873ceSHans Petter Selasky #include "mixer.h"
35903873ceSHans Petter Selasky
36903873ceSHans Petter Selasky #define BASEPATH "/dev/mixer"
37903873ceSHans Petter Selasky
38e5f5ca7fSChristos Margiolis static int _mixer_readvol(struct mix_dev *);
39903873ceSHans Petter Selasky
40903873ceSHans Petter Selasky /*
41903873ceSHans Petter Selasky * Fetch volume from the device.
42903873ceSHans Petter Selasky */
43903873ceSHans Petter Selasky static int
_mixer_readvol(struct mix_dev * dev)44e5f5ca7fSChristos Margiolis _mixer_readvol(struct mix_dev *dev)
45903873ceSHans Petter Selasky {
46903873ceSHans Petter Selasky int v;
47903873ceSHans Petter Selasky
48e5f5ca7fSChristos Margiolis if (ioctl(dev->parent_mixer->fd, MIXER_READ(dev->devno), &v) < 0)
49903873ceSHans Petter Selasky return (-1);
50903873ceSHans Petter Selasky dev->vol.left = MIX_VOLNORM(v & 0x00ff);
51903873ceSHans Petter Selasky dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff);
52903873ceSHans Petter Selasky
53903873ceSHans Petter Selasky return (0);
54903873ceSHans Petter Selasky }
55903873ceSHans Petter Selasky
56903873ceSHans Petter Selasky /*
57903873ceSHans Petter Selasky * Open a mixer device in `/dev/mixerN`, where N is the number of the mixer.
58903873ceSHans Petter Selasky * Each device maps to an actual pcm audio card, so `/dev/mixer0` is the
59903873ceSHans Petter Selasky * mixer for pcm0, and so on.
60903873ceSHans Petter Selasky *
61903873ceSHans Petter Selasky * @param name path to mixer device. NULL or "/dev/mixer" for the
62903873ceSHans Petter Selasky * the default mixer (i.e `hw.snd.default_unit`).
63903873ceSHans Petter Selasky */
64903873ceSHans Petter Selasky struct mixer *
mixer_open(const char * name)65903873ceSHans Petter Selasky mixer_open(const char *name)
66903873ceSHans Petter Selasky {
67903873ceSHans Petter Selasky struct mixer *m = NULL;
68903873ceSHans Petter Selasky struct mix_dev *dp;
69903873ceSHans Petter Selasky const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
70903873ceSHans Petter Selasky int i;
71903873ceSHans Petter Selasky
72903873ceSHans Petter Selasky if ((m = calloc(1, sizeof(struct mixer))) == NULL)
73903873ceSHans Petter Selasky goto fail;
74903873ceSHans Petter Selasky
75903873ceSHans Petter Selasky if (name != NULL) {
76903873ceSHans Petter Selasky /* `name` does not start with "/dev/mixer". */
77903873ceSHans Petter Selasky if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) {
7839844001SHans Petter Selasky m->unit = -1;
7939844001SHans Petter Selasky } else {
80903873ceSHans Petter Selasky /* `name` is "/dev/mixer" so, we'll use the default unit. */
81903873ceSHans Petter Selasky if (strncmp(name, BASEPATH, strlen(name)) == 0)
82903873ceSHans Petter Selasky goto dunit;
83903873ceSHans Petter Selasky m->unit = strtol(name + strlen(BASEPATH), NULL, 10);
8439844001SHans Petter Selasky }
85903873ceSHans Petter Selasky (void)strlcpy(m->name, name, sizeof(m->name));
86903873ceSHans Petter Selasky } else {
87903873ceSHans Petter Selasky dunit:
88903873ceSHans Petter Selasky if ((m->unit = mixer_get_dunit()) < 0)
89903873ceSHans Petter Selasky goto fail;
9053c768e6SChristos Margiolis (void)snprintf(m->name, sizeof(m->name), BASEPATH "%d", m->unit);
91903873ceSHans Petter Selasky }
92903873ceSHans Petter Selasky
93903873ceSHans Petter Selasky if ((m->fd = open(m->name, O_RDWR)) < 0)
94903873ceSHans Petter Selasky goto fail;
95903873ceSHans Petter Selasky
96903873ceSHans Petter Selasky m->devmask = m->recmask = m->recsrc = 0;
97903873ceSHans Petter Selasky m->f_default = m->unit == mixer_get_dunit();
98903873ceSHans Petter Selasky m->mode = mixer_get_mode(m->unit);
99903873ceSHans Petter Selasky /* The unit number _must_ be set before the ioctl. */
100903873ceSHans Petter Selasky m->mi.dev = m->unit;
101903873ceSHans Petter Selasky m->ci.card = m->unit;
10239844001SHans Petter Selasky if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0) {
10339844001SHans Petter Selasky memset(&m->mi, 0, sizeof(m->mi));
10439844001SHans Petter Selasky strlcpy(m->mi.name, m->name, sizeof(m->mi.name));
10539844001SHans Petter Selasky }
10639844001SHans Petter Selasky if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0)
10739844001SHans Petter Selasky memset(&m->ci, 0, sizeof(m->ci));
10839844001SHans Petter Selasky if (ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 ||
109903873ceSHans Petter Selasky ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 ||
110903873ceSHans Petter Selasky ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 ||
111903873ceSHans Petter Selasky ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
112903873ceSHans Petter Selasky goto fail;
113903873ceSHans Petter Selasky
114903873ceSHans Petter Selasky TAILQ_INIT(&m->devs);
115903873ceSHans Petter Selasky for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
116903873ceSHans Petter Selasky if (!MIX_ISDEV(m, i))
117903873ceSHans Petter Selasky continue;
118903873ceSHans Petter Selasky if ((dp = calloc(1, sizeof(struct mix_dev))) == NULL)
119903873ceSHans Petter Selasky goto fail;
120903873ceSHans Petter Selasky dp->parent_mixer = m;
121903873ceSHans Petter Selasky dp->devno = i;
122903873ceSHans Petter Selasky dp->nctl = 0;
123e5f5ca7fSChristos Margiolis if (_mixer_readvol(dp) < 0)
124903873ceSHans Petter Selasky goto fail;
125903873ceSHans Petter Selasky (void)strlcpy(dp->name, names[i], sizeof(dp->name));
126903873ceSHans Petter Selasky TAILQ_INIT(&dp->ctls);
127903873ceSHans Petter Selasky TAILQ_INSERT_TAIL(&m->devs, dp, devs);
128903873ceSHans Petter Selasky m->ndev++;
129903873ceSHans Petter Selasky }
130903873ceSHans Petter Selasky
131903873ceSHans Petter Selasky /* The default device is always "vol". */
132903873ceSHans Petter Selasky m->dev = TAILQ_FIRST(&m->devs);
133903873ceSHans Petter Selasky
134903873ceSHans Petter Selasky return (m);
135903873ceSHans Petter Selasky fail:
136903873ceSHans Petter Selasky if (m != NULL)
137903873ceSHans Petter Selasky (void)mixer_close(m);
138903873ceSHans Petter Selasky
139903873ceSHans Petter Selasky return (NULL);
140903873ceSHans Petter Selasky }
141903873ceSHans Petter Selasky
142903873ceSHans Petter Selasky /*
143903873ceSHans Petter Selasky * Free resources and close the mixer.
144903873ceSHans Petter Selasky */
145903873ceSHans Petter Selasky int
mixer_close(struct mixer * m)146903873ceSHans Petter Selasky mixer_close(struct mixer *m)
147903873ceSHans Petter Selasky {
148903873ceSHans Petter Selasky struct mix_dev *dp;
149903873ceSHans Petter Selasky int r;
150903873ceSHans Petter Selasky
151903873ceSHans Petter Selasky r = close(m->fd);
152903873ceSHans Petter Selasky while (!TAILQ_EMPTY(&m->devs)) {
153903873ceSHans Petter Selasky dp = TAILQ_FIRST(&m->devs);
154903873ceSHans Petter Selasky TAILQ_REMOVE(&m->devs, dp, devs);
155903873ceSHans Petter Selasky while (!TAILQ_EMPTY(&dp->ctls))
156903873ceSHans Petter Selasky (void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls));
157903873ceSHans Petter Selasky free(dp);
158903873ceSHans Petter Selasky }
159903873ceSHans Petter Selasky free(m);
160903873ceSHans Petter Selasky
161903873ceSHans Petter Selasky return (r);
162903873ceSHans Petter Selasky }
163903873ceSHans Petter Selasky
164903873ceSHans Petter Selasky /*
165903873ceSHans Petter Selasky * Select a mixer device. The mixer structure keeps a list of all the devices
166903873ceSHans Petter Selasky * the mixer has, but only one can be manipulated at a time -- this is what
167903873ceSHans Petter Selasky * the `dev` in the mixer structure field is for. Each time a device is to be
168903873ceSHans Petter Selasky * manipulated, `dev` has to point to it first.
169903873ceSHans Petter Selasky *
170903873ceSHans Petter Selasky * The caller must manually assign the return value to `m->dev`.
171903873ceSHans Petter Selasky */
172903873ceSHans Petter Selasky struct mix_dev *
mixer_get_dev(struct mixer * m,int dev)173903873ceSHans Petter Selasky mixer_get_dev(struct mixer *m, int dev)
174903873ceSHans Petter Selasky {
175903873ceSHans Petter Selasky struct mix_dev *dp;
176903873ceSHans Petter Selasky
177903873ceSHans Petter Selasky if (dev < 0 || dev >= m->ndev) {
178903873ceSHans Petter Selasky errno = ERANGE;
179903873ceSHans Petter Selasky return (NULL);
180903873ceSHans Petter Selasky }
181903873ceSHans Petter Selasky TAILQ_FOREACH(dp, &m->devs, devs) {
182903873ceSHans Petter Selasky if (dp->devno == dev)
183903873ceSHans Petter Selasky return (dp);
184903873ceSHans Petter Selasky }
185903873ceSHans Petter Selasky errno = EINVAL;
186903873ceSHans Petter Selasky
187903873ceSHans Petter Selasky return (NULL);
188903873ceSHans Petter Selasky }
189903873ceSHans Petter Selasky
190903873ceSHans Petter Selasky /*
191903873ceSHans Petter Selasky * Select a device by name.
192903873ceSHans Petter Selasky *
193903873ceSHans Petter Selasky * @param name device name (e.g vol, pcm, ...)
194903873ceSHans Petter Selasky */
195903873ceSHans Petter Selasky struct mix_dev *
mixer_get_dev_byname(struct mixer * m,const char * name)196903873ceSHans Petter Selasky mixer_get_dev_byname(struct mixer *m, const char *name)
197903873ceSHans Petter Selasky {
198903873ceSHans Petter Selasky struct mix_dev *dp;
199903873ceSHans Petter Selasky
200903873ceSHans Petter Selasky TAILQ_FOREACH(dp, &m->devs, devs) {
201903873ceSHans Petter Selasky if (!strncmp(dp->name, name, sizeof(dp->name)))
202903873ceSHans Petter Selasky return (dp);
203903873ceSHans Petter Selasky }
204903873ceSHans Petter Selasky errno = EINVAL;
205903873ceSHans Petter Selasky
206903873ceSHans Petter Selasky return (NULL);
207903873ceSHans Petter Selasky }
208903873ceSHans Petter Selasky
209903873ceSHans Petter Selasky /*
210903873ceSHans Petter Selasky * Add a mixer control to a device.
211903873ceSHans Petter Selasky */
212903873ceSHans Petter Selasky int
mixer_add_ctl(struct mix_dev * parent_dev,int id,const char * name,int (* mod)(struct mix_dev *,void *),int (* print)(struct mix_dev *,void *))213903873ceSHans Petter Selasky mixer_add_ctl(struct mix_dev *parent_dev, int id, const char *name,
214903873ceSHans Petter Selasky int (*mod)(struct mix_dev *, void *),
215903873ceSHans Petter Selasky int (*print)(struct mix_dev *, void *))
216903873ceSHans Petter Selasky {
217903873ceSHans Petter Selasky struct mix_dev *dp;
218903873ceSHans Petter Selasky mix_ctl_t *ctl, *cp;
219903873ceSHans Petter Selasky
220903873ceSHans Petter Selasky /* XXX: should we accept NULL name? */
221903873ceSHans Petter Selasky if (parent_dev == NULL) {
222903873ceSHans Petter Selasky errno = EINVAL;
223903873ceSHans Petter Selasky return (-1);
224903873ceSHans Petter Selasky }
225903873ceSHans Petter Selasky if ((ctl = calloc(1, sizeof(mix_ctl_t))) == NULL)
226903873ceSHans Petter Selasky return (-1);
227903873ceSHans Petter Selasky ctl->parent_dev = parent_dev;
228903873ceSHans Petter Selasky ctl->id = id;
229903873ceSHans Petter Selasky if (name != NULL)
230903873ceSHans Petter Selasky (void)strlcpy(ctl->name, name, sizeof(ctl->name));
231903873ceSHans Petter Selasky ctl->mod = mod;
232903873ceSHans Petter Selasky ctl->print = print;
233903873ceSHans Petter Selasky dp = ctl->parent_dev;
234903873ceSHans Petter Selasky /* Make sure the same ID or name doesn't exist already. */
235903873ceSHans Petter Selasky TAILQ_FOREACH(cp, &dp->ctls, ctls) {
236903873ceSHans Petter Selasky if (!strncmp(cp->name, name, sizeof(cp->name)) || cp->id == id) {
237903873ceSHans Petter Selasky errno = EINVAL;
238903873ceSHans Petter Selasky return (-1);
239903873ceSHans Petter Selasky }
240903873ceSHans Petter Selasky }
241903873ceSHans Petter Selasky TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls);
242903873ceSHans Petter Selasky dp->nctl++;
243903873ceSHans Petter Selasky
244903873ceSHans Petter Selasky return (0);
245903873ceSHans Petter Selasky }
246903873ceSHans Petter Selasky
247903873ceSHans Petter Selasky /*
248903873ceSHans Petter Selasky * Same as `mixer_add_ctl`.
249903873ceSHans Petter Selasky */
250903873ceSHans Petter Selasky int
mixer_add_ctl_s(mix_ctl_t * ctl)251903873ceSHans Petter Selasky mixer_add_ctl_s(mix_ctl_t *ctl)
252903873ceSHans Petter Selasky {
253903873ceSHans Petter Selasky if (ctl == NULL)
254903873ceSHans Petter Selasky return (-1);
255903873ceSHans Petter Selasky
256903873ceSHans Petter Selasky return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name,
257903873ceSHans Petter Selasky ctl->mod, ctl->print));
258903873ceSHans Petter Selasky }
259903873ceSHans Petter Selasky
260903873ceSHans Petter Selasky /*
261903873ceSHans Petter Selasky * Remove a mixer control from a device.
262903873ceSHans Petter Selasky */
263903873ceSHans Petter Selasky int
mixer_remove_ctl(mix_ctl_t * ctl)264903873ceSHans Petter Selasky mixer_remove_ctl(mix_ctl_t *ctl)
265903873ceSHans Petter Selasky {
266903873ceSHans Petter Selasky struct mix_dev *p;
267903873ceSHans Petter Selasky
268903873ceSHans Petter Selasky if (ctl == NULL) {
269903873ceSHans Petter Selasky errno = EINVAL;
270903873ceSHans Petter Selasky return (-1);
271903873ceSHans Petter Selasky }
272903873ceSHans Petter Selasky p = ctl->parent_dev;
273903873ceSHans Petter Selasky if (!TAILQ_EMPTY(&p->ctls)) {
274903873ceSHans Petter Selasky TAILQ_REMOVE(&p->ctls, ctl, ctls);
275903873ceSHans Petter Selasky free(ctl);
276903873ceSHans Petter Selasky }
277903873ceSHans Petter Selasky
278903873ceSHans Petter Selasky return (0);
279903873ceSHans Petter Selasky }
280903873ceSHans Petter Selasky
281903873ceSHans Petter Selasky /*
282903873ceSHans Petter Selasky * Get a mixer control by id.
283903873ceSHans Petter Selasky */
284903873ceSHans Petter Selasky mix_ctl_t *
mixer_get_ctl(struct mix_dev * d,int id)285903873ceSHans Petter Selasky mixer_get_ctl(struct mix_dev *d, int id)
286903873ceSHans Petter Selasky {
287903873ceSHans Petter Selasky mix_ctl_t *cp;
288903873ceSHans Petter Selasky
289903873ceSHans Petter Selasky TAILQ_FOREACH(cp, &d->ctls, ctls) {
290903873ceSHans Petter Selasky if (cp->id == id)
291903873ceSHans Petter Selasky return (cp);
292903873ceSHans Petter Selasky }
293903873ceSHans Petter Selasky errno = EINVAL;
294903873ceSHans Petter Selasky
295903873ceSHans Petter Selasky return (NULL);
296903873ceSHans Petter Selasky }
297903873ceSHans Petter Selasky
298903873ceSHans Petter Selasky /*
299903873ceSHans Petter Selasky * Get a mixer control by name.
300903873ceSHans Petter Selasky */
301903873ceSHans Petter Selasky mix_ctl_t *
mixer_get_ctl_byname(struct mix_dev * d,const char * name)302903873ceSHans Petter Selasky mixer_get_ctl_byname(struct mix_dev *d, const char *name)
303903873ceSHans Petter Selasky {
304903873ceSHans Petter Selasky mix_ctl_t *cp;
305903873ceSHans Petter Selasky
306903873ceSHans Petter Selasky TAILQ_FOREACH(cp, &d->ctls, ctls) {
307903873ceSHans Petter Selasky if (!strncmp(cp->name, name, sizeof(cp->name)))
308903873ceSHans Petter Selasky return (cp);
309903873ceSHans Petter Selasky }
310903873ceSHans Petter Selasky errno = EINVAL;
311903873ceSHans Petter Selasky
312903873ceSHans Petter Selasky return (NULL);
313903873ceSHans Petter Selasky }
314903873ceSHans Petter Selasky
315903873ceSHans Petter Selasky /*
316903873ceSHans Petter Selasky * Change the mixer's left and right volume. The allowed volume values are
317903873ceSHans Petter Selasky * between MIX_VOLMIN and MIX_VOLMAX. The `ioctl` for volume change requires
318903873ceSHans Petter Selasky * an integer value between 0 and 100 stored as `lvol | rvol << 8` -- for
319903873ceSHans Petter Selasky * that reason, we de-normalize the 32-bit float volume value, before
320903873ceSHans Petter Selasky * we pass it to the `ioctl`.
321903873ceSHans Petter Selasky *
322903873ceSHans Petter Selasky * Volume clumping should be done by the caller.
323903873ceSHans Petter Selasky */
324903873ceSHans Petter Selasky int
mixer_set_vol(struct mixer * m,mix_volume_t vol)325903873ceSHans Petter Selasky mixer_set_vol(struct mixer *m, mix_volume_t vol)
326903873ceSHans Petter Selasky {
327903873ceSHans Petter Selasky int v;
328903873ceSHans Petter Selasky
329903873ceSHans Petter Selasky if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX ||
330903873ceSHans Petter Selasky vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) {
331903873ceSHans Petter Selasky errno = ERANGE;
332903873ceSHans Petter Selasky return (-1);
333903873ceSHans Petter Selasky }
334903873ceSHans Petter Selasky v = MIX_VOLDENORM(vol.left) | MIX_VOLDENORM(vol.right) << 8;
335903873ceSHans Petter Selasky if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &v) < 0)
336903873ceSHans Petter Selasky return (-1);
337e5f5ca7fSChristos Margiolis if (_mixer_readvol(m->dev) < 0)
338903873ceSHans Petter Selasky return (-1);
339903873ceSHans Petter Selasky
340903873ceSHans Petter Selasky return (0);
341903873ceSHans Petter Selasky }
342903873ceSHans Petter Selasky
343903873ceSHans Petter Selasky /*
344903873ceSHans Petter Selasky * Manipulate a device's mute.
345903873ceSHans Petter Selasky *
346903873ceSHans Petter Selasky * @param opt MIX_MUTE mute device
347903873ceSHans Petter Selasky * MIX_UNMUTE unmute device
348903873ceSHans Petter Selasky * MIX_TOGGLEMUTE toggle device's mute
349903873ceSHans Petter Selasky */
350903873ceSHans Petter Selasky int
mixer_set_mute(struct mixer * m,int opt)351903873ceSHans Petter Selasky mixer_set_mute(struct mixer *m, int opt)
352903873ceSHans Petter Selasky {
353903873ceSHans Petter Selasky switch (opt) {
354903873ceSHans Petter Selasky case MIX_MUTE:
355903873ceSHans Petter Selasky m->mutemask |= (1 << m->dev->devno);
356903873ceSHans Petter Selasky break;
357903873ceSHans Petter Selasky case MIX_UNMUTE:
358903873ceSHans Petter Selasky m->mutemask &= ~(1 << m->dev->devno);
359903873ceSHans Petter Selasky break;
360903873ceSHans Petter Selasky case MIX_TOGGLEMUTE:
361903873ceSHans Petter Selasky m->mutemask ^= (1 << m->dev->devno);
362903873ceSHans Petter Selasky break;
363903873ceSHans Petter Selasky default:
364903873ceSHans Petter Selasky errno = EINVAL;
365903873ceSHans Petter Selasky return (-1);
366903873ceSHans Petter Selasky }
367903873ceSHans Petter Selasky if (ioctl(m->fd, SOUND_MIXER_WRITE_MUTE, &m->mutemask) < 0)
368903873ceSHans Petter Selasky return (-1);
369903873ceSHans Petter Selasky if (ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0)
370903873ceSHans Petter Selasky return (-1);
371903873ceSHans Petter Selasky
372903873ceSHans Petter Selasky return 0;
373903873ceSHans Petter Selasky }
374903873ceSHans Petter Selasky
375903873ceSHans Petter Selasky /*
376903873ceSHans Petter Selasky * Modify a recording device. The selected device has to be a recording device,
377903873ceSHans Petter Selasky * otherwise the function will fail.
378903873ceSHans Petter Selasky *
379903873ceSHans Petter Selasky * @param opt MIX_ADDRECSRC add device to recording sources
380903873ceSHans Petter Selasky * MIX_REMOVERECSRC remove device from recording sources
381903873ceSHans Petter Selasky * MIX_SETRECSRC set device as the only recording source
382903873ceSHans Petter Selasky * MIX_TOGGLERECSRC toggle device from recording sources
383903873ceSHans Petter Selasky */
384903873ceSHans Petter Selasky int
mixer_mod_recsrc(struct mixer * m,int opt)385903873ceSHans Petter Selasky mixer_mod_recsrc(struct mixer *m, int opt)
386903873ceSHans Petter Selasky {
387903873ceSHans Petter Selasky if (!m->recmask || !MIX_ISREC(m, m->dev->devno)) {
388903873ceSHans Petter Selasky errno = ENODEV;
389903873ceSHans Petter Selasky return (-1);
390903873ceSHans Petter Selasky }
391903873ceSHans Petter Selasky switch (opt) {
392903873ceSHans Petter Selasky case MIX_ADDRECSRC:
393903873ceSHans Petter Selasky m->recsrc |= (1 << m->dev->devno);
394903873ceSHans Petter Selasky break;
395903873ceSHans Petter Selasky case MIX_REMOVERECSRC:
396903873ceSHans Petter Selasky m->recsrc &= ~(1 << m->dev->devno);
397903873ceSHans Petter Selasky break;
398903873ceSHans Petter Selasky case MIX_SETRECSRC:
399903873ceSHans Petter Selasky m->recsrc = (1 << m->dev->devno);
400903873ceSHans Petter Selasky break;
401903873ceSHans Petter Selasky case MIX_TOGGLERECSRC:
402903873ceSHans Petter Selasky m->recsrc ^= (1 << m->dev->devno);
403903873ceSHans Petter Selasky break;
404903873ceSHans Petter Selasky default:
405903873ceSHans Petter Selasky errno = EINVAL;
406903873ceSHans Petter Selasky return (-1);
407903873ceSHans Petter Selasky }
408903873ceSHans Petter Selasky if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0)
409903873ceSHans Petter Selasky return (-1);
410903873ceSHans Petter Selasky if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
411903873ceSHans Petter Selasky return (-1);
412903873ceSHans Petter Selasky
413903873ceSHans Petter Selasky return (0);
414903873ceSHans Petter Selasky }
415903873ceSHans Petter Selasky
416903873ceSHans Petter Selasky /*
417903873ceSHans Petter Selasky * Get default audio card's number. This is used to open the default mixer
418903873ceSHans Petter Selasky * and set the mixer structure's `f_default` flag.
419903873ceSHans Petter Selasky */
420903873ceSHans Petter Selasky int
mixer_get_dunit(void)421903873ceSHans Petter Selasky mixer_get_dunit(void)
422903873ceSHans Petter Selasky {
423903873ceSHans Petter Selasky size_t size;
424903873ceSHans Petter Selasky int unit;
425903873ceSHans Petter Selasky
426903873ceSHans Petter Selasky size = sizeof(int);
427903873ceSHans Petter Selasky if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0)
428903873ceSHans Petter Selasky return (-1);
429903873ceSHans Petter Selasky
430903873ceSHans Petter Selasky return (unit);
431903873ceSHans Petter Selasky }
432903873ceSHans Petter Selasky
433903873ceSHans Petter Selasky /*
434903873ceSHans Petter Selasky * Change the default audio card. This is normally _not_ a mixer feature, but
435903873ceSHans Petter Selasky * it's useful to have, so the caller can avoid having to manually use
436903873ceSHans Petter Selasky * the sysctl API.
437903873ceSHans Petter Selasky *
438903873ceSHans Petter Selasky * @param unit the audio card number (e.g pcm0, pcm1, ...).
439903873ceSHans Petter Selasky */
440903873ceSHans Petter Selasky int
mixer_set_dunit(struct mixer * m,int unit)441903873ceSHans Petter Selasky mixer_set_dunit(struct mixer *m, int unit)
442903873ceSHans Petter Selasky {
443903873ceSHans Petter Selasky size_t size;
444903873ceSHans Petter Selasky
445903873ceSHans Petter Selasky size = sizeof(int);
446903873ceSHans Petter Selasky if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0)
447903873ceSHans Petter Selasky return (-1);
448903873ceSHans Petter Selasky /* XXX: how will other mixers get updated? */
449903873ceSHans Petter Selasky m->f_default = m->unit == unit;
450903873ceSHans Petter Selasky
451903873ceSHans Petter Selasky return (0);
452903873ceSHans Petter Selasky }
453903873ceSHans Petter Selasky
454903873ceSHans Petter Selasky /*
455903873ceSHans Petter Selasky * Get sound device mode (none, play, rec, play+rec). Userland programs can
456790b5264SHans Petter Selasky * use the MIX_MODE_* flags to determine the mode of the device.
457903873ceSHans Petter Selasky */
458903873ceSHans Petter Selasky int
mixer_get_mode(int unit)459903873ceSHans Petter Selasky mixer_get_mode(int unit)
460903873ceSHans Petter Selasky {
461903873ceSHans Petter Selasky char buf[64];
462903873ceSHans Petter Selasky size_t size;
463903873ceSHans Petter Selasky unsigned int mode;
464903873ceSHans Petter Selasky
465903873ceSHans Petter Selasky (void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit);
466903873ceSHans Petter Selasky size = sizeof(unsigned int);
467903873ceSHans Petter Selasky if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0)
46839844001SHans Petter Selasky return (0);
469903873ceSHans Petter Selasky
470903873ceSHans Petter Selasky return (mode);
471903873ceSHans Petter Selasky }
472903873ceSHans Petter Selasky
473903873ceSHans Petter Selasky /*
474903873ceSHans Petter Selasky * Get the total number of mixers in the system.
475903873ceSHans Petter Selasky */
476903873ceSHans Petter Selasky int
mixer_get_nmixers(void)477903873ceSHans Petter Selasky mixer_get_nmixers(void)
478903873ceSHans Petter Selasky {
479903873ceSHans Petter Selasky struct mixer *m;
480903873ceSHans Petter Selasky oss_sysinfo si;
481903873ceSHans Petter Selasky
482903873ceSHans Petter Selasky /*
483903873ceSHans Petter Selasky * Open a dummy mixer because we need the `fd` field for the
484903873ceSHans Petter Selasky * `ioctl` to work.
485903873ceSHans Petter Selasky */
486903873ceSHans Petter Selasky if ((m = mixer_open(NULL)) == NULL)
487903873ceSHans Petter Selasky return (-1);
488903873ceSHans Petter Selasky if (ioctl(m->fd, OSS_SYSINFO, &si) < 0) {
489903873ceSHans Petter Selasky (void)mixer_close(m);
490903873ceSHans Petter Selasky return (-1);
491903873ceSHans Petter Selasky }
492903873ceSHans Petter Selasky (void)mixer_close(m);
493903873ceSHans Petter Selasky
494903873ceSHans Petter Selasky return (si.nummixers);
495903873ceSHans Petter Selasky }
496*67c89b21SChristos Margiolis
497*67c89b21SChristos Margiolis /*
498*67c89b21SChristos Margiolis * Get the full path to a mixer device.
499*67c89b21SChristos Margiolis */
500*67c89b21SChristos Margiolis int
mixer_get_path(char * buf,size_t size,int unit)501*67c89b21SChristos Margiolis mixer_get_path(char *buf, size_t size, int unit)
502*67c89b21SChristos Margiolis {
503*67c89b21SChristos Margiolis size_t n;
504*67c89b21SChristos Margiolis
505*67c89b21SChristos Margiolis if (!(unit == -1 || (unit >= 0 && unit < mixer_get_nmixers()))) {
506*67c89b21SChristos Margiolis errno = EINVAL;
507*67c89b21SChristos Margiolis return (-1);
508*67c89b21SChristos Margiolis }
509*67c89b21SChristos Margiolis if (unit == -1)
510*67c89b21SChristos Margiolis n = strlcpy(buf, BASEPATH, size);
511*67c89b21SChristos Margiolis else
512*67c89b21SChristos Margiolis n = snprintf(buf, size, BASEPATH "%d", unit);
513*67c89b21SChristos Margiolis
514*67c89b21SChristos Margiolis if (n >= size) {
515*67c89b21SChristos Margiolis errno = ENOMEM;
516*67c89b21SChristos Margiolis return (-1);
517*67c89b21SChristos Margiolis }
518*67c89b21SChristos Margiolis
519*67c89b21SChristos Margiolis return (0);
520*67c89b21SChristos Margiolis }
521