/* * Copyright (c) 2022 Omar Polo * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "amused.h" #include "log.h" #include "xmalloc.h" struct sio_hdl *hdl; struct pollfd *player_pfds; static struct imsgbuf *ibuf; static int stopped = 1; static int nextfd = -1; volatile sig_atomic_t halted; void player_signal_handler(int signo) { halted = 1; } int player_setup(int bits, int rate, int channels) { static struct sio_par par; int nfds, fpct; log_debug("%s: bits=%d, rate=%d, channels=%d", __func__, bits, rate, channels); fpct = (rate*5)/100; /* don't stop if the parameters are the same */ if (bits == par.bits && channels == par.pchan && par.rate - fpct <= rate && rate <= par.rate + fpct) { if (stopped) goto start; return 0; } again: if (!stopped) { sio_stop(hdl); stopped = 1; } sio_initpar(&par); par.bits = bits; par.rate = rate; par.pchan = channels; if (!sio_setpar(hdl, &par)) { if (errno == EAGAIN) { nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT); if (poll(player_pfds + 1, nfds, INFTIM) == -1) fatal("poll"); goto again; } log_warnx("invalid params (bits=%d, rate=%d, channels=%d", bits, rate, channels); return -1; } if (!sio_getpar(hdl, &par)) { log_warnx("can't get params"); return -1; } if (par.bits != bits || par.pchan != channels) { log_warnx("failed to set params"); return -1; } /* TODO: check the sample rate? */ start: if (!sio_start(hdl)) { log_warn("sio_start"); return -1; } stopped = 0; return 0; } /* process only one message */ int player_dispatch(void) { struct pollfd pfd; struct imsg imsg; ssize_t n; int ret; if (halted != 0) return IMSG_STOP; again: if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("imsg_get"); if (n == 0) { pfd.fd = ibuf->fd; pfd.events = POLLIN; if (poll(&pfd, 1, INFTIM) == -1) fatal("poll"); if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read"); if (n == 0) fatalx("pipe closed"); goto again; } ret = imsg.hdr.type; switch (imsg.hdr.type) { case IMSG_PLAY: if (nextfd != -1) fatalx("track already enqueued"); if ((nextfd = imsg.fd) == -1) fatalx("%s: got invalid file descriptor", __func__); log_debug("song enqueued"); ret = IMSG_STOP; break; case IMSG_RESUME: case IMSG_PAUSE: case IMSG_STOP: break; default: fatalx("unknown imsg %d", imsg.hdr.type); } imsg_free(&imsg); return ret; } void player_senderr(void) { imsg_compose(ibuf, IMSG_ERR, 0, 0, -1, NULL, 0); imsg_flush(ibuf); } void player_sendeof(void) { imsg_compose(ibuf, IMSG_EOF, 0, 0, -1, NULL, 0); imsg_flush(ibuf); } int player_playnext(void) { static char buf[512]; ssize_t r; int fd = nextfd; assert(nextfd != -1); nextfd = -1; r = read(fd, buf, sizeof(buf)); /* 8 byte is the larger magic number */ if (r < 8) { log_warn("read failed"); goto err; } if (lseek(fd, 0, SEEK_SET) == -1) { log_warn("lseek failed"); goto err; } if (memcmp(buf, "fLaC", 4) == 0) return play_flac(fd); if (memcmp(buf, "ID3", 3) == 0 || memcmp(buf, "\xFF\xFB", 2) == 0) return play_mp3(fd); if (memmem(buf, r, "OpusHead", 8) != NULL) return play_opus(fd); if (memmem(buf, r, "OggS", 4) != NULL) return play_oggvorbis(fd); log_warnx("unknown file type"); err: close(fd); return -1; } int player_pause(void) { int r; r = player_dispatch(); return r == IMSG_RESUME; } int player_shouldstop(void) { switch (player_dispatch()) { case IMSG_PAUSE: if (player_pause()) break; /* fallthrough */ case IMSG_STOP: return 1; } return 0; } int play(const void *buf, size_t len) { size_t w; int nfds, revents, r; while (len != 0) { nfds = sio_pollfd(hdl, player_pfds + 1, POLLOUT); r = poll(player_pfds, nfds + 1, INFTIM); if (r == -1) fatal("poll"); if (player_pfds[0].revents & (POLLHUP|POLLIN)) { if (player_shouldstop()) { sio_flush(hdl); stopped = 1; return 0; } } revents = sio_revents(hdl, player_pfds + 1); if (revents & POLLHUP) fatalx("sndio hang-up"); if (revents & POLLOUT) { w = sio_write(hdl, buf, len); len -= w; buf += w; } } return 1; } int player(int debug, int verbose) { int r; log_init(debug, LOG_DAEMON); log_setverbose(verbose); setproctitle("player"); log_procinit("player"); #if 0 { static int attached; while (!attached) sleep(1); } #endif if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 1)) == NULL) fatal("sio_open"); /* allocate one extra for imsg */ player_pfds = calloc(sio_nfds(hdl) + 1, sizeof(*player_pfds)); if (player_pfds == NULL) fatal("calloc"); player_pfds[0].events = POLLIN; player_pfds[0].fd = 3; ibuf = xmalloc(sizeof(*ibuf)); imsg_init(ibuf, 3); signal(SIGINT, player_signal_handler); signal(SIGTERM, player_signal_handler); signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); if (pledge("stdio recvfd audio", NULL) == -1) fatal("pledge"); while (!halted) { while (nextfd == -1) player_dispatch(); r = player_playnext(); if (r == -1) player_senderr(); if (r == 0) player_sendeof(); } return 0; }