123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429 |
- #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/cp",
- [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;
- 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), 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;
- }
|