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 <sys/types.h>
18 #include <sys/queue.h>
19 #include <sys/uio.h>
21 #include <limits.h>
23 #include <assert.h>
24 #include <errno.h>
25 #include <event.h>
26 #include <poll.h>
27 #include <signal.h>
28 #include <sndio.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <stdint.h>
32 #include <imsg.h>
33 #include <string.h>
34 #include <syslog.h>
35 #include <unistd.h>
37 #include "amused.h"
38 #include "log.h"
39 #include "xmalloc.h"
41 struct sio_hdl *hdl;
42 struct pollfd *player_pfds;
43 static struct imsgbuf *ibuf;
45 static int stopped = 1;
46 static int nextfd = -1;
48 volatile sig_atomic_t halted;
50 void
51 player_signal_handler(int signo)
52 {
53 halted = 1;
54 }
56 int
57 player_setup(int bits, int rate, int channels)
58 {
59 static struct sio_par par;
60 int nfds, fpct;
62 log_debug("%s: bits=%d, rate=%d, channels=%d", __func__,
63 bits, rate, channels);
65 fpct = (rate*5)/100;
67 /* don't stop if the parameters are the same */
68 if (bits == par.bits && channels == par.pchan &&
69 par.rate - fpct <= rate && rate <= par.rate + fpct)
70 goto end;
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 end:
106 if (!sio_start(hdl)) {
107 log_warn("sio_start");
108 return -1;
110 stopped = 0;
111 return 0;
114 /* process only one message */
115 int
116 player_dispatch(void)
118 struct pollfd pfd;
119 struct imsg imsg;
120 ssize_t n;
121 int ret;
123 if (halted != 0)
124 return IMSG_STOP;
126 again:
127 if ((n = imsg_get(ibuf, &imsg)) == -1)
128 fatal("imsg_get");
129 if (n == 0) {
130 pfd.fd = ibuf->fd;
131 pfd.events = POLLIN;
132 if (poll(&pfd, 1, INFTIM) == -1)
133 fatal("poll");
134 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
135 fatal("imsg_read");
136 if (n == 0)
137 fatalx("pipe closed");
138 goto again;
141 ret = imsg.hdr.type;
142 switch (imsg.hdr.type) {
143 case IMSG_PLAY:
144 if (nextfd != -1)
145 fatalx("track already enqueued");
146 if ((nextfd = imsg.fd) == -1)
147 fatalx("%s: got invalid file descriptor", __func__);
148 log_debug("song enqueued");
149 ret = IMSG_STOP;
150 break;
151 case IMSG_RESUME:
152 case IMSG_PAUSE:
153 case IMSG_STOP:
154 break;
155 default:
156 fatalx("unknown imsg %d", imsg.hdr.type);
159 imsg_free(&imsg);
160 return ret;
163 void
164 player_senderr(void)
166 imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, NULL, 0);
167 imsg_flush(ibuf);
170 void
171 player_sendeof(void)
173 imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0);
174 imsg_flush(ibuf);
177 int
178 player_playnext(void)
180 static char buf[512];
181 ssize_t r;
182 int fd = nextfd;
184 assert(nextfd != -1);
185 nextfd = -1;
187 r = read(fd, buf, sizeof(buf));
189 /* 8 byte is the larger magic number */
190 if (r < 8) {
191 log_warn("read failed");
192 goto err;
195 if (lseek(fd, 0, SEEK_SET) == -1) {
196 log_warn("lseek failed");
197 goto err;
200 if (memcmp(buf, "fLaC", 4) == 0)
201 return play_flac(fd);
202 if (memcmp(buf, "ID3", 3) == 0 ||
203 memcmp(buf, "\xFF\xFB", 2) == 0)
204 return play_mp3(fd);
205 if (memmem(buf, r, "OpusHead", 8) != NULL)
206 return play_opus(fd);
207 if (memmem(buf, r, "OggS", 4) != NULL)
208 return play_oggvorbis(fd);
210 log_warnx("unknown file type");
211 err:
212 close(fd);
213 return -1;
216 int
217 player_pause(void)
219 int r;
221 r = player_dispatch();
222 return r == IMSG_RESUME;
225 int
226 player_shouldstop(void)
228 switch (player_dispatch()) {
229 case IMSG_PAUSE:
230 if (player_pause())
231 break;
232 /* fallthrough */
233 case IMSG_STOP:
234 return 1;
237 return 0;
240 int
241 play(const void *buf, size_t len)
243 size_t w;
244 int nfds, revents, r;
246 while (len != 0) {
247 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
248 r = poll(player_pfds, nfds + 1, INFTIM);
249 if (r == -1)
250 fatal("poll");
252 if (player_pfds[0].revents & (POLLHUP|POLLIN)) {
253 if (player_shouldstop()) {
254 sio_flush(hdl);
255 stopped = 1;
256 return 0;
260 revents = sio_revents(hdl, player_pfds + 1);
261 if (revents & POLLHUP)
262 fatalx("sndio hang-up");
263 if (revents & POLLOUT) {
264 w = sio_write(hdl, buf, len);
265 len -= w;
266 buf += w;
270 return 1;
273 int
274 player(int debug, int verbose)
276 int r;
278 log_init(debug, LOG_DAEMON);
279 log_setverbose(verbose);
281 setproctitle("player");
282 log_procinit("player");
284 #if 0
286 static int attached;
288 while (!attached)
289 sleep(1);
291 #endif
293 if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1)) == NULL)
294 fatal("sio_open");
296 /* allocate one extra for imsg */
297 player_pfds = calloc(sio_nfds(hdl) + 1, sizeof(*player_pfds));
298 if (player_pfds == NULL)
299 fatal("calloc");
301 player_pfds[0].events = POLLIN;
302 player_pfds[0].fd = 3;
304 ibuf = xmalloc(sizeof(*ibuf));
305 imsg_init(ibuf, 3);
307 signal(SIGINT, player_signal_handler);
308 signal(SIGTERM, player_signal_handler);
310 signal(SIGHUP, SIG_IGN);
311 signal(SIGPIPE, SIG_IGN);
313 if (pledge("stdio recvfd audio", NULL) == -1)
314 fatal("pledge");
316 while (!halted) {
317 while (nextfd == -1)
318 player_dispatch();
320 r = player_playnext();
321 if (r == -1)
322 player_senderr();
323 if (r == 0)
324 player_sendeof();
327 return 0;