chiark / gitweb /
Merge config aliasing bug fix.
authorRichard Kettlewell <rjk@greenend.org.uk>
Sat, 14 Mar 2009 19:03:08 +0000 (19:03 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sat, 14 Mar 2009 19:03:08 +0000 (19:03 +0000)
36 files changed:
.bzrignore
CHANGES.html
README.upgrades
clients/Makefile.am
clients/disorder.c
clients/playrtp.c
debian/postrm.disorder-server
disobedience/disobedience.c
disobedience/login.c
doc/disorder-playrtp.1.in
doc/disorder_config.5.in
lib/addr.c
lib/addr.h
lib/basen.c
lib/basen.h
lib/client-common.c
lib/configuration.c
lib/configuration.h
lib/random.c
lib/uaudio-alsa.c
lib/uaudio-command.c
lib/uaudio-coreaudio.c
lib/uaudio-oss.c
lib/uaudio-rtp.c
lib/uaudio-thread.c
lib/uaudio.c
lib/uaudio.h
libtests/t-basen.c
plugins/Makefile.am
server/Makefile.am
server/disorderd.c
server/endian.c [new file with mode: 0644]
server/server.c
server/speaker.c
server/state.c
tests/dtest.py

index e5fb4931a6fb37405c8362b20d4775d78f79eaed..ec15e8ead6300c20032851c6812dcdbb6c69e172 100644 (file)
@@ -201,3 +201,4 @@ plugins/index.html
 doc/disorder-choose.8
 doc/disorder-choose.8.html
 config.aux/compile
+server/endian
index 3722b613bfd004b602587cfabf07025f66e6cd24..fe3c687f138509bc6ed222cd3469c64b4a0b5d97 100644 (file)
@@ -62,15 +62,29 @@ span.command {
 
   <div class=section>
   
-    <h3>Mac OS X</h3>
-  
+    <h3>Server</h3>
     <div class=section>
 
-      <p>The <tt>device</tt> configuration option and <tt>--device</tt> option
-      to <tt>disorder-playrtp</tt> now work.  Devices may be specified either
-      by UID or name.  Fixes <a
+      <p>The <tt>device</tt> configuration option work under OS X.  Devices may
+      be specified either by UID or name.  Fixes <a
        href="http://code.google.com/p/disorder/issues/detail?id=27">Issue
       27</a>.</p>
+
+      <p>Confirmation URLs for online registrations are cleaner now.</p>
+
+    </div>
+      
+    <h3>RTP Player</h3>
+  
+    <div class=section>
+
+      <p>There is a new <tt>--command</tt> option which allows the RTP player
+      to send audio data to a user-chosen command instead of an audio API. See
+      the man page for details.</p>
+      
+      <p>The <tt>--device</tt> option to <tt>disorder-playrtp</tt> now works
+      under OS X (as above).</p>
         
     </div>
 
index c586dd87804865bb6f1340a446e2f19910fc3099..cbe86373ebff24bb87f2f4a981f5ac10807f9450 100644 (file)
@@ -17,6 +17,14 @@ all 1.1.x versions.
 
 If you install from .deb files then much of this work is automated.
 
+* 4.x -> 4.4
+
+The syntax of confirmation strings for online registrations has changed and old
+ones no longer work.  This only affects users who registered before the upgrade
+but have not yet confirmed their login.  You can delete such half-created users
+with 'disorder deluser USERNAME' (as an administrative user, for instance as
+root on the server) and they can start the registration process again.
+
 * 3.0 -> 4.x
 
 If you customized any of the templates, you will pretty much have to start from
index 5456bd1c8ee4f34458dbf4b9d6fa9e3490e49c7e..1ec55a98b9f713513dec27ec98ade75b95e1b98d 100644 (file)
@@ -25,7 +25,7 @@ AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib
 disorder_SOURCES=disorder.c authorize.c authorize.h \
        ../lib/memgc.c
 disorder_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
-       $(LIBGC) $(LIBGCRYPT) $(LIBPCRE) $(LIBDB)
+       $(LIBGC) $(LIBGCRYPT) $(LIBPCRE) $(LIBDB) $(LIBPTHREAD)
 disorder_DEPENDENCIES=$(LIBOBJS) ../lib/libdisorder.a
 
 disorderfm_SOURCES=disorderfm.c \
index 8dc6d7fda7f35baa96bc38f0c78ddbbc1a93f13f..24663111ae745942148ea3c64d3035ad6ee524eb 100644 (file)
@@ -32,6 +32,7 @@
 #include <unistd.h>
 #include <pcre.h>
 #include <ctype.h>
+#include <gcrypt.h>
 
 #include "configuration.h"
 #include "syscalls.h"
@@ -761,9 +762,14 @@ int main(int argc, char **argv) {
   if(password)
     config->password = password;
   if(local)
-    config->connect.n = 0;
+    config->connect.af = -1;
   n = optind;
   optind = 1;                          /* for subsequent getopt calls */
+  /* gcrypt initialization */
+  if(!gcry_check_version(NULL))
+    disorder_fatal(0, "gcry_check_version failed");
+  gcry_control(GCRYCTL_INIT_SECMEM, 0);
+  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
   /* accumulate command args */
   while(n < argc) {
     if((i = TABLE_FIND(commands, name, argv[n])) < 0)
index 03e4ad2e297de1a3c10601f3c8c3c6418953a234..338cbbfc90aafd99953cbb56a2ae3c6d4b12a0ca 100644 (file)
@@ -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,
@@ -591,7 +595,7 @@ int main(int argc, char **argv) {
   mem_init();
   if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
   backend = uaudio_apis[0];
-  while((n = getopt_long(argc, argv, "hVdD:m:b:x:L:R:M:aocC:re:", options, 0)) >= 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 */
   }
index 7ba0933d4f4aef6efc8bfc54582b0240bc4159bb..4cd590a460baccd27a21a3a5e0511aa74a7dc69b 100755 (executable)
@@ -30,6 +30,7 @@ cleanup_remove() {
   rm -f $state/__db.*
   rm -f $state/noticed.db
   rm -f $state/search.db
+  rm -f $state/isearch.db
   rm -f $state/tags.db
   rm -f $state/tracks.db
 }
@@ -43,6 +44,7 @@ cleanup_purge() {
   rm -f $state/prefs.db
   rm -f $state/schedule.db
   rm -f $state/users.db
+  rm -f $state/playlists.db
 }
 
 case "$1" in
index e0fa295bbb0185a9e078bf4adc86cf2cbac2baa5..dea9d59276b0bc65a587a2f7120ef59bd66a4a37 100644 (file)
@@ -25,6 +25,7 @@
 #include <getopt.h>
 #include <locale.h>
 #include <pcre.h>
+#include <gcrypt.h>
 
 /* Apologies for the numerous de-consting casts, but GLib et al do not seem to
  * have heard of const. */
@@ -448,6 +449,11 @@ int main(int argc, char **argv) {
   }
   if(!gtkok)
     fatal(0, "failed to initialize GTK+");
+  /* gcrypt initialization */
+  if(!gcry_check_version(NULL))
+    disorder_fatal(0, "gcry_check_version failed");
+  gcry_control(GCRYCTL_INIT_SECMEM, 0);
+  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
   signal(SIGPIPE, SIG_IGN);
   init_styles();
   load_settings();
index 1c6df79c57107c834dd9bc21fd74ff136e5f23e9..36850c11da60b9f201c59da9b0033c3bea1cdedc 100644 (file)
@@ -73,7 +73,7 @@ static void default_connect(void) {
   if(config->password)
     return;
   /* If we already have a host and/or port that's good too */
-  if(config->connect.n)
+  if(config->connect.af != -1)
     return;
   /* If there's a suitable socket that's probably what we wanted */
   const char *s = config_get_file("socket");
@@ -84,11 +84,21 @@ static void default_connect(void) {
 }
 
 static const char *get_hostname(void) {
-  return config->connect.n >= 2 ? config->connect.s[0] : "";
+  if(config->connect.af == -1 || !config->connect.address)
+    return "";
+  else
+    return config->connect.address;
 }
 
 static const char *get_service(void) {
-  return config->connect.n >= 2 ? config->connect.s[1] : "";
+  if(config->connect.af == -1)
+    return "";
+  else {
+    char *s;
+
+    byte_xasprintf(&s, "%d", config->connect.port);
+    return s;
+  }
 }
 
 static const char *get_username(void) {
@@ -100,11 +110,13 @@ static const char *get_password(void) {
 }
 
 static void set_hostname(struct config *c, const char *s) {
-  c->connect.s[0] = (char *)s;
+  if(c->connect.af == -1)
+    c->connect.af = AF_UNSPEC;
+  c->connect.address = xstrdup(s);
 }
 
 static void set_service(struct config *c, const char *s) {
-  c->connect.s[1] = (char *)s;
+  c->connect.port = atoi(s);
 }
 
 static void set_username(struct config *c, const char *s) {
@@ -131,13 +143,10 @@ static void login_update_config(struct config *c) {
   size_t n;
   const gboolean remote = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(lwi_remote));
 
-  if(remote) {
-    c->connect.n = 2;
-    c->connect.s = xcalloc(2, sizeof (char *));
-  } else {
-    c->connect.n = 0;
-    c->connect.s = 0;
-  }
+  if(remote)
+    c->connect.af = AF_UNSPEC;
+  else
+    c->connect.af = -1;
   for(n = 0; n < NLWIS; ++n)
     if(remote || !(lwis[n].flags & LWI_REMOTE))
       lwis[n].set(c, xstrdup(gtk_entry_get_text(GTK_ENTRY(lwi_entry[n]))));
@@ -161,10 +170,12 @@ static void login_save_config(void) {
                    "password %s\n",
                    quoteutf8(config->username),
                    quoteutf8(config->password));
-  if(rc >= 0 && config->connect.n)
-    rc = fprintf(fp, "connect %s %s\n",
-                 quoteutf8(config->connect.s[0]),
-                 quoteutf8(config->connect.s[1]));
+  if(rc >= 0 && config->connect.af != -1) {
+    char **vec;
+
+    netaddress_format(&config->connect, NULL, &vec);
+    rc = fprintf(fp, "connect %s %s %s\n", vec[0], vec[1], vec[2]);
+  }
   if(rc < 0) {
     fpopup_msg(GTK_MESSAGE_ERROR, "error writing to %s: %s",
                tmp, strerror(errno));
@@ -327,7 +338,7 @@ void login_box(void) {
   }
   /* Initial settings */
   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(lwi_remote),
-                               config->connect.n >= 2);
+                               config->connect.af != -1);
   lwi_remote_toggled(GTK_TOGGLE_BUTTON(lwi_remote), 0);
   buttonbox = create_buttons(buttons, NBUTTONS);
   vbox = gtk_vbox_new(FALSE, 1);
index ea4c93fe408f775e6487cd15e8a44aab19ffcb94..c313ece3aed08856b7499414dbc41b6e1b5129f7 100644 (file)
@@ -66,7 +66,8 @@ If \fICOMMAND\fR exits it is re-executed; any samples that had been written to
 the pipe but not processed by the previous instance will be lost.
 .IP
 .B \-\-device
-is redundant with this option.
+is redundant with this option, but you might want to set
+.BR \-\-pause\-mode .
 .IP
 As an example,
 .B "-e \(aqcat > dump\(aq"
@@ -75,6 +76,12 @@ You could convert it to another format with, for instance:
 .IP
 .B "sox -c2 -traw -r44100 -s -w dump dump.wav"
 .TP
+.B \-\-pause\-mode \fIMODE\fR, \fB-P \fIMODE
+Set the pause mode for \fB\-\-command\fR to either \fBsilence\fR (the default), in
+which pauses are represented by sending silent samples, or \fBsuspend\fR, in which
+writes to  the subprocess are suspended, requiring it to infer a pause from flow
+control.
+.TP
 .B \-\-config \fIPATH\fR, \fB\-C \fIPATH
 Set the configuration file.
 The default is
index d033258359123b9e76b5b86242d4c23aa7b08dcc..1813152b0038474109ca1101ccf3d857a5d9b0f7 100644 (file)
@@ -275,6 +275,10 @@ Execute a command.
 This is the default if
 .B speaker_command
 is specified, or if no native is available.
+.IP
+You might want to set
+.B pause_mode
+with this backend.
 .TP
 .B rtp
 Transmit audio over the network.
@@ -293,14 +297,22 @@ See
 .BR disorder_protocol (5)
 for more details.
 .TP
-.B broadcast \fIADDRESS\fR \fIPORT\fR
+.B broadcast \fR[\fIFAMILY\fR] \fIADDRESS\fR \fIPORT\fR
 Transmit sound data to \fIADDRESS\fR using UDP port \fIPORT\fR.
 This implies \fBapi rtp\fR.
 .IP
+\fIFAMILY\fR can be \fB-4\fR or \fB-6\fR to force IPv4 or IPv6, if this is not
+implied by \fIADDRESS\fR.
+Note that IPv6 is not currently well tested.
+.IP
 See also \fBmulticast_loop\fR and \fBmulticast_ttl\fR.
 .TP
-.B broadcast_from \fIADDRESS\fR \fIPORT\fR
+.B broadcast_from \fR[\fIFAMILY\fR] \fIADDRESS\fR \fIPORT\fR
 Sets the (local) source address used by \fBbroadcast\fR.
+.IP
+\fIFAMILY\fR can be \fB-4\fR or \fB-6\fR to force IPv4 or IPv6, if this is not
+implied by \fIADDRESS\fR.
+Note that IPv6 is not currently well tested.
 .TP
 .B channel \fICHANNEL\fR
 The mixer channel that the volume control should use.
@@ -309,6 +321,7 @@ For \fBapi oss\fR the possible values are:
 .RS
 .TP 8
 .B pcm
+
 Output level for the audio device.
 This is probably what you want and is the default.
 .TP
@@ -409,10 +422,14 @@ reinstated.
 Specifies the number of recently played tracks to remember (including
 failed tracks and scratches).
 .TP
-.B listen \fR[\fIHOST\fR] \fISERVICE\fR
+.B listen \fR[\fIFAMILY\fR] \fR[\fIHOST\fR] \fISERVICE\fR
 Listen for connections on the address specified by \fIHOST\fR and port
 specified by \fISERVICE\fR.
-If \fIHOST\fR is omitted then listens on all local addresses.
+If \fIHOST\fR is omitted, or is \fB*\fR, then listens on all local addresses.
+.IP
+\fIFAMILY\fR can be \fB-4\fR or \fB-6\fR to force IPv4 or IPv6, if this is not
+implied by \fIHOST\fR.
+Note that IPv6 is not currently well tested.
 .IP
 Normally the server only listens on a UNIX domain socket.
 .TP
@@ -527,6 +544,19 @@ The maximum days that a track can survive in the database of newly added
 tracks.
 The default is 31.
 .TP
+.B pause_mode \fIMODE
+Sets the pause mode for the \fBcommand\fR backend.
+The possible values are:
+.RS
+.TP
+.B silence
+Send silent (0-value) samples when paused.
+This is the default.
+.TP
+.B suspend
+Stop writing when paused.
+.RE
+.TP
 .B player \fIPATTERN\fR \fIMODULE\fR [\fIOPTIONS.. [\fB\-\-\fR]] \fIARGS\fR...
 Specifies the player for files matching the glob \fIPATTERN\fR.
 \fIMODULE\fR specifies which plugin module to use.
@@ -712,9 +742,13 @@ These options would normally be used in \fI~\fRUSERNAME\fI/.disorder/passwd\fR
 or
 \fIpkgconfdir/config.\fRUSERNAME.
 .TP
-.B connect \fIHOST SERVICE\fR
+.B connect \fR[\fIFAMILY\fR] \fIHOST SERVICE\fR
 Connect to the address specified by \fIHOST\fR and port specified by
 \fISERVICE\fR.
+.IP
+\fIFAMILY\fR can be \fB-4\fR or \fB-6\fR to force IPv4 or IPv6, if this is not
+implied by \fIHOST\fR.
+Note that IPv6 is not currently well tested.
 .TP
 .B password \fIPASSWORD\fR
 Specify password.
index fecc28c24b467612e3df8b4dbe626ddabcd007de..ecbab22e7c48ea9d4f66e8658c334d1aef3d8c97 100644 (file)
@@ -30,6 +30,9 @@
 #include "printf.h"
 #include "addr.h"
 #include "mem.h"
+#include "syscalls.h"
+#include "configuration.h"
+#include "vector.h"
 
 /** @brief Convert a pair of strings to an address
  * @param a Pointer to string list
@@ -92,29 +95,38 @@ struct addrinfo *get_address(const struct stringlist *a,
  */
 int addrinfocmp(const struct addrinfo *a,
                const struct addrinfo *b) {
-  const struct sockaddr_in *ina, *inb;
-  const struct sockaddr_in6 *in6a, *in6b;
-  
   if(a->ai_family != b->ai_family) return a->ai_family - b->ai_family;
   if(a->ai_socktype != b->ai_socktype) return a->ai_socktype - b->ai_socktype;
   if(a->ai_protocol != b->ai_protocol) return a->ai_protocol - b->ai_protocol;
-  switch(a->ai_family) {
+  return sockaddrcmp(a->ai_addr, b->ai_addr);
+}
+
+/** @brief Comparison function for socket addresses
+ *
+ * Suitable for qsort().
+ */
+int sockaddrcmp(const struct sockaddr *a,
+               const struct sockaddr *b) {
+  const struct sockaddr_in *ina, *inb;
+  const struct sockaddr_in6 *in6a, *in6b;
+  
+  if(a->sa_family != b->sa_family) return a->sa_family - b->sa_family;
+  switch(a->sa_family) {
   case PF_INET:
-    ina = (const struct sockaddr_in *)a->ai_addr;
-    inb = (const struct sockaddr_in *)b->ai_addr;
+    ina = (const struct sockaddr_in *)a;
+    inb = (const struct sockaddr_in *)b;
     if(ina->sin_port != inb->sin_port) return ina->sin_port - inb->sin_port;
     return ina->sin_addr.s_addr - inb->sin_addr.s_addr;
     break;
   case PF_INET6:
-    in6a = (const struct sockaddr_in6 *)a->ai_addr;
-    in6b = (const struct sockaddr_in6 *)b->ai_addr;
+    in6a = (const struct sockaddr_in6 *)a;
+    in6b = (const struct sockaddr_in6 *)b;
     if(in6a->sin6_port != in6b->sin6_port)
       return in6a->sin6_port - in6b->sin6_port;
     return memcmp(&in6a->sin6_addr, &in6b->sin6_addr,
                  sizeof (struct in6_addr));
   default:
-    error(0, "unsupported protocol family %d", a->ai_protocol);
-    return memcmp(a->ai_addr, b->ai_addr, a->ai_addrlen); /* kludge */
+    fatal(0, "unsupported protocol family %d", a->sa_family);
   }
 }
 
@@ -194,6 +206,142 @@ char *format_sockaddr(const struct sockaddr *sa) {
   }
 }
 
+/** @brief Parse the text form of a network address
+ * @param na Where to store result
+ * @param nvec Number of strings
+ * @param vec List of strings
+ * @return 0 on success, -1 on syntax error
+ */
+int netaddress_parse(struct netaddress *na,
+                    int nvec,
+                    char **vec) {
+  const char *port;
+
+  na->af = AF_UNSPEC;
+  if(nvec > 0 && vec[0][0] == '-') {
+    if(!strcmp(vec[0], "-4"))
+      na->af = AF_INET;
+    else if(!strcmp(vec[0], "-6")) 
+      na->af = AF_INET6;
+    else if(!strcmp(vec[0], "-unix"))
+      na->af = AF_UNIX;
+    else if(!strcmp(vec[0], "-"))
+      na->af = AF_UNSPEC;
+    else
+      return -1;
+    --nvec;
+    ++vec;
+  }
+  if(nvec == 0)
+    return -1;
+  /* Possibilities are:
+   *
+   *       /path/to/unix/socket      an AF_UNIX socket
+   *       * PORT                    any address, specific port
+   *       PORT                      any address, specific port
+   *       ADDRESS PORT              specific address, specific port
+   */
+  if(vec[0][0] == '/' && na->af == AF_UNSPEC)
+    na->af = AF_UNIX;
+  if(na->af == AF_UNIX) {
+    if(nvec != 1)
+      return -1;
+    na->address = xstrdup(vec[0]);
+    na->port = -1;                     /* makes no sense */
+  } else {
+    switch(nvec) {
+    case 1:
+      na->address = NULL;
+      port = vec[0];
+      break;
+    case 2:
+      if(!strcmp(vec[0], "*"))
+       na->address = NULL;
+      else
+       na->address = xstrdup(vec[0]);
+      port = vec[1];
+      break;
+    default:
+      return -1;
+    }
+    if(port[strspn(port, "0123456789")])
+      return -1;
+    long p;
+    int e = xstrtol(&p, port, NULL, 10);
+
+    if(e)
+      return -1;
+    if(p > 65535)
+      return -1;
+    na->port = (int)p;
+  }
+  return 0;
+}
+
+/** @brief Format a @ref netaddress structure
+ * @param na Network address to format
+ * @param nvecp Where to put string count, or NULL
+ * @param vecp Where to put strings
+ *
+ * The formatted form is suitable for passing to netaddress_parse().
+ */
+void netaddress_format(const struct netaddress *na,
+                      int *nvecp,
+                      char ***vecp) {
+  struct vector v[1];
+
+  vector_init(v);
+  switch(na->af) {
+  case AF_UNSPEC: vector_append(v, xstrdup("-")); break;
+  case AF_INET: vector_append(v, xstrdup("-4")); break;
+  case AF_INET6: vector_append(v, xstrdup("-6")); break;
+  case AF_UNIX: vector_append(v, xstrdup("-unix")); break;
+  }
+  if(na->address)
+    vector_append(v, xstrdup(na->address));
+  else
+    vector_append(v, xstrdup("*"));
+  if(na->port != -1) {
+    char buffer[64];
+
+    snprintf(buffer, sizeof buffer, "%d", na->port);
+    vector_append(v, xstrdup(buffer));
+  }
+  vector_terminate(v);
+  if(nvecp)
+    *nvecp = v->nvec;
+  if(vecp)
+    *vecp = v->vec;
+}
+
+/** @brief Resolve a network address
+ * @param na Address structure
+ * @param passive True if passive (bindable) address is desired
+ * @param protocol Protocol number desired (e.g. @c IPPROTO_TCP)
+ * @return List of suitable addresses or NULL
+ */
+struct addrinfo *netaddress_resolve(const struct netaddress *na,
+                                   int passive,
+                                   int protocol) {
+  struct addrinfo *res, hints[1];
+  char service[64];
+  int rc;
+
+  memset(hints, 0, sizeof hints);
+  hints->ai_family = na->af;
+  hints->ai_protocol = protocol;
+  hints->ai_flags = passive ? AI_PASSIVE : 0;
+  snprintf(service, sizeof service, "%d", na->port);
+  rc = getaddrinfo(na->address, service, hints, &res);
+  if(rc) {
+    error(0, "getaddrinfo %s %d: %s",
+         na->address ? na->address : "*",
+         na->port, gai_strerror(rc));
+    return NULL;
+  }
+  return res;
+}
+
 /*
 Local Variables:
 c-basic-offset:2
index 973733f57ae9a2aa74581eb6b44551100ef94acc..90c165eadf242c46223db49a14ed8fa646d3f5e0 100644 (file)
 
 #include <netdb.h>
 
-#include "configuration.h"
+struct stringlist;
+
+/** @brief A network address */
+struct netaddress {
+  /** @brief Address family
+   *
+   * Typically @c AF_UNIX, @c AF_INET, @c AF_INET6 or @c AF_UNSPEC.
+   * Set to -1 to mean 'no address'.
+   */
+  int af;
+
+  /** @brief Address or NULL for 'any' */
+  char *address;
+
+  /** @brief Port number */
+  int port;
+};
 
 struct addrinfo *get_address(const struct stringlist *a,
                             const struct addrinfo *pref,
@@ -31,10 +47,22 @@ struct addrinfo *get_address(const struct stringlist *a,
 
 int addrinfocmp(const struct addrinfo *a,
                const struct addrinfo *b);
+int sockaddrcmp(const struct sockaddr *a,
+               const struct sockaddr *b);
 
 int multicast(const struct sockaddr *sa);
 char *format_sockaddr(const struct sockaddr *sa);
 
+int netaddress_parse(struct netaddress *na,
+                    int nvec,
+                    char **vec);
+void netaddress_format(const struct netaddress *na,
+                      int *nvecp,
+                      char ***vecp);
+struct addrinfo *netaddress_resolve(const struct netaddress *na,
+                                   int passive,
+                                   int protocol);
+
 #endif /* ADDR_H */
 
 /*
index 87d2795340d28657fd53616e86aff57d30009c99..7c1f9ec5c3ad72c4a0a81004b5a6e287b4ddf72a 100644 (file)
@@ -31,7 +31,7 @@
  * @param nwords Length of bignum
  * @return !v
  */
-static int zero(const unsigned long *v, int nwords) {
+static int zero(const uint32_t *v, int nwords) {
   int n;
 
   for(n = 0; n < nwords && !v[n]; ++n)
@@ -47,7 +47,7 @@ static int zero(const unsigned long *v, int nwords) {
  *
  * The quotient is stored in @p v.
  */
-static unsigned divide(unsigned long *v, int nwords, unsigned long m) {
+static unsigned divide(uint32_t *v, int nwords, unsigned long m) {
   unsigned long r = 0, a, b;
   int n;
 
@@ -66,6 +66,31 @@ static unsigned divide(unsigned long *v, int nwords, unsigned long m) {
   return r;
 }
 
+/** @brief Multiple v by m and add a
+ * @param v Pointer to bigendian bignum
+ * @param nwords Length of bignum
+ * @param m Value to multiply by
+ * @param a Value to add
+ * @return 0 on success, non-0 on overflow
+ *
+ * Does v = m * v + a.
+ */
+static int mla(uint32_t *v, int nwords, uint32_t m, uint32_t a) {
+  int n = nwords - 1;
+  uint32_t carry = a;
+
+  while(n >= 0) {
+    const uint64_t p = (uint64_t)v[n] * m + carry;
+    carry = (uint32_t)(p >> 32);
+    v[n] = (uint32_t)p;
+    --n;
+  }
+  /* If there is still a carry then we overflowed */
+  return !!carry;
+}
+
+static const char basen_chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
 /** @brief Convert v to a chosen base
  * @param v Pointer to bigendian bignum (modified!)
  * @param nwords Length of bignum
@@ -75,26 +100,53 @@ static unsigned divide(unsigned long *v, int nwords, unsigned long m) {
  * @return 0 on success, -1 if the buffer is too small
  *
  * Converts @p v to a string in the given base using decimal digits, lower case
- * letter sand upper case letters as digits.
+ * letters and upper case letters as digits.
+ *
+ * The inverse of nesab().
  */
-int basen(unsigned long *v,
+int basen(uint32_t *v,
          int nwords,
          char buffer[],
          size_t bufsize,
          unsigned base) {
   size_t i = bufsize;
-  static const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
   
   do {
     if(i <= 1)
       return -1;                       /* overflow */
-    buffer[--i] = chars[divide(v, nwords, base)];
+    buffer[--i] = basen_chars[divide(v, nwords, base)];
   } while(!zero(v, nwords));
   memmove(buffer, buffer + i, bufsize - i);
   buffer[bufsize - i] = 0;
   return 0;
 }
 
+/** @brief Convert a string back to a large integer in an arbitrary base
+ * @param v Where to store result as a bigendian bignum
+ * @param nwords Space available in @p v
+ * @param s Input string
+ * @param base Number base (2..62)
+ * @return 0 on success, non-0 on overflow or invalid input
+ *
+ * The inverse of basen().  If the number is much smaller than the buffer then
+ * the first words will be 0.
+ */
+int nesab(uint32_t *v,
+          int nwords,
+          const char *s,
+          unsigned base) {
+  /* Initialize to 0 */
+  memset(v, 0, nwords * sizeof *v);
+  while(*s) {
+    const char *dp = strchr(basen_chars, *s++);
+    if(!dp)
+      return -1;
+    if(mla(v, nwords, base, dp - basen_chars))
+      return -1;
+  }
+  return 0;
+}
+
 /*
 Local Variables:
 c-basic-offset:2
index 717f2eaeb48396fa887dc3b3883d9aeb9ceef2a1..c1a60f0c7835c728b649d0a95048e6186c0057e7 100644 (file)
@@ -19,7 +19,7 @@
 #ifndef BASEN_H
 #define BASEN_H
 
-int basen(unsigned long *v,
+int basen(uint32_t *v,
          int nwords,
          char buffer[],
          size_t bufsize,
@@ -29,6 +29,11 @@ int basen(unsigned long *v,
  * Returns 0 on success or -1 on overflow.
  */
 
+int nesab(uint32_t *v,
+          int nwords,
+          const char *s,
+          unsigned base);
+
 #endif /* BASEN_H */
 
 /*
index ced82e4fb6eaf71199dc79fd422cb9ae4cbca8d4..aa4312430a3ae837c83b4eabeb63ab92b323fd2b 100644 (file)
@@ -45,17 +45,11 @@ socklen_t find_server(struct config *c,
   struct addrinfo *res = 0;
   char *name;
   socklen_t len;
-   
-  static const struct addrinfo pref = {
-    .ai_flags = 0,
-    .ai_family = PF_INET,
-    .ai_socktype = SOCK_STREAM,
-    .ai_protocol = IPPROTO_TCP,
-  };
 
-  if(c->connect.n) {
-    res = get_address(&c->connect, &pref, &name);
-    if(!res) return -1;
+  if(c->connect.af != -1) {
+    res = netaddress_resolve(&c->connect, 0, IPPROTO_TCP);
+    if(!res) 
+      return -1;
     sa = res->ai_addr;
     len = res->ai_addrlen;
   } else {
@@ -73,7 +67,7 @@ socklen_t find_server(struct config *c,
   *sap = xmalloc_noptr(len);
   memcpy(*sap, sa, len);
   if(namep)
-    *namep = name;
+    *namep = format_sockaddr(sa);
   if(res)
     freeaddrinfo(res);
   return len;
index 958ea6cb3633f051cba67b86b9229ef259fcf33e..1fcfb1a6eefd138ef66aaba05dfa47438632c8da 100644 (file)
@@ -489,6 +489,18 @@ static int set_rights(const struct config_state *cs,
   return 0;
 }
 
+static int set_netaddress(const struct config_state *cs,
+                         const struct conf *whoami,
+                         int nvec, char **vec) {
+  struct netaddress *na = ADDRESS(cs->config, struct netaddress);
+
+  if(netaddress_parse(na, nvec, vec)) {
+    error(0, "%s:%d: invalid network address", cs->path, cs->line);
+    return -1;
+  }
+  return 0;
+}
+
 /* free functions */
 
 static void free_none(struct config attribute((unused)) *c,
@@ -573,6 +585,13 @@ static void free_transformlist(struct config *c,
   xfree(tl->t);
 }
 
+static void free_netaddress(struct config *c,
+                           const struct conf *whoami) {
+  struct netaddress *na = ADDRESS(c, struct netaddress);
+
+  xfree(na->address);
+}
+
 /* configuration types */
 
 static const struct conftype
@@ -588,6 +607,7 @@ static const struct conftype
   type_restrict = { set_restrict, free_none },
   type_namepart = { set_namepart, free_namepartlist },
   type_transform = { set_transform, free_transformlist },
+  type_netaddress = { set_netaddress, free_netaddress },
   type_rights = { set_rights, free_none };
 
 /* specific validation routine */
@@ -814,45 +834,6 @@ static int validate_alias(const struct config_state *cs,
   return 0;
 }
 
-static int validate_addrport(const struct config_state attribute((unused)) *cs,
-                            int nvec,
-                            char attribute((unused)) **vec) {
-  switch(nvec) {
-  case 0:
-    error(0, "%s:%d: missing address",
-         cs->path, cs->line);
-    return -1;
-  case 1:
-    error(0, "%s:%d: missing port name/number",
-         cs->path, cs->line);
-    return -1;
-  case 2:
-    return 0;
-  default:
-    error(0, "%s:%d: expected ADDRESS PORT",
-         cs->path, cs->line);
-    return -1;
-  }
-}
-
-static int validate_port(const struct config_state attribute((unused)) *cs,
-                        int nvec,
-                        char attribute((unused)) **vec) {
-  switch(nvec) {
-  case 0:
-    error(0, "%s:%d: missing address",
-         cs->path, cs->line);
-    return -1;
-  case 1:
-  case 2:
-    return 0;
-  default:
-    error(0, "%s:%d: expected [ADDRESS] PORT",
-         cs->path, cs->line);
-    return -1;
-  }
-}
-
 static int validate_algo(const struct config_state attribute((unused)) *cs,
                         int nvec,
                         char **vec) {
@@ -890,6 +871,31 @@ static int validate_backend(const struct config_state attribute((unused)) *cs,
   return 0;
 }
 
+static int validate_pausemode(const struct config_state attribute((unused)) *cs,
+                              int nvec,
+                              char **vec) {
+  if(nvec == 1 && (!strcmp(vec[0], "silence") || !strcmp(vec[0], "suspend")))
+    return 0;
+  error(0, "%s:%d: invalid pause mode", cs->path, cs->line);
+  return -1;
+}
+
+static int validate_destaddr(const struct config_state attribute((unused)) *cs,
+                            int nvec,
+                            char **vec) {
+  struct netaddress na[1];
+
+  if(netaddress_parse(na, nvec, vec)) {
+    error(0, "%s:%d: invalid network address", cs->path, cs->line);
+    return -1;
+  }
+  if(!na->address) {
+    error(0, "%s:%d: destination address required", cs->path, cs->line);
+    return -1;
+  }
+  return 0;
+}
+
 /** @brief Item name and and offset */
 #define C(x) #x, offsetof(struct config, x)
 /** @brief Item name and and offset */
@@ -901,13 +907,13 @@ static const struct conf conf[] = {
   { C(allow),            &type_stringlist_accum, validate_allow },
   { C(api),              &type_string,           validate_backend },
   { C(authorization_algorithm), &type_string,    validate_algo },
-  { C(broadcast),        &type_stringlist,       validate_addrport },
-  { C(broadcast_from),   &type_stringlist,       validate_addrport },
+  { C(broadcast),        &type_netaddress,       validate_destaddr },
+  { C(broadcast_from),   &type_netaddress,       validate_any },
   { C(channel),          &type_string,           validate_any },
   { C(checkpoint_kbyte), &type_integer,          validate_non_negative },
   { C(checkpoint_min),   &type_integer,          validate_non_negative },
   { C(collection),       &type_collections,      validate_any },
-  { C(connect),          &type_stringlist,       validate_addrport },
+  { C(connect),          &type_netaddress,       validate_destaddr },
   { C(cookie_login_lifetime),  &type_integer,    validate_positive },
   { C(cookie_key_lifetime),  &type_integer,      validate_positive },
   { C(dbversion),        &type_integer,          validate_positive },
@@ -916,7 +922,7 @@ static const struct conf conf[] = {
   { C(gap),              &type_integer,          validate_non_negative },
   { C(history),          &type_integer,          validate_positive },
   { C(home),             &type_string,           validate_isabspath },
-  { C(listen),           &type_stringlist,       validate_port },
+  { C(listen),           &type_netaddress,       validate_any },
   { C(lock),             &type_boolean,          validate_any },
   { C(mail_sender),      &type_string,           validate_any },
   { C(mixer),            &type_string,           validate_any },
@@ -932,6 +938,7 @@ static const struct conf conf[] = {
   { C(nice_speaker),     &type_integer,          validate_any },
   { C(noticed_history),  &type_integer,          validate_positive },
   { C(password),         &type_string,           validate_any },
+  { C(pause_mode),       &type_string,           validate_pausemode },
   { C(player),           &type_stringlist_accum, validate_player },
   { C(plugins),          &type_string_accum,     validate_isdir },
   { C(prefsync),         &type_integer,          validate_positive },
@@ -941,6 +948,7 @@ static const struct conf conf[] = {
   { C(reminder_interval), &type_integer,         validate_positive },
   { C(remote_userman),   &type_boolean,          validate_any },
   { C2(restrict, restrictions),         &type_restrict,         validate_any },
+  { C(rtp_delay_threshold), &type_integer,       validate_positive },
   { C(sample_format),    &type_sample_format,    validate_sample_format },
   { C(scratch),          &type_string_accum,     validate_isreg },
   { C(sendmail),         &type_string,           validate_isabspath },
@@ -1192,6 +1200,10 @@ static struct config *config_default(void) {
                       default_players[n], "disorder-tracklength", (char *)0))
       exit(1);
   }
+  c->broadcast.af = -1;
+  c->broadcast_from.af = -1;
+  c->listen.af = -1;
+  c->connect.af = -1;
   return c;
 }
 
@@ -1263,7 +1275,7 @@ static void config_postdefaults(struct config *c,
   if(!c->api) {
     if(c->speaker_command)
       c->api = xstrdup("command");
-    else if(c->broadcast.n)
+    else if(c->broadcast.af != -1)
       c->api = xstrdup("rtp");
     else if(config_uaudio_apis)
       c->api = xstrdup(config_uaudio_apis[0]->name);
@@ -1275,7 +1287,7 @@ static void config_postdefaults(struct config *c,
   if(server) {
     if(!strcmp(c->api, "command") && !c->speaker_command)
       fatal(0, "'api command' but speaker_command is not set");
-    if((!strcmp(c->api, "rtp")) && !c->broadcast.n)
+    if((!strcmp(c->api, "rtp")) && c->broadcast.af == -1)
       fatal(0, "'api rtp' but broadcast is not set");
   }
   /* Override sample format */
index d9af67dd3c0fa4511a035dec732540e73a440c0e..4cb56774671ebeec323d4069d6528bb1d75a919f 100644 (file)
@@ -26,6 +26,7 @@
 
 #include "speaker-protocol.h"
 #include "rights.h"
+#include "addr.h"
 
 struct uaudio;
 
@@ -154,7 +155,7 @@ struct config {
   long prefsync;                       /* preflog sync interval */
 
   /** @brief Secondary listen address */
-  struct stringlist listen;
+  struct netaddress listen;
 
   /** @brief Alias format string */
   const char *alias;
@@ -171,6 +172,9 @@ struct config {
   /** @brief Command execute by speaker to play audio */
   const char *speaker_command;
 
+  /** @brief Pause mode for command backend */
+  const char *pause_mode;
+  
   /** @brief Target sample format */
   struct stream_header sample_format;
 
@@ -190,7 +194,7 @@ struct config {
   const char *password;
 
   /** @brief Address to connect to */
-  struct stringlist connect;
+  struct netaddress connect;
 
   /** @brief Directories to search for web templates */
   struct stringlist templates;
@@ -230,11 +234,14 @@ struct config {
   struct transformlist transform;      /* path name transformations */
 
   /** @brief Address to send audio data to */
-  struct stringlist broadcast;
+  struct netaddress broadcast;
 
   /** @brief Source address for network audio transmission */
-  struct stringlist broadcast_from;
+  struct netaddress broadcast_from;
 
+  /** @brief RTP delay threshold */
+  long rtp_delay_threshold;
+  
   /** @brief TTL for multicast packets */
   long multicast_ttl;
 
index 3f8f714a8829f9ad969a00d51de3b4de457667d4..d6be3d9f47ec27614f8a536f8a5c88a104b9b63a 100644 (file)
@@ -75,7 +75,7 @@ void random_get(void *ptr, size_t bytes) {
 
 /** @brief Return a random ID string */
 char *random_id(void) {
-  unsigned long words[2];
+  uint32_t words[2];
   char id[128];
 
   random_get(words, sizeof words);
index 2236c918b522b71587b4f7a7f64a0e00856a5246..721639c74a1c032da761578bb7340ae34834d36f 100644 (file)
@@ -26,6 +26,7 @@
 #include "mem.h"
 #include "log.h"
 #include "uaudio.h"
+#include "configuration.h"
 
 /** @brief The current PCM handle */
 static snd_pcm_t *alsa_pcm;
@@ -135,7 +136,8 @@ static void alsa_start(uaudio_callback *callback,
   alsa_open();
   uaudio_thread_start(callback, userdata, alsa_play,
                       32 / uaudio_sample_size,
-                      4096 / uaudio_sample_size);
+                      4096 / uaudio_sample_size,
+                      0);
 }
 
 static void alsa_stop(void) {
@@ -245,6 +247,12 @@ static void alsa_set_volume(int *left, int *right) {
   *right = to_percent(r);
 }
 
+static void alsa_configure(void) {
+  uaudio_set("device", config->device);
+  uaudio_set("mixer-control", config->mixer);
+  uaudio_set("mixer-channel", config->channel);
+}
+
 const struct uaudio uaudio_alsa = {
   .name = "alsa",
   .options = alsa_options,
@@ -256,6 +264,7 @@ const struct uaudio uaudio_alsa = {
   .close_mixer = alsa_close_mixer,
   .get_volume = alsa_get_volume,
   .set_volume = alsa_set_volume,
+  .configure = alsa_configure
 };
 
 #endif
index 012185aa794a7fc458fbd00e0e457aaf3049f016..2c610ff08225e4d7fa478bfdc0eeccb08f61a8b8 100644 (file)
@@ -34,6 +34,7 @@
 #include "mem.h"
 #include "wstat.h"
 #include "uaudio.h"
+#include "configuration.h"
 
 /** @brief Pipe to subprocess */
 static int command_fd;
@@ -43,6 +44,7 @@ static pid_t command_pid;
 
 static const char *const command_options[] = {
   "command",
+  "pause-mode",
   NULL
 };
 
@@ -114,13 +116,23 @@ static size_t command_play(void *buffer, size_t nsamples) {
 
 static void command_start(uaudio_callback *callback,
                       void *userdata) {
+  const char *pausemode = uaudio_get("pause-mode", "silence");
+  unsigned flags = 0;
+
+  if(!strcmp(pausemode, "silence"))
+    flags |= UAUDIO_THREAD_FAKE_PAUSE;
+  else if(!strcmp(pausemode, "suspend"))
+    ;
+  else
+    fatal(0, "unknown pause mode '%s'", pausemode);
   command_open();
   uaudio_schedule_init();
   uaudio_thread_start(callback,
                       userdata,
                       command_play,
                       uaudio_channels,
-                     4096 / uaudio_sample_size);
+                     4096 / uaudio_sample_size,
+                      flags);
 }
 
 static void command_stop(void) {
@@ -137,13 +149,19 @@ static void command_deactivate(void) {
   uaudio_thread_deactivate();
 }
 
+static void command_configure(void) {
+  uaudio_set("command", config->speaker_command);
+  uaudio_set("pause-mode", config->pause_mode);
+}
+
 const struct uaudio uaudio_command = {
   .name = "command",
   .options = command_options,
   .start = command_start,
   .stop = command_stop,
   .activate = command_activate,
-  .deactivate = command_deactivate
+  .deactivate = command_deactivate,
+  .configure = command_configure,
 };
 
 /*
index 81a5711b924bdb99f1e500834eae33a31cd83288..5f28e671b8d1ac1a573f6fdc8882ac440e4acc45 100644 (file)
@@ -26,6 +26,7 @@
 #include "mem.h"
 #include "log.h"
 #include "syscalls.h"
+#include "configuration.h"
 
 /** @brief Callback to request sample data */
 static uaudio_callback *coreaudio_callback;
@@ -182,13 +183,18 @@ static void coreaudio_deactivate(void) {
     coreaudio_fatal(status, "AudioDeviceStop");
 }
 
+static void coreaudio_configure(void) {
+  uaudio_set("device", config->device);
+}
+
 const struct uaudio uaudio_coreaudio = {
   .name = "coreaudio",
   .options = coreaudio_options,
   .start = coreaudio_start,
   .stop = coreaudio_stop,
   .activate = coreaudio_activate,
-  .deactivate = coreaudio_deactivate
+  .deactivate = coreaudio_deactivate,
+  .configure = coreaudio_configure,
 };
 
 #endif
index 081e1eb4bef09c6b595706cc57f816b7a4c223f5..6d902cb60108225a77c4624dce52c7a1cfafe250 100644 (file)
@@ -32,6 +32,7 @@
 #include "mem.h"
 #include "log.h"
 #include "uaudio.h"
+#include "configuration.h"
 
 #ifndef AFMT_U16_NE
 # if BYTE_ORDER == BIG_ENDIAN
@@ -127,13 +128,15 @@ static void oss_start(uaudio_callback *callback,
   /* Very specific buffer size requirements here apparently */
   uaudio_thread_start(callback, userdata, oss_play, 
                       4608 / uaudio_sample_size,
-                      4608 / uaudio_sample_size);
+                      4608 / uaudio_sample_size,
+                      0);
 #else
   /* We could SNDCTL_DSP_GETBLKSIZE but only when the device is already open,
    * which is kind of inconvenient.  We go with 1-4Kbyte for now. */
   uaudio_thread_start(callback, userdata, oss_play, 
                       32 / uaudio_sample_size,
-                      4096 / uaudio_sample_size);
+                      4096 / uaudio_sample_size,
+                      0);
 #endif
 }
 
@@ -195,6 +198,12 @@ static void oss_set_volume(int *left, int *right) {
   }
 }
 
+static void oss_configure(void) {
+  uaudio_set("device", config->device);
+  uaudio_set("mixer-device", config->mixer);
+  uaudio_set("mixer-channel", config->channel);
+}
+
 const struct uaudio uaudio_oss = {
   .name = "oss",
   .options = oss_options,
@@ -206,6 +215,7 @@ const struct uaudio uaudio_oss = {
   .close_mixer = oss_close_mixer,
   .get_volume = oss_get_volume,
   .set_volume = oss_set_volume,
+  .configure = oss_configure,
 };
 
 #endif
index 00609404372ab9e8778d65bc04b3acb7a256fc50..bb03be4094b5c6c06687aedde8711b971e954865 100644 (file)
@@ -23,6 +23,8 @@
 #include <sys/socket.h>
 #include <ifaddrs.h>
 #include <net/if.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
 #include <gcrypt.h>
 #include <unistd.h>
 #include <time.h>
@@ -36,6 +38,7 @@
 #include "addr.h"
 #include "ifreq.h"
 #include "timeval.h"
+#include "configuration.h"
 
 /** @brief Bytes to send per network packet
  *
@@ -82,6 +85,50 @@ static const char *const rtp_options[] = {
   NULL
 };
 
+static void rtp_get_netconfig(const char *af,
+                              const char *addr,
+                              const char *port,
+                              struct netaddress *na) {
+  char *vec[3];
+  
+  vec[0] = uaudio_get(af, NULL);
+  vec[1] = uaudio_get(addr, NULL);
+  vec[2] = uaudio_get(port, NULL);
+  if(!*vec)
+    na->af = -1;
+  else
+    if(netaddress_parse(na, 3, vec))
+      fatal(0, "invalid RTP address");
+}
+
+static void rtp_set_netconfig(const char *af,
+                              const char *addr,
+                              const char *port,
+                              const struct netaddress *na) {
+  uaudio_set(af, NULL);
+  uaudio_set(addr, NULL);
+  uaudio_set(port, NULL);
+  if(na->af != -1) {
+    int nvec;
+    char **vec;
+
+    netaddress_format(na, &nvec, &vec);
+    if(nvec > 0) {
+      uaudio_set(af, vec[0]);
+      xfree(vec[0]);
+    }
+    if(nvec > 1) {
+      uaudio_set(addr, vec[1]);
+      xfree(vec[1]);
+    }
+    if(nvec > 2) {
+      uaudio_set(port, vec[2]);
+      xfree(vec[2]);
+    }
+    xfree(vec);
+  }
+}
+
 static size_t rtp_play(void *buffer, size_t nsamples) {
   struct rtp_header header;
   struct iovec vec[2];
@@ -126,52 +173,31 @@ static size_t rtp_play(void *buffer, size_t nsamples) {
 
 static void rtp_open(void) {
   struct addrinfo *res, *sres;
-  static const struct addrinfo pref = {
-    .ai_flags = 0,
-    .ai_family = PF_INET,
-    .ai_socktype = SOCK_DGRAM,
-    .ai_protocol = IPPROTO_UDP,
-  };
-  static const struct addrinfo prefbind = {
-    .ai_flags = AI_PASSIVE,
-    .ai_family = PF_INET,
-    .ai_socktype = SOCK_DGRAM,
-    .ai_protocol = IPPROTO_UDP,
-  };
   static const int one = 1;
   int sndbuf, target_sndbuf = 131072;
   socklen_t len;
-  char *sockname, *ssockname;
-  struct stringlist dst, src;
+  struct netaddress dst[1], src[1];
   
   /* Get configuration */
-  dst.n = 2;
-  dst.s = xcalloc(2, sizeof *dst.s);
-  dst.s[0] = uaudio_get("rtp-destination", NULL);
-  dst.s[1] = uaudio_get("rtp-destination-port", NULL);
-  src.n = 2;
-  src.s = xcalloc(2, sizeof *dst.s);
-  src.s[0] = uaudio_get("rtp-source", NULL);
-  src.s[1] = uaudio_get("rtp-source-port", NULL);
-  if(!dst.s[0])
-    fatal(0, "'rtp-destination' not set");
-  if(!dst.s[1])
-    fatal(0, "'rtp-destination-port' not set");
-  if(src.s[0]) {
-    if(!src.s[1])
-      fatal(0, "'rtp-source-port' not set");
-    src.n = 2;
-  } else
-    src.n = 0;
+  rtp_get_netconfig("rtp-destination-af",
+                    "rtp-destination",
+                    "rtp-destination-port",
+                    dst);
+  rtp_get_netconfig("rtp-source-af",
+                    "rtp-source",
+                    "rtp-source-port",
+                    src);
   rtp_delay_threshold = atoi(uaudio_get("rtp-delay-threshold", "1000"));
   /* ...microseconds */
 
   /* Resolve addresses */
-  res = get_address(&dst, &pref, &sockname);
-  if(!res) exit(-1);
-  if(src.n) {
-    sres = get_address(&src, &prefbind, &ssockname);
-    if(!sres) exit(-1);
+  res = netaddress_resolve(dst, 0, IPPROTO_UDP);
+  if(!res)
+    exit(-1);
+  if(src->af != -1) {
+    sres = netaddress_resolve(src, 1, IPPROTO_UDP);
+    if(!sres)
+      exit(-1);
   } else
     sres = 0;
   /* Create the socket */
@@ -206,7 +232,7 @@ static void rtp_open(void) {
       fatal(0, "unsupported address family %d", res->ai_family);
     }
     info("multicasting on %s TTL=%d loop=%s", 
-         sockname, ttl, loop ? "yes" : "no");
+         format_sockaddr(res->ai_addr), ttl, loop ? "yes" : "no");
   } else {
     struct ifaddrs *ifs;
 
@@ -225,9 +251,10 @@ static void rtp_open(void) {
     if(ifs) {
       if(setsockopt(rtp_fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof one) < 0)
         fatal(errno, "error setting SO_BROADCAST on broadcast socket");
-      info("broadcasting on %s (%s)", sockname, ifs->ifa_name);
+      info("broadcasting on %s (%s)", 
+           format_sockaddr(res->ai_addr), ifs->ifa_name);
     } else
-      info("unicasting on %s", sockname);
+      info("unicasting on %s", format_sockaddr(res->ai_addr));
   }
   /* Enlarge the socket buffer */
   len = sizeof sndbuf;
@@ -247,9 +274,11 @@ static void rtp_open(void) {
   /* We might well want to set additional broadcast- or multicast-related
    * options here */
   if(sres && bind(rtp_fd, sres->ai_addr, sres->ai_addrlen) < 0)
-    fatal(errno, "error binding broadcast socket to %s", ssockname);
+    fatal(errno, "error binding broadcast socket to %s", 
+          format_sockaddr(sres->ai_addr));
   if(connect(rtp_fd, res->ai_addr, res->ai_addrlen) < 0)
-    fatal(errno, "error connecting broadcast socket to %s", sockname);
+    fatal(errno, "error connecting broadcast socket to %s", 
+          format_sockaddr(res->ai_addr));
 }
 
 static void rtp_start(uaudio_callback *callback,
@@ -278,7 +307,8 @@ static void rtp_start(uaudio_callback *callback,
                       rtp_play,
                       256 / uaudio_sample_size,
                       (NETWORK_BYTES - sizeof(struct rtp_header))
-                      / uaudio_sample_size);
+                      / uaudio_sample_size,
+                      0);
 }
 
 static void rtp_stop(void) {
@@ -296,13 +326,30 @@ static void rtp_deactivate(void) {
   uaudio_thread_deactivate();
 }
 
+static void rtp_configure(void) {
+  char buffer[64];
+
+  rtp_set_netconfig("rtp-destination-af",
+                    "rtp-destination",
+                    "rtp-destination-port", &config->broadcast);
+  rtp_set_netconfig("rtp-source-af",
+                    "rtp-source",
+                    "rtp-source-port", &config->broadcast_from);
+  snprintf(buffer, sizeof buffer, "%ld", config->multicast_ttl);
+  uaudio_set("multicast-ttl", buffer);
+  uaudio_set("multicast-loop", config->multicast_loop ? "yes" : "no");
+  snprintf(buffer, sizeof buffer, "%ld", config->rtp_delay_threshold);
+  uaudio_set("delay-threshold", buffer);
+}
+
 const struct uaudio uaudio_rtp = {
   .name = "rtp",
   .options = rtp_options,
   .start = rtp_start,
   .stop = rtp_stop,
   .activate = rtp_activate,
-  .deactivate = rtp_deactivate
+  .deactivate = rtp_deactivate,
+  .configure = rtp_configure,
 };
 
 /*
index cd0d7278b96811ea4d211ad3cf867b21779fd752..6bed4a8012f2bf62ba7e30f6da14b75115425aa1 100644 (file)
@@ -68,6 +68,9 @@ static pthread_t uaudio_collect_thread;
 /** @brief Playing thread ID */
 static pthread_t uaudio_play_thread;
 
+/** @brief Flags */
+static unsigned uaudio_thread_flags;
+
 static uaudio_callback *uaudio_thread_collect_callback;
 static uaudio_playcallback *uaudio_thread_play_callback;
 static void *uaudio_thread_userdata;
@@ -104,7 +107,8 @@ static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
     /* We are definitely active now */
     uaudio_thread_collecting = 1;
     pthread_cond_broadcast(&uaudio_thread_cond);
-    while(uaudio_thread_activated) {
+    while(uaudio_thread_activated
+          || (uaudio_thread_flags & UAUDIO_THREAD_FAKE_PAUSE)) {
       if(uaudio_buffers_used() < UAUDIO_THREAD_BUFFERS - 1) {
         /* At least one buffer is available.  We release the lock while
          * collecting data so that other already-filled buffers can be played
@@ -115,12 +119,17 @@ static void *uaudio_collect_thread_fn(void attribute((unused)) *arg) {
         
         /* Keep on trying until we get the minimum required amount of data */
         b->nsamples = 0;
-        while(b->nsamples < uaudio_thread_min) {
-          b->nsamples += uaudio_thread_collect_callback
-            ((char *)b->samples
-             + b->nsamples * uaudio_sample_size,
-             uaudio_thread_max - b->nsamples,
-             uaudio_thread_userdata);
+        if(uaudio_thread_activated) {
+          while(b->nsamples < uaudio_thread_min) {
+            b->nsamples += uaudio_thread_collect_callback
+              ((char *)b->samples
+               + b->nsamples * uaudio_sample_size,
+               uaudio_thread_max - b->nsamples,
+               uaudio_thread_userdata);
+          }
+        } else if(uaudio_thread_flags & UAUDIO_THREAD_FAKE_PAUSE) {
+          memset(b->samples, 0, uaudio_thread_min * uaudio_sample_size);
+          b->nsamples += uaudio_thread_min;
         }
         pthread_mutex_lock(&uaudio_thread_lock);
         /* Advance to next buffer */
@@ -199,13 +208,15 @@ void uaudio_thread_start(uaudio_callback *callback,
                         void *userdata,
                         uaudio_playcallback *playcallback,
                         size_t min,
-                         size_t max) {
+                         size_t max,
+                         unsigned flags) {
   int e;
   uaudio_thread_collect_callback = callback;
   uaudio_thread_userdata = userdata;
   uaudio_thread_play_callback = playcallback;
   uaudio_thread_min = min;
   uaudio_thread_max = max;
+  uaudio_thread_flags = flags;
   uaudio_thread_started = 1;
   for(int n = 0; n < UAUDIO_THREAD_BUFFERS; ++n)
     uaudio_buffers[n].samples = xcalloc_noptr(uaudio_thread_max,
@@ -253,9 +264,10 @@ void uaudio_thread_deactivate(void) {
   pthread_mutex_lock(&uaudio_thread_lock);
   uaudio_thread_activated = 0;
   pthread_cond_broadcast(&uaudio_thread_cond);
-
-  while(uaudio_thread_collecting || uaudio_buffers_used())
-    pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
+  if(!(uaudio_thread_flags & UAUDIO_THREAD_FAKE_PAUSE)) {
+    while(uaudio_thread_collecting || uaudio_buffers_used())
+      pthread_cond_wait(&uaudio_thread_cond, &uaudio_thread_lock);
+  }
   pthread_mutex_unlock(&uaudio_thread_lock);
 }
 
index 2f6b67cc67e1c9095d6c43c553df78a297d575f4..e793045ce77c995cb2f13ef52e4ae736e6168cdd 100644 (file)
@@ -24,6 +24,7 @@
 #include "uaudio.h"
 #include "hash.h"
 #include "mem.h"
+#include "log.h"
 
 /** @brief Options for chosen uaudio API */
 static hash *uaudio_options;
@@ -50,6 +51,11 @@ size_t uaudio_sample_size;
 
 /** @brief Set a uaudio option */
 void uaudio_set(const char *name, const char *value) {
+  if(!value) {
+    if(uaudio_options)
+      hash_remove(uaudio_options, name);
+    return;
+  }
   if(!uaudio_options)
     uaudio_options = hash_new(sizeof(char *));
   value = xstrdup(value);
@@ -58,10 +64,12 @@ void uaudio_set(const char *name, const char *value) {
 
 /** @brief Get a uaudio option */
 char *uaudio_get(const char *name, const char *default_value) {
-  const char *value = (uaudio_options ?
-                       *(char **)hash_find(uaudio_options, name)
-                       : default_value);
-  return value ? xstrdup(value) : NULL;
+  if(!uaudio_options)
+    return default_value ? xstrdup(default_value) : 0;
+  char **valuep = hash_find(uaudio_options, name);
+  if(!valuep)
+    return default_value ? xstrdup(default_value) : 0;
+  return xstrdup(*valuep);
 }
 
 /** @brief Set sample format 
index 20bfbbcfca77b59ff688dae3cb3d6610348fda41..d6376c895f399c522b65f4c490d857a458f3b55f 100644 (file)
@@ -34,6 +34,15 @@ extern size_t uaudio_sample_size;
  * @param max_samples How many samples to supply
  * @param userdata As passed to uaudio_open()
  * @return Number of samples filled
+ *
+ * This function should not block if possible (better to fill the buffer with
+ * 0s) and should definitely not block indefinitely.  This great caution with
+ * any locks or syscalls!  In particular avoid it taking a lock that may be
+ * held while any of the @ref uaudio members are called.
+ *
+ * If it's more convenient, it's OK to return less than the maximum number of
+ * samples (including 0) provided you expect to be called again for more
+ * samples immediately.
  */
 typedef size_t uaudio_callback(void *buffer,
                                size_t max_samples,
@@ -114,6 +123,9 @@ struct uaudio {
    * 0 is silent and 100 is maximum volume.
    */
   void (*set_volume)(int *left, int *right);
+
+  /** @brief Set configuration */
+  void (*configure)(void);
   
 };
 
@@ -124,7 +136,17 @@ void uaudio_thread_start(uaudio_callback *callback,
                         void *userdata,
                         uaudio_playcallback *playcallback,
                         size_t min,
-                         size_t max);
+                         size_t max,
+                         unsigned flags);
+
+/** @brief Fake pauses
+ *
+ * This flag is used for audio backends that cannot sensibly be paused.
+ * The thread support code will supply silence while deactivated in this
+ * case.
+ */
+#define UAUDIO_THREAD_FAKE_PAUSE 0x00000001
+
 void uaudio_thread_stop(void);
 void uaudio_thread_activate(void);
 void uaudio_thread_deactivate(void);
index 715490ca9e976d9d85b1797ccef925185ed20aea..3cbc044a27ea7120f9d3f5edcc8f8a363729eb05 100644 (file)
 #include "test.h"
 
 static void test_basen(void) {
-  unsigned long v[64];
+  uint32_t v[64];
   char buffer[1024];
 
   v[0] = 999;
   insist(basen(v, 1, buffer, sizeof buffer, 10) == 0);
   check_string(buffer, "999");
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 1, buffer, 10) == 0);
+  check_integer(v[0], 999);
+  check_integer(v[1], 0xFFFFFFFF);
+  insist(nesab(v, 4, buffer, 10) == 0);
+  check_integer(v[0], 0);
+  check_integer(v[1], 0);
+  check_integer(v[2], 0);
+  check_integer(v[3], 999);
+  check_integer(v[4], 0xFFFFFFFF);
 
   v[0] = 1+2*7+3*7*7+4*7*7*7;
   insist(basen(v, 1, buffer, sizeof buffer, 7) == 0);
@@ -35,6 +45,24 @@ static void test_basen(void) {
   v[3] = 0x0C0D0E0F;
   insist(basen(v, 4, buffer, sizeof buffer, 256) == 0);
   check_string(buffer, "123456789abcdef");
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 4, buffer, 256) == 0);
+  check_integer(v[0], 0x00010203);
+  check_integer(v[1], 0x04050607);
+  check_integer(v[2], 0x08090A0B);
+  check_integer(v[3], 0x0C0D0E0F);
+  check_integer(v[4], 0xFFFFFFFF);
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 8, buffer, 256) == 0);
+  check_integer(v[0], 0);
+  check_integer(v[1], 0);
+  check_integer(v[2], 0);
+  check_integer(v[3], 0);
+  check_integer(v[4], 0x00010203);
+  check_integer(v[5], 0x04050607);
+  check_integer(v[6], 0x08090A0B);
+  check_integer(v[7], 0x0C0D0E0F);
+  check_integer(v[8], 0xFFFFFFFF);
 
   v[0] = 0x00010203;
   v[1] = 0x04050607;
@@ -42,6 +70,24 @@ static void test_basen(void) {
   v[3] = 0x0C0D0E0F;
   insist(basen(v, 4, buffer, sizeof buffer, 16) == 0);
   check_string(buffer, "102030405060708090a0b0c0d0e0f");
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 4, buffer, 16) == 0);
+  check_integer(v[0], 0x00010203);
+  check_integer(v[1], 0x04050607);
+  check_integer(v[2], 0x08090A0B);
+  check_integer(v[3], 0x0C0D0E0F);
+  check_integer(v[4], 0xFFFFFFFF);
+  memset(v, 0xFF, sizeof v);
+  insist(nesab(v, 8, buffer, 16) == 0);
+  check_integer(v[0], 0);
+  check_integer(v[1], 0);
+  check_integer(v[2], 0);
+  check_integer(v[3], 0);
+  check_integer(v[4], 0x00010203);
+  check_integer(v[5], 0x04050607);
+  check_integer(v[6], 0x08090A0B);
+  check_integer(v[7], 0x0C0D0E0F);
+  check_integer(v[8], 0xFFFFFFFF);
 
   v[0] = 0x00010203;
   v[1] = 0x04050607;
index ae3f8eb7aff711f425ab7778c14c1054582eaafa..7852152a2a81f30d09fafac385ea89dcda784828 100644 (file)
@@ -25,7 +25,7 @@ notify_la_LDFLAGS=-module
 
 disorder_tracklength_la_SOURCES=tracklength.c mad.c madshim.h ../lib/wav.h ../lib/wav.c
 disorder_tracklength_la_LDFLAGS=-module
-disorder_tracklength_la_LIBADD=$(LIBVORBISFILE) $(LIBMAD) $(LIBFLAC)
+disorder_tracklength_la_LIBADD=$(LIBVORBISFILE) $(LIBMAD) $(LIBFLAC) -lm
 
 fs_la_SOURCES=fs.c
 fs_la_LDFLAGS=-module
index 7dfe99ded52fb423289971aaf4d386c67b1c9880..5ed5defaf281d3f2e9e9d47d33750334a7b594a1 100644 (file)
@@ -19,7 +19,7 @@
 sbin_PROGRAMS=disorderd disorder-deadlock disorder-rescan disorder-dump \
              disorder-speaker disorder-decode disorder-normalize \
              disorder-stats disorder-dbupgrade disorder-choose
-noinst_PROGRAMS=trackname
+noinst_PROGRAMS=trackname endian
 
 AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib
 
@@ -39,7 +39,8 @@ disorder_deadlock_DEPENDENCIES=../lib/libdisorder.a
 
 disorder_speaker_SOURCES=speaker.c
 disorder_speaker_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
-       $(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO)
+       $(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO) \
+       $(LIBPTHREAD)
 disorder_speaker_DEPENDENCIES=../lib/libdisorder.a
 
 disorder_decode_SOURCES=decode.c disorder-server.h
@@ -116,12 +117,12 @@ check-help: all
        ./disorder-choose --version > /dev/null
 
 # My sox doesn't know MP3 or FLAC unfortunately
-check-decode: disorder-decode disorder-normalize
+check-decode: disorder-decode disorder-normalize endian
        echo "speaker_backend network" > config
        echo "broadcast 127.255.255.255 discard" > config
        ./disorder-decode ${top_srcdir}/sounds/scratch.ogg | \
          ./disorder-normalize --config config > decoded.raw
-       oggdec -b 16 -e 1 -R -s 1 -o oggdec.raw ${top_srcdir}/sounds/scratch.ogg
+       oggdec -b 16 -e `./endian` -R -s 1 -o oggdec.raw ${top_srcdir}/sounds/scratch.ogg
        cmp decoded.raw oggdec.raw
        sox ${top_srcdir}/sounds/scratch.ogg scratch.wav
        ./disorder-decode scratch.wav | \
index 72a8e3d918cbd97333c8c03fab0b83fcf856679d..74fc07eb4a7d3c49059b3d54d5383f56d6f0d944 100644 (file)
@@ -209,7 +209,10 @@ int main(int argc, char **argv) {
   fix_path();
   srand(time(0));                      /* don't start the same every time */
   /* gcrypt initialization */
+  if(!gcry_check_version(NULL))
+    disorder_fatal(0, "gcry_check_version failed");
   gcry_control(GCRYCTL_INIT_SECMEM, 1);
+  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
   /* make sure we can't have more than FD_SETSIZE files open (event.c does
    * check but this provides an additional line of defence) */
   if(getrlimit(RLIMIT_NOFILE, rl) < 0)
diff --git a/server/endian.c b/server/endian.c
new file mode 100644 (file)
index 0000000..2e4b152
--- /dev/null
@@ -0,0 +1,20 @@
+#include <config.h>
+#include <stdio.h>
+
+int main(void) {
+#if WORDS_BIGENDIAN
+  puts("1");
+#else
+  puts("0");
+#endif
+  return 0;
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 345c2ca48d007fd6cf1a5d7f1b7627d15cf0c08d..8bd23e74329227ea0bda09779357ab20c0f14727 100644 (file)
  */
 
 #include "disorder-server.h"
+#include "basen.h"
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
 #endif
 
 #ifndef CONFIRM_SIZE
-# define CONFIRM_SIZE 10
+/** @brief Size of nonce in confirmation string in 32-bit words
+ *
+ * 64 bits gives 11 digits (in base 62).
+ */
+# define CONFIRM_SIZE 2
 #endif
 
 int volume_left, volume_right;         /* last known volume */
@@ -829,7 +834,7 @@ static int c_volume(struct conn *c,
     sink_writes(ev_writer_sink(c->w), "550 error accessing mixer\n");
     return 1;
   }
-  api->set_volume(&l, &r);
+  (set ? api->set_volume : api->get_volume)(&l, &r);
   sink_printf(ev_writer_sink(c->w), "252 %d %d\n", l, r);
   if(l != volume_left || r != volume_right) {
     volume_left = l;
@@ -1100,9 +1105,12 @@ static int c_rtp_address(struct conn *c,
                         char attribute((unused)) **vec,
                         int attribute((unused)) nvec) {
   if(api == &uaudio_rtp) {
+    char **addr;
+
+    netaddress_format(&config->broadcast, NULL, &addr);
     sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
-               quoteutf8(config->broadcast.s[0]),
-               quoteutf8(config->broadcast.s[1]));
+               quoteutf8(addr[1]),
+               quoteutf8(addr[2]));
   } else
     sink_writes(ev_writer_sink(c->w), "550 No RTP\n");
   return 1;
@@ -1319,30 +1327,23 @@ static int c_users(struct conn *c,
   return 1;                            /* completed */
 }
 
-/** @brief Base64 mapping table for confirmation strings
- *
- * This is used with generic_to_base64() and generic_base64().  We cannot use
- * the MIME table as that contains '+' and '=' which get quoted when
- * URL-encoding.  (The CGI still does the URL encoding but it is desirable to
- * avoid it being necessary.)
- */
-static const char confirm_base64_table[] =
-  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/.*";
-
 static int c_register(struct conn *c,
                      char **vec,
                      int attribute((unused)) nvec) {
-  char *buf, *cs;
-  size_t bufsize;
-  int offset;
-
-  /* The confirmation string is base64(username;nonce) */
-  bufsize = strlen(vec[0]) + CONFIRM_SIZE + 2;
-  buf = xmalloc_noptr(bufsize);
-  offset = byte_snprintf(buf, bufsize, "%s;", vec[0]);
-  gcry_randomize(buf + offset, CONFIRM_SIZE, GCRY_STRONG_RANDOM);
-  cs = generic_to_base64((uint8_t *)buf, offset + CONFIRM_SIZE,
-                        confirm_base64_table);
+  char *cs;
+  uint32_t nonce[CONFIRM_SIZE];
+  char nonce_str[(32 * CONFIRM_SIZE) / 5 + 1];
+
+  /* The confirmation string is username/base62(nonce).  The confirmation
+   * process will pick the username back out to identify them but the _whole_
+   * string is used as the confirmation string.  Base 62 means we used only
+   * letters and digits, minimizing the chance of the URL being mispasted. */
+  gcry_randomize(nonce, sizeof nonce, GCRY_STRONG_RANDOM);
+  if(basen(nonce, CONFIRM_SIZE, nonce_str, sizeof nonce_str, 62)) {
+    error(0, "buffer too small encoding confirmation string");
+    sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
+  }
+  byte_xasprintf(&cs, "%s/%s", vec[0], nonce_str);
   if(trackdb_adduser(vec[0], vec[1], config->default_rights, vec[2], cs))
     sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
   else
@@ -1353,7 +1354,6 @@ static int c_register(struct conn *c,
 static int c_confirm(struct conn *c,
                     char **vec,
                     int attribute((unused)) nvec) {
-  size_t nuser;
   char *user, *sep;
   rights_type rights;
   const char *host;
@@ -1363,12 +1363,12 @@ static int c_confirm(struct conn *c,
     sink_writes(ev_writer_sink(c->w), "530 Authentication failure\n");
     return 1;
   }
-  if(!(user = generic_base64(vec[0], &nuser, confirm_base64_table))
-     || !(sep = memchr(user, ';', nuser))) {
+  /* Picking the LAST / means we don't (here) rule out slashes in usernames. */
+  if(!(sep = strrchr(vec[0], '/'))) {
     sink_writes(ev_writer_sink(c->w), "550 Malformed confirmation string\n");
     return 1;
   }
-  *sep = 0;
+  user = xstrndup(vec[0], sep - vec[0]);
   if(trackdb_confirm(user, vec[0], &rights))
     sink_writes(ev_writer_sink(c->w), "550 Incorrect confirmation string\n");
   else {
@@ -1850,6 +1850,7 @@ int server_start(ev_source *ev, int pf,
   l->pf = pf;
   if(ev_listen(ev, fd, listen_callback, l, "server listener"))
     exit(EXIT_FAILURE);
+  info("listening on %s", name);
   return fd;
 }
 
index 6a212dd6230cf3e78ecdaaaa8c865bac792a1527..b41c94f844d8f9cee721c6d959c174ddc09ddda5 100644 (file)
  * obvious way.  If the callback finds itself required to play when there is no
  * playing track it returns dead air.
  *
+ * To implement gapless playback, the server is notified that a track has
+ * finished slightly early.  @ref SM_PLAY is therefore allowed to arrive while
+ * the previous track is still playing provided an early @ref SM_FINISHED has
+ * been sent for it.
+ *
  * @b Encodings.  The encodings supported depend entirely on the uaudio backend
  * chosen.  See @ref uaudio.h, etc.
  *
@@ -79,6 +84,8 @@
 #include <sys/un.h>
 #include <sys/stat.h>
 #include <pthread.h>
+#include <sys/resource.h>
+#include <gcrypt.h>
 
 #include "configuration.h"
 #include "syscalls.h"
 /** @brief Maximum number of FDs to poll for */
 #define NFDS 1024
 
+/** @brief Number of bytes before end of track to send SM_FINISHED
+ *
+ * Generally set to 1 second.
+ */
+static size_t early_finish;
+
 /** @brief Track structure
  *
  * Known tracks are kept in a linked list.  Usually there will be at most two
@@ -131,6 +144,14 @@ struct track {
    * out life not playable.
    */
   int playable;
+
+  /** @brief Set when finished
+   *
+   * This is set when we've notified the server that the track is finished.
+   * Once this has happened (typically very late in the track's lifetime) the
+   * track cannot be paused or cancelled.
+   */
+  int finished;
   
   /** @brief Input buffer
    *
@@ -142,9 +163,13 @@ struct track {
 /** @brief Lock protecting data structures
  *
  * This lock protects values shared between the main thread and the callback.
- * It is needed e.g. if changing @ref playing or if modifying buffer pointers.
- * It is not needed to add a new track, to read values only modified in the
- * same thread, etc.
+ *
+ * It is held 'all' the time by the main thread, the exceptions being when
+ * called activate/deactivate callbacks and when calling (potentially) slow
+ * system calls (in particular poll(), where in fact the main thread will spend
+ * most of its time blocked).
+ *
+ * The callback holds it when it's running.
  */
 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 
@@ -153,11 +178,17 @@ static struct track *tracks;
 
 /** @brief Playing track, or NULL
  *
- * This means the DESIRED playing track.  It does not reflect any other state
- * (e.g. activation of uaudio backend).
+ * This means the track the speaker process intends to play.  It does not
+ * reflect any other state (e.g. activation of uaudio backend).
  */
 static struct track *playing;
 
+/** @brief Pending playing track, or NULL
+ *
+ * This means the track the server wants the speaker to play.
+ */
+static struct track *pending_playing;
+
 /** @brief Array of file descriptors for poll() */
 static struct pollfd fds[NFDS];
 
@@ -272,7 +303,6 @@ static int speaker_fill(struct track *t) {
      t->id, t->eof, t->used));
   if(t->eof)
     return -1;
-  pthread_mutex_lock(&lock);
   if(t->used < sizeof t->buffer) {
     /* there is room left in the buffer */
     where = (t->start + t->used) % sizeof t->buffer;
@@ -307,7 +337,6 @@ static int speaker_fill(struct track *t) {
       rc = 0;
     }
   }
-  pthread_mutex_unlock(&lock);
   return rc;
 }
 
@@ -315,10 +344,15 @@ static int speaker_fill(struct track *t) {
  *
  * We want to play audio if there is a current track; and it is not paused; and
  * it is playable according to the rules for @ref track::playable.
+ *
+ * We don't allow tracks to be paused if we've already told the server we've
+ * finished them; that would cause such tracks to survive much longer than the
+ * few samples they're supposed to, with report() remaining silent for the
+ * duration.
  */
 static int playable(void) {
   return playing
-         && !paused
+         && (!paused || playing->finished)
          && playing->playable;
 }
 
@@ -327,15 +361,17 @@ static void report(void) {
   struct speaker_message sm;
 
   if(playing) {
+    /* Had better not send a report for a track that the server thinks has
+     * finished, that would be confusing. */
+    if(playing->finished)
+      return;
     memset(&sm, 0, sizeof sm);
     sm.type = paused ? SM_PAUSED : SM_PLAYING;
     strcpy(sm.id, playing->id);
-    pthread_mutex_lock(&lock);
     sm.data = playing->played / (uaudio_rate * uaudio_channels);
-    pthread_mutex_unlock(&lock);
     speaker_send(1, &sm);
+    time(&last_report);
   }
-  time(&last_report);
 }
 
 /** @brief Add a file descriptor to the set to poll() for
@@ -401,6 +437,10 @@ static size_t speaker_callback(void *buffer,
   if(!provided_samples) {
     memset(buffer, 0, max_bytes);
     provided_samples = max_samples;
+    if(playing)
+      info("%zu samples silence, playing->used=%zu", provided_samples, playing->used);
+    else
+      info("%zu samples silence, playing=NULL", provided_samples);
   }
   pthread_mutex_unlock(&lock);
   return provided_samples;
@@ -413,13 +453,14 @@ static void mainloop(void) {
   int n, fd, stdin_slot, timeout, listen_slot, sigpipe_slot;
 
   /* Keep going while our parent process is alive */
+  pthread_mutex_lock(&lock);
   while(getppid() != 1) {
     int force_report = 0;
 
     fdno = 0;
-    /* By default we will wait up to a second before thinking about current
-     * state. */
-    timeout = 1000;
+    /* By default we will wait up to half a second before thinking about
+     * current state. */
+    timeout = 500;
     /* Always ready for commands from the main server. */
     stdin_slot = addfd(0, POLLIN);
     /* Also always ready for inbound connections */
@@ -445,9 +486,11 @@ static void mainloop(void) {
         } else
           t->slot = -1;
       }
-    sigpipe_slot = addfd(sigpipe[1], POLLIN);
+    sigpipe_slot = addfd(sigpipe[0], POLLIN);
     /* Wait for something interesting to happen */
+    pthread_mutex_unlock(&lock);
     n = poll(fds, fdno, timeout);
+    pthread_mutex_lock(&lock);
     if(n < 0) {
       if(errno == EINTR) continue;
       fatal(errno, "error calling poll");
@@ -493,18 +536,32 @@ static void mainloop(void) {
        * this won't be the case, so we don't bother looping around to pick them
        * all up. */ 
       n = speaker_recv(0, &sm);
-      /* TODO */
       if(n > 0)
+        /* As a rule we don't send success replies to most commands - we just
+         * force the regular status update to be sent immediately rather than
+         * on schedule. */
        switch(sm.type) {
        case SM_PLAY:
-          if(playing)
-            fatal(0, "got SM_PLAY but already playing something");
+          /* SM_PLAY is only allowed if the server reasonably believes that
+           * nothing is playing */
+          if(playing) {
+            /* If finished isn't set then the server can't believe that this
+             * track has finished */
+            if(!playing->finished)
+              fatal(0, "got SM_PLAY but already playing something");
+            /* If pending_playing is set then the server must believe that that
+             * is playing */
+            if(pending_playing)
+              fatal(0, "got SM_PLAY but have a pending playing track");
+          }
          t = findtrack(sm.id, 1);
           D(("SM_PLAY %s fd %d", t->id, t->fd));
           if(t->fd == -1)
             error(0, "cannot play track because no connection arrived");
-          playing = t;
-          force_report = 1;
+          pending_playing = t;
+          /* If nothing is currently playing then we'll switch to the pending
+           * track below so there's no point distinguishing the situations
+           * here. */
          break;
        case SM_PAUSE:
           D(("SM_PAUSE"));
@@ -520,11 +577,15 @@ static void mainloop(void) {
           D(("SM_CANCEL %s", sm.id));
          t = removetrack(sm.id);
          if(t) {
-            pthread_mutex_lock(&lock);
-           if(t == playing) {
-              /* scratching the playing track */
+           if(t == playing || t == pending_playing) {
+              /* Scratching the track that the server believes is playing,
+               * which might either be the actual playing track or a pending
+               * playing track */
               sm.type = SM_FINISHED;
-             playing = 0;
+              if(t == playing)
+                playing = 0;
+              else
+                pending_playing = 0;
             } else {
               /* Could be scratching the playing track before it's quite got
                * going, or could be just removing a track from the queue.  We
@@ -536,7 +597,6 @@ static void mainloop(void) {
             }
             strcpy(sm.id, t->id);
            destroy(t);
-            pthread_mutex_unlock(&lock);
          } else {
             /* Probably scratching the playing track well before it's got
              * going, but could indicate a bug, so we log this as an error. */
@@ -569,29 +629,47 @@ static void mainloop(void) {
 
       read(sigpipe[0], buffer, sizeof buffer);
     }
-    if(playing && playing->used == 0 && playing->eof) {
-      /* The playing track is done.  Tell the server, and destroy it. */
+    /* Send SM_FINISHED when we're near the end of the track.
+     *
+     * This is how we implement gapless play; we hope that the SM_PLAY from the
+     * server arrives before the remaining bytes of the track play out.
+     */
+    if(playing
+       && playing->eof
+       && !playing->finished
+       && playing->used <= early_finish) {
       memset(&sm, 0, sizeof sm);
       sm.type = SM_FINISHED;
       strcpy(sm.id, playing->id);
       speaker_send(1, &sm);
+      playing->finished = 1;
+    }
+    /* When the track is actually finished, deconfigure it */
+    if(playing && playing->eof && !playing->used) {
       removetrack(playing->id);
-      pthread_mutex_lock(&lock);
       destroy(playing);
       playing = 0;
-      pthread_mutex_unlock(&lock);
-      /* The server will presumalby send as an SM_PLAY by return */
+    }
+    /* Act on the pending SM_PLAY */
+    if(!playing && pending_playing) {
+      playing = pending_playing;
+      pending_playing = 0;
+      force_report = 1;
     }
     /* Impose any state change required by the above */
     if(playable()) {
       if(!activated) {
         activated = 1;
+        pthread_mutex_unlock(&lock);
         backend->activate();
+        pthread_mutex_lock(&lock);
       }
     } else {
       if(activated) {
         activated = 0;
+        pthread_mutex_unlock(&lock);
         backend->deactivate();
+        pthread_mutex_lock(&lock);
       }
     }
     /* If we've not reported our state for a second do so now. */
@@ -651,6 +729,11 @@ int main(int argc, char **argv) {
     info("set RLIM_NOFILE to %lu", (unsigned long)rl->rlim_cur);
   } else
     info("RLIM_NOFILE is %lu", (unsigned long)rl->rlim_cur);
+  /* gcrypt initialization */
+  if(!gcry_check_version(NULL))
+    disorder_fatal(0, "gcry_check_version failed");
+  gcry_control(GCRYCTL_INIT_SECMEM, 0);
+  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
   /* create a pipe between the backend callback and the poll() loop */
   xpipe(sigpipe);
   nonblock(sigpipe[0]);
@@ -659,9 +742,12 @@ int main(int argc, char **argv) {
                     config->sample_format.channels,
                     config->sample_format.bits,
                     config->sample_format.bits != 8);
+  early_finish = uaudio_sample_size * uaudio_channels * uaudio_rate;
   /* TODO other parameters! */
   backend = uaudio_find(config->api);
   /* backend-specific initialization */
+  if(backend->configure)
+    backend->configure();
   backend->start(speaker_callback, NULL);
   /* create the socket directory */
   byte_xasprintf(&dir, "%s/speaker", config->home);
index a0ea660dfd95d5a88c667fd54edd8a77c410e009..9a93dc967acfa31dd064e2bca58090e74370ddc4 100644 (file)
 static const char *current_unix;
 static int current_unix_fd;
 
-static struct addrinfo *current_listen_addrinfo;
-static int current_listen_fd;
+/** @brief TCP listener definition */
+struct listener {
+  struct listener *next;
+  struct sockaddr *sa;
+  int fd;
+};
+
+/** @brief Current listeners */
+static struct listener *listeners;
 
 /** @brief Current audio API */
 const struct uaudio *api;
@@ -38,18 +45,17 @@ void quit(ev_source *ev) {
   exit(0);
 }
 
+static struct sockaddr *copy_sockaddr(const struct addrinfo *addr) {
+  struct sockaddr *sa = xmalloc_noptr(addr->ai_addrlen);
+  memcpy(sa, addr->ai_addr, addr->ai_addrlen);
+  return sa;
+}
+
 static void reset_socket(ev_source *ev) {
   const char *new_unix;
-  struct addrinfo *res;
+  struct addrinfo *res, *r;
+  struct listener *l, **ll;
   struct sockaddr_un sun;
-  char *name;
-  
-  static const struct addrinfo pref = {
-    .ai_flags = AI_PASSIVE,
-    .ai_family = PF_INET,
-    .ai_socktype = SOCK_STREAM,
-    .ai_protocol = IPPROTO_TCP,
-  };
 
   /* unix first */
   new_unix = config_get_file("socket");
@@ -80,28 +86,42 @@ static void reset_socket(ev_source *ev) {
   }
 
   /* get the new listen config */
-  if(config->listen.n)
-    res = get_address(&config->listen, &pref, &name);
+  if(config->listen.af != -1)
+    res = netaddress_resolve(&config->listen, 1, IPPROTO_TCP);
   else
     res = 0;
 
-  if((res && !current_listen_addrinfo)
-     || (current_listen_addrinfo
-        && (!res
-            || addrinfocmp(res, current_listen_addrinfo)))) {
-    /* something has to change */
-    if(current_listen_addrinfo) {
-      /* delete the old listener */
-      server_stop(ev, current_listen_fd);
-      freeaddrinfo(current_listen_addrinfo);
-      current_listen_addrinfo = 0;
+  /* Close any current listeners that aren't required any more */
+  ll = &listeners;
+  while((l = *ll)) {
+    for(r = res; r; r = r->ai_next)
+      if(!sockaddrcmp(r->ai_addr, l->sa))
+       break;
+    if(!r) {
+      /* Didn't find a match, remove this one */
+      server_stop(ev, l->fd);
+      *ll = l->next;
+    } else {
+      /* This address is still wanted */
+      ll = &l->next;
     }
-    if(res) {
-      /* start the new listener */
-      if((current_listen_fd = server_start(ev, res->ai_family, res->ai_addrlen,
-                                          res->ai_addr, name)) >= 0) {
-       current_listen_addrinfo = res;
-       res = 0;
+  }
+
+  /* Open any new listeners that are required */
+  for(r = res; r; r = r->ai_next) {
+    for(l = listeners; l; l = l->next)
+      if(!sockaddrcmp(r->ai_addr, l->sa))
+       break;
+    if(!l) {
+      /* Didn't find a match, need a new listener */
+      int fd = server_start(ev, r->ai_family, r->ai_addrlen, r->ai_addr,
+                           format_sockaddr(r->ai_addr));
+      if(fd >= 0) {
+       l = xmalloc(sizeof *l);
+       l->next = listeners;
+       l->sa = copy_sockaddr(r);
+       l->fd = fd;
+       listeners = l;
       }
     }
   }
@@ -135,6 +155,8 @@ int reconfigure(ev_source *ev, int reload) {
     /* We only allow for upgrade at startup */
     trackdb_open(TRACKDB_CAN_UPGRADE);
   api = uaudio_find(config->api);
+  if(api->configure)
+    api->configure();
   if(api->open_mixer)
     api->open_mixer();
   if(need_another_rescan)
index 1c62dc548f325e0f188c97d04de7e84505292b3f..d2bb20111e86adda41f63b1445476142f33e4361 100644 (file)
@@ -190,7 +190,7 @@ tracklength *.mp3 disorder-tracklength
 tracklength *.ogg disorder-tracklength
 tracklength *.wav disorder-tracklength
 tracklength *.flac disorder-tracklength
-api network
+api rtp
 broadcast 127.0.0.1 %d
 broadcast_from 127.0.0.1 %d
 mail_sender no.such.user.sorry@greenend.org.uk