alsa_mixer.c 5.26 KB
Newer Older
1 2

#include "../output_api.h"
3
#include "../mixer_api.h"
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

#include <glib.h>
#include <alsa/asoundlib.h>

#define VOLUME_MIXER_ALSA_DEFAULT		"default"
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT	"PCM"

struct alsa_mixer {
	char *device;
	char *control;
	snd_mixer_t *handle;
	snd_mixer_elem_t *elem;
	long volume_min;
	long volume_max;
	int volume_set;
};

Viliam Mateicka's avatar
Viliam Mateicka committed
21
static struct mixer_data *
22 23 24 25 26 27 28 29 30 31
alsa_mixer_init(void)
{
	struct alsa_mixer *am = g_malloc(sizeof(struct alsa_mixer));
	am->device = NULL;
	am->control = NULL;
	am->handle = NULL;
	am->elem = NULL;
	am->volume_min = 0;
	am->volume_max = 0;
	am->volume_set = -1;
Viliam Mateicka's avatar
Viliam Mateicka committed
32
	return (struct mixer_data *)am;
33 34
}

Viliam Mateicka's avatar
Viliam Mateicka committed
35 36
static void
alsa_mixer_finish(struct mixer_data *data)
37
{
Viliam Mateicka's avatar
Viliam Mateicka committed
38
	struct alsa_mixer *am = (struct alsa_mixer *)data;
39 40 41

	g_free(am->device);
	g_free(am->control);
42 43 44
	g_free(am);
}

Viliam Mateicka's avatar
Viliam Mateicka committed
45
static void
46
alsa_mixer_configure(struct mixer_data *data, struct config_param *param)
47
{
Viliam Mateicka's avatar
Viliam Mateicka committed
48
	struct alsa_mixer *am = (struct alsa_mixer *)data;
49
	const char *value;
50

51 52 53
	if (param == NULL)
		return;

54 55
	value = config_get_block_string(param, "mixer_device", NULL);
	if (value != NULL) {
56
		g_free(am->device);
57
		am->device = g_strdup(value);
58
	}
59 60 61

	value = config_get_block_string(param, "mixer_control", NULL);
	if (value != NULL) {
62
		g_free(am->control);
63
		am->control = g_strdup(value);
64
	}
65 66
}

Viliam Mateicka's avatar
Viliam Mateicka committed
67 68
static void
alsa_mixer_close(struct mixer_data *data)
69
{
Viliam Mateicka's avatar
Viliam Mateicka committed
70
	struct alsa_mixer *am = (struct alsa_mixer *)data;
71 72 73 74
	if (am->handle) snd_mixer_close(am->handle);
	am->handle = NULL;
}

Viliam Mateicka's avatar
Viliam Mateicka committed
75 76
static bool
alsa_mixer_open(struct mixer_data *data)
77
{
Viliam Mateicka's avatar
Viliam Mateicka committed
78
	struct alsa_mixer *am = (struct alsa_mixer *)data;
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
	int err;
	snd_mixer_elem_t *elem;
	const char *control_name = VOLUME_MIXER_ALSA_CONTROL_DEFAULT;
	const char *device = VOLUME_MIXER_ALSA_DEFAULT;

	if (am->device) {
		device = am->device;
	}
	err = snd_mixer_open(&am->handle, 0);
	snd_config_update_free_global();
	if (err < 0) {
		g_warning("problems opening alsa mixer: %s\n", snd_strerror(err));
		return false;
	}

	if ((err = snd_mixer_attach(am->handle, device)) < 0) {
		g_warning("problems attaching alsa mixer: %s\n",
			snd_strerror(err));
Viliam Mateicka's avatar
Viliam Mateicka committed
97
		alsa_mixer_close(data);
98 99 100 101 102 103 104
		return false;
	}

	if ((err = snd_mixer_selem_register(am->handle, NULL,
		    NULL)) < 0) {
		g_warning("problems snd_mixer_selem_register'ing: %s\n",
			snd_strerror(err));
Viliam Mateicka's avatar
Viliam Mateicka committed
105
		alsa_mixer_close(data);
106 107 108 109 110 111
		return false;
	}

	if ((err = snd_mixer_load(am->handle)) < 0) {
		g_warning("problems snd_mixer_selem_register'ing: %s\n",
			snd_strerror(err));
Viliam Mateicka's avatar
Viliam Mateicka committed
112
		alsa_mixer_close(data);
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
		return false;
	}

	elem = snd_mixer_first_elem(am->handle);

	if (am->control) {
		control_name = am->control;
	}

	while (elem) {
		if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
			if (strcasecmp(control_name,
				       snd_mixer_selem_get_name(elem)) == 0) {
				break;
			}
		}
		elem = snd_mixer_elem_next(elem);
	}

	if (elem) {
		am->elem = elem;
		snd_mixer_selem_get_playback_volume_range(am->elem,
							  &am->volume_min,
							  &am->volume_max);
		return true;
	}

	g_warning("can't find alsa mixer control \"%s\"\n", control_name);

