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;
46 volatile sig_atomic_t halted;
48 static void
49 player_signal_handler(int signo)
50 {
51 halted = 1;
52 }
54 int
55 player_setup(unsigned int bits, unsigned int rate, unsigned int channels)
56 {
57 int nfds, fpct;
59 log_debug("%s: bits=%d, rate=%d, channels=%d", __func__,
60 bits, rate, channels);
62 fpct = (rate*5)/100;
64 /* don't stop if the parameters are the same */
65 if (bits == par.bits && channels == par.pchan &&
66 par.rate - fpct <= rate && rate <= par.rate + fpct) {
67 if (stopped)
68 goto start;
69 return 0;
70 }
72 again:
73 if (!stopped) {
74 sio_stop(hdl);
75 stopped = 1;
76 }
78 sio_initpar(&par);
79 par.bits = bits;
80 par.rate = rate;
81 par.pchan = channels;
82 if (!sio_setpar(hdl, &par)) {
83 if (errno == EAGAIN) {
84 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
85 if (poll(player_pfds + 1, nfds, INFTIM) == -1)
86 fatal("poll");
87 goto again;
88 }
89 log_warnx("invalid params (bits=%d, rate=%d, channels=%d",
90 bits, rate, channels);
91 return -1;
92 }
93 if (!sio_getpar(hdl, &par)) {
94 log_warnx("can't get params");
95 return -1;
96 }
98 if (par.bits != bits || par.pchan != channels) {
99 log_warnx("failed to set params");
100 return -1;
103 /* TODO: check the sample rate? */
105 start:
106 if (!sio_start(hdl)) {
107 log_warn("sio_start");
108 return -1;
110 stopped = 0;
111 return 0;
114 void
115 player_setduration(int64_t duration)
117 int64_t seconds;
119 seconds = duration / par.rate;
120 imsg_compose(ibuf, IMSG_LEN, 0, 0, -1, &seconds, sizeof(seconds));
121 imsg_flush(ibuf);
124 static void
125 player_onmove(void *arg, int delta)
127 static int64_t reported;
128 int64_t sec;
130 samples += delta;
131 if (llabs(samples - reported) >= par.rate) {
132 reported = samples;
133 sec = samples / par.rate;
135 imsg_compose(ibuf, IMSG_POS, 0, 0, -1, &sec, sizeof(sec));
136 imsg_flush(ibuf);
140 void
141 player_setpos(int64_t pos)
143 samples = pos;
144 player_onmove(NULL, 0);
147 /* process only one message */
148 static int
149 player_dispatch(int64_t *s, int wait)
151 struct player_seek seek;
152 struct pollfd pfd;
153 struct imsg imsg;
154 ssize_t n;
155 int ret;
157 if (halted != 0)
158 return IMSG_STOP;
160 again:
161 if ((n = imsg_get(ibuf, &imsg)) == -1)
162 fatal("imsg_get");
163 if (n == 0) {
164 if (!wait)
165 return -1;
167 pfd.fd = ibuf->fd;
168 pfd.events = POLLIN;
169 if (poll(&pfd, 1, INFTIM) == -1)
170 fatal("poll");
171 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
172 fatal("imsg_read");
173 if (n == 0)
174 fatalx("pipe closed");
175 goto again;
178 ret = imsg.hdr.type;
179 switch (imsg.hdr.type) {
180 case IMSG_PLAY:
181 if (nextfd != -1)
182 fatalx("track already enqueued");
183 if ((nextfd = imsg.fd) == -1)
184 fatalx("%s: got invalid file descriptor", __func__);
185 log_debug("song enqueued");
186 ret = IMSG_STOP;
187 break;
188 case IMSG_RESUME:
189 case IMSG_PAUSE:
190 case IMSG_STOP:
191 break;
192 case IMSG_CTL_SEEK:
193 if (s == NULL)
194 break;
195 if (IMSG_DATA_SIZE(imsg) != sizeof(seek))
196 fatalx("wrong size for seek ctl");
197 memcpy(&seek, imsg.data, sizeof(seek));
198 *s = seek.offset * par.rate;
199 if (seek.relative)
200 *s += samples;
201 if (*s < 0)
202 *s = -1;
203 break;
204 default:
205 fatalx("unknown imsg %d", imsg.hdr.type);
208 imsg_free(&imsg);
209 return ret;
212 static void
213 player_senderr(const char *errstr)
215 size_t len = 0;
217 if (errstr != NULL)
218 len = strlen(errstr) + 1;
220 imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, errstr, len);
221 imsg_flush(ibuf);
224 static void
225 player_sendeof(void)
227 imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0);
228 imsg_flush(ibuf);
231 static int
232 player_playnext(const char **errstr)
234 static char buf[512];
235 ssize_t r;
236 int fd = nextfd;
238 assert(nextfd != -1);
239 nextfd = -1;
241 /* reset samples and set position to zero */
242 samples = 0;
243 imsg_compose(ibuf, IMSG_POS, 0, 0, -1, &samples, sizeof(samples));
244 imsg_flush(ibuf);
246 r = read(fd, buf, sizeof(buf));
248 /* 8 byte is the larger magic number */
249 if (r < 8) {
250 *errstr = "read failed";
251 goto err;
254 if (lseek(fd, 0, SEEK_SET) == -1) {
255 *errstr = "lseek failed";
256 goto err;
259 if (memcmp(buf, "fLaC", 4) == 0)
260 return play_flac(fd, errstr);
261 if (memcmp(buf, "ID3", 3) == 0 ||
262 memcmp(buf, "\xFF\xFB", 2) == 0)
263 return play_mp3(fd, errstr);
264 if (memmem(buf, r, "OpusHead", 8) != NULL)
265 return play_opus(fd, errstr);
266 if (memmem(buf, r, "OggS", 4) != NULL)
267 return play_oggvorbis(fd, errstr);
269 *errstr = "unknown file type";
270 err:
271 close(fd);
272 return -1;
275 static int
276 player_pause(int64_t *s)
278 int r;
280 r = player_dispatch(s, 1);
281 return r == IMSG_RESUME || r == IMSG_CTL_SEEK;
284 static int
285 player_shouldstop(int64_t *s, int wait)
287 switch (player_dispatch(s, wait)) {
288 case IMSG_PAUSE:
289 if (player_pause(s))
290 break;
291 /* fallthrough */
292 case IMSG_STOP:
293 return 1;
296 return 0;
299 int
300 play(const void *buf, size_t len, int64_t *s)
302 size_t w;
303 int nfds, revents, r, wait;
305 *s = -1;
306 while (len != 0) {
307 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
308 r = poll(player_pfds, nfds + 1, INFTIM);
309 if (r == -1)
310 fatal("poll");
312 wait = player_pfds[0].revents & (POLLHUP|POLLIN);
313 if (player_shouldstop(s, wait)) {
314 sio_flush(hdl);
315 stopped = 1;
316 return 0;
319 revents = sio_revents(hdl, player_pfds + 1);
320 if (revents & POLLHUP)
321 fatalx("sndio hang-up");
322 if (revents & POLLOUT) {
323 w = sio_write(hdl, buf, len);
324 len -= w;
325 buf += w;
329 return 1;
332 int
333 player(int debug, int verbose)
335 int r;
337 log_init(debug, LOG_DAEMON);
338 log_setverbose(verbose);
340 setproctitle("player");
341 log_procinit("player");
343 #if 0
345 static int attached;
347 while (!attached)
348 sleep(1);
350 #endif
352 if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1)) == NULL)
353 fatal("sio_open");
355 sio_onmove(hdl, player_onmove, NULL);
357 /* allocate one extra for imsg */
358 player_pfds = calloc(sio_nfds(hdl) + 1, sizeof(*player_pfds));
359 if (player_pfds == NULL)
360 fatal("calloc");
362 player_pfds[0].events = POLLIN;
363 player_pfds[0].fd = 3;
365 ibuf = xmalloc(sizeof(*ibuf));
366 imsg_init(ibuf, 3);
368 signal(SIGINT, player_signal_handler);
369 signal(SIGTERM, player_signal_handler);
371 signal(SIGHUP, SIG_IGN);
372 signal(SIGPIPE, SIG_IGN);
374 if (pledge("stdio recvfd audio", NULL) == -1)
375 fatal("pledge");
377 while (!halted) {
378 const char *errstr = NULL;
380 while (nextfd == -1)
381 player_dispatch(NULL, 1);
383 r = player_playnext(&errstr);
384 if (r == -1)
385 player_senderr(errstr);
386 if (r == 0)
387 player_sendeof();
390 return 0;