/*
* This file is part of DisOrder
* Copyright (C) 2007, 2008 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 3 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, see .
*/
/** @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 "common.h"
#if HAVE_ALSA_ASOUNDLIB_H
#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, "device '%s' mixer control '%s,%s' does not exist",
config->device, config->channel, config->mixer);
goto error;
}
if(!snd_mixer_selem_has_playback_volume(h->elem)) {
error(0, "device '%s' mixer control '%s,%s' has no playback volume",
config->device, config->channel, config->mixer);
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, "device '%s' mixer control '%s,%s' lacks required playback channels",
config->device, config->channel, config->mixer);
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:
*/