X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/163ed8a5e867848f98cbdd1f040381aefe2cbf40..HEAD:/clients/playrtp.c diff --git a/clients/playrtp.c b/clients/playrtp.c index b5da73f..2f98415 100644 --- a/clients/playrtp.c +++ b/clients/playrtp.c @@ -74,6 +74,7 @@ #include "configuration.h" #include "addr.h" #include "syscalls.h" +#include "printf.h" #include "rtp.h" #include "defs.h" #include "vector.h" @@ -99,7 +100,7 @@ static FILE *logfp; /** @brief Output device */ /** @brief Buffer low watermark in samples */ -unsigned minbuffer = 4 * (2 * 44100) / 10; /* 0.4 seconds */ +unsigned minbuffer; /** @brief Maximum buffer size in samples * @@ -216,6 +217,7 @@ static const struct option options[] = { { "pause-mode", required_argument, 0, 'P' }, { "socket", required_argument, 0, 's' }, { "config", required_argument, 0, 'C' }, + { "user-config", required_argument, 0, 'u' }, { "monitor", no_argument, 0, 'M' }, { 0, 0, 0, 0 } }; @@ -508,7 +510,8 @@ static void attribute((noreturn)) help(void) { " --min, -m FRAMES Buffer low water mark\n" " --max, -x FRAMES Buffer maximum size\n" " --rcvbuf, -R BYTES Socket receive buffer size\n" - " --config, -C PATH Set configuration file\n" + " --config, -C PATH Set system configuration file\n" + " --user-config, -u PATH Set user configuration file\n" " --api, -A API Select audio API. Possibilities:\n" " "); int first = 1; @@ -634,59 +637,12 @@ static size_t playrtp_callback(void *buffer, return samples; } -static int compare_family(const struct ifaddrs *a, - const struct ifaddrs *b, - int family) { - int afamily = a->ifa_addr->sa_family; - int bfamily = b->ifa_addr->sa_family; - if(afamily != bfamily) { - /* Preferred family wins */ - if(afamily == family) return 1; - if(bfamily == family) return -1; - /* Either there's no preference or it doesn't help. Prefer IPv4 */ - if(afamily == AF_INET) return 1; - if(bfamily == AF_INET) return -1; - /* Failing that prefer IPv6 */ - if(afamily == AF_INET6) return 1; - if(bfamily == AF_INET6) return -1; - } - return 0; -} - -static int compare_flags(const struct ifaddrs *a, - const struct ifaddrs *b) { - unsigned aflags = a->ifa_flags, bflags = b->ifa_flags; - /* Up interfaces are better than down ones */ - unsigned aup = aflags & IFF_UP, bup = bflags & IFF_UP; - if(aup != bup) - return aup > bup ? 1 : -1; -#if IFF_DYNAMIC - /* Static addresses are better than dynamic */ - unsigned adynamic = aflags & IFF_DYNAMIC, bdynamic = bflags & IFF_DYNAMIC; - if(adynamic != bdynamic) - return adynamic < bdynamic ? 1 : -1; -#endif - unsigned aloopback = aflags & IFF_LOOPBACK, bloopback = bflags & IFF_LOOPBACK; - /* Static addresses are better than dynamic */ - if(aloopback != bloopback) - return aloopback < bloopback ? 1 : -1; - return 0; -} - -static int compare_interfaces(const struct ifaddrs *a, - const struct ifaddrs *b, - int family) { - int c; - if((c = compare_family(a, b, family))) return c; - return compare_flags(a, b); -} - int main(int argc, char **argv) { int n, err; struct addrinfo *res; struct stringlist sl; char *sockname; - int rcvbuf, target_rcvbuf = 0; + int rcvbuf, target_rcvbuf = -1; socklen_t len; struct ip_mreq mreq; struct ipv6_mreq mreq6; @@ -704,7 +660,7 @@ int main(int argc, char **argv) { int monitor = 0; static const int one = 1; - static const struct addrinfo prefs = { + struct addrinfo prefs = { .ai_flags = AI_PASSIVE, .ai_family = PF_INET, .ai_socktype = SOCK_DGRAM, @@ -716,7 +672,7 @@ int main(int argc, char **argv) { logdate = 1; mem_init(); if(!setlocale(LC_CTYPE, "")) disorder_fatal(errno, "error calling setlocale"); - while((n = getopt_long(argc, argv, "hVdD:m:x:L:R:aocC:re:P:MA:", options, 0)) >= 0) { + while((n = getopt_long(argc, argv, "hVdD:m:x:L:R:aocC:u:re:P:MA:", options, 0)) >= 0) { switch(n) { case 'h': help(); case 'V': version("disorder-playrtp"); @@ -745,6 +701,7 @@ int main(int argc, char **argv) { #endif case 'A': backend = uaudio_find(optarg); break; case 'C': configfile = optarg; break; + case 'u': userconfigfile = optarg; break; case 's': control_socket = optarg; break; case 'r': dumpfile = optarg; break; case 'e': backend = &uaudio_command; uaudio_set("command", optarg); break; @@ -767,23 +724,47 @@ int main(int argc, char **argv) { * CoreAudio/AudioHardware.h). */ disorder_fatal(0, "cannot play RTP through RTP"); } - if(!maxbuffer) - maxbuffer = 2 * minbuffer; + /* Set buffering parameters if not overridden */ + if(!minbuffer) { + minbuffer = config->rtp_minbuffer; + if(!minbuffer) minbuffer = (2*44100)*4/10; + } + if(!maxbuffer) { + maxbuffer = config->rtp_maxbuffer; + if(!maxbuffer) maxbuffer = 2 * minbuffer; + } + if(target_rcvbuf < 0) target_rcvbuf = config->rtp_rcvbuf; argc -= optind; argv += optind; switch(argc) { case 0: - /* Get configuration from server */ - if(!(c = disorder_new(1))) exit(EXIT_FAILURE); - if(disorder_connect(c)) exit(EXIT_FAILURE); - if(disorder_rtp_address(c, &address, &port)) exit(EXIT_FAILURE); - sl.n = 2; - sl.s = xcalloc(2, sizeof *sl.s); - sl.s[0] = address; - sl.s[1] = port; + sl.s = xcalloc(3, sizeof *sl.s); + if(config->rtp_always_request) { + sl.s[0] = sl.s[1] = (/*unconst*/ char *)"-"; + sl.n = 2; + } else { + /* Get configuration from server */ + if(!(c = disorder_new(1))) exit(EXIT_FAILURE); + if(disorder_connect(c)) exit(EXIT_FAILURE); + if(disorder_rtp_address(c, &address, &port)) exit(EXIT_FAILURE); + sl.s[0] = address; + sl.s[1] = port; + sl.n = 2; + } + /* If we're requesting a new stream then apply the local network address + * overrides. + */ + if(!strcmp(sl.s[0], "-")) { + if(config->rtp_request_address.port) + byte_xasprintf(&sl.s[1], "%d", config->rtp_request_address.port); + if(config->rtp_request_address.address) { + sl.s[2] = sl.s[1]; + sl.s[1] = config->rtp_request_address.address; + sl.n = 3; + } + } break; - case 1: - case 2: + case 1: case 2: case 3: /* Use command-line ADDRESS+PORT or just PORT */ sl.n = argc; sl.s = argv; @@ -796,35 +777,77 @@ int main(int argc, char **argv) { struct sockaddr *addr; socklen_t addr_len; if(!strcmp(sl.s[0], "-")) { + /* Syntax: - [[ADDRESS] PORT]. Here, the PORT may be `-' to get the local + * kernel to choose. The ADDRESS may be omitted or `-' to pick something + * suitable. */ + const char *node, *svc; + struct sockaddr *sa = 0; + switch (sl.n) { +#define NULLDASH(s) (strcmp((s), "-") ? (s) : 0) + case 1: node = 0; svc = 0; break; + case 2: node = 0; svc = NULLDASH(sl.s[1]); break; + case 3: node = NULLDASH(sl.s[1]); svc = NULLDASH(sl.s[2]); break; + default: disorder_fatal(0, "too many listening-address compoennts"); +#undef NULLDASH + } /* We'll need a connection to request the incoming stream, so open one if * we don't have one already */ if(!c) { if(!(c = disorder_new(1))) exit(EXIT_FAILURE); if(disorder_connect(c)) exit(EXIT_FAILURE); } - /* Pick address family to match known-working connectivity to the server */ - int family = disorder_client_af(c); - /* Get a list of interfaces */ - struct ifaddrs *ifa, *bestifa = NULL; - if(getifaddrs(&ifa) < 0) - disorder_fatal(errno, "error calling getifaddrs"); - /* Try to pick a good one */ - for(; ifa; ifa = ifa->ifa_next) { - if(!ifa->ifa_addr) continue; - if(bestifa == NULL - || compare_interfaces(ifa, bestifa, family) > 0) - bestifa = ifa; + /* If no address was given, we need to pick one. But we already have a + * connection to the server, so we can probably use the address from that. + */ + struct sockaddr_storage ss; + if(!node) { + addr_len = sizeof ss; + if(disorder_client_sockname(c, (struct sockaddr *)&ss, &addr_len)) + exit(EXIT_FAILURE); + if(ss.ss_family != AF_INET && ss.ss_family != AF_INET6) { + /* We're using a Unix-domain socket, so use a loopback address. I'm + * cowardly using IPv4 here. */ + struct sockaddr_in *sin = (struct sockaddr_in *)&ss; + sin->sin_family = AF_INET; + sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); + } + sa = (struct sockaddr *)&ss; + prefs.ai_family = sa->sa_family; + } + /* If we have an address or port to resolve then do that now */ + if (node || svc) { + struct addrinfo *ai; + char errbuf[1024]; + int rc; + if((rc = getaddrinfo(node, svc, &prefs, &ai))) + disorder_fatal(0, "failed to resolve address `%s' and service `%s': %s", + node ? node : "-", svc ? svc : "-", + format_error(ec_getaddrinfo, rc, + errbuf, sizeof(errbuf))); + if(!sa) + sa = ai->ai_addr; + else { + assert(sa->sa_family == ai->ai_addr->sa_family); + switch(sa->sa_family) { + case AF_INET: + ((struct sockaddr_in *)sa)->sin_port = + ((struct sockaddr_in *)ai->ai_addr)->sin_port; + break; + case AF_INET6: + ((struct sockaddr_in6 *)sa)->sin6_port = + ((struct sockaddr_in6 *)ai->ai_addr)->sin6_port; + break; + default: + assert(!"unexpected address family"); + } + } } - if(!bestifa) - disorder_fatal(0, "failed to select a network interface"); - family = bestifa->ifa_addr->sa_family; - if((rtpfd = socket(family, - SOCK_DGRAM, - IPPROTO_UDP)) < 0) - disorder_fatal(errno, "error creating socket (family %d)", family); + if((rtpfd = socket(sa->sa_family, SOCK_DGRAM, IPPROTO_UDP)) < 0) + disorder_fatal(errno, "error creating socket (family %d)", + sa->sa_family); /* Bind the address */ - if(bind(rtpfd, bestifa->ifa_addr, - family == AF_INET + if(bind(rtpfd, sa, + sa->sa_family == AF_INET ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)) < 0) disorder_fatal(errno, "error binding socket"); static struct sockaddr_storage bound_address; @@ -842,8 +865,10 @@ int main(int argc, char **argv) { /* Ask for audio data */ if(disorder_rtp_request(c, addrname, portname)) exit(EXIT_FAILURE); /* Report what we did */ - disorder_info("listening on %s", format_sockaddr(addr)); + disorder_info("listening on %s (stream requested)", + format_sockaddr(addr)); } else { + if(sl.n > 2) disorder_fatal(0, "too many address components"); /* Look up address and port */ if(!(res = get_address(&sl, &prefs, &sockname))) exit(1);