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 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * 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, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20 /** @file lib/mixer-alsa.c
21 * @brief ALSA mixer support
23 * The documentation for ALSA's mixer support is completely hopeless,
24 * which is a particular nuisnace given it's got an incredibly verbose
25 * API. Much of this code is cribbed from
26 * alsa-utils-1.0.13/amixer/amixer.c.
28 * Mono output devices are supported, but the support is not tested
34 #if HAVE_ALSA_ASOUNDLIB_H
46 #include <sys/ioctl.h>
47 #include <alsa/asoundlib.h>
49 #include "configuration.h"
54 /** @brief Shared state for ALSA mixer support */
55 struct alsa_mixer_state
{
56 /** @brief Mixer handle */
59 /** @brief Mixer control */
60 snd_mixer_elem_t
*elem
;
62 /** @brief Left channel */
63 snd_mixer_selem_channel_id_t left
;
65 /** @brief Right channel */
66 snd_mixer_selem_channel_id_t right
;
68 /** @brief Minimum level */
71 /** @brief Maximum level */
75 /** @brief Destroy a @ref alsa_mixer_state */
76 static void alsa_close(struct alsa_mixer_state
*h
) {
79 snd_mixer_close(h
->handle
);
82 /** @brief Initialize a @ref alsa_mixer_state */
83 static int alsa_open(struct alsa_mixer_state
*h
) {
85 snd_mixer_selem_id_t
*id
;
87 snd_mixer_selem_id_alloca(&id
);
88 memset(h
, 0, sizeof h
);
89 if((err
= snd_mixer_open(&h
->handle
, 0))) {
90 error(0, "snd_mixer_open: %s", snd_strerror(err
));
93 if((err
= snd_mixer_attach(h
->handle
, config
->device
))) {
94 error(0, "snd_mixer_attach %s: %s",
95 config
->device
, snd_strerror(err
));
98 if((err
= snd_mixer_selem_register(h
->handle
, 0/*options*/, 0/*classp*/))) {
99 error(0, "snd_mixer_selem_register %s: %s",
100 config
->device
, snd_strerror(err
));
103 if((err
= snd_mixer_load(h
->handle
))) {
104 error(0, "snd_mixer_load %s: %s",
105 config
->device
, snd_strerror(err
));
108 snd_mixer_selem_id_set_name(id
, config
->channel
);
109 snd_mixer_selem_id_set_index(id
, atoi(config
->mixer
));
110 if(!(h
->elem
= snd_mixer_find_selem(h
->handle
, id
))) {
111 error(0, "device '%s' mixer control '%s,%s' does not exist",
112 config
->device
, config
->channel
, config
->mixer
);
115 if(!snd_mixer_selem_has_playback_volume(h
->elem
)) {
116 error(0, "device '%s' mixer control '%s,%s' has no playback volume",
117 config
->device
, config
->channel
, config
->mixer
);
120 if(snd_mixer_selem_is_playback_mono(h
->elem
)) {
121 h
->left
= h
->right
= SND_MIXER_SCHN_MONO
;
123 h
->left
= SND_MIXER_SCHN_FRONT_LEFT
;
124 h
->right
= SND_MIXER_SCHN_FRONT_RIGHT
;
126 if(!snd_mixer_selem_has_playback_channel(h
->elem
, h
->left
)
127 || !snd_mixer_selem_has_playback_channel(h
->elem
, h
->right
)) {
128 error(0, "device '%s' mixer control '%s,%s' lacks required playback channels",
129 config
->device
, config
->channel
, config
->mixer
);
132 snd_mixer_selem_get_playback_volume_range(h
->elem
, &h
->min
, &h
->max
);
139 /** @brief Convert a level to a percentage */
140 static int to_percent(const struct alsa_mixer_state
*h
, long n
) {
141 return (n
- h
->min
) * 100 / (h
->max
- h
->min
);
144 /** @brief Get ALSA volume */
145 static int alsa_get(int *left
, int *right
) {
146 struct alsa_mixer_state h
[1];
152 if((err
= snd_mixer_selem_get_playback_volume(h
->elem
, h
->left
, &l
))
153 || (err
= snd_mixer_selem_get_playback_volume(h
->elem
, h
->right
, &r
))) {
154 error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err
));
157 *left
= to_percent(h
, l
);
158 *right
= to_percent(h
, r
);
166 /** @brief Convert a percentage to a level */
167 static int from_percent(const struct alsa_mixer_state
*h
, int n
) {
168 return h
->min
+ n
* (h
->max
- h
->min
) / 100;
171 /** @brief Set ALSA volume */
172 static int alsa_set(int *left
, int *right
) {
173 struct alsa_mixer_state h
[1];
180 if(h
->left
== h
->right
) {
181 /* Mono output - just use the loudest */
182 if((err
= snd_mixer_selem_set_playback_volume
184 from_percent(h
, *left
> *right ?
*left
: *right
)))) {
185 error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err
));
190 if((err
= snd_mixer_selem_set_playback_volume
191 (h
->elem
, h
->left
, from_percent(h
, *left
)))
192 || (err
= snd_mixer_selem_set_playback_volume
193 (h
->elem
, h
->right
, from_percent(h
, *right
)))) {
194 error(0, "snd_mixer_selem_set_playback_volume: %s", snd_strerror(err
));
198 /* Read it back to see what we ended up at */
199 if((err
= snd_mixer_selem_get_playback_volume(h
->elem
, h
->left
, &l
))
200 || (err
= snd_mixer_selem_get_playback_volume(h
->elem
, h
->right
, &r
))) {
201 error(0, "snd_mixer_selem_get_playback_volume: %s", snd_strerror(err
));
204 *left
= to_percent(h
, l
);
205 *right
= to_percent(h
, r
);
213 /** @brief ALSA mixer vtable */
214 const struct mixer mixer_alsa
= {