volume.c 9.9 KB
Newer Older
Warren Dukes's avatar
Warren Dukes committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
/* the Music Player Daemon (MPD)
 * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
 * This project's homepage is: 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
 */
#include "volume.h"

#include "command.h"
#include "conf.h"
#include "log.h"
#include "player.h"

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#ifndef NO_OSS_MIXER
#include <sys/soundcard.h>
#endif
#ifdef HAVE_ALSA
#include <alsa/asoundlib.h>
#endif

39 40 41
#define VOLUME_MIXER_TYPE_SOFTWARE		0
#define VOLUME_MIXER_TYPE_OSS			1
#define VOLUME_MIXER_TYPE_ALSA			2
Warren Dukes's avatar
Warren Dukes committed
42

43 44 45 46
#define VOLUME_MIXER_SOFTWARE_DEFAULT		""
#define VOLUME_MIXER_OSS_DEFAULT		"/dev/mixer"
#define VOLUME_MIXER_ALSA_DEFAULT		"default"
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT	"Master"
Warren Dukes's avatar
Warren Dukes committed
47 48 49 50

int volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
char * volume_mixerDevice;

51
int volume_softwareSet = 100;
Warren Dukes's avatar
Warren Dukes committed
52

Warren Dukes's avatar
Warren Dukes committed
53 54 55 56 57 58 59 60 61 62
#ifndef NO_OSS_MIXER
int volume_ossFd;
int volume_ossControl = SOUND_MIXER_VOLUME;
#endif

#ifdef HAVE_ALSA
snd_mixer_t * volume_alsaMixerHandle = NULL;
snd_mixer_elem_t * volume_alsaElem;
long volume_alsaMin;
long volume_alsaMax;
Warren Dukes's avatar
Warren Dukes committed
63
int volume_alsaSet = -1;
Warren Dukes's avatar
Warren Dukes committed
64 65 66 67 68 69 70
#endif

#ifndef NO_OSS_MIXER
int prepOssMixer(char * device) {
	int devmask = 0;

	if((volume_ossFd = open(device,O_RDONLY))<0) {
71
		WARNING("unable to open oss mixer \"%s\"\n",device);
Warren Dukes's avatar
Warren Dukes committed
72 73 74 75 76 77 78 79 80
		return -1;
	}

	if(getConf()[CONF_MIXER_CONTROL]) {
		char * labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
		char * dup;
		int i,j;

		if(ioctl(volume_ossFd,SOUND_MIXER_READ_DEVMASK,&devmask)<0) {
81
			WARNING("errors getting read_devmask for oss mixer\n");
Warren Dukes's avatar
Warren Dukes committed
82 83 84 85 86 87 88 89 90
			close(volume_ossFd);
			return -1;
		}

		for(i=0;i<SOUND_MIXER_NRDEVICES;i++) {
			dup = strdup(labels[i]);
			/* eliminate spaces at the end */
			j = strlen(dup)-1;
			while(j>=0 && dup[j]==' ') dup[j--] = '\0';
91
			if(strcasecmp(dup,getConf()[CONF_MIXER_CONTROL])==0) {
Warren Dukes's avatar
Warren Dukes committed
92 93 94 95 96 97 98
				free(dup);
				break;
			}
			free(dup);
		}

		if(i>=SOUND_MIXER_NRDEVICES) {
99
			WARNING("mixer control \"%s\" not found\n",
Warren Dukes's avatar
Warren Dukes committed
100 101 102 103 104
					getConf()[CONF_MIXER_CONTROL]);
			close(volume_ossFd);
			return -1;
		}
		else if(!( ( 1 << i ) & devmask )) {
105
			WARNING("mixer control \"%s\" not usable\n",
Warren Dukes's avatar
Warren Dukes committed
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
					getConf()[CONF_MIXER_CONTROL]);
			close(volume_ossFd);
			return -1;
		}

		volume_ossControl = i;
	}

	return 0;
}

void closeOssMixer() {
	close(volume_ossFd);
}

