1
/*
2
 * Copyright 2008-2011 Various Authors
3
 * Copyright 2004-2005 Timo Hirvonen
4
 *
5
 * This program is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU General Public License as
7
 * published by the Free Software Foundation; either version 2 of the
8
 * License, or (at your option) any later version.
9
 *
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.
14
 *
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/>.
17
 */
18
19
#include "mixer.h"
20
#include "op.h"
21
#include "xmalloc.h"
22
#include "debug.h"
23
24
#include <strings.h>
25
26
#define ALSA_PCM_NEW_HW_PARAMS_API
27
#define ALSA_PCM_NEW_SW_PARAMS_API
28
29
#include <alsa/asoundlib.h>
30
31
static snd_mixer_t *alsa_mixer_handle;
32
static snd_mixer_elem_t *mixer_elem = NULL;
33
static long mixer_vol_min, mixer_vol_max;
34
35
/* configuration */
36
static char *alsa_mixer_device = NULL;
37
static char *alsa_mixer_element = NULL;
38
39
static int alsa_mixer_init(void)
40
{
41
	if (alsa_mixer_device == NULL)
42
		alsa_mixer_device = xstrdup("default");
43
	if (alsa_mixer_element == NULL)
44
		alsa_mixer_element = xstrdup("PCM");
45
	/* FIXME: check device */
46
	return 0;
47
}
48
49
static int alsa_mixer_exit(void)
50
{
51
	free(alsa_mixer_device);
52
	alsa_mixer_device = NULL;
53
	free(alsa_mixer_element);
54
	alsa_mixer_element = NULL;
55
	return 0;
56
}
57
58
static snd_mixer_elem_t *find_mixer_elem_by_name(const char *goal_name)
59
{
60
	snd_mixer_elem_t *elem;
61
	snd_mixer_selem_id_t *sid = NULL;
62
63
	snd_mixer_selem_id_malloc(&sid);
64
65
	for (elem = snd_mixer_first_elem(alsa_mixer_handle); elem;
66
		 elem = snd_mixer_elem_next(elem)) {
67
68
		const char *name;
69
70
		snd_mixer_selem_get_id(elem, sid);
71
		name = snd_mixer_selem_id_get_name(sid);
72
		d_print("name = %s\n", name);
73
		d_print("has playback volume = %d\n", snd_mixer_selem_has_playback_volume(elem));
74
		d_print("has playback switch = %d\n", snd_mixer_selem_has_playback_switch(elem));
75
76
		if (strcasecmp(name, goal_name) == 0) {
77
			if (!snd_mixer_selem_has_playback_volume(elem)) {
78
				d_print("mixer element `%s' does not have playback volume\n", name);
79
				elem = NULL;
80
			}
81
			break;
82
		}
83
	}
84
85
	snd_mixer_selem_id_free(sid);
86
	return elem;
87
}
88
89
static int alsa_mixer_open(int *volume_max)
90
{
91
	snd_mixer_elem_t *elem;
92
	int count;
93
	int rc;
94
95
	rc = snd_mixer_open(&alsa_mixer_handle, 0);
96
	if (rc < 0)
97
		goto error;
98
	rc = snd_mixer_attach(alsa_mixer_handle, alsa_mixer_device);
99
	if (rc < 0)
100
		goto error;
101
	rc = snd_mixer_selem_register(alsa_mixer_handle, NULL, NULL);
102
	if (rc < 0)
103
		goto error;
104
	rc = snd_mixer_load(alsa_mixer_handle);
105
	if (rc < 0)
106
		goto error;
107
	count = snd_mixer_get_count(alsa_mixer_handle);
108
	if (count == 0) {
109
		d_print("error: mixer does not have elements\n");
110
		return -2;
111
	}
112
113
	elem = find_mixer_elem_by_name(alsa_mixer_element);
114
	if (!elem) {
115
		d_print("mixer element `%s' not found, trying `Master'\n", alsa_mixer_element);
116
		elem = find_mixer_elem_by_name("Master");
117
		if (!elem) {
118
			d_print("error: cannot find suitable mixer element\n");
119
			return -2;
120
		}
121
	}
122
	snd_mixer_selem_get_playback_volume_range(elem, &mixer_vol_min, &mixer_vol_max);
123
	/* FIXME: get number of channels */
124
	mixer_elem = elem;
125
	*volume_max = mixer_vol_max - mixer_vol_min;
126
127
	return 0;
128
129
error:
130
	d_print("error: %s\n", snd_strerror(rc));
131
	return -1;
132
}
133
134
static int alsa_mixer_close(void)
135
{
136
	snd_mixer_close(alsa_mixer_handle);
137
	return 0;
138
}
139
140
static int alsa_mixer_get_fds(int *fds)
141
{
142
	struct pollfd pfd[NR_MIXER_FDS];
143
	int count, i;
144
145
	count = snd_mixer_poll_descriptors(alsa_mixer_handle, pfd, NR_MIXER_FDS);
146
	for (i = 0; i < count; i++)
147
		fds[i] = pfd[i].fd;
148
	return count;
149
}
150
151
static int alsa_mixer_set_volume(int l, int r)
152
{
153
	if (mixer_elem == NULL) {
154
		return -1;
155
	}
156
	l += mixer_vol_min;
157
	r += mixer_vol_min;
158
	if (l > mixer_vol_max)
159
		d_print("error: left volume too high (%d > %ld)\n",
160
				l, mixer_vol_max);
161
	if (r > mixer_vol_max)
162
		d_print("error: right volume too high (%d > %ld)\n",
163
				r, mixer_vol_max);
164
	snd_mixer_selem_set_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, l);
