From fc29b83bf2e6030f60c92a6ee4b8c8971c46e133 Mon Sep 17 00:00:00 2001 From: TermeHansen Date: Thu, 5 Jan 2017 12:07:01 +0100 Subject: [PATCH 1/2] Adding volume_mapping from alsa-utils/alsamixer source: http://git.alsa-project.org/?p=alsa-utils.git;a=blob_plain;f=alsamixer/volume_mapping.c;hb=HEAD http://git.alsa-project.org/?p=alsa-utils.git;a=blob_plain;f=alsamixer/volume_mapping.h;hb=HEAD --- src/mixer/plugins/volume_mapping.cxx | 185 +++++++++++++++++++++++++++ src/mixer/plugins/volume_mapping.hxx | 19 +++ 2 files changed, 204 insertions(+) create mode 100644 src/mixer/plugins/volume_mapping.cxx create mode 100644 src/mixer/plugins/volume_mapping.hxx diff --git a/src/mixer/plugins/volume_mapping.cxx b/src/mixer/plugins/volume_mapping.cxx new file mode 100644 index 0000000000..1c0d7c45e6 --- /dev/null +++ b/src/mixer/plugins/volume_mapping.cxx @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2010 Clemens Ladisch + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * The functions in this file map the value ranges of ALSA mixer controls onto + * the interval 0..1. + * + * The mapping is designed so that the position in the interval is proportional + * to the volume as a human ear would perceive it (i.e., the position is the + * cubic root of the linear sample multiplication factor). For controls with + * a small range (24 dB or less), the mapping is linear in the dB values so + * that each step has the same size visually. Only for controls without dB + * information, a linear mapping of the hardware volume register values is used + * (this is the same algorithm as used in the old alsamixer). + * + * When setting the volume, 'dir' is the rounding direction: + * -1/0/1 = down/nearest/up. + */ + +#define _ISOC99_SOURCE /* lrint() */ +#define _GNU_SOURCE /* exp10() */ +#include "aconfig.h" +#include +#include +#include "volume_mapping.h" + +#ifdef __UCLIBC__ +/* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */ +#define exp10(x) (exp((x) * log(10))) +#endif /* __UCLIBC__ */ + +#define MAX_LINEAR_DB_SCALE 24 + +static inline bool use_linear_dB_scale(long dBmin, long dBmax) +{ + return dBmax - dBmin <= MAX_LINEAR_DB_SCALE * 100; +} + +static long lrint_dir(double x, int dir) +{ + if (dir > 0) + return lrint(ceil(x)); + else if (dir < 0) + return lrint(floor(x)); + else + return lrint(x); +} + +enum ctl_dir { PLAYBACK, CAPTURE }; + +static int (* const get_dB_range[2])(snd_mixer_elem_t *, long *, long *) = { + snd_mixer_selem_get_playback_dB_range, + snd_mixer_selem_get_capture_dB_range, +}; +static int (* const get_raw_range[2])(snd_mixer_elem_t *, long *, long *) = { + snd_mixer_selem_get_playback_volume_range, + snd_mixer_selem_get_capture_volume_range, +}; +static int (* const get_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = { + snd_mixer_selem_get_playback_dB, + snd_mixer_selem_get_capture_dB, +}; +static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long *) = { + snd_mixer_selem_get_playback_volume, + snd_mixer_selem_get_capture_volume, +}; +static int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = { + snd_mixer_selem_set_playback_dB, + snd_mixer_selem_set_capture_dB, +}; +static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = { + snd_mixer_selem_set_playback_volume, + snd_mixer_selem_set_capture_volume, +}; + +static double get_normalized_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel, + enum ctl_dir ctl_dir) +{ + long min, max, value; + double normalized, min_norm; + int err; + + err = get_dB_range[ctl_dir](elem, &min, &max); + if (err < 0 || min >= max) { + err = get_raw_range[ctl_dir](elem, &min, &max); + if (err < 0 || min == max) + return 0; + + err = get_raw[ctl_dir](elem, channel, &value); + if (err < 0) + return 0; + + return (value - min) / (double)(max - min); + } + + err = get_dB[ctl_dir](elem, channel, &value); + if (err < 0) + return 0; + + if (use_linear_dB_scale(min, max)) + return (value - min) / (double)(max - min); + + normalized = exp10((value - max) / 6000.0); + if (min != SND_CTL_TLV_DB_GAIN_MUTE) { + min_norm = exp10((min - max) / 6000.0); + normalized = (normalized - min_norm) / (1 - min_norm); + } + + return normalized; +} + +static int set_normalized_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel, + double volume, + int dir, + enum ctl_dir ctl_dir) +{ + long min, max, value; + double min_norm; + int err; + + err = get_dB_range[ctl_dir](elem, &min, &max); + if (err < 0 || min >= max) { + err = get_raw_range[ctl_dir](elem, &min, &max); + if (err < 0) + return err; + + value = lrint_dir(volume * (max - min), dir) + min; + return set_raw[ctl_dir](elem, channel, value); + } + + if (use_linear_dB_scale(min, max)) { + value = lrint_dir(volume * (max - min), dir) + min; + return set_dB[ctl_dir](elem, channel, value, dir); + } + + if (min != SND_CTL_TLV_DB_GAIN_MUTE) { + min_norm = exp10((min - max) / 6000.0); + volume = volume * (1 - min_norm) + min_norm; + } + value = lrint_dir(6000.0 * log10(volume), dir) + max; + return set_dB[ctl_dir](elem, channel, value, dir); +} + +double get_normalized_playback_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel) +{ + return get_normalized_volume(elem, channel, PLAYBACK); +} + +double get_normalized_capture_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel) +{ + return get_normalized_volume(elem, channel, CAPTURE); +} + +int set_normalized_playback_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel, + double volume, + int dir) +{ + return set_normalized_volume(elem, channel, volume, dir, PLAYBACK); +} + +int set_normalized_capture_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel, + double volume, + int dir) +{ + return set_normalized_volume(elem, channel, volume, dir, CAPTURE); +} diff --git a/src/mixer/plugins/volume_mapping.hxx b/src/mixer/plugins/volume_mapping.hxx new file mode 100644 index 0000000000..d4251d6987 --- /dev/null +++ b/src/mixer/plugins/volume_mapping.hxx @@ -0,0 +1,19 @@ +#ifndef VOLUME_MAPPING_H_INCLUDED +#define VOLUME_MAPPING_H_INCLUDED + +#include + +double get_normalized_playback_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel); +double get_normalized_capture_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel); +int set_normalized_playback_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel, + double volume, + int dir); +int set_normalized_capture_volume(snd_mixer_elem_t *elem, + snd_mixer_selem_channel_id_t channel, + double volume, + int dir); + +#endif From d1bf43986c20a3b63528f941e54924483288fcb4 Mon Sep 17 00:00:00 2001 From: TermeHansen Date: Thu, 5 Jan 2017 12:09:13 +0100 Subject: [PATCH 2/2] Rewrite of AlsaMixerPlugin to use volume_mapping Changed AlsaMixerPlugin to use the get and set normalized functions from volume_mapping of alsa-utils/alsamixer Changed volume_mapping set volume to be for all channels and not per channel added volume_mapping files to Makefile.am --- Makefile.am | 5 +++- src/mixer/plugins/AlsaMixerPlugin.cxx | 40 ++------------------------- src/mixer/plugins/volume_mapping.cxx | 30 +++++++++----------- src/mixer/plugins/volume_mapping.hxx | 2 -- 4 files changed, 20 insertions(+), 57 deletions(-) diff --git a/Makefile.am b/Makefile.am index d05a920a80..e417af5d9b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1378,7 +1378,10 @@ MIXER_API_SRC = \ libmixer_plugins_a_SOURCES = \ src/mixer/plugins/NullMixerPlugin.cxx \ src/mixer/plugins/SoftwareMixerPlugin.cxx \ - src/mixer/plugins/SoftwareMixerPlugin.hxx + src/mixer/plugins/SoftwareMixerPlugin.hxx \ + src/mixer/plugins/volume_mapping.hxx \ + src/mixer/plugins/volume_mapping.cxx + libmixer_plugins_a_CPPFLAGS = $(AM_CPPFLAGS) \ $(ALSA_CFLAGS) \ $(PULSE_CFLAGS) diff --git a/src/mixer/plugins/AlsaMixerPlugin.cxx b/src/mixer/plugins/AlsaMixerPlugin.cxx index a5ce941189..27b0e40a37 100644 --- a/src/mixer/plugins/AlsaMixerPlugin.cxx +++ b/src/mixer/plugins/AlsaMixerPlugin.cxx @@ -29,6 +29,7 @@ #include "util/Domain.hxx" #include "util/RuntimeError.hxx" #include "Log.hxx" +#include "mixer/plugins/volume_mapping.hxx" #include @@ -68,9 +69,6 @@ class AlsaMixer final : public Mixer { snd_mixer_t *handle; snd_mixer_elem_t *elem; - long volume_min; - long volume_max; - int volume_set; AlsaMixerMonitor *monitor; @@ -228,9 +226,6 @@ AlsaMixer::Setup() if (elem == nullptr) throw FormatRuntimeError("no such mixer control: %s", control); - snd_mixer_selem_get_playback_volume_range(elem, &volume_min, - &volume_max); - snd_mixer_elem_set_callback_private(elem, this); snd_mixer_elem_set_callback(elem, alsa_mixer_elem_callback); @@ -242,8 +237,6 @@ AlsaMixer::Open() { int err; - volume_set = -1; - err = snd_mixer_open(&handle, 0); if (err < 0) throw FormatRuntimeError("snd_mixer_open() failed: %s", @@ -272,8 +265,6 @@ int AlsaMixer::GetVolume() { int err; - int ret; - long level; assert(handle != nullptr); @@ -282,43 +273,18 @@ AlsaMixer::GetVolume() throw FormatRuntimeError("snd_mixer_handle_events() failed: %s", snd_strerror(err)); - err = snd_mixer_selem_get_playback_volume(elem, - SND_MIXER_SCHN_FRONT_LEFT, - &level); - if (err < 0) - throw FormatRuntimeError("failed to read ALSA volume: %s", - snd_strerror(err)); - - ret = ((volume_set / 100.0) * (volume_max - volume_min) - + volume_min) + 0.5; - if (volume_set > 0 && ret == level) { - ret = volume_set; - } else { - ret = (int)(100 * (((float)(level - volume_min)) / - (volume_max - volume_min)) + 0.5); - } - - return ret; + return (int)100*get_normalized_playback_volume(elem, SND_MIXER_SCHN_FRONT_LEFT); } void AlsaMixer::SetVolume(unsigned volume) { - float vol; - long level; int err; assert(handle != nullptr); - vol = volume; - - volume_set = vol + 0.5; - - level = (long)(((vol / 100.0) * (volume_max - volume_min) + - volume_min) + 0.5); - level = Clamp(level, volume_min, volume_max); + err = set_normalized_playback_volume(elem, 0.01*volume, 1); - err = snd_mixer_selem_set_playback_volume_all(elem, level); if (err < 0) throw FormatRuntimeError("failed to set ALSA volume: %s", snd_strerror(err)); diff --git a/src/mixer/plugins/volume_mapping.cxx b/src/mixer/plugins/volume_mapping.cxx index 1c0d7c45e6..ac137c4f34 100644 --- a/src/mixer/plugins/volume_mapping.cxx +++ b/src/mixer/plugins/volume_mapping.cxx @@ -31,11 +31,9 @@ */ #define _ISOC99_SOURCE /* lrint() */ -#define _GNU_SOURCE /* exp10() */ -#include "aconfig.h" #include #include -#include "volume_mapping.h" +#include "volume_mapping.hxx" #ifdef __UCLIBC__ /* 10^x = 10^(log e^x) = (e^x)^log10 = e^(x * log 10) */ @@ -77,13 +75,13 @@ static int (* const get_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t snd_mixer_selem_get_playback_volume, snd_mixer_selem_get_capture_volume, }; -static int (* const set_dB[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long, int) = { - snd_mixer_selem_set_playback_dB, - snd_mixer_selem_set_capture_dB, +static int (* const set_dB[2])(snd_mixer_elem_t *, long, int) = { + snd_mixer_selem_set_playback_dB_all, + snd_mixer_selem_set_capture_dB_all, }; -static int (* const set_raw[2])(snd_mixer_elem_t *, snd_mixer_selem_channel_id_t, long) = { - snd_mixer_selem_set_playback_volume, - snd_mixer_selem_set_capture_volume, +static int (* const set_raw[2])(snd_mixer_elem_t *, long) = { + snd_mixer_selem_set_playback_volume_all, + snd_mixer_selem_set_capture_volume_all, }; static double get_normalized_volume(snd_mixer_elem_t *elem, @@ -124,7 +122,6 @@ static double get_normalized_volume(snd_mixer_elem_t *elem, } static int set_normalized_volume(snd_mixer_elem_t *elem, - snd_mixer_selem_channel_id_t channel, double volume, int dir, enum ctl_dir ctl_dir) @@ -140,12 +137,12 @@ static int set_normalized_volume(snd_mixer_elem_t *elem, return err; value = lrint_dir(volume * (max - min), dir) + min; - return set_raw[ctl_dir](elem, channel, value); + return set_raw[ctl_dir](elem, value); } if (use_linear_dB_scale(min, max)) { value = lrint_dir(volume * (max - min), dir) + min; - return set_dB[ctl_dir](elem, channel, value, dir); + return set_dB[ctl_dir](elem, value, dir); } if (min != SND_CTL_TLV_DB_GAIN_MUTE) { @@ -153,7 +150,7 @@ static int set_normalized_volume(snd_mixer_elem_t *elem, volume = volume * (1 - min_norm) + min_norm; } value = lrint_dir(6000.0 * log10(volume), dir) + max; - return set_dB[ctl_dir](elem, channel, value, dir); + return set_dB[ctl_dir](elem, value, dir); } double get_normalized_playback_volume(snd_mixer_elem_t *elem, @@ -168,18 +165,17 @@ double get_normalized_capture_volume(snd_mixer_elem_t *elem, return get_normalized_volume(elem, channel, CAPTURE); } + int set_normalized_playback_volume(snd_mixer_elem_t *elem, - snd_mixer_selem_channel_id_t channel, double volume, int dir) { - return set_normalized_volume(elem, channel, volume, dir, PLAYBACK); + return set_normalized_volume(elem, volume, dir, PLAYBACK); } int set_normalized_capture_volume(snd_mixer_elem_t *elem, - snd_mixer_selem_channel_id_t channel, double volume, int dir) { - return set_normalized_volume(elem, channel, volume, dir, CAPTURE); + return set_normalized_volume(elem, volume, dir, CAPTURE); } diff --git a/src/mixer/plugins/volume_mapping.hxx b/src/mixer/plugins/volume_mapping.hxx index d4251d6987..f781542cfd 100644 --- a/src/mixer/plugins/volume_mapping.hxx +++ b/src/mixer/plugins/volume_mapping.hxx @@ -8,11 +8,9 @@ double get_normalized_playback_volume(snd_mixer_elem_t *elem, double get_normalized_capture_volume(snd_mixer_elem_t *elem, snd_mixer_selem_channel_id_t channel); int set_normalized_playback_volume(snd_mixer_elem_t *elem, - snd_mixer_selem_channel_id_t channel, double volume, int dir); int set_normalized_capture_volume(snd_mixer_elem_t *elem, - snd_mixer_selem_channel_id_t channel, double volume, int dir);