1 #include <u.h>
2 #include <libc.h>
3 #include <thread.h>
4 #include <fcall.h>
5 #include "pool.h"
6 #include "playlist.h"
7
8 enum {
9 Pac,
10 Mp3,
11 Pcm,
12 Ogg,
13 };
14
15 typedef struct Playfd Playfd;
16
17 struct Playfd {
18 /* Describes a file to play for starting up pac4dec/mp3,... */
19 char *filename; /* mallocated */
20 int fd; /* filedesc to use */
21 int cfd; /* fildesc to close */
22 };
23
24 Channel *full, *empty, *playout, *spare;
25 Channel *playc, *pacc;
26
27 char *playprog[] = {
28 [Pac] = "/bin/games/pac4dec",
29 [Mp3] = "/bin/games/mp3dec",
30 [Pcm] = "/bin/cp",
31 [Ogg] = "/bin/games/vorbisdec",
32 };
33
34 ulong totbytes, totbuffers;
35
36 static char curfile[8192];
37
38 void
pac4dec(void * a)39 pac4dec(void *a)
40 {
41 Playfd *pfd;
42 Pacbuf *pb;
43 int fd, type;
44 char *ext, buf[256];
45 static char args[6][32];
46 char *argv[6] = {args[0], args[1], args[2], args[3], args[4], args[5]};
47
48 threadsetname("pac4dec");
49 pfd = a;
50 close(pfd->cfd); /* read fd */
51 ext = strrchr(pfd->filename, '.');
52 fd = open(pfd->filename, OREAD);
53 if (fd < 0 && ext == nil){
54 // Try the alternatives
55 ext = buf + strlen(pfd->filename);
56 snprint(buf, sizeof buf, "%s.pac", pfd->filename);
57 fd = open(buf, OREAD);
58 if (fd < 0){
59 snprint(buf, sizeof buf, "%s.mp3", pfd->filename);
60 fd = open(buf, OREAD);
61 }
62 if (fd < 0){
63 snprint(buf, sizeof buf, "%s.ogg", pfd->filename);
64 fd = open(buf, OREAD);
65 }
66 if (fd < 0){
67 snprint(buf, sizeof buf, "%s.pcm", pfd->filename);
68 fd = open(buf, OREAD);
69 }
70 }
71 if (fd < 0){
72 if (debug & DbgPlayer)
73 fprint(2, "pac4dec: %s: %r", pfd->filename);
74 pb = nbrecvp(spare);
75 pb->cmd = Error;
76 pb->off = 0;
77 pb->len = snprint(pb->data, sizeof(pb->data), "startplay: %s: %r", pfd->filename);
78 sendp(full, pb);
79 threadexits("open");
80 }
81 dup(pfd->fd, 1);
82 close(pfd->fd);
83 if(ext == nil || strcmp(ext, ".pac") == 0){
84 type = Pac;
85 snprint(args[0], sizeof args[0], "pac4dec");
86 snprint(args[1], sizeof args[1], "/fd/%d", fd);
87 snprint(args[2], sizeof args[2], "/fd/1");
88 argv[3] = nil;
89 }else if(strcmp(ext, ".mp3") == 0){
90 type = Mp3;
91 snprint(args[0], sizeof args[0], "mp3dec");
92 snprint(args[1], sizeof args[1], "-q");
93 snprint(args[2], sizeof args[1], "-s");
94 snprint(args[3], sizeof args[1], "/fd/%d", fd);
95 argv[4] = nil;
96 }else if(strcmp(ext, ".ogg") == 0){
97 type = Ogg;
98 snprint(args[0], sizeof args[0], "vorbisdec");
99 argv[1] = nil;
100 argv[2] = nil;
101 argv[3] = nil;
102 dup(fd, 0);
103 }else{
104 type = Pcm;
105 snprint(args[0], sizeof args[0], "cat");
106 snprint(args[1], sizeof args[1], "/fd/%d", fd);
107 argv[2] = nil;
108 argv[3] = nil;
109 }
110 free(pfd->filename);
111 free(pfd);
112 if (debug & DbgPlayer)
113 fprint(2, "procexecl %s %s %s %s\n",
114 playprog[type], argv[0], argv[1], argv[2]);
115 procexec(nil, playprog[type], argv);
116 if((pb = nbrecvp(spare)) == nil)
117 pb = malloc(sizeof(Pacbuf));
118 pb->cmd = Error;
119 pb->off = 0;
120 pb->len = snprint(pb->data, sizeof(pb->data), "startplay: %s: exec", playprog[type]);
121 sendp(full, pb);
122 threadexits(playprog[type]);
123 }
124
125 static int
startplay(ushort n)126 startplay(ushort n)
127 {
128 int fd[2];
129 Playfd *pfd;
130 char *file;
131
132 file = getplaylist(n);
133 if(file == nil)
134 return Undef;
135 if (debug & DbgPlayer)
136 fprint(2, "startplay: file is `%s'\n", file);
137 if(pipe(fd) < 0)
138 sysfatal("pipe: %r");
139 pfd = malloc(sizeof(Playfd));
140 pfd->filename = file; /* mallocated already */
141 pfd->fd = fd[1];
142 pfd->cfd = fd[0];
143 procrfork(pac4dec, pfd, STACKSIZE, RFFDG);
144 close(fd[1]); /* write fd, for pac4dec */
145 return fd[0]; /* read fd */
146 }
147
148 static void
rtsched(void)149 rtsched(void)
150 {
151 int fd;
152 char *ctl;
153
154 ctl = smprint("/proc/%ud/ctl", getpid());
155 if((fd = open(ctl, ORDWR)) < 0)
156 sysfatal("%s: %r", ctl);
157 if(fprint(fd, "period 20ms") < 0)
158 sysfatal("%s: %r", ctl);
159 if(fprint(fd, "cost 100µs") < 0)
160 sysfatal("%s: %r", ctl);
161 if(fprint(fd, "sporadic") < 0)
162 sysfatal("%s: %r", ctl);
163 if(fprint(fd, "admit") < 0)
164 sysfatal("%s: %r", ctl);
165 close(fd);
166 free(ctl);
167 }
168
169 void
pacproc(void *)170 pacproc(void*)
171 {
172 Pmsg playstate, newstate;
173 int fd;
174 Pacbuf *pb;
175 Alt a[3] = {
176 {empty, &pb, CHANNOP},
177 {playc, &newstate.m, CHANRCV},
178 {nil, nil, CHANEND},
179 };
180
181 threadsetname("pacproc");
182 close(srvfd[1]);
183 newstate.cmd = playstate.cmd = Stop;
184 newstate.off = playstate.off = 0;
185 fd = -1;
186 for(;;){
187 switch(alt(a)){
188 case 0:
189 /* Play out next buffer (pb points to one already) */
190 assert(fd >= 0); /* Because we must be in Play mode */
191 pb->m = playstate.m;
192 pb->len = read(fd, pb->data, sizeof pb->data);
193 if(pb->len > 0){
194 sendp(full, pb);
195 break;
196 }
197 if(pb->len < 0){
198 if(debug & DbgPlayer)
199 fprint(2, "pac, error: %d\n", playstate.off);
200 pb->cmd = Error;
201 pb->len = snprint(pb->data, sizeof pb->data, "%s: %r", curfile);
202 sendp(full, pb);
203 }else{
204 /* Simple end of file */
205 sendp(empty, pb); /* Don't need buffer after all */
206 }
207 close(fd);
208 fd = -1;
209 if(debug & DbgPlayer)
210 fprint(2, "pac, eof: %d\n", playstate.off);
211 /* End of file, do next by falling through */
212 newstate.cmd = playstate.cmd;
213 newstate.off = playstate.off + 1;
214 case 1:
215 if((debug & DbgPac) && newstate.cmd)
216 fprint(2, "Pacproc: newstate %s-%d, playstate %s-%d\n",
217 statetxt[newstate.cmd], newstate.off,
218 statetxt[playstate.cmd], playstate.off);
219 /* Deal with an incoming command */
220 if(newstate.cmd == Pause || newstate.cmd == Resume){
221 /* Just pass them on, don't change local state */
222 pb = recvp(spare);
223 pb->m = newstate.m;
224 sendp(full, pb);
225 break;
226 }
227 /* Stop whatever we're doing */
228 if(fd >= 0){
229 if(debug & DbgPlayer)
230 fprint(2, "pac, stop\n");
231 /* Stop any active (pac) decoders */
232 close(fd);
233 fd = -1;
234 }
235 a[0].op = CHANNOP;
236 switch(newstate.cmd){
237 default:
238 sysfatal("pacproc: unexpected newstate %d", newstate.cmd);
239 case Stop:
240 /* Wait for state to change */
241 break;
242 case Skip:
243 case Play:
244 fd = startplay(newstate.off);
245 if(fd >=0){
246 playstate = newstate;
247 a[0].op = CHANRCV;
248 continue; /* Start reading */
249 }
250 newstate.cmd = Stop;
251 }
252 pb = recvp(spare);
253 pb->m = newstate.m;
254 sendp(full, pb);
255 playstate = newstate;
256 }
257 }
258 }
259
260 void
pcmproc(void *)261 pcmproc(void*)
262 {
263 Pmsg localstate, newstate, prevstate;
264 int fd, n;
265 Pacbuf *pb, *b;
266 Alt a[3] = {
267 {full, &pb, CHANRCV},
268 {playout, &pb, CHANRCV},
269 {nil, nil, CHANEND},
270 };
271
272 /*
273 * This is the real-time proc.
274 * It gets its input from two sources, full data/control buffers from the pacproc
275 * which mixes decoded data with control messages, and data buffers from the pcmproc's
276 * (*this* proc's) own internal playout buffer.
277 * When a command is received on the `full' channel containing a command that warrants
278 * an immediate change of audio source (e.g., to silence or to another number), we just
279 * toss everything in the pipeline -- i.e., the playout channel
280 * Finally, we report all state changes using `playupdate' (another message channel)
281 */
282 threadsetname("pcmproc");
283 close(srvfd[1]);
284 fd = -1;
285 localstate.cmd = 0; /* Force initial playupdate */
286 newstate.cmd = Stop;
287 newstate.off = 0;
288 // rtsched();
289 for(;;){
290 if(newstate.m != localstate.m){
291 playupdate(newstate, nil);
292 localstate = newstate;
293 }
294 switch(alt(a)){
295 case 0:
296 /* buffer received from pacproc */
297 if((debug & DbgPcm) && localstate.m != prevstate.m){
298 fprint(2, "pcm, full: %s-%d, local state is %s-%d\n",
299 statetxt[pb->cmd], pb->off,
300 statetxt[localstate.cmd], localstate.off);
301 prevstate.m = localstate.m;
302 }
303 switch(pb->cmd){
304 default:
305 sysfatal("pcmproc: unknown newstate: %s-%d", statetxt[pb->cmd], pb->off);
306 case Resume:
307 a[1].op = CHANRCV;
308 newstate.cmd = Play;
309 break;
310 case Pause:
311 a[1].op = CHANNOP;
312 newstate.cmd = Pause;
313 if(fd >= 0){
314 close(fd);
315 fd = -1;
316 }
317 break;
318 case Stop:
319 /* Dump all data in the buffer */
320 while(b = nbrecvp(playout))
321 if(b->cmd == Error){
322 playupdate(b->Pmsg, b->data);
323 sendp(spare, b);
324 }else
325 sendp(empty, b);
326 newstate.m = pb->m;
327 a[1].op = CHANRCV;
328 if(fd >= 0){
329 close(fd);
330 fd = -1;
331 }
332 break;
333 case Skip:
334 /* Dump all data in the buffer, then fall through */
335 while(b = nbrecvp(playout))
336 if(b->cmd == Error){
337 playupdate(pb->Pmsg, pb->data);
338 sendp(spare, pb);
339 }else
340 sendp(empty, b);
341 a[1].op = CHANRCV;
342 newstate.cmd = Play;
343 case Error:
344 case Play:
345 /* deal with at playout, just requeue */
346 sendp(playout, pb);
347 pb = nil;
348 localstate = newstate;
349 break;
350 }
351 /* If we still have a buffer, free it */
352 if(pb)
353 sendp(spare, pb);
354 break;
355 case 1:
356 /* internal buffer */
357 if((debug & DbgPlayer) && localstate.m != prevstate.m){
358 fprint(2, "pcm, playout: %s-%d, local state is %s-%d\n",
359 statetxt[pb->cmd], pb->off,
360 statetxt[localstate.cmd], localstate.off);
361 prevstate.m = localstate.m;
362 }
363 switch(pb->cmd){
364 default:
365 sysfatal("pcmproc: unknown newstate: %s-%d", statetxt[pb->cmd], pb->off);
366 case Error:
367 playupdate(pb->Pmsg, pb->data);
368 localstate = newstate;
369 sendp(spare, pb);
370 break;
371 case Play:
372 if(fd < 0 && (fd = open("/dev/audio", OWRITE)) < 0){
373 a[1].op = CHANNOP;
374 newstate.cmd = Pause;
375 pb->cmd = Error;
376 snprint(pb->data, sizeof(pb->data),
377 "/dev/audio: %r");
378 playupdate(pb->Pmsg, pb->data);
379 sendp(empty, pb);
380 break;
381 }
382 /* play out this buffer */
383 totbytes += pb->len;
384 totbuffers++;
385 n = write(fd, pb->data, pb->len);
386 if (n != pb->len){
387 if (debug & DbgPlayer)
388 fprint(2, "pcmproc: file %d: %r\n", pb->off);
389 if (n < 0)
390 sysfatal("pcmproc: write: %r");
391 }
392 newstate.m = pb->m;
393 sendp(empty, pb);
394 break;
395 }
396 break;
397 }
398 }
399 }
400
401 void
playinit(void)402 playinit(void)
403 {
404 int i;
405
406 full = chancreate(sizeof(Pacbuf*), 1);
407 empty = chancreate(sizeof(Pacbuf*), NPacbuf);
408 spare = chancreate(sizeof(Pacbuf*), NSparebuf);
409 playout = chancreate(sizeof(Pacbuf*), NPacbuf+NSparebuf);
410 for(i = 0; i < NPacbuf; i++)
411 sendp(empty, malloc(sizeof(Pacbuf)));
412 for(i = 0; i < NSparebuf; i++)
413 sendp(spare, malloc(sizeof(Pacbuf)));
414
415 playc = chancreate(sizeof(Pmsg), 1);
416 procrfork(pacproc, nil, 8*STACKSIZE, RFFDG);
417 procrfork(pcmproc, nil, 8*STACKSIZE, RFFDG);
418 }
419
420 char *
getplaystat(char * p,char * e)421 getplaystat(char *p, char *e)
422 {
423 p = seprint(p, e, "empty buffers %d of %d\n", empty->n, empty->s);
424 p = seprint(p, e, "full buffers %d of %d\n", full->n, full->s);
425 p = seprint(p, e, "playout buffers %d of %d\n", playout->n, playout->s);
426 p = seprint(p, e, "spare buffers %d of %d\n", spare->n, spare->s);
427 p = seprint(p, e, "bytes %lud / buffers %lud played\n", totbytes, totbuffers);
428 return p;
429 }
430