+ return backend->activate();
+}
+
+/* Check to see whether the current track has finished playing */
+static void maybe_finished(void) {
+ if(playing
+ && playing->eof
+ && (!playing->got_format
+ || playing->used < bytes_per_frame(&playing->format)))
+ abandon();
+}
+
+static void fork_cmd(void) {
+ pid_t cmdpid;
+ int pfd[2];
+ if(cmdfd != -1) close(cmdfd);
+ xpipe(pfd);
+ cmdpid = xfork();
+ if(!cmdpid) {
+ signal(SIGPIPE, SIG_DFL);
+ 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]);
+ cmdfd = pfd[1];
+ D(("forked cmd %d, fd = %d", cmdpid, cmdfd));
+}
+
+static void play(size_t frames) {
+ size_t avail_bytes, write_bytes, written_frames;
+ ssize_t written_bytes;
+ struct rtp_header header;
+ struct iovec vec[2];
+
+ if(activate()) {
+ if(playing)
+ forceplay = frames;
+ else
+ forceplay = 0; /* Must have called abandon() */
+ return;
+ }
+ D(("play: play %zu/%zu%s %dHz %db %dc", frames, playing->used / bpf,
+ playing->eof ? " EOF" : "",
+ playing->format.rate,
+ playing->format.bits,
+ playing->format.channels));
+ /* If we haven't got enough bytes yet wait until we have. Exception: when
+ * we are at eof. */
+ if(playing->used < frames * bpf && !playing->eof) {
+ forceplay = frames;
+ return;
+ }
+ /* We have got enough data so don't force play again */
+ forceplay = 0;
+ /* Figure out how many frames there are available to write */
+ if(playing->start + playing->used > playing->size)
+ avail_bytes = playing->size - playing->start;
+ else
+ avail_bytes = playing->used;
+
+ switch(config->speaker_backend) {
+#if API_ALSA
+ case BACKEND_ALSA: {
+ snd_pcm_sframes_t pcm_written_frames;
+ size_t avail_frames;
+ int err;
+
+ avail_frames = avail_bytes / bpf;
+ if(avail_frames > frames)
+ avail_frames = frames;
+ if(!avail_frames)
+ return;
+ pcm_written_frames = snd_pcm_writei(pcm,
+ playing->buffer + playing->start,
+ avail_frames);
+ D(("actually play %zu frames, wrote %d",
+ avail_frames, (int)pcm_written_frames));
+ if(pcm_written_frames < 0) {
+ switch(pcm_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)pcm_written_frames);
+ }
+ }
+ written_frames = pcm_written_frames;
+ written_bytes = written_frames * bpf;
+ break;
+ }
+#endif
+ case BACKEND_COMMAND:
+ if(avail_bytes > frames * bpf)
+ avail_bytes = frames * bpf;
+ written_bytes = write(cmdfd, 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, command died; trying another");
+ fork_cmd();
+ return;
+ case EAGAIN:
+ return;
+ }
+ }
+ written_frames = written_bytes / bpf; /* good enough */
+ break;
+ case BACKEND_NETWORK:
+ /* We transmit using RTP (RFC3550) and attempt to conform to the internet
+ * AVT profile (RFC3551). */
+
+ if(idled) {
+ /* There may have been a gap. Fix up the RTP time accordingly. */
+ struct timeval now;
+ uint64_t delta;
+ uint64_t target_rtp_time;
+
+ /* Find the current time */
+ xgettimeofday(&now, 0);
+ /* Find the number of microseconds elapsed since rtp_time=0 */
+ delta = tvsub_us(now, rtp_time_0);
+ assert(delta <= UINT64_MAX / 88200);
+ target_rtp_time = (delta * playing->format.rate
+ * playing->format.channels) / 1000000;
+ /* Overflows at ~6 years uptime with 44100Hz stereo */
+
+ /* rtp_time is the number of samples we've played. NB that we play
+ * RTP_AHEAD_MS ahead of ourselves, so it may legitimately be ahead of
+ * the value we deduce from time comparison.
+ *
+ * Suppose we have 1s track started at t=0, and another track begins to
+ * play at t=2s. Suppose RTP_AHEAD_MS=1000 and 44100Hz stereo. In that
+ * case we'll send 1s of audio as fast as we can, giving rtp_time=88200.
+ * rtp_time stops at this point.
+ *
+ * At t=2s we'll have calculated target_rtp_time=176400. In this case we
+ * set rtp_time=176400 and the player can correctly conclude that it
+ * should leave 1s between the tracks.
+ *
+ * Suppose instead that the second track arrives at t=0.5s, and that
+ * we've managed to transmit the whole of the first track already. We'll
+ * have target_rtp_time=44100.
+ *
+ * The desired behaviour is to play the second track back to back with
+ * first. In this case therefore we do not modify rtp_time.
+ *
+ * Is it ever right to reduce rtp_time? No; for that would imply
+ * transmitting packets with overlapping timestamp ranges, which does not
+ * make sense.
+ */
+ if(target_rtp_time > rtp_time) {
+ /* More time has elapsed than we've transmitted samples. That implies
+ * we've been 'sending' silence. */
+ info("advancing rtp_time by %"PRIu64" samples",
+ target_rtp_time - rtp_time);
+ rtp_time = target_rtp_time;
+ } else if(target_rtp_time < rtp_time) {
+ const int64_t samples_ahead = ((uint64_t)RTP_AHEAD_MS
+ * config->sample_format.rate
+ * config->sample_format.channels
+ / 1000);
+
+ if(target_rtp_time + samples_ahead < rtp_time) {
+ info("reversing rtp_time by %"PRIu64" samples",
+ rtp_time - target_rtp_time);
+ }
+ }
+ }
+ header.vpxcc = 2 << 6; /* V=2, P=0, X=0, CC=0 */
+ header.seq = htons(rtp_seq++);
+ header.timestamp = htonl((uint32_t)rtp_time);
+ header.ssrc = rtp_id;
+ header.mpt = (idled ? 0x80 : 0x00) | 10;
+ /* 10 = L16 = 16-bit x 2 x 44100KHz. We ought to deduce this value from
+ * the sample rate (in a library somewhere so that configuration.c can rule
+ * out invalid rates).
+ */
+ idled = 0;
+ if(avail_bytes > NETWORK_BYTES - sizeof header) {
+ avail_bytes = NETWORK_BYTES - sizeof header;
+ /* Always send a whole number of frames */
+ avail_bytes -= avail_bytes % bpf;
+ }
+ /* "The RTP clock rate used for generating the RTP timestamp is independent
+ * of the number of channels and the encoding; it equals the number of
+ * sampling periods per second. For N-channel encodings, each sampling
+ * period (say, 1/8000 of a second) generates N samples. (This terminology
+ * is standard, but somewhat confusing, as the total number of samples
+ * generated per second is then the sampling rate times the channel
+ * count.)"
+ */
+ write_bytes = avail_bytes;
+ if(write_bytes) {
+ vec[0].iov_base = (void *)&header;
+ vec[0].iov_len = sizeof header;
+ vec[1].iov_base = playing->buffer + playing->start;
+ vec[1].iov_len = avail_bytes;
+ do {
+ written_bytes = writev(bfd,
+ vec,
+ 2);
+ } while(written_bytes < 0 && errno == EINTR);
+ if(written_bytes < 0) {
+ error(errno, "error transmitting audio data");
+ ++audio_errors;
+ if(audio_errors == 10)
+ fatal(0, "too many audio errors");
+ return;
+ }
+ } else
+ audio_errors /= 2;
+ written_bytes = avail_bytes;
+ written_frames = written_bytes / bpf;
+ /* Advance RTP's notion of the time */
+ rtp_time += written_frames * playing->format.channels;
+ break;
+ default:
+ assert(!"reached");
+ }
+ /* written_bytes and written_frames had better both be set and correct by
+ * this point */
+ playing->start += written_bytes;
+ playing->used -= written_bytes;
+ playing->played += written_frames;
+ /* If the pointer is at the end of the buffer (or the buffer is completely
+ * empty) wrap it back to the start. */
+ if(!playing->used || playing->start == playing->size)
+ playing->start = 0;
+ frames -= written_frames;
+}
+
+/* Notify the server what we're up to. */
+static void report(void) {
+ struct speaker_message sm;
+
+ if(playing && playing->buffer != (void *)&playing->format) {
+ memset(&sm, 0, sizeof sm);
+ sm.type = paused ? SM_PAUSED : SM_PLAYING;
+ strcpy(sm.id, playing->id);
+ sm.data = playing->played / playing->format.rate;
+ speaker_send(1, &sm, 0);
+ }
+ time(&last_report);
+}
+
+static void reap(int __attribute__((unused)) sig) {
+ pid_t cmdpid;
+ int st;
+
+ do
+ cmdpid = waitpid(-1, &st, WNOHANG);
+ while(cmdpid > 0);
+ signal(SIGCHLD, reap);
+}
+
+static int addfd(int fd, int events) {
+ if(fdno < NFDS) {
+ fds[fdno].fd = fd;
+ fds[fdno].events = events;
+ return fdno++;
+ } else
+ return -1;
+}
+
+#if API_ALSA
+/** @brief ALSA backend initialization */
+static void alsa_init(void) {
+ info("selected ALSA backend");
+}
+
+/** @brief ALSA backend activation */
+static int alsa_activate(void) {