X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/22b9fa74de8e80471a5033ea067d3b360930b91d..d0b6635eee6d656502d129e88c65a5014c8e042f:/server/speaker-network.c?ds=sidebyside diff --git a/server/speaker-network.c b/server/speaker-network.c index fab81a8..b77334c 100644 --- a/server/speaker-network.c +++ b/server/speaker-network.c @@ -1,6 +1,6 @@ /* * This file is part of DisOrder - * Copyright (C) 2005, 2006, 2007 Richard Kettlewell + * Copyright (C) 2005-2008 Richard Kettlewell * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,6 +30,10 @@ #include #include #include +#include +#include +#include +#include #include "configuration.h" #include "syscalls.h" @@ -37,6 +41,7 @@ #include "addr.h" #include "timeval.h" #include "rtp.h" +#include "ifreq.h" #include "speaker-protocol.h" #include "speaker.h" @@ -79,35 +84,22 @@ static int audio_errors; static void network_init(void) { struct addrinfo *res, *sres; static const struct addrinfo pref = { - 0, - PF_INET, - SOCK_DGRAM, - IPPROTO_UDP, - 0, - 0, - 0, - 0 + .ai_flags = 0, + .ai_family = PF_INET, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, }; static const struct addrinfo prefbind = { - AI_PASSIVE, - PF_INET, - SOCK_DGRAM, - IPPROTO_UDP, - 0, - 0, - 0, - 0 + .ai_flags = AI_PASSIVE, + .ai_family = PF_INET, + .ai_socktype = SOCK_DGRAM, + .ai_protocol = IPPROTO_UDP, }; static const int one = 1; int sndbuf, target_sndbuf = 131072; socklen_t len; char *sockname, *ssockname; - /* Override sample format */ - config->sample_format.rate = 44100; - config->sample_format.channels = 2; - config->sample_format.bits = 16; - config->sample_format.byte_format = AO_FMT_BIG; res = get_address(&config->broadcast, &pref, &sockname); if(!res) exit(-1); if(config->broadcast_from.n) { @@ -119,8 +111,54 @@ static void network_init(void) { 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"); + if(multicast(res->ai_addr)) { + /* Multicasting */ + switch(res->ai_family) { + case PF_INET: { + const int mttl = config->multicast_ttl; + if(setsockopt(bfd, IPPROTO_IP, IP_MULTICAST_TTL, &mttl, sizeof mttl) < 0) + fatal(errno, "error setting IP_MULTICAST_TTL on multicast socket"); + if(setsockopt(bfd, IPPROTO_IP, IP_MULTICAST_LOOP, + &config->multicast_loop, sizeof one) < 0) + fatal(errno, "error setting IP_MULTICAST_LOOP on multicast socket"); + break; + } + case PF_INET6: { + const int mttl = config->multicast_ttl; + if(setsockopt(bfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, + &mttl, sizeof mttl) < 0) + fatal(errno, "error setting IPV6_MULTICAST_HOPS on multicast socket"); + if(setsockopt(bfd, IPPROTO_IP, IPV6_MULTICAST_LOOP, + &config->multicast_loop, sizeof (int)) < 0) + fatal(errno, "error setting IPV6_MULTICAST_LOOP on multicast socket"); + break; + } + default: + fatal(0, "unsupported address family %d", res->ai_family); + } + info("multicasting on %s", sockname); + } else { + struct ifaddrs *ifs; + + if(getifaddrs(&ifs) < 0) + fatal(errno, "error calling getifaddrs"); + while(ifs) { + /* (At least on Darwin) IFF_BROADCAST might be set but ifa_broadaddr + * still a null pointer. It turns out that there's a subsequent entry + * for he same interface which _does_ have ifa_broadaddr though... */ + if((ifs->ifa_flags & IFF_BROADCAST) + && ifs->ifa_broadaddr + && sockaddr_equal(ifs->ifa_broadaddr, res->ai_addr)) + break; + ifs = ifs->ifa_next; + } + if(ifs) { + if(setsockopt(bfd, SOL_SOCKET, SO_BROADCAST, &one, sizeof one) < 0) + fatal(errno, "error setting SO_BROADCAST on broadcast socket"); + info("broadcasting on %s (%s)", sockname, ifs->ifa_name); + } else + info("unicasting on %s", sockname); + } len = sizeof sndbuf; if(getsockopt(bfd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &len) < 0) @@ -143,18 +181,20 @@ static void network_init(void) { 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); } /** @brief Play over the network */ static size_t network_play(size_t frames) { struct rtp_header header; struct iovec vec[2]; - size_t bytes = frames * device_bpf, written_frames; + size_t bytes = frames * bpf, written_frames; int written_bytes; /* We transmit using RTP (RFC3550) and attempt to conform to the internet * AVT profile (RFC3551). */ + /* If we're starting then initialize the base time */ + if(!rtp_time) + xgettimeofday(&rtp_time_0, 0); if(idled) { /* There may have been a gap. Fix up the RTP time accordingly. */ struct timeval now; @@ -165,9 +205,14 @@ static size_t network_play(size_t frames) { 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; + if(delta > UINT64_MAX / 88200) + fatal(0, "rtp_time=%llu now=%ld.%06ld rtp_time_0=%ld.%06ld delta=%llu (%lld)", + rtp_time, + (long)now.tv_sec, (long)now.tv_usec, + (long)rtp_time_0.tv_sec, (long)rtp_time_0.tv_usec, + delta, delta); + target_rtp_time = (delta * config->sample_format.rate + * config->sample_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 @@ -175,24 +220,16 @@ static size_t network_play(size_t frames) { * 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. + * play at t=2s. Suppose 44100Hz stereo. We send 1s of audio over the + * next (about) one second, 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. + * It's never right to reduce rtp_time, for that would imply packets with + * overlapping timestamp ranges, which does not make sense. */ target_rtp_time &= ~(uint64_t)1; /* stereo! */ if(target_rtp_time > rtp_time) { @@ -202,15 +239,8 @@ static size_t network_play(size_t frames) { 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); - } + info("would reverse rtp_time by %"PRIu64" samples", + rtp_time - target_rtp_time); } } header.vpxcc = 2 << 6; /* V=2, P=0, X=0, CC=0 */ @@ -226,7 +256,7 @@ static size_t network_play(size_t frames) { if(bytes > NETWORK_BYTES - sizeof header) { bytes = NETWORK_BYTES - sizeof header; /* Always send a whole number of frames */ - bytes -= bytes % device_bpf; + bytes -= 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 @@ -252,37 +282,50 @@ static size_t network_play(size_t frames) { } else audio_errors /= 2; written_bytes -= sizeof (struct rtp_header); - written_frames = written_bytes / device_bpf; + written_frames = written_bytes / bpf; /* Advance RTP's notion of the time */ - rtp_time += written_frames * playing->format.channels; + rtp_time += written_frames * config->sample_format.channels; return written_frames; } static int bfd_slot; /** @brief Set up poll array for network play */ -static void network_beforepoll(void) { +static void network_beforepoll(int *timeoutp) { struct timeval now; uint64_t target_us; uint64_t target_rtp_time; - const int64_t samples_ahead = ((uint64_t)RTP_AHEAD_MS - * config->sample_format.rate - * config->sample_format.channels - / 1000); + const int64_t samples_per_second = config->sample_format.rate + * config->sample_format.channels; + int64_t lead, ahead_ms; /* If we're starting then initialize the base time */ if(!rtp_time) xgettimeofday(&rtp_time_0, 0); - /* We send audio data whenever we get RTP_AHEAD seconds or more - * behind */ + /* We send audio data whenever we would otherwise get behind */ xgettimeofday(&now, 0); target_us = tvsub_us(now, rtp_time_0); - assert(target_us <= UINT64_MAX / 88200); + if(target_us > UINT64_MAX / 88200) + fatal(0, "rtp_time=%llu rtp_time_0=%ld.%06ld now=%ld.%06ld target_us=%llu (%lld)\n", + rtp_time, + (long)rtp_time_0.tv_sec, (long)rtp_time_0.tv_usec, + (long)now.tv_sec, (long)now.tv_usec, + target_us, target_us); target_rtp_time = (target_us * config->sample_format.rate * config->sample_format.channels) / 1000000; - if((int64_t)(rtp_time - target_rtp_time) < samples_ahead) + /* Lead is how far ahead we are */ + lead = rtp_time - target_rtp_time; + if(lead <= 0) + /* We're behind or even, so we'll need to write as soon as we can */ bfd_slot = addfd(bfd, POLLOUT); + else { + /* We've ahead, we can afford to wait a bit even if the IP stack thinks it + * can accept more. */ + ahead_ms = 1000 * lead / samples_per_second; + if(ahead_ms < *timeoutp) + *timeoutp = ahead_ms; + } } /** @brief Process poll() results for network play */ @@ -295,7 +338,7 @@ static int network_ready(void) { const struct speaker_backend network_backend = { BACKEND_NETWORK, - FIXED_FORMAT, + 0, network_init, 0, /* activate */ network_play,