ref: 9b69f546334e94ec191d35b15dcaace98fbdcb1b
dir: /sys/src/cmd/audio/mixfs/mixfs.c/
#include <u.h>
#include <libc.h>
#include <tos.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#include <pcm.h>
#pragma varargck type "!" Pcmdesc
enum {
NBUF = 8*1024,
NDELAY = 1764, /* 40.0ms */
NQUANTA = 64, /* ~1.45ms */
NCHAN = 2,
ABUF = NBUF*NCHAN*2,
Nsec = 1000000000ULL,
};
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
typedef struct Stream Stream;
struct Stream
{
int used;
int mode;
int flush;
int run;
ulong rp;
ulong wp;
QLock;
Rendez;
};
ulong mixrp;
Lock rplock;
int lbbuf[NBUF][NCHAN];
int mixbuf[NBUF][NCHAN];
Lock mixlock;
Stream streams[64];
char *devaudio;
QLock devlock;
int audiofd = -1;
int volfd = -1;
int volume[2] = {100, 100};
int vol64k[2] = {65536, 65536};
Pcmdesc fmt;
Lock fmtlock;
int fmtchanged;
int isnulldev;
int delay;
int
s16(uchar *p)
{
int v;
v = p[0]<<(sizeof(int)-2)*8 | p[1]<<(sizeof(int)-1)*8;
v >>= (sizeof(int)-2)*8;
return v;
}
int
clip16(int v)
{
if(v > 0x7fff)
return 0x7fff;
if(v < -0x8000)
return -0x8000;
return v;
}
void
closeaudiodev(void)
{
qlock(&devlock);
if(audiofd >= 0){
close(audiofd);
audiofd = -1;
}
qunlock(&devlock);
}
void
updfmt(void)
{
static char svol[4*1024];
char *s, *e;
int n, ok;
ok = 0;
if(volfd >= 0 && (n = pread(volfd, svol+1, sizeof(svol)-2, 0)) > 8){
svol[0] = '\n';
svol[1+n] = 0;
if((s = strstr(svol, "\nfmtout ")) != nil && (e = strchr(s+12, '\n')) != nil){
*e = 0;
lock(&fmtlock);
ok = fmtchanged = mkpcmdesc(s+8, &fmt) == 0;
unlock(&fmtlock);
}
if(!ok && (s = strstr(svol, "\nspeed ")) != nil && (n = strtol(s+6, &s, 10)) > 0){
lock(&fmtlock);
fmt = pcmdescdef;
fmt.rate = n;
ok = fmtchanged = fmt.rate > 0;
unlock(&fmtlock);
}
}
if(!ok){
lock(&fmtlock);
fmt = pcmdescdef;
fmtchanged = 1;
unlock(&fmtlock);
}
}
int
reopendevs(char *name)
{
static char dir[] = "/dev/";
int i, n, dfd, afd, len;
char *p;
Dir *d;
if(name != nil){
if(name != devaudio){
/* hack: restrict to known audio device names */
if((strncmp(name, "/dev/audio", 10) != 0 || strchr(name+10, '/') != nil)
&& (strncmp(name, "#u/audio", 8) != 0 || strchr(name+8, '/') != nil)
&& (strncmp(name, "#A/audio", 8) != 0 || strchr(name+8, '/') != nil)
&& (strncmp(name, "/dev/null", 9) != 0 || strchr(name+9, '/') != nil)){
werrstr("name doesnt look like an audio device");
return -1;
}
}
if((afd = open(name, OWRITE)) >= 0){
name = strdup(name);
goto found;
}
if(name != devaudio)
return -1;
}
if((dfd = open(dir, OREAD)) >= 0){
while((n = dirread(dfd, &d)) > 0){
for(i = 0; i < n; i++){
len = strlen(d[i].name);
if((d[i].mode & DMDIR) != 0
|| len < 5
|| strncmp(d[i].name, "audio", 5) != 0
|| strcmp(d[i].name+len-3, "ctl") == 0
|| strcmp(d[i].name+len-4, "stat") == 0)
continue;
name = smprint("%s%s", dir, d[i].name);
if((afd = open(name, OWRITE)) >= 0){
close(dfd);
free(d);
goto found;
}
free(name);
}
free(d);
}
close(dfd);
werrstr("no devices found");
}
return -1;
found:
qlock(&devlock);
free(devaudio);
devaudio = name;
isnulldev = 0;
audiofd = dup(afd, audiofd);
qunlock(&devlock);
close(afd);
if(volfd >= 0){
close(volfd);
volfd = -1;
}
if((p = utfrrune(name, '/')) != nil)
p++;
else
p = name;
if(strncmp(p, "audio", 5) == 0){
name = smprint("%.*svolume%s", (int)(p - name), name, p+5);
volfd = open(name, ORDWR);
free(name);
if(volfd >= 0)
fprint(volfd, "delay %d\n", delay);
updfmt();
}else if(strncmp(p, "null", 4) == 0){
isnulldev = 1;
updfmt();
}
return 0;
}
void
fsopen(Req *r)
{
Stream *s;
if(strcmp(r->fid->file->name, "audio") != 0){
respond(r, nil);
return;
}
for(s = streams; s < streams+nelem(streams); s++){
qlock(s);
if(s->used == 0 && s->run == 0){
s->used = 1;
s->mode = r->ifcall.mode;
s->flush = 0;
qunlock(s);
r->fid->aux = s;
respond(r, nil);
return;
}
qunlock(s);
}
respond(r, "all streams in use");
}
void
fsflush(Req *r)
{
Fid *f = r->oldreq->fid;
Stream *s;
if(f->file != nil && strcmp(f->file->name, "audio") == 0 && (s = f->aux) != nil){
qlock(s);
if(s->used && s->run){
s->flush = 1;
rwakeup(s);
}
qunlock(s);
}
respond(r, nil);
}
void
fsclunk(Fid *f)
{
Stream *s;
if(f->file != nil && strcmp(f->file->name, "audio") == 0 && (s = f->aux) != nil)
s->used = 0;
f->aux = nil;
}
int
eqfmt(Pcmdesc *a, Pcmdesc *b)
{
return
a->rate == b->rate
&& a->bits == b->bits
&& a->channels == b->channels
&& a->framesz == b->framesz
&& a->fmt == b->fmt;
}
uvlong
nanosec(void)
{
static uvlong fasthz, xstart;
uvlong x;
if(fasthz == ~0ULL)
return nsec() - xstart;
if(fasthz == 0){
if(_tos->cyclefreq){
fasthz = _tos->cyclefreq;
cycles(&xstart);
} else {
fasthz = ~0ULL;
xstart = nsec();
}
return 0;
}
cycles(&x);
x -= xstart;
uvlong q = x / fasthz;
uvlong r = x % fasthz;
return q*Nsec + r*Nsec/fasthz;
}
void
audioproc(void *)
{
static uchar buf[ABUF];
int sweep, i, j, n, m, v, rate;
uvlong t0, dt, ns;
ulong rp;
Stream *s;
uchar *p, *bufconv;
Pcmconv *conv;
threadsetname("audioproc");
conv = nil;
bufconv = nil;
sweep = 0;
rate = fmt.rate;
delay = NDELAY;
ns = Nsec/pcmdescdef.rate;
t0 = nanosec();
for(;;){
m = NBUF;
for(s = streams; s < streams+nelem(streams); s++){
qlock(s);
if(s->run){
if(s->mode & OWRITE){
n = (long)(s->wp - mixrp);
if(n <= 0 && (s->used == 0 || sweep))
s->run = 0;
else if(n < m)
m = n;
if(n < delay)
rwakeup(s);
} else {
n = (long)(mixrp - s->rp);
if(n > NBUF && (s->used == 0 || sweep))
s->run = 0;
if(n > 0)
rwakeup(s);
}
}
qunlock(s);
}
m %= NBUF;
if(m == 0){
int ms;
ms = 100;
if(audiofd >= 0){
if(sweep){
closeaudiodev();
} else {
/* attempt to sleep just shortly before buffer underrun */
ms = seek(audiofd, 0, 2);
ms *= 800;
ms /= rate*NCHAN*2;
}
sweep = 1;
}
sleep(ms);
continue;
}
sweep = 0;
if(audiofd < 0 && reopendevs(devaudio) < 0){
fprint(2, "%s: reopendevs: %r\n", argv0);
sleep(1000);
continue;
}
if(isnulldev){
dt = nanosec() - t0;
m = (dt+ns-1)/ns;
m = MAX(NQUANTA, m);
m = MIN(m, NBUF);
t0 += dt;
}else
m = MIN(m, NQUANTA);
p = buf;
rp = mixrp;
for(i=0; i<m; i++){
for(j=0; j<NCHAN; j++){
v = clip16(mixbuf[rp % NBUF][j])*vol64k[j] / 65536;
lbbuf[rp % NBUF][j] = v;
mixbuf[rp % NBUF][j] = 0;
*p++ = v & 0xFF;
*p++ = v >> 8;
}
rp++;
}
/* barrier */
lock(&rplock);
mixrp = rp;
unlock(&rplock);
if(isnulldev){
sleep(5);
continue;
}
lock(&fmtlock);
if(fmtchanged){
fmtchanged = 0;
free(bufconv);
bufconv = nil;
freepcmconv(conv);
conv = nil;
if(!eqfmt(&fmt, &pcmdescdef))
if((conv = allocpcmconv(&pcmdescdef, &fmt)) == nil ||
(n = pcmratio(conv, ABUF)) < 0 ||
(bufconv = malloc(n)) == nil){
fprint(2, "%s: %r\n", devaudio);
freepcmconv(conv);
conv = nil;
}
rate = fmt.rate;
}
unlock(&fmtlock);
n = p - buf;
p = buf;
if(conv != nil){
n = pcmconv(conv, buf, bufconv, n);
p = bufconv;
}
if(write(audiofd, p, n) != n)
closeaudiodev();
}
}
void
fsread(Req *r)
{
Srv *srv;
int i, j, n, m, v;
char *f, *e;
Stream *s;
uchar *p;
if(r->fid->file->aux == &volfd){
static char svol[4096];
if(r->ifcall.offset == 0){
m = snprint(svol, sizeof(svol), "dev %s\nmix %d %d\nfmtout %!\nspeed %d\n",
devaudio?devaudio:"",
volume[0], volume[1],
pcmdescdef,
pcmdescdef.rate);
if(volfd >= 0 && (n = pread(volfd, svol+m, sizeof(svol)-m-1, 0)) > 0){
svol[m+n] = 0;
/* device's fmtout and speed are removed */
if((f = strstr(svol+m-1, "\nfmtout ")) != nil && (e = strchr(f+8, '\n')) != nil){
n -= e-f;
memmove(f, e, n+1);
}
if((f = strstr(svol+m-1, "\nspeed ")) != nil && (e = strchr(f+7, '\n')) != nil){
n -= e-f;
memmove(f, e, n+1);
}
}
}
readstr(r, svol);
respond(r, nil);
return;
}
p = (uchar*)r->ofcall.data;
n = r->ifcall.count;
n &= ~(NCHAN*2 - 1);
r->ofcall.count = n;
n /= (NCHAN*2);
srv = r->srv;
srvrelease(srv);
s = r->fid->aux;
qlock(s);
while(n > 0){
if(s->run == 0){
s->rp = mixrp;
s->run = 1;
}
m = (long)(mixrp - s->rp);
if(m <= 0){
if(s->flush)
break;
s->run = 1;
rsleep(s);
continue;
}
if(m > NBUF){
m = NBUF;
s->rp = mixrp - m;
}
if(m > n)
m = n;
for(i=0; i<m; i++){
for(j=0; j<NCHAN; j++){
v = lbbuf[s->rp % NBUF][j];
*p++ = v & 0xFF;
*p++ = v >> 8;
}
s->rp++;
}
n -= m;
}
s->flush = 0;
qunlock(s);
respond(r, nil);
srvacquire(srv);
}
void
fswrite(Req *r)
{
Srv *srv;
int i, j, n, m;
Stream *s;
uchar *p;
if(r->fid->file->aux == &volfd){
char msg[64], *f[5];
int x[2], nf;
r->ofcall.count = r->ifcall.count;
snprint(msg, sizeof(msg), "%.*s",
utfnlen((char*)r->ifcall.data, r->ifcall.count), (char*)r->ifcall.data);
nf = tokenize(msg, f, nelem(f));
if(nf > 1 && strcmp(f[0], "dev") == 0){
if(reopendevs(f[1]) < 0){
responderror(r);
return;
}
}else if(nf > 1 && strcmp(f[0], "mix") == 0){
x[0] = atoi(f[1]);
x[1] = nf < 3 ? x[0] : atoi(f[2]);
if(f[1][0] == '+' || f[1][0] == '-'){
x[0] += volume[0];
x[1] += volume[1];
}
volume[0] = MAX(0, x[0]);
volume[0] = MIN(volume[0], 100);
volume[1] = MAX(0, x[1]);
volume[1] = MIN(volume[1], 100);
/* ≈60dB dynamic range; [0-100] → [0-65536] */
vol64k[0] = 65.536 * (exp(volume[0] * 0.0690876) - 1.0);
vol64k[1] = 65.536 * (exp(volume[1] * 0.0690876) - 1.0);
}else if(volfd >= 0){
if(nf > 1 && strcmp(f[0], "delay") == 0){
n = atoi(f[1]);
if(n < NQUANTA || n > NBUF){
responderror(r);
return;
}
delay = n;
}
if(write(volfd, r->ifcall.data, r->ifcall.count) < 0){
responderror(r);
return;
}
}else{
respond(r, "bad msg");
return;
}
respond(r, nil);
return;
}
p = (uchar*)r->ifcall.data;
n = r->ifcall.count;
r->ofcall.count = n;
n /= (NCHAN*2);
srv = r->srv;
srvrelease(srv);
s = r->fid->aux;
qlock(s);
while(n > 0){
if(s->run == 0){
s->wp = mixrp;
s->run = 1;
}
m = NBUF-1 - (long)(s->wp - mixrp);
if(m <= 0){
if(s->flush)
break;
s->run = 1;
rsleep(s);
continue;
}
if(m > n)
m = n;
lock(&mixlock);
for(i=0; i<m; i++){
for(j=0; j<NCHAN; j++){
mixbuf[s->wp % NBUF][j] += s16(p);
p += 2;
}
s->wp++;
}
unlock(&mixlock);
n -= m;
}
if((long)(s->wp - mixrp) >= delay && !s->flush){
s->run = 1;
rsleep(s);
}
s->flush = 0;
qunlock(s);
respond(r, nil);
srvacquire(srv);
}
void
fsstat(Req *r)
{
Stream *s;
r->d.length = 0;
if(r->fid->file != nil && strcmp(r->fid->file->name, "audio") == 0 && (s = r->fid->aux) != nil){
qlock(s);
if(s->run){
r->d.length = (long)(s->wp - mixrp);
r->d.length *= NCHAN*2;
}
qunlock(s);
}
respond(r, nil);
}
void
fsstart(Srv *)
{
Stream *s;
for(s=streams; s < streams+nelem(streams); s++){
s->used = s->run = 0;
s->Rendez.l = &s->QLock;
}
proccreate(audioproc, nil, 16*1024);
}
void
fsend(Srv *)
{
threadexitsall(nil);
}
Srv fs = {
.open= fsopen,
.read= fsread,
.write= fswrite,
.stat= fsstat,
.destroyfid= fsclunk,
.flush= fsflush,
.start= fsstart,
.end= fsend,
};
void
usage(void)
{
fprint(2, "usage: %s [-D] [-v] [-s srvname] [-m mtpt]\n", argv0);
exits("usage");
}
void
threadmain(int argc, char **argv)
{
char *srv = nil;
char *m, *mtpt = "/mnt/mix";
ARGBEGIN{
case 'D':
chatty9p++;
break;
case 's':
srv = EARGF(usage());
break;
case 'm':
mtpt = EARGF(usage());
break;
default:
usage();
}ARGEND;
if(argc > 1)
usage();
fmtinstall('!', pcmdescfmt);
reopendevs(argv[0]);
closeaudiodev();
fs.tree = alloctree(nil, nil, DMDIR|0777, nil);
createfile(fs.tree->root, "audio", nil, 0666, nil);
createfile(fs.tree->root, "volume", nil, 0666, &volfd);
threadpostmountsrv(&fs, srv, mtpt, MREPL);
bind(mtpt, "/dev", MAFTER);
m = smprint("%s/audio", mtpt);
bind(m, "/dev/audio", MREPL);
m = smprint("%s/volume", mtpt);
bind(m, "/dev/volume", MREPL);
threadexits(0);
}