--- /dev/null
+/*
+ * Copyright (C) 2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file alsa_mix.c The ALSA mixer plugin. */
+
+#include <regex.h>
+#include <alsa/asoundlib.h>
+
+#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;
+}
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
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"