2 * This file is part of DisOrder
3 * Copyright (C) 2007, 2008 Richard Kettlewell
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 /** @file lib/mixer-alsa.c
19 * @brief ALSA mixer support
21 * The documentation for ALSA's mixer support is completely hopeless,
22 * which is a particular nuisnace given it's got an incredibly verbose
23 * API. Much of this code is cribbed from
24 * alsa-utils-1.0.13/amixer/amixer.c.
26 * Mono output devices are supported, but the support is not tested
32 #if HAVE_ALSA_ASOUNDLIB_H
39 #include <sys/ioctl.h>
40 #include <alsa/asoundlib.h>
42 #include "configuration.h"
47 /** @brief Shared state for ALSA mixer support */
48 struct alsa_mixer_state {
49 /** @brief Mixer handle */
52 /** @brief Mixer control */
53 snd_mixer_elem_t *elem;
55 /** @brief Left channel */
56 snd_mixer_selem_channel_id_t left;
58 /** @brief Right channel */
59 snd_mixer_selem_channel_id_t right;
61 /** @brief Minimum level */
64 /** @brief Maximum level */
68 /** @brief Destroy a @ref alsa_mixer_state */
69 static void alsa_close(struct alsa_mixer_state *h) {
72 snd_mixer_close(h->handle);
75 /** @brief Initialize a @ref alsa_mixer_state */
76 static int alsa_open(struct alsa_mixer_state *h) {
78 snd_mixer_selem_id_t *id;
80 snd_mixer_selem_id_alloca(&id);
81 memset(h, 0, sizeof h);
82 if((err = snd_mixer_open(&h->handle, 0))) {
83 error(0, "snd_mixer_open: %s", snd_strerror(err));
86 if((err = snd_mixer_attach(h->handle, config->device))) {
87 error(0, "snd_mixer_attach %s: %s",
88 config->device, snd_strerror(err));
91 if((err = snd_mixer_selem_register(h->handle, 0/*options*/, 0/*classp*/))) {
92 error(0, "snd_mixer_selem_register %s: %s",
93 config->device, snd_strerror(err));
96 if((err = snd_mixer_load(h->handle))) {
97 error(0, "snd_mixer_load %s: %s",
98 config->device, snd_strerror(err));
101 snd_mixer_selem_id_set_name(id, config->channel);
102 snd_mixer_selem_id_set_index(id, atoi(config->mixer));
103 if(!(h->elem = snd_mixer_find_selem(h->handle, id))) {
104 error(0, "device '%s' mixer control '%s,%s' does not exist",
105 config->device, config->channel, config->mixer);
108 if(!snd_mixer_selem_has_playback_volume(h->elem)) {
109 error(0, "device '%s' mixer control '%s,%s' has no playback volume",
110 config->device, config->channel, config->mixer);
113 if(snd_mixer_selem_is_playback_mono(h->elem)) {
114 h->left = h->right = SND_MIXER_SCHN_MONO;
116 h->left = SND_MIXER_SCHN_FRONT_LEFT;
117 h->right = SND_MIXER_SCHN_FRONT_RIGHT;
119 if(!snd_mixer_selem_has_playback_channel(h->elem, h->left)
120 || !snd_mixer_selem_has_playback_channel(h->elem, h->right)) {
121 error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
122 config->device, config->channel, config->mixer);
125 snd_mixer_selem_get_playback_volume_range(h->elem, &h->min, &h->max);
132 /** @brief Convert a level to a percentage */
133 static int to_percent(const struct alsa_mixer_state *h, long n) {
134 return (n - h->min) * 100 / (h->max - h->min);
137 /** @brief Get ALSA volume */
138 static int alsa_get(int *left, int *right) {
139 struct alsa_mixer_state h[1];
145 if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
146 || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
147 error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
150 *left = to_percent(h, l);
151 *right = to_percent(h, r);
159 /** @brief Convert a percentage to a level */
160 static int from_percent(const struct alsa_mixer_state *h, int n) {
161 return h->min + n * (h->max - h->min) / 100;
164 /** @brief Set ALSA volume */
165 static int alsa_set(int *left, int *right) {
166 struct alsa_mixer_state h[1];
173 if(h->left == h->right) {
174 /* Mono output - just use the loudest */
175 if((err = snd_mixer_selem_set_playback_volume
177 from_percent(h, *left > *right ? *left : *right)))) {
178 error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
183 if((err = snd_mixer_selem_set_playback_volume
184 (h->elem, h->left, from_percent(h, *left)))
185 || (err = snd_mixer_selem_set_playback_volume
186 (h->elem, h->right, from_percent(h, *right)))) {
187 error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err));
191 /* Read it back to see what we ended up at */
192 if((err = snd_mixer_selem_get_playback_volume(h->elem, h->left, &l))
193 || (err = snd_mixer_selem_get_playback_volume(h->elem, h->right, &r))) {
194 error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err));
197 *left = to_percent(h, l);
198 *right = to_percent(h, r);
206 /** @brief ALSA mixer vtable */
207 const struct mixer mixer_alsa = {