oss_mixer.c 4.61 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Copyright (C) 2003-2009 The Music Player Daemon Project
 * http://www.musicpd.org
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
18

19 20 21
#include "../output_api.h"
#include "../mixer_api.h"

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
#include <glib.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#if defined(__OpenBSD__) || defined(__NetBSD__)
# include <soundcard.h>
#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */
# include <sys/soundcard.h>
#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */

#define VOLUME_MIXER_OSS_DEFAULT		"/dev/mixer"

struct oss_mixer {
39 40 41
	/** the base mixer class */
	struct mixer base;

42 43
	char *device;
	char *control;
44 45 46 47
	int device_fd;
	int volume_control;
};

48
static struct mixer *
49
oss_mixer_init(const struct config_param *param)
50
{
51 52 53
	struct oss_mixer *om = g_new(struct oss_mixer, 1);

	mixer_init(&om->base, &oss_mixer);
54 55 56 57

	om->device = config_dup_block_string(param, "mixer_device", NULL);
	om->control = config_dup_block_string(param, "mixer_control", NULL);

58 59
	om->device_fd = -1;
	om->volume_control = SOUND_MIXER_PCM;
60

61
	return &om->base;
62 63
}

Viliam Mateicka's avatar
Viliam Mateicka committed
64
static void
65
oss_mixer_finish(struct mixer *data)
66
{
Viliam Mateicka's avatar
Viliam Mateicka committed
67
	struct oss_mixer *om = (struct oss_mixer *) data;
68 69 70

	g_free(om->device);
	g_free(om->control);
71 72 73
	g_free(om);
}

Viliam Mateicka's avatar
Viliam Mateicka committed
74
static void
75
oss_mixer_close(struct mixer *data)
76
{
Viliam Mateicka's avatar
Viliam Mateicka committed
77
	struct oss_mixer *om = (struct oss_mixer *) data;
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
	if (om->device_fd != -1)
		while (close(om->device_fd) && errno == EINTR) ;
	om->device_fd = -1;
}

static int
oss_find_mixer(const char *name)
{
	const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
	size_t name_length = strlen(name);

	for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
		if (strncasecmp(name, labels[i], name_length) == 0 &&
		    (labels[i][name_length] == 0 ||
		     labels[i][name_length] == ' '))
			return i;
	}
	return -1;
}

Viliam Mateicka's avatar
Viliam Mateicka committed
98
static bool
99
oss_mixer_open(struct mixer *data)
100
{
Viliam Mateicka's avatar
Viliam Mateicka committed
101
	struct oss_mixer *om = (struct oss_mixer *) data;
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
	const char *device = VOLUME_MIXER_OSS_DEFAULT;

	if (om->device) {
		device = om->device;
	}

	if ((om->device_fd = open(device, O_RDONLY)) < 0) {
		g_warning("Unable to open oss mixer \"%s\"\n", device);
		return false;
	}

	if (om->control) {
		int i;
		int devmask = 0;

		if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
			g_warning("errors getting read_devmask for oss mixer\n");
Viliam Mateicka's avatar
Viliam Mateicka committed
119
			oss_mixer_close(data);
120 121 122 123 124 125 126
			return false;
		}
		i = oss_find_mixer(om->control);

		if (i < 0) {
			g_warning("mixer control \"%s\" not found\n",
				om->control);
Viliam Mateicka's avatar
Viliam Mateicka committed
127
			oss_mixer_close(data);
128 129 130 131
			return false;
		} else if (!((1 << i) & devmask)) {
			g_warning("mixer control \"%s\" not usable\n",
				om->control);
Viliam Mateicka's avatar
Viliam Mateicka committed
132
			oss_mixer_close(data);
133 134 135 136 137 138 139
			return false;
		}
		om->volume_control = i;
	}
	return true;
}

140 141
static int
oss_mixer_get_volume(struct mixer *mixer)
142
{
143 144 145
	struct oss_mixer *om = (struct oss_mixer *)mixer;
	int left, right, level;
	int ret;
146

147 148
	if (om->device_fd < 0 && !oss_mixer_open(mixer))
		return false;
149

150 151 152 153 154 155
	ret = ioctl(om->device_fd, MIXER_READ(om->volume_control), &level);
	if (ret < 0) {
		oss_mixer_close(mixer);
		g_warning("unable to read oss volume\n");
		return false;
	}
156

157 158
	left = level & 0xff;
	right = (level & 0xff00) >> 8;
159

160 161 162
	if (left != right) {
		g_warning("volume for left and right is not the same, \"%i\" and "
			  "\"%i\"\n", left, right);
163 164
	}

165 166
	return left;
}
167

168 169 170 171 172 173
static bool
oss_mixer_set_volume(struct mixer *mixer, unsigned volume)
{
	struct oss_mixer *om = (struct oss_mixer *)mixer;
	int level;
	int ret;
174

175 176
	if (om->device_fd < 0 && !oss_mixer_open(mixer))
		return false;
177

178 179 180 181 182 183 184 185 186 187
	if (volume > 100)
		volume = 100;

	level = (volume << 8) + volume;

	ret = ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level);
	if (ret < 0) {
		g_warning("unable to set oss volume\n");
		oss_mixer_close(mixer);
		return false;
188
	}
189 190

	return true;
191
}
Viliam Mateicka's avatar
Viliam Mateicka committed
192

193
const struct mixer_plugin oss_mixer = {
Viliam Mateicka's avatar
Viliam Mateicka committed
194 195 196
	.init = oss_mixer_init,
	.finish = oss_mixer_finish,
	.open = oss_mixer_open,
197 198 199
	.close = oss_mixer_close,
	.get_volume = oss_mixer_get_volume,
	.set_volume = oss_mixer_set_volume,
Viliam Mateicka's avatar
Viliam Mateicka committed
200
};