*
* Don't make this too big or arithmetic will start to overflow.
*/
-#define NETWORK_BYTES 1024
+#define NETWORK_BYTES (1024+sizeof(struct rtp_header))
-/** @brief Maximum RTP playahead (seconds) */
-#define RTP_AHEAD 2
+/** @brief Maximum RTP playahead (ms) */
+#define RTP_AHEAD_MS 1000
/** @brief Maximum number of FDs to poll for */
#define NFDS 256
static time_t last_report; /* when we last reported */
static int paused; /* pause status */
-static ao_sample_format pcm_format; /* current format if aodev != 0 */
static size_t bpf; /* bytes per frame */
static struct pollfd fds[NFDS]; /* if we need more than that */
static int fdno; /* fd number */
static size_t bufsize; /* buffer size */
#if API_ALSA
-static snd_pcm_t *pcm; /* current pcm handle */
+/** @brief The current PCM handle */
+static snd_pcm_t *pcm;
static snd_pcm_uframes_t last_pcm_bufsize; /* last seen buffer size */
+static ao_sample_format pcm_format; /* current format if aodev != 0 */
#endif
-static int ready; /* ready to send audio */
+
+/** @brief Ready to send audio
+ *
+ * This is set when the destination is ready to receive audio. Generally
+ * this implies that the sound device is open. In the ALSA backend it
+ * does @b not necessarily imply that is has the right sample format.
+ */
+static int ready;
+
static int forceplay; /* frames to force play */
static int cmdfd = -1; /* child process input */
static int bfd = -1; /* broadcast FD */
static int idled; /* set when idled */
static int audio_errors; /* audio error counter */
+/** @brief Structure of a backend */
+struct speaker_backend {
+ /** @brief Which backend this is
+ *
+ * @c -1 terminates the list.
+ */
+ int backend;
+
+ /** @brief Flags
+ *
+ * Possible values
+ * - @ref FIXED_FORMAT
+ */
+ unsigned flags;
+/** @brief Lock to configured sample format */
+#define FIXED_FORMAT 0x0001
+
+ /** @brief Initialization
+ *
+ * Called once at startup. This is responsible for one-time setup
+ * operations, for instance opening a network socket to transmit to.
+ *
+ * When writing to a native sound API this might @b not imply opening the
+ * native sound device - that might be done by @c activate below.
+ */
+ void (*init)(void);
+
+ /** @brief Activation
+ * @return 0 on success, non-0 on error
+ *
+ * Called to activate the output device.
+ *
+ * After this function succeeds, @ref ready should be non-0. As well as
+ * opening the audio device, this function is responsible for reconfiguring
+ * if it necessary to cope with different samples formats (for backends that
+ * don't demand a single fixed sample format for the lifetime of the server).
+ */
+ int (*activate)(void);
+
+ /** @brief Play sound
+ * @param frames Number of frames to play
+ * @return Number of frames actually played
+ */
+ size_t (*play)(size_t frames);
+
+ /** @brief Deactivation
+ *
+ * Called to deactivate the sound device. This is the inverse of
+ * @c activate above.
+ */
+ void (*deactivate)(void);
+};
+
+/** @brief Selected backend */
+static const struct speaker_backend *backend;
+
static const struct option options[] = {
{ "help", no_argument, 0, 'h' },
{ "version", no_argument, 0, 'V' },
* to a sox invocation, which performs the required translation.
*/
static void enable_translation(struct track *t) {
- switch(config->speaker_backend) {
- case BACKEND_COMMAND:
- case BACKEND_NETWORK:
- /* These backends need a specific sample format */
- break;
- case BACKEND_ALSA:
- /* ALSA can cope */
- return;
- }
- if(!formats_equal(&t->format, &config->sample_format)) {
+ if((backend->flags & FIXED_FORMAT)
+ && !formats_equal(&t->format, &config->sample_format)) {
char argbuf[1024], *q = argbuf;
const char *av[18], **pp = av;
int soxpipe[2];
close(soxpipe[1]);
t->fd = soxpipe[0];
t->format = config->sample_format;
- ready = 0;
}
}
/** @brief Close the sound device */
static void idle(void) {
D(("idle"));
-#if API_ALSA
- if(config->speaker_backend == BACKEND_ALSA && pcm) {
- int err;
-
- if((err = snd_pcm_nonblock(pcm, 0)) < 0)
- fatal(0, "error calling snd_pcm_nonblock: %d", err);
- D(("draining pcm"));
- snd_pcm_drain(pcm);
- D(("closing pcm"));
- snd_pcm_close(pcm);
- pcm = 0;
- forceplay = 0;
- D(("released audio device"));
- }
-#endif
+ if(backend->deactivate)
+ backend->deactivate();
idled = 1;
ready = 0;
}
D((" - not got format for %s", playing->id));
return -1;
}
- switch(config->speaker_backend) {
- case BACKEND_COMMAND:
- case BACKEND_NETWORK:
- if(!ready) {
- pcm_format = config->sample_format;
- bufsize = 3 * FRAMES;
- bpf = bytes_per_frame(&config->sample_format);
- D(("acquired audio device"));
- ready = 1;
- }
- return 0;
- case BACKEND_ALSA:
-#if API_ALSA
- /* If we need to change format then close the current device. */
- if(pcm && !formats_equal(&playing->format, &pcm_format))
- idle();
- if(!pcm) {
- snd_pcm_hw_params_t *hwparams;
- snd_pcm_sw_params_t *swparams;
- snd_pcm_uframes_t pcm_bufsize;
- int err;
- int sample_format = 0;
- unsigned rate;
-
- D(("snd_pcm_open"));
- if((err = snd_pcm_open(&pcm,
- config->device,
- SND_PCM_STREAM_PLAYBACK,
- SND_PCM_NONBLOCK))) {
- error(0, "error from snd_pcm_open: %d", err);
- goto error;
- }
- snd_pcm_hw_params_alloca(&hwparams);
- D(("set up hw params"));
- if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
- fatal(0, "error from snd_pcm_hw_params_any: %d", err);
- if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
- SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
- fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
- switch(playing->format.bits) {
- case 8:
- sample_format = SND_PCM_FORMAT_S8;
- break;
- case 16:
- switch(playing->format.byte_format) {
- case AO_FMT_NATIVE: sample_format = SND_PCM_FORMAT_S16; break;
- case AO_FMT_LITTLE: sample_format = SND_PCM_FORMAT_S16_LE; break;
- case AO_FMT_BIG: sample_format = SND_PCM_FORMAT_S16_BE; break;
- error(0, "unrecognized byte format %d", playing->format.byte_format);
- goto fatal;
- }
- break;
- default:
- error(0, "unsupported sample size %d", playing->format.bits);
- goto fatal;
- }
- if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
- sample_format)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_format (%d): %d",
- sample_format, err);
- goto fatal;
- }
- rate = playing->format.rate;
- if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
- playing->format.rate, err);
- goto fatal;
- }
- if(rate != (unsigned)playing->format.rate)
- info("want rate %d, got %u", playing->format.rate, rate);
- if((err = snd_pcm_hw_params_set_channels(pcm, hwparams,
- playing->format.channels)) < 0) {
- error(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
- playing->format.channels, err);
- goto fatal;
- }
- bufsize = 3 * FRAMES;
- pcm_bufsize = bufsize;
- if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams,
- &pcm_bufsize)) < 0)
- fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%d): %d",
- 3 * FRAMES, err);
- if(pcm_bufsize != 3 * FRAMES && pcm_bufsize != last_pcm_bufsize)
- info("asked for PCM buffer of %d frames, got %d",
- 3 * FRAMES, (int)pcm_bufsize);
- last_pcm_bufsize = pcm_bufsize;
- if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
- fatal(0, "error calling snd_pcm_hw_params: %d", err);
- D(("set up sw params"));
- snd_pcm_sw_params_alloca(&swparams);
- if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0)
- fatal(0, "error calling snd_pcm_sw_params_current: %d", err);
- if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, FRAMES)) < 0)
- fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d",
- FRAMES, err);
- if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
- fatal(0, "error calling snd_pcm_sw_params: %d", err);
- pcm_format = playing->format;
- bpf = bytes_per_frame(&pcm_format);
- D(("acquired audio device"));
- log_params(hwparams, swparams);
- ready = 1;
- }
- return 0;
- fatal:
- abandon();
- error:
- /* We assume the error is temporary and that we'll retry in a bit. */
- if(pcm) {
- snd_pcm_close(pcm);
- pcm = 0;
- }
- return -1;
-#endif
- default:
- assert(!"reached");
- }
+ return backend->activate();
}
/* Check to see whether the current track has finished playing */
}
static void play(size_t frames) {
- size_t avail_bytes, write_bytes, written_frames;
+ size_t avail_frames, avail_bytes, write_bytes, written_frames;
ssize_t written_bytes;
struct rtp_header header;
struct iovec vec[2];
+ /* Make sure the output device is activated */
if(activate()) {
if(playing)
forceplay = frames;
forceplay = 0;
/* Figure out how many frames there are available to write */
if(playing->start + playing->used > playing->size)
+ /* The ring buffer is currently wrapped, only play up to the wrap point */
avail_bytes = playing->size - playing->start;
else
+ /* The ring buffer is not wrapped, can play the lot */
avail_bytes = playing->used;
+ avail_frames = avail_bytes / bpf;
+ /* Only play up to the requested amount */
+ if(avail_frames > frames)
+ avail_frames = frames;
+ if(!avail_frames)
+ return;
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);
* AVT profile (RFC3551). */
if(idled) {
- /* There's been a gap. Fix up the RTP time accordingly. */
+ /* There may have been a gap. Fix up the RTP time accordingly. */
struct timeval now;
uint64_t delta;
uint64_t target_rtp_time;
target_rtp_time = (delta * playing->format.rate
* playing->format.channels) / 1000000;
/* Overflows at ~6 years uptime with 44100Hz stereo */
- if(target_rtp_time > rtp_time)
+
+ /* 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);
- else if(target_rtp_time < rtp_time)
- info("reversing rtp_time by %"PRIu64" samples",
- rtp_time - target_rtp_time);
- rtp_time = target_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++);
return -1;
}
-int main(int argc, char **argv) {
- int n, fd, stdin_slot, alsa_slots, cmdfd_slot, bfd_slot, poke, timeout;
- struct track *t;
- struct speaker_message sm;
+#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) {
+ /* If we need to change format then close the current device. */
+ if(pcm && !formats_equal(&playing->format, &pcm_format))
+ idle();
+ if(!pcm) {
+ snd_pcm_hw_params_t *hwparams;
+ snd_pcm_sw_params_t *swparams;
+ snd_pcm_uframes_t pcm_bufsize;
+ int err;
+ int sample_format = 0;
+ unsigned rate;
+
+ D(("snd_pcm_open"));
+ if((err = snd_pcm_open(&pcm,
+ config->device,
+ SND_PCM_STREAM_PLAYBACK,
+ SND_PCM_NONBLOCK))) {
+ error(0, "error from snd_pcm_open: %d", err);
+ goto error;
+ }
+ snd_pcm_hw_params_alloca(&hwparams);
+ D(("set up hw params"));
+ if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_any: %d", err);
+ if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
+ SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
+ switch(playing->format.bits) {
+ case 8:
+ sample_format = SND_PCM_FORMAT_S8;
+ break;
+ case 16:
+ switch(playing->format.byte_format) {
+ case AO_FMT_NATIVE: sample_format = SND_PCM_FORMAT_S16; break;
+ case AO_FMT_LITTLE: sample_format = SND_PCM_FORMAT_S16_LE; break;
+ case AO_FMT_BIG: sample_format = SND_PCM_FORMAT_S16_BE; break;
+ error(0, "unrecognized byte format %d", playing->format.byte_format);
+ goto fatal;
+ }
+ break;
+ default:
+ error(0, "unsupported sample size %d", playing->format.bits);
+ goto fatal;
+ }
+ if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
+ sample_format)) < 0) {
+ error(0, "error from snd_pcm_hw_params_set_format (%d): %d",
+ sample_format, err);
+ goto fatal;
+ }
+ rate = playing->format.rate;
+ if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0) {
+ error(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
+ playing->format.rate, err);
+ goto fatal;
+ }
+ if(rate != (unsigned)playing->format.rate)
+ info("want rate %d, got %u", playing->format.rate, rate);
+ if((err = snd_pcm_hw_params_set_channels(pcm, hwparams,
+ playing->format.channels)) < 0) {
+ error(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
+ playing->format.channels, err);
+ goto fatal;
+ }
+ bufsize = 3 * FRAMES;
+ pcm_bufsize = bufsize;
+ if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams,
+ &pcm_bufsize)) < 0)
+ fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%d): %d",
+ 3 * FRAMES, err);
+ if(pcm_bufsize != 3 * FRAMES && pcm_bufsize != last_pcm_bufsize)
+ info("asked for PCM buffer of %d frames, got %d",
+ 3 * FRAMES, (int)pcm_bufsize);
+ last_pcm_bufsize = pcm_bufsize;
+ if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
+ fatal(0, "error calling snd_pcm_hw_params: %d", err);
+ D(("set up sw params"));
+ snd_pcm_sw_params_alloca(&swparams);
+ if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0)
+ fatal(0, "error calling snd_pcm_sw_params_current: %d", err);
+ if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, FRAMES)) < 0)
+ fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d",
+ FRAMES, err);
+ if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
+ fatal(0, "error calling snd_pcm_sw_params: %d", err);
+ pcm_format = playing->format;
+ bpf = bytes_per_frame(&pcm_format);
+ D(("acquired audio device"));
+ log_params(hwparams, swparams);
+ ready = 1;
+ }
+ return 0;
+fatal:
+ abandon();
+error:
+ /* We assume the error is temporary and that we'll retry in a bit. */
+ if(pcm) {
+ snd_pcm_close(pcm);
+ pcm = 0;
+ }
+ return -1;
+}
+
+/** @brief Play via ALSA */
+static size_t alsa_play(size_t frames) {
+ return frames;
+}
+
+/** @brief ALSA deactivation */
+static void alsa_deactivate(void) {
+ if(pcm) {
+ int err;
+
+ if((err = snd_pcm_nonblock(pcm, 0)) < 0)
+ fatal(0, "error calling snd_pcm_nonblock: %d", err);
+ D(("draining pcm"));
+ snd_pcm_drain(pcm);
+ D(("closing pcm"));
+ snd_pcm_close(pcm);
+ pcm = 0;
+ forceplay = 0;
+ D(("released audio device"));
+ }
+}
+#endif
+
+/** @brief Command backend initialization */
+static void command_init(void) {
+ info("selected command backend");
+ fork_cmd();
+}
+
+/** @brief Play to a subprocess */
+static size_t command_play(size_t frames) {
+ return frames;
+}
+
+/** @brief Command/network backend activation */
+static int generic_activate(void) {
+ if(!ready) {
+ bufsize = 3 * FRAMES;
+ bpf = bytes_per_frame(&config->sample_format);
+ D(("acquired audio device"));
+ ready = 1;
+ }
+ return 0;
+}
+
+/** @brief Network backend initialization */
+static void network_init(void) {
struct addrinfo *res, *sres;
static const struct addrinfo pref = {
0,
0
};
static const int one = 1;
+ int sndbuf, target_sndbuf = 131072;
+ socklen_t len;
char *sockname, *ssockname;
+
+ res = get_address(&config->broadcast, &pref, &sockname);
+ if(!res) exit(-1);
+ if(config->broadcast_from.n) {
+ sres = get_address(&config->broadcast_from, &prefbind, &ssockname);
+ if(!sres) exit(-1);
+ } else
+ sres = 0;
+ if((bfd = socket(res->ai_family,
+ res->ai_socktype,
+ res->ai_protocol)) < 0)
+ fatal(errno, "error creating broadcast socket");
+ if(setsockopt(bfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof one) < 0)
+ fatal(errno, "error setting SO_BROADCAST on broadcast socket");
+ len = sizeof sndbuf;
+ if(getsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
+ &sndbuf, &len) < 0)
+ fatal(errno, "error getting SO_SNDBUF");
+ if(target_sndbuf > sndbuf) {
+ if(setsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
+ &target_sndbuf, sizeof target_sndbuf) < 0)
+ error(errno, "error setting SO_SNDBUF to %d", target_sndbuf);
+ else
+ info("changed socket send buffer size from %d to %d",
+ sndbuf, target_sndbuf);
+ } else
+ info("default socket send buffer is %d",
+ sndbuf);
+ /* We might well want to set additional broadcast- or multicast-related
+ * options here */
+ if(sres && bind(bfd, sres->ai_addr, sres->ai_addrlen) < 0)
+ fatal(errno, "error binding broadcast socket to %s", ssockname);
+ if(connect(bfd, res->ai_addr, res->ai_addrlen) < 0)
+ fatal(errno, "error connecting broadcast socket to %s", sockname);
+ /* Select an SSRC */
+ gcry_randomize(&rtp_id, sizeof rtp_id, GCRY_STRONG_RANDOM);
+ info("selected network backend, sending to %s", sockname);
+ if(config->sample_format.byte_format != AO_FMT_BIG) {
+ info("forcing big-endian sample format");
+ config->sample_format.byte_format = AO_FMT_BIG;
+ }
+}
+
+/** @brief Play over the network */
+static size_t network_play(size_t frames) {
+ return frames;
+}
+
+/** @brief Table of speaker backends */
+static const struct speaker_backend backends[] = {
+#if API_ALSA
+ {
+ BACKEND_ALSA,
+ 0,
+ alsa_init,
+ alsa_activate,
+ alsa_play,
+ alsa_deactivate
+ },
+#endif
+ {
+ BACKEND_COMMAND,
+ FIXED_FORMAT,
+ command_init,
+ generic_activate,
+ command_play,
+ 0 /* deactivate */
+ },
+ {
+ BACKEND_NETWORK,
+ FIXED_FORMAT,
+ network_init,
+ generic_activate,
+ network_play,
+ 0 /* deactivate */
+ },
+ { -1, 0, 0, 0, 0, 0 }
+};
+
+int main(int argc, char **argv) {
+ int n, fd, stdin_slot, alsa_slots, cmdfd_slot, bfd_slot, poke, timeout;
+ struct track *t;
+ struct speaker_message sm;
#if API_ALSA
int alsa_nslots = -1, err;
#endif
become_mortal();
/* make sure we're not root, whatever the config says */
if(getuid() == 0 || geteuid() == 0) fatal(0, "do not run as root");
- switch(config->speaker_backend) {
- case BACKEND_ALSA:
- info("selected ALSA backend");
- case BACKEND_COMMAND:
- info("selected command backend");
- fork_cmd();
- break;
- case BACKEND_NETWORK:
- res = get_address(&config->broadcast, &pref, &sockname);
- if(!res) return -1;
- if(config->broadcast_from.n) {
- sres = get_address(&config->broadcast_from, &prefbind, &ssockname);
- if(!sres) return -1;
- } else
- sres = 0;
- if((bfd = socket(res->ai_family,
- res->ai_socktype,
- res->ai_protocol)) < 0)
- fatal(errno, "error creating broadcast socket");
- if(setsockopt(bfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof one) < 0)
- fatal(errno, "error settting SO_BROADCAST on broadcast socket");
- /* We might well want to set additional broadcast- or multicast-related
- * options here */
- if(sres && bind(bfd, sres->ai_addr, sres->ai_addrlen) < 0)
- fatal(errno, "error binding broadcast socket to %s", ssockname);
- if(connect(bfd, res->ai_addr, res->ai_addrlen) < 0)
- fatal(errno, "error connecting broadcast socket to %s", sockname);
- /* Select an SSRC */
- gcry_randomize(&rtp_id, sizeof rtp_id, GCRY_STRONG_RANDOM);
- info("selected network backend, sending to %s", sockname);
- if(config->sample_format.byte_format != AO_FMT_BIG) {
- info("forcing big-endian sample format");
- config->sample_format.byte_format = AO_FMT_BIG;
- }
- break;
- default:
- fatal(0, "unknown backend %d", config->speaker_backend);
- }
+ /* identify the backend used to play */
+ for(n = 0; backends[n].backend != -1; ++n)
+ if(backends[n].backend == config->speaker_backend)
+ break;
+ if(backends[n].backend == -1)
+ fatal(0, "unsupported backend %d", config->speaker_backend);
+ backend = &backends[n];
+ /* backend-specific initialization */
+ backend->init();
while(getppid() != 1) {
fdno = 0;
/* Always ready for commands from the main server. */
struct timeval now;
uint64_t target_us;
uint64_t target_rtp_time;
- const uint64_t ahead = RTP_AHEAD * config->sample_format.rate
- * config->sample_format.channels;
-
+ const int64_t samples_ahead = ((uint64_t)RTP_AHEAD_MS
+ * config->sample_format.rate
+ * config->sample_format.channels
+ / 1000);
+#if 0
static unsigned logit;
+#endif
/* If we're starting then initialize the base time */
if(!rtp_time)
* config->sample_format.channels)
/ 1000000;
+#if 0
/* TODO remove logging guff */
if(!(logit++ & 1023))
info("rtp_time %llu target %llu difference %lld [%lld]",
rtp_time, target_rtp_time,
rtp_time - target_rtp_time,
- ahead);
- if(rtp_time < target_rtp_time
- || rtp_time - target_rtp_time < ahead)
+ samples_ahead);
+#endif
+ if((int64_t)(rtp_time - target_rtp_time) < samples_ahead)
bfd_slot = addfd(bfd, POLLOUT);
break;
}