diff options
Diffstat (limited to '')
-rw-r--r-- | src/volume.c | 416 |
1 files changed, 416 insertions, 0 deletions
diff --git a/src/volume.c b/src/volume.c new file mode 100644 index 000000000..706b686b6 --- /dev/null +++ b/src/volume.c @@ -0,0 +1,416 @@ +/* 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) { + while(close(volume_ossFd)<0 && errno==EINTR); + 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; + } +} |