#include "configuration.h"
#include "addr.h"
#include "syscalls.h"
+#include "printf.h"
#include "rtp.h"
#include "defs.h"
#include "vector.h"
/** @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
*
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;
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,
}
}
if(config_read(0, NULL)) disorder_fatal(0, "cannot read configuration");
+ /* Choose a sensible default audio backend */
if(!backend) {
backend = uaudio_default(uaudio_apis, UAUDIO_API_CLIENT);
if(!backend)
* 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;
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;
/* 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);