Initial revision 6759
[qball-mpd.git] / src / volume.c
blob59e8b550c650db1b9536bb0f86b44b274a1e19d7
1 /* the Music Player Daemon (MPD)
2 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
3 * This project's homepage is: http://www.musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 #include "volume.h"
20 #include "command.h"
21 #include "conf.h"
22 #include "log.h"
23 #include "player.h"
24 #include "state_file.h"
25 #include "gcc.h"
26 #include "utils.h"
28 #include <math.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/ioctl.h>
33 #include <fcntl.h>
34 #include <errno.h>
35 #ifdef HAVE_OSS
36 #include <sys/soundcard.h>
37 #endif
38 #ifdef HAVE_ALSA
39 #include <alsa/asoundlib.h>
40 #endif
42 #define VOLUME_MIXER_TYPE_SOFTWARE 0
43 #define VOLUME_MIXER_TYPE_OSS 1
44 #define VOLUME_MIXER_TYPE_ALSA 2
46 #define VOLUME_MIXER_SOFTWARE_DEFAULT ""
47 #define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer"
48 #define VOLUME_MIXER_ALSA_DEFAULT "default"
49 #define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
50 #define SW_VOLUME_STATE "sw_volume: "
52 #ifdef HAVE_OSS
53 #define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_OSS
54 #define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_OSS_DEFAULT
55 #else
56 #ifdef HAVE_ALSA
57 #define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_ALSA
58 #define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_ALSA_DEFAULT
59 #else
60 #define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_SOFTWARE
61 #define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_SOFTWARE_DEFAULT
62 #endif
63 #endif
65 static int volume_mixerType = VOLUME_MIXER_TYPE_DEFAULT;
66 static char *volume_mixerDevice = VOLUME_MIXER_DEVICE_DEFAULT;
68 static int volume_softwareSet = 100;
70 #ifdef HAVE_OSS
71 static int volume_ossFd = -1;
72 static int volume_ossControl = SOUND_MIXER_PCM;
73 #endif
75 #ifdef HAVE_ALSA
76 static snd_mixer_t *volume_alsaMixerHandle;
77 static snd_mixer_elem_t *volume_alsaElem;
78 static long volume_alsaMin;
79 static long volume_alsaMax;
80 static int volume_alsaSet = -1;
81 #endif
83 #ifdef HAVE_OSS
85 static void closeOssMixer(void)
87 while (close(volume_ossFd) && errno == EINTR) ;
88 volume_ossFd = -1;
91 static int prepOssMixer(char *device)
93 ConfigParam *param;
95 if ((volume_ossFd = open(device, O_RDONLY)) < 0) {
96 WARNING("unable to open oss mixer \"%s\"\n", device);
97 return -1;
100 param = getConfigParam(CONF_MIXER_CONTROL);
102 if (param) {
103 char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
104 char *dup;
105 int i, j;
106 int devmask = 0;
108 if (ioctl(volume_ossFd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
109 WARNING("errors getting read_devmask for oss mixer\n");
110 closeOssMixer();
111 return -1;
114 for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
115 dup = xstrdup(labels[i]);
116 /* eliminate spaces at the end */
117 j = strlen(dup) - 1;
118 while (j >= 0 && dup[j] == ' ')
119 dup[j--] = '\0';
120 if (strcasecmp(dup, param->value) == 0) {
121 free(dup);
122 break;
124 free(dup);
127 if (i >= SOUND_MIXER_NRDEVICES) {
128 WARNING("mixer control \"%s\" not found at line %i\n",
129 param->value, param->line);
130 closeOssMixer();
131 return -1;
132 } else if (!((1 << i) & devmask)) {
133 WARNING("mixer control \"%s\" not usable at line %i\n",
134 param->value, param->line);
135 closeOssMixer();
136 return -1;
139 volume_ossControl = i;
142 return 0;
145 static int ensure_oss_open(void)
147 if ((volume_ossFd < 0 && prepOssMixer(volume_mixerDevice) < 0))
148 return -1;
149 return 0;
152 static int getOssVolumeLevel(void)
154 int left, right, level;
156 if (ensure_oss_open() < 0)
157 return -1;
159 if (ioctl(volume_ossFd, MIXER_READ(volume_ossControl), &level) < 0) {
160 closeOssMixer();
161 WARNING("unable to read volume\n");
162 return -1;
165 left = level & 0xff;
166 right = (level & 0xff00) >> 8;
168 if (left != right) {
169 WARNING("volume for left and right is not the same, \"%i\" and "
170 "\"%i\"\n", left, right);
173 return left;
176 static int changeOssVolumeLevel(int fd, int change, int rel)
178 int current;
179 int new;
180 int level;
182 if (rel) {
183 if ((current = getOssVolumeLevel()) < 0) {
184 commandError(fd, ACK_ERROR_SYSTEM,
185 "problem getting current volume");
186 return -1;
189 new = current + change;
190 } else {
191 if (ensure_oss_open() < 0)
192 return -1;
193 new = change;
196 if (new < 0)
197 new = 0;
198 else if (new > 100)
199 new = 100;
201 level = (new << 8) + new;
203 if (ioctl(volume_ossFd, MIXER_WRITE(volume_ossControl), &level) < 0) {
204 closeOssMixer();
205 commandError(fd, ACK_ERROR_SYSTEM, "problems setting volume");
206 return -1;
209 return 0;
211 #endif
213 #ifdef HAVE_ALSA
214 static void closeAlsaMixer(void)
216 snd_mixer_close(volume_alsaMixerHandle);
217 volume_alsaMixerHandle = NULL;
220 static int prepAlsaMixer(char *card)
222 int err;
223 snd_mixer_elem_t *elem;
224 char *controlName = VOLUME_MIXER_ALSA_CONTROL_DEFAULT;
225 ConfigParam *param;
227 err = snd_mixer_open(&volume_alsaMixerHandle, 0);
228 snd_config_update_free_global();
229 if (err < 0) {
230 WARNING("problems opening alsa mixer: %s\n", snd_strerror(err));
231 return -1;
234 if ((err = snd_mixer_attach(volume_alsaMixerHandle, card)) < 0) {
235 closeAlsaMixer();
236 WARNING("problems attaching alsa mixer: %s\n",
237 snd_strerror(err));
238 return -1;
241 if ((err =
242 snd_mixer_selem_register(volume_alsaMixerHandle, NULL,
243 NULL)) < 0) {
244 closeAlsaMixer();
245 WARNING("problems snd_mixer_selem_register'ing: %s\n",
246 snd_strerror(err));
247 return -1;
250 if ((err = snd_mixer_load(volume_alsaMixerHandle)) < 0) {
251 closeAlsaMixer();
252 WARNING("problems snd_mixer_selem_register'ing: %s\n",
253 snd_strerror(err));
254 return -1;
257 elem = snd_mixer_first_elem(volume_alsaMixerHandle);
259 param = getConfigParam(CONF_MIXER_CONTROL);
261 if (param) {
262 controlName = param->value;
265 while (elem) {
266 if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
267 if (strcasecmp(controlName,
268 snd_mixer_selem_get_name(elem)) == 0) {
269 break;
272 elem = snd_mixer_elem_next(elem);
275 if (elem) {
276 volume_alsaElem = elem;
277 snd_mixer_selem_get_playback_volume_range(volume_alsaElem,
278 &volume_alsaMin,
279 &volume_alsaMax);
280 return 0;
283 WARNING("can't find alsa mixer_control \"%s\"\n", controlName);
285 closeAlsaMixer();
286 return -1;
289 static int prep_alsa_get_level(long *level)
291 const char *cmd;
292 int err;
294 if (!volume_alsaMixerHandle && prepAlsaMixer(volume_mixerDevice) < 0)
295 return -1;
297 if ((err = snd_mixer_handle_events(volume_alsaMixerHandle)) < 0) {
298 cmd = "handle_events";
299 goto error;
301 if ((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
302 SND_MIXER_SCHN_FRONT_LEFT,
303 level)) < 0) {
304 cmd = "selem_get_playback_volume";
305 goto error;
307 return 0;
309 error:
310 WARNING("problems getting alsa volume: %s (snd_mixer_%s)\n",
311 snd_strerror(err), cmd);
312 closeAlsaMixer();
313 return -1;
316 static int getAlsaVolumeLevel(void)
318 int ret;
319 long level;
320 long max = volume_alsaMax;
321 long min = volume_alsaMin;
323 if (prep_alsa_get_level(&level) < 0)
324 return -1;
326 ret = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5;
327 if (volume_alsaSet > 0 && ret == level) {
328 ret = volume_alsaSet;
329 } else
330 ret = (int)(100 * (((float)(level - min)) / (max - min)) + 0.5);
332 return ret;
335 static int changeAlsaVolumeLevel(int fd, int change, int rel)
337 float vol;
338 long level;
339 long test;
340 long max = volume_alsaMax;
341 long min = volume_alsaMin;
342 int err;
344 if (prep_alsa_get_level(&level) < 0)
345 return -1;
347 if (rel) {
348 test = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5;
349 if (volume_alsaSet >= 0 && level == test) {
350 vol = volume_alsaSet;
351 } else
352 vol = 100.0 * (((float)(level - min)) / (max - min));
353 vol += change;
354 } else
355 vol = change;
357 volume_alsaSet = vol + 0.5;
358 volume_alsaSet = volume_alsaSet > 100 ? 100 :
359 (volume_alsaSet < 0 ? 0 : volume_alsaSet);
361 level = (long)(((vol / 100.0) * (max - min) + min) + 0.5);
362 level = level > max ? max : level;
363 level = level < min ? min : level;
365 if ((err =
366 snd_mixer_selem_set_playback_volume_all(volume_alsaElem,
367 level)) < 0) {
368 commandError(fd, ACK_ERROR_SYSTEM, "problems setting volume");
369 WARNING("problems setting alsa volume: %s\n",
370 snd_strerror(err));
371 closeAlsaMixer();
372 return -1;
375 return 0;
377 #endif
379 static int prepMixer(char *device)
381 switch (volume_mixerType) {
382 #ifdef HAVE_ALSA
383 case VOLUME_MIXER_TYPE_ALSA:
384 return prepAlsaMixer(device);
385 #endif
386 #ifdef HAVE_OSS
387 case VOLUME_MIXER_TYPE_OSS:
388 return prepOssMixer(device);
389 #endif
392 return 0;
395 void finishVolume(void)
397 switch (volume_mixerType) {
398 #ifdef HAVE_ALSA
399 case VOLUME_MIXER_TYPE_ALSA:
400 closeAlsaMixer();
401 break;
402 #endif
403 #ifdef HAVE_OSS
404 case VOLUME_MIXER_TYPE_OSS:
405 closeOssMixer();
406 break;
407 #endif
411 void initVolume(void)
413 ConfigParam *param = getConfigParam(CONF_MIXER_TYPE);
415 if (param) {
416 if (0) ;
417 #ifdef HAVE_ALSA
418 else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) {
419 volume_mixerType = VOLUME_MIXER_TYPE_ALSA;
420 volume_mixerDevice = VOLUME_MIXER_ALSA_DEFAULT;
422 #endif
423 #ifdef HAVE_OSS
424 else if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) {
425 volume_mixerType = VOLUME_MIXER_TYPE_OSS;
426 volume_mixerDevice = VOLUME_MIXER_OSS_DEFAULT;
428 #endif
429 else if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
430 volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
431 volume_mixerDevice = VOLUME_MIXER_SOFTWARE_DEFAULT;
432 } else {
433 FATAL("unknown mixer type %s at line %i\n",
434 param->value, param->line);
438 param = getConfigParam(CONF_MIXER_DEVICE);
440 if (param) {
441 volume_mixerDevice = param->value;
445 void openVolumeDevice(void)
447 if (prepMixer(volume_mixerDevice) < 0) {
448 WARNING("using software volume\n");
449 volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
453 static int getSoftwareVolume(void)
455 return volume_softwareSet;
458 int getVolumeLevel(void)
460 switch (volume_mixerType) {
461 #ifdef HAVE_ALSA
462 case VOLUME_MIXER_TYPE_ALSA:
463 return getAlsaVolumeLevel();
464 #endif
465 #ifdef HAVE_OSS
466 case VOLUME_MIXER_TYPE_OSS:
467 return getOssVolumeLevel();
468 #endif
469 case VOLUME_MIXER_TYPE_SOFTWARE:
470 return getSoftwareVolume();
471 default:
472 return -1;
476 static int changeSoftwareVolume(int fd, int change, int rel)
478 int new = change;
480 if (rel)
481 new += volume_softwareSet;
483 if (new > 100)
484 new = 100;
485 else if (new < 0)
486 new = 0;
488 volume_softwareSet = new;
490 /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */
491 if (new >= 100)
492 new = 1000;
493 else if (new <= 0)
494 new = 0;
495 else
496 new =
497 1000.0 * (exp(new / 25.0) - 1) / (54.5981500331F - 1) + 0.5;
499 setPlayerSoftwareVolume(new);
501 return 0;
504 int changeVolumeLevel(int fd, int change, int rel)
506 switch (volume_mixerType) {
507 #ifdef HAVE_ALSA
508 case VOLUME_MIXER_TYPE_ALSA:
509 return changeAlsaVolumeLevel(fd, change, rel);
510 #endif
511 #ifdef HAVE_OSS
512 case VOLUME_MIXER_TYPE_OSS:
513 return changeOssVolumeLevel(fd, change, rel);
514 #endif
515 case VOLUME_MIXER_TYPE_SOFTWARE:
516 return changeSoftwareVolume(fd, change, rel);
517 default:
518 return 0;
519 break;
523 void read_sw_volume_state(FILE *fp)
525 /* strlen(SW_VOLUME_STATE) + strlen('100') + '\0' */
526 #define bufsize 16
527 char buf[bufsize];
528 const size_t len = strlen(SW_VOLUME_STATE);
529 char *end = NULL;
530 long int sv;
532 if (volume_mixerType != VOLUME_MIXER_TYPE_SOFTWARE)
533 return;
534 while (myFgets(buf, bufsize, fp)) {
535 if (strncmp(buf, SW_VOLUME_STATE, len))
536 continue;
537 sv = strtol(buf + len, &end, 10);
538 if (mpd_likely(!*end))
539 changeSoftwareVolume(STDERR_FILENO, sv, 0);
540 else
541 ERROR("Can't parse software volume: %s\n", buf);
542 return;
544 #undef bufsize
547 void save_sw_volume_state(FILE *fp)
549 if (volume_mixerType == VOLUME_MIXER_TYPE_SOFTWARE)
550 fprintf(fp, SW_VOLUME_STATE "%d\n", volume_softwareSet);