X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/blobdiff_plain/662887576254386b8e3481f6f3e6f0289f2500aa..8d251217b5cfeae9d277054a456c4064eabde569:/clients/playrtp.c diff --git a/clients/playrtp.c b/clients/playrtp.c index 4ea5e3b..c6f0ad9 100644 --- a/clients/playrtp.c +++ b/clients/playrtp.c @@ -24,20 +24,20 @@ * systems. There is no support for Microsoft Windows yet, and that will in * fact probably an entirely separate program. * - * The program runs (at least) three threads. listen_thread() is responsible - * for reading RTP packets off the wire and adding them to the linked list @ref - * received_packets, assuming they are basically sound. queue_thread() takes - * packets off this linked list and adds them to @ref packets (an operation - * which might be much slower due to contention for @ref lock). + * The program runs (at least) three threads: * - * The main thread is responsible for actually playing audio. In ALSA this - * means it waits until ALSA says it's ready for more audio which it then - * plays. See @ref clients/playrtp-alsa.c. + * listen_thread() is responsible for reading RTP packets off the wire and + * adding them to the linked list @ref received_packets, assuming they are + * basically sound. * - * In Core Audio the main thread is only responsible for starting and stopping - * play: the system does the actual playback in its own private thread, and - * calls adioproc() to fetch the audio data. See @ref - * clients/playrtp-coreaudio.c. + * queue_thread() takes packets off this linked list and adds them to @ref + * packets (an operation which might be much slower due to contention for @ref + * lock). + * + * control_thread() accepts commands from Disobedience (or anything else). + * + * The main thread activates and deactivates audio playing via the @ref + * lib/uaudio.h API (which probably implies at least one further thread). * * Sometimes it happens that there is no audio available to play. This may * because the server went away, or a packet was dropped, or the server @@ -104,7 +104,7 @@ unsigned minbuffer = 2 * 44100 / 10; /* 0.2 seconds */ /** @brief Buffer high watermark * * We'll only start playing when this many samples are available. */ -static unsigned readahead = 2 * 2 * 44100; +static unsigned readahead = 44100; /* 0.5 seconds */ /** @brief Maximum buffer size * @@ -217,6 +217,7 @@ static const struct option options[] = { #endif { "dump", required_argument, 0, 'r' }, { "command", required_argument, 0, 'e' }, + { "pause-mode", required_argument, 0, 'P' }, { "socket", required_argument, 0, 's' }, { "config", required_argument, 0, 'C' }, { 0, 0, 0, 0 } @@ -492,7 +493,9 @@ static void help(void) { #if HAVE_COREAUDIO_AUDIOHARDWARE_H " --core-audio, -c Use Core Audio to play audio\n" #endif - " --command, -e COMMAND Pipe audio to command\n" + " --command, -e COMMAND Pipe audio to command.\n" + " --pause-mode, -P silence For -e: pauses send silence (default)\n" + " --pause-mode, -P suspend For -e: pauses suspend writes\n" " --help, -h Display usage message\n" " --version, -V Display version number\n" ); @@ -580,6 +583,7 @@ int main(int argc, char **argv) { union any_sockaddr mgroup; const char *dumpfile = 0; pthread_t ltid; + static const int one = 1; static const struct addrinfo prefs = { .ai_flags = AI_PASSIVE, @@ -590,8 +594,8 @@ int main(int argc, char **argv) { mem_init(); if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale"); - backend = &UAUDIO_DEFAULT; - while((n = getopt_long(argc, argv, "hVdD:m:b:x:L:R:M:aocC:re:", options, 0)) >= 0) { + backend = uaudio_apis[0]; + while((n = getopt_long(argc, argv, "hVdD:m:b:x:L:R:M:aocC:re:P:", options, 0)) >= 0) { switch(n) { case 'h': help(); case 'V': version("disorder-playrtp"); @@ -615,6 +619,7 @@ int main(int argc, char **argv) { case 's': control_socket = optarg; break; case 'r': dumpfile = optarg; break; case 'e': backend = &uaudio_command; uaudio_set("command", optarg); break; + case 'P': uaudio_set("pause-mode", optarg); break; default: fatal(0, "invalid option"); } } @@ -651,8 +656,14 @@ int main(int argc, char **argv) { res->ai_socktype, res->ai_protocol)) < 0) fatal(errno, "error creating socket"); - /* Stash the multicast group address */ - if((is_multicast = multicast(res->ai_addr))) { + /* 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: @@ -661,24 +672,13 @@ int main(int argc, char **argv) { case AF_INET6: mgroup.in6.sin6_port = 0; break; + default: + fatal(0, "unsupported family %d", (int)res->ai_addr->sa_family); } - } - /* Bind to 0/port */ - switch(res->ai_addr->sa_family) { - case AF_INET: - memset(&((struct sockaddr_in *)res->ai_addr)->sin_addr, 0, - sizeof (struct in_addr)); - break; - case AF_INET6: - memset(&((struct sockaddr_in6 *)res->ai_addr)->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", sockname); - if(is_multicast) { + /* 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; @@ -697,10 +697,35 @@ int main(int argc, char **argv) { 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 + } 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"); @@ -763,7 +788,9 @@ int main(int argc, char **argv) { 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 @@ -771,7 +798,9 @@ int main(int argc, char **argv) { 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 */ }