Skip to content

[Bug] In the equalizer, any positive band gain makes playback much quieter #486

Description

@skorokithakis

I've found that the equalizer works weirdly, and makes the output very quiet. I've included Claude's explanation of the issue and a possible solution:

Raising any equalizer band lowers the overall playback level by more than the band gain adds. The effect is immediate and persists until every band is returned to 0, at which point the app disables the effect and normal level returns. Cutting bands does not show the problem.

The volume boost (LoudnessEnhancer) is not involved.

Environment

Reported on Nothing Phone (2), Nothing OS (close to stock AOSP). Expected on any device that uses the AOSP LVM effect bundle.

Steps to reproduce

  1. Start playback.
  2. Open Settings > Equalizer.
  3. Raise any single band by 1 dB or more.
  4. Overall loudness drops immediately, by more than the band gain.
  5. Return all bands to 0. Normal loudness returns.

Expected

A +6 dB band gain boosts that band by 6 dB and leaves the rest of the spectrum unchanged.

Actual

The whole output is attenuated. Measured against the AOSP algorithm, for a single band at 230 Hz:

Band gain set Global attenuation Net on that band Net on all other bands
+1 dB -3 dB -2 dB -3 dB
+6 dB -7 dB -1 dB -7 dB
+15 dB -10 dB +5 dB -10 dB

Root cause

This is not a defect in our code. It is the documented behaviour of the AOSP equalizer effect that PlaybackEnhancerService uses through android.media.audiofx.Equalizer.

BundleContext::limitLevel() in frameworks/av/media/libeffects/lvm/wrapper/Aidl/ performs automatic headroom management to prevent clipping. It estimates the energy the positive band gains could add, then attenuates the entire output by
that amount:

for each band:
    bandFactor  = bandGainMb / 1500.0
    bandEnergy  = bandFactor * coeff[band]^2
    if bandEnergy > 0: energyContribution += bandEnergy

totalEnergy    = sqrt(energyContribution + crossTerms)
maxLevelRound  = (int)(totalEnergy + 0.99)      // ceiling
VC_EffectLevel = volumeDb - maxLevelRound       // global attenuation

coeff is kBandEnergyCoefficient = {7.56, 9.69, 9.59, 7.37, 2.88} in BundleTypes.h. The correction is recomputed from the current band gains whenever the effect is enabled or the stream volume is updated.

Two consequences match the report exactly:

  • Only positive gains contribute (if bandEnergy > 0), so cuts cause no global attenuation. Only boosts trigger the problem.
  • The ceiling rounding means even a 1 dB nudge costs a full 3 dB.

We cannot compensate for this while keeping the current API. The equalizer bundle declares Flags::Volume::CTRL, so it owns the volume stage of the effect chain. There is no gain stage available to us downstream of it.

Recommended fix

Replace android.media.audiofx.Equalizer with android.media.audiofx.DynamicsProcessing.

DynamicsProcessing is available since API 28 and minSdk is already 28. It applies no automatic headroom attenuation, it exposes a real input gain stage, and it provides a limiter stage. A +6 dB band gain becomes an actual +6 dB band gain.

Scope:

  • PlaybackEnhancerService.attachEqualizer / applyEqualizer: build a DynamicsProcessing.Config with a multi-band post-EQ stage and the limiter stage enabled, then set per-band gains.
  • EqualizerBandProvider: DynamicsProcessing does not report a device band layout, so define the band layout in the app. Keeping the existing 60 / 230 / 910 / 3600 / 14000 Hz layout and a -15..+15 dB range preserves the current UI. The probe becomes a simple "can the effect be constructed" check.
  • No change needed in EqualizerSettings, PlaybackPreferences, SettingsViewModel or the UI.
  • If the effect cannot be created, return EqualizerCapabilities.Unavailable. The existing code path already hides the equalizer row in that case.

Risks

  • The limiter stage must be enabled. Once DynamicsProcessing replaces the platform equalizer, nothing protects against clipping on large boosts, so distortion would replace the current quieting.
  • DynamicsProcessing support varies between vendors. Needs testing on real hardware, including at least one non-stock ROM, before release.
  • Existing stored gains stay valid; the persisted format does not change.

Out of scope

  • No compressor or dynamics feature is being added. DynamicsProcessing is used only as an equalizer with a gain stage and a limiter.
  • No change to the volume boost (LoudnessEnhancer).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions