sigap

Simplest audio player possible bundled signal based queue manager
Log | Files | Refs | README | LICENSE

commit f17ce23f607e9d1bc3541fc5f5d58ecf58d1caa5
parent b94cec7303c263c9c8d85de3beca7380d0a89ab8
Author: Jacob R. Edwards <jacobouno@protonmail.com>
Date:   Sun, 18 Jul 2021 14:11:15 -0700

Separate apfd() into multiple public functions

With this sigap no longer has to manage another program making it
much simpler. It's also more useful for other programs and as such
it's now installed as a library.

Diffstat:
Map.1 | 2+-
Map.c | 11++++-------
Dapfd.c | 103-------------------------------------------------------------------------------
Dapfd.h | 1-
Dconfig.h | 1-
Ddie.c | 25-------------------------
Ddie.h | 1-
Alibap/ap.c | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alibap/ap.h | 20++++++++++++++++++++
Mmkfile | 36++++++++++++++++++++++--------------
Msigap.1 | 15++++++---------
Msigap.c | 125+++++++++++++++++++++++++++++++------------------------------------------------
12 files changed, 227 insertions(+), 238 deletions(-)

diff --git a/ap.1 b/ap.1 @@ -1,5 +1,5 @@ .\" Copyright 2021 Jacob R. Edwards -.Dd April 8, 2021 +.Dd July 15, 2021 .Dt AP 1 .Os .Sh NAME diff --git a/ap.c b/ap.c @@ -14,18 +14,15 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -#include <ao/ao.h> +#include <ap.h> #include <fcntl.h> #include <stdio.h> -#include "apfd.h" -#include "die.h" - int main(int argc, char *argv[]) { - char *status; + int status; if (argc > 2) { fprintf(stderr, "usage: %s [file]\n", *argv); @@ -33,11 +30,11 @@ main(int argc, char *argv[]) } ao_initialize(); - status = apfd(open(argv[1] ? argv[1] : "/dev/stdin", O_RDONLY)); + status = ap(open(argv[1] ? argv[1] : "/dev/stdin", O_RDONLY)); ao_shutdown(); if (status) { - fprintf(stderr, "%s.\n", status); + perror("ap"); return 1; } return 0; diff --git a/apfd.c b/apfd.c @@ -1,103 +0,0 @@ -/* Copyright 2021 Jacob R. Edwards - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -#include <ao/ao.h> -#include <sndfile.h> - -#include <errno.h> -#include <string.h> - -#include "config.h" - -#ifdef POLL -#include <poll.h> -#endif - -#define errstr (strerror(errno)) - -int -sferrno(SNDFILE *sf) -{ - switch (sf_error(sf)) { - case SF_ERR_MALFORMED_FILE: - case SF_ERR_UNRECOGNISED_FORMAT: - case SF_ERR_UNSUPPORTED_ENCODING: - return EFTYPE; - default: - return 0; - } -} - -int -aosf(SNDFILE *sf, ao_device *ao) -{ - sf_count_t len; - short buf[1024]; - - while ((len = sf_read_short(sf, buf, sizeof(buf) / sizeof(*buf))) > 0) { - if (!ao_play(ao, (char *)buf, len * sizeof(*buf))) - return 1; - } - - return len != 0; -} - -char * -apfd(int fd) -{ - SF_INFO sfinfo; - SNDFILE *sf; - ao_device *dev; - ao_sample_format aofmt; - char *status; -#ifdef POLL - struct pollfd pfd; - - if (fd < 0) - return strerror(EBADF); - - pfd.fd = fd; - pfd.events = POLLIN; - if (poll(&pfd, 1, INFTIM) == -1) - return errstr; -#endif - - sf = sf_open_fd(fd, SFM_READ, &sfinfo, 0); - if (sf == NULL) - return strerror(sferrno(NULL)); - - memset(&aofmt, 0, sizeof(aofmt)); - aofmt.bits = sizeof(short) * 8; - aofmt.byte_format = AO_FMT_NATIVE; - aofmt.channels = sfinfo.channels; - aofmt.rate = sfinfo.samplerate; - - dev = ao_open_live(0, &aofmt, NULL); - if (dev == NULL) { - sf_close(sf); - return "unable to open audio output"; - } - - status = NULL; - if (aosf(sf, dev)) - status = errstr; - if (sf_close(sf)) - status = "unable to close sound file"; - if (!ao_close(dev)) - status = "unable to close audio output"; - - return status; -} diff --git a/apfd.h b/apfd.h @@ -1 +0,0 @@ -char *apfd(int fd); diff --git a/config.h b/config.h @@ -1 +0,0 @@ -#define POLL /* poll for input before opening with libsndfile */ diff --git a/die.c b/die.c @@ -1,25 +0,0 @@ -/* Copyright 2021 Jacob R. Edwards - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -#include <stdio.h> -#include <stdlib.h> - -void -die(char *s) -{ - perror(s); - exit(1); -} diff --git a/die.h b/die.h @@ -1 +0,0 @@ -void die(char *); diff --git a/libap/ap.c b/libap/ap.c @@ -0,0 +1,125 @@ +/* Copyright 2021 Jacob R. Edwards + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <string.h> + +#include "ap.h" + +int +sferrno(int error) +{ + switch (error) { + case SF_ERR_MALFORMED_FILE: + case SF_ERR_UNRECOGNISED_FORMAT: + case SF_ERR_UNSUPPORTED_ENCODING: + return EFTYPE; + default: + return 0; + } +} + +struct ap * +apopen(int fd) +{ + SF_INFO infmt; + struct ap *ap; + struct pollfd pfd; + + if (fd < 0) { + errno = EBADF; + return NULL; + } + + ap = calloc(1, sizeof(*ap)); + if (ap == NULL) + return NULL; + + pfd.fd = fd; + pfd.events = POLLIN; + if (poll(&pfd, 1, INFTIM) == -1) { + free(ap); + return NULL; + } + + ap->in = sf_open_fd(fd, SFM_READ, &infmt, 0); + if (ap->in == NULL) { + free(ap); + errno = sferrno(sf_error(NULL)); + return NULL; + } + + ap->fmt.bits = sizeof(sample) * 8; + ap->fmt.byte_format = AO_FMT_NATIVE; + ap->fmt.channels = infmt.channels; + ap->fmt.rate = infmt.samplerate; + + ap->out = ao_open_live(0, &ap->fmt, NULL); + if (ap->out == NULL) { + sf_close(ap->in); + free(ap); + return NULL; + } + + return ap; +} + +int +apclose(struct ap *ap) +{ + if (ap == NULL) + return 0; + return (!!sf_close(ap->in) + !ao_close(ap->out)); +} + +ssize_t +applay(struct ap *ap, sample *buf, size_t len) +{ + if (len > SSIZE_MAX) { + errno = EINVAL; + return 1; + } + + len = sf_read_short(ap->in, buf, len); + if (len == 0) + return 0; + if (len < 0) { + errno = sferrno(sf_error(ap->in)); + return -1; + } + + if (!ao_play(ap->out, (char *)buf, len * sizeof(*buf))) + return -1; + return len; +} + +int +ap(int fd) +{ + sample buf[4096]; + ssize_t wrote; + struct ap *ap; + + ap = apopen(fd); + if (ap == NULL) + return 1; + + while ((wrote = applay(ap, buf, sizeof(buf) / sizeof(*buf))) > 0) + ; + return wrote != 0; +} diff --git a/libap/ap.h b/libap/ap.h @@ -0,0 +1,20 @@ +#ifndef _LIBAP_H_ +#define _LIBAP_H_ + +#include <ao/ao.h> +#include <sndfile.h> + +struct ap { + SNDFILE *in; + ao_device *out; + ao_sample_format fmt; +}; + +typedef short sample; + +struct ap *apopen(int); +int apclose(struct ap *); +ssize_t applay(struct ap *, sample *, size_t); +int ap(int); + +#endif /* _LIBAP_H_ */ diff --git a/mkfile b/mkfile @@ -1,39 +1,47 @@ # Copyright 2021 Jacob R. Edwards cc = tcc -cflags = -Wall -Wno-write-strings -static -ldlibs = -lc -lsndfile -lao +cflags = -Wall -Wno-write-strings +cppflags = -I/usr/local/include -I./libap +ldflags = -L/usr/local/lib -L./libap +ldlibs = -lsndfile -lao -lap + prefix = /usr/local manprefix = $prefix/man -names = sigap ap -src = apfd.c die.c -libobj = ${src:%.c=%.o} -obj = ${names:%=%.o} $libobj +prog = ap sigap -all:V: $names +all:V: $prog %.o: %.c - $cc $cppflags $cflags -c -o $stem.o $stem.c + $cc $cflags $cppflags -c -o $stem.o $stem.c + +libap/ap.o: libap/ap.c libap/ap.h + +libap/libap.a: libap/ap.o + ar rcs $target $newprereq -$names: $obj +$prog: libap/libap.a ${prog:%=%.o} for name in $target do - $cc $ldflags -o $name $name.o $libobj $ldlibs + $cc $cflags $ldflags -o $name $name.o $ldlibs done clean:V: - rm -f $names $obj + rm -f $prog libap/libap.a libap/ap.o -install:V: $names - for name in $names +install:V: libap/libap.a $names + cp -f $prog $prefix/bin + cp -f libap/ap.h $prefix/include + for name in $prog do cp -f $name $prefix/bin cp -f $name.1 $manprefix/man1 done uninstall:V: - for name in $names + rm -f $prefix/lib/libap.a $prefix/include/ap.h + for name in $prog do rm -f $prefix/bin/$name rm -f $manprefix/man1/$name.1 diff --git a/sigap.1 b/sigap.1 @@ -1,5 +1,5 @@ .\" Copyright 2021 Jacob R. Edwards -.Dd April 8, 2021 +.Dd July 18, 2021 .Dt SIGAP 1 .Os .Sh NAME @@ -8,7 +8,6 @@ .Sh SYNOPSIS .Nm .Op Fl lp -.Op Fl e Ar player .Ar file ... .Sh DESCRIPTION The @@ -16,17 +15,15 @@ The utility plays the given files while waiting for signals to control it. The .Dv SIGINT -signal skips current file and +signal skips current file, .Dv SIGINFO -displays current filename. +displays current filename, +and +.Dv SIGQUIT +quits the program. .Pp The options are as follows: .Bl -tag -width indent -.It Fl e Ar player -Use -.Ar player -instead of the default -.Pa ap . .It Fl l Loop the queue. .It Fl p diff --git a/sigap.c b/sigap.c @@ -14,100 +14,47 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -#include <sys/wait.h> +#include <ap.h> #include <fcntl.h> #include <signal.h> #include <stdio.h> #include <unistd.h> -#include "apfd.h" -#include "die.h" - -enum state { - OFF, - STOPPED, - RUNNING, -}; - -static struct player { - char *path; - int state; - int status; - pid_t pid; -} player; - -static char *ap[] = { "ap", NULL }; +static int info; static int loop; +static int next; static int persist; +static int quit; void handle(int sig) { - int status; - switch (sig) { case SIGINFO: - puts(player.path); + info = 1; break; case SIGINT: - if (player.state != OFF && kill(player.pid, SIGTERM)) - perror("kill"); + next = 1; break; - case SIGCHLD: - while ((waitpid(player.pid, &status, 0)) != -1) { - if (WIFEXITED(status)) { - player.state = OFF; - player.status = WEXITSTATUS(status); - } else if (WIFSIGNALED(status)) { - player.state = OFF; - } else if (WIFSTOPPED(status)) { - player.state = STOPPED; - } else if (WIFCONTINUED(status)) { - player.state = RUNNING; - } - } + case SIGQUIT: + quit = 1; break; } } int -play(char *path) -{ - char *status; - - player.state = RUNNING; - player.path = path; - player.pid = fork(); - if (player.pid != 0) { - if (player.pid == -1) - player.state = OFF; - return player.pid == -1; - } - - if (dup2(open(path, O_RDONLY), 0) == -1) - die(path); - execvp(*ap, ap); - perror(*ap); - _exit(1); -} - -int main(int argc, char *argv[]) { int c; + int fd; int i; + sample buf[4096]; + ssize_t wrote; + struct ap *ap; -#ifdef __OpenBSD__ - if (pledge("stdio rpath proc exec", NULL) == -1) - die("pledge"); -#endif - - while ((c = getopt(argc, argv, "e:lp")) != -1) { + while ((c = getopt(argc, argv, "lp")) != -1) { switch (c) { - case 'e': - *ap = optarg; - break; case 'l': loop = 1; break; @@ -115,7 +62,7 @@ main(int argc, char *argv[]) persist = 1; break; default: - fprintf(stderr, "usage: %s [-lp] [-e player] file ...\n", + fprintf(stderr, "usage: %s [-lp] file ...\n", *argv); return 1; } @@ -123,18 +70,44 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; - signal(SIGCHLD, handle); signal(SIGINFO, handle); signal(SIGINT, handle); + signal(SIGQUIT, handle); + + ao_initialize(); + + for (i = 0; i < argc; ++i) { + ap = apopen((fd = open(argv[i], O_RDONLY))); + if (ap == NULL) { +aperror: + perror(argv[i]); + if (persist) + continue; + exit(1); + } + + info = 1; + while (!next && + (wrote = applay(ap, buf, sizeof(buf) / sizeof(*buf))) > 0) { + if (quit) { + apclose(ap); + return 0; + } + if (info) { + info = 0; + puts(argv[i]); + } + } - for (i = 0; i < argc && (persist || player.status == 0); - i = ((loop) ? ((i + 1) % argc) : (i + 1))) { - if (play(argv[i])) - die(argv[i]); - puts(player.path); - while (player.state != OFF) - pause(); + apclose(ap); + if (loop && i + 1 == argc) + i = 0; + if (next) + next = 0; + if (wrote < 0) + goto aperror; } - return player.status; + ao_shutdown(); + return 0; }