From: rjk@greenend.org.uk <> Date: Sun, 30 Dec 2007 20:13:20 +0000 (+0000) Subject: ALSA mixer support. X-Git-Tag: 3.0~133 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~mdw/git/disorder/commitdiff_plain/b25aac593c0321473525a3e5b12f406bd3961aec ALSA mixer support. 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. --- 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