Lunar Tech Misadventures 001
Adjusting Android's Audio Volume Curves

2023-01-27

I wrote this post way back in 2021, and it has somehow survived me trying out various static site generators and nuking the project multiple times. I decided that since I have finally settled on how I will make this website (basically bare HTML5/CSS), it would be only right to finally publish it. Keep in mind that this will be a story from 2021.

The problem, or how I started this project

Recently I got a new phone - a Poco X3 128GB ("surya"). Being me, the first thing I did was unlocking the bootloader and slapping a custom ROM on it. I settled for CrDroid, a pretty neat LineageOS-based ROM. Overall it's been a pretty smooth experience, but I have noticed that sometimes even on the lowest volume setting, it was too loud. This was mainly an issue when using my wired headphones (Superlux HD668b). I was quite frustrated with this and decided to try and find a way to make the lower volume level actually lower volume.

Maybe the problem is inherent to how Android handles volume?

At first I thought that Android might just be using a linear scale for volume adjustments instead of a logarithmic one, which would explain the lack of control at lower volumes. I don't fully understand it but it's something about how humans perceive loudness of sounds versus the physical measurements that makes a logarithmic scale give you the perception of a linear change in volume. In any case, if this was the problem, fixing it would be outside of my skill range.

Trying a very simple solution

I decided to try a very non-ideal, but potentially viable solution - just adding more steps to the volume control. I knew in many ROMs this was possible by editing the build.prop file. As it turns out, CrDroid made it easier by just making it a system setting! I increased the amount of volume steps to 45, and while it this solution wasn't perfect (way too much steps in the louder range), it was a step forward. I left it at that because I didn't know a better solution...

Learning how android really controls volume

Fast forward a few months, I got annoyed with this again and decided to find a better solution than just having an insane number of volume levels. I don't remember where I initially learned about this, but at some point I found out that Android actually uses volume curves to map the volume level set by the user to the actual attenuation in milibells. Eventually I ended up looking up "android custom volume curve" and finding the "Configuring Audio Policies" page of Android documentation.

It prompted me to look for the audio policy files on my phone. After digging through the filesystem, I found them in /system/vendor/etc. I took a look at a couple of the XML files in that directory, and there it was, audio_policy_volumes.xml.

The audio volume policy file

There's a sizeable comment at the start of the file which should give you a good idea of what's going on:

<!-- Volume section defines a volume curve for a given use case and device category.
It contains a list of points of this curve expressing the attenuation in Millibels for a given
volume index from 0 to 100.
<volume stream=”AUDIO_STREAM_MUSIC” deviceCategory=””>
<point>0,-9600</point>
<point>100,0</point>
</volume>
-->
          

At first I've tried modifying the first section with deviceCategory="DEVICE_CATEGORY_HEADSET", which didn't work, because the stream I wanted to target was AUDIO_STREAM_MUSIC and I was changing a different one. After realizing that, I quickly located the right one:

<volume stream="AUDIO_STREAM_MUSIC" deviceCategory="DEVICE_CATEGORY_HEADSET">
    <point>1,-6000</point>
    <point>33,-3700</point>
    <point>73,-1350</point>
    <point>100,0</point>
</volume>
          

Here's where I got after 3 iterations of trial and error:

<volume stream="AUDIO_STREAM_MUSIC" deviceCategory="DEVICE_CATEGORY_HEADSET">
    <point>0,-8000</point>
    <point>50,-3700</point>
    <point>73,-1350</point>
    <point>100,0</point>
</volume>
          
As you can see, I've lowered the minimal volume, and moved the -3700mB point from 33% to 50%. This gives me way more control over lower volumes, which was my goal.

How did I modify that file, anyway?

Until now I still haven't talked about a very important detail - how did I actually modify that file. Well at first I tried to just use a file manager with root support, but as it turns out it couldn't manage to remount /vendor as read-write, and so I couldn't modify the file. I knew though that you could use Magisk modules to modify system files, so I looked up it's documentation. Apparently it's relatively easy, and the documentation is pretty good. Here's what the final installable .zip with my module looked like:

.
├── META-INF
│   └── com
│       └── google
│           └── android
│               ├── update-binary   # module_installer.sh from the Magisk repo
│               └── updater-script  # only contains "#MAGISK"
├── module.prop  # contains metadata about the module: id, version, author, etc.
└── system
    └── vendor
        └── etc
            └── audio_policy_volumes.xml # the modified file
          

Final thoughts

Honestly, this was easier than expected. I hadn't thought that I would find a comment explaining how this works in the audio policy file, nor did I know how simple creating Magisk modules is. All in all, this was a cool experience, I made my phone work better and learned a bunch of things along the way!