+ 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;
+ break;
+ case 1:
+ case 2:
+ /* Use command-line ADDRESS+PORT or just PORT */
+ sl.n = argc;
+ sl.s = argv;
+ break;
+ default:
+ disorder_fatal(0, "usage: disorder-playrtp [OPTIONS] [[ADDRESS] PORT]");
+ }
+ disorder_info("version "VERSION" process ID %lu",
+ (unsigned long)getpid());
+ struct sockaddr *addr;
+ socklen_t addr_len;
+ if(!strcmp(sl.s[0], "-")) {
+ /* 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(bestifa == NULL
+ || compare_interfaces(ifa, bestifa, family) > 0)
+ bestifa = ifa;
+ }
+ 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);
+ /* Bind the address */
+ if(bind(rtpfd, bestifa->ifa_addr,
+ family == AF_INET
+ ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)) < 0)
+ disorder_fatal(errno, "error binding socket");
+ static struct sockaddr_storage bound_address;
+ addr = (struct sockaddr *)&bound_address;
+ addr_len = sizeof bound_address;
+ if(getsockname(rtpfd, addr, &addr_len) < 0)
+ disorder_fatal(errno, "error getting socket address");
+ /* Convert to string */
+ char addrname[128], portname[32];
+ if(getnameinfo(addr, addr_len,
+ addrname, sizeof addrname,
+ portname, sizeof portname,
+ NI_NUMERICHOST|NI_NUMERICSERV) < 0)
+ disorder_fatal(errno, "getnameinfo");
+ /* 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));
+ } else {
+ /* Look up address and port */
+ if(!(res = get_address(&sl, &prefs, &sockname)))
+ exit(1);
+ addr = res->ai_addr;
+ addr_len = res->ai_addrlen;
+ /* Create the socket */
+ if((rtpfd = socket(res->ai_family,
+ res->ai_socktype,
+ res->ai_protocol)) < 0)
+ disorder_fatal(errno, "error creating socket");
+ /* Allow multiple listeners */
+ xsetsockopt(rtpfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
+ is_multicast = multicast(addr);
+ /* The multicast and unicast/broadcast cases are different enough that they
+ * are totally split. Trying to find commonality between them causes more
+ * trouble that it's worth. */
+ if(is_multicast) {
+ /* Stash the multicast group address */
+ memcpy(&mgroup, addr, addr_len);
+ switch(res->ai_addr->sa_family) {
+ case AF_INET:
+ mgroup.in.sin_port = 0;
+ break;
+ case AF_INET6:
+ mgroup.in6.sin6_port = 0;
+ break;
+ default:
+ disorder_fatal(0, "unsupported address family %d",
+ (int)addr->sa_family);
+ }
+ /* Bind to to the multicast group address */
+ if(bind(rtpfd, addr, addr_len) < 0)
+ disorder_fatal(errno, "error binding socket to %s",
+ format_sockaddr(addr));
+ /* Add multicast group membership */
+ switch(mgroup.sa.sa_family) {
+ case PF_INET:
+ mreq.imr_multiaddr = mgroup.in.sin_addr;
+ mreq.imr_interface.s_addr = 0; /* use primary interface */
+ if(setsockopt(rtpfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
+ &mreq, sizeof mreq) < 0)
+ disorder_fatal(errno, "error calling setsockopt IP_ADD_MEMBERSHIP");
+ break;
+ case PF_INET6:
+ mreq6.ipv6mr_multiaddr = mgroup.in6.sin6_addr;
+ memset(&mreq6.ipv6mr_interface, 0, sizeof mreq6.ipv6mr_interface);
+ if(setsockopt(rtpfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
+ &mreq6, sizeof mreq6) < 0)
+ disorder_fatal(errno, "error calling setsockopt IPV6_JOIN_GROUP");
+ break;
+ default:
+ disorder_fatal(0, "unsupported address family %d", res->ai_family);
+ }
+ /* Report what we did */
+ disorder_info("listening on %s multicast group %s",
+ format_sockaddr(addr), format_sockaddr(&mgroup.sa));
+ } else {
+ /* Bind to 0/port */
+ switch(addr->sa_family) {
+ case AF_INET: {
+ struct sockaddr_in *in = (struct sockaddr_in *)addr;
+
+ memset(&in->sin_addr, 0, sizeof (struct in_addr));
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)addr;
+
+ memset(&in6->sin6_addr, 0, sizeof (struct in6_addr));
+ break;
+ }
+ default:
+ disorder_fatal(0, "unsupported family %d", (int)addr->sa_family);
+ }
+ if(bind(rtpfd, addr, addr_len) < 0)
+ disorder_fatal(errno, "error binding socket to %s",
+ format_sockaddr(addr));
+ /* Report what we did */
+ disorder_info("listening on %s", format_sockaddr(addr));
+ }
+ }
+ len = sizeof rcvbuf;
+ if(getsockopt(rtpfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) < 0)
+ disorder_fatal(errno, "error calling getsockopt SO_RCVBUF");
+ if(target_rcvbuf > rcvbuf) {
+ if(setsockopt(rtpfd, SOL_SOCKET, SO_RCVBUF,
+ &target_rcvbuf, sizeof target_rcvbuf) < 0)
+ disorder_error(errno, "error calling setsockopt SO_RCVBUF %d",
+ target_rcvbuf);
+ /* We try to carry on anyway */
+ else
+ disorder_info("changed socket receive buffer from %d to %d",
+ rcvbuf, target_rcvbuf);
+ } else
+ disorder_info("default socket receive buffer %d", rcvbuf);
+ //info("minbuffer %u maxbuffer %u", minbuffer, maxbuffer);
+ if(logfp)
+ disorder_info("WARNING: -L option can impact performance");
+ if(control_socket) {
+ pthread_t tid;
+
+ if((err = pthread_create(&tid, 0, control_thread, 0)))
+ disorder_fatal(err, "pthread_create control_thread");
+ }
+ if(dumpfile) {
+ int fd;
+ unsigned char buffer[65536];
+ size_t written;
+
+ if((fd = open(dumpfile, O_RDWR|O_TRUNC|O_CREAT, 0666)) < 0)
+ disorder_fatal(errno, "opening %s", dumpfile);
+ /* Fill with 0s to a suitable size */
+ memset(buffer, 0, sizeof buffer);
+ for(written = 0; written < dump_size * sizeof(int16_t);
+ written += sizeof buffer) {
+ if(write(fd, buffer, sizeof buffer) < 0)
+ disorder_fatal(errno, "clearing %s", dumpfile);
+ }
+ /* Map the buffer into memory for convenience */
+ dump_buffer = mmap(0, dump_size * sizeof(int16_t), PROT_READ|PROT_WRITE,
+ MAP_SHARED, fd, 0);
+ if(dump_buffer == (void *)-1)
+ disorder_fatal(errno, "mapping %s", dumpfile);
+ disorder_info("dumping to %s", dumpfile);
+ }
+ /* Set up output. Currently we only support L16 so there's no harm setting
+ * the format before we know what it is! */
+ uaudio_set_format(44100/*Hz*/, 2/*channels*/,
+ 16/*bits/channel*/, 1/*signed*/);
+ uaudio_set("application", "disorder-playrtp");
+ backend->configure();
+ backend->start(playrtp_callback, NULL);
+ if(backend->open_mixer) backend->open_mixer();
+ /* We receive and convert audio data in a background thread */
+ if((err = pthread_create(<id, 0, listen_thread, 0)))
+ disorder_fatal(err, "pthread_create listen_thread");
+ /* We have a second thread to add received packets to the queue */
+ if((err = pthread_create(<id, 0, queue_thread, 0)))
+ disorder_fatal(err, "pthread_create queue_thread");
+ pthread_mutex_lock(&lock);
+ time_t lastlog = 0;
+ for(;;) {
+ /* Wait for the buffer to fill up a bit */
+ playrtp_fill_buffer();
+ /* Start playing now */
+ disorder_info("Playing...");
+ next_timestamp = pheap_first(&packets)->timestamp;
+ active = 1;
+ pthread_mutex_unlock(&lock);
+ backend->activate();
+ pthread_mutex_lock(&lock);
+ /* Wait until the buffer empties out
+ *
+ * If there's a packet that we can play right now then we definitely
+ * continue.
+ *
+ * Also if there's at least minbuffer samples we carry on regardless and
+ * insert silence. The assumption is there's been a pause but more data
+ * is now available.
+ */
+ while(nsamples >= minbuffer
+ || (nsamples > 0
+ && contains(pheap_first(&packets), next_timestamp))) {
+ if(monitor) {
+ time_t now = xtime(0);
+
+ if(now >= lastlog + 60) {
+ int offset = nsamples - minbuffer;
+ double offtime = (double)offset / (uaudio_rate * uaudio_channels);
+ disorder_info("%+d samples off (%d.%02ds, %d bytes)",
+ offset,
+ (int)fabs(offtime) * (offtime < 0 ? -1 : 1),
+ (int)(fabs(offtime) * 100) % 100,
+ offset * uaudio_bits / CHAR_BIT);
+ lastlog = now;
+ }
+ }
+ //fprintf(stderr, "%8u/%u (%u) PLAYING\n", nsamples, maxbuffer, minbuffer);
+ pthread_cond_wait(&cond, &lock);
+ }
+#if 0
+ if(nsamples) {
+ struct packet *p = pheap_first(&packets);
+ fprintf(stderr, "nsamples=%u (%u) next_timestamp=%"PRIx32", first packet is [%"PRIx32",%"PRIx32")\n",
+ nsamples, minbuffer, next_timestamp,p->timestamp,p->timestamp+p->nsamples);
+ }
+#endif
+ /* Stop playing for a bit until the buffer re-fills */
+ pthread_mutex_unlock(&lock);
+ backend->deactivate();
+ pthread_mutex_lock(&lock);
+ active = 0;
+ /* Go back round */
+ }