Improve Volume Wheel Daemon (Bounty!)


EvilDragon

Administrator
Staff member
Joined
Mar 4, 2003
Messages
29,986
Age
46
Location
Ingolstadt
This is a small bounty - but it also shouldn't be too much work.
Basically, I'd like to improve the volume wheel daemon aTc wrote so that it uses less power and gives some more improvements.

At the moment, it regularly polls the status of the volume wheel and changes the ALSA hardware volume accordingly.
This uses quite a bit of CPU power and additionally, a hardware volume of 0% in ALSA doesn't disable audio completely.
To my knowledge, modern systems usually have a fixed hardware volume and mostly change the software volume in playback (to minimize noise, etc.) - correct me if I'm wrong.

How can that volume wheel daemon be improved? (at least in my opinion, other opinions are more than welcome)

* Poll the wheel less often while it's not being used to reduce needed CPU power. Of course, when the user turns the wheel, it should poll more often to ensure a smooth volume change. But while it's not being touched, no need to poll it as often. So once the user turns the wheel, increase polling. Then go back to slower polling if the wheel is not being touched again for 4 seconds or something like that.

* Directly change PulseAudio volume instead of ALSA volume (there should be a way to talk directly to the PulseAudio daemon...?)

* Add a configuration to the daemon where the user can set a maximum and minimum possible volume and scale the wheel accordingly (thus, adding more steps to the wheel), set the polling frequencies, etc.


Good idea? Bad idea? Let me know!

Otherwise, the bounty I set for an improved volume wheel daemon would be 200 EUR
 
Huh, it happens that i wrote code that performs exactly that during making that snake game to enable volume wheel control. I just need to ad a proper call to pulse as os.execute is ugly and its fine.
No warenties tho, i just copied this together and have tested nothing, as i cant get internet to my pyra to install gcc.
I run my LED daemons that way tho, it should run.
The combined usage time for 2 hours usage of my LED thing is
785 root 20 0 17100 2196 1968 S 0,0 0,1 0:00.72 lua (thats roughly 10-20% of systemd)
Code:
#!/usr/bin/lua

takt=require"takt"
takt.settime(1)-- 1s base poll time
takt.settok(100) --100 ms fast poll

function Deploy(file) --find and open the device
 local file,_=io.open(file)
 if file then   
  WFile=file
  WFile:setvbuf("no")
  Updatewheel= function() WFile:seek('set') return WFile:read() end
 end   
end
--one of these may be right
Deploy("/sys/bus/iio/devices/iio:device0/in_voltage2_input")
Deploy("/sys/bus/iio/devices/iio:device1/in_voltage2_input")
Deploy("/sys/bus/iio/devices/iio:device2/in_voltage2_input")
Deploy("/sys/bus/iio/devices/iio:device3/in_voltage2_input")
Deploy("/sys/bus/iio/devices/iio:device4/in_voltage2_input")
Lastcheck=tonumber(Updatewheel())

local tik=takt.tik
local tok=takt.tok
while true do
 tik()
 local tmpcheck=tonumber(Updatewheel())
 if math.abs(Lastcheck-tmpcheck)>3 then --A greater number prevents being triggered by flickering at the cost of precicion.
  Lastcheck=tmpcheck
  for x=1,100 do --100 times 100ms
   tmpcheck=tonumber(Updatewheel())
   os.execute("pactl set-sink-volume 0 "..math.floor(100/1220*tmpcheck) .."% ") --OS calls are ugly of course.
   tok()
  end
 end
end
C:
#include "../lua/src/lauxlib.h"
#include <time.h>

time_t delay;
time_t delay2;
struct timespec z,zz;

int tik()
{sleep(delay);
return ;
};

int settime(lua_State *L)// seconds
{delay = lua_tonumber(L, 1);
return ;
};

int tok()
{clock_gettime(CLOCK_REALTIME,&z);
zz.tv_nsec = delay2 - (z.tv_nsec % delay2);
nanosleep(&zz , NULL);
return ;
};

int settok(lua_State *L)//input is time in nano seconds
{zz.tv_sec=0;
delay2 = lua_tonumber(L, 1);
return ;
};

static const struct luaL_Reg fucs[] = {
{"tik",tik},
{"tok",tok},
{"settime",settime},
{"settok",settime},
{NULL,NULL}
};

int luaopen_takt (lua_State *L){ 
    lua_newtable(L);
    luaL_setfuncs (L, fucs, 0);
    return 1;
};
Lol, wonder if that is already enough?
 
Last edited:
If you can get it all plumbed together and working well I'd be happy for you to get the bounty, so long as you put in the work required.

