From b4171bad405e7a33e13479c227df5eaade391fc0 Mon Sep 17 00:00:00 2001 From: Andre Noll Date: Mon, 16 Jul 2012 21:25:51 +0200 Subject: [PATCH 1/1] fade: Add the ALSA mixer implementation. This provides another mixer API which relies on the libasound library of ALSA. The manual is changed accordingly to mention that para_fade now supports both OSS and ALSA. --- alsa_mix.c | 224 +++++++++++++++++++++++++++++++++++++++++++ configure.ac | 10 +- error.h | 5 + m4/gengetopt/fade.m4 | 23 +++-- web/manual.m4 | 2 +- 5 files changed, 253 insertions(+), 11 deletions(-) create mode 100644 alsa_mix.c diff --git a/alsa_mix.c b/alsa_mix.c new file mode 100644 index 00000000..5a583546 --- /dev/null +++ b/alsa_mix.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2012 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file alsa_mix.c The ALSA mixer plugin. */ + +#include +#include + +#include "para.h" +#include "error.h" +#include "mix.h" +#include "string.h" + +struct mixer_handle { + /* The ALSA mixer handle */ + snd_mixer_t *mixer; + /* Copy of the alsa device name, e.g. "hw:0". */ + char *card; + /* ALSA's representation of the given mixer channel. */ + snd_mixer_elem_t *elem; + /* Set in ->set_channel(), used for ->get() and ->set(). */ + long pmin, pmax; +}; + +static void alsa_mix_close(struct mixer_handle **handle) +{ + struct mixer_handle *h; + + if (!handle) + return; + h = *handle; + if (h) { + PARA_INFO_LOG("closing mixer handle\n"); + if (h->mixer) /* nec. */ + snd_mixer_close(h->mixer); + free(h->card); + free(h); + /* + * The global ALSA configuration is cached for next usage, + * which causes valgrind to report many memory leaks. Calling + * snd_config_update_free_global() frees the cache. + */ + snd_config_update_free_global(); + } + *handle = NULL; +} + +static int alsa_mix_open(const char *dev, struct mixer_handle **handle) +{ + int ret; + char *msg; + struct mixer_handle *h; + + PARA_INFO_LOG("snd_mixer_{open,attach,register,load}\n"); + *handle = NULL; + h = para_calloc(sizeof(*h)); + h->card = para_strdup(dev? dev : "hw:0"); + ret = snd_mixer_open(&h->mixer, 0); + if (ret < 0) { + msg = make_message("snd_mixer_open() failed: %s", + snd_strerror(ret)); + goto fail; + } + ret = snd_mixer_attach(h->mixer, h->card); + if (ret < 0) { + msg = make_message("mixer attach error (%s): %s", h->card, + snd_strerror(ret)); + goto fail; + } + ret = snd_mixer_selem_register(h->mixer, NULL, NULL); + if (ret < 0) { + msg = make_message("mixer register error (%s): %s", h->card, + snd_strerror(ret)); + goto fail; + } + ret = snd_mixer_load(h->mixer); + if (ret < 0) { + msg = make_message("mixer load error (%s): %s", h->card, + snd_strerror(ret)); + goto fail; + } + /* success */ + *handle = h; + return 1; +fail: + PARA_NOTICE_LOG("%s\n", msg); + free(msg); + alsa_mix_close(&h); + return -E_ALSA_MIX_OPEN; +} + +static bool channel_has_playback(snd_mixer_selem_channel_id_t chn, + snd_mixer_elem_t *elem) +{ + return snd_mixer_selem_has_playback_channel(elem, chn); +} + +static char *alsa_mix_get_channels(struct mixer_handle *handle) +{ + char *list = NULL; + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *elem; + + snd_mixer_selem_id_alloca(&sid); + elem = snd_mixer_first_elem(handle->mixer); + for (; elem; elem = snd_mixer_elem_next(elem)) { + char *tmp = list; + const char *name; + + if (!channel_has_playback(0, elem)) + continue; + if (!snd_mixer_selem_has_playback_volume(elem)) + continue; + snd_mixer_selem_get_id(elem, sid); + name = snd_mixer_selem_id_get_name(sid); + list = make_message("%s%s%s", + list? list : "", + list? ", " : "", + name); + free(tmp); + } + return list; +} + +static int alsa_mix_set_channel(struct mixer_handle *h, + const char *mixer_channel) +{ + int ret, selem_id = 0; + snd_mixer_selem_id_t *sid; + + if (!mixer_channel) + mixer_channel = "Master"; + snd_mixer_selem_id_alloca(&sid); + snd_mixer_selem_id_set_index(sid, selem_id); + snd_mixer_selem_id_set_name(sid, mixer_channel); + h->elem = snd_mixer_find_selem(h->mixer, sid); + if (!h->elem) { + PARA_NOTICE_LOG("unable to find simple control '%s',%i\n", + snd_mixer_selem_id_get_name(sid), + snd_mixer_selem_id_get_index(sid)); + return -E_ALSA_MIX_BAD_ELEM; + } + ret = snd_mixer_selem_get_playback_volume_range(h->elem, + &h->pmin, &h->pmax); + if (ret < 0) { + PARA_NOTICE_LOG("unable to get %s range (%s): %s\n", + mixer_channel, h->card, snd_strerror(ret)); + return -E_ALSA_MIX_BAD_ELEM; + } + if (h->pmin < 0 || h->pmax < 0 || h->pmin >= h->pmax) { + PARA_NOTICE_LOG("alsa reported %s range %ld-%ld (%s)\n", + mixer_channel, h->pmin, h->pmax, h->card); + return -E_ALSA_MIX_BAD_ELEM; + } + return 1; +} + +static int alsa_mix_get(struct mixer_handle *h) +{ + snd_mixer_selem_channel_id_t chn; + int n = 0; + float avg = 0; + + /* compute average over all channels */ + for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { + long val; + int ret; + + if (!channel_has_playback(chn, h->elem)) + continue; + ret = snd_mixer_selem_get_playback_volume(h->elem, chn, &val); + if (ret < 0) { + PARA_NOTICE_LOG("unable to get value for channel %d (%s): %s\n", + (int)chn, h->card, snd_strerror(ret)); + return -E_ALSA_MIX_GET_VAL; + } + /* update the rolling average */ + avg = (val + n * avg) / (n + 1); + n++; + } + /* scale to 0..100 */ + avg = 100 * (avg - h->pmin) / (h->pmax - h->pmin); + return avg; +} + +static int alsa_mix_set(struct mixer_handle *h, int val) +{ + int ret; + snd_mixer_selem_channel_id_t chn; + long scaled = val / 100.0 * (h->pmax - h->pmin) + h->pmin; + + PARA_INFO_LOG("new value: %d\n", val); + for (chn = 0; chn <= SND_MIXER_SCHN_LAST; chn++) { + if (!channel_has_playback(chn, h->elem)) + continue; + ret = snd_mixer_selem_set_playback_volume(h->elem, chn, scaled); + if (ret < 0) { + PARA_NOTICE_LOG("unable to set value for channel %d (%s): %s\n", + (int)chn, h->card, snd_strerror(ret)); + return -E_ALSA_MIX_SET_VAL; + } + } + return 1; +} + +/** + * The init function of the ALSA mixer. + * + * \param self The structure to initialize. + * + * \sa struct \ref mixer, \ref oss_mix_init(). + */ +void alsa_mix_init(struct mixer *self) +{ + self->open = alsa_mix_open; + self->get_channels = alsa_mix_get_channels; + self->set_channel = alsa_mix_set_channel; + self->close = alsa_mix_close; + self->get = alsa_mix_get; + self->set = alsa_mix_set; +} diff --git a/configure.ac b/configure.ac index b6010490..93b60e64 100644 --- a/configure.ac +++ b/configure.ac @@ -861,16 +861,20 @@ if test "$have_alsa" = "yes"; then fi if test "$have_alsa" = "yes"; then - all_errlist_objs="$all_errlist_objs alsa_write" audiod_errlist_objs="$audiod_errlist_objs alsa_write" audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(alsa_write)" audiod_ldflags="$audiod_ldflags -lasound" - write_errlist_objs="$write_errlist_objs alsa_write" write_cmdline_objs="$write_cmdline_objs add_cmdline(alsa_write)" write_ldflags="$write_ldflags -lasound" + fade_errlist_objs="$fade_errlist_objs alsa_mix" + fade_ldflags="$fade_ldflags -lasound" + all_errlist_objs="$all_errlist_objs alsa_write alsa_mix" + writers="$writers alsa" default_writer="ALSA_WRITE" + mixers="${mixers}alsa " + default_mixer="ALSA_MIX" fi CPPFLAGS="$OLD_CPPFLAGS" @@ -909,7 +913,7 @@ if test -n "$mixers"; then init functions of the supported mixers) array="$(for i in $mixers; do printf '{.init = '$i'_mix_init},'; done)" AC_DEFINE_UNQUOTED(MIXER_ARRAY, $array, array of supported mixers) - mixer_summary="mixers supported by para_fade: $mixers" + mixer_summary="supported mixers:: $mixers, default: $default_mixer" else AC_MSG_WARN([no mixer support]) mixer_summary="para_fade: no" diff --git a/error.h b/error.h index c7c57760..8579bd8d 100644 --- a/error.h +++ b/error.h @@ -39,6 +39,11 @@ extern const char **para_errlist[]; #define OSS_MIX_ERRORS \ PARA_ERROR(OSS_MIXER_CHANNEL, "invalid mixer channel"), \ +#define ALSA_MIX_ERRORS \ + PARA_ERROR(ALSA_MIX_OPEN, "could not open mixer"), \ + PARA_ERROR(ALSA_MIX_BAD_ELEM, "invalid/unsupported control element"), \ + PARA_ERROR(ALSA_MIX_GET_VAL, "could not read control element state"), \ + PARA_ERROR(ALSA_MIX_SET_VAL, "could not set control element state"), \ #define SIDEBAND_ERRORS \ PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \ diff --git a/m4/gengetopt/fade.m4 b/m4/gengetopt/fade.m4 index e5d08bf0..42f135fa 100644 --- a/m4/gengetopt/fade.m4 +++ b/m4/gengetopt/fade.m4 @@ -38,8 +38,10 @@ option "mixer-device" m string typestr = "device" optional details = " - If this option is not given '/dev/mixer' is used as - the default. + The default device (used if this option is not given) + depends on the selected mixer API. For ALSA, the + default is 'hw:0' which corresponds to the first sound + device. For OSS, '/dev/mixer' is used as the default. " option "mixer-channel" C @@ -48,11 +50,18 @@ option "mixer-channel" C string typestr = "channel" optional details = " - The possible values are 'volume', 'bass', 'treble', - 'synth', 'pcm', 'speaker', 'line', 'mic', 'cd', 'imix', - 'altpcm', 'reclev', 'igain', 'ogain'. However, not all - listed channels might be supported on any particular - hardware. The default mixer channel is 'volume'. + For the ALSA mixer API, the possible values are + determined at runtime depending on the hardware and + can be printed by specifying an invalid mixer channel, + for example --mixer-channel help. The default channel + is 'Master'. + + For OSS the possible values are invariably 'volume', + 'bass', 'treble', 'synth', 'pcm', 'speaker', 'line', + 'mic', 'cd', 'imix', 'altpcm', 'reclev', 'igain', + 'ogain'. However, not all listed channels might be + supported on any particular hardware. The default + channel is 'volume'. " section "Options for sleep mode" diff --git a/web/manual.m4 b/web/manual.m4 index 5a348bb9..56ee3469 100644 --- a/web/manual.m4 +++ b/web/manual.m4 @@ -183,7 +183,7 @@ can be added easily. *para_fade* -An (OSS-only) alarm clock and volume-fader. +An alarm clock and volume-fader for OSS and ALSA. ----------- Quick start -- 2.39.2