* 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");
+}
+#endif
+
+/** @brief Command backend initialization */
+static void command_init(void) {
+ info("selected command backend");
+ fork_cmd();
+}
+
+/** @brief Network backend initialization */
+static void network_init(void) {
struct addrinfo *res, *sres;
static const struct addrinfo pref = {
0,
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 Structure of a backend */
+struct speaker_backend {
+ /** @brief Which backend this is
+ *
+ * @c -1 terminates the list.
+ */
+ int backend;
+
+ /** @brief Initialization
+ *
+ * Called once at startup.
+ */
+ void (*init)(void);
+};
+
+/** @brief Selected backend */
+static const struct speaker_backend *backend;
+
+/** @brief Table of speaker backends */
+static const struct speaker_backend backends[] = {
+#if API_ALSA
+ {
+ BACKEND_ALSA,
+ alsa_init
+ },
+#endif
+ {
+ BACKEND_COMMAND,
+ command_init
+ },
+ {
+ BACKEND_NETWORK,
+ network_init
+ },
+ { -1, 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 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(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);
- /* 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. */
* 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 1
+#if 0
/* TODO remove logging guff */
if(!(logit++ & 1023))
info("rtp_time %llu target %llu difference %lld [%lld]",