Blob


1 /*
2 * Copyright (c) 2022 Omar Polo <op@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include "config.h"
19 #include <limits.h>
21 #include <assert.h>
22 #include <errno.h>
23 #include <poll.h>
24 #include <signal.h>
25 #include <sndio.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdint.h>
29 #include <string.h>
30 #include <syslog.h>
31 #include <unistd.h>
33 #include "amused.h"
34 #include "log.h"
35 #include "xmalloc.h"
37 struct sio_hdl *hdl;
38 struct sio_par par;
39 struct pollfd *player_pfds;
40 static struct imsgbuf *ibuf;
42 static int stopped = 1;
43 static int nextfd = -1;
44 static int64_t samples;
45 static int64_t duration;
47 volatile sig_atomic_t halted;
49 static void
50 player_signal_handler(int signo)
51 {
52 halted = 1;
53 }
55 int
56 player_setup(unsigned int bits, unsigned int rate, unsigned int channels)
57 {
58 int nfds, fpct;
60 log_debug("%s: bits=%d, rate=%d, channels=%d", __func__,
61 bits, rate, channels);
63 fpct = (rate*5)/100;
65 /* don't stop if the parameters are the same */
66 if (bits == par.bits && channels == par.pchan &&
67 par.rate - fpct <= rate && rate <= par.rate + fpct) {
68 if (stopped)
69 goto start;
70 return 0;
71 }
73 again:
74 if (!stopped) {
75 sio_stop(hdl);
76 stopped = 1;
77 }
79 sio_initpar(&par);
80 par.bits = bits;
81 par.rate = rate;
82 par.pchan = channels;
83 if (!sio_setpar(hdl, &par)) {
84 if (errno == EAGAIN) {
85 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
86 if (poll(player_pfds + 1, nfds, INFTIM) == -1)
87 fatal("poll");
88 goto again;
89 }
90 log_warnx("invalid params (bits=%d, rate=%d, channels=%d",
91 bits, rate, channels);
92 return -1;
93 }
94 if (!sio_getpar(hdl, &par)) {
95 log_warnx("can't get params");
96 return -1;
97 }
99 if (par.bits != bits || par.pchan != channels) {
100 log_warnx("failed to set params");
101 return -1;
104 /* TODO: check the sample rate? */
106 start:
107 if (!sio_start(hdl)) {
108 log_warn("sio_start");
109 return -1;
111 stopped = 0;
112 return 0;
115 void
116 player_setduration(int64_t d)
118 int64_t seconds;
120 duration = d;
121 seconds = duration / par.rate;
122 imsg_compose(ibuf, IMSG_LEN, 0, 0, -1, &seconds, sizeof(seconds));
123 imsg_flush(ibuf);
126 static void
127 player_onmove(void *arg, int delta)
129 static int64_t reported;
130 int64_t sec;
132 samples += delta;
133 if (llabs(samples - reported) >= par.rate) {
134 reported = samples;
135 sec = samples / par.rate;
137 imsg_compose(ibuf, IMSG_POS, 0, 0, -1, &sec, sizeof(sec));
138 imsg_flush(ibuf);
142 void
143 player_setpos(int64_t pos)
145 samples = pos;
146 player_onmove(NULL, 0);
149 /* process only one message */
150 static int
151 player_dispatch(int64_t *s, int wait)
153 struct player_seek seek;
154 struct pollfd pfd;
155 struct imsg imsg;
156 ssize_t n;
157 int ret;
159 if (halted != 0)
160 return IMSG_STOP;
162 again:
163 if ((n = imsg_get(ibuf, &imsg)) == -1)
164 fatal("imsg_get");
165 if (n == 0) {
166 if (!wait)
167 return -1;
169 pfd.fd = ibuf->fd;
170 pfd.events = POLLIN;
171 if (poll(&pfd, 1, INFTIM) == -1)
172 fatal("poll");
173 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
174 fatal("imsg_read");
175 if (n == 0)
176 fatalx("pipe closed");
177 goto again;
180 ret = imsg.hdr.type;
181 switch (imsg.hdr.type) {
182 case IMSG_PLAY:
183 if (nextfd != -1)
184 fatalx("track already enqueued");
185 if ((nextfd = imsg.fd) == -1)
186 fatalx("%s: got invalid file descriptor", __func__);
187 log_debug("song enqueued");
188 ret = IMSG_STOP;
189 break;
190 case IMSG_RESUME:
191 case IMSG_PAUSE:
192 case IMSG_STOP:
193 break;
194 case IMSG_CTL_SEEK:
195 if (s == NULL)
196 break;
197 if (IMSG_DATA_SIZE(imsg) != sizeof(seek))
198 fatalx("wrong size for seek ctl");
199 memcpy(&seek, imsg.data, sizeof(seek));
200 if (seek.percent) {
201 *s = (double)seek.offset * (double)duration / 100.0;
202 } else {
203 *s = seek.offset * par.rate;
204 if (seek.relative)
205 *s += samples;
207 if (*s < 0)
208 *s = 0;
209 break;
210 default:
211 fatalx("unknown imsg %d", imsg.hdr.type);
214 imsg_free(&imsg);
215 return ret;
218 static void
219 player_senderr(const char *errstr)
221 size_t len = 0;
223 if (errstr != NULL)
224 len = strlen(errstr) + 1;
226 imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, errstr, len);
227 imsg_flush(ibuf);
230 static void
231 player_sendeof(void)
233 imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0);
234 imsg_flush(ibuf);
237 static int
238 player_playnext(const char **errstr)
240 static char buf[512];
241 ssize_t r;
242 int fd = nextfd;
244 assert(nextfd != -1);
245 nextfd = -1;
247 /* reset samples and set position to zero */
248 samples = 0;
249 imsg_compose(ibuf, IMSG_POS, 0, 0, -1, &samples, sizeof(samples));
250 imsg_flush(ibuf);
252 r = read(fd, buf, sizeof(buf));
254 /* 8 byte is the larger magic number */
255 if (r < 8) {
256 *errstr = "read failed";
257 goto err;
260 if (lseek(fd, 0, SEEK_SET) == -1) {
261 *errstr = "lseek failed";
262 goto err;
265 if (memcmp(buf, "fLaC", 4) == 0)
266 return play_flac(fd, errstr);
267 if (memcmp(buf, "ID3", 3) == 0 ||
268 memcmp(buf, "\xFF\xFB", 2) == 0)
269 return play_mp3(fd, errstr);
270 if (memmem(buf, r, "OpusHead", 8) != NULL)
271 return play_opus(fd, errstr);
272 if (memmem(buf, r, "OggS", 4) != NULL)
273 return play_oggvorbis(fd, errstr);
275 *errstr = "unknown file type";
276 err:
277 close(fd);
278 return -1;
281 static int
282 player_pause(int64_t *s)
284 int r;
286 r = player_dispatch(s, 1);
287 return r == IMSG_RESUME || r == IMSG_CTL_SEEK;
290 static int
291 player_shouldstop(int64_t *s, int wait)
293 switch (player_dispatch(s, wait)) {
294 case IMSG_PAUSE:
295 if (player_pause(s))
296 break;
297 /* fallthrough */
298 case IMSG_STOP:
299 return 1;
302 return 0;
305 int
306 play(const void *buf, size_t len, int64_t *s)
308 size_t w;
309 int nfds, revents, r, wait;
311 *s = -1;
312 while (len != 0) {
313 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
314 r = poll(player_pfds, nfds + 1, INFTIM);
315 if (r == -1)
316 fatal("poll");
318 wait = player_pfds[0].revents & (POLLHUP|POLLIN);
319 if (player_shouldstop(s, wait)) {
320 sio_flush(hdl);
321 stopped = 1;
322 return 0;
325 revents = sio_revents(hdl, player_pfds + 1);
326 if (revents & POLLHUP)
327 fatalx("sndio hang-up");
328 if (revents & POLLOUT) {
329 w = sio_write(hdl, buf, len);
330 len -= w;
331 buf += w;
335 return 1;
338 int
339 player(int debug, int verbose)
341 int r;
343 log_init(debug, LOG_DAEMON);
344 log_setverbose(verbose);
346 setproctitle("player");
347 log_procinit("player");
349 #if 0
351 static int attached;
353 while (!attached)
354 sleep(1);
356 #endif
358 if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1)) == NULL)
359 fatal("sio_open");
361 sio_onmove(hdl, player_onmove, NULL);
363 /* allocate one extra for imsg */
364 player_pfds = calloc(sio_nfds(hdl) + 1, sizeof(*player_pfds));
365 if (player_pfds == NULL)
366 fatal("calloc");
368 player_pfds[0].events = POLLIN;
369 player_pfds[0].fd = 3;
371 ibuf = xmalloc(sizeof(*ibuf));
372 imsg_init(ibuf, 3);
374 signal(SIGINT, player_signal_handler);
375 signal(SIGTERM, player_signal_handler);
377 signal(SIGHUP, SIG_IGN);
378 signal(SIGPIPE, SIG_IGN);
380 if (pledge("stdio recvfd audio", NULL) == -1)
381 fatal("pledge");
383 while (!halted) {
384 const char *errstr = NULL;
386 while (nextfd == -1)
387 player_dispatch(NULL, 1);
389 r = player_playnext(&errstr);
390 if (r == -1)
391 player_senderr(errstr);
392 if (r == 0)
393 player_sendeof();
396 return 0;