From 9a70c4d06d51cca24dfb93847d1f82187d454e31 Mon Sep 17 00:00:00 2001 From: Viliam Mateicka Date: Wed, 31 Dec 2008 16:46:41 +0100 Subject: Moving mixers to audio outputs --- NEWS | 1 + doc/mpd.conf.5 | 24 ++- doc/mpdconf.example | 16 +- src/Makefile.am | 3 +- src/audio.c | 55 +++++ src/audio.h | 5 + src/command.c | 6 +- src/main.c | 5 +- src/mixer.h | 33 +++ src/mixer/alsa_mixer.c | 206 +++++++++++++++++++ src/mixer/oss_mixer.c | 197 ++++++++++++++++++ src/output/alsa_plugin.c | 22 +- src/output/oss_plugin.c | 19 ++ src/output_api.h | 12 ++ src/volume.c | 508 +++++++++-------------------------------------- src/volume.h | 11 +- 16 files changed, 675 insertions(+), 448 deletions(-) create mode 100644 src/mixer.h create mode 100644 src/mixer/alsa_mixer.c create mode 100644 src/mixer/oss_mixer.c diff --git a/NEWS b/NEWS index 9ceeae55e..632668ed8 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,5 @@ ver 0.15 - (200?/??/??) +* Rewritten mixer code to support multiple mixers * Add audio archive extraction support: - bzip2 - iso9660 diff --git a/doc/mpd.conf.5 b/doc/mpd.conf.5 index 5feefc36e..935bd9b2f 100644 --- a/doc/mpd.conf.5 +++ b/doc/mpd.conf.5 @@ -157,18 +157,23 @@ Linear interpolator, very fast, poor quality. For an up-to-date list of available converters, please see the libsamplerate documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>). .TP -.B mixer_type -This specifies which mixer to use. The default depends on what audio output -support mpd was built with. +.B mixer_type +This specifies which mixer to use. The default is hardware and depends on +what audio output support mpd was built with. Options alsa and oss are +legacy and should not be used in new configs, but when set mixer_device +and mixer_control will apply. .TP .B mixer_device This specifies which mixer to use. The default for oss is "/dev/mixer"; the -default for alsa is "default". +default for alsa is "default". This option is deprecated and should not be +used. Look at the mix_device option of corresponding output device instead. .TP .B mixer_control This specifies which mixer control to use (sometimes referred to as the "device"). Examples of mixer controls are PCM, Line1, Master, etc. An example -for OSS is "Pcm", and an example for alsa is "PCM". +for OSS is "Pcm", and an example for alsa is "PCM". This option is deprecated +and should not be used. Look at the mix_control option of corresponding +output device instead. .TP .B replaygain If specified, mpd will adjust the volume of songs played using ReplayGain tags @@ -276,6 +281,15 @@ whatever audio format is passed to the audio output. .B device This specifies the device to use for audio output. The default is "default". .TP +.B mix_device +This specifies which mixer to use. The default for oss is "/dev/mixer"; the +default for alsa is "default". +.TP +.B mix_control +This specifies which mixer control to use (sometimes referred to as the +"device"). Examples of mixer controls are PCM, Line1, Master, etc. An example +for OSS is "Pcm", and an example for alsa is "PCM". +.TP .B use_mmap Setting this allows you to use memory-mapped I/O. Certain hardware setups may benefit from this, but most do not. Most users do not need to set this. The diff --git a/doc/mpdconf.example b/doc/mpdconf.example index 2de2e05e5..0b8004237 100644 --- a/doc/mpdconf.example +++ b/doc/mpdconf.example @@ -162,6 +162,8 @@ log_file "~/.mpd/log" # name "My ALSA Device" # device "hw:0,0" # optional # format "44100:16:2" # optional +# mix_device "default" # optional +# mix_control "PCM" # optional #} # # An example of an OSS output: @@ -171,6 +173,8 @@ log_file "~/.mpd/log" # name "My OSS Device" # device "/dev/dsp" # optional # format "44100:16:2" # optional +# mix_device "/dev/mixer" # optional +# mix_control "PCM" # optional #} # # An example of a shout output (for streaming to Icecast): @@ -232,17 +236,9 @@ log_file "~/.mpd/log" # specified it may be autodetected at startup, depending on the dependencies # which were compiled into the server. # -# An example for controlling an ALSA mixer: +# An example for controlling an ALSA or OSS mixer: # -#mixer_type "alsa" -#mixer_device "default" -#mixer_control "PCM" -# -# An example for controlling an OSS mixer: -# -#mixer_type "oss" -#mixer_device "/dev/mixer" -#mixer_control "PCM" +#mixer_type "hardware" # # This example is a general volume control mixer, it is used to adjust the # volume of the audio sent to the audio output, and will work with all outputs. diff --git a/src/Makefile.am b/src/Makefile.am index 2cd3f3924..1c18bc4a1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -273,6 +273,7 @@ endif if HAVE_ALSA mpd_SOURCES += output/alsa_plugin.c +mpd_SOURCES += mixer/alsa_mixer.c endif if HAVE_AO @@ -293,6 +294,7 @@ endif if HAVE_OSS mpd_SOURCES += output/oss_plugin.c +mpd_SOURCES += mixer/oss_mixer.c endif if HAVE_OSX @@ -315,7 +317,6 @@ if HAVE_SHOUT_OGG mpd_SOURCES += output/shout_ogg.c endif - mpd_CFLAGS = $(MPD_CFLAGS) mpd_CPPFLAGS = \ $(CURL_CFLAGS) \ diff --git a/src/audio.c b/src/audio.c index 0567b6452..17e63416a 100644 --- a/src/audio.c +++ b/src/audio.c @@ -25,6 +25,7 @@ #include "client.h" #include "idle.h" #include "utils.h" +#include "mixer.h" #include @@ -428,3 +429,57 @@ errline: } } +bool mixer_control_setvol(unsigned int device, int volume, int rel) +{ + struct audio_output *output; + if (device >= audioOutputArraySize) + return false; + + output = &audioOutputArray[device]; + if (output->plugin && output->plugin->control) { + if (rel) { + int cur_volume; + if (!output->plugin->control(output->data, AC_MIXER_GETVOL, &cur_volume)) { + return false; + } + volume = volume + cur_volume; + } + if (volume > 100) + volume = 100; + else if (volume < 0) + volume = 0; + + return output->plugin->control(output->data, AC_MIXER_SETVOL, &volume); + } + return false; +} + +bool mixer_control_getvol(unsigned int device, int *volume) +{ + struct audio_output *output; + if (device >= audioOutputArraySize) + return false; + + output = &audioOutputArray[device]; + if (output->plugin && output->plugin->control) { + return output->plugin->control(output->data, AC_MIXER_GETVOL, volume); + } + return false; +} + +bool mixer_configure_legacy(char *name, ConfigParam *param) +{ + unsigned i; + struct audio_output *output; + + for (i = 0; i < audioOutputArraySize; ++i) { + output = &audioOutputArray[i]; + if (output && output->plugin && !strcmp(name, output->plugin->name)) { + if (output->plugin->control) { + g_debug("reconfiguring %s mixer\n", name); + return output->plugin->control(output->data, AC_MIXER_CONFIGURE, param); + } + } + } + return false; +} diff --git a/src/audio.h b/src/audio.h index 98c377b7e..c83794689 100644 --- a/src/audio.h +++ b/src/audio.h @@ -21,6 +21,7 @@ #include #include +#include "conf.h" #define AUDIO_AO_DRIVER_DEFAULT "default" @@ -70,4 +71,8 @@ void readAudioDevicesState(FILE *fp); void saveAudioDevicesState(FILE *fp); +bool mixer_control_setvol(unsigned int device, int volume, int rel); +bool mixer_control_getvol(unsigned int device, int *volume); +bool mixer_configure_legacy(char *name, ConfigParam *param); + #endif diff --git a/src/command.c b/src/command.c index 3929978ba..a8dc6284b 100644 --- a/src/command.c +++ b/src/command.c @@ -388,7 +388,7 @@ handle_status(struct client *client, COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" COMMAND_STATUS_CROSSFADE ": %i\n" COMMAND_STATUS_STATE ": %s\n", - getVolumeLevel(), + volume_level_get(), getPlaylistRepeatStatus(), getPlaylistRandomStatus(), getPlaylistVersion(), @@ -906,7 +906,7 @@ handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &change, argv[1], need_integer)) return COMMAND_RETURN_ERROR; - ret = changeVolumeLevel(change, 1); + ret = volume_level_change(change, 1); if (ret == -1) command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); @@ -922,7 +922,7 @@ handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) if (!check_int(client, &level, argv[1], need_integer)) return COMMAND_RETURN_ERROR; - ret = changeVolumeLevel(level, 0); + ret = volume_level_change(level, 0); if (ret == -1) command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); diff --git a/src/main.c b/src/main.c index 23bff7c8a..8ab0b3630 100644 --- a/src/main.c +++ b/src/main.c @@ -264,7 +264,7 @@ int main(int argc, char *argv[]) dc_init(); initAudioConfig(); initAudioDriver(); - initVolume(); + volume_init(); client_manager_init(); replay_gain_global_init(); initNormalization(); @@ -278,7 +278,6 @@ int main(int argc, char *argv[]) initZeroconf(); - openVolumeDevice(); decoder_thread_start(); player_create(); read_state_file(); @@ -315,7 +314,7 @@ int main(int argc, char *argv[]) finishNormalization(); finishAudioDriver(); finishAudioConfig(); - finishVolume(); + volume_finish(); mapper_finish(); path_global_finish(); finishPermissions(); diff --git a/src/mixer.h b/src/mixer.h new file mode 100644 index 000000000..43dc3299d --- /dev/null +++ b/src/mixer.h @@ -0,0 +1,33 @@ + +#ifndef MPD_MIXER_H +#define MPD_MIXER_H + +#include "conf.h" + +/** + * alsa mixer + */ + +struct alsa_mixer; + +struct alsa_mixer *alsa_mixer_init(void); +void alsa_mixer_finish(struct alsa_mixer *am); +void alsa_mixer_configure(struct alsa_mixer *am, ConfigParam *param); +bool alsa_mixer_open(struct alsa_mixer *am); +bool alsa_mixer_control(struct alsa_mixer *am, int cmd, void *arg); +void alsa_mixer_close(struct alsa_mixer *am); + +/** + * oss mixer + */ + +struct oss_mixer; + +struct oss_mixer *oss_mixer_init(void); +void oss_mixer_finish(struct oss_mixer *am); +void oss_mixer_configure(struct oss_mixer *am, ConfigParam *param); +bool oss_mixer_open(struct oss_mixer *am); +bool oss_mixer_control(struct oss_mixer *am, int cmd, void *arg); +void oss_mixer_close(struct oss_mixer *am); + +#endif diff --git a/src/mixer/alsa_mixer.c b/src/mixer/alsa_mixer.c new file mode 100644 index 000000000..506daf794 --- /dev/null +++ b/src/mixer/alsa_mixer.c @@ -0,0 +1,206 @@ + +#include "../output_api.h" +#include "../mixer.h" + +#include +#include + +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" + +struct alsa_mixer { + char *device; + char *control; + snd_mixer_t *handle; + snd_mixer_elem_t *elem; + long volume_min; + long volume_max; + int volume_set; +}; + +struct alsa_mixer * +alsa_mixer_init(void) +{ + struct alsa_mixer *am = g_malloc(sizeof(struct alsa_mixer)); + am->device = NULL; + am->control = NULL; + am->handle = NULL; + am->elem = NULL; + am->volume_min = 0; + am->volume_max = 0; + am->volume_set = -1; + return am; +} + +void +alsa_mixer_finish(struct alsa_mixer *am) +{ + g_free(am); +} + +void +alsa_mixer_configure(struct alsa_mixer *am, ConfigParam *param) +{ + BlockParam *bp; + + if ((bp = getBlockParam(param, "mix_device"))) + am->device = bp->value; + if ((bp = getBlockParam(param, "mix_control"))) + am->control = bp->value; +} + +void +alsa_mixer_close(struct alsa_mixer *am) +{ + if (am->handle) snd_mixer_close(am->handle); + am->handle = NULL; +} + +bool +alsa_mixer_open(struct alsa_mixer *am) +{ + int err; + snd_mixer_elem_t *elem; + const char *control_name = VOLUME_MIXER_ALSA_CONTROL_DEFAULT; + const char *device = VOLUME_MIXER_ALSA_DEFAULT; + + if (am->device) { + device = am->device; + } + err = snd_mixer_open(&am->handle, 0); + snd_config_update_free_global(); + if (err < 0) { + g_warning("problems opening alsa mixer: %s\n", snd_strerror(err)); + return false; + } + + if ((err = snd_mixer_attach(am->handle, device)) < 0) { + g_warning("problems attaching alsa mixer: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + + if ((err = snd_mixer_selem_register(am->handle, NULL, + NULL)) < 0) { + g_warning("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + + if ((err = snd_mixer_load(am->handle)) < 0) { + g_warning("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + + elem = snd_mixer_first_elem(am->handle); + + if (am->control) { + control_name = am->control; + } + + while (elem) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) { + if (strcasecmp(control_name, + snd_mixer_selem_get_name(elem)) == 0) { + break; + } + } + elem = snd_mixer_elem_next(elem); + } + + if (elem) { + am->elem = elem; + snd_mixer_selem_get_playback_volume_range(am->elem, + &am->volume_min, + &am->volume_max); + return true; + } + + g_warning("can't find alsa mixer control \"%s\"\n", control_name); + + alsa_mixer_close(am); + return false; +} + +bool +alsa_mixer_control(struct alsa_mixer *am, int cmd, void *arg) +{ + switch (cmd) { + case AC_MIXER_CONFIGURE: + alsa_mixer_configure(am, (ConfigParam *)arg); + if (am->handle) + alsa_mixer_close(am); + return true; + case AC_MIXER_GETVOL: + { + int err; + int ret, *volume = arg; + long level; + + if (!am->handle && !alsa_mixer_open(am)) { + return false; + } + if ((err = snd_mixer_handle_events(am->handle)) < 0) { + g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", + snd_strerror(err), "handle_events"); + alsa_mixer_close(am); + return false; + } + if ((err = snd_mixer_selem_get_playback_volume(am->elem, + SND_MIXER_SCHN_FRONT_LEFT, &level)) < 0) { + g_warning("problems getting alsa volume: %s (snd_mixer_%s)\n", + snd_strerror(err), "selem_get_playback_volume"); + alsa_mixer_close(am); + return false; + } + ret = ((am->volume_set / 100.0) * (am->volume_max - am->volume_min) + + am->volume_min) + 0.5; + if (am->volume_set > 0 && ret == level) { + ret = am->volume_set; + } else { + ret = (int)(100 * (((float)(level - am->volume_min)) / + (am->volume_max - am->volume_min)) + 0.5); + } + *volume = ret; + return true; + } + case AC_MIXER_SETVOL: + { + float vol; + long level; + int *volume = arg; + int err; + + if (!am->handle && !alsa_mixer_open(am)) { + return false; + } + vol = *volume; + + am->volume_set = vol + 0.5; + am->volume_set = am->volume_set > 100 ? 100 : + (am->volume_set < 0 ? 0 : am->volume_set); + + level = (long)(((vol / 100.0) * (am->volume_max - am->volume_min) + + am->volume_min) + 0.5); + level = level > am->volume_max ? am->volume_max : level; + level = level < am->volume_min ? am->volume_min : level; + + if ((err = snd_mixer_selem_set_playback_volume_all(am->elem, + level)) < 0) { + g_warning("problems setting alsa volume: %s\n", + snd_strerror(err)); + alsa_mixer_close(am); + return false; + } + return true; + } + default: + g_warning("Unsuported alsa control\n"); + break; + } + return false; +} diff --git a/src/mixer/oss_mixer.c b/src/mixer/oss_mixer.c new file mode 100644 index 000000000..ca4027f75 --- /dev/null +++ b/src/mixer/oss_mixer.c @@ -0,0 +1,197 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include "../output_api.h" +#include "../mixer.h" + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" + +struct oss_mixer { + const char *device; + const char *control; + int device_fd; + int volume_control; +}; + +struct oss_mixer *oss_mixer_init(void); +void oss_mixer_finish(struct oss_mixer *am); +void oss_mixer_configure(struct oss_mixer *am, ConfigParam *param); +bool oss_mixer_open(struct oss_mixer *am); +bool oss_mixer_control(struct oss_mixer *am, int cmd, void *arg); +void oss_mixer_close(struct oss_mixer *am); + +struct oss_mixer * +oss_mixer_init(void) +{ + struct oss_mixer *om = g_malloc(sizeof(struct oss_mixer)); + om->device = NULL; + om->control = NULL; + om->device_fd = -1; + om->volume_control = SOUND_MIXER_PCM; + return om; +} + +void +oss_mixer_finish(struct oss_mixer *om) +{ + g_free(om); +} + +void +oss_mixer_configure(struct oss_mixer *om, ConfigParam *param) +{ + BlockParam *bp; + bp = getBlockParam(param, "mix_device"); + if (bp) { + om->device = bp->value; + } + bp = getBlockParam(param, "mix_control"); + if (bp) { + om->control = bp->value; + } +} + +void +oss_mixer_close(struct oss_mixer *om) +{ + if (om->device_fd != -1) + while (close(om->device_fd) && errno == EINTR) ; + om->device_fd = -1; +} + +static int +oss_find_mixer(const char *name) +{ + const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + size_t name_length = strlen(name); + + for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + if (strncasecmp(name, labels[i], name_length) == 0 && + (labels[i][name_length] == 0 || + labels[i][name_length] == ' ')) + return i; + } + return -1; +} + +bool +oss_mixer_open(struct oss_mixer *om) +{ + const char *device = VOLUME_MIXER_OSS_DEFAULT; + + if (om->device) { + device = om->device; + } + + if ((om->device_fd = open(device, O_RDONLY)) < 0) { + g_warning("Unable to open oss mixer \"%s\"\n", device); + return false; + } + + if (om->control) { + int i; + int devmask = 0; + + if (ioctl(om->device_fd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + g_warning("errors getting read_devmask for oss mixer\n"); + oss_mixer_close(om); + return false; + } + i = oss_find_mixer(om->control); + + if (i < 0) { + g_warning("mixer control \"%s\" not found\n", + om->control); + oss_mixer_close(om); + return false; + } else if (!((1 << i) & devmask)) { + g_warning("mixer control \"%s\" not usable\n", + om->control); + oss_mixer_close(om); + return false; + } + om->volume_control = i; + } + return true; +} + +bool +oss_mixer_control(struct oss_mixer *om, int cmd, void *arg) +{ + switch (cmd) { + case AC_MIXER_CONFIGURE: + oss_mixer_configure(om, (ConfigParam *)arg); + //if (om->device_fd >= 0) + oss_mixer_close(om); + return true; + break; + case AC_MIXER_GETVOL: + { + int left, right, level; + int *ret; + + if (om->device_fd < 0 && !oss_mixer_open(om)) { + return false; + } + + if (ioctl(om->device_fd, MIXER_READ(om->volume_control), &level) < 0) { + oss_mixer_close(om); + g_warning("unable to read oss volume\n"); + return false; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + g_warning("volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + ret = (int *) arg; + *ret = left; + return true; + } + case AC_MIXER_SETVOL: + { + int new; + int level; + int *value = arg; + + if (om->device_fd < 0 && !oss_mixer_open(om)) { + return false; + } + + new = *value; + if (new < 0) { + new = 0; + } else if (new > 100) { + new = 100; + } + + level = (new << 8) + new; + + if (ioctl(om->device_fd, MIXER_WRITE(om->volume_control), &level) < 0) { + g_warning("unable to set oss volume\n"); + oss_mixer_close(om); + return false; + } + return true; + } + default: + g_warning("Unsuported oss control\n"); + break; + } + return false; +} diff --git a/src/output/alsa_plugin.c b/src/output/alsa_plugin.c index 57d102c51..5f53729a7 100644 --- a/src/output/alsa_plugin.c +++ b/src/output/alsa_plugin.c @@ -18,6 +18,7 @@ #include "../output_api.h" #include "../utils.h" +#include "../mixer.h" #include #include @@ -51,6 +52,9 @@ typedef struct _AlsaData { unsigned int period_time; int sampleSize; int useMmap; + + struct alsa_mixer *mixer; + } AlsaData; static const char * @@ -71,12 +75,15 @@ static AlsaData *newAlsaData(void) ret->buffer_time = MPD_ALSA_BUFFER_TIME_US; ret->period_time = 0; + ret->mixer = alsa_mixer_init(); + return ret; } static void freeAlsaData(AlsaData * ad) { g_free(ad->device); + alsa_mixer_finish(ad->mixer); free(ad); } @@ -125,8 +132,10 @@ static void *alsa_initDriver(mpd_unused struct audio_output *ao, free_global_registered = 1; } - if (param) + if (param) { alsa_configure(ad, param); + alsa_mixer_configure(ad->mixer, param); + } return ad; } @@ -181,6 +190,8 @@ static bool alsa_openDevice(void *data, struct audio_format *audioFormat) unsigned int period_time, period_time_ro; unsigned int buffer_time; + alsa_mixer_open(ad->mixer); + if ((bitformat = get_bitformat(audioFormat)) == SND_PCM_FORMAT_UNKNOWN) g_warning("ALSA device \"%s\" doesn't support %u bit audio\n", alsa_device(ad), audioFormat->bits); @@ -403,6 +414,7 @@ static void alsa_closeDevice(void *data) snd_pcm_close(ad->pcmHandle); ad->pcmHandle = NULL; } + alsa_mixer_close(ad->mixer); } static bool @@ -436,6 +448,13 @@ alsa_playAudio(void *data, const char *playChunk, size_t size) return true; } +static bool +alsa_control(void *data, int cmd, void *arg) +{ + AlsaData *ad = data; + return alsa_mixer_control(ad->mixer, cmd, arg); +} + const struct audio_output_plugin alsaPlugin = { .name = "alsa", .test_default_device = alsa_testDefault, @@ -445,4 +464,5 @@ const struct audio_output_plugin alsaPlugin = { .play = alsa_playAudio, .cancel = alsa_dropBufferedAudio, .close = alsa_closeDevice, + .control = alsa_control }; diff --git a/src/output/oss_plugin.c b/src/output/oss_plugin.c index 4727823c3..1f582b2b4 100644 --- a/src/output/oss_plugin.c +++ b/src/output/oss_plugin.c @@ -20,6 +20,7 @@ */ #include "../output_api.h" +#include "../mixer.h" #include #include @@ -53,6 +54,7 @@ typedef struct _OssData { int numSupported[3]; int *unsupported[3]; int numUnsupported[3]; + struct oss_mixer *mixer; } OssData; enum oss_support { @@ -273,6 +275,8 @@ static OssData *newOssData(void) supportParam(ret, SNDCTL_DSP_CHANNELS, 2); supportParam(ret, SNDCTL_DSP_SAMPLESIZE, 16); + ret->mixer = oss_mixer_init(); + return ret; } @@ -285,6 +289,8 @@ static void freeOssData(OssData * od) g_free(od->unsupported[OSS_CHANNELS]); g_free(od->unsupported[OSS_BITS]); + oss_mixer_finish(od->mixer); + free(od); } @@ -348,6 +354,7 @@ static void *oss_open_default(ConfigParam *param) if (ret[i] == 0) { OssData *od = newOssData(); od->device = default_devices[i]; + oss_mixer_configure(od->mixer, param); return od; } } @@ -388,6 +395,7 @@ static void *oss_initDriver(mpd_unused struct audio_output *audioOutput, if (bp) { OssData *od = newOssData(); od->device = bp->value; + oss_mixer_configure(od->mixer, param); return od; } } @@ -513,6 +521,8 @@ oss_openDevice(void *data, struct audio_format *audioFormat) od->audio_format.bits, od->audio_format.channels, od->audio_format.sample_rate); + oss_mixer_open(od->mixer); + return ret; } @@ -521,6 +531,7 @@ static void oss_closeDevice(void *data) OssData *od = data; oss_close(od); + oss_mixer_close(od->mixer); } static void oss_dropBufferedAudio(void *data) @@ -559,6 +570,13 @@ oss_playAudio(void *data, const char *playChunk, size_t size) return true; } +static bool +oss_control(void *data, int cmd, void *arg) +{ + OssData *od = data; + return oss_mixer_control(od->mixer, cmd, arg); +} + const struct audio_output_plugin ossPlugin = { .name = "oss", .test_default_device = oss_testDefault, @@ -568,4 +586,5 @@ const struct audio_output_plugin ossPlugin = { .play = oss_playAudio, .cancel = oss_dropBufferedAudio, .close = oss_closeDevice, + .control = oss_control, }; diff --git a/src/output_api.h b/src/output_api.h index 7cefea77e..fb4b096a1 100644 --- a/src/output_api.h +++ b/src/output_api.h @@ -100,6 +100,12 @@ struct audio_output_plugin { */ void (*close)(void *data); + /** + * Control the device. Usualy used for implementing + * set and get mixer levels + */ + bool (*control)(void *data, int cmd, void *arg); + /** * Display metadata for the next chunk. Optional method, * because not all devices can display metadata. @@ -118,6 +124,12 @@ enum audio_output_command { AO_COMMAND_KILL }; +enum audio_control_command { + AC_MIXER_GETVOL = 0, + AC_MIXER_SETVOL, + AC_MIXER_CONFIGURE, +}; + struct audio_output; const char *audio_output_get_name(const struct audio_output *ao); diff --git a/src/volume.c b/src/volume.c index 01976888d..e41d1d0e1 100644 --- a/src/volume.c +++ b/src/volume.c @@ -15,475 +15,141 @@ * 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 "player_control.h" #include "utils.h" #include "idle.h" #include "pcm_utils.h" #include "config.h" +#include "audio.h" #include - #include #include -#ifdef HAVE_OSS -#include -#include -#endif -#ifdef HAVE_ALSA -#include -#endif - #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "volume" #define VOLUME_MIXER_TYPE_SOFTWARE 0 -#define VOLUME_MIXER_TYPE_OSS 1 -#define VOLUME_MIXER_TYPE_ALSA 2 +#define VOLUME_MIXER_TYPE_HARDWARE 1 #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 - -static void closeOssMixer(void) -{ - while (close(volume_ossFd) && errno == EINTR) ; - volume_ossFd = -1; -} +const struct audio_output_plugin *default_mixer; +static int volume_mixer_type = VOLUME_MIXER_TYPE_HARDWARE; +static int volume_software_set = 100; -static int -oss_find_mixer(const char *name) +void volume_finish(void) { - const char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; - size_t name_length = strlen(name); - - for (unsigned i = 0; i < SOUND_MIXER_NRDEVICES; i++) { - if (strncasecmp(name, labels[i], name_length) == 0 && - (labels[i][name_length] == 0 || - labels[i][name_length] == ' ')) - return i; - } - - return -1; } -static int prepOssMixer(const char *device) +static void +mixer_reconfigure(char *driver) { - ConfigParam *param; + ConfigParam *newparam, *param; - if ((volume_ossFd = open(device, O_RDONLY)) < 0) { - g_warning("unable to open oss mixer \"%s\"", device); - return -1; - } + //create parameter list + newparam = newConfigParam(NULL, -1); - if ((param = getConfigParam(CONF_MIXER_CONTROL))) { - int i; - int devmask = 0; - - if (ioctl(volume_ossFd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { - g_warning("errors getting read_devmask for oss mixer"); - closeOssMixer(); - return -1; - } - - i = oss_find_mixer(param->value); - - if (i < 0) { - g_warning("mixer control \"%s\" not found at line %i", - param->value, param->line); - closeOssMixer(); - return -1; - } else if (!((1 << i) & devmask)) { - g_warning("mixer control \"%s\" not usable at line %i", - 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(); - g_warning("unable to read volume"); - return -1; - } - - left = level & 0xff; - right = (level & 0xff00) >> 8; - - if (left != right) { - g_warning("volume for left and right is not the same, \"%i\" " - "and \"%i\"", 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) { - g_warning("problems opening alsa mixer: %s", - snd_strerror(err)); - return -1; - } - - if ((err = snd_mixer_attach(volume_alsaMixerHandle, card)) < 0) { - closeAlsaMixer(); - g_warning("problems attaching alsa mixer: %s", - snd_strerror(err)); - return -1; - } - - if ((err = - snd_mixer_selem_register(volume_alsaMixerHandle, NULL, - NULL)) < 0) { - closeAlsaMixer(); - g_warning("problems snd_mixer_selem_register'ing: %s", - snd_strerror(err)); - return -1; - } - - if ((err = snd_mixer_load(volume_alsaMixerHandle)) < 0) { - closeAlsaMixer(); - g_warning("problems snd_mixer_selem_register'ing: %s", - snd_strerror(err)); - return -1; + param = getConfigParam(CONF_MIXER_DEVICE); + if (param) { + g_warning("deprecated option mixer_device found, translating to %s config section\n", driver); + addBlockParam(newparam, "mix_device", param->value, -1); } - - elem = snd_mixer_first_elem(volume_alsaMixerHandle); - param = getConfigParam(CONF_MIXER_CONTROL); - if (param) { - controlName = param->value; + g_warning("deprecated option mixer_control found, translating to %s config section\n", driver); + addBlockParam(newparam, "mix_control", param->value, -1); } - - while (elem) { - if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) { - if (strcasecmp(controlName, - snd_mixer_selem_get_name(elem)) == 0) { - break; - } + if (newparam->numberOfBlockParams > 0) { + //call configure method of corrensponding mixer + if (!mixer_configure_legacy(driver, newparam)) { + g_error("Using mixer_type '%s' with not enabled %s output", driver, driver); } - 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; } - - g_warning("can't find alsa mixer_control \"%s\"", 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: - g_warning("problems getting alsa volume: %s (snd_mixer_%s)", - 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) { - g_warning("problems setting alsa volume: %s", - 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) +void volume_init(void) { ConfigParam *param = getConfigParam(CONF_MIXER_TYPE); - + //hw mixing is by default 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; + if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) { + volume_mixer_type = VOLUME_MIXER_TYPE_SOFTWARE; + } else if (strcmp(param->value, VOLUME_MIXER_HARDWARE) == 0) { + //nothing to do } else { - g_error("unknown mixer type %s at line %i", - param->value, param->line); + //fallback to old config behaviour + if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) { + mixer_reconfigure(param->value); + } else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) { + mixer_reconfigure(param->value); + } else { + g_error("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) +static int hardware_volume_get(void) { - if (prepMixer(volume_mixerDevice) < 0) { - g_message("using software volume"); - volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE; + int device, count; + int volume, volume_total, volume_ok; + + volume_total = 0; + volume_ok = 0; + + count = audio_output_count(); + for (device=0; device 0) { + //return average + return volume_total / volume_ok; + } else { + return -1; } } -static int getSoftwareVolume(void) +static int software_volume_get(void) { - return volume_softwareSet; + return volume_software_set; } -int getVolumeLevel(void) +int volume_level_get(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 + switch (volume_mixer_type) { case VOLUME_MIXER_TYPE_SOFTWARE: - return getSoftwareVolume(); + return software_volume_get(); + case VOLUME_MIXER_TYPE_HARDWARE: + return hardware_volume_get(); default: return -1; } + return -1; } -static int changeSoftwareVolume(int change, int rel) +static int software_volume_change(int change, int rel) { int new = change; if (rel) - new += volume_softwareSet; + new += volume_software_set; if (new > 100) new = 100; else if (new < 0) new = 0; - volume_softwareSet = new; + volume_software_set = new; /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */ if (new >= 100) @@ -499,21 +165,26 @@ static int changeSoftwareVolume(int change, int rel) return 0; } -int changeVolumeLevel(int change, int rel) +static int hardware_volume_change(int change, int rel) +{ + int device, count; + + count = audio_output_count(); + for (device=0; device