doc/disorder-choose.8
doc/disorder-choose.8.html
config.aux/compile
+server/endian
<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>
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
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 \
#include <unistd.h>
#include <pcre.h>
#include <ctype.h>
+#include <gcrypt.h>
#include "configuration.h"
#include "syscalls.h"
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)
/** @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
*
#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 }
#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"
);
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,
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");
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");
}
}
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:
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;
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");
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
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 */
}
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
}
rm -f $state/prefs.db
rm -f $state/schedule.db
rm -f $state/users.db
+ rm -f $state/playlists.db
}
case "$1" in
#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. */
}
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();
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");
}
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) {
}
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) {
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]))));
"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));
}
/* 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);
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"
.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
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.
.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.
.RS
.TP 8
.B pcm
+
Output level for the audio device.
This is probably what you want and is the default.
.TP
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
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.
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.
#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
*/
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);
}
}
}
}
+/** @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
#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,
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 */
/*
* @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)
*
* 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;
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
* @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
#ifndef BASEN_H
#define BASEN_H
-int basen(unsigned long *v,
+int basen(uint32_t *v,
int nwords,
char buffer[],
size_t bufsize,
* Returns 0 on success or -1 on overflow.
*/
+int nesab(uint32_t *v,
+ int nwords,
+ const char *s,
+ unsigned base);
+
#endif /* BASEN_H */
/*
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 {
*sap = xmalloc_noptr(len);
memcpy(*sap, sa, len);
if(namep)
- *namep = name;
+ *namep = format_sockaddr(sa);
if(res)
freeaddrinfo(res);
return len;
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,
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
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 */
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) {
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 */
{ 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 },
{ 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 },
{ 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 },
{ 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 },
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;
}
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);
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 */
#include "speaker-protocol.h"
#include "rights.h"
+#include "addr.h"
struct uaudio;
long prefsync; /* preflog sync interval */
/** @brief Secondary listen address */
- struct stringlist listen;
+ struct netaddress listen;
/** @brief Alias format string */
const char *alias;
/** @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;
const char *password;
/** @brief Address to connect to */
- struct stringlist connect;
+ struct netaddress connect;
/** @brief Directories to search for web templates */
struct stringlist templates;
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;
/** @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);
#include "mem.h"
#include "log.h"
#include "uaudio.h"
+#include "configuration.h"
/** @brief The current PCM handle */
static snd_pcm_t *alsa_pcm;
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) {
*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,
.close_mixer = alsa_close_mixer,
.get_volume = alsa_get_volume,
.set_volume = alsa_set_volume,
+ .configure = alsa_configure
};
#endif
#include "mem.h"
#include "wstat.h"
#include "uaudio.h"
+#include "configuration.h"
/** @brief Pipe to subprocess */
static int command_fd;
static const char *const command_options[] = {
"command",
+ "pause-mode",
NULL
};
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) {
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,
};
/*
#include "mem.h"
#include "log.h"
#include "syscalls.h"
+#include "configuration.h"
/** @brief Callback to request sample data */
static uaudio_callback *coreaudio_callback;
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
#include "mem.h"
#include "log.h"
#include "uaudio.h"
+#include "configuration.h"
#ifndef AFMT_U16_NE
# if BYTE_ORDER == BIG_ENDIAN
/* 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
}
}
}
+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,
.close_mixer = oss_close_mixer,
.get_volume = oss_get_volume,
.set_volume = oss_set_volume,
+ .configure = oss_configure,
};
#endif
#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>
#include "addr.h"
#include "ifreq.h"
#include "timeval.h"
+#include "configuration.h"
/** @brief Bytes to send per network packet
*
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];
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 */
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;
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;
/* 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,
rtp_play,
256 / uaudio_sample_size,
(NETWORK_BYTES - sizeof(struct rtp_header))
- / uaudio_sample_size);
+ / uaudio_sample_size,
+ 0);
}
static void rtp_stop(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,
};
/*
/** @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;
/* 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
/* 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 */
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,
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);
}
#include "uaudio.h"
#include "hash.h"
#include "mem.h"
+#include "log.h"
/** @brief Options for chosen uaudio API */
static hash *uaudio_options;
/** @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);
/** @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
* @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,
* 0 is silent and 100 is maximum volume.
*/
void (*set_volume)(int *left, int *right);
+
+ /** @brief Set configuration */
+ void (*configure)(void);
};
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);
#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);
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;
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;
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
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
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
./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 | \
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)
--- /dev/null
+#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:
+*/
*/
#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 */
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;
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;
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
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;
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 {
l->pf = pf;
if(ev_listen(ev, fd, listen_callback, l, "server listener"))
exit(EXIT_FAILURE);
+ info("listening on %s", name);
return fd;
}
* 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.
*
#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
* 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
*
/** @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;
/** @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];
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;
rc = 0;
}
}
- pthread_mutex_unlock(&lock);
return rc;
}
*
* 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;
}
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
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;
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 */
} 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");
* 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"));
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
}
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. */
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. */
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]);
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);
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;
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");
}
/* 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;
}
}
}
/* 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)
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