#include <u.h>
#include <libc.h>
#include <thread.h>
#include <fcall.h>
#include "pool.h"
#include "playlist.h"
enum {
Pac,
Mp3,
Pcm,
Ogg,
};
typedef struct Playfd Playfd;
struct Playfd {
/* Describes a file to play for starting up pac4dec/mp3,... */
char *filename; /* mallocated */
int fd; /* filedesc to use */
int cfd; /* fildesc to close */
};
Channel *full, *empty, *playout, *spare;
Channel *playc, *pacc;
char *playprog[] = {
[Pac] = "/bin/games/pac4dec",
[Mp3] = "/bin/games/mp3dec",
[Pcm] = "/bin/cat",
[Ogg] = "/bin/games/vorbisdec",
};
ulong totbytes, totbuffers;
static char curfile[8192];
void
pac4dec(void *a)
{
Playfd *pfd;
Pacbuf *pb;
int fd, type;
char *ext, buf[256];
static char args[6][32];
char *argv[6] = {args[0], args[1], args[2], args[3], args[4], args[5]};
threadsetname("pac4dec");
pfd = a;
close(pfd->cfd); /* read fd */
ext = strrchr(pfd->filename, '.');
fd = open(pfd->filename, OREAD);
if (fd < 0 && ext == nil){
// Try the alternatives
ext = buf + strlen(pfd->filename);
snprint(buf, sizeof buf, "%s.pac", pfd->filename);
fd = open(buf, OREAD);
if (fd < 0){
snprint(buf, sizeof buf, "%s.mp3", pfd->filename);
fd = open(buf, OREAD);
}
if (fd < 0){
snprint(buf, sizeof buf, "%s.ogg", pfd->filename);
fd = open(buf, OREAD);
}
if (fd < 0){
snprint(buf, sizeof buf, "%s.pcm", pfd->filename);
fd = open(buf, OREAD);
}
}
if (fd < 0){
if (debug & DbgPlayer)
fprint(2, "pac4dec: %s: %r", pfd->filename);
pb = nbrecvp(spare);
pb->cmd = Error;
pb->off = 0;
pb->len = snprint(pb->data, sizeof(pb->data), "startplay: %s: %r", pfd->filename);
sendp(full, pb);
threadexits("open");
}
dup(pfd->fd, 1);
close(pfd->fd);
if(ext == nil || strcmp(ext, ".pac") == 0){
type = Pac;
snprint(args[0], sizeof args[0], "pac4dec");
snprint(args[1], sizeof args[1], "/fd/%d", fd);
snprint(args[2], sizeof args[2], "/fd/1");
argv[3] = nil;
}else if(strcmp(ext, ".mp3") == 0){
type = Mp3;
snprint(args[0], sizeof args[0], "mp3dec");
snprint(args[1], sizeof args[1], "-q");
snprint(args[2], sizeof args[1], "-s");
snprint(args[3], sizeof args[1], "/fd/%d", fd);
argv[4] = nil;
}else if(strcmp(ext, ".ogg") == 0){
type = Ogg;
snprint(args[0], sizeof args[0], "vorbisdec");
argv[1] = nil;
argv[2] = nil;
argv[3] = nil;
dup(fd, 0);
}else{
type = Pcm;
snprint(args[0], sizeof args[0], "cat");
snprint(args[1], sizeof args[1], "/fd/%d", fd);
argv[2] = nil;
argv[3] = nil;
}
free(pfd->filename);
free(pfd);
if (debug & DbgPlayer)
fprint(2, "procexecl %s %s %s %s\n",
playprog[type], argv[0], argv[1], argv[2]);
procexec(nil, playprog[type], argv);
if((pb = nbrecvp(spare)) == nil)
pb = malloc(sizeof(Pacbuf));
pb->cmd = Error;
pb->off = 0;
pb->len = snprint(pb->data, sizeof(pb->data), "startplay: %s: exec", playprog[type]);
sendp(full, pb);
threadexits(playprog[type]);
}
static int
startplay(ushort n)
{
int fd[2];
Playfd *pfd;
char *file;
file = getplaylist(n);
if(file == nil)
return Undef;
if (debug & DbgPlayer)
fprint(2, "startplay: file is `%s'\n", file);
if(pipe(fd) < 0)
sysfatal("pipe: %r");
pfd = malloc(sizeof(Playfd));
pfd->filename = file; /* mallocated already */
pfd->fd = fd[1];
pfd->cfd = fd[0];
procrfork(pac4dec, pfd, 4096, RFFDG);
close(fd[1]); /* write fd, for pac4dec */
return fd[0]; /* read fd */
}
static void
rtsched(void)
{
int fd;
char *ctl;
ctl = smprint("/proc/%ud/ctl", getpid());
if((fd = open(ctl, ORDWR)) < 0)
sysfatal("%s: %r", ctl);
if(fprint(fd, "period 20ms") < 0)
sysfatal("%s: %r", ctl);
if(fprint(fd, "cost 100µs") < 0)
sysfatal("%s: %r", ctl);
if(fprint(fd, "sporadic") < 0)
sysfatal("%s: %r", ctl);
if(fprint(fd, "admit") < 0)
sysfatal("%s: %r", ctl);
close(fd);
free(ctl);
}
void
pacproc(void*)
{
Pmsg playstate, newstate;
int fd;
Pacbuf *pb;
Alt a[3] = {
{empty, &pb, CHANNOP},
{playc, &newstate.m, CHANRCV},
{nil, nil, CHANEND},
};
threadsetname("pacproc");
close(srvfd[1]);
newstate.cmd = playstate.cmd = Stop;
newstate.off = playstate.off = 0;
fd = -1;
for(;;){
switch(alt(a)){
case 0:
/* Play out next buffer (pb points to one already) */
assert(fd >= 0); /* Because we must be in Play mode */
pb->m = playstate.m;
pb->len = read(fd, pb->data, sizeof pb->data);
if(pb->len > 0){
sendp(full, pb);
break;
}
if(pb->len < 0){
if(debug & DbgPlayer)
fprint(2, "pac, error: %d\n", playstate.off);
pb->cmd = Error;
pb->len = snprint(pb->data, sizeof pb->data, "%s: %r", curfile);
sendp(full, pb);
}else{
/* Simple end of file */
sendp(empty, pb); /* Don't need buffer after all */
}
close(fd);
fd = -1;
if(debug & DbgPlayer)
fprint(2, "pac, eof: %d\n", playstate.off);
/* End of file, do next by falling through */
newstate.cmd = playstate.cmd;
newstate.off = playstate.off + 1;
case 1:
if((debug & DbgPac) && newstate.cmd)
fprint(2, "Pacproc: newstate %s-%d, playstate %s-%d\n",
statetxt[newstate.cmd], newstate.off,
statetxt[playstate.cmd], playstate.off);
/* Deal with an incoming command */
if(newstate.cmd == Pause || newstate.cmd == Resume){
/* Just pass them on, don't change local state */
pb = recvp(spare);
pb->m = newstate.m;
sendp(full, pb);
break;
}
/* Stop whatever we're doing */
if(fd >= 0){
if(debug & DbgPlayer)
fprint(2, "pac, stop\n");
/* Stop any active (pac) decoders */
close(fd);
fd = -1;
}
a[0].op = CHANNOP;
switch(newstate.cmd){
default:
sysfatal("pacproc: unexpected newstate %d", newstate.cmd);
case Stop:
/* Wait for state to change */
break;
case Skip:
case Play:
fd = startplay(newstate.off);
if(fd >=0){
playstate = newstate;
a[0].op = CHANRCV;
continue; /* Start reading */
}
newstate.cmd = Stop;
}
pb = recvp(spare);
pb->m = newstate.m;
sendp(full, pb);
playstate = newstate;
}
}
}
void
pcmproc(void*)
{
Pmsg localstate, newstate, prevstate;
int fd, n;
Pacbuf *pb, *b;
Alt a[3] = {
{full, &pb, CHANRCV},
{playout, &pb, CHANRCV},
{nil, nil, CHANEND},
};
/*
* This is the real-time proc.
* It gets its input from two sources, full data/control buffers from the pacproc
* which mixes decoded data with control messages, and data buffers from the pcmproc's
* (*this* proc's) own internal playout buffer.
* When a command is received on the `full' channel containing a command that warrants
* an immediate change of audio source (e.g., to silence or to another number), we just
* toss everything in the pipeline -- i.e., the playout channel
* Finally, we report all state changes using `playupdate' (another message channel)
*/
threadsetname("pcmproc");
close(srvfd[1]);
fd = -1;
localstate.cmd = 0; /* Force initial playupdate */
newstate.cmd = Stop;
newstate.off = 0;
// rtsched();
for(;;){
if(newstate.m != localstate.m){
playupdate(newstate, nil);
localstate = newstate;
}
switch(alt(a)){
case 0:
/* buffer received from pacproc */
if((debug & DbgPcm) && localstate.m != prevstate.m){
fprint(2, "pcm, full: %s-%d, local state is %s-%d\n",
statetxt[pb->cmd], pb->off,
statetxt[localstate.cmd], localstate.off);
prevstate.m = localstate.m;
}
switch(pb->cmd){
default:
sysfatal("pcmproc: unknown newstate: %s-%d", statetxt[pb->cmd], pb->off);
case Resume:
a[1].op = CHANRCV;
newstate.cmd = Play;
break;
case Pause:
a[1].op = CHANNOP;
newstate.cmd = Pause;
if(fd >= 0){
close(fd);
fd = -1;
}
break;
case Stop:
/* Dump all data in the buffer */
while(b = nbrecvp(playout))
if(b->cmd == Error){
playupdate(b->Pmsg, b->data);
sendp(spare, b);
}else
sendp(empty, b);
newstate.m = pb->m;
a[1].op = CHANRCV;
if(fd >= 0){
close(fd);
fd = -1;
}
break;
case Skip:
/* Dump all data in the buffer, then fall through */
while(b = nbrecvp(playout))
if(b->cmd == Error){
playupdate(pb->Pmsg, pb->data);
sendp(spare, pb);
}else
sendp(empty, b);
a[1].op = CHANRCV;
newstate.cmd = Play;
case Error:
case Play:
/* deal with at playout, just requeue */
sendp(playout, pb);
pb = nil;
localstate = newstate;
break;
}
/* If we still have a buffer, free it */
if(pb)
sendp(spare, pb);
break;
case 1:
/* internal buffer */
if((debug & DbgPlayer) && localstate.m != prevstate.m){
fprint(2, "pcm, playout: %s-%d, local state is %s-%d\n",
statetxt[pb->cmd], pb->off,
statetxt[localstate.cmd], localstate.off);
prevstate.m = localstate.m;
}
switch(pb->cmd){
default:
sysfatal("pcmproc: unknown newstate: %s-%d", statetxt[pb->cmd], pb->off);
case Error:
playupdate(pb->Pmsg, pb->data);
localstate = newstate;
sendp(spare, pb);
break;
case Play:
if(fd < 0 && (fd = open("/dev/audio", OWRITE)) < 0){
a[1].op = CHANNOP;
newstate.cmd = Pause;
pb->cmd = Error;
snprint(pb->data, sizeof(pb->data),
"/dev/audio: %r");
playupdate(pb->Pmsg, pb->data);
sendp(empty, pb);
break;
}
/* play out this buffer */
totbytes += pb->len;
totbuffers++;
n = write(fd, pb->data, pb->len);
if (n != pb->len){
if (debug & DbgPlayer)
fprint(2, "pcmproc: file %d: %r\n", pb->off);
if (n < 0)
sysfatal("pcmproc: write: %r");
}
newstate.m = pb->m;
sendp(empty, pb);
break;
}
break;
}
}
}
void
playinit(void)
{
int i;
Pmsg pmsg;
full = chancreate(sizeof(Pacbuf*), 1);
empty = chancreate(sizeof(Pacbuf*), NPacbuf);
spare = chancreate(sizeof(Pacbuf*), NSparebuf);
playout = chancreate(sizeof(Pacbuf*), NPacbuf+NSparebuf);
for(i = 0; i < NPacbuf; i++)
sendp(empty, malloc(sizeof(Pacbuf)));
for(i = 0; i < NSparebuf; i++)
sendp(spare, malloc(sizeof(Pacbuf)));
playc = chancreate(sizeof(pmsg.m), 1);
procrfork(pacproc, nil, 32*1024, RFFDG);
procrfork(pcmproc, nil, 32*1024, RFFDG);
}
char *
getplaystat(char *p, char *e)
{
p = seprint(p, e, "empty buffers %d of %d\n", empty->n, empty->s);
p = seprint(p, e, "full buffers %d of %d\n", full->n, full->s);
p = seprint(p, e, "playout buffers %d of %d\n", playout->n, playout->s);
p = seprint(p, e, "spare buffers %d of %d\n", spare->n, spare->s);
p = seprint(p, e, "bytes %lud / buffers %lud played\n", totbytes, totbuffers);
return p;
}
|