#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/audioio.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <err.h>
#define BPS(bits) (((bits) <= 8) ? 1 : (((bits) <= 16) ? 2 : 4))
struct audio_device rname;
struct audio_status rstatus;
struct audio_swpar rpar, wpar;
struct audio_pos rpos;
struct field {
char *name;
void *raddr, *waddr;
#define MODE 0
#define NUM 1
#define STR 2
#define ENC 3
int type;
int show;
int set;
} fields[] = {
{"name", &rname.name, NULL, STR},
{"mode", &rstatus.mode, NULL, MODE},
{"pause", &rstatus.pause, NULL, NUM},
{"active", &rstatus.active, NULL, NUM},
{"nblks", &rpar.nblks, &wpar.nblks, NUM},
{"blksz", &rpar.round, &wpar.round, NUM},
{"rate", &rpar.rate, &wpar.rate, NUM},
{"encoding", &rpar, &wpar, ENC},
{"play.channels", &rpar.pchan, &wpar.pchan, NUM},
{"play.bytes", &rpos.play_pos, NULL, NUM},
{"play.errors", &rpos.play_xrun, NULL, NUM},
{"record.channels", &rpar.rchan, &wpar.rchan, NUM},
{"record.bytes", &rpos.rec_pos, NULL, NUM},
{"record.errors", &rpos.rec_xrun, NULL, NUM},
{NULL, NULL, 0}
};
const char usagestr[] =
"usage: audioctl [-nq] [-f file] [-w wait] [name[=value] ...]\n";
int fd, show_names = 1, quiet = 0, wait_sec = 0;
int
strtoenc(struct audio_swpar *ap, char *p)
{
if (*p == 's')
ap->sig = 1;
else if (*p == 'u')
ap->sig = 0;
else
return 0;
p++;
ap->bits = 0;
while (*p >= '0' && *p <= '9') {
ap->bits = (ap->bits * 10) + *p++ - '0';
if (ap->bits > 32)
return 0;
}
if (ap->bits < 8)
return 0;
ap->bps = BPS(ap->bits);
ap->le = (BYTE_ORDER == LITTLE_ENDIAN);
ap->msb = 1;
if (*p == '\0')
return 1;
if (p[0] == 'l' && p[1] == 'e')
ap->le = 1;
else if (p[0] == 'b' && p[1] == 'e')
ap->le = 0;
else
return 0;
p += 2;
if (*p == '\0')
return 1;
if (*p < '0' || *p > '9')
return 0;
ap->bps = *p - '0';
if (ap->bps < ((ap->bits + 7) >> 3) || ap->bps > 4)
return 0;
if (*++p == '\0')
return 1;
if (p[0] == 'm' && p[1] == 's' && p[2] == 'b')
ap->msb = 1;
else if (p[0] == 'l' && p[1] == 's' && p[2] == 'b')
ap->msb = 0;
else if (*p == '\0')
return 1;
p += 3;
if (*p == '\0')
return 1;
return 0;
}
void
print_field(struct field *p, void *addr)
{
int mode;
struct audio_swpar *ap;
switch (p->type) {
case NUM:
printf("%u", *(unsigned int *)addr);
break;
case STR:
printf("%s", (char *)addr);
break;
case MODE:
mode = *(unsigned int *)addr;
if (mode & AUMODE_PLAY)
printf("play");
if (mode & AUMODE_RECORD) {
if (mode & AUMODE_PLAY)
printf(",");
printf("record");
}
break;
case ENC:
ap = addr;
printf("%s%u", ap->sig ? "s" : "u", ap->bits);
if (ap->bps == 1)
break;
printf("%s", ap->le ? "le" : "be");
if (ap->bps != BPS(ap->bits) || ap->bits < ap->bps * 8) {
printf("%u", ap->bps);
if (ap->bits < ap->bps * 8)
printf("%s", ap->msb ? "msb" : "lsb");
}
}
}
void
parse_field(struct field *f, void *addr, char *p)
{
const char *strerr;
switch (f->type) {
case NUM:
*(unsigned int *)addr = strtonum(p, 0, UINT_MAX, &strerr);
if (strerr)
errx(1, "%s: %s", p, strerr);
break;
case ENC:
if (!strtoenc((struct audio_swpar *)addr, p))
errx(1, "%s: bad encoding", p);
}
}
void
audio_main(int argc, char **argv)
{
struct field *f;
char *lhs, *rhs;
int set = 0;
if (argc == 0) {
for (f = fields; f->name != NULL; f++)
f->show = 1;
}
AUDIO_INITPAR(&wpar);
for (; argc > 0; argc--, argv++) {
lhs = *argv;
rhs = strchr(*argv, '=');
if (rhs)
*rhs++ = '\0';
for (f = fields;; f++) {
if (f->name == NULL)
errx(1, "%s: unknown parameter", lhs);
if (strcmp(f->name, lhs) == 0)
break;
}
if (rhs) {
if (f->waddr == NULL)
errx(1, "%s: is read only", f->name);
parse_field(f, f->waddr, rhs);
f->set = 1;
set = 1;
} else
f->show = 1;
}
if (set && wait_sec)
errx(1, "Can't set variables wait_secically");
while (1) {
if (ioctl(fd, AUDIO_GETSTATUS, &rstatus) == -1)
err(1, "AUDIO_GETSTATUS");
if (ioctl(fd, AUDIO_GETDEV, &rname) == -1)
err(1, "AUDIO_GETDEV");
if (ioctl(fd, AUDIO_GETPAR, &rpar) == -1)
err(1, "AUDIO_GETPAR");
if (ioctl(fd, AUDIO_GETPOS, &rpos) == -1)
err(1, "AUDIO_GETPOS");
for (f = fields; f->name != NULL; f++) {
if (!f->show)
continue;
if (show_names)
printf("%s=", f->name);
print_field(f, f->raddr);
printf("\n");
}
if (wait_sec == 0)
break;
sleep(wait_sec);
}
if (!set)
return;
if (ioctl(fd, AUDIO_SETPAR, &wpar) == -1)
err(1, "AUDIO_SETPAR");
if (ioctl(fd, AUDIO_GETPAR, &wpar) == -1)
err(1, "AUDIO_GETPAR");
for (f = fields; f->name != NULL; f++) {
if (!f->set || quiet)
continue;
if (show_names) {
printf("%s: ", f->name);
print_field(f, f->raddr);
printf(" -> ");
}
print_field(f, f->waddr);
printf("\n");
}
}
int
main(int argc, char **argv)
{
char *path = "/dev/audioctl0";
const char *errstr;
int c;
while ((c = getopt(argc, argv, "anf:qw:")) != -1) {
switch (c) {
case 'a':
break;
case 'n':
show_names = 0;
break;
case 'f':
path = optarg;
break;
case 'q':
quiet = 1;
break;
case 'w':
wait_sec = strtonum(optarg, 1, INT_MAX, &errstr);
if (errstr != NULL)
errx(1, "wait is %s: %s", errstr, optarg);
break;
default:
fputs(usagestr, stderr);
return 1;
}
}
argc -= optind;
argv += optind;
if (unveil(path, "w") == -1)
err(1, "unveil %s", path);
if (unveil(NULL, NULL) == -1)
err(1, "unveil");
fd = open(path, O_WRONLY);
if (fd == -1)
err(1, "%s", path);
audio_main(argc, argv);
close(fd);
return 0;
}