9、音频效果处理:效果框架、预置效果与效果链管理

音频效果处理,说白了就是给声音“化妆”。

你听歌时开的均衡器、KTV里的混响、游戏中的3D音效,背后都是这套框架在干活。我在项目中遇到过不少厂商定制音效的需求,每次都得跟这个框架打交道。今天我们就把它彻底拆开看看。

9.1 效果框架的整体架构

Android的效果框架,核心就三个角色:

  • AudioFlinger:效果的管理者,负责创建、销毁、控制效果
  • 效果库(Effect Library):具体算法的实现者,以.so形式存在
  • HAL层:硬件加速的接口,有些效果可以交给DSP处理

它们的关系是这样的:

Android音频效果框架架构 AudioFlinger EffectModule 管理器 效果链 A(播放流) 效果链 B(录音流) 效果链 C(全局) 均衡器库 混响库 虚拟器库 其他效果库 HAL 层(硬件加速 / DSP 处理)

9.2 预置效果:Equalizer 和 Reverb

Android系统内置了几种常用效果。我挑两个典型的说说。

9.2.1 均衡器(Equalizer)

均衡器是最常见的音效。它把音频频段分成几段,每段可以独立调节增益。

核心参数:

  • 频段数:通常5段、10段不等
  • 中心频率:每个频段的中心点
  • 增益范围:-15dB 到 +15dB
  • Q值:带宽控制

创建均衡器的代码大致是这样:

// 获取效果引擎
sp<IEffect> effect = audioFlinger->createEffect(
    EFFECT_UUID_EQUALIZER,   // 均衡器UUID
    sessionId,               // 音频会话ID
    ioHandle,                // 输出流句柄
    &status);

// 设置频段增益
equalizer->setParameter(
    EQ_PARAM_SET_BAND_LEVEL,
    band,        // 频段索引
    level);      // 增益值(mB单位)

我记得有一次,厂商要求实现一个“智能均衡器”,能根据歌曲类型自动切换预设。嗯,其实就是监听音频元数据,然后调用setParameter切换预设值。说起来简单,但预设的平滑过渡花了不少功夫。

9.2.2 混响(Reverb)

混响模拟声音在不同空间中的反射效果。Android支持两种混响:

类型 用途 典型参数
PresetReverb 简单预设混响 房间类型(小/中/大/礼堂)
EnvironmentalReverb 精细控制混响 衰减时间、早反射延迟、扩散度等

EnvironmentalReverb的参数很多,我建议你重点关注这几个:

  • decayTime:混响衰减时间,决定声音“拖尾”多长
  • density:反射密度,影响声音的“厚实感”
  • diffusion:扩散度,控制声音的“融合度”

我的经验:混响参数调不好,声音会像“在厕所里唱歌”。建议先拿PresetReverb找到大致方向,再用EnvironmentalReverb微调。

9.3 效果链管理

效果链,就是把多个效果串起来。比如:先均衡,再混响,最后加个限幅器。

AudioFlinger里,效果链的管理单位是EffectModule。每个EffectModule代表一个具体的效果实例。

9.3.1 效果链的创建流程

// 伪代码:创建效果链
status_t AudioFlinger::createEffectChain(audio_io_handle_t output) {
    // 1. 检查是否已有链
    if (mEffectChains.indexOfKey(output) >= 0) {
        return ALREADY_EXISTS;
    }
    
    // 2. 创建新链
    sp<EffectChain> chain = new EffectChain(this, output);
    mEffectChains.add(output, chain);
    
    // 3. 初始化链
    chain->init();
    
    return OK;
}

9.3.2 效果的处理顺序

效果链的处理顺序很重要。AudioFlinger按照效果插入的顺序依次处理:

  1. 先处理前置效果(Pre-processing),比如降噪
  2. 再处理后置效果(Post-processing),比如均衡器、混响
  3. 最后处理全局效果(Global),比如音量限制

注意:效果顺序会影响最终音质。比如先压缩再均衡,和先均衡再压缩,结果完全不同。我见过有人把限幅器放在均衡器前面,结果高频被削得一塌糊涂。

9.4 效果与HAL的交互

效果不一定都在CPU上跑。很多手机有DSP芯片,专门处理音频效果。这时候就需要HAL层来桥接。

9.4.1 效果HAL接口

Android定义了effect_hal.h接口,核心函数就这几个:

struct effect_interface_s {
    // 打开效果
    int32_t (*open)(effect_handle_t *handle);
    
    // 处理音频数据
    int32_t (*process)(effect_handle_t handle,
                       audio_buffer_t *inBuffer,
                       audio_buffer_t *outBuffer);
    
    // 设置参数
    int32_t (*set_parameter)(effect_handle_t handle,
                             effect_param_t *param);
    
    // 获取参数
    int32_t (*get_parameter)(effect_handle_t handle,
                             effect_param_t *param);
    
    // 关闭效果
    int32_t (*close)(effect_handle_t handle);
};

9.4.2 硬件加速 vs 软件处理

效果到底跑在CPU还是DSP上,由HAL层决定。AudioFlinger会这样判断:

// 伪代码:选择处理方式
if (hal->isEffectSupported(effectUuid)) {
    // 硬件加速
    hal->createEffectOnDSP(effectUuid, sessionId);
} else {
    // 软件处理
    loadEffectLibrary(effectUuid);
    createSoftwareEffect(effectUuid, sessionId);
}

关键点:

  • 硬件加速:延迟低、功耗小,但灵活性差
  • 软件处理:灵活、可定制,但占用CPU
  • 有些效果可以混合使用:部分参数由DSP处理,部分由CPU控制

我曾经遇到一个坑:某款手机的DSP只支持16kHz采样率的混响,但播放44.1kHz的音乐时,效果直接失效。后来加了采样率转换才解决。嗯,这种兼容性问题,你想想看,如果不做充分测试,用户反馈来了才去排查,就很被动。

9.5 效果框架的避坑指南

最后,分享几个我踩过的坑:

  • 效果创建失败:检查UUID是否正确,效果库是否加载。我曾经因为库路径写错,折腾了一整天。
  • 效果链死锁:多个效果共享同一个锁时容易出问题。建议每个EffectModule有自己的锁。
  • 参数同步:UI线程和音频线程同时修改参数,可能导致数据不一致。用原子操作或消息队列。
  • 功耗问题:软件效果太多,CPU占用高,手机发热。能用硬件加速就用硬件加速。

调试技巧:dumpsys media.audio_flinger可以查看当前所有效果链的状态。包括每个效果的参数、处理延迟、是否启用等。这个命令我几乎天天用。

效果框架的内容就这些。核心是理解EffectModule的生命周期,以及效果链的处理顺序。搞懂了这些,不管是定制音效还是排查问题,你都能游刃有余。


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