[MOD][AUDIO] biQuads - Utilizing Qualcomm's Audio Codec for Headphone Compensation

Search This thread

gr-e

Member
Nov 9, 2011
41
33
Rostov-on-Don
Hey, thank you for this amazing mod!
I'm having a problem, though. When I boot my Nexus 5 the IIR filter I have in mixer_paths is not enabled for some reason. It works fine when I design and apply the filter in the biQuads app.
I have this problem with both LineageOS 14 and 15.
 

Attachments

  • mixer_paths.xml
    23.5 KB · Views: 20
  • mixer_paths_stock.xml
    19 KB · Views: 6

chdloc

Senior Member
Jul 19, 2010
1,140
1,723
Hey, thank you for this amazing mod!
I'm having a problem, though. When I boot my Nexus 5 the IIR filter I have in mixer_paths is not enabled for some reason. It works fine when I design and apply the filter in the biQuads app.
I have this problem with both LineageOS 14 and 15.

Three things:
  • in your speaker-stereo device, the plumbing is slightly off (see this post). It should be
    Code:
    <ctl name="RX1 MIX1 INP1" value="IIR1" />                                                                                                                                    
    <ctl name="RX7 MIX1 INP1" value="IIR2" />
    (IIR1 filters are applied to the earpiece while IIR2 filters are applied to the main speaker) instead of
    Code:
    <ctl name="RX1 MIX1 INP1" value="IIR1"/>
    <ctl name="RX1 MIX1 INP2" value="IIR2"/>
    <ctl name="RX7 MIX1 INP1" value="RX1"/>
    <ctl name="RX7 MIX1 INP2" value="RX2"/>
    What you have done is some very interesting channel cross-mixing of filtered and unfiltered signals. Unless, of course, this is what you want...
  • make sure that codec power gating is disabled in your kernel, see here.
    Run
    Code:
    find /sys/module -name '*collapse_enable' | xargs cat
    in a root shell. If you see '1', then you will need to go through the steps listed in that post.
  • I'd disable sidetone in your mixer by deleting
    Code:
    <ctl name="IIR1 INP1 MUX" value="DECX"/>
    especially in your adc (i.e. microphone) and dmic-* devices.
 

gr-e

