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 void
57 player_init(void)
58 {
59 if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1)) == NULL)
60 fatal("sio_open");
61 }
63 int
64 player_setup(int bits, int rate, int channels)
65 {
66 struct sio_par par;
67 int nfds;
69 log_debug("%s: bits=%d, rate=%d, channels=%d", __func__,
70 bits, rate, channels);
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, POLLIN|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 if (!sio_start(hdl)) {
106 log_warn("sio_start");
107 return -1;
109 stopped = 0;
110 return 0;
113 /* process only one message */
114 int
115 player_dispatch(void)
117 struct pollfd pfd;
118 struct imsg imsg;
119 ssize_t n;
120 int ret;
122 if (halted != 0)
123 return IMSG_STOP;
125 again:
126 if ((n = imsg_get(ibuf, &imsg)) == -1)
127 fatal("imsg_get");
128 if (n == 0) {
129 pfd.fd = ibuf->fd;
130 pfd.events = POLLIN;
131 if (poll(&pfd, 1, INFTIM) == -1)
132 fatal("poll");
133 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
134 fatal("imsg_read");
135 if (n == 0)
136 fatalx("pipe closed");
137 goto again;
140 ret = imsg.hdr.type;
141 switch (imsg.hdr.type) {
142 case IMSG_PLAY:
143 if (nextfd != -1)
144 fatalx("track already enqueued");
145 if ((nextfd = imsg.fd) == -1)
146 fatalx("%s: got invalid file descriptor", __func__);
147 log_debug("song enqueued");
148 ret = IMSG_STOP;
149 break;
150 case IMSG_RESUME:
151 case IMSG_PAUSE:
152 case IMSG_STOP:
153 break;
154 default:
155 fatalx("unknown imsg %d", imsg.hdr.type);
158 imsg_free(&imsg);
159 return ret;
162 void
163 player_senderr(void)
165 imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, NULL, 0);
166 imsg_flush(ibuf);
169 void
170 player_sendeof(void)
172 imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0);
173 imsg_flush(ibuf);
176 int
177 player_playnext(void)
179 static char buf[512];
180 ssize_t r;
181 int fd = nextfd;
183 assert(nextfd != -1);
184 nextfd = -1;
186 r = read(fd, buf, sizeof(buf));
188 /* 8 byte is the larger magic number */
189 if (r < 8) {
190 log_warn("read failed");
191 goto err;
194 if (lseek(fd, 0, SEEK_SET) == -1) {
195 log_warn("lseek failed");
196 goto err;
199 if (memcmp(buf, "fLaC", 4) == 0)
200 return play_flac(fd);
201 if (memcmp(buf, "ID3", 3) == 0 ||
202 memcmp(buf, "\xFF\xFB", 2) == 0)
203 return play_mp3(fd);
204 if (memmem(buf, r, "OpusHead", 8) != NULL)
205 return play_opus(fd);
206 if (memmem(buf, r, "OggS", 4) != NULL)
207 return play_oggvorbis(fd);
209 log_warnx("unknown file type");
210 err:
211 close(fd);
212 return -1;
215 int
216 player_pause(void)
218 int r;
220 r = player_dispatch();
221 return r == IMSG_RESUME;
224 int
225 player_shouldstop(void)
227 switch (player_dispatch()) {
228 case IMSG_PAUSE:
229 if (player_pause())
230 break;
231 /* fallthrough */
232 case IMSG_STOP:
233 return 1;
236 return 0;
239 int
240 play(const void *buf, size_t len)
242 size_t w;
243 int nfds, revents, r;
245 while (len != 0) {
246 nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT);
247 r = poll(player_pfds, nfds + 1, INFTIM);
248 if (r == -1)
249 fatal("poll");
251 if (player_pfds[0].revents & (POLLHUP|POLLIN)) {
252 if (player_shouldstop()) {
253 sio_flush(hdl);
254 stopped = 1;
255 return 0;
259 revents = sio_revents(hdl, player_pfds + 1);
260 if (revents & POLLHUP)
261 fatalx("sndio hang-up");
262 if (revents & POLLOUT) {
263 w = sio_write(hdl, buf, len);
264 len -= w;
265 buf += w;
269 return 1;
272 int
273 player(int debug, int verbose)
275 int r;
277 log_init(debug, LOG_DAEMON);
278 log_setverbose(verbose);
280 setproctitle("player");
281 log_procinit("player");
283 #if 0
285 static int attached;
287 while (!attached)
288 sleep(1);
290 #endif
292 player_init();
294 /* allocate one extra for imsg */
295 player_pfds = calloc(sio_nfds(hdl) + 1, sizeof(*player_pfds));
296 if (player_pfds == NULL)
297 fatal("calloc");
299 player_pfds[0].events = POLLIN;
300 player_pfds[0].fd = 3;
302 ibuf = xmalloc(sizeof(*ibuf));
303 imsg_init(ibuf, 3);
305 signal(SIGINT, player_signal_handler);
306 signal(SIGTERM, player_signal_handler);
308 signal(SIGHUP, SIG_IGN);
309 signal(SIGPIPE, SIG_IGN);
311 if (pledge("stdio recvfd audio", NULL) == -1)
312 fatal("pledge");
314 while (!halted) {
315 while (nextfd == -1)
316 player_dispatch();
318 r = player_playnext();
319 if (r == -1)
320 player_senderr();
321 if (r == 0)
322 player_sendeof();
325 return 0;