一、均衡器:从原理到实战
均衡器,说白了就是调整声音不同频段的音量大小。你听歌时觉得低音不够沉、高音太刺耳,调的就是它。我最早接触均衡器是在做音乐播放器的时候,那时候觉得这东西不就是几个滑块嘛,后来深入才发现——嗯,这里面的门道还真不少。
1.1 均衡器的基本原理
均衡器的核心是滤波器。每个频段对应一个带通滤波器,通过调整滤波器的增益,就能提升或衰减该频段的能量。Android 的 AudioEffect 框架里,均衡器被封装成 Equalizer 类,底层由音频 HAL 层实现。
我个人习惯把均衡器理解成「音频的调色板」。低频是画面的底色,中频是主体轮廓,高频是细节高光。你调的不是音量,而是声音的「色彩分布」。
核心公式(简化版):
每个频段的增益系数由滤波器类型(如 Peaking、Low-Shelf、High-Shelf)和 Q 值决定。
输出信号 = 输入信号 × 各频段增益系数之和每个频段的增益系数由滤波器类型(如 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 数据。
实现步骤大致如下:
- 创建 Visualizer 实例,绑定到音频会话 ID
- 设置数据捕获大小(通常 1024 或 2048 采样点)
- 注册监听器,在回调中获取 FFT 数据
- 计算各频段能量,映射到均衡器的频段上
- 绘制到自定义 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 知识体系结构图
下面这张图概括了均衡器与频谱可视化的整体架构:
1.5 实战中的几个要点
- 频段映射:Visualizer 的 FFT 数据是线性频率分布,而均衡器频段是对数分布。你需要把 FFT 的 bin 索引映射到对应的均衡器频段上。我一般用
binIndex = (int)((float)bandCenterFreq / sampleRate * fftSize)来算。 - 增益与显示联动:用户拖动均衡器滑块时,频谱图上的对应频段柱状图应该实时变化。我习惯在
onBandLevelChanged回调里触发重绘。 - 性能优化:FFT 计算和 UI 绘制不要在同一个线程。用
HandlerThread处理数据,主线程只负责刷新 View。
一句话总结:均衡器是「调」声音的,Visualizer 是「看」声音的。两者配合,才能做出专业级的音频效果界面。