volume.c 10.6 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
/* 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>
32
#ifdef HAVE_OSS
Warren Dukes's avatar
Warren Dukes committed
33 34 35 36 37 38
#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
#ifdef HAVE_OSS
49 50 51 52 53 54 55 56 57 58 59 60 61 62
#define VOLUME_MIXER_TYPE_DEFAULT               VOLUME_MIXER_TYPE_OSS
#define VOLUME_MIXER_DEVICE_DEFAULT             VOLUME_MIXER_OSS_DEFAULT
#else
#ifdef HAVE_ALSA
#define VOLUME_MIXER_TYPE_DEFAULT               VOLUME_MIXER_TYPE_ALSA
#define VOLUME_MIXER_DEVICE_DEFAULT             VOLUME_MIXER_ALSA_DEFAULT
#else
#define VOLUME_MIXER_TYPE_DEFAULT               VOLUME_MIXER_TYPE_SOFTWARE
#define VOLUME_MIXER_DEVICE_DEFAULT             VOLUME_MIXER_SOFTWARE_DEFAULT
#endif
#endif

int volume_mixerType = VOLUME_MIXER_TYPE_DEFAULT;
char * volume_mixerDevice = VOLUME_MIXER_DEVICE_DEFAULT;
Warren Dukes's avatar
Warren Dukes committed
63

64
int volume_softwareSet = 100;
Warren Dukes's avatar
Warren Dukes committed
65

66
#ifdef HAVE_OSS
Warren Dukes's avatar
Warren Dukes committed
67 68 69 70 71 72 73 74 75
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
76
int volume_alsaSet = -1;
Warren Dukes's avatar
Warren Dukes committed
77 78
#endif

79
#ifdef HAVE_OSS
Warren Dukes's avatar
Warren Dukes committed
80 81
int prepOssMixer(char * device) {
	int devmask = 0;
82
        ConfigParam * param;
Warren Dukes's avatar
Warren Dukes committed
83 84

	if((volume_ossFd = open(device,O_RDONLY))<0) {
85
		WARNING("unable to open oss mixer \"%s\"\n",device);
Warren Dukes's avatar
Warren Dukes committed
86 87 88
		return -1;
	}

89 90 91
        param = getConfigParam(CONF_MIXER_CONTROL);

	if(param) {
Warren Dukes's avatar
Warren Dukes committed
92 93 94 95 96
		char * labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
		char * dup;
		int i,j;

		if(ioctl(volume_ossFd,SOUND_MIXER_READ_DEVMASK,&devmask)<0) {
97
			WARNING("errors getting read_devmask for oss mixer\n");
Warren Dukes's avatar
Warren Dukes committed
98 99 100 101 102 103 104 105 106
			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';
107
			if(strcasecmp(dup, param->value)==0) {
Warren Dukes's avatar
Warren Dukes committed
108 109 110 111 112 113 114
				free(dup);
				break;
			}
			free(dup);
		}

		if(i>=SOUND_MIXER_NRDEVICES) {
115 116
			WARNING("mixer control \"%s\" not found at line %i\n",
					        param->value, param->line);
Warren Dukes's avatar
Warren Dukes committed
117 118 119 120
			close(volume_ossFd);
			return -1;
		}
		else if(!( ( 1 << i ) & devmask )) {
121 122
			WARNING("mixer control \"%s\" not usable at line %i\n",
					        param->value, param->line);
Warren Dukes's avatar
Warren Dukes committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
			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) {
141
		WARNING("unable to read volume\n");
Warren Dukes's avatar
Warren Dukes committed
142 143 144 145 146 147 148
		return -1;
	}

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

	if(left!=right) {
149
		WARNING("volume for left and right is not the same, \"%i\" and "
Warren Dukes's avatar
Warren Dukes committed
150 151 152 153 154 155 156 157 158 159 160 161 162
			"\"%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
163
			commandError(fp, ACK_ERROR_SYSTEM,
164
                                        "problem getting current volume", NULL);
Warren Dukes's avatar
Warren Dukes committed
165 166 167 168 169 170 171 172 173 174 175 176 177
			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) {
178 179
		commandError(fp, ACK_ERROR_SYSTEM, "problems setting volume",
				NULL);
Warren Dukes's avatar
Warren Dukes committed
180 181 182 183 184 185 186 187 188 189 190
		return -1;
	}

	return 0;
}
#endif

#ifdef HAVE_ALSA
int prepAlsaMixer(char * card) {
	int err;
	snd_mixer_elem_t * elem;
191
	char * controlName = VOLUME_MIXER_ALSA_CONTROL_DEFAULT;
192
        ConfigParam * param;
Warren Dukes's avatar
Warren Dukes committed
193 194

	if((err = snd_mixer_open(&volume_alsaMixerHandle,0))<0) {
195
		WARNING("problems opening alsa mixer: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
196 197 198 199 200
		return -1;
	}
	
	if((err = snd_mixer_attach(volume_alsaMixerHandle,card))<0) {
		snd_mixer_close(volume_alsaMixerHandle);
201
		WARNING("problems problems attaching alsa mixer: %s\n",
Warren Dukes's avatar
Warren Dukes committed
202 203 204 205 206 207
			snd_strerror(err));
		return -1;
	}
	
	if((err = snd_mixer_selem_register(volume_alsaMixerHandle,NULL,NULL))<0) {
		snd_mixer_close(volume_alsaMixerHandle);
208
		WARNING("problems snd_mixer_selem_register'ing: %s\n",
Warren Dukes's avatar
Warren Dukes committed
209 210 211 212 213 214
			snd_strerror(err));
		return -1;
	}
	
	if((err = snd_mixer_load(volume_alsaMixerHandle))<0) {
		snd_mixer_close(volume_alsaMixerHandle);
215
		WARNING("problems snd_mixer_selem_register'ing: %s\n",
Warren Dukes's avatar
Warren Dukes committed
216 217 218 219 220
			snd_strerror(err));
		return -1;
	}

	elem = snd_mixer_first_elem(volume_alsaMixerHandle);
221 222 223 224 225

        param = getConfigParam(CONF_MIXER_CONTROL);

	if(param) {
		controlName = param->value;
226 227 228 229 230 231
	}

	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
232
			{
233
				break;
Warren Dukes's avatar
Warren Dukes committed
234 235
			}
		}
236
		elem = snd_mixer_elem_next(elem);
Warren Dukes's avatar
Warren Dukes committed
237
	}
238 239

	if(elem) {
Warren Dukes's avatar
Warren Dukes committed
240
		volume_alsaElem = elem;
241 242 243 244
		snd_mixer_selem_get_playback_volume_range(
				volume_alsaElem,
				&volume_alsaMin,&volume_alsaMax);
		return 0;
Warren Dukes's avatar
Warren Dukes committed
245 246
	}

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

Warren Dukes's avatar
Warren Dukes committed
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263
	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
264 265
	snd_mixer_handle_events(volume_alsaMixerHandle);

Warren Dukes's avatar
Warren Dukes committed
266 267
	if((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
		SND_MIXER_SCHN_FRONT_LEFT,&level))<0) {
268
		WARNING("problems getting alsa volume: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
269 270 271 272
		return -1;
	}

	snd_mixer_selem_get_playback_volume(volume_alsaElem,
Warren Dukes's avatar
Warren Dukes committed
273 274 275 276 277 278
			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
279 280 281 282 283 284 285

	return ret;
}

int changeAlsaVolumeLevel(FILE * fp, int change, int rel) {
	float vol;
	long level;
Warren Dukes's avatar
Warren Dukes committed
286
	long test;
Warren Dukes's avatar
Warren Dukes committed
287 288 289 290
	long max = volume_alsaMax;
	long min = volume_alsaMin;
	int err;

Warren Dukes's avatar
Warren Dukes committed
291 292
	snd_mixer_handle_events(volume_alsaMixerHandle);

Warren Dukes's avatar
Warren Dukes committed
293 294
	if((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
		SND_MIXER_SCHN_FRONT_LEFT,&level))<0) {
295 296
		commandError(fp, ACK_ERROR_SYSTEM, "problems getting volume",
				NULL);
297
		WARNING("problems getting alsa volume: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
298 299 300 301
		return -1;
	}

	if (rel) {
Warren Dukes's avatar
Warren Dukes committed
302 303 304 305
		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
306
		else vol =  100.0*(((float)(level-min))/(max-min));
Warren Dukes's avatar
Warren Dukes committed
307 308 309 310 311
		vol+=change;
	}
	else
		vol = change;

Warren Dukes's avatar
Warren Dukes committed
312 313 314 315
	volume_alsaSet = vol+0.5;
	volume_alsaSet = volume_alsaSet>100 ? 100 : 
			(volume_alsaSet<0 ? 0 : volume_alsaSet);

Warren Dukes's avatar
Warren Dukes committed
316 317 318 319 320 321
	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) {
322 323
		commandError(fp, ACK_ERROR_SYSTEM, "problems setting volume",
				NULL);
324
		WARNING("problems setting alsa volume: %s\n",snd_strerror(err));
Warren Dukes's avatar
Warren Dukes committed
325 326 327 328 329 330 331 332 333 334 335 336 337
		return -1;
	}

	return 0;
}
#endif

int prepMixer(char * device) {
	switch(volume_mixerType) {
#ifdef HAVE_ALSA
	case VOLUME_MIXER_TYPE_ALSA:
		return prepAlsaMixer(device);
#endif
338
#ifdef HAVE_OSS
Warren Dukes's avatar
Warren Dukes committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
	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
354
#ifdef HAVE_OSS
Warren Dukes's avatar
Warren Dukes committed
355 356 357 358 359 360 361 362
	case VOLUME_MIXER_TYPE_OSS:
		closeOssMixer();
		break;
#endif
	}
}

void initVolume() {
363 364 365 366
        ConfigParam * param = getConfigParam(CONF_MIXER_TYPE);

        if(param) {
	        if(0);
Warren Dukes's avatar
Warren Dukes committed
367
#ifdef HAVE_ALSA
368 369 370 371
		else if(strcmp(param->value, VOLUME_MIXER_ALSA)==0) {
			volume_mixerType = VOLUME_MIXER_TYPE_ALSA;
			volume_mixerDevice = VOLUME_MIXER_ALSA_DEFAULT;
		}
Warren Dukes's avatar
Warren Dukes committed
372
#endif
373
#ifdef HAVE_OSS
374 375 376 377
		else if(strcmp(param->value, VOLUME_MIXER_OSS)==0) {
			volume_mixerType = VOLUME_MIXER_TYPE_OSS;
			volume_mixerDevice = VOLUME_MIXER_OSS_DEFAULT;
		}
Warren Dukes's avatar
Warren Dukes committed
378
#endif
379 380 381 382 383 384 385 386 387
		else if(strcmp(param->value ,VOLUME_MIXER_SOFTWARE)==0) {
			volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
			volume_mixerDevice = VOLUME_MIXER_SOFTWARE_DEFAULT;
		}
		else {
			ERROR("unknown mixer type %s at line %i\n",
					param->value, param->line);
			exit(EXIT_FAILURE);
		}
Warren Dukes's avatar
Warren Dukes committed
388
	}
389 390 391 392 393

	param = getConfigParam(CONF_MIXER_DEVICE);
	
	if(param) {
		volume_mixerDevice = param->value;
Warren Dukes's avatar
Warren Dukes committed
394 395 396 397 398
	}
}

void openVolumeDevice() {
	if(prepMixer(volume_mixerDevice)<0) {
399
		WARNING("using software volume\n");
Warren Dukes's avatar
Warren Dukes committed
400 401 402 403 404
		volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
	}
}

int getSoftwareVolume() {
405
	return volume_softwareSet;
Warren Dukes's avatar
Warren Dukes committed
406 407 408 409 410 411 412 413
}

int getVolumeLevel() {
	switch(volume_mixerType) {
#ifdef HAVE_ALSA
	case VOLUME_MIXER_TYPE_ALSA:
		return getAlsaVolumeLevel();
#endif
414
#ifdef HAVE_OSS
Warren Dukes's avatar
Warren Dukes committed
415 416 417 418 419 420 421 422 423 424 425 426 427
	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;

428
	if(rel) new+=volume_softwareSet;
Warren Dukes's avatar
Warren Dukes committed
429 430 431 432

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

Warren Dukes's avatar
Warren Dukes committed
433 434
	volume_softwareSet = new;

435 436 437
	/*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;
438
	else new = 1000.0*(exp(new/25.0)-1)/(54.5981500331F-1)+0.5;
Warren Dukes's avatar
Warren Dukes committed
439 440 441 442 443 444 445 446 447 448 449 450

	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
451
#ifdef HAVE_OSS
Warren Dukes's avatar
Warren Dukes committed
452 453 454 455 456 457
	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
458 459
                return 0;
                break;
Warren Dukes's avatar
Warren Dukes committed
460 461
	}
}