return 0;
}
+static int parse_sample_format(const struct config_state *cs,
+ ao_sample_format *ao,
+ int nvec, char **vec) {
+ char *p = vec[0];
+ long t;
+
+ if (nvec != 1) {
+ error(0, "%s:%d: wrong number of arguments", cs->path, cs->line);
+ return -1;
+ }
+ if (xstrtol(&t, p, &p, 0)) {
+ error(errno, "%s:%d: converting bits-per-sample", cs->path, cs->line);
+ return -1;
+ }
+ if (t != 8 && t != 16) {
+ error(0, "%s:%d: bad bite-per-sample (%ld)", cs->path, cs->line, t);
+ return -1;
+ }
+ if (ao) ao->bits = t;
+ switch (*p) {
+ case 'l': case 'L': t = AO_FMT_LITTLE; p++; break;
+ case 'b': case 'B': t = AO_FMT_BIG; p++; break;
+ default: t = AO_FMT_NATIVE; break;
+ }
+ if (ao) ao->byte_format = t;
+ if (*p != '/') {
+ error(errno, "%s:%d: expected `/' after bits-per-sample",
+ cs->path, cs->line);
+ return -1;
+ }
+ p++;
+ if (xstrtol(&t, p, &p, 0)) {
+ error(errno, "%s:%d: converting sample-rate", cs->path, cs->line);
+ return -1;
+ }
+ if (t < 1 || t > INT_MAX) {
+ error(0, "%s:%d: silly sample-rate (%ld)", cs->path, cs->line, t);
+ return -1;
+ }
+ if (ao) ao->rate = t;
+ if (*p != '/') {
+ error(0, "%s:%d: expected `/' after sample-rate",
+ cs->path, cs->line);
+ return -1;
+ }
+ p++;
+ if (xstrtol(&t, p, &p, 0)) {
+ error(errno, "%s:%d: converting channels", cs->path, cs->line);
+ return -1;
+ }
+ if (t < 1 || t > 8) {
+ error(0, "%s:%d: silly number (%ld) of channels", cs->path, cs->line, t);
+ return -1;
+ }
+ if (ao) ao->channels = t;
+ if (*p) {
+ error(0, "%s:%d: junk after channels", cs->path, cs->line);
+ return -1;
+ }
+ return 0;
+}
+
+static int set_sample_format(const struct config_state *cs,
+ const struct conf *whoami,
+ int nvec, char **vec) {
+ return parse_sample_format(cs, ADDRESS(cs->config, ao_sample_format),
+ nvec, vec);
+}
+
static int set_namepart(const struct config_state *cs,
const struct conf *whoami,
int nvec, char **vec) {
type_integer = { set_integer, free_none },
type_stringlist_accum = { set_stringlist_accum, free_stringlistlist },
type_string_accum = { set_string_accum, free_stringlist },
+ type_sample_format = { set_sample_format, free_none },
type_restrict = { set_restrict, free_none },
type_namepart = { set_namepart, free_namepartlist },
type_transform = { set_transform, free_transformlist };
return 0;
}
+static int validate_sample_format(const struct config_state *cs,
+ int attribute((unused)) nvec,
+ char **vec) {
+ return parse_sample_format(cs, 0, nvec, vec);
+}
+
static int validate_channel(const struct config_state *cs,
int attribute((unused)) nvec,
char **vec) {
{ C(prefsync), &type_integer, validate_positive },
{ C(refresh), &type_integer, validate_positive },
{ C2(restrict, restrictions), &type_restrict, validate_any },
+ { C(sample_format), &type_sample_format, validate_sample_format },
{ C(scratch), &type_string_accum, validate_isreg },
{ C(signal), &type_signal, validate_any },
+ { C(speaker_command), &type_string, validate_any },
{ C(stopword), &type_string_accum, validate_any },
{ C(templates), &type_string_accum, validate_isdir },
{ C(transform), &type_transform, validate_any },
c->lock = 1;
c->device = xstrdup("default");
c->nice_rescan = 10;
+ c->speaker_command = 0;
+ c->sample_format.bits = 16;
+ c->sample_format.rate = 44100;
+ c->sample_format.channels = 2;
+ c->sample_format.byte_format = AO_FMT_NATIVE;
return c;
}
#include <string.h>
#include <assert.h>
#include <sys/select.h>
+#include <sys/wait.h>
#include <time.h>
#include "configuration.h"
static int fdno; /* fd number */
static snd_pcm_uframes_t pcm_bufsize; /* buffer size */
static snd_pcm_uframes_t last_pcm_bufsize; /* last seen buffer size */
+static int ready; /* ready to send audio */
static int forceplay; /* frames to force play */
+static int kidfd = -1; /* child process input */
static const struct option options[] = {
{ "help", no_argument, 0, 'h' },
forceplay = 0;
D(("released audio device"));
}
+ ready = 0;
}
/* Abandon the current track */
}
}
+static void soxargs(const char ***pp, char **qq, ao_sample_format *ao)
+{
+ int n;
+
+ *(*pp)++ = "-t.raw";
+ *(*pp)++ = "-s";
+ *(*pp)++ = *qq; n = sprintf(*qq, "-r%d", ao->rate); *qq += n + 1;
+ switch(ao->byte_format) {
+ case AO_FMT_NATIVE: break;
+ case AO_FMT_BIG: *(*pp)++ = "-B";
+ case AO_FMT_LITTLE: *(*pp)++ = "-L";
+ }
+ *(*pp)++ = *qq; n = sprintf(*qq, "-%d", ao->bits/8); *qq += n + 1;
+ *(*pp)++ = *qq; n = sprintf(*qq, "-c%d", ao->channels); *qq += n + 1;
+}
+
/* Make sure the sound device is open and has the right sample format. Return
* 0 on success and -1 on error. */
static int activate(void) {
D((" - not got format for %s", playing->id));
return -1;
}
+ if(kidfd >= 0) {
+ if(!formats_equal(&playing->format, &config->sample_format)) {
+ char argbuf[1024], *q = argbuf;
+ const char *av[18], **pp = av;
+ int soxpipe[2];
+ pid_t soxkid;
+ *pp++ = "sox";
+ soxargs(&pp, &q, &playing->format);
+ *pp++ = "-";
+ soxargs(&pp, &q, &config->sample_format);
+ *pp++ = "-";
+ *pp++ = 0;
+ if(debugging) {
+ for(pp = av; *pp; pp++)
+ D(("sox arg[%d] = %s", pp - av, *pp));
+ D(("end args"));
+ }
+ xpipe(soxpipe);
+ soxkid = xfork();
+ if(soxkid == 0) {
+ xdup2(playing->fd, 0);
+ xdup2(soxpipe[1], 1);
+ fcntl(0, F_SETFL, fcntl(0, F_GETFL) & ~O_NONBLOCK);
+ close(soxpipe[0]);
+ close(soxpipe[1]);
+ close(playing->fd);
+ execvp("sox", (char **)av);
+ _exit(1);
+ }
+ D(("forking sox for format conversion (kid = %d)", soxkid));
+ close(playing->fd);
+ close(soxpipe[1]);
+ playing->fd = soxpipe[0];
+ playing->format = config->sample_format;
+ ready = 0;
+ }
+ if(!ready) {
+ pcm_format = config->sample_format;
+ pcm_bufsize = 3 * FRAMES;
+ bpf = bytes_per_frame(&config->sample_format);
+ D(("acquired audio device"));
+ ready = 1;
+ }
+ return 0;
+ }
/* If we need to change format then close the current device. */
if(pcm && !formats_equal(&playing->format, &pcm_format))
idle();
bpf = bytes_per_frame(&pcm_format);
D(("acquired audio device"));
log_params(hwparams, swparams);
+ ready = 1;
}
return 0;
fatal:
abandon();
}
+static void fork_kid(void) {
+ pid_t kid;
+ int pfd[2];
+ if(kidfd != -1) close(kidfd);
+ xpipe(pfd);
+ kid = xfork();
+ if(!kid) {
+ xdup2(pfd[0], 0);
+ close(pfd[0]);
+ close(pfd[1]);
+ execl("/bin/sh", "sh", "-c", config->speaker_command, (char *)0);
+ fatal(errno, "error execing /bin/sh");
+ }
+ close(pfd[0]);
+ kidfd = pfd[1];
+ D(("forked kid %d, fd = %d", kid, kidfd));
+}
+
static void play(size_t frames) {
snd_pcm_sframes_t written_frames;
- size_t avail_bytes, avail_frames, written_bytes;
+ size_t avail_bytes, avail_frames;
+ ssize_t written_bytes;
int err;
if(activate()) {
avail_bytes = playing->size - playing->start;
else
avail_bytes = playing->used;
- avail_frames = avail_bytes / bpf;
- if(avail_frames > frames)
- avail_frames = frames;
- if(!avail_frames)
- return;
- written_frames = snd_pcm_writei(pcm,
- playing->buffer + playing->start,
- avail_frames);
- D(("actually play %zu frames, wrote %d",
- avail_frames, (int)written_frames));
- if(written_frames < 0) {
- switch(written_frames) {
- case -EPIPE: /* underrun */
- error(0, "snd_pcm_writei reports underrun");
- if((err = snd_pcm_prepare(pcm)) < 0)
- fatal(0, "error calling snd_pcm_prepare: %d", err);
- return;
- case -EAGAIN:
+
+ if(kidfd == -1) {
+ avail_frames = avail_bytes / bpf;
+ if(avail_frames > frames)
+ avail_frames = frames;
+ if(!avail_frames)
return;
- default:
- fatal(0, "error calling snd_pcm_writei: %d", (int)written_frames);
+ written_frames = snd_pcm_writei(pcm,
+ playing->buffer + playing->start,
+ avail_frames);
+ D(("actually play %zu frames, wrote %d",
+ avail_frames, (int)written_frames));
+ if(written_frames < 0) {
+ switch(written_frames) {
+ case -EPIPE: /* underrun */
+ error(0, "snd_pcm_writei reports underrun");
+ if((err = snd_pcm_prepare(pcm)) < 0)
+ fatal(0, "error calling snd_pcm_prepare: %d", err);
+ return;
+ case -EAGAIN:
+ return;
+ default:
+ fatal(0, "error calling snd_pcm_writei: %d", (int)written_frames);
+ }
+ }
+ written_bytes = written_frames * bpf;
+ } else {
+ if(avail_bytes > frames * bpf)
+ avail_bytes = frames * bpf;
+ written_bytes = write(kidfd, playing->buffer + playing->start,
+ avail_bytes);
+ D(("actually play %zu bytes, wrote %d",
+ avail_bytes, (int)written_bytes));
+ if(written_bytes < 0) {
+ switch(errno) {
+ case EPIPE:
+ error(0, "hmm, kid died; trying another");
+ fork_kid();
+ return;
+ case EAGAIN:
+ return;
+ }
}
+ written_frames = written_bytes / bpf; /* good enough */
}
- written_bytes = written_frames * bpf;
playing->start += written_bytes;
playing->used -= written_bytes;
playing->played += written_frames;
time(&last_report);
}
+static void reap(int __attribute__((unused)) sig) {
+ pid_t kid;
+ int st;
+
+ do
+ kid = waitpid(-1, &st, WNOHANG);
+ while(kid > 0);
+ signal(SIGCHLD, reap);
+}
+
static int addfd(int fd, int events) {
if(fdno < NFDS) {
fds[fdno].fd = fd;
}
int main(int argc, char **argv) {
- int n, fd, stdin_slot, alsa_slots, alsa_nslots = -1, err;
+ int n, fd, stdin_slot, alsa_slots, alsa_nslots = -1, kid_slot, err;
unsigned short alsa_revents;
struct track *t;
struct speaker_message sm;
if(config_read()) fatal(0, "cannot read configuration");
/* ignore SIGPIPE */
signal(SIGPIPE, SIG_IGN);
+ /* reap kids */
+ signal(SIGCHLD, reap);
/* set nice value */
xnice(config->nice_speaker);
/* change user */
/* make sure we're not root, whatever the config says */
if(getuid() == 0 || geteuid() == 0) fatal(0, "do not run as root");
info("started");
+ if(config->speaker_command) fork_kid();
while(getppid() != 1) {
fdno = 0;
/* Always ready for commands from the main server. */
playing->slot = -1;
/* If forceplay is set then wait until it succeeds before waiting on the
* sound device. */
+ alsa_slots = -1;
+ kid_slot = -1;
if(pcm && !forceplay) {
- int retry = 3;
-
- alsa_slots = fdno;
- do {
- retry = 0;
- alsa_nslots = snd_pcm_poll_descriptors(pcm, &fds[fdno], NFDS - fdno);
- if((alsa_nslots <= 0
- || !(fds[alsa_slots].events & POLLOUT))
- && snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
- error(0, "underrun detected after call to snd_pcm_poll_descriptors()");
- if((err = snd_pcm_prepare(pcm)))
- fatal(0, "error calling snd_pcm_prepare: %d", err);
- } else
- break;
- } while(retry-- > 0);
- if(alsa_nslots >= 0)
- fdno += alsa_nslots;
- } else
- alsa_slots = -1;
+ if(kidfd >= 0)
+ kid_slot = addfd(kidfd, POLLOUT);
+ else {
+ int retry = 3;
+
+ alsa_slots = fdno;
+ do {
+ retry = 0;
+ alsa_nslots = snd_pcm_poll_descriptors(pcm, &fds[fdno], NFDS - fdno);
+ if((alsa_nslots <= 0
+ || !(fds[alsa_slots].events & POLLOUT))
+ && snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
+ error(0, "underrun detected after call to snd_pcm_poll_descriptors()");
+ if((err = snd_pcm_prepare(pcm)))
+ fatal(0, "error calling snd_pcm_prepare: %d", err);
+ } else
+ break;
+ } while(retry-- > 0);
+ if(alsa_nslots >= 0)
+ fdno += alsa_nslots;
+ }
+ }
/* If any other tracks don't have a full buffer, try to read sample data
* from them. */
for(t = tracks; t; t = t->next)
if(t != playing) {
if(!t->eof && t->used < t->size) {
- t->slot = addfd(t->fd, POLLIN);
+ t->slot = addfd(t->fd, POLLIN | POLLHUP);
} else
t->slot = -1;
}
alsa_nslots,
&alsa_revents)) < 0)
fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err);
- if(alsa_revents & POLLOUT)
+ if(alsa_revents & (POLLOUT | POLLERR))
+ play(3 * FRAMES);
+ } else if(kid_slot != -1) {
+ if(fds[kid_slot].revents & (POLLOUT | POLLERR))
play(3 * FRAMES);
} else {
/* Some attempt to play must have failed */
}
/* Read in any buffered data */
for(t = tracks; t; t = t->next)
- if(t->slot != -1 && (fds[t->slot].revents & POLLIN))
+ if(t->slot != -1 && (fds[t->slot].revents & (POLLIN | POLLHUP)))
fill(t);
/* We might be able to play now */
- if(pcm && forceplay && playing && !paused)
+ if(ready && forceplay && playing && !paused)
play(forceplay);
/* Maybe we finished playing a track somewhere in the above */
maybe_finished();
/* If we don't need the sound device for now then close it for the benefit
* of anyone else who wants it. */
- if((!playing || paused) && pcm)
+ if((!playing || paused) && ready)
idle();
/* If we've not reported out state for a second do so now. */
if(time(0) > last_report)