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 if (stopped)
71 goto start;
72 return 0;
73 }
75 again:
76 if (!stopped) {
77 sio_stop(hdl);
78 stopped = 1;
79 }
81 sio_initpar(&par);
82 par.bits = bits;
83 par.rate = rate;
84 par.pchan = channels;
85 if (!sio_setpar(hdl, &par)) {
86 if (errno == EAGAIN) {
87 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
88 if (poll(player_pfds + 1, nfds, INFTIM) == -1)
89 fatal("poll");
90 goto again;
91 }
92 log_warnx("invalid params (bits=%d, rate=%d, channels=%d",
93 bits, rate, channels);
94 return -1;
95 }
96 if (!sio_getpar(hdl, &par)) {
97 log_warnx("can't get params");
98 return -1;
99 }
101 if (par.bits != bits || par.pchan != channels) {
102 log_warnx("failed to set params");
103 return -1;
106 /* TODO: check the sample rate? */
108 start:
109 if (!sio_start(hdl)) {
110 log_warn("sio_start");
111 return -1;
113 stopped = 0;
114 return 0;
117 /* process only one message */
118 int
119 player_dispatch(void)
121 struct pollfd pfd;
122 struct imsg imsg;
123 ssize_t n;
124 int ret;
126 if (halted != 0)
127 return IMSG_STOP;
129 again:
130 if ((n = imsg_get(ibuf, &imsg)) == -1)
131 fatal("imsg_get");
132 if (n == 0) {
133 pfd.fd = ibuf->fd;
134 pfd.events = POLLIN;
135 if (poll(&pfd, 1, INFTIM) == -1)
136 fatal("poll");
137 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
138 fatal("imsg_read");
139 if (n == 0)
140 fatalx("pipe closed");
141 goto again;
144 ret = imsg.hdr.type;
145 switch (imsg.hdr.type) {
146 case IMSG_PLAY:
147 if (nextfd != -1)
148 fatalx("track already enqueued");
149 if ((nextfd = imsg.fd) == -1)
150 fatalx("%s: got invalid file descriptor", __func__);
151 log_debug("song enqueued");
152 ret = IMSG_STOP;
153 break;
154 case IMSG_RESUME:
155 case IMSG_PAUSE:
156 case IMSG_STOP:
157 break;
158 default:
159 fatalx("unknown imsg %d", imsg.hdr.type);
162 imsg_free(&imsg);
163 return ret;
166 void
167 player_senderr(void)
169 imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, NULL, 0);
170 imsg_flush(ibuf);
173 void
174 player_sendeof(void)
176 imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0);
177 imsg_flush(ibuf);
180 int
181 player_playnext(void)
183 static char buf[512];
184 ssize_t r;
185 int fd = nextfd;
187 assert(nextfd != -1);
188 nextfd = -1;
190 r = read(fd, buf, sizeof(buf));
192 /* 8 byte is the larger magic number */
193 if (r < 8) {
194 log_warn("read failed");
195 goto err;
198 if (lseek(fd, 0, SEEK_SET) == -1) {
199 log_warn("lseek failed");
200 goto err;
203 if (memcmp(buf, "fLaC", 4) == 0)
204 return play_flac(fd);
205 if (memcmp(buf, "ID3", 3) == 0 ||
206 memcmp(buf, "\xFF\xFB", 2) == 0)
207 return play_mp3(fd);
208 if (memmem(buf, r, "OpusHead", 8) != NULL)
209 return play_opus(fd);
210 if (memmem(buf, r, "OggS", 4) != NULL)
211 return play_oggvorbis(fd);
213 log_warnx("unknown file type");
214 err:
215 close(fd);
216 return -1;
219 int
220 player_pause(void)
222 int r;
224 r = player_dispatch();
225 return r == IMSG_RESUME;
228 int
229 player_shouldstop(void)
231 switch (player_dispatch()) {
232 case IMSG_PAUSE:
233 if (player_pause())
234 break;
235 /* fallthrough */
236 case IMSG_STOP:
237 return 1;
240 return 0;
243 int
244 play(const void *buf, size_t len)
246 size_t w;
247 int nfds, revents, r;
249 while (len != 0) {
250 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
251 r = poll(player_pfds, nfds + 1, INFTIM);
252 if (r == -1)
253 fatal("poll");
255 if (player_pfds[0].revents & (POLLHUP|POLLIN)) {
256 if (player_shouldstop()) {
257 sio_flush(hdl);
258 stopped = 1;
259 return 0;
263 revents = sio_revents(hdl, player_pfds + 1);
264 if (revents & POLLHUP)
265 fatalx("sndio hang-up");
266 if (revents & POLLOUT) {
267 w = sio_write(hdl, buf, len);
268 len -= w;
269 buf += w;
273 return 1;
276 int
277 player(int debug, int verbose)
279 int r;
281 log_init(debug, LOG_DAEMON);
282 log_setverbose(verbose);
284 setproctitle("player");
285 log_procinit("player");
287 #if 0
289 static int attached;
291 while (!attached)
292 sleep(1);
294 #endif
296 if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1)) == NULL)
297 fatal("sio_open");
299 /* allocate one extra for imsg */
300 player_pfds = calloc(sio_nfds(hdl) + 1, sizeof(*player_pfds));
301 if (player_pfds == NULL)
302 fatal("calloc");
304 player_pfds[0].events = POLLIN;
305 player_pfds[0].fd = 3;
307 ibuf = xmalloc(sizeof(*ibuf));
308 imsg_init(ibuf, 3);
310 signal(SIGINT, player_signal_handler);
311 signal(SIGTERM, player_signal_handler);
313 signal(SIGHUP, SIG_IGN);
314 signal(SIGPIPE, SIG_IGN);
316 if (pledge("stdio recvfd audio", NULL) == -1)
317 fatal("pledge");
319 while (!halted) {
320 while (nextfd == -1)
321 player_dispatch();
323 r = player_playnext();
324 if (r == -1)
325 player_senderr();
326 if (r == 0)
327 player_sendeof();
330 return 0;