chiark / gitweb /
'api' configuration command now uses uaudio. The list of APIs is only
authorRichard Kettlewell <rjk@greenend.org.uk>
Sun, 8 Mar 2009 11:28:22 +0000 (11:28 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sun, 8 Mar 2009 11:28:22 +0000 (11:28 +0000)
available in server processes (since it currently drags in the API
implementations which in turn drags in external audio support
libraries, which extends the dependencies of the command-line client
unacceptable).

disorder-speaker now uses uaudio, abolishing the various speaker-*.c
API-specific files.

uaudio now handles volume control as well as playback, abolishing
mixer.h et al.

"rtp" is the preferred name for the RTP API.  "network" is retained
for backward compatibility.

Lightly tested - works for local play on a Mac.

31 files changed:
disobedience/control.c
disobedience/disobedience.c
disobedience/disobedience.h
doc/disorder_config.5.in
lib/Makefile.am
lib/configuration.c
lib/configuration.h
lib/mixer-alsa.c [deleted file]
lib/mixer-oss.c [deleted file]
lib/mixer.c [deleted file]
lib/mixer.h [deleted file]
lib/uaudio-alsa.c
lib/uaudio-apis.c
lib/uaudio-command.c
lib/uaudio-coreaudio.c
lib/uaudio-oss.c
lib/uaudio-rtp.c
lib/uaudio.c
lib/uaudio.h
server/Makefile.am
server/disorder-server.h
server/disorderd.c
server/server.c
server/speaker-alsa.c [deleted file]
server/speaker-command.c [deleted file]
server/speaker-coreaudio.c [deleted file]
server/speaker-network.c [deleted file]
server/speaker-oss.c [deleted file]
server/speaker.c
server/speaker.h [deleted file]
server/state.c

index c51205966057c1aae229cdedd6a3abd4d0bb45a7..852885f7198827f79680ebf739c040545ba0a1ad 100644 (file)
@@ -20,7 +20,6 @@
  */
 
 #include "disobedience.h"
  */
 
 #include "disobedience.h"
-#include "mixer.h"
 
 /* Forward declarations ---------------------------------------------------- */
 
 
 /* Forward declarations ---------------------------------------------------- */
 
@@ -323,7 +322,7 @@ static void volume_changed(const char attribute((unused)) *event,
   ++suppress_actions;
   /* Only display volume/balance controls if they will work */
   if(!rtp_supported
   ++suppress_actions;
   /* Only display volume/balance controls if they will work */
   if(!rtp_supported
-     || (rtp_supported && mixer_supported(DEFAULT_BACKEND)))
+     || (rtp_supported && backend && backend->set_volume))
     volume_supported = TRUE;
   else
     volume_supported = FALSE;
     volume_supported = TRUE;
   else
     volume_supported = FALSE;
@@ -444,7 +443,8 @@ static void volume_adjusted(GtkAdjustment attribute((unused)) *a,
    * from the log. */
   if(rtp_supported) {
     int l = nearbyint(left(v, b) * 100), r = nearbyint(right(v, b) * 100);
    * from the log. */
   if(rtp_supported) {
     int l = nearbyint(left(v, b) * 100), r = nearbyint(right(v, b) * 100);
-    mixer_control(DEFAULT_BACKEND, &l, &r, 1);
+    if(backend && backend->set_volume)
+      backend->set_volume(&l, &r);
   } else
     disorder_eclient_volume(client, volume_completed,
                             nearbyint(left(v, b) * 100),
   } else
     disorder_eclient_volume(client, volume_completed,
                             nearbyint(left(v, b) * 100),
index ab51bb3e9a9cce2a17f361e503c586b740ccd693..e0fa295bbb0185a9e078bf4adc86cf2cbac2baa5 100644 (file)
@@ -20,7 +20,6 @@
  */
 
 #include "disobedience.h"
  */
 
 #include "disobedience.h"
-#include "mixer.h"
 #include "version.h"
 
 #include <getopt.h>
 #include "version.h"
 
 #include <getopt.h>
@@ -68,6 +67,9 @@ int volume_l;
 /** @brief Right channel volume */
 int volume_r;
 
 /** @brief Right channel volume */
 int volume_r;
 
+/** @brief Audio backend */
+const struct uaudio *backend;
+
 double goesupto = 10;                   /* volume upper bound */
 
 /** @brief True if a NOP is in flight */
 double goesupto = 10;                   /* volume upper bound */
 
 /** @brief True if a NOP is in flight */
@@ -263,10 +265,10 @@ static gboolean periodic_fast(gpointer attribute((unused)) data) {
   }
   last = now;
 #endif
   }
   last = now;
 #endif
-  if(rtp_supported && mixer_supported(DEFAULT_BACKEND)) {
+  if(rtp_supported && backend && backend->get_volume) {
     int nl, nr;
     int nl, nr;
-    if(!mixer_control(DEFAULT_BACKEND, &nl, &nr, 0)
-       && (nl != volume_l || nr != volume_r)) {
+    backend->get_volume(&nl, &nr);
+    if(nl != volume_l || nr != volume_r) {
       volume_l = nl;
       volume_r = nr;
       event_raise("volume-changed", 0);
       volume_l = nl;
       volume_r = nr;
       event_raise("volume-changed", 0);
@@ -453,6 +455,10 @@ int main(int argc, char **argv) {
   D(("create main loop"));
   mainloop = g_main_loop_new(0, 0);
   if(config_read(0)) fatal(0, "cannot read configuration");
   D(("create main loop"));
   mainloop = g_main_loop_new(0, 0);
   if(config_read(0)) fatal(0, "cannot read configuration");
+  /* we'll need mixer support */
+  backend = uaudio_apis[0];
+  if(backend->open_mixer)
+    backend->open_mixer();
   /* create the clients */
   if(!(client = gtkclient())
      || !(logclient = gtkclient()))
   /* create the clients */
   if(!(client = gtkclient())
      || !(logclient = gtkclient()))
index db4bff0e1444020db84306d661fa5a4bedbd711f..ca5f7eff32ba7c6a2d2e91a0e911f6fcb39409d5 100644 (file)
@@ -47,6 +47,7 @@
 #include "eventdist.h"
 #include "split.h"
 #include "timeval.h"
 #include "eventdist.h"
 #include "split.h"
 #include "timeval.h"
+#include "uaudio.h"
 
 #include <glib.h>
 #include <gtk/gtk.h>
 
 #include <glib.h>
 #include <gtk/gtk.h>
@@ -104,6 +105,7 @@ extern GtkTooltips *tips;
 extern int rtp_supported;
 extern int rtp_is_running;
 extern GtkItemFactory *mainmenufactory;
 extern int rtp_supported;
 extern int rtp_is_running;
 extern GtkItemFactory *mainmenufactory;
+extern const struct uaudio *backend;
 
 extern const disorder_eclient_log_callbacks log_callbacks;
 
 
 extern const disorder_eclient_log_callbacks log_callbacks;
 
index 0364126c3bd7dd1e4beec93169952cb0f307ef9f..d033258359123b9e76b5b86242d4c23aa7b08dcc 100644 (file)
@@ -1,5 +1,5 @@
 .\"
 .\"
-.\" Copyright (C) 2004-2008 Richard Kettlewell
+.\" Copyright (C) 2004-2009 Richard Kettlewell
 .\"
 .\" This program is free software: you can redistribute it and/or modify
 .\" it under the terms of the GNU General Public License as published by
 .\"
 .\" This program is free software: you can redistribute it and/or modify
 .\" it under the terms of the GNU General Public License as published by
@@ -276,12 +276,14 @@ This is the default if
 .B speaker_command
 is specified, or if no native is available.
 .TP
 .B speaker_command
 is specified, or if no native is available.
 .TP
-.B network
+.B rtp
 Transmit audio over the network.
 This is the default if \fBbroadcast\fR is specified.
 You can use
 .BR disorder-playrtp (1)
 to receive and play the resulting stream on Linux and OS X.
 Transmit audio over the network.
 This is the default if \fBbroadcast\fR is specified.
 You can use
 .BR disorder-playrtp (1)
 to receive and play the resulting stream on Linux and OS X.
+.B network
+is a deprecated synonym for this API.
 .RE
 .TP
 .B authorization_algorithm \fIALGORITHM\fR
 .RE
 .TP
 .B authorization_algorithm \fIALGORITHM\fR
@@ -293,7 +295,7 @@ for more details.
 .TP
 .B broadcast \fIADDRESS\fR \fIPORT\fR
 Transmit sound data to \fIADDRESS\fR using UDP port \fIPORT\fR.
 .TP
 .B broadcast \fIADDRESS\fR \fIPORT\fR
 Transmit sound data to \fIADDRESS\fR using UDP port \fIPORT\fR.
-This implies \fBapi network\fR.
+This implies \fBapi rtp\fR.
 .IP
 See also \fBmulticast_loop\fR and \fBmulticast_ttl\fR.
 .TP
 .IP
 See also \fBmulticast_loop\fR and \fBmulticast_ttl\fR.
 .TP
@@ -435,12 +437,12 @@ For \fBapi coreaudio\fR, volume setting is not currently supported.
 .B multicast_loop yes\fR|\fBno
 Determines whether multicast packets are loop backed to the sending host.
 The default is \fByes\fR.
 .B multicast_loop yes\fR|\fBno
 Determines whether multicast packets are loop backed to the sending host.
 The default is \fByes\fR.
-This only applies if \fBapi\fR is set to \fBnetwork\fR and \fBbroadcast\fR
+This only applies if \fBapi\fR is set to \fBrtp\fR and \fBbroadcast\fR
 is actually a multicast address.
 .TP
 .B multicast_ttl \fIHOPS\fR
 Set the maximum number of hops to send multicast packets.
 is actually a multicast address.
 .TP
 .B multicast_ttl \fIHOPS\fR
 Set the maximum number of hops to send multicast packets.
-This only applies if \fBapi\fR is set to \fBnetwork\fR and
+This only applies if \fBapi\fR is set to \fBrtp\fR and
 \fBbroadcast\fR is actually a multicast address.
 The default is 1.
 .TP
 \fBbroadcast\fR is actually a multicast address.
 The default is 1.
 .TP
@@ -628,7 +630,7 @@ The default is
 .BR 16/44100/2 .
 .PP
 With the
 .BR 16/44100/2 .
 .PP
 With the
-.B network
+.B rtp
 backend the sample format is forced to
 .B 16b/44100/2
 and with the
 backend the sample format is forced to
 .B 16b/44100/2
 and with the
index e244c2554d54180e66d51079215a2802aaa33429..1dacb382166ed9f1e623e01b5e7fe72e4827a5a0 100644 (file)
@@ -60,7 +60,6 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        macros.c macros-builtin.c macros.h              \
        mem.c mem.h                                     \
        mime.h mime.c                                   \
        macros.c macros-builtin.c macros.h              \
        mem.c mem.h                                     \
        mime.h mime.c                                   \
-       mixer.c mixer.h mixer-oss.c mixer-alsa.c        \
        printf.c printf.h                               \
        asprintf.c fprintf.c snprintf.c                 \
        queue.c queue.h                                 \
        printf.c printf.h                               \
        asprintf.c fprintf.c snprintf.c                 \
        queue.c queue.h                                 \
index 2a0b89ce0dfb5837017d993400072e52dfa8ad96..035e81443744a75bec914b85116eeab25a84a836 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
  * Portions copyright (C) 2007 Mark Wooding
  *
  * This program is free software: you can redistribute it and/or modify
  * Portions copyright (C) 2007 Mark Wooding
  *
  * This program is free software: you can redistribute it and/or modify
 #include "inputline.h"
 #include "charset.h"
 #include "defs.h"
 #include "inputline.h"
 #include "charset.h"
 #include "defs.h"
-#include "mixer.h"
 #include "printf.h"
 #include "regsub.h"
 #include "signame.h"
 #include "authhash.h"
 #include "vector.h"
 #include "printf.h"
 #include "regsub.h"
 #include "signame.h"
 #include "authhash.h"
 #include "vector.h"
+#include "uaudio.h"
 
 /** @brief Path to config file 
  *
 
 /** @brief Path to config file 
  *
@@ -62,6 +62,12 @@ char *configfile;
  */
 int config_per_user = 1;
 
  */
 int config_per_user = 1;
 
+/** @brief Table of audio APIs
+ *
+ * Only set in server processes.
+ */
+const struct uaudio *const *config_uaudio_apis;
+
 /** @brief Config file parser state */
 struct config_state {
   /** @brief Filename */
 /** @brief Config file parser state */
 struct config_state {
   /** @brief Filename */
@@ -466,52 +472,6 @@ static int set_transform(const struct config_state *cs,
   return 0;
 }
 
   return 0;
 }
 
-static int set_backend(const struct config_state *cs,
-                      const struct conf *whoami,
-                      int nvec, char **vec) {
-  int *const valuep = ADDRESS(cs->config, int);
-  
-  if(nvec != 1) {
-    error(0, "%s:%d: '%s' requires one argument",
-         cs->path, cs->line, whoami->name);
-    return -1;
-  }
-  if(!strcmp(vec[0], "alsa")) {
-#if HAVE_ALSA_ASOUNDLIB_H
-    *valuep = BACKEND_ALSA;
-#else
-    error(0, "%s:%d: ALSA is not available on this platform",
-         cs->path, cs->line);
-    return -1;
-#endif
-  } else if(!strcmp(vec[0], "command"))
-    *valuep = BACKEND_COMMAND;
-  else if(!strcmp(vec[0], "network"))
-    *valuep = BACKEND_NETWORK;
-  else if(!strcmp(vec[0], "coreaudio")) {
-#if HAVE_COREAUDIO_AUDIOHARDWARE_H
-    *valuep = BACKEND_COREAUDIO;
-#else
-    error(0, "%s:%d: Core Audio is not available on this platform",
-         cs->path, cs->line);
-    return -1;
-#endif
-  } else if(!strcmp(vec[0], "oss")) {
-#if HAVE_SYS_SOUNDCARD_H
-    *valuep = BACKEND_OSS;
-#else
-    error(0, "%s:%d: OSS is not available on this platform",
-         cs->path, cs->line);
-    return -1;
-#endif
-  } else {
-    error(0, "%s:%d: invalid '%s' value '%s'",
-         cs->path, cs->line, whoami->name, vec[0]);
-    return -1;
-  }
-  return 0;
-}
-
 static int set_rights(const struct config_state *cs,
                      const struct conf *whoami,
                      int nvec, char **vec) {
 static int set_rights(const struct config_state *cs,
                      const struct conf *whoami,
                      int nvec, char **vec) {
@@ -627,8 +587,7 @@ static const struct conftype
   type_restrict = { set_restrict, free_none },
   type_namepart = { set_namepart, free_namepartlist },
   type_transform = { set_transform, free_transformlist },
   type_restrict = { set_restrict, free_none },
   type_namepart = { set_namepart, free_namepartlist },
   type_transform = { set_transform, free_transformlist },
-  type_rights = { set_rights, free_none },
-  type_backend = { set_backend, free_none };
+  type_rights = { set_rights, free_none };
 
 /* specific validation routine */
 
 
 /* specific validation routine */
 
@@ -907,6 +866,29 @@ static int validate_algo(const struct config_state attribute((unused)) *cs,
   return 0;
 }
 
   return 0;
 }
 
+static int validate_backend(const struct config_state attribute((unused)) *cs,
+                            int nvec,
+                            char **vec) {
+  int n;
+  if(nvec != 1) {
+    error(0, "%s:%d: invalid sound API specification", cs->path, cs->line);
+    return -1;
+  }
+  if(!strcmp(vec[0], "network")) {
+    error(0, "'api network' is deprecated; use 'api rtp'");
+    return 0;
+  }
+  if(config_uaudio_apis) {
+    for(n = 0; config_uaudio_apis[n]; ++n)
+      if(!strcmp(vec[0], config_uaudio_apis[n]->name))
+        return 0;
+    error(0, "%s:%d: unrecognized sound API '%s'", cs->path, cs->line, vec[0]);
+    return -1;
+  }
+  /* In non-server processes we have no idea what's valid */
+  return 0;
+}
+
 /** @brief Item name and and offset */
 #define C(x) #x, offsetof(struct config, x)
 /** @brief Item name and and offset */
 /** @brief Item name and and offset */
 #define C(x) #x, offsetof(struct config, x)
 /** @brief Item name and and offset */
@@ -916,7 +898,7 @@ static int validate_algo(const struct config_state attribute((unused)) *cs,
 static const struct conf conf[] = {
   { C(alias),            &type_string,           validate_alias },
   { C(allow),            &type_stringlist_accum, validate_allow },
 static const struct conf conf[] = {
   { C(alias),            &type_string,           validate_alias },
   { C(allow),            &type_stringlist_accum, validate_allow },
-  { C(api),              &type_backend,          validate_any },
+  { 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(authorization_algorithm), &type_string,    validate_algo },
   { C(broadcast),        &type_stringlist,       validate_addrport },
   { C(broadcast_from),   &type_stringlist,       validate_addrport },
@@ -965,7 +947,7 @@ static const struct conf conf[] = {
   { C(signal),           &type_signal,           validate_any },
   { C(smtp_server),      &type_string,           validate_any },
   { C(sox_generation),   &type_integer,          validate_non_negative },
   { C(signal),           &type_signal,           validate_any },
   { C(smtp_server),      &type_string,           validate_any },
   { C(sox_generation),   &type_integer,          validate_non_negative },
-  { C2(speaker_backend, api),  &type_backend,          validate_any },
+  { C2(speaker_backend, api),  &type_string,     validate_backend },
   { C(speaker_command),  &type_string,           validate_any },
   { C(stopword),         &type_string_accum,     validate_any },
   { C(templates),        &type_string_accum,     validate_isdir },
   { C(speaker_command),  &type_string,           validate_any },
   { C(stopword),         &type_string_accum,     validate_any },
   { C(templates),        &type_string_accum,     validate_isdir },
@@ -1178,7 +1160,7 @@ static struct config *config_default(void) {
   c->sample_format.endian = ENDIAN_NATIVE;
   c->queue_pad = 10;
   c->replay_min = 8 * 3600;
   c->sample_format.endian = ENDIAN_NATIVE;
   c->queue_pad = 10;
   c->replay_min = 8 * 3600;
-  c->api = -1;
+  c->api = NULL;
   c->multicast_ttl = 1;
   c->multicast_loop = 1;
   c->authorization_algorithm = xstrdup("sha1");
   c->multicast_ttl = 1;
   c->multicast_loop = 1;
   c->authorization_algorithm = xstrdup("sha1");
@@ -1277,34 +1259,36 @@ static void config_postdefaults(struct config *c,
     for(n = 0; n < NTRANSFORM; ++n)
       set_transform(&cs, whoami, 5, (char **)transform[n]);
   }
     for(n = 0; n < NTRANSFORM; ++n)
       set_transform(&cs, whoami, 5, (char **)transform[n]);
   }
-  if(c->api == -1) {
+  if(!c->api) {
     if(c->speaker_command)
     if(c->speaker_command)
-      c->api = BACKEND_COMMAND;
+      c->api = xstrdup("command");
     else if(c->broadcast.n)
     else if(c->broadcast.n)
-      c->api = BACKEND_NETWORK;
+      c->api = xstrdup("rtp");
+    else if(config_uaudio_apis)
+      c->api = xstrdup(config_uaudio_apis[0]->name);
     else
     else
-      c->api = DEFAULT_BACKEND;
+      c->api = xstrdup("<none>");
   }
   }
+  if(!strcmp(c->api, "network"))
+    c->api = xstrdup("rtp");
   if(server) {
   if(server) {
-    if(c->api == BACKEND_COMMAND && !c->speaker_command)
+    if(!strcmp(c->api, "command") && !c->speaker_command)
       fatal(0, "'api command' but speaker_command is not set");
       fatal(0, "'api command' but speaker_command is not set");
-    if(c->api == BACKEND_NETWORK && !c->broadcast.n)
-      fatal(0, "'api network' but broadcast is not set");
+    if((!strcmp(c->api, "rtp")) && !c->broadcast.n)
+      fatal(0, "'api rtp' but broadcast is not set");
   }
   /* Override sample format */
   }
   /* Override sample format */
-  switch(c->api) {
-  case BACKEND_NETWORK:
+  if(!strcmp(c->api, "rtp")) {
     c->sample_format.rate = 44100;
     c->sample_format.channels = 2;
     c->sample_format.bits = 16;
     c->sample_format.rate = 44100;
     c->sample_format.channels = 2;
     c->sample_format.bits = 16;
-    c->sample_format.endian = ENDIAN_BIG;
-    break;
-  case BACKEND_COREAUDIO:
+    c->sample_format.endian = ENDIAN_NATIVE;
+  }
+  if(!strcmp(c->api, "coreaudio")) {
     c->sample_format.rate = 44100;
     c->sample_format.channels = 2;
     c->sample_format.bits = 16;
     c->sample_format.endian = ENDIAN_NATIVE;
     c->sample_format.rate = 44100;
     c->sample_format.channels = 2;
     c->sample_format.bits = 16;
     c->sample_format.endian = ENDIAN_NATIVE;
-    break; 
   }
   if(!c->default_rights) {
     rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
   }
   if(!c->default_rights) {
     rights_type r = RIGHTS__MASK & ~(RIGHT_ADMIN|RIGHT_REGISTER
index 624e495780001cf690d56dc39b0b612274b6508c..d9af67dd3c0fa4511a035dec732540e73a440c0e 100644 (file)
@@ -1,7 +1,6 @@
-
 /*
  * This file is part of DisOrder.
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -28,6 +27,8 @@
 #include "speaker-protocol.h"
 #include "rights.h"
 
 #include "speaker-protocol.h"
 #include "rights.h"
 
+struct uaudio;
+
 /* Configuration is kept in a @struct config@; the live configuration
  * is always pointed to by @config@.  Values in @config@ are UTF-8 encoded.
  */
 /* Configuration is kept in a @struct config@; the live configuration
  * is always pointed to by @config@.  Values in @config@ are UTF-8 encoded.
  */
@@ -176,29 +177,8 @@ struct config {
   /** @brief Sox syntax generation */
   long sox_generation;
 
   /** @brief Sox syntax generation */
   long sox_generation;
 
-  /** @brief API used to play sound
-   *
-   * Choices are @ref BACKEND_ALSA, @ref BACKEND_COMMAND or @ref
-   * BACKEND_NETWORK.
-   */
-  int api;
-
-/* These values had better be non-negative */
-#define BACKEND_ALSA 0                 /**< Use ALSA (Linux only) */
-#define BACKEND_COMMAND 1              /**< Execute a command */
-#define BACKEND_NETWORK 2              /**< Transmit RTP  */
-#define BACKEND_COREAUDIO 3            /**< Use Core Audio (Mac only) */
-#define BACKEND_OSS 4                  /**< Use OSS */
-
-#if HAVE_ALSA_ASOUNDLIB_H
-# define DEFAULT_BACKEND BACKEND_ALSA
-#elif HAVE_SYS_SOUNDCARD_H || EMPEG_HOST
-# define DEFAULT_BACKEND BACKEND_OSS
-#elif HAVE_COREAUDIO_AUDIOHARDWARE_H
-# define DEFAULT_BACKEND BACKEND_COREAUDIO
-#else
-# error Cannot choose a default backend
-#endif
+  /** @brief API used to play sound */
+  const char *api;
 
   /** @brief Home directory for state files */
   const char *home;
 
   /** @brief Home directory for state files */
   const char *home;
@@ -328,6 +308,8 @@ char *config_private(void);
 extern char *configfile;
 extern int config_per_user;
 
 extern char *configfile;
 extern int config_per_user;
 
+extern const struct uaudio *const *config_uaudio_apis;
+
 #endif /* CONFIGURATION_H */
 
 /*
 #endif /* CONFIGURATION_H */
 
 /*
diff --git a/lib/mixer-alsa.c b/lib/mixer-alsa.c
deleted file mode 100644 (file)
index 1ab85ab..0000000
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007, 2008 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer-alsa.c
- * @brief ALSA mixer support
- *
- * The documentation for ALSA's mixer support is completely hopeless,
- * which is a particular nuisnace given it's got an incredibly verbose
- * API.  Much of this code is cribbed from
- * alsa-utils-1.0.13/amixer/amixer.c.
- *
- * Mono output devices are supported, but the support is not tested
- * (as I don't one).
- */
-
-#include "common.h"
-
-#if HAVE_ALSA_ASOUNDLIB_H
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stddef.h>
-#include <ctype.h>
-#include <sys/ioctl.h>
-#include <alsa/asoundlib.h>
-
-#include "configuration.h"
-#include "mixer.h"
-#include "log.h"
-#include "syscalls.h"
-
-/** @brief Shared state for ALSA mixer support */
-struct alsa_mixer_state {
-  /** @brief Mixer handle */
-  snd_mixer_t *handle;
-
-  /** @brief Mixer control */
-  snd_mixer_elem_t *elem;
-
-  /** @brief Left channel */
-  snd_mixer_selem_channel_id_t left;
-
-  /** @brief Right channel */
-  snd_mixer_selem_channel_id_t right;
-
-  /** @brief Minimum level */
-  long min;
-
-  /** @brief Maximum level */
-  long max;
-};
-
-/** @brief Destroy a @ref alsa_mixer_state */
-static void alsa_close(struct alsa_mixer_state *h) {
-  /* TODO h->elem */
-  if(h->handle)
-    snd_mixer_close(h->handle);
-}
-
-/** @brief Initialize a @ref alsa_mixer_state */
-static int alsa_open(struct alsa_mixer_state *h) {
-  int err;
-  snd_mixer_selem_id_t *id;
-
-  snd_mixer_selem_id_alloca(&id);
-  memset(h, 0, sizeof h);
-  if((err = snd_mixer_open(&h->handle, 0))) {
-    error(0, "snd_mixer_open: %s", snd_strerror(err));
-    return -1;
-  }
-  if((err = snd_mixer_attach(h->handle, config->device))) {
-    error(0, "snd_mixer_attach %s: %s",
-         config->device, snd_strerror(err));
-    goto error;
-  }
-  if((err = snd_mixer_selem_register(h->handle, 0/*options*/, 0/*classp*/))) {
-    error(0, "snd_mixer_selem_register %s: %s",
-         config->device, snd_strerror(err));
-    goto error;
-  }
-  if((err = snd_mixer_load(h->handle))) {
-    error(0, "snd_mixer_load %s: %s",
-         config->device, snd_strerror(err));
-    goto error;
-  }
-  snd_mixer_selem_id_set_name(id, config->channel);
-  snd_mixer_selem_id_set_index(id, atoi(config->mixer));
-  if(!(h->elem = snd_mixer_find_selem(h->handle, id))) {
-    error(0, "device '%s' mixer control '%s,%s' does not exist",
-         config->device, config->channel, config->mixer);
-    goto error;
-  }
-  if(!snd_mixer_selem_has_playback_volume(h->elem)) {
-    error(0, "device '%s' mixer control '%s,%s' has no playback volume",
-         config->device, config->channel, config->mixer);
-    goto error;
-  }
-  if(snd_mixer_selem_is_playback_mono(h->elem)) {
-    h->left = h->right = SND_MIXER_SCHN_MONO;
-  } else {
-    h->left = SND_MIXER_SCHN_FRONT_LEFT;
-    h->right = SND_MIXER_SCHN_FRONT_RIGHT;
-  }
-  if(!snd_mixer_selem_has_playback_channel(h->elem, h->left)
-     || !snd_mixer_selem_has_playback_channel(h->elem, h->right)) {
-    error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
-         config->device, config->channel, config->mixer);
-    goto error;
-  }
-  snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
-  return 0;
-error:
-  alsa_close(h);
-  return -1;
-}
-
-/** @brief Convert a level to a percentage */
-static int to_percent(const struct alsa_mixer_state *h, long n) {
-  return (n - h->min) * 100 / (h->max - h->min);
-}
-
-/** @brief Get ALSA volume */
-static int alsa_get(int *left, int *right) {
-  struct alsa_mixer_state h[1];
-  long l, r;
-  int err;
-  
-  if(alsa_open(h))
-    return -1;
-  if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
-     || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
-    error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
-    goto error;
-  }
-  *left = to_percent(h, l);
-  *right = to_percent(h, r);
-  alsa_close(h);
-  return 0;
-error:
-  alsa_close(h);
-  return -1;
-}
-
-/** @brief Convert a percentage to a level */
-static int from_percent(const struct alsa_mixer_state *h, int n) {
-  return h->min + n * (h->max - h->min) / 100;
-}
-
-/** @brief Set ALSA volume */
-static int alsa_set(int *left, int *right) {
-  struct alsa_mixer_state h[1];
-  long l, r;
-  int err;
-  
-  if(alsa_open(h))
-    return -1;
-  /* Set the volume */
-  if(h->left == h->right) {
-    /* Mono output - just use the loudest */
-    if((err = snd_mixer_selem_set_playback_volume
-       (h->elem, h->left,
-        from_percent(h, *left > *right ? *left : *right)))) {
-      error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
-      goto error;
-    }
-  } else {
-    /* Stereo output */
-    if((err = snd_mixer_selem_set_playback_volume
-       (h->elem, h->left, from_percent(h, *left)))
-       || (err = snd_mixer_selem_set_playback_volume
-          (h->elem, h->right, from_percent(h, *right)))) {
-      error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
-      goto error;
-    }
-  }
-  /* Read it back to see what we ended up at */
-  if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
-     || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
-    error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
-    goto error;
-  }
-  *left = to_percent(h, l);
-  *right = to_percent(h, r);
-  alsa_close(h);
-  return 0;
-error:
-  alsa_close(h);
-  return -1;
-}
-
-/** @brief ALSA mixer vtable */
-const struct mixer mixer_alsa = {
-  BACKEND_ALSA,
-  alsa_get,
-  alsa_set,
-  "0",
-  "PCM"
-};
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
diff --git a/lib/mixer-oss.c b/lib/mixer-oss.c
deleted file mode 100644 (file)
index a4ee419..0000000
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2004, 2007, 2008 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer-oss.c
- * @brief OSS mixer support
- *
- * Mono output devices aren't explicitly supported (but may work
- * nonetheless).
- */
-
-#include "common.h"
-
-#if HAVE_SYS_SOUNDCARD_H
-
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <stddef.h>
-#include <sys/ioctl.h>
-#include <sys/soundcard.h>
-
-#include "configuration.h"
-#include "mixer.h"
-#include "log.h"
-#include "syscalls.h"
-#include "mem.h"
-
-/* documentation does not match implementation! */
-#ifndef SOUND_MIXER_READ
-# define SOUND_MIXER_READ(x) MIXER_READ(x)
-#endif
-#ifndef SOUND_MIXER_WRITE
-# define SOUND_MIXER_WRITE(x) MIXER_WRITE(x)
-#endif
-
-/** @brief Channel names */
-static const char *channels[] = SOUND_DEVICE_NAMES;
-
-/** @brief Convert channel name to number */
-static int mixer_channel(const char *c) {
-  unsigned n;
-  
-  if(!c[strspn(c, "0123456789")])
-    return atoi(c);
-  else {
-    for(n = 0; n < sizeof channels / sizeof *channels; ++n)
-      if(!strcmp(channels[n], c))
-       return n;
-    return -1;
-  }
-}
-
-/** @brief Open the OSS mixer device and return its fd */
-static int oss_do_open(void) {
-  int fd;
-  
-  if((fd = open(config->mixer, O_RDWR, 0)) < 0) {
-    static char *reported;
-
-    if(!reported || strcmp(reported, config->mixer)) {
-      if(reported)
-       xfree(reported);
-      reported = xstrdup(config->mixer);
-      error(errno, "error opening %s", config->mixer);
-    }
-  }
-  return fd;
-}
-
-/** @brief Get the OSS mixer setting */
-static int oss_do_get(int *left, int *right, int fd, int ch) {
-  int r;
-  
-  if(ioctl(fd, SOUND_MIXER_READ(ch), &r) == -1) {
-    error(errno, "error reading %s channel %s",
-         config->mixer, config->channel);
-    return -1;
-  }
-  *left = r & 0xff;
-  *right = (r >> 8) & 0xff;
-  return 0;
-}
-
-/** @brief Get OSS volume */
-static int oss_get(int *left, int *right) {
-  int ch, fd;
-
-  if(config->mixer
-     && config->channel
-     && (ch = mixer_channel(config->channel)) != -1) {
-    if((fd = oss_do_open()) < 0)
-      return -1;
-    if(oss_do_get(left, right, fd, ch) < 0) {
-      xclose(fd);
-      return -1;
-    }
-    xclose(fd);
-    return 0;
-  } else
-    return -1;
-}
-
-/** @brief Set OSS volume */
-static int oss_set(int *left, int *right) {
-  int ch, fd, r;
-
-  if(config->mixer
-     && config->channel
-     && (ch = mixer_channel(config->channel)) != -1) {
-    if((fd = oss_do_open()) < 0)
-      return -1;
-    r = (*left & 0xff) + (*right & 0xff) * 256;
-    if(ioctl(fd, SOUND_MIXER_WRITE(ch), &r) == -1) {
-      error(errno, "error changing %s channel %s",
-           config->mixer, config->channel);
-      xclose(fd);
-      return -1;
-    }
-    if(oss_do_get(left, right, fd, ch) < 0) {
-      xclose(fd);
-      return -1;
-    }
-    xclose(fd);
-    return 0;
-  } else
-    return -1;
-}
-
-/** @brief OSS mixer vtable */
-const struct mixer mixer_oss = {
-  BACKEND_OSS,
-  oss_get,
-  oss_set,
-  "/dev/mixer",
-  "pcm"
-};
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
diff --git a/lib/mixer.c b/lib/mixer.c
deleted file mode 100644 (file)
index a24f7eb..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer.c
- * @brief Mixer support
- */
-
-#include "common.h"
-
-#include "configuration.h"
-#include "mixer.h"
-#include "log.h"
-#include "mem.h"
-
-/** @brief Whether lack of volume support has been reported yet */
-static int none_reported;
-
-/** @brief Get/set volume stub if volume control is not supported */
-static int none_get_set(int attribute((unused)) *left,
-                       int attribute((unused)) *right) {
-  if(!none_reported) {
-    error(0, "don't know how to get/set volume with this api");
-    none_reported = 1;
-  }
-  return -1;
-}
-
-/** @brief Stub mixer control */
-static const struct mixer mixer_none = {
-  -1,
-  none_get_set,
-  none_get_set,
-  "",
-  ""
-};
-
-/** @brief Table of mixer definitions */
-static const struct mixer *mixers[] = {
-#if HAVE_SYS_SOUNDCARD_H
-  &mixer_oss,
-#endif
-#if HAVE_ALSA_ASOUNDLIB_H
-  &mixer_alsa,
-#endif
-  &mixer_none                          /* make sure array is never empty */
-};
-
-/** @brief Number of mixer definitions */
-#define NMIXERS (sizeof mixers / sizeof *mixers)
-
-/** @brief Find the mixer definition */
-static const struct mixer *find_mixer(int api) {
-  size_t n;
-
-  if(api == -1)
-    api = config->api;
-  for(n = 0; n < NMIXERS; ++n)
-    if(mixers[n]->api == api)
-      return mixers[n];
-  return &mixer_none;
-}
-
-/** @brief Return true if we know how to drive the mixer
- * @param api Sound api or -1 for default
- * @return true if suppored, false otherwise
- */
-int mixer_supported(int api) {
-  const struct mixer *const m = find_mixer(api);
-  return m != &mixer_none;
-}
-
-/** @brief Get/set volume
- * @param api Sound api or -1 for default
- * @param left Left channel level, 0-100
- * @param right Right channel level, 0-100
- * @param set Set volume if non-0
- * @return 0 on success, non-0 on error
- *
- * If getting the volume then @p left and @p right are filled in.
- *
- * If setting the volume then the target levels are read from @p left and
- * @p right, and the actual level set is stored in them.
- */
-int mixer_control(int api, int *left, int *right, int set) {
-  const struct mixer *const m = find_mixer(api);
-
-  /* We impose defaults bizarrely late, but this has the advantage of
-   * not making everything depend on sound libraries */
-  if(!config->mixer)
-    config->mixer = xstrdup(m->device);
-  if(!config->channel)
-    config->channel = xstrdup(m->channel);
-  if(set)
-    return m->set(left, right);
-  else
-    return m->get(left, right);
-}
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
diff --git a/lib/mixer.h b/lib/mixer.h
deleted file mode 100644 (file)
index 3d42246..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2004, 2007 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file lib/mixer.h
- * @brief Mixer support
- */
-
-#ifndef MIXER_H
-#define MIXER_H
-
-/** @brief Definition of a mixer */
-struct mixer {
-  /** @brief API used by this mixer */
-  int api;
-
-  /** @brief Get the volume
-   * @param left Where to store left-channel volume
-   * @param right Where to store right-channel volume
-   * @return 0 on success, non-0 on error
-   */
-  int (*get)(int *left, int *right);
-
-  /** @brief Set the volume
-   * @param left Pointer to target left-channel volume
-   * @param right Pointer to target right-channel volume
-   * @return 0 on success, non-0 on error
-   *
-   * @p left and @p right are updated with the actual volume set.
-   */
-  int (*set)(int *left, int *right);
-
-  /** @brief Default device */
-  const char *device;
-
-  /** @brief Default channel */
-  const char *channel;
-};
-
-int mixer_control(int api, int *left, int *right, int set);
-const char *mixer_default_device(int api);
-const char *mixer_default_channel(int api);
-int mixer_supported(int api);
-
-extern const struct mixer mixer_oss;
-extern const struct mixer mixer_alsa;
-
-#endif /* MIXER_H */
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-End:
-*/
index 343d1aabf8657206922681104d73a96bce228e44..be716cfcde8688fc5eff6d42bed01917f8f41f2b 100644 (file)
@@ -32,9 +32,29 @@ static snd_pcm_t *alsa_pcm;
 
 static const char *const alsa_options[] = {
   "device",
 
 static const char *const alsa_options[] = {
   "device",
+  "mixer-control",
+  "mixer-channel",
   NULL
 };
 
   NULL
 };
 
+/** @brief Mixer handle */
+snd_mixer_t *alsa_mixer_handle;
+
+/** @brief Mixer control */
+static snd_mixer_elem_t *alsa_mixer_elem;
+
+/** @brief Left channel */
+static snd_mixer_selem_channel_id_t alsa_mixer_left;
+
+/** @brief Right channel */
+static snd_mixer_selem_channel_id_t alsa_mixer_right;
+
+/** @brief Minimum level */
+static long alsa_mixer_min;
+
+/** @brief Maximum level */
+static long alsa_mixer_max;
+
 /** @brief Actually play sound via ALSA */
 static size_t alsa_play(void *buffer, size_t samples) {
   int err;
 /** @brief Actually play sound via ALSA */
 static size_t alsa_play(void *buffer, size_t samples) {
   int err;
@@ -59,11 +79,9 @@ static size_t alsa_play(void *buffer, size_t samples) {
 
 /** @brief Open the ALSA sound device */
 static void alsa_open(void) {
 
 /** @brief Open the ALSA sound device */
 static void alsa_open(void) {
-  const char *device = uaudio_get("device");
+  const char *device = uaudio_get("device", "default");
   int err;
 
   int err;
 
-  if(!device || !*device)
-    device = "default";
   if((err = snd_pcm_open(&alsa_pcm,
                         device,
                         SND_PCM_STREAM_PLAYBACK,
   if((err = snd_pcm_open(&alsa_pcm,
                         device,
                         SND_PCM_STREAM_PLAYBACK,
@@ -126,13 +144,113 @@ static void alsa_stop(void) {
   alsa_pcm = 0;
 }
 
   alsa_pcm = 0;
 }
 
+/** @brief Convert a level to a percentage */
+static int to_percent(long n) {
+  return (n - alsa_mixer_min) * 100 / (alsa_mixer_max - alsa_mixer_min);
+}
+
+static void alsa_open_mixer(void) {
+  int err;
+  snd_mixer_selem_id_t *id;
+  const char *device = uaudio_get("device", "default");
+  const char *mixer = uaudio_get("mixer-control", "0");
+  const char *channel = uaudio_get("mixer-channel", "PCM");
+
+  snd_mixer_selem_id_alloca(&id);
+  if((err = snd_mixer_open(&alsa_mixer_handle, 0)))
+    fatal(0, "snd_mixer_open: %s", snd_strerror(err));
+  if((err = snd_mixer_attach(alsa_mixer_handle, config->device)))
+    fatal(0, "snd_mixer_attach %s: %s", config->device, snd_strerror(err));
+  if((err = snd_mixer_selem_register(alsa_mixer_handle,
+                                     0/*options*/, 0/*classp*/)))
+    fatal(0, "snd_mixer_selem_register %s: %s",
+          device, snd_strerror(err));
+  if((err = snd_mixer_load(alsa_mixer_handle)))
+    fatal(0, "snd_mixer_load %s: %s", device, snd_strerror(err));
+  snd_mixer_selem_id_set_name(id, channel);
+  snd_mixer_selem_id_set_index(id, atoi(mixer));
+  if(!(alsa_mixer_elem = snd_mixer_find_selem(alsa_mixer_handle, id)))
+    fatal(0, "device '%s' mixer control '%s,%s' does not exist",
+         device, channel, mixer);
+  if(!snd_mixer_selem_has_playback_volume(alsa_mixer_elem))
+    fatal(0, "device '%s' mixer control '%s,%s' has no playback volume",
+         device, channel, mixer);
+  if(snd_mixer_selem_is_playback_mono(alsa_mixer_elem)) {
+    alsa_mixer_left = alsa_mixer_right = SND_MIXER_SCHN_MONO;
+  } else {
+    alsa_mixer_left = SND_MIXER_SCHN_FRONT_LEFT;
+    alsa_mixer_right = SND_MIXER_SCHN_FRONT_RIGHT;
+  }
+  if(!snd_mixer_selem_has_playback_channel(alsa_mixer_elem,
+                                           alsa_mixer_left)
+     || !snd_mixer_selem_has_playback_channel(alsa_mixer_elem,
+                                              alsa_mixer_right))
+    fatal(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
+         device, channel, mixer);
+  snd_mixer_selem_get_playback_volume_range(alsa_mixer_elem,
+                                            &alsa_mixer_min, &alsa_mixer_max);
+
+}
+
+static void alsa_close_mixer(void) {
+  /* TODO alsa_mixer_elem */
+  if(alsa_mixer_handle)
+    snd_mixer_close(alsa_mixer_handle);
+}
+
+static void alsa_get_volume(int *left, int *right) {
+  long l, r;
+  int err;
+  
+  if((err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+                                                alsa_mixer_left, &l))
+     || (err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+                                                   alsa_mixer_right, &r)))
+    fatal(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
+  *left = to_percent(l);
+  *right = to_percent(r);
+}
+
+static void alsa_set_volume(int *left, int *right) {
+  long l, r;
+  int err;
+  
+  /* Set the volume */
+  if(alsa_mixer_left == alsa_mixer_right) {
+    /* Mono output - just use the loudest */
+    if((err = snd_mixer_selem_set_playback_volume
+       (alsa_mixer_elem, alsa_mixer_left,
+        from_percent(*left > *right ? *left : *right))))
+      fatal(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
+  } else {
+    /* Stereo output */
+    if((err = snd_mixer_selem_set_playback_volume
+       (alsa_mixer_elem, alsa_mixer_left, from_percent(*left)))
+       || (err = snd_mixer_selem_set_playback_volume
+          (alsa_mixer_elem, alsa_mixer_right, from_percent(*right))))
+      fatal(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
+  }
+  /* Read it back to see what we ended up at */
+  if((err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+                                                alsa_mixer_left, &l))
+     || (err = snd_mixer_selem_get_playback_volume(alsa_mixer_elem,
+                                                   alsa_mixer_right, &r)))
+    fatal(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
+  *left = to_percent(l);
+  *right = to_percent(r);
+}
+
 const struct uaudio uaudio_alsa = {
   .name = "alsa",
   .options = alsa_options,
   .start = alsa_start,
   .stop = alsa_stop,
   .activate = alsa_activate,
 const struct uaudio uaudio_alsa = {
   .name = "alsa",
   .options = alsa_options,
   .start = alsa_start,
   .stop = alsa_stop,
   .activate = alsa_activate,
-  .deactivate = alsa_deactivate
+  .deactivate = alsa_deactivate,
+  .open_mixer = alsa_open_mixer,
+  .close_mixer = alsa_close_mixer,
+  .get_volume = alsa_get_volume,
+  .set_volume = alsa_set_volume,
 };
 
 #endif
 };
 
 #endif
index b864aa05834bed6c487fcf9d74a8d1a1a6b3618d..1006fd91a64f6bba789e0af57a654021dc866022 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "common.h"
 #include "uaudio.h"
 
 #include "common.h"
 #include "uaudio.h"
+#include "log.h"
 
 /** @brief List of known APIs
  *
 
 /** @brief List of known APIs
  *
@@ -30,7 +31,7 @@
  * The first one will be used as a default, so putting ALSA before OSS
  * constitutes a policy decision.
  */
  * The first one will be used as a default, so putting ALSA before OSS
  * constitutes a policy decision.
  */
-const struct uaudio *const  uaudio_apis[] = {
+const struct uaudio *const uaudio_apis[] = {
 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
   &uaudio_coreaudio,
 #endif  
 #if HAVE_COREAUDIO_AUDIOHARDWARE_H
   &uaudio_coreaudio,
 #endif  
@@ -45,6 +46,18 @@ const struct uaudio *const  uaudio_apis[] = {
   NULL,
 };
 
   NULL,
 };
 
+/** @brief Look up an audio API by name */
+const struct uaudio *uaudio_find(const char *name) {
+  int n;
+
+  for(n = 0; uaudio_apis[n]; ++n)
+    if(!strcmp(uaudio_apis[n]->name, name))
+      return uaudio_apis[n];
+  if(!strcmp(name, "network"))
+    return &uaudio_rtp;
+  fatal(0, "cannot find audio API '%s'", name);
+}
+
 /*
 Local Variables:
 c-basic-offset:2
 /*
 Local Variables:
 c-basic-offset:2
index b9a1abb74a54075bb1d0ecbbddb0c497bd69d49a..012185aa794a7fc458fbd00e0e457aaf3049f016 100644 (file)
@@ -69,7 +69,7 @@ static void command_open(void) {
   int pfd[2];
   const char *command;
 
   int pfd[2];
   const char *command;
 
-  if(!(command = uaudio_get("command")))
+  if(!(command = uaudio_get("command", NULL)))
     fatal(0, "'command' not set");
   xpipe(pfd);
   command_pid = xfork();
     fatal(0, "'command' not set");
   xpipe(pfd);
   command_pid = xfork();
index 8cde8c3d1b8751cd49a14427b864edf639a25355..81a5711b924bdb99f1e500834eae33a31cd83288 100644 (file)
@@ -127,7 +127,7 @@ static void coreaudio_start(uaudio_callback *callback,
                    uaudio_bits);
   coreaudio_callback = callback;
   coreaudio_userdata = userdata;
                    uaudio_bits);
   coreaudio_callback = callback;
   coreaudio_userdata = userdata;
-  device = uaudio_get("device");
+  device = uaudio_get("device", "default");
   coreaudio_adid = coreaudio_getdevice(device);
   /* Get the device properties */
   propertySize = sizeof asbd;
   coreaudio_adid = coreaudio_getdevice(device);
   /* Get the device properties */
   propertySize = sizeof asbd;
index 7bdfb356a1ff9b44669e14c01d5b4d375e173657..d4c3711561c1eb0e678080dd89ad8fefa6d2a6c1 100644 (file)
 # endif
 #endif
 
 # endif
 #endif
 
+/* documentation does not match implementation! */
+#ifndef SOUND_MIXER_READ
+# define SOUND_MIXER_READ(x) MIXER_READ(x)
+#endif
+#ifndef SOUND_MIXER_WRITE
+# define SOUND_MIXER_WRITE(x) MIXER_WRITE(x)
+#endif
+
 static int oss_fd = -1;
 static int oss_fd = -1;
+static int oss_mixer_fd = -1;
+static int oss_mixer_channel;
 
 static const char *const oss_options[] = {
   "device",
 
 static const char *const oss_options[] = {
   "device",
+  "mixer-device",
+  "mixer-channel",
   NULL
 };
 
   NULL
 };
 
@@ -59,7 +71,7 @@ static size_t oss_play(void *buffer, size_t samples) {
 
 /** @brief Open the OSS sound device */
 static void oss_open(void) {
 
 /** @brief Open the OSS sound device */
 static void oss_open(void) {
-  const char *device = uaudio_get("device");
+  const char *device = uaudio_get("device", NULL);
 
 #if EMPEG_HOST
   if(!device || !*device || !strcmp(device, "default"))
 
 #if EMPEG_HOST
   if(!device || !*device || !strcmp(device, "default"))
@@ -129,13 +141,71 @@ static void oss_stop(void) {
   uaudio_thread_stop();
 }
 
   uaudio_thread_stop();
 }
 
+/** @brief Channel names */
+static const char *oss_channels[] = SOUND_DEVICE_NAMES;
+
+static int oss_mixer_find_channel(const char *channel) {
+  if(!channel[strspn(c, "0123456789")])
+    return atoi(channel);
+  else {
+    for(int n = 0; n < sizeof oss_channels / sizeof *oss_channels; ++n)
+      if(!strcmp(oss_channels[n], channels))
+       return n;
+    return -1;
+  }
+}  
+
+static void oss_open_mixer(void) {
+  const char *mixer = uaudio_get("mixer-device", "/dev/mixer");
+  /* TODO infer mixer-device from device */
+  if((oss_mixer_fd = open(mixer, O_RDWR, 0)) < 0)
+    fatal(errno, "error opening %s", mixer);
+  const char *channel = uaudio_get("mixer-channel", "pcm");
+  oss_mixer_channel = oss_mixer_find_channel(channel);
+  if(oss_mixer_channel < 0)
+    fatal(0, "no such channel as '%s'", channel);
+}
+
+static void oss_close_mixer(void) {
+  close(oss_mixer_fd);
+  oss_mixer_fd = -1;
+}
+
+static void oss_get_volume(int *left, int *right) {
+  int r;
+
+  *left = *right = 0;
+  if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(ch), &r) < 0)
+    error(errno, "error getting volume");
+  else {
+    *left = r & 0xff;
+    *right = (r >> 8) & 0xff;
+  }
+}
+
+static void oss_set_volume(int *left, int *right) {
+  int r =  (*left & 0xff) + (*right & 0xff) * 256;
+  if(ioctl(fd, SOUND_MIXER_WRITE(ch), &r) == -1)
+    error(errno, "error setting volume");
+  else if(ioctl(oss_mixer_fd, SOUND_MIXER_READ(ch), &r) < 0)
+    error(errno, "error getting volume");
+  else {
+    *left = r & 0xff;
+    *right = (r >> 8) & 0xff;
+  }
+}
+
 const struct uaudio uaudio_oss = {
   .name = "oss",
   .options = oss_options,
   .start = oss_start,
   .stop = oss_stop,
   .activate = oss_activate,
 const struct uaudio uaudio_oss = {
   .name = "oss",
   .options = oss_options,
   .start = oss_start,
   .stop = oss_stop,
   .activate = oss_activate,
-  .deactivate = oss_deactivate
+  .deactivate = oss_deactivate,
+  .open_mixer = oss_open_mixer,
+  .close_mixer = oss_close_mixer,
+  .get_volume = oss_get_volume,
+  .set_volume = oss_set_volume,
 };
 
 #endif
 };
 
 #endif
index ea1d1ca76d15faa935278d02c43e83b1bd4cf9c1..00609404372ab9e8778d65bc04b3acb7a256fc50 100644 (file)
@@ -143,17 +143,16 @@ static void rtp_open(void) {
   socklen_t len;
   char *sockname, *ssockname;
   struct stringlist dst, src;
   socklen_t len;
   char *sockname, *ssockname;
   struct stringlist dst, src;
-  const char *delay;
   
   /* Get configuration */
   dst.n = 2;
   dst.s = xcalloc(2, sizeof *dst.s);
   
   /* Get configuration */
   dst.n = 2;
   dst.s = xcalloc(2, sizeof *dst.s);
-  dst.s[0] = uaudio_get("rtp-destination");
-  dst.s[1] = uaudio_get("rtp-destination-port");
+  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.n = 2;
   src.s = xcalloc(2, sizeof *dst.s);
-  src.s[0] = uaudio_get("rtp-source");
-  src.s[1] = uaudio_get("rtp-source-port");
+  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])
   if(!dst.s[0])
     fatal(0, "'rtp-destination' not set");
   if(!dst.s[1])
@@ -164,10 +163,8 @@ static void rtp_open(void) {
     src.n = 2;
   } else
     src.n = 0;
     src.n = 2;
   } else
     src.n = 0;
-  if((delay = uaudio_get("rtp-delay-threshold")))
-    rtp_delay_threshold = atoi(delay);
-  else
-    rtp_delay_threshold = 1000;         /* microseconds */
+  rtp_delay_threshold = atoi(uaudio_get("rtp-delay-threshold", "1000"));
+  /* ...microseconds */
 
   /* Resolve addresses */
   res = get_address(&dst, &pref, &sockname);
 
   /* Resolve addresses */
   res = get_address(&dst, &pref, &sockname);
@@ -184,10 +181,8 @@ static void rtp_open(void) {
     fatal(errno, "error creating broadcast socket");
   if(multicast(res->ai_addr)) {
     /* Enable multicast options */
     fatal(errno, "error creating broadcast socket");
   if(multicast(res->ai_addr)) {
     /* Enable multicast options */
-    const char *ttls = uaudio_get("multicast-ttl");
-    const int ttl = ttls ? atoi(ttls) : 1;
-    const char *loops = uaudio_get("multicast-loop");
-    const int loop = loops ? !strcmp(loops, "yes") : 1;
+    const int ttl = atoi(uaudio_get("multicast-ttl", "1"));
+    const int loop = !strcmp(uaudio_get("multicast-loop", "yes"), "yes");
     switch(res->ai_family) {
     case PF_INET: {
       if(setsockopt(rtp_fd, IPPROTO_IP, IP_MULTICAST_TTL,
     switch(res->ai_family) {
     case PF_INET: {
       if(setsockopt(rtp_fd, IPPROTO_IP, IP_MULTICAST_TTL,
index 3625901eeccc44e1ef29c3c199ba133c9ae96b6a..2f6b67cc67e1c9095d6c43c553df78a297d575f4 100644 (file)
@@ -56,11 +56,11 @@ void uaudio_set(const char *name, const char *value) {
   hash_add(uaudio_options, name, &value, HASH_INSERT_OR_REPLACE);
 }
 
   hash_add(uaudio_options, name, &value, HASH_INSERT_OR_REPLACE);
 }
 
-/** @brief Set a uaudio option */
-char *uaudio_get(const char *name) {
+/** @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)
   const char *value = (uaudio_options ?
                        *(char **)hash_find(uaudio_options, name)
-                       : NULL);
+                       : default_value);
   return value ? xstrdup(value) : NULL;
 }
 
   return value ? xstrdup(value) : NULL;
 }
 
index ba62fedcc983c91f8ea746e8f4f69a0ce75f06d8..20bfbbcfca77b59ff688dae3cb3d6610348fda41 100644 (file)
@@ -90,11 +90,36 @@ struct uaudio {
    */
   void (*deactivate)(void);
 
    */
   void (*deactivate)(void);
 
+  /** @brief Open mixer device */
+  void (*open_mixer)(void);
+
+  /** @brief Closer mixer device */
+  void (*close_mixer)(void);
+
+  /** @brief Get volume
+   * @param left Where to put the left-channel value
+   * @param right Where to put the right-channel value
+   *
+   * 0 is silent and 100 is maximum volume.
+   */
+  void (*get_volume)(int *left, int *right);
+
+  /** @brief Set volume
+   * @param left Pointer to left-channel value (updated)
+   * @param right Pointer to right-channel value (updated)
+   *
+   * The values are updated with those actually set by the underlying system
+   * call.
+   *
+   * 0 is silent and 100 is maximum volume.
+   */
+  void (*set_volume)(int *left, int *right);
+  
 };
 
 void uaudio_set_format(int rate, int channels, int samplesize, int signed_);
 void uaudio_set(const char *name, const char *value);
 };
 
 void uaudio_set_format(int rate, int channels, int samplesize, int signed_);
 void uaudio_set(const char *name, const char *value);
-char *uaudio_get(const char *name);
+char *uaudio_get(const char *name, const char *default_value);
 void uaudio_thread_start(uaudio_callback *callback,
                         void *userdata,
                         uaudio_playcallback *playcallback,
 void uaudio_thread_start(uaudio_callback *callback,
                         void *userdata,
                         uaudio_playcallback *playcallback,
@@ -106,6 +131,7 @@ void uaudio_thread_deactivate(void);
 void uaudio_schedule_synchronize(void);
 void uaudio_schedule_update(size_t written_samples);
 void uaudio_schedule_init(void);
 void uaudio_schedule_synchronize(void);
 void uaudio_schedule_update(size_t written_samples);
 void uaudio_schedule_init(void);
+const struct uaudio *uaudio_find(const char *name);
 
 extern uint64_t uaudio_schedule_timestamp;
 extern int uaudio_schedule_reactivated;
 
 extern uint64_t uaudio_schedule_timestamp;
 extern int uaudio_schedule_reactivated;
index a35bd803dadad2a65248a21d8f18b4f6287f005f..7dfe99ded52fb423289971aaf4d386c67b1c9880 100644 (file)
@@ -1,6 +1,6 @@
 #
 # This file is part of DisOrder.
 #
 # This file is part of DisOrder.
-# Copyright (C) 2004-2008 Richard Kettlewell
+# Copyright (C) 2004-2009 Richard Kettlewell
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -28,7 +28,7 @@ disorderd_SOURCES=disorderd.c api.c api-server.c daemonize.c play.c   \
        schedule.c exports.c ../lib/memgc.c disorder-server.h
 disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
        schedule.c exports.c ../lib/memgc.c disorder-server.h
 disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
-       $(LIBASOUND)
+       $(LIBASOUND) $(COREAUDIO)
 disorderd_LDFLAGS=-export-dynamic
 disorderd_DEPENDENCIES=../lib/libdisorder.a
 
 disorderd_LDFLAGS=-export-dynamic
 disorderd_DEPENDENCIES=../lib/libdisorder.a
 
@@ -37,12 +37,7 @@ disorder_deadlock_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
 disorder_deadlock_DEPENDENCIES=../lib/libdisorder.a
 
        $(LIBDB) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT)
 disorder_deadlock_DEPENDENCIES=../lib/libdisorder.a
 
-disorder_speaker_SOURCES=speaker.c speaker.h \
-                        speaker-command.c \
-                        speaker-network.c \
-                        speaker-coreaudio.c \
-                        speaker-oss.c \
-                        speaker-alsa.c
+disorder_speaker_SOURCES=speaker.c
 disorder_speaker_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO)
 disorder_speaker_DEPENDENCIES=../lib/libdisorder.a
 disorder_speaker_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
        $(LIBASOUND) $(LIBPCRE) $(LIBICONV) $(LIBGCRYPT) $(COREAUDIO)
 disorder_speaker_DEPENDENCIES=../lib/libdisorder.a
index 4eb58e45845e9f34689104166d8272888cc7fd87..6d04c2646fc3f66a841851e4de52b6a4aa66c6e3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder
 /*
  * This file is part of DisOrder
- * Copyright (C) 2008 Richard Kettlewell
+ * Copyright (C) 2008, 2009 Richard Kettlewell
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -67,7 +67,6 @@
 #include "logfd.h"
 #include "mem.h"
 #include "mime.h"
 #include "logfd.h"
 #include "mem.h"
 #include "mime.h"
-#include "mixer.h"
 #include "printf.h"
 #include "queue.h"
 #include "random.h"
 #include "printf.h"
 #include "queue.h"
 #include "random.h"
 #include "trackdb-int.h"
 #include "trackdb.h"
 #include "trackname.h"
 #include "trackdb-int.h"
 #include "trackdb.h"
 #include "trackname.h"
+#include "uaudio.h"
 #include "unicode.h"
 #include "user.h"
 #include "vector.h"
 #include "version.h"
 #include "wstat.h"
 
 #include "unicode.h"
 #include "user.h"
 #include "vector.h"
 #include "version.h"
 #include "wstat.h"
 
+extern const struct uaudio *api;
+
 void daemonize(const char *tag, int fac, const char *pidfile);
 /* Go into background.  Send stdout/stderr to syslog.
  * If @pri@ is non-null, it should be "facility.level"
 void daemonize(const char *tag, int fac, const char *pidfile);
 /* Go into background.  Send stdout/stderr to syslog.
  * If @pri@ is non-null, it should be "facility.level"
index 2342c3c07e63fd67b620a667a3c224a506933eb4..72a8e3d918cbd97333c8c03fab0b83fcf856679d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -132,7 +132,8 @@ static void periodic_volume_check(ev_source attribute((unused)) *ev_) {
   int l, r;
   char lb[32], rb[32];
 
   int l, r;
   char lb[32], rb[32];
 
-  if(!mixer_control(-1/*as configured*/, &l, &r, 0)) {
+  if(api && api->get_volume) {
+    api->get_volume(&l, &r);
     if(l != volume_left || r != volume_right) {
       volume_left = l;
       volume_right = r;
     if(l != volume_left || r != volume_right) {
       volume_left = l;
       volume_right = r;
@@ -225,6 +226,7 @@ int main(int argc, char **argv) {
   ev = ev_new();
   if(ev_child_setup(ev)) fatal(0, "ev_child_setup failed");
   /* read config */
   ev = ev_new();
   if(ev_child_setup(ev)) fatal(0, "ev_child_setup failed");
   /* read config */
+  config_uaudio_apis = uaudio_apis;
   if(config_read(1))
     fatal(0, "cannot read configuration");
   /* make sure the home directory exists and has suitable permissions */
   if(config_read(1))
     fatal(0, "cannot read configuration");
   /* make sure the home directory exists and has suitable permissions */
index 2874357a4e8959f7ceef798a093feb340d1d577c..345c2ca48d007fd6cf1a5d7f1b7627d15cf0c08d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004-2008 Richard Kettlewell
+ * Copyright (C) 2004-2009 Richard Kettlewell
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -825,17 +825,18 @@ static int c_volume(struct conn *c,
     sink_writes(ev_writer_sink(c->w), "510 Prohibited\n");
     return 1;
   }
     sink_writes(ev_writer_sink(c->w), "510 Prohibited\n");
     return 1;
   }
-  if(mixer_control(-1/*as configured*/, &l, &r, set))
+  if(!api || !api->set_volume) {
     sink_writes(ev_writer_sink(c->w), "550 error accessing mixer\n");
     sink_writes(ev_writer_sink(c->w), "550 error accessing mixer\n");
-  else {
-    sink_printf(ev_writer_sink(c->w), "252 %d %d\n", l, r);
-    if(l != volume_left || r != volume_right) {
-      volume_left = l;
-      volume_right = r;
-      snprintf(lb, sizeof lb, "%d", l);
-      snprintf(rb, sizeof rb, "%d", r);
-      eventlog("volume", lb, rb, (char *)0);
-    }
+    return 1;
+  }
+  api->set_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;
+    volume_right = r;
+    snprintf(lb, sizeof lb, "%d", l);
+    snprintf(rb, sizeof rb, "%d", r);
+    eventlog("volume", lb, rb, (char *)0);
   }
   return 1;
 }
   }
   return 1;
 }
@@ -1098,7 +1099,7 @@ static int c_new(struct conn *c,
 static int c_rtp_address(struct conn *c,
                         char attribute((unused)) **vec,
                         int attribute((unused)) nvec) {
 static int c_rtp_address(struct conn *c,
                         char attribute((unused)) **vec,
                         int attribute((unused)) nvec) {
-  if(config->api == BACKEND_NETWORK) {
+  if(api == &uaudio_rtp) {
     sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
                quoteutf8(config->broadcast.s[0]),
                quoteutf8(config->broadcast.s[1]));
     sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
                quoteutf8(config->broadcast.s[0]),
                quoteutf8(config->broadcast.s[1]));
diff --git a/server/speaker-alsa.c b/server/speaker-alsa.c
deleted file mode 100644 (file)
index d10ec19..0000000
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005, 2006, 2007 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-alsa.c
- * @brief Support for @ref BACKEND_ALSA */
-
-#include "common.h"
-
-#if HAVE_ALSA_ASOUNDLIB_H
-
-#include <unistd.h>
-#include <poll.h>
-#include <alsa/asoundlib.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief The current PCM handle */
-static snd_pcm_t *pcm;
-
-/** @brief Last seen buffer size */
-static snd_pcm_uframes_t last_pcm_bufsize;
-
-/** @brief ALSA backend initialization */
-static void alsa_init(void) {
-  info("selected ALSA backend");
-}
-
-/** @brief Log ALSA parameters */
-static void log_params(snd_pcm_hw_params_t *hwparams,
-                       snd_pcm_sw_params_t *swparams) {
-  snd_pcm_uframes_t f;
-
-  return;                               /* too verbose */
-  if(hwparams) {
-    /* TODO */
-  }
-  if(swparams) {
-    snd_pcm_sw_params_get_silence_size(swparams, &f);
-    info("sw silence_size=%lu", (unsigned long)f);
-    snd_pcm_sw_params_get_silence_threshold(swparams, &f);
-    info("sw silence_threshold=%lu", (unsigned long)f);
-#if HAVE_SND_PCM_SW_PARAMS_GET_SLEEP_MIN
-    {
-      unsigned u;
-
-      snd_pcm_sw_params_get_sleep_min(swparams, &u);
-      info("sw sleep_min=%lu", (unsigned long)u);
-    }
-#endif
-    snd_pcm_sw_params_get_start_threshold(swparams, &f);
-    info("sw start_threshold=%lu", (unsigned long)f);
-    snd_pcm_sw_params_get_stop_threshold(swparams, &f);
-    info("sw stop_threshold=%lu", (unsigned long)f);
-#if HAVE_SND_PCM_SW_PARAMS_GET_XFER_ALIGN
-    snd_pcm_sw_params_get_xfer_align(swparams, &f);
-    info("sw xfer_align=%lu", (unsigned long)f);
-#endif
-  }
-}
-
-/** @brief ALSA deactivation */
-static void alsa_deactivate(void) {
-  if(pcm) {
-    int err;
-    
-    if((err = snd_pcm_nonblock(pcm, 0)) < 0)
-      fatal(0, "error calling snd_pcm_nonblock: %d", err);
-    D(("draining pcm"));
-    snd_pcm_drain(pcm);
-    D(("closing pcm"));
-    snd_pcm_close(pcm);
-    pcm = 0;
-    device_state = device_closed;
-    D(("released audio device"));
-  }
-}
-
-/** @brief ALSA backend activation */
-static void alsa_activate(void) {
-  if(!pcm) {
-    snd_pcm_hw_params_t *hwparams;
-    snd_pcm_sw_params_t *swparams;
-    snd_pcm_uframes_t pcm_bufsize;
-    int err;
-    int sample_format = 0;
-    unsigned rate;
-
-    D(("snd_pcm_open"));
-    if((err = snd_pcm_open(&pcm,
-                           config->device,
-                           SND_PCM_STREAM_PLAYBACK,
-                           SND_PCM_NONBLOCK))) {
-      error(0, "error from snd_pcm_open: %d", err);
-      goto error;
-    }
-    snd_pcm_hw_params_alloca(&hwparams);
-    D(("set up hw params"));
-    if((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0)
-      fatal(0, "error from snd_pcm_hw_params_any: %d", err);
-    if((err = snd_pcm_hw_params_set_access(pcm, hwparams,
-                                           SND_PCM_ACCESS_RW_INTERLEAVED)) < 0)
-      fatal(0, "error from snd_pcm_hw_params_set_access: %d", err);
-    switch(config->sample_format.bits) {
-    case 8:
-      sample_format = SND_PCM_FORMAT_S8;
-      break;
-    case 16:
-      switch(config->sample_format.endian) {
-      case ENDIAN_LITTLE: sample_format = SND_PCM_FORMAT_S16_LE; break;
-      case ENDIAN_BIG: sample_format = SND_PCM_FORMAT_S16_BE; break;
-      default:
-        error(0, "unrecognized byte format %d", config->sample_format.endian);
-        goto fatal;
-      }
-      break;
-    default:
-      error(0, "unsupported sample size %d", config->sample_format.bits);
-      goto fatal;
-    }
-    if((err = snd_pcm_hw_params_set_format(pcm, hwparams,
-                                           sample_format)) < 0) {
-      error(0, "error from snd_pcm_hw_params_set_format (%d): %d",
-            sample_format, err);
-      goto fatal;
-    }
-    rate = config->sample_format.rate;
-    if((err = snd_pcm_hw_params_set_rate_near(pcm, hwparams, &rate, 0)) < 0) {
-      error(0, "error from snd_pcm_hw_params_set_rate (%d): %d",
-            config->sample_format.rate, err);
-      goto fatal;
-    }
-    if(rate != (unsigned)config->sample_format.rate)
-      info("want rate %d, got %u", config->sample_format.rate, rate);
-    if((err = snd_pcm_hw_params_set_channels
-        (pcm, hwparams, config->sample_format.channels)) < 0) {
-      error(0, "error from snd_pcm_hw_params_set_channels (%d): %d",
-            config->sample_format.channels, err);
-      goto fatal;
-    }
-    pcm_bufsize = 3 * FRAMES;
-    if((err = snd_pcm_hw_params_set_buffer_size_near(pcm, hwparams,
-                                                     &pcm_bufsize)) < 0)
-      fatal(0, "error from snd_pcm_hw_params_set_buffer_size (%d): %d",
-            3 * FRAMES, err);
-    if(pcm_bufsize != 3 * FRAMES && pcm_bufsize != last_pcm_bufsize)
-      info("asked for PCM buffer of %d frames, got %d",
-           3 * FRAMES, (int)pcm_bufsize);
-    last_pcm_bufsize = pcm_bufsize;
-    if((err = snd_pcm_hw_params(pcm, hwparams)) < 0)
-      fatal(0, "error calling snd_pcm_hw_params: %d", err);
-    D(("set up sw params"));
-    snd_pcm_sw_params_alloca(&swparams);
-    if((err = snd_pcm_sw_params_current(pcm, swparams)) < 0)
-      fatal(0, "error calling snd_pcm_sw_params_current: %d", err);
-    if((err = snd_pcm_sw_params_set_avail_min(pcm, swparams, FRAMES)) < 0)
-      fatal(0, "error calling snd_pcm_sw_params_set_avail_min %d: %d",
-            FRAMES, err);
-    if((err = snd_pcm_sw_params(pcm, swparams)) < 0)
-      fatal(0, "error calling snd_pcm_sw_params: %d", err);
-    D(("acquired audio device"));
-    log_params(hwparams, swparams);
-    device_state = device_open;
-  }
-  return;
-fatal:
-  abandon();
-error:
-  /* We assume the error is temporary and that we'll retry in a bit. */
-  if(pcm) {
-    snd_pcm_close(pcm);
-    pcm = 0;
-    device_state = device_error;
-  }
-  return;
-}
-
-/** @brief Play via ALSA */
-static size_t alsa_play(size_t frames) {
-  snd_pcm_sframes_t pcm_written_frames;
-  int err;
-  
-  pcm_written_frames = snd_pcm_writei(pcm,
-                                      playing->buffer + playing->start,
-                                      frames);
-  D(("actually play %zu frames, wrote %d",
-     frames, (int)pcm_written_frames));
-  if(pcm_written_frames < 0) {
-    switch(pcm_written_frames) {
-    case -EPIPE:                        /* underrun */
-      error(0, "snd_pcm_writei reports underrun");
-      if((err = snd_pcm_prepare(pcm)) < 0)
-        fatal(0, "error calling snd_pcm_prepare: %d", err);
-      return 0;
-    case -EAGAIN:
-      return 0;
-    default:
-      fatal(0, "error calling snd_pcm_writei: %d",
-            (int)pcm_written_frames);
-    }
-  } else
-    return pcm_written_frames;
-}
-
-static int alsa_slots, alsa_nslots = -1;
-
-/** @brief Fill in poll fd array for ALSA */
-static void alsa_beforepoll(int attribute((unused)) *timeoutp) {
-  /* We send sample data to ALSA as fast as it can accept it, relying on
-   * the fact that it has a relatively small buffer to minimize pause
-   * latency. */
-  int retry = 3, err;
-  
-  alsa_slots = fdno;
-  do {
-    retry = 0;
-    alsa_nslots = snd_pcm_poll_descriptors(pcm, &fds[fdno], NFDS - fdno);
-    if((alsa_nslots <= 0
-        || !(fds[alsa_slots].events & POLLOUT))
-       && snd_pcm_state(pcm) == SND_PCM_STATE_XRUN) {
-      error(0, "underrun detected after call to snd_pcm_poll_descriptors()");
-      if((err = snd_pcm_prepare(pcm)))
-        fatal(0, "error calling snd_pcm_prepare: %d", err);
-    } else
-      break;
-  } while(retry-- > 0);
-  if(alsa_nslots >= 0)
-    fdno += alsa_nslots;
-}
-
-/** @brief Process poll() results for ALSA */
-static int alsa_ready(void) {
-  int err;
-
-  unsigned short alsa_revents;
-  
-  if((err = snd_pcm_poll_descriptors_revents(pcm,
-                                             &fds[alsa_slots],
-                                             alsa_nslots,
-                                             &alsa_revents)) < 0)
-    fatal(0, "error calling snd_pcm_poll_descriptors_revents: %d", err);
-  if(alsa_revents & (POLLOUT | POLLERR))
-    return 1;
-  else
-    return 0;
-}
-
-const struct speaker_backend alsa_backend = {
-  BACKEND_ALSA,
-  0,
-  alsa_init,
-  alsa_activate,
-  alsa_play,
-  alsa_deactivate,
-  alsa_beforepoll,
-  alsa_ready
-};
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
diff --git a/server/speaker-command.c b/server/speaker-command.c
deleted file mode 100644 (file)
index e85d198..0000000
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005, 2006, 2007 Richard Kettlewell
- * Portions (C) 2007 Mark Wooding
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-command.c
- * @brief Support for @ref BACKEND_COMMAND */
-
-#include "common.h"
-
-#include <unistd.h>
-#include <poll.h>
-#include <errno.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief Pipe to subprocess
- *
- * This is the file descriptor to write to for @ref BACKEND_COMMAND.
- */
-static int cmdfd = -1;
-
-/** @brief poll array slot for @ref cmdfd
- *
- * Set by command_beforepoll().
- */
-static int cmdfd_slot;
-
-/** @brief Start the subprocess for @ref BACKEND_COMMAND */
-static void fork_cmd(void) {
-  pid_t cmdpid;
-  int pfd[2];
-  if(cmdfd != -1) close(cmdfd);
-  xpipe(pfd);
-  cmdpid = xfork();
-  if(!cmdpid) {
-    exitfn = _exit;
-    signal(SIGPIPE, SIG_DFL);
-    xdup2(pfd[0], 0);
-    close(pfd[0]);
-    close(pfd[1]);
-    execl("/bin/sh", "sh", "-c", config->speaker_command, (char *)0);
-    fatal(errno, "error execing /bin/sh");
-  }
-  close(pfd[0]);
-  cmdfd = pfd[1];
-  D(("forked cmd %d, fd = %d", cmdpid, cmdfd));
-}
-
-/** @brief Command backend initialization */
-static void command_init(void) {
-  info("selected command backend");
-  fork_cmd();
-}
-
-/** @brief Play to a subprocess */
-static size_t command_play(size_t frames) {
-  size_t bytes = frames * bpf;
-  int written_bytes;
-
-  written_bytes = write(cmdfd, playing->buffer + playing->start, bytes);
-  D(("actually play %zu bytes, wrote %d",
-     bytes, written_bytes));
-  if(written_bytes < 0) {
-    switch(errno) {
-    case EPIPE:
-      error(0, "hmm, command died; trying another");
-      fork_cmd();
-      return 0;
-    case EAGAIN:
-      return 0;
-    default:
-      fatal(errno, "error writing to subprocess");
-    }
-  } else
-    return written_bytes / bpf;
-}
-
-/** @brief Update poll array for writing to subprocess */
-static void command_beforepoll(int attribute((unused)) *timeoutp) {
-  /* We send sample data to the subprocess as fast as it can accept it.
-   * This isn't ideal as pause latency can be very high as a result. */
-  if(cmdfd >= 0)
-    cmdfd_slot = addfd(cmdfd, POLLOUT);
-}
-
-/** @brief Process poll() results for subprocess play */
-static int command_ready(void) {
-  if(fds[cmdfd_slot].revents & (POLLOUT | POLLERR))
-    return 1;
-  else
-    return 0;
-}
-
-const struct speaker_backend command_backend = {
-  BACKEND_COMMAND,
-  0,
-  command_init,
-  0,                                    /* activate */
-  command_play,
-  0,                                    /* deactivate */
-  command_beforepoll,
-  command_ready
-};
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
diff --git a/server/speaker-coreaudio.c b/server/speaker-coreaudio.c
deleted file mode 100644 (file)
index ac4e7e7..0000000
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-coreaudio.c
- * @brief Support for @ref BACKEND_COREAUDIO
- *
- * Core Audio likes to make callbacks from a separate player thread
- * which then fill in the required number of bytes of audio.  We fit
- * this into the existing architecture by means of a pipe between the
- * threads.
- *
- * We currently only support 16-bit 44100Hz stereo (and enforce this
- * in @ref lib/configuration.c.)  There are some nasty bodges in this
- * code which depend on this and on further assumptions still...
- *
- * @todo support @ref config::device
- */
-
-#include "common.h"
-
-#if HAVE_COREAUDIO_AUDIOHARDWARE_H
-
-#include <poll.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-#include "coreaudio.h"
-
-/** @brief Core Audio Device ID */
-static AudioDeviceID adid;
-
-/** @brief Pipe between main and player threads
- *
- * We'll write samples to pfd[1] and read them from pfd[0].
- */
-static int pfd[2];
-
-/** @brief Slot number in poll array */
-static int pfd_slot;
-
-/** @brief Callback from Core Audio */
-static OSStatus adioproc
-    (AudioDeviceID attribute((unused)) inDevice,
-     const AudioTimeStamp attribute((unused)) *inNow,
-     const AudioBufferList attribute((unused)) *inInputData,
-     const AudioTimeStamp attribute((unused)) *inInputTime,
-     AudioBufferList *outOutputData,
-     const AudioTimeStamp attribute((unused)) *inOutputTime,
-     void attribute((unused)) *inClientData) {
-  UInt32 nbuffers = outOutputData->mNumberBuffers;
-  AudioBuffer *ab = outOutputData->mBuffers;
-
-  while(nbuffers > 0) {
-    float *samplesOut = ab->mData;
-    size_t samplesOutLeft = ab->mDataByteSize / sizeof (float);
-    int16_t input[1024], *ptr;
-    size_t bytes;
-    ssize_t bytes_read;
-    size_t samples;
-
-    while(samplesOutLeft > 0) {
-      /* Read some more data */
-      bytes = samplesOutLeft * sizeof (int16_t);
-      if(bytes > sizeof input)
-       bytes = sizeof input;
-      
-      bytes_read = read(pfd[0], input, bytes);
-      if(bytes_read < 0)
-       switch(errno) {
-       case EINTR:
-         continue;             /* just try again */
-       case EAGAIN:
-         return 0;             /* underrun - just play 0s */
-       default:
-         fatal(errno, "read error in core audio thread");
-       }
-      assert(bytes_read % 4 == 0); /* TODO horrible bodge! */
-      samples = bytes_read / sizeof (int16_t);
-      assert(samples <= samplesOutLeft);
-      ptr = input;
-      samplesOutLeft -= samples;
-      while(samples-- > 0)
-       *samplesOut++ = *ptr++ * (0.5 / 32767);
-    }
-    ++ab;
-    --nbuffers;
-  }
-  return 0;
-}
-
-/** @brief Core Audio backend initialization */
-static void coreaudio_init(void) {
-  OSStatus status;
-  UInt32 propertySize;
-  AudioStreamBasicDescription asbd;
-
-  adid = coreaudio_getdevice(config->device);
-  propertySize = sizeof asbd;
-  status = AudioDeviceGetProperty(adid, 0, false,
-                                 kAudioDevicePropertyStreamFormat,
-                                 &propertySize, &asbd);
-  if(status)
-    fatal(0, "AudioHardwareGetProperty: %d", (int)status);
-  D(("mSampleRate       %f", asbd.mSampleRate));
-  D(("mFormatID         %08lx", asbd.mFormatID));
-  D(("mFormatFlags      %08lx", asbd.mFormatFlags));
-  D(("mBytesPerPacket   %08lx", asbd.mBytesPerPacket));
-  D(("mFramesPerPacket  %08lx", asbd.mFramesPerPacket));
-  D(("mBytesPerFrame    %08lx", asbd.mBytesPerFrame));
-  D(("mChannelsPerFrame %08lx", asbd.mChannelsPerFrame));
-  D(("mBitsPerChannel   %08lx", asbd.mBitsPerChannel));
-  D(("mReserved         %08lx", asbd.mReserved));
-  if(asbd.mFormatID != kAudioFormatLinearPCM)
-    fatal(0, "audio device does not support kAudioFormatLinearPCM");
-  status = AudioDeviceAddIOProc(adid, adioproc, 0);
-  if(status)
-    fatal(0, "AudioDeviceAddIOProc: %d", (int)status);
-  if(socketpair(PF_UNIX, SOCK_STREAM, 0, pfd) < 0)
-    fatal(errno, "error calling socketpair");
-  nonblock(pfd[0]);
-  nonblock(pfd[1]);
-  info("selected Core Audio backend");
-}
-
-/** @brief Core Audio deactivation */
-static void coreaudio_deactivate(void) {
-  const OSStatus status = AudioDeviceStop(adid, adioproc);
-  if(status) {
-    error(0, "AudioDeviceStop: %d", (int)status);
-    device_state = device_error;
-  } else
-    device_state = device_closed;
-}
-
-/** @brief Core Audio backend activation */
-static void coreaudio_activate(void) {
-  const OSStatus status = AudioDeviceStart(adid, adioproc);
-
-  if(status) {
-    error(0, "AudioDeviceStart: %d", (int)status);
-    device_state = device_error;  
-  }
-  device_state = device_open;
-}
-
-/** @brief Play via Core Audio */
-static size_t coreaudio_play(size_t frames) {
-  static size_t leftover;
-
-  size_t bytes = frames * bpf + leftover;
-  ssize_t bytes_written;
-
-  if(leftover)
-    /* There is a partial frame left over from an earlier write.  Try
-     * and finish that off before doing anything else. */
-    bytes = leftover;
-  bytes_written = write(pfd[1], playing->buffer + playing->start, bytes);
-  if(bytes_written < 0)
-    switch(errno) {
-    case EINTR:                        /* interrupted */
-    case EAGAIN:               /* buffer full */
-      return 0;                        /* try later */
-    default:
-      fatal(errno, "error writing to core audio player thread");
-    }
-  if(leftover) {
-    /* We were dealing the leftover bytes of a partial frame */
-    leftover -= bytes_written;
-    return !leftover;
-  }
-  leftover = bytes_written % bpf;
-  return bytes_written / bpf;
-}
-
-/** @brief Fill in poll fd array for Core Audio */
-static void coreaudio_beforepoll(int attribute((unused)) *timeoutp) {
-  pfd_slot = addfd(pfd[1], POLLOUT);
-}
-
-/** @brief Process poll() results for Core Audio */
-static int coreaudio_ready(void) {
-  return !!(fds[pfd_slot].revents & (POLLOUT|POLLERR));
-}
-
-/** @brief Backend definition for Core Audio */
-const struct speaker_backend coreaudio_backend = {
-  BACKEND_COREAUDIO,
-  0,
-  coreaudio_init,
-  coreaudio_activate,
-  coreaudio_play,
-  coreaudio_deactivate,
-  coreaudio_beforepoll,
-  coreaudio_ready
-};
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
diff --git a/server/speaker-network.c b/server/speaker-network.c
deleted file mode 100644 (file)
index e8a7190..0000000
+++ /dev/null
@@ -1,353 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005-2008 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-network.c
- * @brief Support for @ref BACKEND_NETWORK */
-
-#include "common.h"
-
-#include <unistd.h>
-#include <poll.h>
-#include <netdb.h>
-#include <gcrypt.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-#include <net/if.h>
-#include <ifaddrs.h>
-#include <errno.h>
-#include <netinet/in.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "addr.h"
-#include "timeval.h"
-#include "rtp.h"
-#include "ifreq.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief Network socket
- *
- * This is the file descriptor to write to for @ref BACKEND_NETWORK.
- */
-static int bfd = -1;
-
-/** @brief RTP timestamp
- *
- * This counts the number of samples played (NB not the number of frames
- * played).
- *
- * The timestamp in the packet header is only 32 bits wide.  With 44100Hz
- * stereo, that only gives about half a day before wrapping, which is not
- * particularly convenient for certain debugging purposes.  Therefore the
- * timestamp is maintained as a 64-bit integer, giving around six million years
- * before wrapping, and truncated to 32 bits when transmitting.
- */
-static uint64_t rtp_time;
-
-/** @brief RTP base timestamp
- *
- * This is the real time correspoding to an @ref rtp_time of 0.  It is used
- * to recalculate the timestamp after idle periods.
- */
-static struct timeval rtp_time_0;
-
-/** @brief RTP packet sequence number */
-static uint16_t rtp_seq;
-
-/** @brief RTP SSRC */
-static uint32_t rtp_id;
-
-/** @brief Error counter */
-static int audio_errors;
-
-/** @brief Network backend initialization */
-static void network_init(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;
-
-  res = get_address(&config->broadcast, &pref, &sockname);
-  if(!res) exit(-1);
-  if(config->broadcast_from.n) {
-    sres = get_address(&config->broadcast_from, &prefbind, &ssockname);
-    if(!sres) exit(-1);
-  } else
-    sres = 0;
-  if((bfd = socket(res->ai_family,
-                   res->ai_socktype,
-                   res->ai_protocol)) < 0)
-    fatal(errno, "error creating broadcast socket");
-  if(multicast(res->ai_addr)) {
-    /* Multicasting */
-    switch(res->ai_family) {
-    case PF_INET: {
-      const int mttl = config->multicast_ttl;
-      if(setsockopt(bfd, IPPROTO_IP, IP_MULTICAST_TTL, &mttl, sizeof mttl) < 0)
-        fatal(errno, "error setting IP_MULTICAST_TTL on multicast socket");
-      if(setsockopt(bfd, IPPROTO_IP, IP_MULTICAST_LOOP,
-                    &config->multicast_loop, sizeof one) < 0)
-        fatal(errno, "error setting IP_MULTICAST_LOOP on multicast socket");
-      break;
-    }
-    case PF_INET6: {
-      const int mttl = config->multicast_ttl;
-      if(setsockopt(bfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS,
-                    &mttl, sizeof mttl) < 0)
-        fatal(errno, "error setting IPV6_MULTICAST_HOPS on multicast socket");
-      if(setsockopt(bfd, IPPROTO_IP, IPV6_MULTICAST_LOOP,
-                    &config->multicast_loop, sizeof (int)) < 0)
-        fatal(errno, "error setting IPV6_MULTICAST_LOOP on multicast socket");
-      break;
-    }
-    default:
-      fatal(0, "unsupported address family %d", res->ai_family);
-    }
-    info("multicasting on %s", sockname);
-  } else {
-    struct ifaddrs *ifs;
-
-    if(getifaddrs(&ifs) < 0)
-      fatal(errno, "error calling getifaddrs");
-    while(ifs) {
-      /* (At least on Darwin) IFF_BROADCAST might be set but ifa_broadaddr
-       * still a null pointer.  It turns out that there's a subsequent entry
-       * for he same interface which _does_ have ifa_broadaddr though... */
-      if((ifs->ifa_flags & IFF_BROADCAST)
-         && ifs->ifa_broadaddr
-         && sockaddr_equal(ifs->ifa_broadaddr, res->ai_addr))
-        break;
-      ifs = ifs->ifa_next;
-    }
-    if(ifs) {
-      if(setsockopt(bfd, 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);
-    } else
-      info("unicasting on %s", sockname);
-  }
-  len = sizeof sndbuf;
-  if(getsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
-                &sndbuf, &len) < 0)
-    fatal(errno, "error getting SO_SNDBUF");
-  if(target_sndbuf > sndbuf) {
-    if(setsockopt(bfd, SOL_SOCKET, SO_SNDBUF,
-                  &target_sndbuf, sizeof target_sndbuf) < 0)
-      error(errno, "error setting SO_SNDBUF to %d", target_sndbuf);
-    else
-      info("changed socket send buffer size from %d to %d",
-           sndbuf, target_sndbuf);
-  } else
-    info("default socket send buffer is %d",
-         sndbuf);
-  /* We might well want to set additional broadcast- or multicast-related
-   * options here */
-  if(sres && bind(bfd, sres->ai_addr, sres->ai_addrlen) < 0)
-    fatal(errno, "error binding broadcast socket to %s", ssockname);
-  if(connect(bfd, res->ai_addr, res->ai_addrlen) < 0)
-    fatal(errno, "error connecting broadcast socket to %s", sockname);
-  /* Select an SSRC */
-  gcry_randomize(&rtp_id, sizeof rtp_id, GCRY_STRONG_RANDOM);
-}
-
-/** @brief Play over the network */
-static size_t network_play(size_t frames) {
-  struct rtp_header header;
-  struct iovec vec[2];
-  size_t bytes = frames * bpf, written_frames;
-  int written_bytes;
-  /* We transmit using RTP (RFC3550) and attempt to conform to the internet
-   * AVT profile (RFC3551). */
-
-  /* If we're starting then initialize the base time */
-  if(!rtp_time)
-    xgettimeofday(&rtp_time_0, 0);
-  if(idled) {
-    /* There may have been a gap.  Fix up the RTP time accordingly. */
-    struct timeval now;
-    uint64_t delta;
-    uint64_t target_rtp_time;
-
-    /* Find the current time */
-    xgettimeofday(&now, 0);
-    /* Find the number of microseconds elapsed since rtp_time=0 */
-    delta = tvsub_us(now, rtp_time_0);
-    if(delta > UINT64_MAX / 88200)
-      fatal(0, "rtp_time=%"PRIu64" now=%ld.%06ld rtp_time_0=%ld.%06ld delta=%"PRIu64" (%"PRId64")",
-            rtp_time,
-            (long)now.tv_sec, (long)now.tv_usec,
-            (long)rtp_time_0.tv_sec, (long)rtp_time_0.tv_usec,
-            delta, delta);
-    target_rtp_time = (delta * config->sample_format.rate
-                       * config->sample_format.channels) / 1000000;
-    /* Overflows at ~6 years uptime with 44100Hz stereo */
-
-    /* rtp_time is the number of samples we've played.  NB that we play
-     * RTP_AHEAD_MS ahead of ourselves, so it may legitimately be ahead of
-     * the value we deduce from time comparison.
-     *
-     * Suppose we have 1s track started at t=0, and another track begins to
-     * play at t=2s.  Suppose 44100Hz stereo.  We send 1s of audio over the
-     * next (about) one second, giving rtp_time=88200.  rtp_time stops at this
-     * point.
-     *
-     * At t=2s we'll have calculated target_rtp_time=176400.  In this case we
-     * set rtp_time=176400 and the player can correctly conclude that it
-     * should leave 1s between the tracks.
-     *
-     * It's never right to reduce rtp_time, for that would imply packets with
-     * overlapping timestamp ranges, which does not make sense.
-     */
-    target_rtp_time &= ~(uint64_t)1;    /* stereo! */
-    if(target_rtp_time > rtp_time) {
-      /* More time has elapsed than we've transmitted samples.  That implies
-       * we've been 'sending' silence.  */
-      info("advancing rtp_time by %"PRIu64" samples",
-           target_rtp_time - rtp_time);
-      rtp_time = target_rtp_time;
-    } else if(target_rtp_time < rtp_time) {
-      info("would reverse rtp_time by %"PRIu64" samples",
-           rtp_time - target_rtp_time);
-    }
-  }
-  header.vpxcc = 2 << 6;              /* V=2, P=0, X=0, CC=0 */
-  header.seq = htons(rtp_seq++);
-  header.timestamp = htonl((uint32_t)rtp_time);
-  header.ssrc = rtp_id;
-  header.mpt = (idled ? 0x80 : 0x00) | 10;
-  /* 10 = L16 = 16-bit x 2 x 44100KHz.  We ought to deduce this value from
-   * the sample rate (in a library somewhere so that configuration.c can rule
-   * out invalid rates).
-   */
-  idled = 0;
-  if(bytes > NETWORK_BYTES - sizeof header) {
-    bytes = NETWORK_BYTES - sizeof header;
-    /* Always send a whole number of frames */
-    bytes -= bytes % bpf;
-  }
-  /* "The RTP clock rate used for generating the RTP timestamp is independent
-   * of the number of channels and the encoding; it equals the number of
-   * sampling periods per second.  For N-channel encodings, each sampling
-   * period (say, 1/8000 of a second) generates N samples. (This terminology
-   * is standard, but somewhat confusing, as the total number of samples
-   * generated per second is then the sampling rate times the channel
-   * count.)"
-   */
-  vec[0].iov_base = (void *)&header;
-  vec[0].iov_len = sizeof header;
-  vec[1].iov_base = playing->buffer + playing->start;
-  vec[1].iov_len = bytes;
-  do {
-    written_bytes = writev(bfd, vec, 2);
-  } while(written_bytes < 0 && errno == EINTR);
-  if(written_bytes < 0) {
-    error(errno, "error transmitting audio data");
-    ++audio_errors;
-    if(audio_errors == 10)
-      fatal(0, "too many audio errors");
-    return 0;
-  } else
-    audio_errors /= 2;
-  written_bytes -= sizeof (struct rtp_header);
-  written_frames = written_bytes / bpf;
-  /* Advance RTP's notion of the time */
-  rtp_time += written_frames * config->sample_format.channels;
-  return written_frames;
-}
-
-static int bfd_slot;
-
-/** @brief Set up poll array for network play */
-static void network_beforepoll(int *timeoutp) {
-  struct timeval now;
-  uint64_t target_us;
-  uint64_t target_rtp_time;
-  const int64_t samples_per_second = config->sample_format.rate
-                                   * config->sample_format.channels;
-  int64_t lead, ahead_ms;
-  
-  /* If we're starting then initialize the base time */
-  if(!rtp_time)
-    xgettimeofday(&rtp_time_0, 0);
-  /* We send audio data whenever we would otherwise get behind */
-  xgettimeofday(&now, 0);
-  target_us = tvsub_us(now, rtp_time_0);
-  if(target_us > UINT64_MAX / 88200)
-    fatal(0, "rtp_time=%"PRIu64" rtp_time_0=%ld.%06ld now=%ld.%06ld target_us=%"PRIu64" (%"PRId64")\n",
-          rtp_time,
-          (long)rtp_time_0.tv_sec, (long)rtp_time_0.tv_usec,
-          (long)now.tv_sec, (long)now.tv_usec,
-          target_us, target_us);
-  target_rtp_time = (target_us * config->sample_format.rate
-                               * config->sample_format.channels)
-                     / 1000000;
-  /* Lead is how far ahead we are */
-  lead = rtp_time - target_rtp_time;
-  if(lead <= 0)
-    /* We're behind or even, so we'll need to write as soon as we can */
-    bfd_slot = addfd(bfd, POLLOUT);
-  else {
-    /* We've ahead, we can afford to wait a bit even if the IP stack thinks it
-     * can accept more. */
-    ahead_ms = 1000 * lead / samples_per_second;
-    if(ahead_ms < *timeoutp)
-      *timeoutp = ahead_ms;
-  }
-}
-
-/** @brief Process poll() results for network play */
-static int network_ready(void) {
-  if(fds[bfd_slot].revents & (POLLOUT | POLLERR))
-    return 1;
-  else
-    return 0;
-}
-
-const struct speaker_backend network_backend = {
-  BACKEND_NETWORK,
-  0,
-  network_init,
-  0,                                    /* activate */
-  network_play,
-  0,                                    /* deactivate */
-  network_beforepoll,
-  network_ready
-};
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
diff --git a/server/speaker-oss.c b/server/speaker-oss.c
deleted file mode 100644 (file)
index a84d0bb..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2007, 2008 Richard Kettlewell
- * Portions copyright (C) 2007 Ross Younger
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- * 
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker-oss.c
- * @brief Support for @ref BACKEND_OSS */
-
-#include "common.h"
-
-#if HAVE_SYS_SOUNDCARD_H
-
-#include <unistd.h>
-#include <poll.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <errno.h>
-#include <sys/ioctl.h>
-#include <sys/soundcard.h>
-
-#include "configuration.h"
-#include "syscalls.h"
-#include "log.h"
-#include "speaker-protocol.h"
-#include "speaker.h"
-
-/** @brief Current device */
-static int ossfd = -1;
-
-/** @brief OSS backend initialization */
-static void oss_init(void) {
-  info("selected OSS backend");
-}
-
-/** @brief OSS deactivation */
-static void oss_deactivate(void) {
-  if(ossfd != -1) {
-    xclose(ossfd);
-    ossfd = -1;
-    device_state = device_closed;
-    D(("released audio device"));
-  }
-}
-
-/** @brief OSS backend activation */
-static void oss_activate(void) {
-  int stereo, format, rate;
-  const char *device;
-
-  if(ossfd == -1) {
-    /* Try to pick a device */
-    if(!strcmp(config->device, "default")) {
-      if(access("/dev/dsp", W_OK) == 0)
-       device = "/dev/dsp";
-      else if(access("/dev/audio", W_OK) == 0)
-       device = "/dev/audio";
-      else {
-        static int reported;
-        
-        if(!reported) {
-          error(0, "cannot determine default OSS device");
-          reported = 1;
-        }
-        goto failed;
-      }
-    } else
-      device = config->device; /* just believe the user */
-    /* Open the device */
-    if((ossfd = open(device, O_WRONLY, 0)) < 0) {
-      error(errno, "error opening %s", device);
-      goto failed;
-    }
-    /* Set the audio format */
-    stereo = (config->sample_format.channels == 2);
-    if(ioctl(ossfd, SNDCTL_DSP_STEREO, &stereo) < 0) {
-      error(errno, "error calling ioctl SNDCTL_DSP_STEREO %d", stereo);
-      goto failed;
-    }
-    if(config->sample_format.bits == 8)
-      format = AFMT_U8;
-    else if(config->sample_format.bits == 16)
-      format = (config->sample_format.endian == ENDIAN_LITTLE
-               ? AFMT_S16_LE : AFMT_S16_BE);
-    else {
-      error(0, "unsupported sample_format for oss backend"); 
-      goto failed;
-    }
-    if(ioctl(ossfd, SNDCTL_DSP_SETFMT, &format) < 0) {
-      error(errno, "error calling ioctl SNDCTL_DSP_SETFMT %#x", format);
-      goto failed;
-    }
-    rate = config->sample_format.rate;
-    if(ioctl(ossfd, SNDCTL_DSP_SPEED, &rate) < 0) {
-      error(errno, "error calling ioctl SNDCTL_DSP_SPEED %d", rate);
-      goto failed;
-    }
-    if((unsigned)rate != config->sample_format.rate)
-      error(0, "asked for %luHz, got %dHz",
-           (unsigned long)config->sample_format.rate, rate);
-    nonblock(ossfd);
-    device_state = device_open;
-  }
-  return;
-failed:
-  device_state = device_error;
-  if(ossfd >= 0) {
-    xclose(ossfd);
-    ossfd = -1;
-  }
-}
-
-/** @brief Play via OSS */
-static size_t oss_play(size_t frames) {
-  const size_t bytes_to_play = frames * bpf;
-  ssize_t bytes_written;
-
-  bytes_written = write(ossfd, playing->buffer + playing->start,
-                       bytes_to_play);
-  if(bytes_written < 0)
-    switch(errno) {
-    case EINTR:                        /* interruped */
-    case EAGAIN:               /* overrun */
-      return 0;                        /* try again later */
-    default:
-      fatal(errno, "error writing to audio device");
-    }
-  return bytes_written / bpf;
-}
-
-static int oss_slot;
-
-/** @brief Fill in poll fd array for OSS */
-static void oss_beforepoll(int attribute((unused)) *timeoutp) {
-  oss_slot = addfd(ossfd, POLLOUT|POLLERR);
-}
-
-/** @brief Process poll() results for OSS */
-static int oss_ready(void) {
-  return !!(fds[oss_slot].revents & (POLLOUT|POLLERR));
-}
-
-const struct speaker_backend oss_backend = {
-  BACKEND_OSS,
-  0,
-  oss_init,
-  oss_activate,
-  oss_play,
-  oss_deactivate,
-  oss_beforepoll,
-  oss_ready
-};
-
-#endif
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
index 6f8a5d9486f299e782c1658d5979171483a53a15..6a212dd6230cf3e78ecdaaaa8c865bac792a1527 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder
 /*
  * This file is part of DisOrder
- * Copyright (C) 2005-2008 Richard Kettlewell
+ * Copyright (C) 2005-2009 Richard Kettlewell
  * Portions (C) 2007 Mark Wooding
  *
  * This program is free software: you can redistribute it and/or modify
  * Portions (C) 2007 Mark Wooding
  *
  * This program is free software: you can redistribute it and/or modify
  * process that is about to become disorder-normalize) and plays them in the
  * right order.
  *
  * process that is about to become disorder-normalize) and plays them in the
  * right order.
  *
- * @b Encodings.  For the <a href="http://www.alsa-project.org/">ALSA</a> API,
- * 8- and 16- bit stereo and mono are supported, with any sample rate (within
- * the limits that ALSA can deal with.)
+ * @b Model.  mainloop() implements a select loop awaiting commands from the
+ * main server, new connections to the speaker socket, and audio data on those
+ * connections.  Each connection starts with a queue ID (with a 32-bit
+ * native-endian length word), allowing it to be referred to in commands from
+ * the server.
+ *
+ * Data read on connections is buffered, up to a limit (currently 1Mbyte per
+ * track).  No attempt is made here to limit the number of tracks, it is
+ * assumed that the main server won't start outrageously many decoders.
+ *
+ * Audio is supplied from this buffer to the uaudio play callback.  Playback is
+ * enabled when a track is to be played and disabled when the its last bytes
+ * have been return by the callback; pause and resume is implemneted the
+ * obvious way.  If the callback finds itself required to play when there is no
+ * playing track it returns dead air.
+ *
+ * @b Encodings.  The encodings supported depend entirely on the uaudio backend
+ * chosen.  See @ref uaudio.h, etc.
  *
  * Inbound data is expected to match @c config->sample_format.  In normal use
  * this is arranged by the @c disorder-normalize program (see @ref
  *
  * Inbound data is expected to match @c config->sample_format.  In normal use
  * this is arranged by the @c disorder-normalize program (see @ref
@@ -63,6 +78,7 @@
 #include <poll.h>
 #include <sys/un.h>
 #include <sys/stat.h>
 #include <poll.h>
 #include <sys/un.h>
 #include <sys/stat.h>
+#include <pthread.h>
 
 #include "configuration.h"
 #include "syscalls.h"
 
 #include "configuration.h"
 #include "syscalls.h"
 #include "mem.h"
 #include "speaker-protocol.h"
 #include "user.h"
 #include "mem.h"
 #include "speaker-protocol.h"
 #include "user.h"
-#include "speaker.h"
 #include "printf.h"
 #include "version.h"
 #include "printf.h"
 #include "version.h"
+#include "uaudio.h"
 
 
-/** @brief Linked list of all prepared tracks */
-struct track *tracks;
+/** @brief Maximum number of FDs to poll for */
+#define NFDS 1024
+
+/** @brief Track structure
+ *
+ * Known tracks are kept in a linked list.  Usually there will be at most two
+ * of these but rearranging the queue can cause there to be more.
+ */
+struct track {
+  /** @brief Next track */
+  struct track *next;
+
+  /** @brief Input file descriptor */
+  int fd;                               /* input FD */
+
+  /** @brief Track ID */
+  char id[24];
+
+  /** @brief Start position of data in buffer */
+  size_t start;
+
+  /** @brief Number of bytes of data in buffer */
+  size_t used;
+
+  /** @brief Set @c fd is at EOF */
+  int eof;
+
+  /** @brief Total number of samples played */
+  unsigned long long played;
+
+  /** @brief Slot in @ref fds */
+  int slot;
+
+  /** @brief Set when playable
+   *
+   * A track becomes playable whenever it fills its buffer or reaches EOF; it
+   * stops being playable when it entirely empties its buffer.  Tracks start
+   * out life not playable.
+   */
+  int playable;
+  
+  /** @brief Input buffer
+   *
+   * 1Mbyte is enough for nearly 6s of 44100Hz 16-bit stereo
+   */
+  char buffer[1048576];
+};
+
+/** @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.
+ */
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
 
 
-/** @brief Playing track, or NULL */
-struct track *playing;
+/** @brief Linked list of all prepared tracks */
+static struct track *tracks;
 
 
-/** @brief Number of bytes pre frame */
-size_t bpf;
+/** @brief Playing track, or NULL
+ *
+ * This means the DESIRED playing track.  It does not reflect any other state
+ * (e.g. activation of uaudio backend).
+ */
+static struct track *playing;
 
 /** @brief Array of file descriptors for poll() */
 
 /** @brief Array of file descriptors for poll() */
-struct pollfd fds[NFDS];
+static struct pollfd fds[NFDS];
 
 /** @brief Next free slot in @ref fds */
 
 /** @brief Next free slot in @ref fds */
-int fdno;
+static int fdno;
 
 /** @brief Listen socket */
 static int listenfd;
 
 
 /** @brief Listen socket */
 static int listenfd;
 
-static time_t last_report;              /* when we last reported */
-static int paused;                      /* pause status */
+/** @brief Timestamp of last potential report to server */
+static time_t last_report;
 
 
-/** @brief The current device state */
-enum device_states device_state;
+/** @brief Set when paused */
+static int paused;
 
 
-/** @brief Set when idled
- *
- * This is set when the sound device is deliberately closed by idle().
- */
-int idled;
+/** @brief Set when back end activated */
+static int activated;
+
+/** @brief Signal pipe back into the poll() loop */
+static int sigpipe[2];
 
 /** @brief Selected backend */
 
 /** @brief Selected backend */
-static const struct speaker_backend *backend;
+static const struct uaudio *backend;
 
 static const struct option options[] = {
   { "help", no_argument, 0, 'h' },
 
 static const struct option options[] = {
   { "help", no_argument, 0, 'h' },
@@ -136,12 +210,11 @@ static void help(void) {
   exit(0);
 }
 
   exit(0);
 }
 
-/** @brief Return the number of bytes per frame in @p format */
-static size_t bytes_per_frame(const struct stream_header *format) {
-  return format->channels * format->bits / 8;
-}
-
-/** @brief Find track @p id, maybe creating it if not found */
+/** @brief Find track @p id, maybe creating it if not found
+ * @param id Track ID to find
+ * @param create If nonzero, create track structure of @p id not found
+ * @return Pointer to track structure or NULL
+ */
 static struct track *findtrack(const char *id, int create) {
   struct track *t;
 
 static struct track *findtrack(const char *id, int create) {
   struct track *t;
 
@@ -158,7 +231,10 @@ static struct track *findtrack(const char *id, int create) {
   return t;
 }
 
   return t;
 }
 
-/** @brief Remove track @p id (but do not destroy it) */
+/** @brief Remove track @p id (but do not destroy it)
+ * @param id Track ID to remove
+ * @return Track structure or NULL if not found
+ */
 static struct track *removetrack(const char *id) {
   struct track *t, **tt;
 
 static struct track *removetrack(const char *id) {
   struct track *t, **tt;
 
@@ -170,10 +246,13 @@ static struct track *removetrack(const char *id) {
   return t;
 }
 
   return t;
 }
 
-/** @brief Destroy a track */
+/** @brief Destroy a track
+ * @param t Track structure
+ */
 static void destroy(struct track *t) {
   D(("destroy %s", t->id));
 static void destroy(struct track *t) {
   D(("destroy %s", t->id));
-  if(t->fd != -1) xclose(t->fd);
+  if(t->fd != -1)
+    xclose(t->fd);
   free(t);
 }
 
   free(t);
 }
 
@@ -187,90 +266,49 @@ static void destroy(struct track *t) {
  */
 static int speaker_fill(struct track *t) {
   size_t where, left;
  */
 static int speaker_fill(struct track *t) {
   size_t where, left;
-  int n;
+  int n, rc;
 
   D(("fill %s: eof=%d used=%zu",
      t->id, t->eof, t->used));
 
   D(("fill %s: eof=%d used=%zu",
      t->id, t->eof, t->used));
-  if(t->eof) return -1;
+  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;
     /* Get as much data as we can */
   if(t->used < sizeof t->buffer) {
     /* there is room left in the buffer */
     where = (t->start + t->used) % sizeof t->buffer;
     /* Get as much data as we can */
-    if(where >= t->start) left = (sizeof t->buffer) - where;
-    else left = t->start - where;
+    if(where >= t->start)
+      left = (sizeof t->buffer) - where;
+    else
+      left = t->start - where;
+    pthread_mutex_unlock(&lock);
     do {
       n = read(t->fd, t->buffer + where, left);
     } while(n < 0 && errno == EINTR);
     do {
       n = read(t->fd, t->buffer + where, left);
     } while(n < 0 && errno == EINTR);
+    pthread_mutex_lock(&lock);
     if(n < 0) {
     if(n < 0) {
-      if(errno != EAGAIN) fatal(errno, "error reading sample stream");
-      return 0;
-    }
-    if(n == 0) {
+      if(errno != EAGAIN)
+        fatal(errno, "error reading sample stream");
+      rc = 0;
+    } else if(n == 0) {
       D(("fill %s: eof detected", t->id));
       t->eof = 1;
       D(("fill %s: eof detected", t->id));
       t->eof = 1;
+      /* A track always becomes playable at EOF; we're not going to see any
+       * more data. */
       t->playable = 1;
       t->playable = 1;
-      return -1;
+      rc = -1;
+    } else {
+      t->used += n;
+      /* A track becomes playable when it (first) fills its buffer.  For
+       * 44.1KHz 16-bit stereo this is ~6s of audio data.  The latency will
+       * depend how long that takes to decode (hopefuly not very!) */
+      if(t->used == sizeof t->buffer)
+        t->playable = 1;
+      rc = 0;
     }
     }
-    t->used += n;
-    if(t->used == sizeof t->buffer)
-      t->playable = 1;
   }
   }
-  return 0;
-}
-
-/** @brief Close the sound device
- *
- * This is called to deactivate the output device when pausing, and also by the
- * ALSA backend when changing encoding (in which case the sound device will be
- * immediately reactivated).
- */
-static void idle(void) {
-  D(("idle"));
-  if(backend->deactivate) 
-    backend->deactivate();
-  else
-    device_state = device_closed;
-  idled = 1;
-}
-
-/** @brief Abandon the current track */
-void abandon(void) {
-  struct speaker_message sm;
-
-  D(("abandon"));
-  memset(&sm, 0, sizeof sm);
-  sm.type = SM_FINISHED;
-  strcpy(sm.id, playing->id);
-  speaker_send(1, &sm);
-  removetrack(playing->id);
-  destroy(playing);
-  playing = 0;
-}
-
-/** @brief Enable sound output
- *
- * Makes sure the sound device is open and has the right sample format.  Return
- * 0 on success and -1 on error.
- */
-static void activate(void) {
-  if(backend->activate)
-    backend->activate();
-  else
-    device_state = device_open;
-}
-
-/** @brief Check whether the current track has finished
- *
- * The current track is determined to have finished either if the input stream
- * eded before the format could be determined (i.e. it is malformed) or the
- * input is at end of file and there is less than a frame left unplayed.  (So
- * it copes with decoders that crash mid-frame.)
- */
-static void maybe_finished(void) {
-  if(playing
-     && playing->eof
-     && playing->used < bytes_per_frame(&config->sample_format))
-    abandon();
+  pthread_mutex_unlock(&lock);
+  return rc;
 }
 
 /** @brief Return nonzero if we want to play some audio
 }
 
 /** @brief Return nonzero if we want to play some audio
@@ -284,72 +322,7 @@ static int playable(void) {
          && playing->playable;
 }
 
          && playing->playable;
 }
 
-/** @brief Play up to @p frames frames of audio
- *
- * It is always safe to call this function.
- * - If @ref playing is 0 then it will just return
- * - If @ref paused is non-0 then it will just return
- * - If @ref device_state != @ref device_open then it will call activate() and
- * return if it it fails.
- * - If there is not enough audio to play then it play what is available.
- *
- * If there are not enough frames to play then whatever is available is played
- * instead.  It is up to mainloop() to ensure that speaker_play() is not called
- * when unreasonably only an small amounts of data is available to play.
- */
-static void speaker_play(size_t frames) {
-  size_t avail_frames, avail_bytes, written_frames;
-  ssize_t written_bytes;
-
-  /* Make sure there's a track to play and it is not paused */
-  if(!playable())
-    return;
-  /* Make sure the output device is open */
-  if(device_state != device_open) {
-    activate(); 
-    if(device_state != device_open)
-      return;
-  }
-  D(("play: play %zu/%zu%s %dHz %db %dc",  frames, playing->used / bpf,
-     playing->eof ? " EOF" : "",
-     config->sample_format.rate,
-     config->sample_format.bits,
-     config->sample_format.channels));
-  /* Figure out how many frames there are available to write */
-  if(playing->start + playing->used > sizeof playing->buffer)
-    /* The ring buffer is currently wrapped, only play up to the wrap point */
-    avail_bytes = (sizeof playing->buffer) - playing->start;
-  else
-    /* The ring buffer is not wrapped, can play the lot */
-    avail_bytes = playing->used;
-  avail_frames = avail_bytes / bpf;
-  /* Only play up to the requested amount */
-  if(avail_frames > frames)
-    avail_frames = frames;
-  if(!avail_frames)
-    return;
-  /* Play it, Sam */
-  written_frames = backend->play(avail_frames);
-  written_bytes = written_frames * bpf;
-  /* written_bytes and written_frames had better both be set and correct by
-   * this point */
-  playing->start += written_bytes;
-  playing->used -= written_bytes;
-  playing->played += written_frames;
-  /* If the pointer is at the end of the buffer (or the buffer is completely
-   * empty) wrap it back to the start. */
-  if(!playing->used || playing->start == (sizeof playing->buffer))
-    playing->start = 0;
-  /* If the buffer emptied out mark the track as unplayably */
-  if(!playing->used && !playing->eof) {
-    error(0, "track buffer emptied");
-    playing->playable = 0;
-  }
-  frames -= written_frames;
-  return;
-}
-
-/* Notify the server what we're up to. */
+/** @brief Notify the server what we're up to */
 static void report(void) {
   struct speaker_message sm;
 
 static void report(void) {
   struct speaker_message sm;
 
@@ -357,23 +330,20 @@ static void report(void) {
     memset(&sm, 0, sizeof sm);
     sm.type = paused ? SM_PAUSED : SM_PLAYING;
     strcpy(sm.id, playing->id);
     memset(&sm, 0, sizeof sm);
     sm.type = paused ? SM_PAUSED : SM_PLAYING;
     strcpy(sm.id, playing->id);
-    sm.data = playing->played / config->sample_format.rate;
+    pthread_mutex_lock(&lock);
+    sm.data = playing->played / (uaudio_rate * uaudio_channels);
+    pthread_mutex_unlock(&lock);
     speaker_send(1, &sm);
   }
   time(&last_report);
 }
 
     speaker_send(1, &sm);
   }
   time(&last_report);
 }
 
-static void reap(int __attribute__((unused)) sig) {
-  pid_t cmdpid;
-  int st;
-
-  do
-    cmdpid = waitpid(-1, &st, WNOHANG);
-  while(cmdpid > 0);
-  signal(SIGCHLD, reap);
-}
-
-int addfd(int fd, int events) {
+/** @brief Add a file descriptor to the set to poll() for
+ * @param fd File descriptor
+ * @param events Events to wait for e.g. @c POLLIN
+ * @return Slot number
+ */
+static int addfd(int fd, int events) {
   if(fdno < NFDS) {
     fds[fdno].fd = fd;
     fds[fdno].events = events;
   if(fdno < NFDS) {
     fds[fdno].fd = fd;
     fds[fdno].events = events;
@@ -382,29 +352,70 @@ int addfd(int fd, int events) {
     return -1;
 }
 
     return -1;
 }
 
-/** @brief Table of speaker backends */
-static const struct speaker_backend *backends[] = {
-#if HAVE_ALSA_ASOUNDLIB_H
-  &alsa_backend,
-#endif
-  &command_backend,
-  &network_backend,
-#if HAVE_COREAUDIO_AUDIOHARDWARE_H
-  &coreaudio_backend,
-#endif
-#if HAVE_SYS_SOUNDCARD_H
-  &oss_backend,
-#endif
-  0
-};
+/** @brief Callback to return some sampled data
+ * @param buffer Where to put sample data
+ * @param max_samples How many samples to return
+ * @param userdata User data
+ * @return Number of samples written
+ *
+ * See uaudio_callback().
+ */
+static size_t speaker_callback(void *buffer,
+                               size_t max_samples,
+                               void attribute((unused)) *userdata) {
+  const size_t max_bytes = max_samples * uaudio_sample_size;
+  size_t provided_samples = 0;
+
+  pthread_mutex_lock(&lock);
+  /* TODO perhaps we should immediately go silent if we've been asked to pause
+   * or cancel the playing track (maybe block in the cancel case and see what
+   * else turns up?) */
+  if(playing) {
+    if(playing->used > 0) {
+      size_t bytes;
+      /* Compute size of largest contiguous chunk.  We get called as often as
+       * necessary so there's no need for cleverness here. */
+      if(playing->start + playing->used > sizeof playing->buffer)
+        bytes = sizeof playing->buffer - playing->start;
+      else
+        bytes = playing->used;
+      /* Limit to what we were asked for */
+      if(bytes > max_bytes)
+        bytes = max_bytes;
+      /* Provide it */
+      memcpy(buffer, playing->buffer + playing->start, bytes);
+      playing->start += bytes;
+      playing->used -= bytes;
+      /* Wrap around to start of buffer */
+      if(playing->start == sizeof playing->buffer)
+        playing->start = 0;
+      /* See if we've reached the end of the track */
+      if(playing->used == 0 && playing->eof)
+        write(sigpipe[1], "", 1);
+      provided_samples = bytes / uaudio_sample_size;
+      playing->played += provided_samples;
+    }
+  }
+  /* If we couldn't provide anything at all, play dead air */
+  /* TODO maybe it would be better to block, in some cases? */
+  if(!provided_samples) {
+    memset(buffer, 0, max_bytes);
+    provided_samples = max_samples;
+  }
+  pthread_mutex_unlock(&lock);
+  return provided_samples;
+}
 
 /** @brief Main event loop */
 static void mainloop(void) {
   struct track *t;
   struct speaker_message sm;
 
 /** @brief Main event loop */
 static void mainloop(void) {
   struct track *t;
   struct speaker_message sm;
-  int n, fd, stdin_slot, timeout, listen_slot;
+  int n, fd, stdin_slot, timeout, listen_slot, sigpipe_slot;
 
 
+  /* Keep going while our parent process is alive */
   while(getppid() != 1) {
   while(getppid() != 1) {
+    int force_report = 0;
+
     fdno = 0;
     /* By default we will wait up to a second before thinking about current
      * state. */
     fdno = 0;
     /* By default we will wait up to a second before thinking about current
      * state. */
@@ -422,18 +433,6 @@ static void mainloop(void) {
       playing->slot = addfd(playing->fd, POLLIN);
     else if(playing)
       playing->slot = -1;
       playing->slot = addfd(playing->fd, POLLIN);
     else if(playing)
       playing->slot = -1;
-    if(playable()) {
-      /* We want to play some audio.  If the device is closed then we attempt
-       * to open it. */
-      if(device_state == device_closed)
-        activate();
-      /* If the device is (now) open then we will wait up until it is ready for
-       * more.  If something went wrong then we should have device_error
-       * instead, but the post-poll code will cope even if it's
-       * device_closed. */
-      if(device_state == device_open)
-        backend->beforepoll(&timeout);
-    }
     /* If any other tracks don't have a full buffer, try to read sample data
      * from them.  We do this last of all, so that if we run out of slots,
      * nothing important can't be monitored. */
     /* If any other tracks don't have a full buffer, try to read sample data
      * from them.  We do this last of all, so that if we run out of slots,
      * nothing important can't be monitored. */
@@ -446,28 +445,13 @@ static void mainloop(void) {
         } else
           t->slot = -1;
       }
         } else
           t->slot = -1;
       }
+    sigpipe_slot = addfd(sigpipe[1], POLLIN);
     /* Wait for something interesting to happen */
     n = poll(fds, fdno, timeout);
     if(n < 0) {
       if(errno == EINTR) continue;
       fatal(errno, "error calling poll");
     }
     /* Wait for something interesting to happen */
     n = poll(fds, fdno, timeout);
     if(n < 0) {
       if(errno == EINTR) continue;
       fatal(errno, "error calling poll");
     }
-    /* Play some sound before doing anything else */
-    if(playable()) {
-      /* We want to play some audio */
-      if(device_state == device_open) {
-        if(backend->ready())
-          speaker_play(3 * FRAMES);
-      } else {
-        /* We must be in _closed or _error, and it should be the latter, but we
-         * cope with either.
-         *
-         * We most likely timed out, so now is a good time to retry.
-         * speaker_play() knows to re-activate the device if necessary.
-         */
-        speaker_play(3 * FRAMES);
-      }
-    }
     /* Perhaps a connection has arrived */
     if(fds[listen_slot].revents & POLLIN) {
       struct sockaddr_un addr;
     /* Perhaps a connection has arrived */
     if(fds[listen_slot].revents & POLLIN) {
       struct sockaddr_un addr;
@@ -513,37 +497,30 @@ static void mainloop(void) {
       if(n > 0)
        switch(sm.type) {
        case SM_PLAY:
       if(n > 0)
        switch(sm.type) {
        case SM_PLAY:
-          if(playing) fatal(0, "got SM_PLAY but already playing something");
+          if(playing)
+            fatal(0, "got SM_PLAY but already playing something");
          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;
          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;
-          /* We attempt to play straight away rather than going round the loop.
-           * speaker_play() is clever enough to perform any activation that is
-           * required. */
-          speaker_play(3 * FRAMES);
-          report();
+          force_report = 1;
          break;
        case SM_PAUSE:
           D(("SM_PAUSE"));
          paused = 1;
          break;
        case SM_PAUSE:
           D(("SM_PAUSE"));
          paused = 1;
-          report();
+          force_report = 1;
           break;
        case SM_RESUME:
           D(("SM_RESUME"));
           break;
        case SM_RESUME:
           D(("SM_RESUME"));
-          if(paused) {
-            paused = 0;
-            /* As for SM_PLAY we attempt to play straight away. */
-            if(playing)
-              speaker_play(3 * FRAMES);
-          }
-          report();
+          paused = 0;
+          force_report = 1;
          break;
        case SM_CANCEL:
           D(("SM_CANCEL %s", sm.id));
          t = removetrack(sm.id);
          if(t) {
          break;
        case SM_CANCEL:
           D(("SM_CANCEL %s", sm.id));
          t = removetrack(sm.id);
          if(t) {
+            pthread_mutex_lock(&lock);
            if(t == playing) {
               /* scratching the playing track */
               sm.type = SM_FINISHED;
            if(t == playing) {
               /* scratching the playing track */
               sm.type = SM_FINISHED;
@@ -559,6 +536,7 @@ static void mainloop(void) {
             }
             strcpy(sm.id, t->id);
            destroy(t);
             }
             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. */
          } else {
             /* Probably scratching the playing track well before it's got
              * going, but could indicate a bug, so we log this as an error. */
@@ -566,11 +544,12 @@ static void mainloop(void) {
            error(0, "SM_CANCEL for unknown track %s", sm.id);
           }
           speaker_send(1, &sm);
            error(0, "SM_CANCEL for unknown track %s", sm.id);
           }
           speaker_send(1, &sm);
-          report();
+          force_report = 1;
          break;
        case SM_RELOAD:
           D(("SM_RELOAD"));
          break;
        case SM_RELOAD:
           D(("SM_RELOAD"));
-         if(config_read(1)) error(0, "cannot read configuration");
+         if(config_read(1))
+            error(0, "cannot read configuration");
           info("reloaded configuration");
          break;
        default:
           info("reloaded configuration");
          break;
        default:
@@ -583,14 +562,40 @@ static void mainloop(void) {
          && t->slot != -1
          && (fds[t->slot].revents & (POLLIN | POLLHUP)))
          speaker_fill(t);
          && t->slot != -1
          && (fds[t->slot].revents & (POLLIN | POLLHUP)))
          speaker_fill(t);
-    /* Maybe we finished playing a track somewhere in the above */
-    maybe_finished();
-    /* If we don't need the sound device for now then close it for the benefit
-     * of anyone else who wants it. */
-    if((!playing || paused) && device_state == device_open)
-      idle();
-    /* If we've not reported out state for a second do so now. */
-    if(time(0) > last_report)
+    /* Drain the signal pipe.  We don't care about its contents, merely that it
+     * interrupted poll(). */
+    if(fds[sigpipe_slot].revents & POLLIN) {
+      char buffer[64];
+
+      read(sigpipe[0], buffer, sizeof buffer);
+    }
+    if(playing && playing->used == 0 && playing->eof) {
+      /* The playing track is done.  Tell the server, and destroy it. */
+      memset(&sm, 0, sizeof sm);
+      sm.type = SM_FINISHED;
+      strcpy(sm.id, playing->id);
+      speaker_send(1, &sm);
+      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 */
+    }
+    /* Impose any state change required by the above */
+    if(playable()) {
+      if(!activated) {
+        activated = 1;
+        backend->activate();
+      }
+    } else {
+      if(activated) {
+        activated = 0;
+        backend->deactivate();
+      }
+    }
+    /* If we've not reported our state for a second do so now. */
+    if(force_report || time(0) > last_report)
       report();
   }
 }
       report();
   }
 }
@@ -602,6 +607,7 @@ int main(int argc, char **argv) {
   struct speaker_message sm;
   const char *d;
   char *dir;
   struct speaker_message sm;
   const char *d;
   char *dir;
+  struct rlimit rl[1];
 
   set_progname(argv);
   if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
 
   set_progname(argv);
   if(!setlocale(LC_CTYPE, "")) fatal(errno, "error calling setlocale");
@@ -622,27 +628,41 @@ int main(int argc, char **argv) {
     openlog(progname, LOG_PID, LOG_DAEMON);
     log_default = &log_syslog;
   }
     openlog(progname, LOG_PID, LOG_DAEMON);
     log_default = &log_syslog;
   }
+  config_uaudio_apis = uaudio_apis;
   if(config_read(1)) fatal(0, "cannot read configuration");
   if(config_read(1)) fatal(0, "cannot read configuration");
-  bpf = bytes_per_frame(&config->sample_format);
   /* ignore SIGPIPE */
   signal(SIGPIPE, SIG_IGN);
   /* ignore SIGPIPE */
   signal(SIGPIPE, SIG_IGN);
-  /* reap kids */
-  signal(SIGCHLD, reap);
   /* set nice value */
   xnice(config->nice_speaker);
   /* change user */
   become_mortal();
   /* make sure we're not root, whatever the config says */
   /* set nice value */
   xnice(config->nice_speaker);
   /* change user */
   become_mortal();
   /* make sure we're not root, whatever the config says */
-  if(getuid() == 0 || geteuid() == 0) fatal(0, "do not run as root");
-  /* identify the backend used to play */
-  for(n = 0; backends[n]; ++n)
-    if(backends[n]->backend == config->api)
-      break;
-  if(!backends[n])
-    fatal(0, "unsupported api %d", config->api);
-  backend = backends[n];
+  if(getuid() == 0 || geteuid() == 0)
+    fatal(0, "do not run as root");
+  /* Make sure we can't have more than NFDS files open (it would bust our
+   * poll() array) */
+  if(getrlimit(RLIMIT_NOFILE, rl) < 0)
+    fatal(errno, "getrlimit RLIMIT_NOFILE");
+  if(rl->rlim_cur > NFDS) {
+    rl->rlim_cur = NFDS;
+    if(setrlimit(RLIMIT_NOFILE, rl) < 0)
+      fatal(errno, "setrlimit to reduce RLIMIT_NOFILE to %lu",
+            (unsigned long)rl->rlim_cur);
+    info("set RLIM_NOFILE to %lu", (unsigned long)rl->rlim_cur);
+  } else
+    info("RLIM_NOFILE is %lu", (unsigned long)rl->rlim_cur);
+  /* create a pipe between the backend callback and the poll() loop */
+  xpipe(sigpipe);
+  nonblock(sigpipe[0]);
+  /* set up audio backend */
+  uaudio_set_format(config->sample_format.rate,
+                    config->sample_format.channels,
+                    config->sample_format.bits,
+                    config->sample_format.bits != 8);
+  /* TODO other parameters! */
+  backend = uaudio_find(config->api);
   /* backend-specific initialization */
   /* backend-specific initialization */
-  backend->init();
+  backend->start(speaker_callback, NULL);
   /* create the socket directory */
   byte_xasprintf(&dir, "%s/speaker", config->home);
   unlink(dir);                          /* might be a leftover socket */
   /* create the socket directory */
   byte_xasprintf(&dir, "%s/speaker", config->home);
   unlink(dir);                          /* might be a leftover socket */
diff --git a/server/speaker.h b/server/speaker.h
deleted file mode 100644 (file)
index 0cc0cb8..0000000
+++ /dev/null
@@ -1,250 +0,0 @@
-/*
- * This file is part of DisOrder
- * Copyright (C) 2005-2008 Richard Kettlewell
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-/** @file server/speaker.h
- * @brief Speaker process
- */
-#ifndef SPEAKER_H
-#define SPEAKER_H
-
-#ifdef WORDS_BIGENDIAN
-# define MACHINE_AO_FMT AO_FMT_BIG
-#else
-# define MACHINE_AO_FMT AO_FMT_LITTLE
-#endif
-
-/** @brief Minimum number of frames to try to play at once
- *
- * The main loop will only attempt to play any audio when this many
- * frames are available (or the current track has reached the end).
- * The actual number of frames it attempts to play will often be
- * larger than this (up to three times).
- *
- * For ALSA we request a buffer of three times this size and set the low
- * watermark to this amount.  The goal is then to keep between 1 and 3 times
- * this many frames in play.
- *
- * For other we attempt to play up to three times this many frames per
- * shot.  In practice we will often only send much less than this.
- */
-#define FRAMES 4096
-
-/** @brief Bytes to send per network packet
- *
- * This is the maximum number of bytes we pass to write(2); to determine actual
- * packet sizes, add a UDP header and an IP header (and a link layer header if
- * it's the link layer size you care about).
- *
- * Don't make this too big or arithmetic will start to overflow.
- */
-#define NETWORK_BYTES (1500-8/*UDP*/-40/*IP*/-8/*conservatism*/)
-
-/** @brief Maximum number of FDs to poll for */
-#define NFDS 256
-
-/** @brief Track structure
- *
- * Known tracks are kept in a linked list.  Usually there will be at most two
- * of these but rearranging the queue can cause there to be more.
- */
-struct track {
-  /** @brief Next track */
-  struct track *next;
-
-  /** @brief Input file descriptor */
-  int fd;                               /* input FD */
-
-  /** @brief Track ID */
-  char id[24];
-
-  /** @brief Start position of data in buffer */
-  size_t start;
-
-  /** @brief Number of bytes of data in buffer */
-  size_t used;
-
-  /** @brief Set @c fd is at EOF */
-  int eof;
-
-  /** @brief Total number of frames played */
-  unsigned long long played;
-
-  /** @brief Slot in @ref fds */
-  int slot;
-
-  /** @brief Set when playable
-   *
-   * A track becomes playable whenever it fills its buffer or reaches EOF; it
-   * stops being playable when it entirely empties its buffer.  Tracks start
-   * out life not playable.
-   */
-  int playable;
-  
-  /** @brief Input buffer
-   *
-   * 1Mbyte is enough for nearly 6s of 44100Hz 16-bit stereo
-   */
-  char buffer[1048576];
-};
-
-/** @brief Structure of a backend */
-struct speaker_backend {
-  /** @brief Which backend this is
-   *
-   * @c -1 terminates the list.
-   */
-  int backend;
-
-  /** @brief Flags
-   *
-   * This field is currently not used and must be 0.
-   */
-  unsigned flags;
-  
-  /** @brief Initialization
-   *
-   * Called once at startup.  This is responsible for one-time setup
-   * operations, for instance opening a network socket to transmit to.
-   *
-   * When writing to a native sound API this might @b not imply opening the
-   * native sound device - that might be done by @c activate below.
-   */
-  void (*init)(void);
-
-  /** @brief Activation
-   * @return 0 on success, non-0 on error
-   *
-   * Called to activate the output device.
-   *
-   * On input @ref device_state may be anything.  If it is @ref
-   * device_open then the device is already open but might be using
-   * the wrong sample format.  The device should be reconfigured to
-   * use the right sample format.
-   *
-   * If it is @ref device_error then a retry is underway and an
-   * attempt to recover or re-open the device (with the right sample
-   * format) should be made.
-   *
-   * If it is @ref device_closed then the device should be opened with
-   * the right sample format.
-   *
-   * Some devices are effectively always open and have no error state, in which
-   * case this callback can be NULL.  Note that @ref device_state still
-   * switches between @ref device_open and @ref device_closed in this case.
-   */
-  void (*activate)(void);
-
-  /** @brief Play sound
-   * @param frames Number of frames to play
-   * @return Number of frames actually played
-   *
-   * If an error occurs (and it is not immediately recovered) this
-   * should set @ref device_state to @ref device_error.
-   */
-  size_t (*play)(size_t frames);
-  
-  /** @brief Deactivation
-   *
-   * Called to deactivate the sound device.  This is the inverse of @c
-   * activate above.
-   *
-   * For sound devices that are open all the time and have no error
-   * state, this callback can be NULL.  Note that @ref device_state
-   * still switches between @ref device_open and @ref device_closed in
-   * this case.
-   */
-  void (*deactivate)(void);
-
-  /** @brief Called before poll()
-   * @param timeoutp Pointer to timeout
-   *
-   * Called before the call to poll().
-   *
-   * If desirable, should call addfd() to update the FD array and stash the
-   * slot number somewhere safe.  This will only be called if @ref device_state
-   * is @ref device_open.
-   *
-   * @p timeoutp points to the poll timeout value in milliseconds.  It may be
-   * reduced, but never increased.
-   *
-   * NB you can NOT assume that @c beforepoll is always called before @c play.
-   */
-  void (*beforepoll)(int *timeoutp);
-
-  /** @brief Called after poll()
-   * @return 1 if output device ready for play, 0 otherwise
-   *
-   * Called after the call to poll().  This will only be called if
-   * @ref device_state = @ref device_open.
-   *
-   * The return value should be 1 if the device was ready to play, or
-   * 0 if it was not.
-   */
-  int (*ready)(void);
-};
-
-/** @brief Possible device states */
-enum device_states {
-  /** @brief The device is closed */
-  device_closed,
-
-  /** @brief The device is open and ready to receive sound
-   *
-   * The current device sample format is potentially part of this state.
-   */
-  device_open,
-  
-  /** @brief An error has occurred on the device
-   *
-   * This state is used to ensure that a small interval is left
-   * between retrying the device.  If errors just set @ref
-   * device_closed then the main loop would busy-wait on broken output
-   * devices.
-   *
-   * The current device sample format is potentially part of this state.
-   */
-  device_error
-};
-
-extern enum device_states device_state;
-extern struct track *tracks;
-extern struct track *playing;
-
-extern const struct speaker_backend network_backend;
-extern const struct speaker_backend alsa_backend;
-extern const struct speaker_backend command_backend;
-extern const struct speaker_backend coreaudio_backend;
-extern const struct speaker_backend oss_backend;
-
-extern struct pollfd fds[NFDS];
-extern int fdno;
-extern size_t bpf;
-extern int idled;
-
-int addfd(int fd, int events);
-void abandon(void);
-
-#endif /* SPEAKER_H */
-
-/*
-Local Variables:
-c-basic-offset:2
-comment-column:40
-fill-column:79
-indent-tabs-mode:nil
-End:
-*/
index a99474eff11e65ef9a9a6bfa99c401a47595e34f..a0ea660dfd95d5a88c667fd54edd8a77c410e009 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder.
 /*
  * This file is part of DisOrder.
- * Copyright (C) 2004, 2005, 2007, 2008 Richard Kettlewell
+ * Copyright (C) 2004, 2005, 2007-2009 Richard Kettlewell
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -26,6 +26,9 @@ static int current_unix_fd;
 static struct addrinfo *current_listen_addrinfo;
 static int current_listen_fd;
 
 static struct addrinfo *current_listen_addrinfo;
 static int current_listen_fd;
 
+/** @brief Current audio API */
+const struct uaudio *api;
+
 void quit(ev_source *ev) {
   info("shutting down...");
   quitting(ev);
 void quit(ev_source *ev) {
   info("shutting down...");
   quitting(ev);
@@ -112,6 +115,11 @@ int reconfigure(ev_source *ev, int reload) {
   int ret = 0;
 
   D(("reconfigure(%d)", reload));
   int ret = 0;
 
   D(("reconfigure(%d)", reload));
+  if(api) {
+    if(api->close_mixer)
+      api->close_mixer();
+    api = NULL;
+  }
   if(reload) {
     need_another_rescan = trackdb_rescan_cancel();
     trackdb_close();
   if(reload) {
     need_another_rescan = trackdb_rescan_cancel();
     trackdb_close();
@@ -126,6 +134,9 @@ int reconfigure(ev_source *ev, int reload) {
   } else
     /* We only allow for upgrade at startup */
     trackdb_open(TRACKDB_CAN_UPGRADE);
   } else
     /* We only allow for upgrade at startup */
     trackdb_open(TRACKDB_CAN_UPGRADE);
+  api = uaudio_find(config->api);
+  if(api->open_mixer)
+    api->open_mixer();
   if(need_another_rescan)
     trackdb_rescan(ev, 1/*check*/, 0, 0);
   /* Arrange timeouts for schedule actions */
   if(need_another_rescan)
     trackdb_rescan(ev, 1/*check*/, 0, 0);
   /* Arrange timeouts for schedule actions */