int getOssVolumeLevel() {
	int left, right, level;

	if(ioctl(volume_ossFd,MIXER_READ(volume_ossControl),&level) < 0) {
125
		WARNING("unable to read volume\n");
Warren Dukes's avatar
Warren Dukes committed
126 127 128 129 130 131 132
		return -1;
	}

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

	if(left!=right) {
133
		WARNING("volume for left and right is not the same, \"%i\" and "
Warren Dukes's avatar
Warren Dukes committed
134 135 136 137 138 139 140 141 142 143 144 145 146
			"\"%i\"\n",left,right);
	}

	return left;
}

int changeOssVolumeLevel(FILE * fp, int change, int rel) {
	int current;
	int new;
	int level;

	if (rel) {
		if((current = getOssVolumeLevel()) < 0) {
Warren Dukes's avatar
Warren Dukes committed
147 148
			commandError(fp, ACK_ERROR_SYSTEM,
                                        "problem getting current volume");
Warren Dukes's avatar
Warren Dukes committed
149 150 151 152 153 154 155 156 157 158 159 160 161
			return -1;
		}

		new = current+change;
	}
	else new = change;

	if(new<0) new = 0;
	else if(new>100) new = 100;

	level = (new << 8) + new;

	if(ioctl(volume_ossFd,MIXER_WRITE(volume_ossControl),&level) < 0) {
Warren Dukes's avatar
Warren Dukes committed
162
		commandError(fp, ACK_ERROR_SYSTEM, "problems setting volume");
Warren Dukes's avatar
Warren Dukes committed
163 164 165 166 167 168 169 170 171 172 173
		return -1;
	}

	return 0;
}
#endif