Viliam Mateicka's avatar
Viliam Mateicka committed
142
	alsa_mixer_close(data);
143 144 145
	return false;
}

Viliam Mateicka's avatar
Viliam Mateicka committed
146 147
static bool
alsa_mixer_control(struct mixer_data *data, int cmd, void *arg)
148
{
Viliam Mateicka's avatar
Viliam Mateicka committed
149
	struct alsa_mixer *am = (struct alsa_mixer *)data;
150 151
	switch (cmd) {
	case AC_MIXER_CONFIGURE:
152
		alsa_mixer_configure(data, (struct config_param *)arg);
153
		if (am->handle)
Viliam Mateicka's avatar
Viliam Mateicka committed
154
			alsa_mixer_close(data);
155 156 157 158 159 160 161
		return true;
	case AC_MIXER_GETVOL:
	{
		int err;
		int ret, *volume = arg;
		long level;

Viliam Mateicka's avatar
Viliam Mateicka committed
162
		if (!am->handle && !alsa_mixer_open(data)) {
163 164 165 166 167
			return false;
		}
		if ((err = snd_mixer_handle_events(am->handle)) < 0) {
			g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
				snd_strerror(err), "handle_events");
Viliam Mateicka's avatar
Viliam Mateicka committed
168
			alsa_mixer_close(data);
169 170 171 172 173 174
			return false;
		}
		if ((err = snd_mixer_selem_get_playback_volume(am->elem,
			       SND_MIXER_SCHN_FRONT_LEFT, &level)) < 0) {
			g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n",
				snd_strerror(err), "selem_get_playback_volume");
Viliam Mateicka's avatar
Viliam Mateicka committed
175
			alsa_mixer_close(data);
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
			return false;
		}
		ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min)
			+ am->volume_min) + 0.5;
		if (am->volume_set > 0 && ret == level) {
			ret = am->volume_set;
		} else {
			ret = (int)(100 * (((float)(level - am->volume_min)) /
				(am->volume_max - am->volume_min)) + 0.5);
		}
		*volume = ret;
		return true;
	}
	case AC_MIXER_SETVOL:
	{
		float vol;
		long level;
		int *volume = arg;
		int err;

Viliam Mateicka's avatar
Viliam Mateicka committed
196
		if (!am->handle && !alsa_mixer_open(data)) {
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
			return false;
		}
		vol = *volume;

		am->volume_set = vol + 0.5;
		am->volume_set = am->volume_set > 100 ? 100 :
			    (am->volume_set < 0 ? 0 : am->volume_set);

		level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) +
			 am->volume_min) + 0.5);
		level = level > am->volume_max ? am->volume_max : level;
		level = level < am->volume_min ? am->volume_min : level;

		if ((err = snd_mixer_selem_set_playback_volume_all(am->elem,
								level)) < 0) {
			g_warning("problems setting alsa volume: %s\n",
				snd_strerror(err));
Viliam Mateicka's avatar
Viliam Mateicka committed
214
			alsa_mixer_close(data);
215 216 217 218 219 220 221 222 223 224
			return false;
		}
		return true;
	}
	default:
		g_warning("Unsuported alsa control\n");
		break;
	}
	return false;
}
Viliam Mateicka's avatar
Viliam Mateicka committed
225 226 227 228 229 230 231 232 233

struct mixer_plugin alsa_mixer = {
	.init = alsa_mixer_init,
	.finish = alsa_mixer_finish,
	.configure = alsa_mixer_configure,
	.open = alsa_mixer_open,
	.control = alsa_mixer_control,
	.close = alsa_mixer_close
};