GNOME Bugzilla – Bug 753304
pulsesink: volume does not appear to be linear; violates playbin contract
Last modified: 2015-09-09 13:34:56 UTC
When I set the volume of a pulsesink, it does not appear to be linear, as documented at [1]. Let's start by listing the current system volume "pactl list sinks | grep Volume"... looks like 16%. Ok, play a file with playbin: gst-launch-1.0 playbin uri=file:///file.ogg volume=0.1 When I do 'pactl list sink-inputs' it shows the volume set to 46%. Doesn't appear to be linear to me... a side effect of this is that if my application tries to set the volume to some N, the only really usable values for the user are somewhere between 0 and 0.1/0.2ish, as everything else will tend to be too loud. Probably related to bug 680779. [1] http://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-plugins/html/gst-plugins-base-plugins-playbin.html#GstPlayBin--volume
If you set volume to 0.1, what is the volume in pactl? What is it for 0.2? What is it for 0.3 and 1.0? Let's get an idea about how linear or not it is :)
Also, if you can share you code setting the volume that could help too.
The code setting the volume is the gst-launch line above. ;) I start out by setting the system volume to 3%, then running the command line above, incrementing the volume parameter each time. volume=0.01, system is 22% volume=0.02, system is 27% volume=0.04, system is 34% volume=0.08, system is 43% volume=0.1, system is 46% volume=0.2, system is 58% volume=0.3, system is 67% volume=0.4, system is 74% volume=0.5, system is 79% volume=0.6, system is 84% volume=0.7, system is 89% volume=0.8, system is 93% volume=0.9, system is 97% volume=1.0, system is 100%
That's definitely not linear :)
I agree. It surprises me that nobody else has ran into this problem, though I suspect it's really only significant when the "flat volume" option in PA is enabled -- which seems to be the default now.
Ok, so looking at pulsesink.c, we see that gst_pulsesink_set_volume calls gst_pulse_cvolume_from_linear, which calls pa_sw_volume_from_linear to convert a [0, 1.0] to pulse's [0, 0x10000]. Opening up pulse's source, in src/pulse/volume.c we see the following code for pa_sw_volume_from_linear: return (pa_volume_t) PA_CLAMP_VOLUME((uint64_t) lround(cbrt(v) * PA_VOLUME_NORM)); So, it's doing a cube root on the volume. I can run that through python: for i in range(1,10): print (i/10.0) ** (1/3.0) 0.464158883361 0.584803547643 0.669432950082 0.736806299728 0.793700525984 0.843432665302 0.887904001743 0.928317766723 0.965489384606 Looks like we've found our culprit! So the docs for that says: /** Convert a linear factor to a volume. 0.0 and less is muted while * 1.0 is PA_VOLUME_NORM. This is only valid for software volumes! */ My guess is that the value that playbin is representing as a linear range (volume) is *not* the same as a "linear factor" in pulse's terminology... but then looking around at what to set the volume to, there doesn't seem to be a function to set it to x*PA_VOLUME_NORM, though that would seem to be the desired behavior. Ok, so they have more docs (http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/Developer/Clients/WritingVolumeControlUIs/), and there's this: "The actual mapping of the gradient of pa_volume_t in relation to the linear volume is hidden inside of PA. It's chosen in a way that makes a volume of 32768 actually feel "half as loud" as a volume of 65536. (Or at least we try to map it like this.) The actual algorithm is not considered part of the ABI. I take the freedom to change it any time. You should not assume that the mapping between linear/decibel volumes on one hand and pa_volume_t volumes on the other will always stay the same in future versions. " The problem of course is that setting my system's volume to 50% when my slider says 0.1, seems a bit off.
Created attachment 310963 [details] [review] pulse: Make volume property behave in a linear fashion The volume property on our elements is supposed to apply as a purely linear function, and the application should use the GstStreamVolume interface to apply a cubic mapping to a UI slider if required. Using pa_sw_volume_to/from_linear() does not work as this performs a cubic mapping under the hood, which is not what we want.
Why does the from/to linear API for pulseaudio do cubic stuff under the hood? That seems very confusing
This one is my bad. The cubic mapping is done under the hood while storing the volume _but_, while applying the volume, it is undone. From an API perspective, pa_sw_volume_from_linear() is what we provide to apply a linear multiplication factor. So there is nothing to do here. (this internal conversion exists because if you directly set the volume as slider_value * PA_VOLUME_NORM instead of using the from_linear() API, PA will apply a cubic mapping for you assuming that is what you wanted to do)
Comment on attachment 310963 [details] [review] pulse: Make volume property behave in a linear fashion As mentioned in the previous comment, this patch is wrong, and pa_sw_volume_from/to_linear() is what we should be using.