+ /* Allow multiple listeners */
+ xsetsockopt(rtpfd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one);
+ is_multicast = multicast(res->ai_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, res->ai_addr, res->ai_addrlen);
+ 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:
+ fatal(0, "unsupported family %d", (int)res->ai_addr->sa_family);
+ }
+ /* Bind to to the multicast group address */
+ if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0)
+ fatal(errno, "error binding socket to %s", format_sockaddr(res->ai_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)
+ 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)
+ fatal(errno, "error calling setsockopt IPV6_JOIN_GROUP");
+ break;
+ default:
+ fatal(0, "unsupported address family %d", res->ai_family);
+ }
+ /* Report what we did */
+ info("listening on %s multicast group %s",
+ format_sockaddr(res->ai_addr), format_sockaddr(&mgroup.sa));
+ } else {
+ /* Bind to 0/port */
+ switch(res->ai_addr->sa_family) {
+ case AF_INET: {
+ struct sockaddr_in *in = (struct sockaddr_in *)res->ai_addr;
+
+ memset(&in->sin_addr, 0, sizeof (struct in_addr));
+ if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0)
+ fatal(errno, "error binding socket to 0.0.0.0 port %d",
+ ntohs(in->sin_port));
+ break;
+ }
+ case AF_INET6: {
+ struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)res->ai_addr;
+
+ memset(&in6->sin6_addr, 0, sizeof (struct in6_addr));
+ break;
+ }
+ default:
+ fatal(0, "unsupported family %d", (int)res->ai_addr->sa_family);
+ }
+ if(bind(rtpfd, res->ai_addr, res->ai_addrlen) < 0)
+ fatal(errno, "error binding socket to %s", format_sockaddr(res->ai_addr));
+ /* Report what we did */
+ info("listening on %s", format_sockaddr(res->ai_addr));
+ }
+ len = sizeof rcvbuf;
+ if(getsockopt(rtpfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &len) < 0)
+ fatal(errno, "error calling getsockopt SO_RCVBUF");
+ if(target_rcvbuf > rcvbuf) {
+ if(setsockopt(rtpfd, SOL_SOCKET, SO_RCVBUF,
+ &target_rcvbuf, sizeof target_rcvbuf) < 0)
+ error(errno, "error calling setsockopt SO_RCVBUF %d",
+ target_rcvbuf);
+ /* We try to carry on anyway */
+ else
+ info("changed socket receive buffer from %d to %d",
+ rcvbuf, target_rcvbuf);
+ } else
+ info("default socket receive buffer %d", rcvbuf);
+ if(logfp)
+ info("WARNING: -L option can impact performance");
+ if(control_socket) {
+ pthread_t tid;
+
+ if((err = pthread_create(&tid, 0, control_thread, 0)))
+ 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)
+ 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)
+ 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)
+ fatal(errno, "mapping %s", dumpfile);
+ 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*/);
+ backend->start(playrtp_callback, NULL);
+ /* We receive and convert audio data in a background thread */
+ if((err = pthread_create(<id, 0, listen_thread, 0)))
+ 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)))
+ fatal(err, "pthread_create queue_thread");
+ pthread_mutex_lock(&lock);
+ for(;;) {
+ /* Wait for the buffer to fill up a bit */
+ playrtp_fill_buffer();
+ /* Start playing now */
+ 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 */
+ while(nsamples >= minbuffer
+ || (nsamples > 0
+ && contains(pheap_first(&packets), next_timestamp))) {
+ pthread_cond_wait(&cond, &lock);
+ }
+ /* 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 */
+ }