I'm not sure I see a need to vary the polling rate to be honest. Presumably at present it's in effect polling the volume wheel and adjusting the volume every few milliseconds which is excessive. But if that were reduced to only poll the wheel every half second or something like that that might be enough for most people tweaking the volume wheel, and also slow enough to not cost the battery too badly.
 
Its polling once a second, you should learn to read better. If During poll a change is detected it goes into a fast circle.
Varying the settings is obvious... change the numbers.

There probably are bugs anyway... i just had a few beer. But the concept should be clear.
 
Ah, i could not sleep before trying it anyway, so here is a running version.
First kill the original daemon, then do ./volume bla.lua
Sufficent for a test at least.
The IDLE cpu usage is surprisingly shitty tho, should go down a bigger step when using luajit, but its not a drop in replacement.
And the usage cpu spike is simply the wrong call to the pulse tool, one needs to read up on how to control it in C.
EDIT: No its not, its obviously cause i tried to set milliseconds in a microsecond field and bumped up cpu usage by 1000.

Obviously it only needs less cpu than the original daemon because the polling rate is poorly slow and not because it is better or anything, my point here was only to show up how surprisingly easy it is to slam together such a tool.
And that however you write it, the final cpu usage is fully depending on your poll rate.
 

Attachments

  • vol.zip
    93.5 KB · Views: 216
Last edited:
Why PA, though? Isn't there a lower level point of control that could be used, so that it'll also work for those madman, that rather use sndio, just ALSA, or pipewire?
 
Good start :)

Ah, another thing that came to my mind: Maybe disable polling completely while audio is not playing anything is also an idea.
No need to change the volume while nothing plays.
Post automatically merged:

Why PA, though? Isn't there a lower level point of control that could be used, so that it'll also work for those madman, that rather use sndio, just ALSA, or pipewire?

PulseAudio has full audio emulation, most software fully works with that.
And it has the way better controls for routing audio on the fly, for example one stream to headset, another one to bluetooth, a third one to the speakers, etc.
 
or pipewire?
PipeWire's PusleAudio front-end is not limited to audio streams, it fully supports PA's configuration APIs as well (if the version is recent enough, that is), so everything PA also works with PW. It is literally a drop-in replacement.

I don't think it is possible to use any kind of hardware acceleration in a meaningful way without actually replacing PA with PW, though - hardware acceleration has not and will never be supported by PA, the original design idea was that audio hardware acceleration is deprecated and unnecessary. Which is not the case with PW, support for audio hardware acceleration was planned from the very beginning.
 
Last edited:
Then you need to poll if music plays >.<

Guy here has a point. Such features as checking if music plays is highly specific, if you implement it for pulse all those who hate pulse, and there are quiet a lot, will be angry.
 
Looking at the original volume daemon there are two things i can note:

It has this line: if(volume!=pv) which lets it set the volume if the previous value differs.
The problem is that it doesnt do a steady report but usually flickers between two numbers.
This leads to volume changes without the wheel being touched and to higher cpu usage.
Its probably already a major cpu improvement if this could handle slight differences.
And then setting the waiting time a notch up too, i see no reason to "fix it" then anymore.

And "// temporary daemon, should be made part of the input daemon", whats your opinion on putting input stuff together?
That could shave of some cpu and makes stuff like "dont poll when not being used" easier to implement. On the other side killing one thing kills all...

Edit: The current waiting time is 100000, 17000000 is the screen refresh rate. The daemon polls over 100 times faster than anyone can notice :D
 
Last edited:
Technically that's not the original volume daemon, the original is even worse :) ( https://dev.pyra-handheld.com/packages/pyra-scripts/-/blob/master/scripts/letux/volumed )
It was mostly written to get something slightly more efficient as that bash script before trying to get it into the input daemon (which looked to be quite involved to get it in there)

