From b25aac593c0321473525a3e5b12f406bd3961aec Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sun, 30 Dec 2007 20:13:20 +0000 Subject: [PATCH] ALSA mixer support. Organization: Straylight/Edgeware From: rjk@greenend.org.uk <> 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 | 3 +- doc/disorder_config.5.in | 9 +- lib/Makefile.am | 2 +- lib/configuration.c | 13 --- lib/mixer-alsa.c | 225 +++++++++++++++++++++++++++++++++++++++ lib/mixer-oss.c | 43 +++----- lib/mixer.c | 106 +++++++++--------- lib/mixer.h | 12 +-- server/Makefile.am | 3 +- 9 files changed, 313 insertions(+), 103 deletions(-) create mode 100644 lib/mixer-alsa.c diff --git a/disobedience/Makefile.am b/disobedience/Makefile.am index bcab45a..93a280a 100644 --- a/disobedience/Makefile.am +++ b/disobedience/Makefile.am @@ -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: diff --git a/doc/disorder_config.5.in b/doc/disorder_config.5.in index a57a337..4652369 100644 --- a/doc/disorder_config.5.in +++ b/doc/disorder_config.5.in @@ -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 diff --git a/lib/Makefile.am b/lib/Makefile.am index 3f526e6..3c3a96d 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -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 \ diff --git a/lib/configuration.c b/lib/configuration.c index db37837..90ae058 100644 --- a/lib/configuration.c +++ b/lib/configuration.c @@ -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 index 0000000..7cf8878 --- /dev/null +++ b/lib/mixer-alsa.c @@ -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 + +#if HAVE_ALSA_ASOUNDLIB_H + +#include "types.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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: +*/ diff --git a/lib/mixer-oss.c b/lib/mixer-oss.c index 7ab73a2..7ce57be 100644 --- a/lib/mixer-oss.c +++ b/lib/mixer-oss.c @@ -17,8 +17,17 @@ * 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 + +#if HAVE_SYS_SOUNDCARD_H + #include "types.h" #include @@ -29,15 +38,13 @@ #include #include #include +#include #include "configuration.h" #include "mixer.h" #include "log.h" #include "syscalls.h" -#if HAVE_SYS_SOUNDCARD_H -#include - /* documentation does not match implementation! */ #ifndef SOUND_MIXER_READ # define SOUND_MIXER_READ(x) MIXER_READ(x) @@ -46,8 +53,10 @@ # 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", diff --git a/lib/mixer.c b/lib/mixer.c index f674d68..58d9e60 100644 --- a/lib/mixer.c +++ b/lib/mixer.c @@ -17,82 +17,88 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ +/** @file lib/mixer.c + * @brief Mixer support + */ #include #include "types.h" -#include -#include -#include -#include -#include -#include -#include -#include - #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); } /* diff --git a/lib/mixer.h b/lib/mixer.h index cfbbce9..6739ed8 100644 --- a/lib/mixer.h +++ b/lib/mixer.h @@ -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 */ diff --git a/server/Makefile.am b/server/Makefile.am index 5d38b56..eca95c4 100644 --- a/server/Makefile.am +++ b/server/Makefile.am @@ -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 -- [mdw]