Member
Nov 9, 2011
41
33
Rostov-on-Don
What you have done is some very interesting channel cross-mixing of filtered and unfiltered signals. Unless, of course, this is what you want...
Yeah, it's a dual-mono setup with a high pass filter on the earpiece.
Run
Code:
find /sys/module -name '*collapse_enable' | xargs cat
in a root shell. If you see '1', then you will need to go through the steps listed in that post.
It didnt find anything :(
I'd disable sidetone in your mixer
Not sure, if it's related to the problem, but it didn't help.
 

chdloc

Senior Member
Jul 19, 2010
1,140
1,723
Yeah, it's a dual-mono setup with a high pass filter on the earpiece.

Run

It didnt find anything :(

Not sure, if it's related to the problem, but it didn't help.
Are you using a system-less method to modify your mixer? If so, you may need to modify the system partition directly.
 

chdloc

Senior Member
Jul 19, 2010
1,140
1,723
I noticed that if I press the "Listen" button in biQuads app with no filters set up, it starts using the filter I have in mixer_paths. Until reboot.
So you are saying that even though the "Test" tab shows no designed filters (i.e. "type=N/A") it appears that filters are being applied when you tap "Listen"? Can you show me the output of
Code:
logcat | grep chdloc
after you tap "Listen" along with a screenshot?

Also, after a reboot, play audio using your speakers, execute the following command
Code:
tinymix > /sdcard/tinymix_out.txt
in a root shell while audio is playing and post the resulting text file.
 

gr-e

Member
Nov 9, 2011
41
33
Rostov-on-Don
So you are saying that even though the "Test" tab shows no designed filters (i.e. "type=N/A") it appears that filters are being applied when you tap "Listen"? Can you show me the output of
Code:
logcat | grep chdloc
after you tap "Listen" along with a screenshot?
Code:
hammerhead:/ # logcat | grep chdloc
logcat | grep chdloc
04-25 14:54:58.529   498  2800 I ActivityManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.chdloc.biquads/.biquadsActivity bnds=[640,1164][841,1420]} from uid 10091
04-25 14:54:58.560   498  4077 I ActivityManager: Start proc 4587:com.chdloc.biquads/u0a154 for activity com.chdloc.biquads/.biquadsActivity
04-25 14:55:02.754  4805  4805 D su      : Checking whether app [uid:10154, pkgName: com.chdloc.biquads] is allowed to be root
04-25 14:55:03.248  4587  4587 D chdloc  : codec does not have IIR0
04-25 14:55:03.249  4805  4805 D su      : Finishing su operation for app [uid:10154, pkgName: com.chdloc.biquads]
04-25 14:55:03.254  4587  4587 D chdloc  : should be ([email protected] fc=10000Hz, gain=-3dB, Q=3: tinymix "IIR0 Band1" 255844396 957105853 194802389 957105853 182211329
04-25 14:55:03.254  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band1" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band1" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band2" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band2" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band3" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band3" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band4" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band4" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band5" 268435456 0 0 0 0
04-25 14:55:03.255  4587  4587 D chdloc  : apply_filter_to_codec: tinymix command: /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band5" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : tinymix command to be applied to codec: /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band1" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band1" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band2" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band2" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band3" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band3" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band4" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band4" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band5" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band5" 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band1" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band1" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band2" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band2" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band3" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band3" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band4" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band4" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Band5" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Band5" 268435456 0 0 0 0
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band1" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band1" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band2" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band2" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band3" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band3" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band4" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band4" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 Enable Band5" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 Enable Band5" 1
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR1 INP0 Volume" 84
04-25 14:55:03.256  4587  4587 D chdloc  : /data/user/0/com.chdloc.biquads/files/tinymix "IIR2 INP0 Volume" 84
04-25 14:55:03.272  4847  4847 D su      : Checking whether app [uid:10154, pkgName: com.chdloc.biquads] is allowed to be root
04-25 14:55:03.503  4587  4592 I zygote  : Compiler allocated 8MB to compile void com.chdloc.biquads.biquadsActivity.apply_filter_to_codec(int)
04-25 14:55:04.952  4847  4847 D su      : Finishing su operation for app [uid:10154, pkgName: com.chdloc.biquads]
Also, after a reboot, play audio using your speakers, execute the following command
Code:
tinymix > /sdcard/tinymix_out.txt
in a root shell while audio is playing and post the resulting text file.
tinymix: not found
upd. Installed tinymix, report attached
 

Attachments

  • Screenshot_biQuads_20180425-150004.png
    Screenshot_biQuads_20180425-150004.png
    131.8 KB · Views: 636
  • tinymix_out.txt
    31.9 KB · Views: 30
Last edited:

chdloc

Senior Member
Jul 19, 2010
1,140
1,723
Code:
hammerhead:/ # logcat | grep chdloc
[/QUOTE]
OK, the app does what it is supposed to. There are no filters designed and no filters applied.

[QUOTE]
Installed tinymix, report attached[/QUOTE]
Ahh, you did not enable the filters in your mixer.
Add
[CODE]	<ctl name="IIR1 Enable Band1" value="1" />                                                                                                                                           
	<ctl name="IIR1 Enable Band2" value="1" />                                                                                                                                           
	<ctl name="IIR1 Enable Band3" value="1" />                                                                                                                                           
	<ctl name="IIR1 Enable Band4" value="1" />                                                                                                                                           
	<ctl name="IIR1 Enable Band5" value="1" />                                                                                                                                           
	<ctl name="IIR2 Enable Band1" value="1" />                                                                                                                                           
	<ctl name="IIR2 Enable Band2" value="1" />                                                                                                                                           
	<ctl name="IIR2 Enable Band3" value="1" />                                                                                                                                           
	<ctl name="IIR2 Enable Band4" value="1" />                                                                                                                                           
	<ctl name="IIR2 Enable Band5" value="1" />
to your speaker-stereo device, reboot, and all should be well.
 
  • Like
Reactions: gr-e

Nouty

Member
Aug 14, 2015
18
8
@chdloc
I'm not sure I sent you the correct file! Can you tell me what it really needs? Thanks for you work!

*Oneplus 6 a6000
 

Attachments

  • mixer_paths_i2s.xml
    11 KB · Views: 18
  • Screenshot_MiXplorer_20180831-190127.png
    Screenshot_MiXplorer_20180831-190127.png
    231.2 KB · Views: 385
Last edited:

chdloc

Senior Member
Jul 19, 2010
1,140
1,723
@chdloc
I'm not sure I sent you the correct file! Can you tell me what it really needs? Thanks for you work!

*Oneplus 6 a6000
I'm guessing it is mixer_paths_tavil.xml
To know for sure you would need to look at the logcat immediately after the phone has booted and look for a reference to the mixer, i.e.
Code:
logcat | grep mixer_paths
 

LazerL0rd

Senior Member
Nov 2, 2016
1,363
1,150
Aberdeen, United Kingdom
www.thezest.dev
Will this work on Oroe LGV20 H918 magisk 17.3

Not on your Hi-fi DAC.

---------- Post added at 08:56 PM ---------- Previous post was at 08:55 PM ----------

hi, i dont find (/system/etc/mixer_paths.xml) in my pixel XL with Lineage 16. Any idea...? :)
Possibly mixer_paths_tasha.xml or mixer_paths_<something>.xml where the something is your sound card codename, and check /vendor/etc/ too.
 

Top Liked Posts

  • There are no posts matching your filters.
  • 53
    UPDATE
    Things have gotten a little easier: A native Android app that replaces the below script mess along with the Tasker-generated app is now available here. Unfortunately, you will still need to manually update your mixer_paths.xml file.


    Newer devices and/or kernels now utilize a codec feature that is incompatible with biQuads. If after appropriate modification of your phone's mixer file you only hear audio coming out of your right headphone channel, then I suggest you read this post for a fix.

    This mod is not just another collection of black-box sound "enhancement" libraries. There are plenty of those in this forum. The uniqueness of this mod is its ability to offer highly flexible and truly personalized headphone compensation filters. The potential of this proposition becomes clear when you realize that audio "quality" is experienced on a very subjective/individual level. The final compensation filters as obtained by the attached app simply require a modification of a single text file (mixer_paths.xml) that control the IIR filter as offered by the audio codec (Qualcomm only!). The audio mod itself is independent of kernel, ROM, and audio playback software used.


    Introduction

    The aim of this mod is to offer the user of a rooted Qualcomm-based Android device running Lollipop or later the ability to take full advantage of the IIR filter implementation offered by Qualcomm audio codecs (WCD93xx). The IIR filters are implemented in hardware as a cascade of five biquads that can be designed independently. Before diving into your own filter designs, you may want to visit this website to play with an interactive visualization of biquad filters. The mod and associated app aims to provide a similar design experience right on your device.

    The advantages of using Qualcomm's hardware filter as opposed to other equalization filters offered by software implementations, such as Viper and friends are:
    • the filters are implemented in hardware and independent of the ROM, kernel, and media playback software used
    • the additional impact on battery usage is virtually non-existent
    • no black box: the user has full control over the design; the filters can be designed to avoid any distortions to the audio signal

    Installation instructions

    • install the attached app
      [*]make sure you have busybox installed
      [*]flash the attached zip using a custom recovery (tested with TWRP) Update: For devices using the Qualcomm WCD9335 and newer you will need to install an updated version, which is available here
    • prepare your mixer (/system/etc/mixer_paths.xml) to accept the hardware filter. This is the most complex part of this mod. Unfortunately, this needs to be done manually as each device is (potentially) different. This post hosts the relevant sections of various devices, which will hopefully be updated regularly with new devices.
    • Newer devices and/or kernels now utilize a codec feature that is incompatible with biQuads. If after appropriate modification of your phone's mixer file you only hear audio coming out of your right headphone channel, then I suggest you read this post for a fix.

    Operation of the software

    This software is very useful to (within reason) design compensation filters for your headphone's frequency response. For example, many in-ear-monitors (IEMs) suffer from some level of high-frequency boost due to ear canal resonances. These resonances can be effectively "eliminated" by attenuating the offending frequencies. Each offending frequency (or frequency range) is then taken care of by designing its own biquad. You can find measured frequency responses (that you can use as a reference) of a large number of headphones either on GoldenEars, Inner Fidelity, or in forums like Head-Fi.

    Open the biQuads app and you will see the main screen that allows you to enter the parameters of each of the five available biquads, see the example section below. Enter the parameters and tap "Design Biquad". You can repeat this process for up to five biquads, but you can also leave any number of biquads at their default, which is a simple bypass (does not change the frequency response at all). A biquad that is kept at its default values is marked with a white IIR X (X=1,2,...,5) text label. Once a biquad has been designed the text turns green. A designed biquad filter can be reset to its default value by tapping the corresponding green text label. Note that your most recent filter design will remain in memory and will even survive a reboot.

    Once you are happy with your biquad design you can tap the "Review Filter" button. This action will combine all previously designed biquads (green text labels) to the final filter which can then be applied to the audio codec ("Listen" button) while you are listening to music. Once you are completely satisfied with your filter you can hit the "Save" button which will allow you to save the filter coefficients along with a screenshot of the final frequency response along with all design parameters. You will then need to manually copy the contents of the saved filter coefficients file to your (modified) mixer_paths.xml file. The format of the coefficients file is such you can simply copy and paste into your mixer_path.xml file.

    Please keep an eye on the maximum gain applied by the overall filter. In order to avoid distortions introduced by that gain, I highly recommend to appropriately lower the playback volume; each volume "tick" in the upper playback range corresponds to 3 dB. For example if your overall filter adds a maximum of 3 dB of gain, the playback level should be kept below the highest Volume setting to avoid non-linear distortions.

    Limitations of this software
    • this app has been created with the incredible Tasker software. While Tasker is extremely powerful, exported apps are not exactly pretty and they do not show up in the "Recent Apps" view. (At least not this app) may not scale well on certain screen resolutions.
    • unfortunately, mixer_path edits are close to impossible to automate and some manual edits are necessary
    • when listening to the effect of the designed filters, keep in mind that the filters will reset to whatever (if any) filter has been hardcoded in your mixer_path.xml file with any audio stream change (fast forward, skip, etc.). You will have to tap the "Listen" button to, again, apply the designed filters.
    • not necessarily a limitation of this app, but a limitation of using the Qualcomm-provided IIR filter for headphone compensation: any other reference to IIR filtering in your mixer_paths.xml file should be removed. The original use-case for the IIR filter is sidetone compensation for telephony.
    • due to the nature of this implementation (Tasker executing shell scripts and writing files that don't sit in /system), root privileges are frequently requested. I have seen instances where SuperSU runs out of memory to spawn new "root shells". At this point, no app will work that relies on root privileges and yo have to reboot your device. I'm not sure whether this is a Tasker or SuperSU problem.


    A design example with screenshots

    Below is a screenshot of the parameter entry screen for a peak filter (IIR/biquad 1) at 7 kHz with a gain of -6.0 dB (attenuation) and Q=1.0:
    thumbnail



    Below is a screenshot of the frequency response screen (after tapping the "Design Biquad" button) for a peak filter (IIR/biquad 1) at 7 kHz with a gain of -6.0 dB (attenuation) and Q=1.0:
    thumbnail


    Below is a screenshot of the parameter entry screen for a lowpass (LP) shelf filter (IIR/biquad 2) at 200 Hz with a gain of 3.0 dB and Q=1.0:
    thumbnail


    Below is a screenshot of the frequency response screen (after tapping the "Design Biquad" button) for a lowpass (LP) shelf filter (IIR/biquad 2) at 200 Hz with a gain of 3.0 dB and Q=1.0:
    thumbnail


    Below is a screenshot of the overall designed filter (after tapping the "Review Filter" button) of the two biquads designed above:
    thumbnail



    Useful posts in this thread

    Tested devices
    • Nexus 5 (my own and only device)
    • Samsung Note 4 (Qualcomm)
    • HTC M9

    Acknowledgements
    Many thanks to my good friends @bjrmd and @avs333 whose comments and time spent on debugging have been incredibly valuable to the project.

    Changelog
    • Version 1.0 (December 12, 2015): Initial revision
    • Version 1.0.1 (December 14, 2015): added tinymix binary required to apply filters to the codec from within the app
    • Version 1.1 (December 20, 2015): added feature: the gain introduced by the IIR filter can now be compensated for. This is particularly important when playing back audio at high levels and designing biquads that introduce overall gain, particularly at low frequencies. Now you can long-tap the "Listen" button to force gain compensation. This update also includes a few bugfixes. Please reflash the attached zip.
    • Version 1.1.1 (December 21, 2015): for as of yet unknown reasons, the tinymix binary that I've packaged with the last two releases does not seem to work with Android version prior to Marshmallow. This release packages the tinymix attached to the second post, which has been built from Lollipop sources and still works on Marshmallow. Thanks @bjrmd for reporting the issue!
    • Version 1.2 (December 29, 2015): app now allows for applying any of the designed biquads to either the left channel or right channel only, which paths the way for compensation of asymmetric hearing and/or physically different transducers (loudspeakers on the phone). This mode can be enabled by tapping the filter parameter text box on the "Review Filter" screen. The filters are still applied to both channels simultaneously by default. Note that, at this stage, the filters are still saved for both channels, regardless of what playback mode has been selected. Some manual labor is required to transfer the final filters to your mixer_paths.xml file. Note that you will need additional edits to your mixer_paths file to support built-in speaker compensation. See this post for an example pertaining to the Nexus 5.
    • Version 1.2.1 (March 9, 2016): small update to address a potential problem writing data (filter coefficients) to external storage. Thanks @bjrmd for reporting the issue!
    • Version 1.2.2 (April 4, 2016): update that fixes filter coefficient overflow inside the codec that may occur with a limited number of design parameter combinations. Yet again, I'm indebted to @bjrmd for pointing out the issue.
    • (New) Version 1.0 (November 17, 2017): First release of a native Android app doing the same as the previous version 1.2.2. This app now self-contained and root-method-agnostic, so there is no need to flash anything anymore.
    19
    Some Theory

    The following hidden text shows the original two posts of this thread for those of you who are interested in some gory details

    Let me start off by pointing out that all of this is very technical and that there may be a steep learning curve. So please fire away with all your questions, so I can improve on the clarity of this and the following few posts.

    Modern Qualcomm audio codecs, such as the WCD9310 (used, e.g., in the Nexus 4), the WCD9320 (used, e.g., in the Nexus 5), or the WCD9330 (used, e.g., in the Nexus 6, Nexus 5x, and Nexus 6p) offer the option to apply hardware filtering of the audio signals passing through the chip. Qualcomm offers two software-customizable 10-th order infinite-impulse response (IIR) filter, implemented as a cascade of five direct-form I biquad IIR filters, also known as second-order sections (SOS).

    What are the advantages of using hardware filters instead of filters implemented in software at the Android level?
    • as the filters are implemented in hardware, there is no (maybe minute?) additional impact on battery life
    • as the filters are implemented at the lowest possible level, there is, in principle, no dependence on the media player, ROM, or kernel. Hence, a useful application of this IIR filter may be to compensate (within reasonable levels) one's favorite headphones for any media player. Please see this post for a suggestion on how you could measure your headphones (and hearing) to derive your own compensation filter.
    • the filter coefficients are supplied in the human-readable mixer_paths.xml file which can be modified by any knowledgeable user. More on that below.
    • if designed properly, the filters will not introduce any non-linear distortions; the user has full control over the design (see post #3)
    • a very simple channel cross-feed can be implemented. Cross-feed can help some folks that suffer from listening fatigue when listening to music via headphones.


    As there is no documentation available that details the audio filtering options offered by the Qualcomm codecs, I spent some time reverse engineering.
    After some quality time, I have succeeded in enabling the hardware audio filters inside the Qualcomm for equalization purposes which may be useful for any device that sports a Qualcomm codec. My examples below are tailored toward the Nexus 5, but porting this to other devices should be relatively straightforward.

    (If you are reading this for the first time it may be instructional to jump to post #3 now, which details a practical example, before continuing to read this post)

    The following details the current status of my investigations.


    Qualcomm's biquad implementation is given by the following difference equation:
    Code:
    a0*y[n] = b0*x[n] + b1*x[n-1] + b2*x[x-2] - a1*y[n-1] - a2*y[n-2]
    a0 is implicitly set to 1 internally, so it does not need to be defined explicitly.
    Hence, each SOS is fully represented by five parameters, i.e. b0, b1, b2, a1, and a2.

    The fixed-point numbers defining the filter coefficients seem to be stored in Q28 format, i.e. 1.0 corresponds to 2^{28}=268435456


    Example: Highpass filter w/ cutoff @ 100Hz
    • Matlab design: elliptic IIR highpass; f_stop=80Hz, f_pass=100Hz, A_stop=40dB, A_pass=1dB; order = 5, sections: 3
      SOS1: b0=1, b1=-1.99989100262078, b2=1, a0=1, a1=-1.99852109834172, a2=0.998692521205726
      SOS2: b0=1, b1=-1.99994495162412, b2=1, a0=1, a1=-1.99015476348031, a2=0.990440326636932
      SOS3: b0=1, b1=-1 b2=0, a0=1, a1=-0.966597321365327, a2=0
    • SOS fixed point <=> floor(SOS*268435456)
      SOS1: b0=268435456, b1=-536841654, b2=268435456, a0=268435456, a1=-536473923, a2=268084482
      SOS2: b0=268435456, b1=-536856136, b2=268435456, a0=268435456, a1=-534228102, a2=265869300
      SOS3: b0=268435456, b1=-268435456, b2=0, a0=268435456, a1=-259468993, a2=0
    • id="0" <=> b0, id="1" <=> b1, id="2" <=> b2, id="3" <=> a1, id="4"<=> a2
    • keep all positive numbers as is and omit a0
    • negative numbers: two's complement, i.e. convert absolute number to binary, flip bits and add 1;
      the two MSB are reserved (cf. wcd9320.c @ function set_iir_band_coeff() )!
      example:
      -536841654 (b1 of first SOS)
      0001 1111 1111 1111 1000 1101 1011 0110 (converted absolute number, i.e. 536841654, to 32-bit binary)
      1110 0000 0000 0000 0111 0010 0100 1001 (flipped bits of 32-bit binary)
      0010 0000 0000 0000 0111 0010 0100 1010 (flip bits + 1; top two bits reserved, i.e. zero'ed)
      536900170 (converted back to decimal)


    What follows is a new "headphone" section for a Nexus 5 in mixer_paths.xml file with modified routing and the IIR filters set to be in "passthrough" mode, i.e. the data flows through the filters, but remain unmodified. In other words, b0=1.0, a1=a2=b1=b2=0.0, i.e. y[n]=x[n]. This new headphone section can now be used to accept any newly designed IIR filter on-the-fly for testing (Warning: make sure that the filters are stable before trying to route audio through them!). When a final filter is found the filter coefficients need to be hard-coded into the mixer_paths.xml file.

    Code:
        <path name="headphones">
            <ctl name="SLIM RX1 MUX" value="AIF1_PB" />
            <ctl name="SLIM RX2 MUX" value="AIF1_PB" />
            <ctl name="SLIM_0_RX Channels" value="Two" />
    <!-- begin stock routing (commented out) -->
            <!-- <ctl name="RX1 MIX1 INP1" value="RX1" /> -->
            <!-- <ctl name="RX2 MIX1 INP1" value="RX2" /> -->
    <!-- end stock routing (commented out) -->
            <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1" />
            <ctl name="HPHL DAC Switch" value="1" />
    
    <!-- enabling IIR filter; coefficients can be modified on-the-fly with tinymix, see below -->
       <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
            <ctl name="IIR1 INP1 MUX" value="RX1" />
            <ctl name="IIR2 INP1 MUX" value="RX2" />	
       <!-- IIR1 filter cofficients (pass-through) -->
            <ctl name="IIR1 Band1" id ="0" value="268435456" />
            <ctl name="IIR1 Band1" id ="1" value="0" />   
            <ctl name="IIR1 Band1" id ="2" value="0" />   
            <ctl name="IIR1 Band1" id ="3" value="0" />   
            <ctl name="IIR1 Band1" id ="4" value="0" />
            <ctl name="IIR1 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band2" id ="1" value="0" />  
            <ctl name="IIR1 Band2" id ="2" value="0" />   
            <ctl name="IIR1 Band2" id ="3" value="0" />   
            <ctl name="IIR1 Band2" id ="4" value="0" />
            <ctl name="IIR1 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band3" id ="1" value="0" />  
            <ctl name="IIR1 Band3" id ="2" value="0" />   
            <ctl name="IIR1 Band3" id ="3" value="0" />   
            <ctl name="IIR1 Band3" id ="4" value="0" />
            <ctl name="IIR1 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band4" id ="1" value="0" />  
            <ctl name="IIR1 Band4" id ="2" value="0" />   
            <ctl name="IIR1 Band4" id ="3" value="0" />   
            <ctl name="IIR1 Band4" id ="4" value="0" />
            <ctl name="IIR1 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band5" id ="1" value="0" />  
            <ctl name="IIR1 Band5" id ="2" value="0" />   
            <ctl name="IIR1 Band5" id ="3" value="0" />   
            <ctl name="IIR1 Band5" id ="4" value="0" />
            <ctl name="IIR1 Enable Band1" value="1" />
            <ctl name="IIR1 Enable Band2" value="1" />
            <ctl name="IIR1 Enable Band3" value="1" />
            <ctl name="IIR1 Enable Band4" value="1" />
            <ctl name="IIR1 Enable Band5" value="1" />
       <!-- IIR2 filter coefficients; same as IIR1 -->
            <ctl name="IIR2 Band1" id ="0" value="268435456" />
            <ctl name="IIR2 Band1" id ="1" value="0" />   
            <ctl name="IIR2 Band1" id ="2" value="0" />   
            <ctl name="IIR2 Band1" id ="3" value="0" />   
            <ctl name="IIR2 Band1" id ="4" value="0" />
            <ctl name="IIR2 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band2" id ="1" value="0" />  
            <ctl name="IIR2 Band2" id ="2" value="0" />   
            <ctl name="IIR2 Band2" id ="3" value="0" />   
            <ctl name="IIR2 Band2" id ="4" value="0" />
            <ctl name="IIR2 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band3" id ="1" value="0" />  
            <ctl name="IIR2 Band3" id ="2" value="0" />   
            <ctl name="IIR2 Band3" id ="3" value="0" />   
            <ctl name="IIR2 Band3" id ="4" value="0" />
            <ctl name="IIR2 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band4" id ="1" value="0" />  
            <ctl name="IIR2 Band4" id ="2" value="0" />   
            <ctl name="IIR2 Band4" id ="3" value="0" />   
            <ctl name="IIR2 Band4" id ="4" value="0" />
            <ctl name="IIR2 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band5" id ="1" value="0" />  
            <ctl name="IIR2 Band5" id ="2" value="0" />   
            <ctl name="IIR2 Band5" id ="3" value="0" />   
            <ctl name="IIR2 Band5" id ="4" value="0" />
            <ctl name="IIR2 Enable Band1" value="1" />
            <ctl name="IIR2 Enable Band2" value="1" />
            <ctl name="IIR2 Enable Band3" value="1" />
            <ctl name="IIR2 Enable Band4" value="1" />
            <ctl name="IIR2 Enable Band5" value="1" />
       <!-- IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
            <ctl name="RX1 MIX1 INP1" value="IIR1" />
            <ctl name="RX2 MIX1 INP1" value="IIR2" />
       <!-- set IIR1 and IIR2 gain (84<=>0dB; lower this parameter appropriately if any of the biquads introduce gain) -->
            <ctl name="IIR1 INP1 Volume" value="84" />
            <ctl name="IIR2 INP1 Volume" value="84" />
    
     <!-- cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
            <ctl name="IIR1 INP2 MUX" value="RX2" />                                                                                                        
            <ctl name="IIR2 INP2 MUX" value="RX1" />  
     <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
            <ctl name="IIR1 INP2 Volume" value="60" />                                                                                                      
            <ctl name="IIR2 INP2 Volume" value="60" />
    
    <!-- end test filter --> 
    
    <!-- back to stock below -->
            <ctl name="RX1 Digital Volume" value="84" />
            <ctl name="RX2 Digital Volume" value="84" />
            <!-- lowered the HPHL[R] Volume from 15 to 10 to significantly reduce THD -->
            <ctl name="HPHL Volume" value="10" />
            <ctl name="HPHR Volume" value="10" />
        </path>
    Note that the entries pertaining to cross-feed are optional and do not work on the Nexus 5 stock kernel as the AOSP codec driver is missing the plumbing for multiple inputs.
    I noticed that the "headphones" section on Shamu and Mako do look different. Other devices will likely differ from the Hammerhead one, too. I should be able to help folks getting their "headphones" section on non-Hammerhead devices in shape.

    Once booted with the modified headphone section, the filters can be modified on-the-fly by using tinymix:
    Code:
    # first, disable the filter to make sure that the filter does not become unstable during the update process!
    tinymix "IIR1 Enable Band1" 0
    tinymix "IIR1 Enable Band2" 0
    tinymix "IIR1 Enable Band3" 0
    tinymix "IIR1 Enable Band4" 0
    tinymix "IIR1 Enable Band5" 0
    tinymix "IIR2 Enable Band1" 0
    tinymix "IIR2 Enable Band2" 0
    tinymix "IIR2 Enable Band3" 0
    tinymix "IIR2 Enable Band4" 0
    tinymix "IIR2 Enable Band5" 0
    # second, write new IIR1 filter (for the left audio channel)
    tinymix "IIR1 Band1" 268435456 537978230 268435456 778747325 114847078
    tinymix "IIR1 Band2" 268435456 542902433 268435456 601441125 243151927
    tinymix "IIR1 Band3" 268435456 0 0 0 0
    tinymix "IIR1 Band4" 268435456 0 0 0 0
    tinymix "IIR1 Band5" 268435456 0 0 0 0
    # third, write new IIR2 filter (for the right audio channel); you typically want IIR2 to be the same filter as IIR1
    tinymix "IIR2 Band1" 268435456 537978230 268435456 778747325 114847078
    tinymix "IIR2 Band2" 268435456 542902433 268435456 601441125 243151927
    tinymix "IIR2 Band3" 268435456 0 0 0 0
    tinymix "IIR2 Band4" 268435456 0 0 0 0
    tinymix "IIR2 Band5" 268435456 0 0 0 0
    # fourth, enable the filter
    tinymix "IIR1 Enable Band1" 1
    tinymix "IIR1 Enable Band2" 1
    tinymix "IIR1 Enable Band3" 1
    tinymix "IIR1 Enable Band4" 1
    tinymix "IIR1 Enable Band5" 1
    tinymix "IIR2 Enable Band1" 1
    tinymix "IIR2 Enable Band2" 1
    tinymix "IIR2 Enable Band3" 1
    tinymix "IIR2 Enable Band4" 1
    tinymix "IIR2 Enable Band5" 1

    NOTE: the filter coefficients shown above just provide a proof-of-concept. The designed filter in this particular example eliminates all signals below 1000 Hz to make the effect apparent. I will make Octave scripts (a free Matlab alternative) available with which you can design you own filters in a later post. To go back to passthrough mode use
    Code:
    tinymix "IIR1 Enable Band1" 0
    tinymix "IIR1 Enable Band2" 0
    tinymix "IIR1 Enable Band3" 0
    tinymix "IIR1 Enable Band4" 0
    tinymix "IIR1 Enable Band5" 0
    tinymix "IIR2 Enable Band1" 0
    tinymix "IIR2 Enable Band2" 0
    tinymix "IIR2 Enable Band3" 0
    tinymix "IIR2 Enable Band4" 0
    tinymix "IIR2 Enable Band5" 0
    tinymix "IIR1 Band1" 268435456 0 0 0 0 
    tinymix "IIR1 Band2" 268435456 0 0 0 0
    tinymix "IIR1 Band3" 268435456 0 0 0 0
    tinymix "IIR1 Band4" 268435456 0 0 0 0
    tinymix "IIR1 Band5" 268435456 0 0 0 0
    tinymix "IIR2 Band1" 268435456 0 0 0 0
    tinymix "IIR2 Band2" 268435456 0 0 0 0 
    tinymix "IIR2 Band3" 268435456 0 0 0 0
    tinymix "IIR2 Band4" 268435456 0 0 0 0
    tinymix "IIR2 Band5" 268435456 0 0 0 0
    tinymix "IIR1 Enable Band1" 1
    tinymix "IIR1 Enable Band2" 1
    tinymix "IIR1 Enable Band3" 1
    tinymix "IIR1 Enable Band4" 1
    tinymix "IIR1 Enable Band5" 1
    tinymix "IIR2 Enable Band1" 1
    tinymix "IIR2 Enable Band2" 1
    tinymix "IIR2 Enable Band3" 1
    tinymix "IIR2 Enable Band4" 1
    tinymix "IIR2 Enable Band5" 1


    Note that restarting the audio stream will default to the filters currently present in the mixer_paths.xml file.

    Known issues
    • the above implementation seems to be incompatible with any type of hotword detection: if audio is playing through headphones and the user goes back to the home screen with hotword detection enabled causes the codec to freak out. The exact reason is as of yet unknown, but it is believed to be related to the new routing scheme. Bottom line is that the music will stop playing through the headphones (and it does not come back automatically) if hotword detection is being activated, no matter how. Audio will resume only after a swap of the audio route, e.g. by unplugging the headphones, has been forced. Everything will work as expected if audio is first stopped, Google Now invoked manually, followed by resuming audio playback after the Google Now interaction has ended. Hotword detection now works happily alongside IIR filtering. First, however, the reference to "IIR1 INP1 MUX" needs to be removed from the entire device path that is used for the hotword detection device in mixer_paths.xml. On hammerhead, it is the "voice-rec-mic" device. You may need to trace all the way back to one of the "adcX" devices, where X={1,2,3,...}
    • the above implementation may not work with any "advanced" audio player that is capable of direct manipulation of the soundcard and configuring it to sampling rates higher than 48 kHz. I did get it to work, however, while writing audio (up to 24bit/192kHz) to the audio codec directly via ALSA. One thing to keep in mind, however, is that the sampling frequency is a filter design parameter. Hence, if desired/necessary, separate filters need to be designed for 48, 96, and 192 kHz sampling rate.
    • going from 16 bit to 24 bit sample resolution causes a drop of 48 dB in level when playing back a stereo signal via ALSA directly (libasound and alsa_aplay; not sure about libtinyalsa and tinyplay as I have not gotten it to work yet).
      I believe this is due to the fact that the ALSA library presents the 24 bit in the lower 3 Bytes while the codec looks at the top 3 Bytes for its digital-to-analog conversion. The codec's digital volume control ("RX Volume" and "IIR INP Volume") can be used to apply the missing 48 dB of gain, i.e. shifting the lower 3 Bytes to the top 3 Bytes.
    • audio needs to be played once with IIR filtering disabled immediately after the sampling rate has been changed (48 <-> 96 <-> 192).
    • The IIR filter gain parameter, "IIR1/2 INP1 Volume", seems to be ignored. Please see this post for further details.
    • The IIR filter seems to add a 180 degree phase shift to the signals, which is perceptually irrelevant as long as both channels undergo the same phase shift.




    The IIR filter is implemented inside the Qualcomm audio codec as a cascade of five biquad filters. A straightforward design of the IIR filter can be achieved by designing each individual biquad separately, where each section "takes care" of a certain region of the frequency response to be modified. For example, imagine you have a headphone that exhibits a resonance (peak) in its frequency response. One of the five biquads can be designed to reduce the gain around that frequency to the desired level. I will be giving examples in the third post below.

    This post discusses, how the biquads -- and the resulting IIR filter to be written to the codec -- can be designed.

    Prerequisites
    • familiarize yourself with the types of biquads available and how each of them change the frequency response with a set of parameters. There is an excellent website available that offers a very nice interface for playing around with any of the biquad designs
    • once you have a rough understanding of biquads, download and install Octave. Once installed, you will also need the "signal package", see here and here
    • download the attached archive and extract the two Octave/Matlab scripts, wcd93xx_sos.m and wcd93xx_filter.m
    • depending on whether you are running a lollipop- or kitkat-based ROM, download the appropriate archive that contains the tinymix binary (part of AOSP, but not installed by default). Copy it to, e.g. /system/bin, and make executable (744).

    Now, launch Octave and type (after navigating to the directory where the two scripts reside)
    Code:
    help wcd93xx_sos
    which will show some details on how to use this function to design individual biquads. For example, let's assume we would like to reduce a resonance around 6.5 kHz by about 6 dB for 48 kHz sampling rate. One possible option would be
    Code:
    [biquad1, SOS1]=wcd93xx_sos(6500,3,-6,48000,7);
    which will show you the magnitude and phase response of the biquad.
    You can now design up to five biquads to "deal" with deficiencies in the frequency response you are trying to equalize (lowpass, highpass, bandpass, notch filter, or shelf filter). Make sure you call each biquad with different output parameters [biquadX, SOSX] where X={1,2,3,4,5}. Once you have a set of biquads you deem appropriate, call the script wcd93xx_filter which will simply ask you how many biquads you have designed to generate the cascade and to plot the overall magnitude and phase response. This script also prints a series tinymix commands with which the filters can be applied to the Qualcomm audio codec while music is playing through your headphones, but only if the "headphones" section from the previous post had been added to your mixer_paths.xml file. The series of commands as well as the above "headphones" section target the Nexus 5, but they may work with other phones either unaltered, or with only minor modifications. Please post your "headphones" section of /system/etc/mixer_paths.xml if you run into trouble.

    I understand that all of this may be overwhelming and confusing. To address this, I will walk you through a complete design procedure in the next post in great detail.

    Some basic design guidelines
    • be very careful when applying any gains (AdB>0) as you may end up with bad distortions. If you do need amplification of certain frequencies or ranges of frequencies, you may want to appropriately reduce the IIR filter gain first (mixer variable: "IIR1/2 INP1 Volume", see next post)
    • <to be completed>

    Make sure you make a backup of your /system/etc/mixer_paths.xml file (and know how to restore it if things go south) first before attempting to change anything.


    Update (07/31/2015): small update to the Matlab/Octave scripts. A bug has been fixed that would show the overall compensation filter using 48 kHz sampling rate only.
    16
    Real-world Filter Examples - Part 1: Apple Earpods

    In this post I will be detailing the design process for the current Apple earpods. Note that the resulting design just serves as an example and is only meant as a starting point for your own designs and is by no means "optimal". A possible compensation filter design for the Sennheiser IE80 IEMs is detailed here.

    Before we start, let me get this out of the way:
    I highly suggest you not "go crazy" with the compensation filters as the danger of introducing distortion is high if you don't know what you are doing. I rather see the option to apply hardware filtering as a fine tuning tool. If your headphones are lacking quality I'd highly recommend finding headphones whose "sound signature" you like out-of-the-box. Filtering will not magically make make $5 headphones sound like $100 ones. Design flaws cannot be compensated for by filters. Also, please read posts #1 and #2 for some general information, prerequisites, and the current incompatibility of this approach with hotword detection.

    Step 1: Find/measure the frequency response of the headphones you'd like to equalize

    If you cannot find frequency responses of your headphones online and have no means to measure it (see here for a suggestion on how to make relative measurements), trust your ears and experiment.
    There are plenty of measurements available for the Apple earpods, and I measured them myself with a lab-grade sound recording device.
    Below is the resulting measurement.
    3284932.jpg

    Note that there is virtually no signal being visible below 1 kHz. The reason for this is that I simply held the microphone of my recording device very close to one of the earbuds. Earbuds and in-ear-monitors (IEMs) rely on the fact that they sit either in the ear or in the ear canal which will provide some level of seal that helps with amplifying the low frequencies that cannot be measured in "free-field". Hence, low frequency response is critically dependent on the eartips used. When listening to these earpods I noticed some harshness which I attribute to the two peaks (resonances) at around 2 kHz and 6.5 kHz. I will be addressing these two "shortcomings" with the below filter design.

    Step 2: Design the biquads

    You will either need to have Matlab (somewhat unlikely as it is very expensive for non-students) or Octave (see previous post) installed.
    I will be showing the process with Matlab, but the commands for Octave are identical. The plots are formatted slightly different, though.

    Step 2.1: First biquad - notch filter to reduce the gain around 6.5 kHz by 6 dB

    Type
    Code:
    help wcd93xx_sos
    to learn about the options that the biquad design tool offers.
    A possible way to design the notch filter is
    Code:
    [biquad1, SOS1]=wcd93xx_sos(6500,3,-6,48000,7);
    The design tool check for filter stability (should always be) and minimum phase and returns the result on the command line. A filter that is not minimum phase may introduce artifacts to the audio, see e.g. here.
    The resulting filter that is returned by the design tool is shown below
    3284933.jpg

    You can see that, indeed, the biquad attenuates signals around 6.5 kHz. The width of the "cone" can be controlled by the Q-factor, which is the second input parameter to the wcd93xx_sos function (chosen to be 3 in this example). This plot also shows how the phase of a signal traveling through this filter will be modified. Phase modifications are less audible than amplitude modifications, and I do not expect this particular filter having much audible effect due to it modifying the phase.

    Step 2.2: Second biquad - notch filter to reduce the gain around 2 kHz by 3 dB

    Similarly to the above steps, this biquad can be designed with
    Code:
    [biquad2, SOS2]=wcd93xx_sos(2000,3,-3,48000,7);
    and the resulting filter is shown below
    3284934.jpg


    Step 2.3-2.5: third-to-fifth biquad - any additional designs
    This particular example does not have any additional filters, but up to three more biquads can be incorporated. Make sure that you call them with the following command as the output parameters have to have a specified name (biquad and SOS)
    Code:
    [biquadN, SOSN]=wcd93xx_sos(F0,Q,AdB,fs,type);
    where N={3,4,5}.
    Important NOTE: be very careful if you decide to choose AdB>0, i.e. if you want to apply gain to the signal. It is important to understand that music material is mastered to take advantage of the entire (well, at least the top-end of the) dynamic range. This means that loud sections of music will be very close to digital full-scale which will "max out" the digital representation of the signal. Any further digital amplification of these signals will invariably lead to clipping, which in turn will introduce non-linear distortions. This problem is precisely the reason why I typically avoid any type of sound "enhancement" apps/libraries. If you do feel that amplification of certain frequencies or ranges of frequencies is warranted, then you should appropriately lower the input gain of the IIR filter, i.e. the mixer setting "IIRx INP1 Volume" where x={1,2} in your mixer_paths.xml. The neutral gain setting, i.e. 0 dB, is 84. Each "tick" corresponds to 1 dB. For instance, setting this parameter to 78 will lower the gain by 6 dB while setting this parameter to 90 will increase the gain by 6 dB.

    UPDATE: please read this post on limitations of the IIR filter gain, i.e. "IIR1/2 INP1 Volume" as specified in mixer_paths.xml

    Step 3: Get overall filter and coefficients suitable for the Qualcomm codec

    Within the same Matlab/Octave session, type
    Code:
    wcd93xx_filter
    You will be asked how many biquads you have designed. In this example the answer would be two and the script plots the overall filter (cascade of two biquads):
    3284935.jpg

    The script also prints a series of mixer settings which should be copied to an executable shell script, e.g. /data/local/tmp/apply_filter.sh
    Code:
    tinymix "IIR1 Enable Band1" 0
    tinymix "IIR1 Enable Band2" 0
    tinymix "IIR1 Enable Band3" 0
    tinymix "IIR1 Enable Band4" 0
    tinymix "IIR1 Enable Band5" 0
    tinymix "IIR2 Enable Band1" 0
    tinymix "IIR2 Enable Band2" 0
    tinymix "IIR2 Enable Band3" 0
    tinymix "IIR2 Enable Band4" 0
    tinymix "IIR2 Enable Band5" 0
    tinymix "IIR1 Band1" 248299376 772991194 207835511 772991194 187699432
    tinymix "IIR1 Band2" 264612186 580454136 246076806 580454136 242253536
    tinymix "IIR1 Band3" 268435456 0 0 0 0
    tinymix "IIR1 Band4" 268435456 0 0 0 0
    tinymix "IIR1 Band5" 268435456 0 0 0 0
    tinymix "IIR2 Band1" 248299376 772991194 207835511 772991194 187699432
    tinymix "IIR2 Band2" 264612186 580454136 246076806 580454136 242253536
    tinymix "IIR2 Band3" 268435456 0 0 0 0
    tinymix "IIR2 Band4" 268435456 0 0 0 0
    tinymix "IIR2 Band5" 268435456 0 0 0 0
    tinymix "IIR1 Enable Band1" 1
    tinymix "IIR1 Enable Band2" 1
    tinymix "IIR1 Enable Band3" 1
    tinymix "IIR1 Enable Band4" 1
    tinymix "IIR1 Enable Band5" 1
    tinymix "IIR2 Enable Band1" 1
    tinymix "IIR2 Enable Band2" 1
    tinymix "IIR2 Enable Band3" 1
    tinymix "IIR2 Enable Band4" 1
    tinymix "IIR2 Enable Band5" 1
    Now you can run the script (or multiple ones if you want to do A/B comparisons, a script for passthrough mode can be obtained from the OP) while audio is playing through your headphones. Note, that you will need to have IIR filtering enabled by having copied the modified "headphones" section to your mixer_paths.xml, see OP. Note also that the posted "headphones" section is taken from the Nexus 5. I do expect other phones have very similar if not identical "headphones" sections.
    I applied this example filter to the Apple earpods and measured the resulting frequency response, which is shown as the blue curve in the first plot of this post. Personally, I believe this filter removes some of the harshness in the sound signature of these headphones.

    Step 4: Hardcode your final filter design

    Once you are happy with the filters you have designed you need to copy the coefficients to your mixer_paths.xml file and reboot, so they are applied whenever audio is playing.
    15
    Unlocking the hardware filter in the Qualcomm Snapdragon?

    Ignoring Bluetooth audio for the time being, there are two ways of playing audio on Android
    1. Let Android do all the work (decoding, fx, volume control, and mixing) and send audio to the codec (ADM)
    2. Let the aDSP do the heavy lifting (decoding, fx, volume control, and mixing) and have it send the final audio stream to the codec (ASM)
    The ASM kernel-aDSP interface offers the ability to apply fx to the audio stream. One of the fx options are 12 biquads (they can be queried with tinymix). Unfortunately, the stock kernel on Nexus and Pixel devices cannot take advantage of the biquads as only the MultiMedia1, MultiMedia2, and MultiMedia3 DAI front-ends are defined/enabled. To use the biquads one needs to, at a minimum, enable the plumbing for MultiMedia4 EQ in the kernel, which is the one dedicated to offload processing.

    To make a long story short, the biquads in the aDSP would even theoretically only be accessible when audio is being offloaded to the aDSP. That does not work, however, as the required plumbing is not present. So even if the plumbing were present, only very few apps would be making use of it as most apps do not offload.


    This is very much work in progress. Some random thoughts and discoveries are listed below.
    (The details refer to the Nexus 5, but should be generally applicable to any Snapdragon-based device)

    • There is another set of biquad (?) filters available inside the Snapdragon chip; they show up in tinymix (and in the kernel sources @ msm-pcm-routing.c) as MultiMediaX EQ BandY, where X={1,2,3} and Y=[1(1)12]. This means that the filters are only applicable to streams that make use of one of the first three MultiMedia streams. However, by default, music is routed through MultiMedia5, which is not eligible for filtering. This filter may be a 24-th order IIR filter which would allow more degrees of freedom than the IIR filters in the codec would.
      Simply changing the stream from MulitMedia5 to MultiMedia1 in mixer_paths.xml (section: low-latency-playback) does not work, kernel log error: "MSM8974 LowLatency: asoc: MSM8974 LowLatency no valid playback route from source to sink". Where in the kernel sources is the backend-to-frontend forced to use MultiMedia5 for low-latency-playback?
      logcat error:
      D/audio_hw_primary( 193): enable_snd_device: snd_device(5: headphones)
      D/audio_hw_primary( 193): enable_audio_route: apply and update mixer path: low-latency-playback
      E/audio_hw_primary( 193): start_output_stream: cannot open device '/dev/snd/pcmC0D15p': Invalid argument

      The device #15 is hard-coded in the audio HAL (audio_hw.c: [USECASE_AUDIO_PLAYBACK_LOW_LATENCY] = {15, 15} and msm8974/platform.h: #define LOWLATENCY_PCM_DEVICE 15)

      # cat /proc/asound/card0/pcm15p/info
      card: 0
      device: 15
      subdevice: 0
      stream: PLAYBACK
      id: MultiMedia5 (*)
      [...]

      MultiMedia1 is mapped to pcm0p, MultiMedia2 is mapped to pcm1p, MultiMedia3/4 are not mapped anywhere
      If the music is offloaded, then it seems to always bypass the MultiMedia mixer and directly connect to MM_DL4

      The only way to enable the filters seems to change the hardcoded device number in the audio HAL from 15 to either 0 (MultiMedia1) or 1 (MultiMedia2) and make the corresponding changes in mixer_paths.xml (point to MultiMedia1/2 for the low-latency-playback use-case).
      Filters will only be available in the non-offloaded case.


      UPDATE: applied the above modifications using MultiMedia2; music plays fine, but kernel log reads
      <3>[ 101.532335] q6asm_get_audio_client: invalid session: -1065112576
      <3>[ 101.532453] msm_send_eq_values: Could not get audio client for session: -1065112576
      and the filters never get applied; also, the maximum allowed value for the first filter parameter seems to be 65535; hence the filter implementation differs from the IIR in the WCD93xx codec.
    • Lots of debug information in /sys/kernel/debug/asoc/msm8974-taiko-mtp-snd-card and /sys/devices/sound.29/

    Some new findings:
    Indeed, the filters in the ADSP are a set of biquads, each of which expects the following five parameters (msm-pcm-routing-v2.c):
    • first parameter: band_indx: ranges from 0 to 11
    • second parameter: filter_type: the following types are defined in apr_audio-v2.h
      /* No equalizer effect.*/
      #define ASM_PARAM_EQYPE_NONE 0
      /* Bass boost equalizer effect.*/
      #define ASM_PARAM_EQ_BASS_BOOST 1
      /*Bass cut equalizer effect.*/
      #define ASM_PARAM_EQ_BASS_CUT 2
      /* Treble boost equalizer effect */
      #define ASM_PARAM_EQREBLE_BOOST 3
      /* Treble cut equalizer effect.*/
      #define ASM_PARAM_EQREBLE_CUT 4
      /* Band boost equalizer effect.*/
      #define ASM_PARAM_EQ_BAND_BOOST 5
      /* Band cut equalizer effect.*/
      #define ASM_PARAM_EQ_BAND_CUT 6
    • third parameter: center_freq_hz
    • fourth parameter: filter_gain: -12 to +12 dB in 1 dB increments; format unclear
    • fifth parameter: q_factor: expects the numerator of the Q8 representation; the numerator is always 2^8=256; for example 1.5=384/256, so 384 is the expected parameter

    The basic problem persists: the filters are applied only to MultiMediaX, X={1,2,3}, streams. At least on the devices I've seen, music playback is routed through MultiMedia4 (compress-offload-playback or low-latency-playback use cases, see mixer_paths.xml).
    There are two options, both of which require changes to the kernel sources
    1. add support for EQ on MultiMedia4 path
    2. modify front-end to back-end routes from MultiMedia4 to MultiMedia1
    15
    modified mixer_paths.xml for various Qualcomm-based devices

    The intention of this post is to host the relevant device sections of mixer_path.xml files that enable IIR hardware filtering. Simply replace the relevant section in your mixer with the device-appropriate section below.

    Alternatively, try the attached entire IIR-prepared mixer_paths.xml file for your device. However, there may be carrier/country-specific variances between mixer_paths files. Only Nexus devices seem to be immune from this carrier-specific madness. Please make sure to backup your current mixer_paths.xml file before proceeding.

    Relevant device in mixer_paths.xml ("headphones") modified for IIR passthrough (bypass) on Nexus 5 and LG G2. Thanks @lordc !
    Code:
        <path name="headphones">
            <ctl name="SLIM RX1 MUX" value="AIF1_PB" />
            <ctl name="SLIM RX2 MUX" value="AIF1_PB" />
            <ctl name="SLIM_0_RX Channels" value="Two" />
    <!-- begin stock routing (commented out) -->
            <!-- <ctl name="RX1 MIX1 INP1" value="RX1" /> -->
            <!-- <ctl name="RX2 MIX1 INP1" value="RX2" /> -->
    <!-- end stock routing (commented out) -->
            <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1" />
            <ctl name="HPHL DAC Switch" value="1" />
    
    <!-- enabling IIR filter; coefficients can be modified on-the-fly with tinymix, see below -->
       <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
            <ctl name="IIR1 INP1 MUX" value="RX1" />
            <ctl name="IIR2 INP1 MUX" value="RX2" />	
       <!-- IIR1 filter cofficients (pass-through) -->
            <ctl name="IIR1 Band1" id ="0" value="268435456" />
            <ctl name="IIR1 Band1" id ="1" value="0" />   
            <ctl name="IIR1 Band1" id ="2" value="0" />   
            <ctl name="IIR1 Band1" id ="3" value="0" />   
            <ctl name="IIR1 Band1" id ="4" value="0" />
            <ctl name="IIR1 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band2" id ="1" value="0" />  
            <ctl name="IIR1 Band2" id ="2" value="0" />   
            <ctl name="IIR1 Band2" id ="3" value="0" />   
            <ctl name="IIR1 Band2" id ="4" value="0" />
            <ctl name="IIR1 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band3" id ="1" value="0" />  
            <ctl name="IIR1 Band3" id ="2" value="0" />   
            <ctl name="IIR1 Band3" id ="3" value="0" />   
            <ctl name="IIR1 Band3" id ="4" value="0" />
            <ctl name="IIR1 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band4" id ="1" value="0" />  
            <ctl name="IIR1 Band4" id ="2" value="0" />   
            <ctl name="IIR1 Band4" id ="3" value="0" />   
            <ctl name="IIR1 Band4" id ="4" value="0" />
            <ctl name="IIR1 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR1 Band5" id ="1" value="0" />  
            <ctl name="IIR1 Band5" id ="2" value="0" />   
            <ctl name="IIR1 Band5" id ="3" value="0" />   
            <ctl name="IIR1 Band5" id ="4" value="0" />
            <ctl name="IIR1 Enable Band1" value="1" />
            <ctl name="IIR1 Enable Band2" value="1" />
            <ctl name="IIR1 Enable Band3" value="1" />
            <ctl name="IIR1 Enable Band4" value="1" />
            <ctl name="IIR1 Enable Band5" value="1" />
       <!-- IIR2 filter coefficients; same as IIR1 -->
            <ctl name="IIR2 Band1" id ="0" value="268435456" />
            <ctl name="IIR2 Band1" id ="1" value="0" />   
            <ctl name="IIR2 Band1" id ="2" value="0" />   
            <ctl name="IIR2 Band1" id ="3" value="0" />   
            <ctl name="IIR2 Band1" id ="4" value="0" />
            <ctl name="IIR2 Band2" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band2" id ="1" value="0" />  
            <ctl name="IIR2 Band2" id ="2" value="0" />   
            <ctl name="IIR2 Band2" id ="3" value="0" />   
            <ctl name="IIR2 Band2" id ="4" value="0" />
            <ctl name="IIR2 Band3" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band3" id ="1" value="0" />  
            <ctl name="IIR2 Band3" id ="2" value="0" />   
            <ctl name="IIR2 Band3" id ="3" value="0" />   
            <ctl name="IIR2 Band3" id ="4" value="0" />
            <ctl name="IIR2 Band4" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band4" id ="1" value="0" />  
            <ctl name="IIR2 Band4" id ="2" value="0" />   
            <ctl name="IIR2 Band4" id ="3" value="0" />   
            <ctl name="IIR2 Band4" id ="4" value="0" />
            <ctl name="IIR2 Band5" id ="0" value="268435456" /> 
            <ctl name="IIR2 Band5" id ="1" value="0" />  
            <ctl name="IIR2 Band5" id ="2" value="0" />   
            <ctl name="IIR2 Band5" id ="3" value="0" />   
            <ctl name="IIR2 Band5" id ="4" value="0" />
            <ctl name="IIR2 Enable Band1" value="1" />
            <ctl name="IIR2 Enable Band2" value="1" />
            <ctl name="IIR2 Enable Band3" value="1" />
            <ctl name="IIR2 Enable Band4" value="1" />
            <ctl name="IIR2 Enable Band5" value="1" />
       <!-- IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
            <ctl name="RX1 MIX1 INP1" value="IIR1" />
            <ctl name="RX2 MIX1 INP1" value="IIR2" />
       <!-- set IIR1 and IIR2 gain (84<=>0dB; lower this parameter appropriately if any of the biquads introduce gain) -->
            <ctl name="IIR1 INP1 Volume" value="84" />
            <ctl name="IIR2 INP1 Volume" value="84" />
    
     <!-- uncomment below if desired for cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
      <!--      <ctl name="IIR1 INP2 MUX" value="RX2" />         -->                                                                                               
      <!--         <ctl name="IIR2 INP2 MUX" value="RX1" />    -->
     <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
       <!--        <ctl name="IIR1 INP2 Volume" value="60" />        -->                                                                                               
      <!--         <ctl name="IIR2 INP2 Volume" value="60" /> -->
    
    <!-- end test filter --> 
    
    <!-- back to stock below -->
            <ctl name="RX1 Digital Volume" value="84" />
            <ctl name="RX2 Digital Volume" value="84" />
            <!-- lowered the HPHL[R] Volume from 15 to 10 to significantly reduce THD -->
            <ctl name="HPHL Volume" value="10" />
            <ctl name="HPHR Volume" value="10" />
        </path>
    Relevant device in mixer_paths.xml ("headset") modified for IIR passthrough on Note 4 (Snapdragon). Thanks @bjrmd !
    Code:
    <path name="headset">
          <ctl name="SLIM RX1 MUX" value="AIF1_PB"/>
          <ctl name="SLIM RX2 MUX" value="AIF1_PB"/>
          <ctl name="SLIM_0_RX Channels" value="Two"/>
          <ctl name="CLASS_H_DSM MUX" value="DSM_HPHL_RX1"/>
          <ctl name="HPHL DAC Switch" value="1"/>
          
          <!-- enabling IIR filter in pass-through mode; coefficients can be modified on-the-fly with tinymix -->
          <!-- RX1 plugs into the IIR1 mux and RX2 plugs into the IIR2 mux -->
          <ctl name="IIR1 INP1 MUX" value="RX1"/>
          <ctl name="IIR2 INP1 MUX" value="RX2"/>
          <!-- coefficients below define a no-op filter (0 dB of gain for all frequencies) -->
          <ctl name="IIR1 Band1" id="0" value="268435456"/>
          <ctl name="IIR1 Band1" id="1" value="0"/>
          <ctl name="IIR1 Band1" id="2" value="0"/>
          <ctl name="IIR1 Band1" id="3" value="0"/>
          <ctl name="IIR1 Band1" id="4" value="0"/>
          <ctl name="IIR1 Band2" id="0" value="268435456"/>
          <ctl name="IIR1 Band2" id="1" value="0"/>
          <ctl name="IIR1 Band2" id="2" value="0"/>
          <ctl name="IIR1 Band2" id="3" value="0"/>
          <ctl name="IIR1 Band2" id="4" value="0"/>
          <ctl name="IIR1 Band3" id="0" value="268435456"/>
          <ctl name="IIR1 Band3" id="1" value="0"/>
          <ctl name="IIR1 Band3" id="2" value="0"/>
          <ctl name="IIR1 Band3" id="3" value="0"/>
          <ctl name="IIR1 Band3" id="4" value="0"/>
          <ctl name="IIR1 Band4" id="0" value="268435456"/>
          <ctl name="IIR1 Band4" id="1" value="0"/>
          <ctl name="IIR1 Band4" id="2" value="0"/>
          <ctl name="IIR1 Band4" id="3" value="0"/>
          <ctl name="IIR1 Band4" id="4" value="0"/>
          <ctl name="IIR1 Band5" id="0" value="268435456"/>
          <ctl name="IIR1 Band5" id="1" value="0"/>
          <ctl name="IIR1 Band5" id="2" value="0"/>
          <ctl name="IIR1 Band5" id="3" value="0"/>
          <ctl name="IIR1 Band5" id="4" value="0"/>
          <ctl name="IIR1 Enable Band1" value="1"/>
          <ctl name="IIR1 Enable Band2" value="1"/>
          <ctl name="IIR1 Enable Band3" value="1"/>
          <ctl name="IIR1 Enable Band4" value="1"/>
          <ctl name="IIR1 Enable Band5" value="1"/>
          <!--  IIR2 filter coefficients; same as IIR1  -->
          <ctl name="IIR2 Band1" id="0" value="268435456"/>
          <ctl name="IIR2 Band1" id="1" value="0"/>
          <ctl name="IIR2 Band1" id="2" value="0"/>
          <ctl name="IIR2 Band1" id="3" value="0"/>
          <ctl name="IIR2 Band1" id="4" value="0"/>
          <ctl name="IIR2 Band2" id="0" value="268435456"/>
          <ctl name="IIR2 Band2" id="1" value="0"/>
          <ctl name="IIR2 Band2" id="2" value="0"/>
          <ctl name="IIR2 Band2" id="3" value="0"/>
          <ctl name="IIR2 Band2" id="4" value="0"/>
          <ctl name="IIR2 Band3" id="0" value="268435456"/>
          <ctl name="IIR2 Band3" id="1" value="0"/>
          <ctl name="IIR2 Band3" id="2" value="0"/>
          <ctl name="IIR2 Band3" id="3" value="0"/>
          <ctl name="IIR2 Band3" id="4" value="0"/>
          <ctl name="IIR2 Band4" id="0" value="268435456"/>
          <ctl name="IIR2 Band4" id="1" value="0"/>
          <ctl name="IIR2 Band4" id="2" value="0"/>
          <ctl name="IIR2 Band4" id="3" value="0"/>
          <ctl name="IIR2 Band4" id="4" value="0"/>
          <ctl name="IIR2 Band5" id="0" value="268435456"/>
          <ctl name="IIR2 Band5" id="1" value="0"/>
          <ctl name="IIR2 Band5" id="2" value="0"/>
          <ctl name="IIR2 Band5" id="3" value="0"/>
          <ctl name="IIR2 Band5" id="4" value="0"/>
          <ctl name="IIR2 Enable Band1" value="1"/>
          <ctl name="IIR2 Enable Band2" value="1"/>
          <ctl name="IIR2 Enable Band3" value="1"/>
          <ctl name="IIR2 Enable Band4" value="1"/>
          <ctl name="IIR2 Enable Band5" value="1"/>
          <!--IIR1 plugs back into RX1 mux and IIR2 plugs back into RX2 mux -->
          <ctl name="RX1 MIX1 INP1" value="IIR1"/>
          <ctl name="RX2 MIX1 INP1" value="IIR2"/>
          <!--  set IIR1 and IIR2 gain (84 equals 0 dB)  -->
          <ctl name="IIR1 INP1 Volume" value="84"/>
          <ctl name="IIR2 INP1 Volume" value="84"/>
          
            <!-- cross-feed into second input of the IIR filter (needs kernel mod on hammerhead!) -->
       <!--         <ctl name="IIR1 INP2 MUX" value="RX2" />    -->                                                                                                    
        <!--         <ctl name="IIR2 INP2 MUX" value="RX1" />  -->
              <!-- set the gain of the cross-feed (here: cross terms down by 24 dB, i.e 84-60) -->
       <!--          <ctl name="IIR1 INP2 Volume" value="60" />          -->                                                                                            
       <!--          <ctl name="IIR2 INP2 Volume" value="60" />        -->
    
          <ctl name="HPHL Volume" value="20"/>
          <ctl name="HPHR Volume" value="20"/>
          <ctl name="RX1 Digital Volume" value="80"/>
          <ctl name="RX2 Digital Volume" value="80"/>
          <ctl name="COMP1 Switch" value="1"/>
    </path>
Our Apps
Get our official app!
The best way to access XDA on your phone
Nav Gestures
Add swipe gestures to any Android
One Handed Mode
Eases uses one hand with your phone