#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "mixer.h"
#define BASEPATH "/dev/mixer"
static int _mixer_readvol(struct mix_dev *);
static int
_mixer_readvol(struct mix_dev *dev)
{
int v;
if (ioctl(dev->parent_mixer->fd, MIXER_READ(dev->devno), &v) < 0)
return (-1);
dev->vol.left = MIX_VOLNORM(v & 0x00ff);
dev->vol.right = MIX_VOLNORM((v >> 8) & 0x00ff);
return (0);
}
struct mixer *
mixer_open(const char *name)
{
struct mixer *m = NULL;
struct mix_dev *dp;
const char *names[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_NAMES;
int i;
if ((m = calloc(1, sizeof(struct mixer))) == NULL)
goto fail;
if (name != NULL) {
if (strncmp(name, BASEPATH, strlen(BASEPATH)) != 0) {
m->unit = -1;
} else {
if (strncmp(name, BASEPATH, strlen(name)) == 0)
goto dunit;
m->unit = strtol(name + strlen(BASEPATH), NULL, 10);
}
(void)strlcpy(m->name, name, sizeof(m->name));
} else {
dunit:
if ((m->unit = mixer_get_dunit()) < 0)
goto fail;
(void)snprintf(m->name, sizeof(m->name), BASEPATH "%d", m->unit);
}
if ((m->fd = open(m->name, O_RDWR)) < 0)
goto fail;
m->devmask = m->recmask = m->recsrc = 0;
m->f_default = m->unit == mixer_get_dunit();
m->mode = mixer_get_mode(m->unit);
m->mi.dev = m->unit;
m->ci.card = m->unit;
if (ioctl(m->fd, SNDCTL_MIXERINFO, &m->mi) < 0) {
memset(&m->mi, 0, sizeof(m->mi));
strlcpy(m->mi.name, m->name, sizeof(m->mi.name));
}
if (ioctl(m->fd, SNDCTL_CARDINFO, &m->ci) < 0)
memset(&m->ci, 0, sizeof(m->ci));
if (ioctl(m->fd, SOUND_MIXER_READ_DEVMASK, &m->devmask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_RECMASK, &m->recmask) < 0 ||
ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
goto fail;
TAILQ_INIT(&m->devs);
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
if (!MIX_ISDEV(m, i) && !MIX_ISREC(m, i))
continue;
if ((dp = calloc(1, sizeof(struct mix_dev))) == NULL)
goto fail;
dp->parent_mixer = m;
dp->devno = i;
dp->nctl = 0;
if (MIX_ISDEV(m, i) && _mixer_readvol(dp) < 0)
goto fail;
(void)strlcpy(dp->name, names[i], sizeof(dp->name));
TAILQ_INIT(&dp->ctls);
TAILQ_INSERT_TAIL(&m->devs, dp, devs);
m->ndev++;
}
m->dev = TAILQ_FIRST(&m->devs);
return (m);
fail:
if (m != NULL)
(void)mixer_close(m);
return (NULL);
}
int
mixer_close(struct mixer *m)
{
struct mix_dev *dp;
int r;
r = close(m->fd);
while (!TAILQ_EMPTY(&m->devs)) {
dp = TAILQ_FIRST(&m->devs);
TAILQ_REMOVE(&m->devs, dp, devs);
while (!TAILQ_EMPTY(&dp->ctls))
(void)mixer_remove_ctl(TAILQ_FIRST(&dp->ctls));
free(dp);
}
free(m);
return (r);
}
struct mix_dev *
mixer_get_dev(struct mixer *m, int dev)
{
struct mix_dev *dp;
if (dev < 0 || dev >= m->ndev) {
errno = ERANGE;
return (NULL);
}
TAILQ_FOREACH(dp, &m->devs, devs) {
if (dp->devno == dev)
return (dp);
}
errno = EINVAL;
return (NULL);
}
struct mix_dev *
mixer_get_dev_byname(struct mixer *m, const char *name)
{
struct mix_dev *dp;
TAILQ_FOREACH(dp, &m->devs, devs) {
if (!strncmp(dp->name, name, sizeof(dp->name)))
return (dp);
}
errno = EINVAL;
return (NULL);
}
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 *))
{
struct mix_dev *dp;
mix_ctl_t *ctl, *cp;
if (parent_dev == NULL) {
errno = EINVAL;
return (-1);
}
if ((ctl = calloc(1, sizeof(mix_ctl_t))) == NULL)
return (-1);
ctl->parent_dev = parent_dev;
ctl->id = id;
if (name != NULL)
(void)strlcpy(ctl->name, name, sizeof(ctl->name));
ctl->mod = mod;
ctl->print = print;
dp = ctl->parent_dev;
TAILQ_FOREACH(cp, &dp->ctls, ctls) {
if (!strncmp(cp->name, name, sizeof(cp->name)) || cp->id == id) {
errno = EINVAL;
return (-1);
}
}
TAILQ_INSERT_TAIL(&dp->ctls, ctl, ctls);
dp->nctl++;
return (0);
}
int
mixer_add_ctl_s(mix_ctl_t *ctl)
{
if (ctl == NULL)
return (-1);
return (mixer_add_ctl(ctl->parent_dev, ctl->id, ctl->name,
ctl->mod, ctl->print));
}
int
mixer_remove_ctl(mix_ctl_t *ctl)
{
struct mix_dev *p;
if (ctl == NULL) {
errno = EINVAL;
return (-1);
}
p = ctl->parent_dev;
if (!TAILQ_EMPTY(&p->ctls)) {
TAILQ_REMOVE(&p->ctls, ctl, ctls);
free(ctl);
}
return (0);
}
mix_ctl_t *
mixer_get_ctl(struct mix_dev *d, int id)
{
mix_ctl_t *cp;
TAILQ_FOREACH(cp, &d->ctls, ctls) {
if (cp->id == id)
return (cp);
}
errno = EINVAL;
return (NULL);
}
mix_ctl_t *
mixer_get_ctl_byname(struct mix_dev *d, const char *name)
{
mix_ctl_t *cp;
TAILQ_FOREACH(cp, &d->ctls, ctls) {
if (!strncmp(cp->name, name, sizeof(cp->name)))
return (cp);
}
errno = EINVAL;
return (NULL);
}
int
mixer_set_vol(struct mixer *m, mix_volume_t vol)
{
int v;
if (vol.left < MIX_VOLMIN || vol.left > MIX_VOLMAX ||
vol.right < MIX_VOLMIN || vol.right > MIX_VOLMAX) {
errno = ERANGE;
return (-1);
}
v = MIX_VOLDENORM(vol.left) | MIX_VOLDENORM(vol.right) << 8;
if (ioctl(m->fd, MIXER_WRITE(m->dev->devno), &v) < 0)
return (-1);
if (_mixer_readvol(m->dev) < 0)
return (-1);
return (0);
}
int
mixer_set_mute(struct mixer *m, int opt)
{
switch (opt) {
case MIX_MUTE:
m->mutemask |= (1 << m->dev->devno);
break;
case MIX_UNMUTE:
m->mutemask &= ~(1 << m->dev->devno);
break;
case MIX_TOGGLEMUTE:
m->mutemask ^= (1 << m->dev->devno);
break;
default:
errno = EINVAL;
return (-1);
}
if (ioctl(m->fd, SOUND_MIXER_WRITE_MUTE, &m->mutemask) < 0)
return (-1);
if (ioctl(m->fd, SOUND_MIXER_READ_MUTE, &m->mutemask) < 0)
return (-1);
return 0;
}
int
mixer_mod_recsrc(struct mixer *m, int opt)
{
if (!m->recmask || !MIX_ISREC(m, m->dev->devno)) {
errno = ENODEV;
return (-1);
}
switch (opt) {
case MIX_ADDRECSRC:
m->recsrc |= (1 << m->dev->devno);
break;
case MIX_REMOVERECSRC:
m->recsrc &= ~(1 << m->dev->devno);
break;
case MIX_SETRECSRC:
m->recsrc = (1 << m->dev->devno);
break;
case MIX_TOGGLERECSRC:
m->recsrc ^= (1 << m->dev->devno);
break;
default:
errno = EINVAL;
return (-1);
}
if (ioctl(m->fd, SOUND_MIXER_WRITE_RECSRC, &m->recsrc) < 0)
return (-1);
if (ioctl(m->fd, SOUND_MIXER_READ_RECSRC, &m->recsrc) < 0)
return (-1);
return (0);
}
int
mixer_get_dunit(void)
{
size_t size;
int unit;
size = sizeof(int);
if (sysctlbyname("hw.snd.default_unit", &unit, &size, NULL, 0) < 0)
return (-1);
return (unit);
}
int
mixer_set_dunit(struct mixer *m, int unit)
{
size_t size;
size = sizeof(int);
if (sysctlbyname("hw.snd.default_unit", NULL, 0, &unit, size) < 0)
return (-1);
m->f_default = m->unit == unit;
return (0);
}
int
mixer_get_mode(int unit)
{
char buf[64];
size_t size;
unsigned int mode;
(void)snprintf(buf, sizeof(buf), "dev.pcm.%d.mode", unit);
size = sizeof(unsigned int);
if (sysctlbyname(buf, &mode, &size, NULL, 0) < 0)
return (0);
return (mode);
}
int
mixer_get_nmixers(void)
{
struct mixer *m;
oss_sysinfo si;
if ((m = mixer_open(NULL)) == NULL)
return (-1);
if (ioctl(m->fd, OSS_SYSINFO, &si) < 0) {
(void)mixer_close(m);
return (-1);
}
(void)mixer_close(m);
return (si.nummixers);
}
int
mixer_get_path(char *buf, size_t size, int unit)
{
size_t n;
if (!(unit == -1 || (unit >= 0 && unit < mixer_get_nmixers()))) {
errno = EINVAL;
return (-1);
}
if (unit == -1)
n = strlcpy(buf, BASEPATH, size);
else
n = snprintf(buf, size, BASEPATH "%d", unit);
if (n >= size) {
errno = ENOMEM;
return (-1);
}
return (0);
}