mpd/src/volume.c
Warren Dukes f515692aa9 fix a bug where getting OS Mixer volume may fail, and we were closing
the fd, but didn't mark anything indicated the fd was close, so chaos
ensued.  Here, we just remove the close() statement and assume it was
just a fluke!!

git-svn-id: https://svn.musicpd.org/mpd/trunk@125 09075e82-0dd4-0310-85a5-a0d7c8717e4f
2004-02-29 08:37:21 +00:00

416 lines
9.3 KiB
C

/* 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
#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"
int volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
char * volume_mixerDevice;
#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;
#endif
#ifndef NO_OSS_MIXER
int prepOssMixer(char * device) {
int devmask = 0;
if((volume_ossFd = open(device,O_RDONLY))<0) {
ERROR("unable to open oss mixer \"%s\"\n",device);
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) {
ERROR("errors getting read_devmask for oss mixer\n");
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';
if(strcmp(dup,getConf()[CONF_MIXER_CONTROL])==0) {
free(dup);
break;
}
free(dup);
}
if(i>=SOUND_MIXER_NRDEVICES) {
ERROR("mixer control \"%s\" not found\n",
getConf()[CONF_MIXER_CONTROL]);
close(volume_ossFd);
return -1;
}
else if(!( ( 1 << i ) & devmask )) {
ERROR("mixer control \"%s\" not usable\n",
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) {
ERROR("unable to read volume\n");
return -1;
}
left = level & 0xff;
right = (level & 0xff00) >> 8;
if(left!=right) {
ERROR("volume for left and right is not the same, \"%i\" and "
"\"%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) {
myfprintf(fp,"%s problem getting current volume\n",
COMMAND_RESPOND_ERROR);
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) {
myfprintf(fp,"%s problems setting volume\n",COMMAND_RESPOND_ERROR);
return -1;
}
return 0;
}
#endif
#ifdef HAVE_ALSA
int prepAlsaMixer(char * card) {
int err;
snd_mixer_elem_t * elem;
if((err = snd_mixer_open(&volume_alsaMixerHandle,0))<0) {
ERROR("problems opening alsa mixer: %s\n",snd_strerror(err));
return -1;
}
if((err = snd_mixer_attach(volume_alsaMixerHandle,card))<0) {
snd_mixer_close(volume_alsaMixerHandle);
ERROR("problems problems attaching alsa mixer: %s\n",
snd_strerror(err));
return -1;
}
if((err = snd_mixer_selem_register(volume_alsaMixerHandle,NULL,NULL))<0) {
snd_mixer_close(volume_alsaMixerHandle);
ERROR("problems snd_mixer_selem_register'ing: %s\n",
snd_strerror(err));
return -1;
}
if((err = snd_mixer_load(volume_alsaMixerHandle))<0) {
snd_mixer_close(volume_alsaMixerHandle);
ERROR("problems snd_mixer_selem_register'ing: %s\n",
snd_strerror(err));
return -1;
}
elem = snd_mixer_first_elem(volume_alsaMixerHandle);
if(getConf()[CONF_MIXER_CONTROL]) {
while(elem) {
if(snd_mixer_elem_get_type(elem)
==SND_MIXER_ELEM_SIMPLE)
{
if(strcmp(getConf()[CONF_MIXER_CONTROL],
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;
}
}
else {
volume_alsaElem = elem;
if(snd_mixer_elem_get_type(volume_alsaElem)
==SND_MIXER_ELEM_SIMPLE)
{
snd_mixer_selem_get_playback_volume_range(
volume_alsaElem,
&volume_alsaMin,&volume_alsaMax);
return 0;
}
}
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;
if((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
SND_MIXER_SCHN_FRONT_LEFT,&level))<0) {
ERROR("problems getting alsa volume: %s\n",snd_strerror(err));
return -1;
}
snd_mixer_selem_get_playback_volume(volume_alsaElem,
SND_MIXER_SCHN_FRONT_LEFT,&level);
ret = (int)(100*(((float)(level-min))/(max-min))+0.5);
return ret;
}
int changeAlsaVolumeLevel(FILE * fp, int change, int rel) {
float vol;
long level;
long max = volume_alsaMax;
long min = volume_alsaMin;
int err;
if((err = snd_mixer_selem_get_playback_volume(volume_alsaElem,
SND_MIXER_SCHN_FRONT_LEFT,&level))<0) {
myfprintf(fp,"%s problems getting volume\n",
COMMAND_RESPOND_ERROR);
ERROR("problems getting alsa volume: %s\n",snd_strerror(err));
return -1;
}
if (rel) {
vol = 100.0*(((float)(level-min))/(max-min));
vol+=change;
}
else
vol = change;
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) {
myfprintf(fp,"%s problems setting volume\n",
COMMAND_RESPOND_ERROR);
ERROR("problems setting alsa volume: %s\n",snd_strerror(err));
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]);
exit(-1);
}
if(strlen((getConf())[CONF_MIXER_DEVICE])) {
volume_mixerDevice = (getConf())[CONF_MIXER_DEVICE];
}
}
void openVolumeDevice() {
if(prepMixer(volume_mixerDevice)<0) {
ERROR("using software volume\n");
volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE;
}
}
int getSoftwareVolume() {
return 50*log((getPlayerSoftwareVolume()*(M_E*M_E-1)/100.0)+1)+0.5;
}
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;
if(rel) new+=getSoftwareVolume();
if(new>100) new = 100;
else if(new<0) new = 0;
new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5;
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:
myfprintf(fp,"%s no volume support!\n",COMMAND_RESPOND_ERROR);
return -1;
}
}