Current waiting time should be equal to 10 times/second though, usleep takes microseconds (1/1.000.000) , not nanoseconds like nanosleep does. (we don't need the accuracy of nanosleep)
Slow enough to not eat too much cpu, fast enough to not notice any lag on volume changes.

the "flickering between two numbers" is minimized by first dividing the raw adc value into the range of the volume control (which is very low, another reason to use pulse and have a much finer control over the volume than the hardware has).
At least on my machine that produces a constant output.

Integrating it into the input daemon is probably still the best thing, since it probably needs to take care of some of the other iio sensors too ( although some of them are already mapped to other input devices using a kernel driver. I don't think any of them have the hardware connections to be able to generate an interrupt when the value changes)
 
Why not use interrupts ?

I think it's not supported by the hardware.

Then you need to poll if music plays >.<

Hm. it might be possible that the system informs the daemon that music is playing, so no polling needed.
Not sure.

Usually, when no audio is playing, the output is being disabled (power saving). That should be known to the system.
I think it's somehow possible to automatically activate or inform processes when something changes...?

But yes, including this into the input daemon would probably be best. Including reacting to the headset switch (to disable the speakers when a headset is plugged it, etc.).
 
Unfortunately polling does need to happen periodically even when nothing is playing. It is a mobile device of course. The volume level you use alone at home probably isn't appropriate for the office or public transport. For example, sitting down in a theater and being asked to silence your mobile device. I would probably prefer rolling the volume wheel down over opening the device to make that adjustment. It sounds like I would need to either start an audio stream at the previous volume just to use the volume wheel or use software controls (while being rude with my bright device illuminating my surroundings). There are many cases where adjusting volume before any playback occurs is necessary.
 
Thanks for explaining @aTc
I happen to mix up units a lot when drunk. I once needed nanoseconds, ironically for sound syncing, and i just sticked with it. Here its clearly too much for the task.
Now if i misinterpreted the file, i dont see why it creates that much cpu usage anymore. Maybe you should not map against the volume range on each poll?

But yes, including this into the input daemon would probably be best. Including reacting to the headset switch (to disable the speakers when a headset is plugged it, etc.).
I strongly warn against putting too much functionality into one process, this is deemed to produce big blobs that need a lot maintaining whenever changes need to be done.

@liveunderwater has an important idea. But there is no need to change the volume before playing, its static within the wheel and the daemon can react quick enough to stop sound right before it plays.
BUT what we need to absolutely avoid are longer sleep periods, imagine you accidentally start a loud file and the volume lacks one sec behind just because it happens to be asleep.
In fact i am all against having long polling times and causing situations where it is not responsive.

One thing that came to my mind is: Do we want to use the wheel exclusive for volume? As said before i already experimented with it as gaming control, i can also see it being used for backlight/LED brightness and such.
I propose having a lock in userspace that programs can create whenever they want to abuse the wheel.
 
BUT what we need to absolutely avoid are longer sleep periods, imagine you accidentally start a loud file and the volume lacks one sec behind just because it happens to be asleep.
In fact i am all against having long polling times and causing situations where it is not responsive.
Yes, exactly my point of reference.
 
Have anyone tried using "inotify" which is linux kernel subsystem for monitoring filesystem changes. Here is an test example that monitors test file in homefolder for changes.

to use this create a test file in home folder, "touch $HOME/test" and run the below script
Bash:
#!/bin/sh

while inotifywait -e modify $HOME/test; do
  notify-send "test is changed"
done
now modify the test file "echo "hello" > $HOME/test" and the script will give notification of change. I guess its atleast better compared to while true loop and maybe this puts less load on CPU but someone has to try it first.
 
Last edited:
Here is quickly modified volumed file for volume wheel, for someone to test,
Bash:
#!/bin/sh
#
# Pyra volume control daemon
#
SCRIPTPATH="$(dirname "$0")"
GPADC=$($SCRIPTPATH/findiio "palmas-gpadc")
WHEEL=$(< $GPADC/in_voltage2_input)

[ "$GPADC" ] || exit 1

LASTVOL="unknown"
MAXWHEEL=1200    # reports 0..1250 mV but leave some margin
while [ "$1" ]
case "$1" in
    "-q" ) QUIET=true
        ;;
    "-nhf" ) NOHANDSFREE=yes
        ;;
    * )    break
        ;;
esac
    shift
done

NUM=$($SCRIPTPATH/findsoundcard "L15")    # twl6040 on Pyra

while inotify -e modify $GPADC/in_voltage2_input
do
    # could apply non-linear scale for more intuitive control
    VOL=$(( 100 * ${WHEEL:-0} / $MAXWHEEL ))
    [ "$VOL" -lt 0 ] && VOL=0
    [ "$VOL" -gt 100 ] && VOL=100
    # should check for abs($VOL-$LASTVOL) > threshold
    if [ "$VOL" != "$LASTVOL" ]
    then # update only if changed since amixer -c "$NUM" command needs some resources
        [ "$QUIET" ] || echo $WHEEL - $VOL%
        if [ "$NOHANDSFREE" -o "$VOL" = 0 ]
        then # turn off
            amixer -c "$NUM" -q set "Handsfree Left Playback" "Off" || exit
            amixer -c "$NUM" -q set "Handsfree Right Playback" "Off" || exit
        else
            amixer -c "$NUM" -q set "Handsfree Left Playback" "HF DAC" || exit
            amixer -c "$NUM" -q set "Handsfree Right Playback" "HF DAC" || exit
            amixer -c "$NUM" -q set "Handsfree" "$VOL%" || exit
        fi
        amixer -c "$NUM" -q set "Headset" "$VOL%" || exit
        amixer -c "$NUM" -q set "Earphone" "$VOL%" || exit
        LASTVOL=$VOL
    fi
done
 
Back
Top