AudioEffect 基础:核心 API、会话与效果链

各位同学,今天我们来聊聊 AudioEffect 框架的基石。说实话,我刚接触 Android 音频时,也被这套机制绕得有点晕。但一旦你理解了「会话」和「效果链」这两个概念,整个框架就豁然开朗了。

我个人习惯把 AudioEffect 想象成一个「音频效果器插件系统」。你想想看,我们平时用手机听歌、录音、打电话,背后都有一堆效果器在工作——均衡器、混响、降噪……它们怎么挂上去的?怎么保证只影响某个音频流?这就是我们今天要讲的核心。

1. AudioEffect 类的核心 API

AudioEffect 是所有效果器的基类。它不直接干活,但定义了效果器的生命周期和通信方式。

先看几个最常用的 API:

// 创建效果器
AudioEffect effect = new AudioEffect(
    effectType,      // UUID,比如均衡器
    effectUuid,      // 具体实现 UUID
    priority,        // 优先级,影响效果链顺序
    audioSessionId   // 绑定到哪个音频会话
);

// 启用/禁用
effect.setEnabled(true);

// 释放资源
effect.release();

这里有个坑,我踩过好几次——创建效果器时,audioSessionId 不能传 0。传 0 表示全局效果,但很多设备上全局效果根本创建不出来。我曾经在某个项目里,因为传了 0,结果效果器创建成功但就是不生效,排查了半天……

核心 API 速查表

方法说明注意事项
new AudioEffect(...)创建效果器实例sessionId 不能为 0(全局)
setEnabled(boolean)启用/禁用效果创建后默认禁用
release()释放效果器资源必须调用,否则内存泄漏
getDescriptor()获取效果器描述信息包含名称、实现者等
setParameter(...)设置效果参数不同效果器参数格式不同

2. 效果会话(Session)的概念

会话是什么?说白了,就是一个「音频流身份证」。每个音频播放或录制流,都有一个唯一的 session ID。

为什么需要会话?我举个例子你就明白了:

你打开网易云音乐听歌,同时微信来了一条语音消息。这两个音频流是独立的。如果你给 session 100 挂了均衡器,那只会影响网易云音乐,不会影响微信语音。这就是会话隔离的作用。

获取会话 ID 的方式很简单:

// 播放时获取 session ID
AudioTrack track = new AudioTrack(...);
int sessionId = track.getAudioSessionId();

// 录制时获取 session ID
AudioRecord record = new AudioRecord(...);
int sessionId = record.getAudioSessionId();

嗯,这里要注意:同一个 AudioTrack 或 AudioRecord 的 session ID 在整个生命周期内保持不变。但如果你重新创建了一个 AudioTrack,它会分配一个新的 session ID。

小技巧:如果你想让多个 AudioTrack 共享同一个效果链,可以在创建时指定相同的 session ID:

AudioTrack track1 = new AudioTrack(..., AudioManager.AUDIO_SESSION_ID_GENERATE);
AudioTrack track2 = new AudioTrack(..., track1.getAudioSessionId());

这样 track1 和 track2 就会共享效果器。

3. 效果链(Effect Chain)的构建与销毁

效果链,就是挂载在同一个 session 上的多个效果器,按优先级排列形成的链条。音频数据会依次经过每个效果器处理。

构建效果链的步骤:

  1. 创建 AudioTrack/AudioRecord,获取 session ID
  2. 按优先级创建效果器,绑定到同一个 session
  3. 设置效果参数,启用效果器
  4. 开始播放/录制,音频流自动经过效果链

来看一个完整的例子:

// 1. 创建 AudioTrack
AudioTrack track = new AudioTrack(...);
int sessionId = track.getAudioSessionId();

// 2. 创建均衡器(优先级 0)
AudioEffect eq = new AudioEffect(
    AudioEffect.EFFECT_TYPE_EQUALIZER,
    UUID.randomUUID(),
    0,  // 优先级
    sessionId
);

// 3. 创建混响器(优先级 1,排在均衡器后面)
AudioEffect reverb = new AudioEffect(
    AudioEffect.EFFECT_TYPE_ENV_REVERB,
    UUID.randomUUID(),
    1,  // 优先级更高,后处理
    sessionId
);

// 4. 设置参数并启用
eq.setEnabled(true);
reverb.setEnabled(true);

// 5. 开始播放
track.play();

效果链的销毁顺序很重要。我建议:先停掉 AudioTrack,再释放效果器,最后释放 AudioTrack。顺序反了可能会 crash。

// 正确的销毁顺序
track.stop();
eq.release();
reverb.release();
track.release();

警告:效果器释放后,AudioTrack 还在播放的话,音频流会直接绕过效果器输出。但如果你先释放了 AudioTrack,再释放效果器,效果器内部可能还在引用 AudioTrack 的资源,导致野指针。我曾经在某个项目里因为顺序搞反,导致偶发性的 native crash,查了两天才定位到问题。

知识体系总览

下面这张图,是我自己总结的 AudioEffect 核心知识结构。你看一眼,应该就能把今天的内容串起来:

AudioEffect 核心知识体系 AudioEffect API • 创建效果器 • setEnabled() • release() • getDescriptor() • setParameter() UUID 标识类型与实现 优先级决定处理顺序 效果会话 (Session) • 音频流唯一标识 • 隔离不同音频流 • 共享效果链 AudioTrack.getAudioSessionId() AudioRecord.getAudioSessionId() sessionId = 0 表示全局 不推荐使用全局 session 效果链 (Chain) • 按优先级排序 • 音频依次经过 • 构建与销毁顺序 优先级 0 → 1 → 2 ... 先创建的先处理 销毁:先停流再释放 顺序反了会 crash 三者关系:API 创建效果器 → 绑定到 Session → 形成 Effect Chain

好了,今天的内容就到这里。记住三个关键词:API、Session、Chain。下次我们聊具体的效果器实现,比如均衡器怎么调参、混响怎么设置空间感。到时候我会分享一些我在车载音频项目里调音的经验,挺有意思的。


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