165
	snd_mixer_selem_set_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, r);
166
	return 0;
167
}
168
169
static int alsa_mixer_get_volume(int *l, int *r)
170
{
171
	long lv, rv;
172
173
	if (mixer_elem == NULL)
174
		return -1;
175
	snd_mixer_handle_events(alsa_mixer_handle);
176
	snd_mixer_selem_get_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_LEFT, &lv);
177
	snd_mixer_selem_get_playback_volume(mixer_elem, SND_MIXER_SCHN_FRONT_RIGHT, &rv);
178
	*l = lv - mixer_vol_min;
179
	*r = rv - mixer_vol_min;
180
	return 0;
181
}
182
183
static int alsa_mixer_set_option(int key, const char *val)
184
{
185
	switch (key) {
186
	case 0:
187
		free(alsa_mixer_element);
188
		alsa_mixer_element = xstrdup(val);
189
		break;
190
	case 1:
191
		free(alsa_mixer_device);
192
		alsa_mixer_device = xstrdup(val);
193
		break;
194
	default:
195
		return -OP_ERROR_NOT_OPTION;
196
	}
197
	return 0;
198
}
199
200
static int alsa_mixer_get_option(int key, char **val)
201
{
202
	switch (key) {
203
	case 0:
204
		if (alsa_mixer_element)
205
			*val = xstrdup(alsa_mixer_element);
206
		break;
207
	case 1:
208
		if (alsa_mixer_device)
209
			*val = xstrdup(alsa_mixer_device);
210
		break;
211
	default:
212
		return -OP_ERROR_NOT_OPTION;
213
	}
214
	return 0;
215
}
216
217
const struct mixer_plugin_ops op_mixer_ops = {
218
	.init = alsa_mixer_init,
219
	.exit = alsa_mixer_exit,
220
	.open = alsa_mixer_open,
221
	.close = alsa_mixer_close,
222
	.get_fds = alsa_mixer_get_fds,
223
	.set_volume = alsa_mixer_set_volume,
224
	.get_volume = alsa_mixer_get_volume,
225
	.set_option = alsa_mixer_set_option,
226
	.get_option = alsa_mixer_get_option
227
};
228
229
const char * const op_mixer_options[] = {
230
	"channel",
231
	"device",
232
	NULL
233
};