xref: /plan9/sys/src/games/music/playlistfs/player.c (revision 6ca6a3e703ee2ec4aed99c2177f71d7f127da6d9)
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