chiark / gitweb /
Unify sound API configuration.
authorrjk@greenend.org.uk <>
Sun, 30 Dec 2007 16:20:37 +0000 (16:20 +0000)
committerrjk@greenend.org.uk <>
Sun, 30 Dec 2007 16:20:37 +0000 (16:20 +0000)
'speaker_backend' is now spelled 'api' (though the old name still
works) and effects the choice of volume setting API as well as the
device used by the speaker process.

For volume setting, only OSS is implemented (still).  The new
structure means that ALSA volume setting no longer works as it will no
longer use the OSS interface instead.  However, this was never
reliable as it depended upon ALSA's OSS emulation being enabled.

README.streams
doc/disorder_config.5.in
lib/Makefile.am
lib/configuration.c
lib/configuration.h
lib/mixer-oss.c [new file with mode: 0644]
lib/mixer.c
lib/mixer.h
server/server.c
server/speaker.c
tests/dtest.py

index 9fe3e3af82de02220ed7f793a7a497c2405e698c..8fbe6c7ce566ec5cfaaea906d61d8df0151d815b 100644 (file)
@@ -13,7 +13,7 @@ To enable this make sure that all players use the speaker process, i.e. execraw
 rather than exec (or it won't work properly) and configure the network speaker
 backend:
 
-    speaker_backend network
+    api network
     broadcast 172.17.207.255 9003
     broadcast_from 172.17.207.2 9002
 
index 78acb2a68d06738b727921536227c349d47f500c..a57a33708b2d46bf989f4215efb381aecb690692 100644 (file)
@@ -247,6 +247,34 @@ automatically included, but should include the proper extension.
 .IP
 The default is \fB{/artist}{/album}{/title}{ext}\fR.
 .TP
+.B api \fINAME\fR
+Selects the backend used to play sound and to set the volume.  The following
+options are available:
+.RS
+.TP
+.B alsa
+Use the ALSA API.  This is only available on Linux systems, on which it is the
+default.
+.TP
+.B coreaudio
+Use Apple Core Audio.  This only available on OS X systems, on which it is the
+default.
+.TP
+.B oss
+Use the OSS (/dev/dsp) API.  Not available on all platforms.
+.TP
+.B command
+Execute a command.  This is the default if
+.B speaker_command
+is specified, or if no native is available.
+.TP
+.B network
+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.
+.RE
+.TP
 .B authorization_algorithm \fIALGORITHM\fR
 Defines the algorithm used to authenticate clients.  The valid options
 are sha1 (the default), sha256, sha384 and sha512.  See
@@ -255,7 +283,7 @@ for more details.
 .TP
 .B broadcast \fIADDRESS\fR \fIPORT\fR
 Transmit sound data to \fIADDRESS\fR using UDP port \fIPORT\fR.  This implies
-\fBspeaker_backend network\fR.
+\fBapi network\fR.
 .IP
 See also \fBmulticast_loop\fR and \fBmulticast_ttl\fR.
 .TP
@@ -263,9 +291,9 @@ See also \fBmulticast_loop\fR and \fBmulticast_ttl\fR.
 Sets the (local) source address used by \fBbroadcast\fR.
 .TP
 .B channel \fICHANNEL\fR
-The mixer channel that the volume control should use.  Valid names depend on
-your operating system and hardware, but some standard ones that might be useful
-are:
+The mixer channel that the volume control should use.
+.IP
+For \fBapi oss\fR the possible values are:
 .RS
 .TP 8
 .B pcm
@@ -283,8 +311,11 @@ Master output level.  The OSS documentation recommends against using this, as
 it affects all output devices.
 .RE
 .IP
-You can also specify channels by number, if you know the right value.  NB that
-volume setting only works on OSS systems (including ALSA, via emulation).
+You can also specify channels by number, if you know the right value.
+.IP
+For \fBapi alsa\fR, volume setting is not currently supported.
+.IP
+For \fBapi coreaudio\fR, volume setting is not currently supported.
 .TP
 .B collection \fIMODULE\fR \fIENCODING\fR \fIROOT\fR
 .TP
@@ -321,8 +352,16 @@ The default is to allow everything except \fBadmin\fR and \fBregister\fR
 (modified in legacy configurations by the obsolete \fBrestrict\fR directive).
 .TP
 .B device \fINAME\fR
-ALSA device to play raw-format audio.  Default is \fBdefault\fR, i.e. to use
-the whatever the ALSA configured default is.
+Sound output device.
+.IP
+For \fBapi oss\fR this is the path to the device to use.  If it is set to
+\fBdefault\fR then \fI/dev/dsp\fR and \fI/dev/audio\fR will be tried.
+.IP
+For \fBapi alsa\fR this is the device name to use.
+.IP
+For \fBapi coreaudio\fR this is currently ignored.
+.IP
+The default is \fBdefault\fR.
 .TP
 .B gap \fISECONDS\fR
 Specifies the number of seconds to leave between tracks.  The default
@@ -344,19 +383,26 @@ Determines whether the server locks against concurrent operation.  Default is
 \fByes\fR.  There is no good reason to set this to \fBno\fR and the option will
 probably be removed in a future version.
 .TP
-.B mixer \fIPATH\fR
-The path to the mixer device, if you want access to the volume control,
-e.g. \fB/dev/mixer\fR (the default).
+.B mixer \fIDEVICE\fR
+The mixer device name, if it needs to be specified separately from
+\fBdevice\fR.
+.IP
+For \fBapi oss\fR this should be the path to the mixer device and the default
+is \fI/dev/mixer\fR.
+.IP
+For \fBapi alsa\fR, volume setting is not currently supported.
+.IP
+For \fBapi coreaudio\fR, volume setting is not currently supported.
 .TP
 .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
-\fBspeaker_backend\fR is set to \fBnetwork\fR and \fBbroadcast\fR is actually a
+\fBapi\fR is set to \fBnetwork\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.  This only applies if
-\fBspeaker_backend\fR is set to \fBnetwork\fR and \fBbroadcast\fR is actually a
+\fBapi\fR is set to \fBnetwork\fR and \fBbroadcast\fR is actually a
 multicast address.  The default is 1.
 .TP
 .B namepart \fIPART\fR \fIREGEXP\fR \fISUBST\fR [\fICONTEXT\fR [\fIREFLAGS\fR]]
@@ -518,40 +564,15 @@ scratched.  The default is \fBSIGKILL\fR.
 Signals are specified by their full C name, i.e. \fBSIGINT\fR and not \fBINT\fR
 or \fBInterrupted\fR or whatever.
 .TP
-.B speaker_backend \fINAME\fR
-Selects the backend use by the speaker process.  The following options are
-available:
-.RS
-.TP
-.B alsa
-Use the ALSA API.  This is only available on Linux systems, on which it is the
-default.
-.TP
-.B coreaudio
-Use Apple Core Audio.  This only available on OS X systems, on which it is the
-default.
-.TP
-.B oss
-Use the OSS (/dev/dsp) API.  Not available on all platforms.
-.TP
-.B command
-Execute a command.  This is the default if
-.B speaker_command
-is specified, or if no native is available.
-.TP
-.B network
-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.
-.RE
-.TP
 .B sox_generation \fB0\fR|\fB1
 Determines whether calls to \fBsox\fR(1) should use \fB-b\fR, \fB-x\fR, etc (if
 the generation is 0) or \fB-\fIbits\fR, \fB-L\fR etc (if it is 1).  See the
 documentation for your installed copy of \fBsox\fR to determine which you need.
 The default is 0.
 .TP
+.B speaker_backend \fINAME
+This is an alias for \fBapi\fR; see above.
+.TP
 .B speaker_command \fICOMMAND
 Causes the speaker subprocess to pipe audio data into shell command
 \fICOMMAND\fR, rather than writing to a local sound card.  The sample format is
index d658f862adfe3401ff9a554c147cf5dc7bfa737f..3f526e63e8392dd20b69d210c14858e45f392da8 100644 (file)
@@ -48,7 +48,7 @@ libdisorder_a_SOURCES=charset.c charset.h             \
        logfd.c logfd.h                                 \
        mem.c mem.h mem-impl.h                          \
        mime.h mime.c                                   \
-       mixer.c mixer.h                                 \
+       mixer.c mixer.h mixer-oss.c                     \
        plugin.c plugin.h                               \
        printf.c printf.h                               \
        asprintf.c fprintf.c snprintf.c                 \
index 95720a3fdca228601b3ddc70e26056eb9ba0a522..db37837115f68efb6dbc053aea8d55700f553e4e 100644 (file)
@@ -679,12 +679,6 @@ static int validate_isreg(const struct config_state *cs,
   return 0;
 }
 
-static int validate_ischr(const struct config_state *cs,
-                         int nvec, char **vec) {
-  VALIDATE_FILE(S_ISCHR, "character device");
-  return 0;
-}
-
 static int validate_player(const struct config_state *cs,
                           int nvec,
                           char attribute((unused)) **vec) {
@@ -781,16 +775,6 @@ static int validate_sample_format(const struct config_state *cs,
   return parse_sample_format(cs, 0, nvec, vec);
 }
 
-static int validate_channel(const struct config_state *cs,
-                           int attribute((unused)) nvec,
-                           char **vec) {
-  if(mixer_channel(vec[0]) == -1) {
-    error(0, "%s:%d: invalid channel '%s'", cs->path, cs->line, vec[0]);
-    return -1;
-  }
-  return 0;
-}
-
 static int validate_any(const struct config_state attribute((unused)) *cs,
                        int attribute((unused)) nvec,
                        char attribute((unused)) **vec) {
@@ -937,10 +921,11 @@ 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 },
+  { C(api),              &type_backend,          validate_any },
   { C(authorization_algorithm), &type_string,    validate_algo },
   { C(broadcast),        &type_stringlist,       validate_addrport },
   { C(broadcast_from),   &type_stringlist,       validate_addrport },
-  { C(channel),          &type_string,           validate_channel },
+  { C(channel),          &type_string,           validate_any },
   { C(checkpoint_kbyte), &type_integer,          validate_non_negative },
   { C(checkpoint_min),   &type_integer,          validate_non_negative },
   { C(collection),       &type_collections,      validate_any },
@@ -956,7 +941,7 @@ static const struct conf conf[] = {
   { C(listen),           &type_stringlist,       validate_port },
   { C(lock),             &type_boolean,          validate_any },
   { C(mail_sender),      &type_string,           validate_any },
-  { C(mixer),            &type_string,           validate_ischr },
+  { C(mixer),            &type_string,           validate_any },
   { C(multicast_loop),   &type_boolean,          validate_any },
   { C(multicast_ttl),    &type_integer,          validate_non_negative },
   { C(namepart),         &type_namepart,         validate_any },
@@ -978,7 +963,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(speaker_backend),  &type_backend,          validate_any },
+  { C2(speaker_backend, api),  &type_backend,          validate_any },
   { C(speaker_command),  &type_string,           validate_any },
   { C(stopword),         &type_string_accum,     validate_any },
   { C(templates),        &type_string_accum,     validate_isdir },
@@ -1165,14 +1150,14 @@ static struct config *config_default(void) {
   c->sample_format.channels = 2;
   c->sample_format.endian = ENDIAN_NATIVE;
   c->queue_pad = 10;
-  c->speaker_backend = -1;
+  c->api = -1;
   c->multicast_ttl = 1;
   c->multicast_loop = 1;
   c->authorization_algorithm = xstrdup("sha1");
   c->noticed_history = 31;
   c->short_display = 32;
-  c->mixer = xstrdup("/dev/mixer");
-  c->channel = xstrdup("pcm");
+  c->mixer = 0;
+  c->channel = 0;
   c->dbversion = 2;
   c->cookie_login_lifetime = 86400;
   c->cookie_key_lifetime = 86400 * 7;
@@ -1247,31 +1232,31 @@ static void config_postdefaults(struct config *c,
     for(n = 0; n < NTRANSFORM; ++n)
       set_transform(&cs, whoami, 5, (char **)transform[n]);
   }
-  if(c->speaker_backend == -1) {
+  if(c->api == -1) {
     if(c->speaker_command)
-      c->speaker_backend = BACKEND_COMMAND;
+      c->api = BACKEND_COMMAND;
     else if(c->broadcast.n)
-      c->speaker_backend = BACKEND_NETWORK;
+      c->api = BACKEND_NETWORK;
     else {
 #if HAVE_ALSA_ASOUNDLIB_H
-      c->speaker_backend = BACKEND_ALSA;
+      c->api = BACKEND_ALSA;
 #elif HAVE_SYS_SOUNDCARD_H
-      c->speaker_backend = BACKEND_OSS;
+      c->api = BACKEND_OSS;
 #elif HAVE_COREAUDIO_AUDIOHARDWARE_H
-      c->speaker_backend = BACKEND_COREAUDIO;
+      c->api = BACKEND_COREAUDIO;
 #else
-      c->speaker_backend = BACKEND_COMMAND;
+      c->api = BACKEND_COMMAND;
 #endif
     }
   }
   if(server) {
-    if(c->speaker_backend == BACKEND_COMMAND && !c->speaker_command)
-      fatal(0, "speaker_backend is command but speaker_command is not set");
-    if(c->speaker_backend == BACKEND_NETWORK && !c->broadcast.n)
-      fatal(0, "speaker_backend is network but broadcast is not set");
+    if(c->api == BACKEND_COMMAND && !c->speaker_command)
+      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");
   }
   /* Override sample format */
-  switch(c->speaker_backend) {
+  switch(c->api) {
   case BACKEND_NETWORK:
     c->sample_format.rate = 44100;
     c->sample_format.channels = 2;
@@ -1304,6 +1289,10 @@ static void config_postdefaults(struct config *c,
       r |= RIGHT_REMOVE_ANY;
     c->default_rights = rights_string(r);
   }
+  if(!c->mixer)
+    c->mixer = xstrdup(mixer_default_device(c->api));
+  if(!c->channel)
+    c->channel = xstrdup(mixer_default_channel(c->api));
 }
 
 /** @brief (Re-)read the config file
@@ -1346,6 +1335,15 @@ int config_read(int server) {
   config_postdefaults(c, server);
   /* everything is good so we shall use the new config */
   config_free(config);
+  /* final error checking */
+  if(c->mixer && !mixer_valid_device(c->api, c->mixer)) {
+    error(0, "invalid mixer device: %s", c->mixer);
+    return -1;
+  }
+  if(c->channel && !mixer_valid_channel(c->api, c->channel)) {
+    error(0, "invalid mixer channel: %s", c->channel);
+    return -1;
+  }
   /* warn about obsolete directives */
   if(c->restrictions)
     error(0, "'restrict' will be removed in a future version");
index d212a7ac80d017ba350561f2c614416875c011c4..f965bbd972a8f8232dc3f3b27311243387e88316 100644 (file)
@@ -177,12 +177,12 @@ struct config {
   /** @brief Sox syntax generation */
   long sox_generation;
 
-  /** @brief Speaker backend
+  /** @brief API used to play sound
    *
    * Choices are @ref BACKEND_ALSA, @ref BACKEND_COMMAND or @ref
    * BACKEND_NETWORK.
    */
-  int speaker_backend;
+  int api;
 #define BACKEND_ALSA 0                 /**< Use ALSA (Linux only) */
 #define BACKEND_COMMAND 1              /**< Execute a command */
 #define BACKEND_NETWORK 2              /**< Transmit RTP  */
diff --git a/lib/mixer-oss.c b/lib/mixer-oss.c
new file mode 100644 (file)
index 0000000..7ab73a2
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <config.h>
+#include "types.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <stddef.h>
+#include <sys/ioctl.h>
+
+#include "configuration.h"
+#include "mixer.h"
+#include "log.h"
+#include "syscalls.h"
+
+#if HAVE_SYS_SOUNDCARD_H
+#include <sys/soundcard.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
+
+static const char *channels[] = SOUND_DEVICE_NAMES;
+
+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;
+  }
+}
+
+static int oss_validate_channel(const char *c) {
+  if(mixer_channel(c) != -1)
+    return 1;
+  else
+    return 0;
+}
+
+static int oss_validate_device(const char *d) {
+  struct stat sb;
+
+  if(stat(d, &sb) < 0) {
+    error(errno, "%s", d);
+    return 0;
+  }
+  if(!S_ISCHR(sb.st_mode)) {
+    error(0, "%s: not a character device", d);
+    return 0;
+  }
+  return 1;
+}
+
+static int oss_do_open(void) {
+  int fd;
+  
+  if((fd = open(config->mixer, O_RDWR, 0)) < 0)
+    error(errno, "error opening %s", config->mixer);
+  return fd;
+}
+
+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;
+}
+
+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;
+}
+
+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;
+}
+
+const struct mixer mixer_oss = {
+  BACKEND_OSS,
+  oss_validate_device,
+  oss_validate_channel,
+  oss_get,
+  oss_set,
+  "/dev/mixer",
+  "pcm"
+};
+#endif
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+End:
+*/
index 4fd4a9a957f9cf3d4c275e36a5cc0a0fe388b803..f674d687096c57b640dcc5da0a173679ba5d0b9a 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder
- * Copyright (C) 2004 Richard Kettlewell
+ * 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
 #include "log.h"
 #include "syscalls.h"
 
+static const struct mixer *mixers[] = {
 #if HAVE_SYS_SOUNDCARD_H
-#include <sys/soundcard.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)
+  &mixer_oss
 #endif
+};
 
-static const char *channels[] = SOUND_DEVICE_NAMES;
-
-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;
-  }
+#define NMIXERS (sizeof mixers / sizeof *mixers)
+
+static const struct mixer *find_mixer(int api) {
+  size_t n;
+
+  for(n = 0; n < NMIXERS; ++n)
+    if(mixers[n]->api == api)
+      return mixers[n];
+  return 0;
+}
+
+int mixer_valid_channel(int api, const char *c) {
+  const struct mixer *const m = find_mixer(api);
+
+  return m ? m->validate_channel(c) : 1;
+}
+
+int mixer_valid_device(int api, const char *d) {
+  const struct mixer *const m = find_mixer(api);
+
+  return m ? m->validate_device(d) : 1;
 }
 
 int mixer_control(int *left, int *right, int set) {
-  int fd, ch, r;
-    
-  if(config->mixer
-     && config->channel
-     && (ch = mixer_channel(config->channel)) != -1) {
-    if((fd = open(config->mixer, O_RDWR, 0)) < 0) {
-      error(errno, "error opening %s", config->mixer);
-      return -1;
-    }
-    if(set) {
-      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(ioctl(fd, SOUND_MIXER_READ(ch), &r) == -1) {
-      error(errno, "error reading %s channel %s",
-           config->mixer, config->channel);
-      xclose(fd);
-      return -1;
+  const struct mixer *const m = find_mixer(config->api);
+
+  if(m) {
+    if(set)
+      return m->set(left, right);
+    else
+      return m->get(left, right);
+  } else {
+    static int reported;
+
+    if(!reported) {
+      error(0, "don't know how to get/set volume with this api");
+      reported = 1;
     }
-    *left = r & 0xff;
-    *right = (r >> 8) & 0xff;
-    xclose(fd);
-    return 0;
-  } else
     return -1;
+  }
 }
-#else
-int mixer_channel(const char attribute((unused)) *c) {
-  return 0;
+
+const char *mixer_default_device(int api) {
+  const struct mixer *const m = find_mixer(api);
+
+  return m ? m->device : "";
 }
 
-int mixer_control(int attribute((unused)) *left, 
-                 int attribute((unused)) *right,
-                 int attribute((unused)) set) {
-  static int reported;
+const char *mixer_default_channel(int api) {
+  const struct mixer *const m = find_mixer(api);
 
-  if(!reported) {
-    error(0, "don't know how to set volume on this platform");
-    reported = 1;
-  }
-  return -1;
+  return m ? m->channel : "";
 }
-#endif
 
 /*
 Local Variables:
index 07aab5d21fb1dae517a604a95455d9b68945075d..cfbbce934e71485904f98940473ed48ab45534de 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * This file is part of DisOrder
- * Copyright (C) 2004 Richard Kettlewell
+ * 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
 #ifndef MIXER_H
 #define MIXER_H
 
-int mixer_channel(const char *c);
-/* convert @c@ to a channel number, or return -1 if it does not match */
+/** @brief Definition of a mixer */
+struct mixer {
+  /** @brief API used by this mixer */
+  int api;
 
+  /** @brief Return non-0 iff @p d is a valid device name */
+  int (*validate_device)(const char *d);
+  
+  /** @brief Return non-0 iff @p c is a valid channel name */
+  int (*validate_channel)(const char *c);
+  
+  /** @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_valid_device(int api, const char *d);
+int mixer_valid_channel(int api, const char *c);
 int mixer_control(int *left, int *right, int set);
-/* get/set the current level.  If @set@ is true then the level is set
- * according to the pointers.  In either case the eventual level is
- * returned via the pointers.
- *
- * Returns 0 on success and -1 on error.
- */
+const char *mixer_default_device(int api);
+const char *mixer_default_channel(int api);
+
+extern const struct mixer mixer_oss;
 
 #endif /* MIXER_H */
 
index b680f2d49d0ed62af9af5b10d8b8b2ed1eb9ae9e..f0dfeb40d492ecbda12ce02573b893d825f3e90c 100644 (file)
@@ -989,7 +989,7 @@ static int c_new(struct conn *c,
 static int c_rtp_address(struct conn *c,
                         char attribute((unused)) **vec,
                         int attribute((unused)) nvec) {
-  if(config->speaker_backend == BACKEND_NETWORK) {
+  if(config->api == BACKEND_NETWORK) {
     sink_printf(ev_writer_sink(c->w), "252 %s %s\n",
                quoteutf8(config->broadcast.s[0]),
                quoteutf8(config->broadcast.s[1]));
index 8a3eb06cc91a897c39d4a8880c4d28c502ec19a5..ae43f120fd6513e9af92b988c166f452b7b86a8e 100644 (file)
@@ -635,10 +635,10 @@ int main(int argc, char **argv) {
   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->speaker_backend)
+    if(backends[n]->backend == config->api)
       break;
   if(!backends[n])
-    fatal(0, "unsupported backend %d", config->speaker_backend);
+    fatal(0, "unsupported api %d", config->api);
   backend = backends[n];
   /* backend-specific initialization */
   backend->init();
index bca2b4af5719a53927a236c1f51fd9d3064c7a72..982f4d499d07a9ff44305659886da51bd558e329 100644 (file)
@@ -191,7 +191,7 @@ tracklength *.mp3 disorder-tracklength
 tracklength *.ogg disorder-tracklength
 tracklength *.wav disorder-tracklength
 tracklength *.flac disorder-tracklength
-speaker_backend network
+api network
 broadcast 127.0.0.1 %d
 broadcast_from 127.0.0.1 %d
 mail_sender no.such.user.sorry@greenend.org.uk