X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/60e5bc860049493e2fa3d8b027ea5ead694ad3d2..ba70caca3f9debf14c9f551ff4dcaddd1eb07d3d:/lib/uaudio-rtp.c diff --git a/lib/uaudio-rtp.c b/lib/uaudio-rtp.c index 9980ba0..b800b8b 100644 --- a/lib/uaudio-rtp.c +++ b/lib/uaudio-rtp.c @@ -36,6 +36,7 @@ #include "addr.h" #include "ifreq.h" #include "timeval.h" +#include "configuration.h" /** @brief Bytes to send per network packet * @@ -59,28 +60,6 @@ static uint32_t rtp_id; /** @brief RTP sequence number */ static uint16_t rtp_sequence; -/** @brief RTP timestamp - * - * This is the timestamp that will be used on the next outbound packet. - * - * The timestamp in the packet header is only 32 bits wide. With 44100Hz - * stereo, that only gives about half a day before wrapping, which is not - * particularly convenient for certain debugging purposes. Therefore the - * timestamp is maintained as a 64-bit integer, giving around six million years - * before wrapping, and truncated to 32 bits when transmitting. - */ -static uint64_t rtp_timestamp; - -/** @brief Actual time corresponding to @ref rtp_timestamp - * - * This is the time, on this machine, at which the sample at @ref rtp_timestamp - * ought to be sent, interpreted as the time the last packet was sent plus the - * time length of the packet. */ -static struct timeval rtp_timeval; - -/** @brief Set when we (re-)activate, to provoke timestamp resync */ -static int rtp_reactivated; - /** @brief Network error count * * If too many errors occur in too short a time, we give up. @@ -100,21 +79,20 @@ static const char *const rtp_options[] = { "rtp-source-port", "multicast-ttl", "multicast-loop", - "rtp-delay-threshold", + "delay-threshold", NULL }; static size_t rtp_play(void *buffer, size_t nsamples) { struct rtp_header header; struct iovec vec[2]; - struct timeval now; /* We do as much work as possible before checking what time it is */ /* Fill out header */ header.vpxcc = 2 << 6; /* V=2, P=0, X=0, CC=0 */ header.seq = htons(rtp_sequence++); header.ssrc = rtp_id; - header.mpt = (rtp_reactivated ? 0x80 : 0x00) | rtp_payload; + header.mpt = (uaudio_schedule_reactivated ? 0x80 : 0x00) | rtp_payload; #if !WORDS_BIGENDIAN /* Convert samples to network byte order */ uint16_t *u = buffer, *const limit = u + nsamples; @@ -127,61 +105,8 @@ static size_t rtp_play(void *buffer, size_t nsamples) { vec[0].iov_len = sizeof header; vec[1].iov_base = buffer; vec[1].iov_len = nsamples * uaudio_sample_size; -retry: - xgettimeofday(&now, NULL); - if(rtp_reactivated) { - /* We've been deactivated for some unknown interval. We need to advance - * rtp_timestamp to account for the dead air. */ - /* On the first run through we'll set the start time. */ - if(!rtp_timeval.tv_sec) - rtp_timeval = now; - /* See how much time we missed. - * - * This will be 0 on the first run through, in which case we'll not modify - * anything. - * - * It'll be negative in the (rare) situation where the deactivation - * interval is shorter than the last packet we sent. In this case we wait - * for that much time and then return having sent no samples, which will - * cause uaudio_play_thread_fn() to retry. - * - * In the normal case it will be positive. - */ - const int64_t delay = tvsub_us(now, rtp_timeval); /* microseconds */ - if(delay < 0) { - usleep(-delay); - goto retry; - } - /* Advance the RTP timestamp to the present. With 44.1KHz stereo this will - * overflow the intermediate value with a delay of a bit over 6 years. - * This seems acceptable. */ - uint64_t update = (delay * uaudio_rate * uaudio_channels) / 1000000; - /* Don't throw off channel synchronization */ - update -= update % uaudio_channels; - /* We log nontrivial changes */ - if(update) - info("advancing rtp_time by %"PRIu64" samples", update); - rtp_timestamp += update; - rtp_timeval = now; - rtp_reactivated = 0; - } else { - /* Chances are we've been called right on the heels of the previous packet. - * If we just sent packets as fast as we got audio data we'd get way ahead - * of the player and some buffer somewhere would fill (or at least become - * unreasonably large). - * - * First find out how far ahead of the target time we are. - */ - const int64_t ahead = tvsub_us(now, rtp_timeval); /* microseconds */ - /* Only delay at all if we are nontrivially ahead. */ - if(ahead > rtp_delay_threshold) { - /* Don't delay by the full amount */ - usleep(ahead - rtp_delay_threshold / 2); - /* Refetch time (so we don't get out of step with reality) */ - xgettimeofday(&now, NULL); - } - } - header.timestamp = htonl((uint32_t)rtp_timestamp); + uaudio_schedule_synchronize(); + header.timestamp = htonl((uint32_t)uaudio_schedule_timestamp); int written_bytes; do { written_bytes = writev(rtp_fd, vec, 2); @@ -195,17 +120,8 @@ retry: } else rtp_errors /= 2; /* gradual decay */ written_bytes -= sizeof (struct rtp_header); - size_t written_samples = written_bytes / uaudio_sample_size; - /* rtp_timestamp and rtp_timestamp are supposed to refer to the first sample - * of the next packet */ - rtp_timestamp += written_samples; - const unsigned usec = (rtp_timeval.tv_usec - + 1000000 * written_samples / (uaudio_rate - * uaudio_channels)); - /* ...will only overflow 32 bits if one packet is more than about half an - * hour long, which is not plausible. */ - rtp_timeval.tv_sec += usec / 1000000; - rtp_timeval.tv_usec = usec % 1000000; + const size_t written_samples = written_bytes / uaudio_sample_size; + uaudio_schedule_update(written_samples); return written_samples; } @@ -228,17 +144,16 @@ static void rtp_open(void) { socklen_t len; char *sockname, *ssockname; struct stringlist dst, src; - const char *delay; /* Get configuration */ dst.n = 2; dst.s = xcalloc(2, sizeof *dst.s); - dst.s[0] = uaudio_get("rtp-destination"); - dst.s[1] = uaudio_get("rtp-destination-port"); + dst.s[0] = uaudio_get("rtp-destination", NULL); + dst.s[1] = uaudio_get("rtp-destination-port", NULL); src.n = 2; src.s = xcalloc(2, sizeof *dst.s); - src.s[0] = uaudio_get("rtp-source"); - src.s[1] = uaudio_get("rtp-source-port"); + src.s[0] = uaudio_get("rtp-source", NULL); + src.s[1] = uaudio_get("rtp-source-port", NULL); if(!dst.s[0]) fatal(0, "'rtp-destination' not set"); if(!dst.s[1]) @@ -249,10 +164,8 @@ static void rtp_open(void) { src.n = 2; } else src.n = 0; - if((delay = uaudio_get("rtp-delay-threshold"))) - rtp_delay_threshold = atoi(delay); - else - rtp_delay_threshold = 1000; /* microseconds */ + rtp_delay_threshold = atoi(uaudio_get("rtp-delay-threshold", "1000")); + /* ...microseconds */ /* Resolve addresses */ res = get_address(&dst, &pref, &sockname); @@ -269,10 +182,8 @@ static void rtp_open(void) { fatal(errno, "error creating broadcast socket"); if(multicast(res->ai_addr)) { /* Enable multicast options */ - const char *ttls = uaudio_get("multicast-ttl"); - const int ttl = ttls ? atoi(ttls) : 1; - const char *loops = uaudio_get("multicast-loop"); - const int loop = loops ? !strcmp(loops, "yes") : 1; + const int ttl = atoi(uaudio_get("multicast-ttl", "1")); + const int loop = !strcmp(uaudio_get("multicast-loop", "yes"), "yes"); switch(res->ai_family) { case PF_INET: { if(setsockopt(rtp_fd, IPPROTO_IP, IP_MULTICAST_TTL, @@ -340,14 +251,6 @@ static void rtp_open(void) { fatal(errno, "error binding broadcast socket to %s", ssockname); if(connect(rtp_fd, res->ai_addr, res->ai_addrlen) < 0) fatal(errno, "error connecting broadcast socket to %s", sockname); - /* Various fields are required to have random initial values by RFC3550. The - * packet contents are highly public so there's no point asking for very - * strong randomness. */ - gcry_create_nonce(&rtp_id, sizeof rtp_id); - gcry_create_nonce(&rtp_sequence, sizeof rtp_sequence); - gcry_create_nonce(&rtp_timestamp, sizeof rtp_timestamp); - /* rtp_play() will spot this and choose an initial value */ - rtp_timeval.tv_sec = 0; } static void rtp_start(uaudio_callback *callback, @@ -364,7 +267,13 @@ static void rtp_start(uaudio_callback *callback, else fatal(0, "asked for %d/%d/%d 16/44100/1 and 16/44100/2", uaudio_bits, uaudio_rate, uaudio_channels); + /* Various fields are required to have random initial values by RFC3550. The + * packet contents are highly public so there's no point asking for very + * strong randomness. */ + gcry_create_nonce(&rtp_id, sizeof rtp_id); + gcry_create_nonce(&rtp_sequence, sizeof rtp_sequence); rtp_open(); + uaudio_schedule_init(); uaudio_thread_start(callback, userdata, rtp_play, @@ -380,7 +289,7 @@ static void rtp_stop(void) { } static void rtp_activate(void) { - rtp_reactivated = 1; + uaudio_schedule_reactivated = 1; uaudio_thread_activate(); } @@ -388,13 +297,33 @@ static void rtp_deactivate(void) { uaudio_thread_deactivate(); } +static void rtp_configure(void) { + char buffer[64]; + + uaudio_set("rtp-destination", config->broadcast.s[0]); + uaudio_set("rtp-destination-port", config->broadcast.s[1]); + if(config->broadcast_from.n) { + uaudio_set("rtp-source", config->broadcast_from.s[0]); + uaudio_set("rtp-source-port", config->broadcast_from.s[0]); + } else { + uaudio_set("rtp-source", NULL); + uaudio_set("rtp-source-port", NULL); + } + snprintf(buffer, sizeof buffer, "%ld", config->multicast_ttl); + uaudio_set("multicast-ttl", buffer); + uaudio_set("multicast-loop", config->multicast_loop ? "yes" : "no"); + snprintf(buffer, sizeof buffer, "%ld", config->rtp_delay_threshold); + uaudio_set("delay-threshold", buffer); +} + const struct uaudio uaudio_rtp = { .name = "rtp", .options = rtp_options, .start = rtp_start, .stop = rtp_stop, .activate = rtp_activate, - .deactivate = rtp_deactivate + .deactivate = rtp_deactivate, + .configure = rtp_configure, }; /*