一、均衡器:从原理到实战

均衡器,说白了就是调整声音不同频段的音量大小。你听歌时觉得低音不够沉、高音太刺耳,调的就是它。我最早接触均衡器是在做音乐播放器的时候,那时候觉得这东西不就是几个滑块嘛,后来深入才发现——嗯,这里面的门道还真不少。

1.1 均衡器的基本原理

均衡器的核心是滤波器。每个频段对应一个带通滤波器,通过调整滤波器的增益,就能提升或衰减该频段的能量。Android 的 AudioEffect 框架里,均衡器被封装成 Equalizer 类,底层由音频 HAL 层实现。

我个人习惯把均衡器理解成「音频的调色板」。低频是画面的底色,中频是主体轮廓,高频是细节高光。你调的不是音量,而是声音的「色彩分布」。

核心公式(简化版):
输出信号 = 输入信号 × 各频段增益系数之和
每个频段的增益系数由滤波器类型(如 Peaking、Low-Shelf、High-Shelf)和 Q 值决定。

1.2 频段配置与增益控制

Android 的均衡器支持动态配置频段数量。不同设备的频段数可能不同,从 5 段到 31 段都有。我遇到过一台平板只有 5 段,而某款旗舰手机有 10 段。所以写代码时千万别写死频段数。

获取频段信息的典型代码:

// 获取均衡器实例
Equalizer equalizer = new Equalizer(0, audioSessionId);

// 获取支持的频段数量
short numberOfBands = equalizer.getNumberOfBands();

// 获取每个频段的中心频率(单位:mHz)
for (short i = 0; i < numberOfBands; i++) {
    int centerFreq = equalizer.getCenterFreq(i);
    // centerFreq 是毫赫兹,转成赫兹:centerFreq / 1000
    Log.d("EQ", "频段 " + i + " 中心频率: " + (centerFreq / 1000) + " Hz");
}

// 设置增益(单位:毫分贝)
equalizer.setBandLevel((short) 0, (short) 3000);  // 提升 3dB
equalizer.setBandLevel((short) 1, (short) -2000); // 衰减 2dB
注意:增益值的单位是毫分贝(mB),1000 mB = 1 dB。设置范围通常在 -15000 到 15000 之间,超出会抛异常。我曾经在测试时直接设了 20000,结果应用闪退——嗯,边界检查很重要。

1.3 实时频谱可视化实现思路

实时频谱可视化,说白了就是把音频信号从时域转换到频域,然后画出来。Android 里做这个通常用 Visualizer 类,它可以直接从音频会话中捕获 PCM 数据或 FFT 数据。

实现步骤大致如下:

  1. 创建 Visualizer 实例,绑定到音频会话 ID
  2. 设置数据捕获大小(通常 1024 或 2048 采样点)
  3. 注册监听器,在回调中获取 FFT 数据
  4. 计算各频段能量,映射到均衡器的频段上
  5. 绘制到自定义 View,用 Canvas 画柱状图或曲线

核心代码片段:

Visualizer visualizer = new Visualizer(audioSessionId);
visualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]); // 最大尺寸
visualizer.setDataCaptureListener(
    new Visualizer.OnDataCaptureListener() {
        @Override
        public void onWaveFormDataCapture(byte[] waveform, int samplingRate) {
            // 时域数据,一般不用
        }

        @Override
        public void onFftDataCapture(byte[] fft, int samplingRate) {
            // fft 数组包含实部和虚部,需要计算幅度
            float[] magnitudes = new float[fft.length / 2];
            for (int i = 0; i < magnitudes.length; i++) {
                // 实部:fft[2*i],虚部:fft[2*i+1]
                float real = (float) fft[2 * i];
                float imag = (float) fft[2 * i + 1];
                magnitudes[i] = (float) Math.sqrt(real * real + imag * imag);
            }
            // 将 magnitudes 映射到 UI 更新
            updateSpectrumUI(magnitudes);
        }
    },
    Visualizer.getMaxCaptureRate(), // 更新频率
    false,  // 不捕获波形
    true    // 捕获 FFT
);
visualizer.setEnabled(true);
避坑指南:我曾经在低端设备上发现 Visualizer 回调频率太高导致 UI 卡顿。后来把更新频率降到 20Hz,并且只在 UI 线程用 postInvalidate() 刷新,问题就解决了。另外,别忘了在页面销毁时调用 visualizer.release(),否则会一直占用音频资源。

1.4 知识体系结构图

下面这张图概括了均衡器与频谱可视化的整体架构:

均衡器与频谱可视化架构 音频源(MediaPlayer/AAudio) Equalizer(频段增益控制) setBandLevel() / getBandLevel() 音频输出(扬声器/耳机) Visualizer(FFT 数据捕获) 自定义 View 绘制频谱 图:均衡器处理与频谱可视化数据流

1.5 实战中的几个要点

  • 频段映射:Visualizer 的 FFT 数据是线性频率分布,而均衡器频段是对数分布。你需要把 FFT 的 bin 索引映射到对应的均衡器频段上。我一般用 binIndex = (int)((float)bandCenterFreq / sampleRate * fftSize) 来算。
  • 增益与显示联动:用户拖动均衡器滑块时,频谱图上的对应频段柱状图应该实时变化。我习惯在 onBandLevelChanged 回调里触发重绘。
  • 性能优化:FFT 计算和 UI 绘制不要在同一个线程。用 HandlerThread 处理数据,主线程只负责刷新 View。
一句话总结:均衡器是「调」声音的,Visualizer 是「看」声音的。两者配合,才能做出专业级的音频效果界面。

公众号:蓝海资料掘金营,微信deep3321