add audio/mixfs to allow simultanious playback streams over a single /dev/audio

This commit is contained in:
cinap_lenrek 2013-12-26 21:04:25 +01:00
parent cad92eedd9
commit b9bec46b98
4 changed files with 300 additions and 1 deletions

View file

@ -53,6 +53,17 @@ q ] [
.I length
]
.PP
.B audio/mixfs
[
.B -D
] [
.B -s
.I srvname
] [
.B -m
.I mtpt
]
.PP
.SH DESCRIPTION
These programs decode and encode various audio formats from and to
16-bit stereo PCM (little endian). The decoders read the compressed
@ -260,6 +271,38 @@ until it reached end of file or, if
was given, a number of
.I length
bytes have been consumed from input.
.PP
.I Mixfs
is a fileserver serving a single
.B audio
file which allows simultanious playback of audio streams. When
run, it binds over
.B /dev/audio
and mixes the audio samples that get written to it.
A service name
.I srvname
can be given with the
.B -s
option which gets posted to
.BR /srv .
By default,
.I mixfs
mounts itself on
.B /mnt/mix
and then binds
.B /mnt/mix/audio
over
.BR /dev .
A alternative mountpoint
.I mtpt
can be specified with the
.B -m
option.
The
.B -D
option causes
.B 9p
debug messages to be written to file-descriptor 2.
.SH EXAMPLE
Play back an
.L .mp3

View file

@ -0,0 +1,248 @@
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
enum {
NBUF = 8*1024,
NDELAY = 2048,
NCHAN = 2,
};
typedef struct Stream Stream;
struct Stream
{
int used;
int run;
ulong wp;
QLock;
Rendez;
};
ulong mixrp;
int mixbuf[NBUF][NCHAN];
Stream streams[16];
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
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;
qunlock(s);
r->fid->aux = s;
respond(r, nil);
return;
}
qunlock(s);
}
respond(r, "all streams in use");
}
void
fsclunk(Fid *f)
{
Stream *s;
if(f->file != nil && strcmp(f->file->name, "audio") == 0 && (s = f->aux) != nil){
f->aux = nil;
s->used = 0;
}
}
void
audioproc(void *)
{
static uchar buf[NBUF*NCHAN*2];
int nsleep, fd, i, j, n, m, v;
Stream *s;
uchar *p;
threadsetname("audioproc");
fd = -1;
nsleep = 0;
for(;;){
m = NBUF;
for(s = streams; s < streams+nelem(streams); s++){
qlock(s);
if(s->run){
n = (long)(s->wp - mixrp);
if(n <= 0 && nsleep > 4)
s->run = 0;
else if(n < m)
m = n;
if(n < NDELAY)
rwakeup(s);
}
qunlock(s);
}
m %= NBUF;
if(m == 0){
sleep(1<<nsleep);
if(nsleep < 7)
nsleep++;
else {
close(fd);
fd = -1;
}
continue;
}
if(fd < 0)
if((fd = open("/dev/audio", OWRITE)) < 0)
continue;
nsleep = 0;
p = buf;
for(i=0; i<m; i++){
for(j=0; j<NCHAN; j++){
v = clip16(mixbuf[mixrp % NBUF][j]);
mixbuf[mixrp % NBUF][j] = 0;
*p++ = v & 0xFF;
*p++ = v >> 8;
}
mixrp++;
}
write(fd, buf, p - buf);
}
}
void
fswrite(Req *r)
{
Srv *srv;
int i, j, n, m;
Stream *s;
uchar *p;
p = (uchar*)r->ifcall.data;
n = r->ifcall.count;
m = n/(NCHAN*2);
srv = r->srv;
srvrelease(srv);
s = r->fid->aux;
qlock(s);
if(s->run == 0){
s->wp = mixrp;
s->run = 1;
}
for(i=0; i<m; i++){
while((long)(s->wp - mixrp) >= NBUF-1){
s->run = 1;
rsleep(s);
}
for(j=0; j<NCHAN; j++){
mixbuf[s->wp % NBUF][j] += s16(p);
p += 2;
}
s->wp++;
}
if((long)(s->wp - mixrp) >= NDELAY){
s->run = 1;
rsleep(s);
}
qunlock(s);
r->ofcall.count = n;
respond(r, nil);
srvacquire(srv);
}
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,
.write= fswrite,
.destroyfid= fsclunk,
.start= fsstart,
.end= fsend,
};
void
usage(void)
{
fprint(2, "usage: %s [-D] [-s srvname] [-m mtpt]\n", argv0);
exits("usage");
}
void
threadmain(int argc, char **argv)
{
char *srv = nil;
char *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)
usage();
fs.tree = alloctree(nil, nil, DMDIR|0777, nil);
createfile(fs.tree->root, "audio", nil, 0222, nil);
threadpostmountsrv(&fs, srv, mtpt, MREPL);
mtpt = smprint("%s/audio", mtpt);
if(bind(mtpt, "/dev/audio", MREPL) < 0)
sysfatal("bind: %r");
free(mtpt);
threadexits(0);
}

View file

@ -0,0 +1,8 @@
</$objtype/mkfile
<../config
OFILES=mixfs.$O
TARG=mixfs
</sys/src/cmd/mkone

View file

@ -1,7 +1,7 @@
</$objtype/mkfile
LIBS=libogg libvorbis libFLAC
PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec wavdec sundec
PROGS=pcmconv oggdec oggenc mp3dec mp3enc flacdec wavdec sundec mixfs
#libs must be made first
DIRS=$LIBS $PROGS