chiark / gitweb /
ALSA mixer support.
authorrjk@greenend.org.uk <>
Sun, 30 Dec 2007 20:13:20 +0000 (20:13 +0000)
committerrjk@greenend.org.uk <>
Sun, 30 Dec 2007 20:13:20 +0000 (20:13 +0000)
Mixer configuration is no longer checked when reading the config file,
as with the current design this would force every DisOrder program to
link against all the sound libraries.

disobedience/Makefile.am
doc/disorder_config.5.in
lib/Makefile.am
lib/configuration.c
lib/mixer-alsa.c [new file with mode: 0644]
lib/mixer-oss.c
lib/mixer.c
lib/mixer.h
server/Makefile.am

index bcab45a90026b5c22abfc31a97bd9ddac8e9367f..93a280a36d1882d1a1006739c0a33f2831f092c5 100644 (file)
@@ -29,7 +29,8 @@ disobedience_SOURCES=disobedience.h disobedience.c client.c queue.c   \
                  choose.c misc.c control.c properties.c menu.c \
                  log.c progress.c login.c rtp.c help.c \
                  ../lib/memgc.c settings.c
-disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT)
+disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) \
+       $(LIBASOUND) $(COREAUDIO)
 disobedience_LDFLAGS=$(GTK_LIBS)
 
 install-exec-hook:
index a57a33708b2d46bf989f4215efb381aecb690692..46523699a39c0f77901618f86072e75fcaa6a2bc 100644 (file)
@@ -313,7 +313,8 @@ it affects all output devices.
 .IP
 You can also specify channels by number, if you know the right value.
 .IP
-For \fBapi alsa\fR, volume setting is not currently supported.
+For \fBapi alsa\fR, this is the name of the mixer control to use.  The default
+is \fBPCM\fR.  Use \fBamixer scontrols\fR or similar to get a full list.
 .IP
 For \fBapi coreaudio\fR, volume setting is not currently supported.
 .TP
@@ -361,7 +362,8 @@ 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.
+The default is \fBdefault\fR, which is intended to map to whatever the system's
+default is.
 .TP
 .B gap \fISECONDS\fR
 Specifies the number of seconds to leave between tracks.  The default
@@ -390,7 +392,8 @@ The mixer device name, if it needs to be specified separately from
 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.
+For \fBapi alsa\fR, this is the index of the mixer control to use.  The default
+is 0.
 .IP
 For \fBapi coreaudio\fR, volume setting is not currently supported.
 .TP
index 3f526e63e8392dd20b69d210c14858e45f392da8..3c3a96d07ddc357347ac6bdddc68ea4159786636 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-oss.c                     \
+       mixer.c mixer.h mixer-oss.c mixer-alsa.c        \
        plugin.c plugin.h                               \
        printf.c printf.h                               \
        asprintf.c fprintf.c snprintf.c                 \
index db37837115f68efb6dbc053aea8d55700f553e4e..90ae0586c51461d63277a41f34a8de8c9cb654ed 100644 (file)
@@ -1289,10 +1289,6 @@ 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
@@ -1335,15 +1331,6 @@ 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");
diff --git a/lib/mixer-alsa.c b/lib/mixer-alsa.c
new file mode 100644 (file)
index 0000000..7cf8878
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+ * 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 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
+ */
+/** @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 <config.h>
+
+#if HAVE_ALSA_ASOUNDLIB_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 <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, "snd_mixer_find_selem returned NULL");
+    goto error;
+  }
+  if(!snd_mixer_selem_has_playback_volume(h->elem)) {
+    error(0, "configured mixer control has no playback volume");
+    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, "configured mixer control lacks required playback channels");
+    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:
+*/
index 7ab73a2f10ca616549533ce2a30b00986dc45c9b..7ce57be881b02ad1e3037a689f6c6fc72cf65610 100644 (file)
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  * USA
  */
