27fad52c6b
LOC reduction and less noise makes things easier for tired old folks to follow.
535 lines
12 KiB
C
535 lines
12 KiB
C
/* the Music Player Daemon (MPD)
|
|
* Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
|
|
* 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 "conf.h"
|
|
#include "log.h"
|
|
#include "player_control.h"
|
|
#include "utils.h"
|
|
#include "os_compat.h"
|
|
|
|
#include "../config.h"
|
|
|
|
#ifdef HAVE_OSS
|
|
#include <sys/soundcard.h>
|
|
#endif
|
|
#ifdef HAVE_ALSA
|
|
#include <alsa/asoundlib.h>
|
|
#endif
|
|
|
|
#define VOLUME_MIXER_TYPE_SOFTWARE 0
|
|
#define VOLUME_MIXER_TYPE_OSS 1
|
|
#define VOLUME_MIXER_TYPE_ALSA 2
|
|
|
|
#define VOLUME_MIXER_SOFTWARE_DEFAULT ""
|
|
#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer"
|
|
#define VOLUME_MIXER_ALSA_DEFAULT "default"
|
|
#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM"
|
|
#define SW_VOLUME_STATE "sw_volume: "
|
|
|
|
#ifdef HAVE_OSS
|
|
#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
|
|
|
|
static int volume_mixerType = VOLUME_MIXER_TYPE_DEFAULT;
|
|
static const char *volume_mixerDevice = VOLUME_MIXER_DEVICE_DEFAULT;
|
|
|
|
static int volume_softwareSet = 100;
|
|
|
|
#ifdef HAVE_OSS
|
|
static int volume_ossFd = -1;
|
|
static int volume_ossControl = SOUND_MIXER_PCM;
|
|
#endif
|
|
|
|
#ifdef HAVE_ALSA
|
|
static snd_mixer_t *volume_alsaMixerHandle;
|
|
static snd_mixer_elem_t *volume_alsaElem;
|
|
static long volume_alsaMin;
|
|
static long volume_alsaMax;
|
|
static int volume_alsaSet = -1;
|
|
#endif
|
|
|
|
#ifdef HAVE_OSS
|
|
|
|
#include <alloca.h> /* only alloca user in mpd atm, may change ... */
|
|
|
|
static void closeOssMixer(void)
|
|
{
|
|
while (close(volume_ossFd) && errno == EINTR) ;
|
|
volume_ossFd = -1;
|
|
}
|
|
|
|
static int prepOssMixer(const char *device)
|
|
{
|
|
ConfigParam *param;
|
|
|
|
if ((volume_ossFd = open(device, O_RDONLY)) < 0) {
|
|
WARNING("unable to open oss mixer \"%s\"\n", device);
|
|
return -1;
|
|
}
|
|
|
|
if ((param = getConfigParam(CONF_MIXER_CONTROL))) {
|
|
const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS;
|
|
int i;
|
|
int devmask = 0;
|
|
|
|
if (ioctl(volume_ossFd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) {
|
|
WARNING("errors getting read_devmask for oss mixer\n");
|
|
closeOssMixer();
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) {
|
|
ssize_t len = strlen(labels[i]);
|
|
char *duplicated = alloca(len + 1);
|
|
|
|
/* eliminate spaces at the end */
|
|
memcpy(duplicated, labels[i], len + 1);
|
|
len -= 2;
|
|
while (len >= 0 && duplicated[len] == ' ')
|
|
duplicated[len--] = '\0';
|
|
if (strcasecmp(duplicated, param->value) == 0)
|
|
break;
|
|
}
|
|
|
|
if (i >= SOUND_MIXER_NRDEVICES) {
|
|
WARNING("mixer control \"%s\" not found at line %i\n",
|
|
param->value, param->line);
|
|
closeOssMixer();
|
|
return -1;
|
|
} else if (!((1 << i) & devmask)) {
|
|
WARNING("mixer control \"%s\" not usable at line %i\n",
|
|
param->value, param->line);
|
|
closeOssMixer();
|
|
return -1;
|
|
}
|
|
|
|
volume_ossControl = i;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ensure_oss_open(void)
|
|
{
|
|
if ((volume_ossFd < 0 && prepOssMixer(volume_mixerDevice) < 0))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int getOssVolumeLevel(void)
|
|
{
|
|
int left, right, level;
|
|
|
|
if (ensure_oss_open() < 0)
|
|
return -1;
|
|
|
|
if (ioctl(volume_ossFd, MIXER_READ(volume_ossControl), &level) < 0) {
|
|
closeOssMixer();
|
|
WARNING("unable to read volume\n");
|
|
return -1;
|
|
}
|
|
|
|
left = level & 0xff;
|
|
right = (level & 0xff00) >> 8;
|
|
|
|
if (left != right) {
|
|
WARNING("volume for left and right is not the same, \"%i\" and "
|
|
"\"%i\"\n", left, right);
|
|
}
|
|
|
|
return left;
|
|
}
|
|
|
|
static int changeOssVolumeLevel(int change, int rel)
|
|
{
|
|
int current;
|
|
int new;
|
|
int level;
|
|
|
|
if (rel) {
|
|
if ((current = getOssVolumeLevel()) < 0)
|
|
return -1;
|
|
|
|
new = current + change;
|
|
} else {
|
|
if (ensure_oss_open() < 0)
|
|
return -1;
|
|
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) {
|
|
closeOssMixer();
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_ALSA
|
|
static void closeAlsaMixer(void)
|
|
{
|
|
snd_mixer_close(volume_alsaMixerHandle);
|
|
volume_alsaMixerHandle = NULL;
|
|
}
|
|
|
|
static int prepAlsaMixer(const char *card)
|
|
{
|
|
int err;
|
|
snd_mixer_elem_t *elem;
|
|
const char *controlName = VOLUME_MIXER_ALSA_CONTROL_DEFAULT;
|
|
ConfigParam *param;
|
|
|
|
err = snd_mixer_open(&volume_alsaMixerHandle, 0);
|
|
snd_config_update_free_global();
|
|
if (err < 0) {
|
|
WARNING("problems opening alsa mixer: %s\n", snd_strerror(err));
|
|
return -1;
|
|
}
|
|
|
|
if ((err = snd_mixer_attach(volume_alsaMixerHandle, card)) < 0) {
|
|
closeAlsaMixer();
|
|
WARNING("problems attaching alsa mixer: %s\n",
|
|
snd_strerror(err));
|
|
return -1;
|
|
}
|
|
|
|
if ((err =
|
|
snd_mixer_selem_register(volume_alsaMixerHandle, NULL,
|
|
NULL)) < 0) {
|
|
closeAlsaMixer();
|
|
WARNING("problems snd_mixer_selem_register'ing: %s\n",
|
|
snd_strerror(err));
|
|
return -1;
|
|
}
|
|
|
|
if ((err = snd_mixer_load(volume_alsaMixerHandle)) < 0) {
|
|
closeAlsaMixer();
|
|
WARNING("problems snd_mixer_selem_register'ing: %s\n",
|
|
snd_strerror(err));
|
|
return -1;
|
|
}
|
|
|
|
elem = snd_mixer_first_elem(volume_alsaMixerHandle);
|
|
|
|
param = getConfigParam(CONF_MIXER_CONTROL);
|
|
|
|
if (param) {
|
|
controlName = param->value;
|
|
}
|
|
|
|
while (elem) {
|
|
if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) {
|
|
if (strcasecmp(controlName,
|
|
snd_mixer_selem_get_name(elem)) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
elem = snd_mixer_elem_next(elem);
|
|
}
|
|
|
|
if (elem) {
|
|
volume_alsaElem = elem;
|
|
snd_mixer_selem_get_playback_volume_range(volume_alsaElem,
|
|
&volume_alsaMin,
|
|
&volume_alsaMax);
|
|
return 0;
|
|
}
|
|
|
|
WARNING("can't find alsa mixer_control \"%s\"\n", controlName);
|
|
|
|
closeAlsaMixer();
|
|
return -1;
|
|
}
|
|
|
|
static int prep_alsa_get_level(long *level)
|
|
{
|
|
const char *cmd;
|
|
int err;
|
|
|
|
if (!volume_alsaMixerHandle && prepAlsaMixer(volume_mixerDevice) < 0)
|
|
return -1;
|
|
|
|
if ((err = snd_mixer_handle_events(volume_alsaMixerHandle)) < 0) {
|
|
cmd = "handle_events";
|
|
goto error;
|
|
}
|
|
if ((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
|
|
SND_MIXER_SCHN_FRONT_LEFT,
|
|
level)) < 0) {
|
|
cmd = "selem_get_playback_volume";
|
|
goto error;
|
|
}
|
|
return 0;
|
|
|
|
error:
|
|
WARNING("problems getting alsa volume: %s (snd_mixer_%s)\n",
|
|
snd_strerror(err), cmd);
|
|
closeAlsaMixer();
|
|
return -1;
|
|
}
|
|
|
|
static int getAlsaVolumeLevel(void)
|
|
{
|
|
int ret;
|
|
long level;
|
|
long max = volume_alsaMax;
|
|
long min = volume_alsaMin;
|
|
|
|
if (prep_alsa_get_level(&level) < 0)
|
|
return -1;
|
|
|
|
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);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int changeAlsaVolumeLevel(int change, int rel)
|
|
{
|
|
float vol;
|
|
long level;
|
|
long test;
|
|
long max = volume_alsaMax;
|
|
long min = volume_alsaMin;
|
|
int err;
|
|
|
|
if (prep_alsa_get_level(&level) < 0)
|
|
return -1;
|
|
|
|
if (rel) {
|
|
test = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5;
|
|
if (volume_alsaSet >= 0 && level == test) {
|
|
vol = volume_alsaSet;
|
|
} else
|
|
vol = 100.0 * (((float)(level - min)) / (max - min));
|
|
vol += change;
|
|
} else
|
|
vol = change;
|
|
|
|
volume_alsaSet = vol + 0.5;
|
|
volume_alsaSet = volume_alsaSet > 100 ? 100 :
|
|
(volume_alsaSet < 0 ? 0 : volume_alsaSet);
|
|
|
|
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) {
|
|
WARNING("problems setting alsa volume: %s\n",
|
|
snd_strerror(err));
|
|
closeAlsaMixer();
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int prepMixer(const char *device)
|
|
{
|
|
switch (volume_mixerType) {
|
|
#ifdef HAVE_ALSA
|
|
case VOLUME_MIXER_TYPE_ALSA:
|
|
return prepAlsaMixer(device);
|
|
#endif
|
|
#ifdef HAVE_OSS
|
|
case VOLUME_MIXER_TYPE_OSS:
|
|
return prepOssMixer(device);
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void finishVolume(void)
|
|
{
|
|
switch (volume_mixerType) {
|
|
#ifdef HAVE_ALSA
|
|
case VOLUME_MIXER_TYPE_ALSA:
|
|
closeAlsaMixer();
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_OSS
|
|
case VOLUME_MIXER_TYPE_OSS:
|
|
closeOssMixer();
|
|
break;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void initVolume(void)
|
|
{
|
|
ConfigParam *param = getConfigParam(CONF_MIXER_TYPE);
|
|
|
|
if (param) {
|
|
if (0) ;
|
|
#ifdef HAVE_ALSA
|
|
else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) {
|
|
volume_mixerType = VOLUME_MIXER_TYPE_ALSA;
|
|
volume_mixerDevice = VOLUME_MIXER_ALSA_DEFAULT;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_OSS
|
|
else if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) {
|
|
volume_mixerType = VOLUME_MIXER_TYPE_OSS;
|
|
volume_mixerDevice = VOLUME_MIXER_OSS_DEFAULT;
|
|
}
|
|
#endif
|
|
else if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) {
|
|
volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
|
|
volume_mixerDevice = VOLUME_MIXER_SOFTWARE_DEFAULT;
|
|
} else {
|
|
FATAL("unknown mixer type %s at line %i\n",
|
|
param->value, param->line);
|
|
}
|
|
}
|
|
|
|
param = getConfigParam(CONF_MIXER_DEVICE);
|
|
|
|
if (param) {
|
|
volume_mixerDevice = param->value;
|
|
}
|
|
}
|
|
|
|
void openVolumeDevice(void)
|
|
{
|
|
if (prepMixer(volume_mixerDevice) < 0) {
|
|
WARNING("using software volume\n");
|
|
volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
|
|
}
|
|
}
|
|
|
|
static int getSoftwareVolume(void)
|
|
{
|
|
return volume_softwareSet;
|
|
}
|
|
|
|
int getVolumeLevel(void)
|
|
{
|
|
switch (volume_mixerType) {
|
|
#ifdef HAVE_ALSA
|
|
case VOLUME_MIXER_TYPE_ALSA:
|
|
return getAlsaVolumeLevel();
|
|
#endif
|
|
#ifdef HAVE_OSS
|
|
case VOLUME_MIXER_TYPE_OSS:
|
|
return getOssVolumeLevel();
|
|
#endif
|
|
case VOLUME_MIXER_TYPE_SOFTWARE:
|
|
return getSoftwareVolume();
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
static int changeSoftwareVolume(int change, int rel)
|
|
{
|
|
int new = change;
|
|
|
|
if (rel)
|
|
new += volume_softwareSet;
|
|
|
|
if (new > 100)
|
|
new = 100;
|
|
else if (new < 0)
|
|
new = 0;
|
|
|
|
volume_softwareSet = new;
|
|
|
|
/*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;
|
|
else
|
|
new =
|
|
1000.0 * (exp(new / 25.0) - 1) / (54.5981500331F - 1) + 0.5;
|
|
|
|
setPlayerSoftwareVolume(new);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int changeVolumeLevel(int change, int rel)
|
|
{
|
|
switch (volume_mixerType) {
|
|
#ifdef HAVE_ALSA
|
|
case VOLUME_MIXER_TYPE_ALSA:
|
|
return changeAlsaVolumeLevel(change, rel);
|
|
#endif
|
|
#ifdef HAVE_OSS
|
|
case VOLUME_MIXER_TYPE_OSS:
|
|
return changeOssVolumeLevel(change, rel);
|
|
#endif
|
|
case VOLUME_MIXER_TYPE_SOFTWARE:
|
|
return changeSoftwareVolume(change, rel);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void read_sw_volume_state(FILE *fp)
|
|
{
|
|
char buf[sizeof(SW_VOLUME_STATE) + sizeof("100") - 1];
|
|
char *end = NULL;
|
|
long int sv;
|
|
|
|
if (volume_mixerType != VOLUME_MIXER_TYPE_SOFTWARE)
|
|
return;
|
|
while (myFgets(buf, sizeof(buf), fp)) {
|
|
if (prefixcmp(buf, SW_VOLUME_STATE))
|
|
continue;
|
|
sv = strtol(buf + strlen(SW_VOLUME_STATE), &end, 10);
|
|
if (mpd_likely(!*end))
|
|
changeSoftwareVolume(sv, 0);
|
|
else
|
|
ERROR("Can't parse software volume: %s\n", buf);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void save_sw_volume_state(FILE *fp)
|
|
{
|
|
if (volume_mixerType == VOLUME_MIXER_TYPE_SOFTWARE)
|
|
fprintf(fp, SW_VOLUME_STATE "%d\n", volume_softwareSet);
|
|
}
|
|
|