Unlocking the hardware filter in the Qualcomm audio codec
(Let me preface this post by pointing out that this is very technical, but that, unfortunately comes with the territory.)
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) 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 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.
The following details the current status of my investigations.
Qualcomm's biquad implementation is given by the following difference equation:
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
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.
Once booted with the modified headphone section, the filters can be modified on-the-fly by using tinymix:
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
Note that restarting the audio stream will default to the filters currently present in the mixer_paths.xml file.
Known issues
(Let me preface this post by pointing out that this is very technical, but that, unfortunately comes with the territory.)
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) 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.
- 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.
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.
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]
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.
Once booted with the modified headphone section, the filters can be modified on-the-fly by using tinymix:
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
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.
- 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.