+/** @file lib/mixer-oss.c
+ * @brief OSS mixer support
+ *
+ * Mono output devices aren't explicitly supported (but may work
+ * nonetheless).
+ */
 
 #include <config.h>
+
+#if HAVE_SYS_SOUNDCARD_H
+
 #include "types.h"
 
 #include <stdio.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"
 
-#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)
 # 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;
   
@@ -61,27 +70,7 @@ static int mixer_channel(const char *c) {
   }
 }
 
-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;
-}
-
+/** @brief Open the OSS mixer device and return its fd */
 static int oss_do_open(void) {
   int fd;
   
@@ -90,6 +79,7 @@ static int oss_do_open(void) {
   return fd;
 }
 
+/** @brief Get the OSS mixer setting */
 static int oss_do_get(int *left, int *right, int fd, int ch) {
   int r;
   
@@ -103,6 +93,7 @@ static int oss_do_get(int *left, int *right, int fd, int ch) {
   return 0;
 }
 
+/** @brief Get OSS volume */
 static int oss_get(int *left, int *right) {
   int ch, fd;
 
@@ -121,6 +112,7 @@ static int oss_get(int *left, int *right) {
     return -1;
 }
 
+/** @brief Set OSS volume */
 static int oss_set(int *left, int *right) {
   int ch, fd, r;
 
@@ -146,10 +138,9 @@ static int oss_set(int *left, int *right) {
     return -1;
 }
 
+/** @brief OSS mixer vtable */
 const struct mixer mixer_oss = {
   BACKEND_OSS,
-  oss_validate_device,
-  oss_validate_channel,
   oss_get,
   oss_set,
   "/dev/mixer",
index f674d687096c57b640dcc5da0a173679ba5d0b9a..58d9e60632aeef4daaa5d8c6aec2f9e467da2bba 100644 (file)
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  * USA
  */
+/** @file lib/mixer.c
+ * @brief Mixer support
+ */
 
 #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"
+#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
+  &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;
 
   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;
+  return &mixer_none;
 }
 
+/** @brief Get/set volume
+ * @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 *left, int *right, int set) {
   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;
-    }
-    return -1;
-  }
-}
-
-const char *mixer_default_device(int api) {
-  const struct mixer *const m = find_mixer(api);
-
-  return m ? m->device : "";
-}
-
-const char *mixer_default_channel(int api) {
-  const struct mixer *const m = find_mixer(api);
-
-  return m ? m->channel : "";
+  /* 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);
 }
 
 /*
index cfbbce934e71485904f98940473ed48ab45534de..6739ed82a31962d386235350192af68f7d9b2826 100644 (file)
@@ -17,6 +17,9 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  * USA
  */
+/** @file lib/mixer.h
+ * @brief Mixer support
+ */
 
 #ifndef MIXER_H
 #define MIXER_H
@@ -26,12 +29,6 @@ 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
@@ -55,13 +52,12 @@ struct mixer {
   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);
 const char *mixer_default_device(int api);
 const char *mixer_default_channel(int api);
 
 extern const struct mixer mixer_oss;
+extern const struct mixer mixer_alsa;
 
 #endif /* MIXER_H */
 
index 5d38b56b244159e47133d1b0af8771afa45bda55..eca95c4ff8a9637b1afe2f408b8deaee129d8123 100644 (file)
@@ -36,7 +36,8 @@ disorderd_SOURCES=disorderd.c                         \
        exports.c                                       \
        ../lib/memgc.c
 disorderd_LDADD=$(LIBOBJS) ../lib/libdisorder.a \
-       $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV)
+       $(LIBPCRE) $(LIBDB) $(LIBAO) $(LIBGC) $(LIBGCRYPT) $(LIBICONV) \
+       $(LIBASOUND) $(COREAUDIO)
 disorderd_LDFLAGS=-export-dynamic
 disorderd_DEPENDENCIES=../lib/libdisorder.a