#include <sys/nv.h>
#include <sys/queue.h>
#include <sys/sbuf.h>
#include <sys/sndstat.h>
#include <sys/soundcard.h>
#include <sys/sysctl.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <mixer.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libxo/xo.h>
#define SNDCTL_XO_VERSION "1"
#define STATUS_LEN 64
#define FMTSTR_LEN 16
struct snd_chan {
char name[NAME_MAX];
char parentchan[NAME_MAX];
int unit;
#define INPUT 0
#define OUTPUT 1
int direction;
char caps[BUFSIZ];
int latency;
int rate;
char format[FMTSTR_LEN];
int pid;
char proc[NAME_MAX];
int interrupts;
int xruns;
int feedcount;
int volume;
struct {
char format[FMTSTR_LEN];
int rate;
int size_bytes;
int size_frames;
int blksz;
int blkcnt;
int free;
int ready;
} hwbuf, swbuf;
char feederchain[BUFSIZ];
struct snd_dev *dev;
TAILQ_ENTRY(snd_chan) next;
};
struct snd_dev {
char name[NAME_MAX];
char desc[NAME_MAX];
char status[BUFSIZ];
char devnode[NAME_MAX];
int from_user;
int unit;
char caps[BUFSIZ];
int bitperfect;
int realtime;
int autoconv;
struct {
char format[FMTSTR_LEN];
int rate;
int pchans;
int vchans;
int min_rate;
int max_rate;
int min_chans;
int max_chans;
char formats[BUFSIZ];
} play, rec;
TAILQ_HEAD(, snd_chan) chans;
};
struct snd_ctl {
const char *name;
size_t off;
#define STR 0
#define NUM 1
#define VOL 2
#define GRP 3
int type;
int (*mod)(struct snd_dev *, void *);
};
struct map {
int val;
const char *str;
};
static int mod_bitperfect(struct snd_dev *, void *);
static int mod_autoconv(struct snd_dev *, void *);
static int mod_realtime(struct snd_dev *, void *);
static int mod_play_vchans(struct snd_dev *, void *);
static int mod_play_rate(struct snd_dev *, void *);
static int mod_play_format(struct snd_dev *, void *);
static int mod_rec_vchans(struct snd_dev *, void *);
static int mod_rec_rate(struct snd_dev *, void *);
static int mod_rec_format(struct snd_dev *, void *);
static struct snd_ctl dev_ctls[] = {
#define F(member) offsetof(struct snd_dev, member)
{ "name", F(name), STR, NULL },
{ "desc", F(desc), STR, NULL },
{ "status", F(status), STR, NULL },
{ "devnode", F(devnode), STR, NULL },
{ "from_user", F(from_user), NUM, NULL },
{ "unit", F(unit), NUM, NULL },
{ "caps", F(caps), STR, NULL },
{ "bitperfect", F(bitperfect), NUM, mod_bitperfect },
{ "autoconv", F(autoconv), NUM, mod_autoconv },
{ "realtime", F(realtime), NUM, mod_realtime },
{ "play", F(play), GRP, NULL },
{ "play.format", F(play.format), STR, mod_play_format },
{ "play.rate", F(play.rate), NUM, mod_play_rate },
{ "play.vchans", F(play.vchans), NUM, mod_play_vchans },
{ "play.min_rate", F(play.min_rate), NUM, NULL },
{ "play.max_rate", F(play.max_rate), NUM, NULL },
{ "play.min_chans", F(play.min_chans), NUM, NULL },
{ "play.max_chans", F(play.max_chans), NUM, NULL },
{ "play.formats", F(play.formats), STR, NULL },
{ "rec", F(rec), GRP, NULL },
{ "rec.rate", F(rec.rate), NUM, mod_rec_rate },
{ "rec.format", F(rec.format), STR, mod_rec_format },
{ "rec.vchans", F(rec.vchans), NUM, mod_rec_vchans },
{ "rec.min_rate", F(rec.min_rate), NUM, NULL },
{ "rec.max_rate", F(rec.max_rate), NUM, NULL },
{ "rec.min_chans", F(rec.min_chans), NUM, NULL },
{ "rec.max_chans", F(rec.max_chans), NUM, NULL },
{ "rec.formats", F(rec.formats), STR, NULL },
{ NULL, 0, 0, NULL }
#undef F
};
static struct snd_ctl chan_ctls[] = {
#define F(member) offsetof(struct snd_chan, member)
{ "name", F(name), STR, NULL },
{ "parentchan", F(parentchan), STR, NULL },
{ "unit", F(unit), NUM, NULL },
{ "caps", F(caps), STR, NULL },
{ "latency", F(latency), NUM, NULL },
{ "rate", F(rate), NUM, NULL },
{ "format", F(format), STR, NULL },
{ "pid", F(pid), NUM, NULL },
{ "proc", F(proc), STR, NULL },
{ "interrupts", F(interrupts), NUM, NULL },
{ "xruns", F(xruns), NUM, NULL },
{ "feedcount", F(feedcount), NUM, NULL },
{ "volume", F(volume), VOL, NULL },
{ "hwbuf", F(hwbuf), GRP, NULL },
{ "hwbuf.format", F(hwbuf.format), STR, NULL },
{ "hwbuf.rate", F(hwbuf.rate), NUM, NULL },
{ "hwbuf.size_bytes", F(hwbuf.size_bytes), NUM, NULL },
{ "hwbuf.size_frames", F(hwbuf.size_frames), NUM, NULL },
{ "hwbuf.blksz", F(hwbuf.blksz), NUM, NULL },
{ "hwbuf.blkcnt", F(hwbuf.blkcnt), NUM, NULL },
{ "hwbuf.free", F(hwbuf.free), NUM, NULL },
{ "hwbuf.ready", F(hwbuf.ready), NUM, NULL },
{ "swbuf", F(swbuf), GRP, NULL },
{ "swbuf.format", F(swbuf.format), STR, NULL },
{ "swbuf.rate", F(swbuf.rate), NUM, NULL },
{ "swbuf.size_bytes", F(swbuf.size_bytes), NUM, NULL },
{ "swbuf.size_frames", F(swbuf.size_frames), NUM, NULL },
{ "swbuf.blksz", F(swbuf.blksz), NUM, NULL },
{ "swbuf.blkcnt", F(swbuf.blkcnt), NUM, NULL },
{ "swbuf.free", F(swbuf.free), NUM, NULL },
{ "swbuf.ready", F(swbuf.ready), NUM, NULL },
{ "feederchain", F(feederchain), STR, NULL },
{ NULL, 0, 0, NULL }
#undef F
};
static struct map capmap[] = {
{ PCM_CAP_ANALOGIN, "ANALOGIN" },
{ PCM_CAP_ANALOGOUT, "ANALOGOUT" },
{ PCM_CAP_BATCH, "BATCH" },
{ PCM_CAP_BIND, "BIND" },
{ PCM_CAP_COPROC, "COPROC" },
{ PCM_CAP_DEFAULT, "DEFAULT" },
{ PCM_CAP_DIGITALIN, "DIGITALIN" },
{ PCM_CAP_DIGITALOUT, "DIGITALOUT" },
{ PCM_CAP_DUPLEX, "DUPLEX" },
{ PCM_CAP_FREERATE, "FREERATE" },
{ PCM_CAP_HIDDEN, "HIDDEN" },
{ PCM_CAP_INPUT, "INPUT" },
{ PCM_CAP_MMAP, "MMAP" },
{ PCM_CAP_MODEM, "MODEM" },
{ PCM_CAP_MULTI, "MULTI" },
{ PCM_CAP_OUTPUT, "OUTPUT" },
{ PCM_CAP_REALTIME, "REALTIME" },
{ PCM_CAP_REVISION, "REVISION" },
{ PCM_CAP_SHADOW, "SHADOW" },
{ PCM_CAP_SPECIAL, "SPECIAL" },
{ PCM_CAP_TRIGGER, "TRIGGER" },
{ PCM_CAP_VIRTUAL, "VIRTUAL" },
{ 0, NULL }
};
static struct map fmtmap[] = {
{ AFMT_A_LAW, "alaw" },
{ AFMT_MU_LAW, "mulaw" },
{ AFMT_S8, "s8" },
{ AFMT_U8, "u8" },
{ AFMT_AC3, "ac3" },
{ AFMT_S16_LE, "s16le" },
{ AFMT_S16_BE, "s16be" },
{ AFMT_U16_LE, "u16le" },
{ AFMT_U16_BE, "u16be" },
{ AFMT_S24_LE, "s24le" },
{ AFMT_S24_BE, "s24be" },
{ AFMT_U24_LE, "u24le" },
{ AFMT_U24_BE, "u24be" },
{ AFMT_S32_LE, "s32le" },
{ AFMT_S32_BE, "s32be" },
{ AFMT_U32_LE, "u32le" },
{ AFMT_U32_BE, "u32be" },
{ AFMT_F32_LE, "f32le" },
{ AFMT_F32_BE, "f32be" },
{ 0, NULL }
};
static bool oflag = false;
static bool vflag = false;
static void
cap2str(char *buf, size_t size, int caps)
{
struct map *p;
for (p = capmap; p->str != NULL; p++) {
if ((p->val & caps) == 0)
continue;
strlcat(buf, p->str, size);
strlcat(buf, ",", size);
}
if (*buf == '\0')
strlcpy(buf, "UNKNOWN", size);
else
buf[strlen(buf) - 1] = '\0';
}
static void
fmt2str(char *buf, size_t size, int fmt)
{
struct map *p;
int enc, ch, ext;
enc = fmt & 0xf00fffff;
ch = (fmt & 0x07f00000) >> 20;
ext = (fmt & 0x08000000) >> 27;
for (p = fmtmap; p->str != NULL; p++) {
if ((p->val & enc) == 0)
continue;
strlcat(buf, p->str, size);
if (ch) {
snprintf(buf + strlen(buf), size,
":%d.%d", ch - ext, ext);
}
strlcat(buf, ",", size);
}
if (*buf == '\0')
strlcpy(buf, "UNKNOWN", size);
else
buf[strlen(buf) - 1] = '\0';
}
static int
bytes2frames(int bytes, int fmt)
{
int enc, ch, samplesz;
enc = fmt & 0xf00fffff;
ch = (fmt & 0x07f00000) >> 20;
ch += (fmt & 0x08000000) >> 27;
if (enc & (AFMT_S8 | AFMT_U8 | AFMT_MU_LAW | AFMT_A_LAW))
samplesz = 1;
else if (enc & (AFMT_S16_NE | AFMT_U16_NE))
samplesz = 2;
else if (enc & (AFMT_S24_NE | AFMT_U24_NE))
samplesz = 3;
else if (enc & (AFMT_S32_NE | AFMT_U32_NE | AFMT_F32_NE))
samplesz = 4;
else
samplesz = 0;
if (!samplesz || !ch)
return (-1);
return (bytes / (samplesz * ch));
}
static int
sysctl_int(const char *buf, const char *arg, int *var)
{
size_t size;
int n, prev;
size = sizeof(int);
if (sysctlbyname(buf, &prev, &size, NULL, 0) < 0) {
xo_warn("sysctlbyname(%s)", buf);
return (-1);
}
if (arg != NULL) {
errno = 0;
n = strtol(arg, NULL, 10);
if (errno == EINVAL || errno == ERANGE) {
xo_warn("strtol(%s)", arg);
return (-1);
}
if (sysctlbyname(buf, NULL, 0, &n, size) < 0) {
xo_warn("sysctlbyname(%s, %d)", buf, n);
return (-1);
}
}
if (sysctlbyname(buf, &n, &size, NULL, 0) < 0) {
xo_warn("sysctlbyname(%s)", buf);
return (-1);
}
if (arg != NULL && xo_get_style(NULL) == XO_STYLE_TEXT)
printf("%s: %d -> %d\n", buf, prev, n);
if (var != NULL)
*var = n;
return (0);
}
static int
sysctl_str(const char *buf, const char *arg, char *var, size_t varsz)
{
size_t size;
char prev[BUFSIZ];
char *tmp;
size = sizeof(prev);
if (sysctlbyname(buf, prev, &size, NULL, 0) < 0) {
xo_warn("sysctlbyname(%s)", buf);
return (-1);
}
if (arg != NULL) {
size = strlen(arg);
if (sysctlbyname(buf, NULL, 0, arg, size) < 0) {
xo_warn("sysctlbyname(%s, %s)", buf, arg);
return (-1);
}
if (sysctlbyname(buf, NULL, &size, NULL, 0) < 0) {
xo_warn("sysctlbyname(%s)", buf);
return (-1);
}
}
if ((tmp = calloc(1, size)) == NULL)
xo_err(1, "calloc");
if (sysctlbyname(buf, tmp, &size, NULL, 0) < 0) {
xo_warn("sysctlbyname(%s)", buf);
free(tmp);
return (-1);
}
if (arg != NULL && xo_get_style(NULL) == XO_STYLE_TEXT)
printf("%s: %s -> %s\n", buf, prev, tmp);
if (var != NULL)
strlcpy(var, tmp, varsz);
free(tmp);
return (0);
}
static struct snd_dev *
read_dev(char *path)
{
nvlist_t *nvl;
const nvlist_t * const *di;
const nvlist_t * const *cdi;
struct sndstioc_nv_arg arg;
struct snd_dev *dp = NULL;
struct snd_chan *ch;
size_t nitems, nchans, i, j;
int fd, caps, unit, t1, t2, t3;
if ((fd = open("/dev/sndstat", O_RDONLY)) < 0)
xo_err(1, "open(/dev/sndstat)");
if (ioctl(fd, SNDSTIOC_REFRESH_DEVS, NULL) < 0)
xo_err(1, "ioctl(SNDSTIOC_REFRESH_DEVS)");
arg.nbytes = 0;
arg.buf = NULL;
if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0)
xo_err(1, "ioctl(SNDSTIOC_GET_DEVS#1)");
if ((arg.buf = malloc(arg.nbytes)) == NULL)
xo_err(1, "malloc");
if (ioctl(fd, SNDSTIOC_GET_DEVS, &arg) < 0)
xo_err(1, "ioctl(SNDSTIOC_GET_DEVS#2)");
if ((nvl = nvlist_unpack(arg.buf, arg.nbytes, 0)) == NULL)
xo_err(1, "nvlist_unpack");
if (nvlist_empty(nvl) || !nvlist_exists(nvl, SNDST_DSPS))
xo_errx(1, "no soundcards attached");
if (path == NULL || (path != NULL && strcmp(basename(path), "dsp") == 0))
unit = mixer_get_dunit();
else
unit = -1;
di = nvlist_get_nvlist_array(nvl, SNDST_DSPS, &nitems);
for (i = 0; i < nitems; i++) {
if (unit == -1 && strcmp(basename(path),
nvlist_get_string(di[i], SNDST_DSPS_DEVNODE)) == 0)
break;
else if (nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO) &&
(int)nvlist_get_number(nvlist_get_nvlist(di[i],
SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_UNIT) == unit)
break;;
}
if (i == nitems)
xo_errx(1, "device not found");
#define NV(type, item) \
nvlist_get_ ## type (di[i], SNDST_DSPS_ ## item)
if ((dp = calloc(1, sizeof(struct snd_dev))) == NULL)
xo_err(1, "calloc");
dp->unit = -1;
strlcpy(dp->name, NV(string, NAMEUNIT), sizeof(dp->name));
strlcpy(dp->desc, NV(string, DESC), sizeof(dp->desc));
strlcpy(dp->devnode, NV(string, DEVNODE), sizeof(dp->devnode));
dp->from_user = NV(bool, FROM_USER);
dp->play.pchans = NV(number, PCHAN);
dp->rec.pchans = NV(number, RCHAN);
#undef NV
if (dp->play.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_PLAY))
xo_errx(1, "%s: playback channel list empty", dp->name);
if (dp->rec.pchans && !nvlist_exists(di[i], SNDST_DSPS_INFO_REC))
xo_errx(1, "%s: recording channel list empty", dp->name);
#define NV(type, mode, item) \
nvlist_get_ ## type (nvlist_get_nvlist(di[i], \
SNDST_DSPS_INFO_ ## mode), SNDST_DSPS_INFO_ ## item)
if (dp->play.pchans) {
dp->play.min_rate = NV(number, PLAY, MIN_RATE);
dp->play.max_rate = NV(number, PLAY, MAX_RATE);
dp->play.min_chans = NV(number, PLAY, MIN_CHN);
dp->play.max_chans = NV(number, PLAY, MAX_CHN);
fmt2str(dp->play.formats, sizeof(dp->play.formats),
NV(number, PLAY, FORMATS));
}
if (dp->rec.pchans) {
dp->rec.min_rate = NV(number, REC, MIN_RATE);
dp->rec.max_rate = NV(number, REC, MAX_RATE);
dp->rec.min_chans = NV(number, REC, MIN_CHN);
dp->rec.max_chans = NV(number, REC, MAX_CHN);
fmt2str(dp->rec.formats, sizeof(dp->rec.formats),
NV(number, REC, FORMATS));
}
#undef NV
if (strcmp(nvlist_get_string(di[i], SNDST_DSPS_PROVIDER),
SNDST_DSPS_SOUND4_PROVIDER) != 0)
goto done;
if (!nvlist_exists(di[i], SNDST_DSPS_PROVIDER_INFO))
xo_errx(1, "%s: provider_info list empty", dp->name);
#define NV(type, item) \
nvlist_get_ ## type (nvlist_get_nvlist(di[i], \
SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_ ## item)
strlcpy(dp->status, NV(string, STATUS), sizeof(dp->status));
dp->unit = NV(number, UNIT);
dp->bitperfect = NV(bool, BITPERFECT);
dp->play.vchans = NV(bool, PVCHAN);
dp->play.rate = NV(number, PVCHANRATE);
fmt2str(dp->play.format, sizeof(dp->play.format),
NV(number, PVCHANFORMAT));
dp->rec.vchans = NV(bool, RVCHAN);
dp->rec.rate = NV(number, RVCHANRATE);
fmt2str(dp->rec.format, sizeof(dp->rec.format),
NV(number, RVCHANFORMAT));
#undef NV
dp->autoconv = (dp->play.vchans || dp->rec.vchans) && !dp->bitperfect;
if (sysctl_int("hw.snd.latency", NULL, &t1) ||
sysctl_int("hw.snd.latency_profile", NULL, &t2) ||
sysctl_int("kern.timecounter.alloweddeviation", NULL, &t3))
xo_err(1, "%s: sysctl", dp->name);
if (t1 == 0 && t2 == 0 && t3 == 0)
dp->realtime = 1;
if (!nvlist_exists(nvlist_get_nvlist(di[i],
SNDST_DSPS_PROVIDER_INFO), SNDST_DSPS_SOUND4_CHAN_INFO))
xo_errx(1, "%s: channel info list empty", dp->name);
cdi = nvlist_get_nvlist_array(
nvlist_get_nvlist(di[i], SNDST_DSPS_PROVIDER_INFO),
SNDST_DSPS_SOUND4_CHAN_INFO, &nchans);
TAILQ_INIT(&dp->chans);
caps = 0;
for (j = 0; j < nchans; j++) {
#define NV(type, item) \
nvlist_get_ ## type (cdi[j], SNDST_DSPS_SOUND4_CHAN_ ## item)
if ((ch = calloc(1, sizeof(struct snd_chan))) == NULL)
xo_err(1, "calloc");
strlcpy(ch->name, NV(string, NAME), sizeof(ch->name));
strlcpy(ch->parentchan, NV(string, PARENTCHAN),
sizeof(ch->parentchan));
ch->unit = NV(number, UNIT);
ch->direction = (NV(number, CAPS) & PCM_CAP_INPUT) ?
INPUT : OUTPUT;
cap2str(ch->caps, sizeof(ch->caps), NV(number, CAPS));
ch->latency = NV(number, LATENCY);
ch->rate = NV(number, RATE);
fmt2str(ch->format, sizeof(ch->format), NV(number, FORMAT));
ch->pid = NV(number, PID);
strlcpy(ch->proc, NV(string, COMM), sizeof(ch->proc));
ch->interrupts = NV(number, INTR);
ch->xruns = NV(number, XRUNS);
ch->feedcount = NV(number, FEEDCNT);
ch->volume = NV(number, LEFTVOL) |
NV(number, RIGHTVOL) << 8;
fmt2str(ch->hwbuf.format, sizeof(ch->hwbuf.format),
NV(number, HWBUF_FORMAT));
ch->hwbuf.rate = NV(number, HWBUF_RATE);
ch->hwbuf.size_bytes = NV(number, HWBUF_SIZE);
ch->hwbuf.size_frames =
bytes2frames(ch->hwbuf.size_bytes, NV(number, HWBUF_FORMAT));
ch->hwbuf.blksz = NV(number, HWBUF_BLKSZ);
ch->hwbuf.blkcnt = NV(number, HWBUF_BLKCNT);
ch->hwbuf.free = NV(number, HWBUF_FREE);
ch->hwbuf.ready = NV(number, HWBUF_READY);
fmt2str(ch->swbuf.format, sizeof(ch->swbuf.format),
NV(number, SWBUF_FORMAT));
ch->swbuf.rate = NV(number, SWBUF_RATE);
ch->swbuf.size_bytes = NV(number, SWBUF_SIZE);
ch->swbuf.size_frames =
bytes2frames(ch->swbuf.size_bytes, NV(number, SWBUF_FORMAT));
ch->swbuf.blksz = NV(number, SWBUF_BLKSZ);
ch->swbuf.blkcnt = NV(number, SWBUF_BLKCNT);
ch->swbuf.free = NV(number, SWBUF_FREE);
ch->swbuf.ready = NV(number, SWBUF_READY);
strlcpy(ch->feederchain, NV(string, FEEDERCHAIN),
sizeof(ch->feederchain));
ch->dev = dp;
caps |= NV(number, CAPS);
TAILQ_INSERT_TAIL(&dp->chans, ch, next);
if (!dp->rec.vchans && ch->direction == INPUT) {
strlcpy(dp->rec.format, ch->hwbuf.format,
sizeof(dp->rec.format));
dp->rec.rate = ch->hwbuf.rate;
} else if (!dp->play.vchans && ch->direction == OUTPUT) {
strlcpy(dp->play.format, ch->hwbuf.format,
sizeof(dp->play.format));
dp->play.rate = ch->hwbuf.rate;
}
#undef NV
}
cap2str(dp->caps, sizeof(dp->caps), caps);
done:
free(arg.buf);
nvlist_destroy(nvl);
close(fd);
return (dp);
}
static void
free_dev(struct snd_dev *dp)
{
struct snd_chan *ch;
while (!TAILQ_EMPTY(&dp->chans)) {
ch = TAILQ_FIRST(&dp->chans);
TAILQ_REMOVE(&dp->chans, ch, next);
free(ch);
}
free(dp);
}
static void
print_dev_ctl(struct snd_dev *dp, struct snd_ctl *ctl, bool simple,
bool showgrp)
{
struct snd_ctl *cp;
size_t len;
if (ctl->type != GRP && xo_get_style(NULL) == XO_STYLE_TEXT) {
if (simple)
printf("%s=", ctl->name);
else
printf(" %-20s= ", ctl->name);
}
switch (ctl->type) {
case STR:
xo_emit("{a:%s/%s}\n", ctl->name, (char *)dp + ctl->off);
break;
case NUM:
xo_emit("{a:%s/%d}\n", ctl->name, *(int *)((intptr_t)dp + ctl->off));
break;
case VOL:
break;
case GRP:
if (!simple || !showgrp)
break;
for (cp = dev_ctls; cp->name != NULL; cp++) {
len = strlen(ctl->name);
if (strncmp(ctl->name, cp->name, len) == 0 &&
cp->name[len] == '.' && cp->type != GRP)
print_dev_ctl(dp, cp, simple, showgrp);
}
break;
}
}
static void
print_chan_ctl(struct snd_chan *ch, struct snd_ctl *ctl, bool simple,
bool showgrp)
{
struct snd_ctl *cp;
size_t len;
int v;
if (ctl->type != GRP && xo_get_style(NULL) == XO_STYLE_TEXT) {
if (simple)
printf("%s.%s=", ch->name, ctl->name);
else
printf(" %-20s= ", ctl->name);
}
switch (ctl->type) {
case STR:
xo_emit("{a:%s/%s}\n", ctl->name, (char *)ch + ctl->off);
break;
case NUM:
xo_emit("{a:%s/%d}\n", ctl->name, *(int *)((intptr_t)ch + ctl->off));
break;
case VOL:
v = *(int *)((intptr_t)ch + ctl->off);
xo_emit("{a:%s/%.2f:%.2f}\n", ctl->name,
MIX_VOLNORM(v & 0x00ff), MIX_VOLNORM((v >> 8) & 0x00ff));
break;
case GRP:
if (!simple || !showgrp)
break;
for (cp = chan_ctls; cp->name != NULL; cp++) {
len = strlen(ctl->name);
if (strncmp(ctl->name, cp->name, len) == 0 &&
cp->name[len] == '.' && cp->type != GRP)
print_chan_ctl(ch, cp, simple, showgrp);
}
break;
}
}
static void
print_dev(struct snd_dev *dp)
{
struct snd_chan *ch;
struct snd_ctl *ctl;
struct sbuf sb;
char buf[16];
xo_open_instance("devices");
if (!oflag || xo_get_style(NULL) != XO_STYLE_TEXT) {
sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN);
sbuf_printf(&sb, "(");
if (dp->play.pchans)
sbuf_printf(&sb, "play");
if (dp->play.pchans && dp->rec.pchans)
sbuf_printf(&sb, "/");
if (dp->rec.pchans)
sbuf_printf(&sb, "rec");
sbuf_printf(&sb, ")");
xo_emit("{:header/%s: <%s> %s %s}\n",
dp->name, dp->desc, dp->status, sbuf_data(&sb));
sbuf_delete(&sb);
}
for (ctl = dev_ctls; ctl->name != NULL; ctl++)
print_dev_ctl(dp, ctl, oflag, false);
if (vflag) {
xo_open_list("channels");
TAILQ_FOREACH(ch, &dp->chans, next) {
xo_open_instance("channels");
if (!oflag && xo_get_style(NULL) == XO_STYLE_TEXT)
printf(" %s\n", ch->name);
for (ctl = chan_ctls; ctl->name != NULL; ctl++)
print_chan_ctl(ch, ctl, oflag, false);
xo_close_instance("channels");
}
xo_close_list("channels");
}
xo_close_instance("devices");
}
static int
mod_bitperfect(struct snd_dev *dp, void *arg)
{
char buf[64];
if (dp->from_user)
return (-1);
snprintf(buf, sizeof(buf), "dev.pcm.%d.bitperfect", dp->unit);
return (sysctl_int(buf, arg, &dp->bitperfect));
}
static int
mod_autoconv(struct snd_dev *dp, void *arg)
{
const char *val = arg;
const char *zero = "0";
const char *one = "1";
int rc = -1;
if (dp->from_user)
return (rc);
if (strcmp(val, zero) == 0) {
rc = mod_play_vchans(dp, __DECONST(char *, zero)) ||
mod_rec_vchans(dp, __DECONST(char *, zero)) ||
mod_bitperfect(dp, __DECONST(char *, one));
if (rc == 0)
dp->autoconv = 0;
} else if (strcmp(val, one) == 0) {
rc = mod_play_vchans(dp, __DECONST(char *, one)) ||
mod_rec_vchans(dp, __DECONST(char *, one)) ||
mod_bitperfect(dp, __DECONST(char *, zero));
if (rc == 0)
dp->autoconv = 1;
}
return (rc);
}
static int
mod_realtime(struct snd_dev *dp, void *arg)
{
const char *val = arg;
int rc = -1;
if (dp->from_user)
return (-1);
if (strcmp(val, "0") == 0) {
rc = sysctl_int("hw.snd.latency", "2", NULL) ||
sysctl_int("hw.snd.latency_profile", "1", NULL) ||
sysctl_int("kern.timecounter.alloweddeviation", "5", NULL);
if (rc == 0)
dp->realtime = 0;
} else if (strcmp(val, "1") == 0) {
rc = sysctl_int("hw.snd.latency", "0", NULL) ||
sysctl_int("hw.snd.latency_profile", "0", NULL) ||
sysctl_int("kern.timecounter.alloweddeviation", "0", NULL);
if (rc == 0)
dp->realtime = 1;
}
return (rc);
}
static int
mod_play_vchans(struct snd_dev *dp, void *arg)
{
char buf[64];
if (dp->from_user)
return (-1);
if (!dp->play.pchans)
return (0);
snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchans", dp->unit);
return (sysctl_int(buf, arg, &dp->play.vchans));
}
static int
mod_play_rate(struct snd_dev *dp, void *arg)
{
char buf[64];
if (dp->from_user)
return (-1);
if (!dp->play.vchans)
return (0);
snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchanrate", dp->unit);
return (sysctl_int(buf, arg, &dp->play.rate));
}
static int
mod_play_format(struct snd_dev *dp, void *arg)
{
char buf[64];
if (dp->from_user)
return (-1);
if (!dp->play.vchans)
return (0);
snprintf(buf, sizeof(buf), "dev.pcm.%d.play.vchanformat", dp->unit);
return (sysctl_str(buf, arg, dp->play.format, sizeof(dp->play.format)));
}
static int
mod_rec_vchans(struct snd_dev *dp, void *arg)
{
char buf[64];
if (dp->from_user)
return (-1);
if (!dp->rec.pchans)
return (0);
snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchans", dp->unit);
return (sysctl_int(buf, arg, &dp->rec.vchans));
}
static int
mod_rec_rate(struct snd_dev *dp, void *arg)
{
char buf[64];
if (dp->from_user)
return (-1);
if (!dp->rec.vchans)
return (0);
snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchanrate", dp->unit);
return (sysctl_int(buf, arg, &dp->rec.rate));
}
static int
mod_rec_format(struct snd_dev *dp, void *arg)
{
char buf[64];
if (dp->from_user)
return (-1);
if (!dp->rec.vchans)
return (0);
snprintf(buf, sizeof(buf), "dev.pcm.%d.rec.vchanformat", dp->unit);
return (sysctl_str(buf, arg, dp->rec.format, sizeof(dp->rec.format)));
}
static void __dead2
usage(void)
{
xo_error("usage: %s [--libxo] [-f device] [-hov] [control[=value] ...]\n",
getprogname());
xo_finish();
exit(1);
}
int
main(int argc, char *argv[])
{
struct snd_dev *dp;
struct snd_chan *ch;
struct snd_ctl *ctl;
char *path = NULL;
char *s, *propstr;
bool show = true, found;
int c;
argc = xo_parse_args(argc, argv);
if (argc < 0)
exit(1);
while ((c = getopt(argc, argv, "f:hov")) != -1) {
switch (c) {
case 'f':
path = optarg;
break;
case 'o':
oflag = true;
break;
case 'v':
vflag = true;
break;
case 'h':
case '?':
default:
usage();
}
}
argc -= optind;
argv += optind;
xo_set_version(SNDCTL_XO_VERSION);
xo_open_container("sndctl");
dp = read_dev(path);
xo_open_container("executed_controls");
while (argc > 0) {
if ((s = strdup(*argv)) == NULL) {
xo_close_container("executed_controls");
xo_close_container("sndctl");
if (xo_finish() < 0)
xo_err(1, "xo_finish");
xo_err(1, "strdup(%s)", *argv);
}
propstr = strsep(&s, "=");
if (propstr == NULL)
goto next;
found = false;
for (ctl = dev_ctls; ctl->name != NULL; ctl++) {
if (strcmp(ctl->name, propstr) != 0)
continue;
if (s == NULL)
show = false;
else if (ctl->mod != NULL && ctl->mod(dp, s) < 0)
xo_warnx("%s(%s) failed", ctl->name, s);
if (s == NULL || xo_get_style(NULL) != XO_STYLE_TEXT) {
print_dev_ctl(dp, ctl, true, true);
}
found = true;
break;
}
TAILQ_FOREACH(ch, &dp->chans, next) {
for (ctl = chan_ctls; ctl->name != NULL; ctl++) {
if (strcmp(ctl->name, propstr) != 0)
continue;
print_chan_ctl(ch, ctl, true, true);
show = false;
found = true;
break;
}
}
if (!found)
xo_warnx("%s: no such property", propstr);
next:
free(s);
argc--;
argv++;
}
xo_close_container("executed_controls");
if (show || xo_get_style(NULL) != XO_STYLE_TEXT) {
xo_open_list("devices");
print_dev(dp);
xo_close_list("devices");
}
free_dev(dp);
xo_close_container("sndctl");
if (xo_finish() < 0)
xo_err(1, "xo_finish");
return (0);
}