#ifdef HAVE_ALSA
int prepAlsaMixer(char * card) {
	int err;
	snd_mixer_elem_t * elem;
174
	char * controlName = VOLUME_MIXER_ALSA_CONTROL_DEFAULT;
Warren Dukes's avatar
Warren Dukes committed
175 176

	if((err = snd_mixer_open(&volume_alsaMixerHandle,0))<0) {
177
		WARNING("problems opening alsa mixer: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
178 179 180 181 182
		return -1;
	}
	
	if((err = snd_mixer_attach(volume_alsaMixerHandle,card))<0) {
		snd_mixer_close(volume_alsaMixerHandle);
183
		WARNING("problems problems attaching alsa mixer: %s\n",
Warren Dukes's avatar
Warren Dukes committed
184 185 186 187 188 189
			snd_strerror(err));
		return -1;
	}
	
	if((err = snd_mixer_selem_register(volume_alsaMixerHandle,NULL,NULL))<0) {
		snd_mixer_close(volume_alsaMixerHandle);
190
		WARNING("problems snd_mixer_selem_register'ing: %s\n",
Warren Dukes's avatar
Warren Dukes committed
191 192 193 194 195 196
			snd_strerror(err));
		return -1;
	}
	
	if((err = snd_mixer_load(volume_alsaMixerHandle))<0) {
		snd_mixer_close(volume_alsaMixerHandle);
197
		WARNING("problems snd_mixer_selem_register'ing: %s\n",
Warren Dukes's avatar
Warren Dukes committed
198 199 200 201 202 203
			snd_strerror(err));
		return -1;
	}

	elem = snd_mixer_first_elem(volume_alsaMixerHandle);
	if(getConf()[CONF_MIXER_CONTROL]) {
204 205 206 207 208 209 210
		controlName = getConf()[CONF_MIXER_CONTROL];
	}

	while(elem) {
		if(snd_mixer_elem_get_type(elem)==SND_MIXER_ELEM_SIMPLE) {
			if(strcasecmp(controlName,
					snd_mixer_selem_get_name(elem))==0)
Warren Dukes's avatar
Warren Dukes committed
211
			{
212
				break;
Warren Dukes's avatar
Warren Dukes committed
213 214
			}
		}
215
		elem = snd_mixer_elem_next(elem);
Warren Dukes's avatar
Warren Dukes committed
216
	}
217 218

	if(elem) {
Warren Dukes's avatar
Warren Dukes committed
219
		volume_alsaElem = elem;
220 221 222 223
		snd_mixer_selem_get_playback_volume_range(
				volume_alsaElem,
				&volume_alsaMin,&volume_alsaMax);
		return 0;
Warren Dukes's avatar
Warren Dukes committed
224 225
	}

226
	WARNING("can't find alsa mixer_control \"%s\"\n",controlName);
227

Warren Dukes's avatar
Warren Dukes committed
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
	snd_mixer_close(volume_alsaMixerHandle);
	return -1;
}

void closeAlsaMixer() {
	snd_mixer_close(volume_alsaMixerHandle);
}

int getAlsaVolumeLevel() {
	int ret;
	long level;
	long max = volume_alsaMax;
	long min = volume_alsaMin;
	int err;

Warren Dukes's avatar
Warren Dukes committed
243 244
	snd_mixer_handle_events(volume_alsaMixerHandle);

Warren Dukes's avatar
Warren Dukes committed
245 246
	if((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
		SND_MIXER_SCHN_FRONT_LEFT,&level))<0) {
247
		WARNING("problems getting alsa volume: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
248 249 250 251
		return -1;
	}

	snd_mixer_selem_get_playback_volume(volume_alsaElem,
Warren Dukes's avatar
Warren Dukes committed
252 253 254 255 256 257
			SND_MIXER_SCHN_FRONT_LEFT,&level);
	ret = ((volume_alsaSet/100.0)*(max-min)+min)+0.5;
	if(volume_alsaSet>0 && ret==level) {
		ret = volume_alsaSet;
	}
	else ret =  (int)(100*(((float)(level-min))/(max-min))+0.5);
Warren Dukes's avatar
Warren Dukes committed
258 259 260 261 262 263 264

	return ret;
}

int changeAlsaVolumeLevel(FILE * fp, int change, int rel) {
	float vol;
	long level;
Warren Dukes's avatar
Warren Dukes committed
265
	long test;
Warren Dukes's avatar
Warren Dukes committed
266 267 268 269
	long max = volume_alsaMax;
	long min = volume_alsaMin;
	int err;

Warren Dukes's avatar
Warren Dukes committed
270 271
	snd_mixer_handle_events(volume_alsaMixerHandle);

Warren Dukes's avatar
Warren Dukes committed
272 273
	if((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
		SND_MIXER_SCHN_FRONT_LEFT,&level))<0) {
Warren Dukes's avatar
Warren Dukes committed
274
		commandError(fp, ACK_ERROR_SYSTEM, "problems getting volume");
275
		WARNING("problems getting alsa volume: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
276 277 278 279
		return -1;
	}

	if (rel) {
Warren Dukes's avatar
Warren Dukes committed
280 281 282 283
		test = ((volume_alsaSet/100.0)*(max-min)+min)+0.5;
		if(volume_alsaSet >= 0 && level==test) {
			vol = volume_alsaSet;
		}
Warren Dukes's avatar
Warren Dukes committed
284
		else vol =  100.0*(((float)(level-min))/(max-min));
Warren Dukes's avatar
Warren Dukes committed
285 286 287 288 289
		vol+=change;
	}
	else
		vol = change;

Warren Dukes's avatar
Warren Dukes committed
290 291 292 293
	volume_alsaSet = vol+0.5;
	volume_alsaSet = volume_alsaSet>100 ? 100 : 
			(volume_alsaSet<0 ? 0 : volume_alsaSet);

Warren Dukes's avatar
Warren Dukes committed
294 295 296 297 298 299
	level = (long)(((vol/100.0)*(max-min)+min)+0.5);
	level = level>max?max:level;
	level = level<min?min:level;

	if((err = snd_mixer_selem_set_playback_volume_all(
				volume_alsaElem,level))<0) {
Warren Dukes's avatar
Warren Dukes committed
300
		commandError(fp, ACK_ERROR_SYSTEM, "problems setting volume");
301
		WARNING("problems setting alsa volume: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
		return -1;
	}

	return 0;
}
#endif

int prepMixer(char * device) {
	switch(volume_mixerType) {
#ifdef HAVE_ALSA
	case VOLUME_MIXER_TYPE_ALSA:
		return prepAlsaMixer(device);
#endif
#ifndef NO_OSS_MIXER
	case VOLUME_MIXER_TYPE_OSS:
		return prepOssMixer(device);
#endif
	}

	return 0;
}

void finishVolume() {
	switch(volume_mixerType) {
#ifdef HAVE_ALSA
	case VOLUME_MIXER_TYPE_ALSA:
		closeAlsaMixer();
		break;
#endif
#ifndef NO_OSS_MIXER
	case VOLUME_MIXER_TYPE_OSS:
		closeOssMixer();
		break;
#endif
	}
}

void initVolume() {
	if(0);
#ifdef HAVE_ALSA
	else if(strcmp((getConf())[CONF_MIXER_TYPE],VOLUME_MIXER_ALSA)==0) {
		volume_mixerType = VOLUME_MIXER_TYPE_ALSA;
		volume_mixerDevice = VOLUME_MIXER_ALSA_DEFAULT;
	}
#endif
#ifndef NO_OSS_MIXER
	else if(strcmp((getConf())[CONF_MIXER_TYPE],VOLUME_MIXER_OSS)==0) {
		volume_mixerType = VOLUME_MIXER_TYPE_OSS;
		volume_mixerDevice = VOLUME_MIXER_OSS_DEFAULT;
	}
#endif
	else if(strcmp((getConf())[CONF_MIXER_TYPE],VOLUME_MIXER_SOFTWARE)==0) {
		volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
		volume_mixerDevice = VOLUME_MIXER_SOFTWARE_DEFAULT;
	}
	else {
		ERROR("unknown mixer type: %s\n",(getConf())[CONF_MIXER_TYPE]);
359
		exit(EXIT_FAILURE);
Warren Dukes's avatar
Warren Dukes committed
360 361 362 363 364 365 366 367
	}
	if(strlen((getConf())[CONF_MIXER_DEVICE])) {
		volume_mixerDevice = (getConf())[CONF_MIXER_DEVICE];
	}
}

void openVolumeDevice() {
	if(prepMixer(volume_mixerDevice)<0) {
368
		WARNING("using software volume\n");
Warren Dukes's avatar
Warren Dukes committed
369 370 371 372 373
		volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
	}
}

int getSoftwareVolume() {
374
	return volume_softwareSet;
Warren Dukes's avatar
Warren Dukes committed
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
}

int getVolumeLevel() {
	switch(volume_mixerType) {
#ifdef HAVE_ALSA
	case VOLUME_MIXER_TYPE_ALSA:
		return getAlsaVolumeLevel();
#endif
#ifndef NO_OSS_MIXER
	case VOLUME_MIXER_TYPE_OSS:
		return getOssVolumeLevel();
#endif
	case VOLUME_MIXER_TYPE_SOFTWARE:
		return getSoftwareVolume();
	default:
		return -1;
	}
}

int changeSoftwareVolume(FILE * fp, int change, int rel) {
	int new = change;

397
	if(rel) new+=volume_softwareSet;
Warren Dukes's avatar
Warren Dukes committed
398 399 400 401

	if(new>100) new = 100;
	else if(new<0) new = 0;

Warren Dukes's avatar
Warren Dukes committed
402 403
	volume_softwareSet = new;

404 405 406
	/*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5;*/
	if(new>=100) new = 1000;
	else if(new<=0) new = 0;
407
	else new = 1000.0*(exp(new/25.0)-1)/(54.5981500331F-1)+0.5;
Warren Dukes's avatar
Warren Dukes committed
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426

	setPlayerSoftwareVolume(new);

	return 0;
}

int changeVolumeLevel(FILE * fp, int change, int rel) {
	switch(volume_mixerType) {
#ifdef HAVE_ALSA
	case VOLUME_MIXER_TYPE_ALSA:
		return changeAlsaVolumeLevel(fp,change,rel);
#endif
#ifndef NO_OSS_MIXER
	case VOLUME_MIXER_TYPE_OSS:
		return changeOssVolumeLevel(fp,change,rel);
#endif
	case VOLUME_MIXER_TYPE_SOFTWARE:
		return changeSoftwareVolume(fp,change,rel);
	default:
Warren Dukes's avatar
Warren Dukes committed
427 428
                return 0;
                break;
Warren Dukes's avatar
Warren Dukes committed
429 430
	}
}
431
/* vim:set shiftwidth=4 tabstop=8 expandtab: */