/* * 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; static struct imsgbuf *ibuf; static int nextfd = -1; volatile sig_atomic_t halted; void player_signal_handler(int signo) { halted = 1; } void player_init(void) { if ((hdl = sio_open(SIO_DEVANY, SIO_PLAY, 0)) == NULL) fatal("sio_open"); if (!sio_start(hdl)) fatal("sio_start"); } int player_setup(int bits, int rate, int channels) { struct sio_par par; log_debug("%s: bits=%d, rate=%d, channels=%d", __func__, bits, rate, channels); sio_stop(hdl); sio_initpar(&par); par.bits = bits; par.rate = rate; par.pchan = channels; if (!sio_setpar(hdl, &par) || !sio_getpar(hdl, &par)) { log_warnx("invalid params (bits=%d, rate=%d, channels=%d", bits, rate, channels); return -1; } if (par.bits != bits || par.pchan != channels) { log_warnx("failed to set params"); return -1; } /* TODO: check the sample rate? */ if (!sio_start(hdl)) { log_warn("sio_start"); return -1; } return 0; } int player_pendingimsg(void) { struct pollfd pfd; int r; if (halted != 0) return 1; pfd.fd = ibuf->fd; pfd.events = POLLIN; r = poll(&pfd, 1, 0); if (r == -1) fatal("poll"); return r; } /* 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) { if (!player_pendingimsg()) return 0; 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) { if (player_shouldstop()) return 0; sio_write(hdl, buf, len); 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 player_init(); 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; }