音效处理与 EQ
音频均衡器(EQ)与音效处理子系统:包含 PCM 软件 EQ(pcm_eq_api.c)、通用 EQ 效果框架(audio_eq.h)以及硬件 IIR 滤波器系数设计(hw_eq.h),为 AW30N 蓝牙 SoC 的音频播放链路提供多段均衡、音效模式切换与在线调音能力。
Purpose and Scope
本文档覆盖 AW30N BLE SDK 中与音效处理、均衡器(EQ)相关的完整能力:
- PCM 软件 EQ:
pcm_eq_api.c中基于预计算系数表的 10 段软件均衡器实现,及其与声音输出链路的挂接方式; - 通用 EQ 效果框架:
audio_eq.h定义的EQ_STUCT_API抽象、EQ_BUFF/EQ_HDL运行期对象、EQ 命令(EQ_CMD)与在线调音工具结构; - 硬件 EQ / IIR 滤波器设计:
hw_eq.h中各种滤波器类型(高通、低通、带通、搁架式、峰值)的系数设计函数与eq_seg_info段描述结构。
以下主题属于相邻目录页,不在本文展开:音频编解码格式与解码器管理(见解码器相关文档)、DAC/ADC 硬件驱动(见音频 DAC 相关文档)、音频链路同步与 RF 音频传输(见音频链路/audio_link 相关文档)。
Overview
均衡器是音频播放链路中最常用的音效处理单元,其本质是级联的 IIR(无限脉冲响应)滤波器,通过调整不同频段的增益来改变声音的音色特征。AW30N 的 EQ 子系统按实现方式分为两条路径:
- 软件 EQ(PCM EQ):在 CPU 上以定点运算处理 PCM 数据,由
PCM_SW_EQ_EN编译宏使能,使用 EQTools V3.1.7 生成的预计算系数表(eq_filt_XXXXX),支持 8k/12k/16k/24k/32k/44.1k/48k 等采样率,10 段滤波器级联,左右声道独立增益控制。 - 硬件 EQ 框架(audio_eq / hw_eq):面向通用效果框架的软件实现,通过
EQ_STUCT_API虚函数表提供need_buf/open/run/config四个操作,支持在线切换系数表(EQ_CMD_SWITCH_TAB)、淡入淡出(B_EQ_FADE_IN/B_EQ_FADE_OUT)、音效模式切换(eq_mode_sw)以及基于music_eq_tool的在线调音工具。
设计意图:将"EQ 效果"抽象为可挂接到 sound_out_obj 输出链路的 effect 节点(effect 指针 + B_DEC_EFFECT 标志),使解码器输出的音频数据可在送入 DAC 之前被任意效果节点串联处理;滤波器系数与运行逻辑分离,系数可由工具离线生成或运行时动态设计(design_pe 等函数),兼顾了嵌入式场景的实时性(定点、查表)与灵活性(在线调音)。
Architecture
flowchart TD
subgraph sg_Decoder["解码器层"]
Decoder["Decoder / 解码输出"]
end
subgraph sg_EffectChain["音效链路(sound_out_obj 串联)"]
LinkPcm["link_pcm_eq_sound()"]
LinkEq["link_eq_sound()"]
PcmEq["pcm_eq_api() → pcm_eq_phy()"]
EqHdl["EQ_HDL / EQ_BUFF"]
end
subgraph sg_Coeff["系数来源"]
Tab44100["eq_filt_44100[10段×5]"]
TabOther["eq_filt_22050/11025/48000/24000/12000/32000/16000/08000"]
GetTab["get_pcm_eq_tab(sr)"]
DesignFn["design_lp/hp/pe/ls/hs/bp()"]
MusicTool["music_eq_tool(在线调音)"]
end
subgraph sg_Dac["输出层"]
DacBuf["DAC 缓存(p_dac_cbuf)"]
Dac["DAC 硬件"]
end
Decoder -->|"PCM 数据"| LinkPcm
Decoder -->|"PCM 数据"| LinkEq
LinkPcm -->|"初始化 PCM_EQ_PARM"| PcmEq
LinkEq -->|"初始化 EQ_PARA_STRUCT"| EqHdl
PcmEq -->|"选择系数表"| GetTab
GetTab --> Tab44100
GetTab --> TabOther
EqHdl -->|"系数设计/更新"| DesignFn
MusicTool -->|"EQ_CMD_SWITCH_TAB"| EqHdl
PcmEq -->|"处理后数据"| DacBuf
EqHdl -->|"处理后数据"| DacBuf
DacBuf --> Dac
架构说明:
- 解码器层输出原始 PCM 数据,通过
sound_out_obj结构体上的effect字段将效果节点挂入链路。link_pcm_eq_sound()与link_eq_sound()是两条 EQ 路径的挂接入口,二者都接受当前 sound 节点、DAC 缓存指针与采样率/声道数,返回处理后的下一个 sound 节点。 - PCM EQ 路径(
pcm_eq_api.c)使用固定 10 段、预先烧录的整型系数表,初始化开销小,适合出厂预设音效;get_pcm_eq_tab(sr)按采样率选择对应表,左右声道各自持有系数指针,LGain/RGain均为1 << 20(约 0 dB)。 - 通用 EQ 框架路径(
audio_eq.h/hw_eq.h)以EQ_STUCT_API操作集封装 EQ 对象,支持 float 型系数(eq_oeff_tab缓存运算后系数表)、淡入淡出与在线参数更新,适合 App 侧动态调音。 - 系数来源分为静态表(EQTools 离线生成)与动态设计(
design_*系列 IIR 系数计算函数),两条路径最终都汇入 DAC 缓存输出。
主要实现
PCM 软件 EQ 初始化与运行
pcm_eq_api() 是 PCM EQ 的初始化入口,位于 pcm_eq_api.c。它首先清零 PCM_EQ_PARM 参数结构,固定配置 10 段(parm.nSection = 10),随后通过 pcm_eq_buf_len() 计算所需工作区大小并与静态数组 g_work_buf(660 字节,放置于 .pcm_eq_data 段)比对,空间不足时直接返回 NULL 使 EQ 挂接失败:
static u32 g_work_buf[660 / 4] AT(.pcm_eq_data);
PCM_EQ_PARM parm;
void *pcm_eq_api(void *obuf, u32 sr, u32 channel, void **ppsound)
{
memset(&parm, 0, sizeof(PCM_EQ_PARM));
parm.nSection = 10;
u32 buf_size = pcm_eq_buf_len(parm.nSection, channel);
if (sizeof(g_work_buf) < buf_size) {
log_info("PCM EQ Work Data space is not big enough:%d : %d", sizeof(g_work_buf), buf_size);
return NULL;
}
log_info("PCM EQ Work Data space is enough:%d : %d", sizeof(g_work_buf), buf_size);
parm.LCoeff_OnChip = get_pcm_eq_tab(sr);
if (1 < channel) {
parm.RCoeff_OnChip = get_pcm_eq_tab(sr);
}
parm.LGain = 1 << 20;
parm.RGain = 1 << 20;
parm.SHI = 10;
parm.SHO = 8;
parm.channel = channel;
return pcm_eq_phy(obuf, &g_work_buf[0], &parm, ppsound);
}
Source: pcm_eq_api.c
关键参数语义:SHI/SHO 分别为输入/输出定点移位(Q 格式缩放),LGain/RGain 为左右声道总增益(1 << 20 对应单位增益),channel > 1 时右声道复用同一系数表。整个函数被 #if defined(PCM_SW_EQ_EN) && (PCM_SW_EQ_EN) 编译开关包裹,未使能时整段代码不参与编译。
PCM EQ 与输出链路的挂接
link_pcm_eq_sound() 将 EQ 效果插入声音输出链表:它遍历当前 sound_out_obj,把 pcm_eq_api() 返回的效果句柄写入 p_curr_sound->effect,成功后置位 B_DEC_EFFECT 标志并让链表的下一节点直接使用 DAC 缓存作为输出缓冲;失败则打印日志并保留原链路:
void *link_pcm_eq_sound(void *p_sound_out, void *p_dac_cbuf, void **pp_effect, u32 sr, u32 channel)
{
sound_out_obj *p_next_sound = 0;
sound_out_obj *p_curr_sound = p_sound_out;
p_curr_sound->effect = pcm_eq_api(p_curr_sound->p_obuf, sr, channel, (void **)&p_next_sound);
if (NULL != p_curr_sound->effect) {
if (NULL != pp_effect) {
*pp_effect = p_curr_sound->effect;
}
p_curr_sound->enable |= B_DEC_EFFECT;
p_curr_sound = p_next_sound;
p_curr_sound->p_obuf = p_dac_cbuf;
} else {
log_info("pcm eq init fail\n");
}
log_info("pcm eq init succ\n");
return p_curr_sound;
}
Source: pcm_eq_api.c
设计意图:sound_out_obj 是 SDK 音频输出链路的通用节点,effect 指针使任意效果(EQ、混响等)可以透明地插入解码器与 DAC 之间;B_DEC_EFFECT 标志用于通知音频管理模块"解码输出已带效果",避免二次处理。挂接失败时链路回退为直通,保证音频不中断(降级而非报错)。
预计算滤波器系数表
系数表由 EQTools V3.1.7 beta(生成时间 2021-07-13)自动生成,每段滤波器 5 个整型系数(对应 IIR 双二阶节的 b0/b1/b2/a1/a2),以 4 对齐的定点格式存储。get_pcm_eq_tab(sr) 按采样率选择表,未命中时默认回落到 32 kHz 表:
const int *get_pcm_eq_tab(u32 sr)
{
const int *sr_tab = &eq_filt_32000[0];
switch (sr) {
case 8000:
sr_tab = &eq_filt_08000[0];
break;
case 12000:
sr_tab = &eq_filt_12000[0];
break;
case 16000:
sr_tab = &eq_filt_16000[0];
break;
case 24000:
sr_tab = &eq_filt_24000[0];
break;
case 32000:
sr_tab = &eq_filt_32000[0];
break;
default:
break;
}
return sr_tab;
}
Source: pcm_eq_api.c
仓库中共有 9 张采样率表(8k/12k/16k/24k/32k/44.1k/48k 及 22.05k/11.025k)。以 44.1 kHz 表为例,第 9 段(seg 9)系数为 4194304, 0, 0, 0, 0,即 2^22 与全零——这是直通段(增益 0 dB、无滤波),用于把有效段数不足 10 段时的尾部补齐为直通;4194304 = 1 << 22 是定点归一化基值。每段 5 个系数中,第 1 个系数接近 4194304 的段为低增益宽带段,系数偏离较大的段(如 seg 7 的 5667101, -2169436, 515593, 4557489, -3373064)为高 Q 峰值滤波段,反映典型的 10 段图形均衡器频率分布。
硬件 EQ:IIR 滤波器类型与段描述
hw_eq.h 定义滤波器类型枚举与段描述结构 eq_seg_info,这是通用 EQ 框架的"用户可编程"接口——App 通过填充 freq/gain/q 描述期望的滤波器特性,由 design_* 系列函数实时计算 IIR 系数:
/*eq IIR type*/
typedef enum {
EQ_IIR_TYPE_HIGH_PASS = 0x00,
EQ_IIR_TYPE_LOW_PASS,
EQ_IIR_TYPE_BAND_PASS,
EQ_IIR_TYPE_HIGH_SHELF,
EQ_IIR_TYPE_LOW_SHELF,
EQ_IIR_TYPE_BAND_PASS_NEW,//用于频谱分析
} EQ_IIR_TYPE;
struct eq_seg_info {
unsigned short index; //eq段序号
unsigned short iir_type; //滤波器类型EQ_IIR_TYPE
int freq; //中心截止频率
float gain; //增益(-12 ~12 db)
float q; //q值(0.3~30)
};
Source: hw_eq.h
配套的系数设计函数(design_lp/design_hp/design_pe/design_ls/design_hs/design_bp,以及供整型增益使用的 design_pe_for_int)输出 5 个 float 系数,经 eq_stable_check() 做稳定性校验,eq_db2mag() 完成 dB 到幅值的换算。定点宏 mul64/div64 与 EQ_OUTQ = 22 定义了运行期定点乘法/除法的 Q 格式,EQ_COEFF0~EQ_COEFF4 将数组下标映射为滤波器差分方程的 b0/b1/b2/a1/a2。
通用 EQ 框架与运行期对象
audio_eq.h 将 EQ 封装为可复用的效果组件:
EQ_STUCT_API:效果操作集(need_buf/open/run/config),通过get_eq_context()获取,实现"算法与对象解耦";EQ_BUFF:运行期缓冲,内嵌EQ_IO_CONTEXT、EQ_PARA_STRUCT(当前段/下一段、全局增益、采样率、最大段数、声道数)、sound_fade_obj fade(淡入淡出)、内部数据缓冲buff[EQ_MAX_POINT * 2]与系数缓存eq_oeff_tab[];EQ_HDL:完整句柄,包含EFFECT_OBJ obj(通用效果对象基类)、sound_in_obj si(输入描述)、EQ_IO_CONTEXT io与EQ_BUFF buff;- 命令接口:
EQ_CMD_SWITCH_TAB(在线切换系数表/音效)、EQ_GET_CURR_MODE(查询当前模式),由eq_mode_sw()/eq_online_sw()驱动; - 模式集合:
EQ_MODE枚举(见pcm_eq_api.h)定义EQ_ZERO/EQ_POP/EQ_ROCK/EQ_JAZZ/EQ_CLASSIC/EQ_MODEMAX六种预设音效,get_eq_mode_total_num()返回模式总数。
音效模式枚举(来自 pcm_eq_api.h):
typedef enum {
EQ_ZERO = 0x01,
EQ_POP,
EQ_ROCK,
EQ_JAZZ,
EQ_CLASSIC,
EQ_MODEMAX
} EQ_MODE;
设计意图:EQ_PARA_STRUCT 同时保存"当前段"与"下一段"(seg/next_seg),配合 next_global_gain 实现参数平滑过渡——切换音效时先更新 next_seg,运行期逐帧逼近新系数,避免爆音(pop noise)。这也是 EQ_CMD_SWITCH_TAB 与 eq_online_sw() 存在的意义:在线调音不重建 EQ 对象,只交换系数。
Core Flow
下图展示两条 EQ 路径在音频播放期间的完整数据流(以 PCM EQ 为主路径,通用 EQ 框架为并行路径):
sequenceDiagram
participant Dec as 解码器
participant Lnk as link_pcm_eq_sound / link_eq_sound
participant Pcm as pcm_eq_api / EQ open
participant Coef as 系数表 / design_* 函数
participant Run as EQRun / eq_run_api
participant Dac as DAC 缓存
Dec->>Lnk: 输出 PCM 流(sr, channel)
Lnk->>Pcm: 初始化效果(obuf, sr, channel)
Pcm->>Pcm: 校验工作区大小(g_work_buf vs pcm_eq_buf_len)
Pcm->>Coef: get_pcm_eq_tab(sr) / design_pe(...)
Coef-->>Pcm: 10 段系数(LCoeff/R Coeff)
Pcm-->>Lnk: effect 句柄 / EQ_HDL
Lnk->>Lnk: 置 B_DEC_EFFECT,衔接 DAC 缓冲
loop 每帧音频
Dec->>Run: 送入 inbuf(128 点/帧)
Run->>Run: 10 段 IIR 级联 + 增益(1<<20) + SHI/SHO 缩放
Run-->>Dec: outbuf 处理结果
end
Run->>Dac: 效果后数据写入 DAC 缓存
Dac->>Dac: 硬件播放
流程说明:
- 解码器输出 PCM 数据时携带采样率
sr与声道数channel,音频管理模块调用link_pcm_eq_sound()(或通用框架的link_eq_sound())构建效果链; - 初始化阶段做工作区尺寸校验(660 字节静态缓冲),随后从系数表或
design_*动态计算获取 10 段 IIR 系数,构造PCM_EQ_PARM/EQ_PARA_STRUCT; - 挂接成功后,原 sound 节点
enable |= B_DEC_EFFECT,其输出缓冲改为 DAC 缓存,效果句柄通过pp_effect回传供后续参数更新(pcm_eq_updata/EQ_UPDATE); - 播放过程中每帧(
EQ_MAX_POINT = 128点)调用EQRun()/eq_run_api()执行 10 段 IIR 级联滤波,配合SHI=10/SHO=8的定点缩放与LGain/RGain总增益; - 处理后的数据直接落入 DAC 缓存,由硬件播放;切换音效时通过
EQ_CMD_SWITCH_TAB更新next_seg系数并平滑过渡。
Usage Examples
挂接 PCM EQ 到播放链路(播放器侧调用)
播放器在建立音频输出时调用 link_pcm_eq_sound(),将 PCM EQ 插入解码器与 DAC 之间。第二个示例展示通用框架的挂接入口 link_eq_sound(),二者签名一致(均返回下一个 sound 节点):
// PCM 软件 EQ 路径(pcm_eq_api.c)
void *link_pcm_eq_sound(void *p_sound_out, void *p_dac_cbuf, void **pp_effect, u32 sr, u32 channel);
// 通用 EQ 框架路径(audio_eq.h)
void *link_eq_sound(void *p_sound_out, void *p_dac_cbuf, void **pp_effect, u32 sr, u8 ch);
Sources:
运行时更新 EQ 参数(切换音效/在线调音)
低层 API 提供 EQInit/EQUpdate/EQRun 三件套:EQInit 首次初始化工作区,EQUpdate 在播放中替换系数与增益(用于音效切换),EQRun 逐帧处理 npoint 个采样点。PCM 底层物理接口 pcm_eq_phy/pcm_eq_updata 与之对应,pcm_eq_updata 用于运行期更新参数:
int getEQBuf(int nSection, int channel);
void EQInit(void *workBuf, const int *LCoeff_OnChip, const int *RCoeff_OnChip, int LGain, int RGain, int SHI, int SHO, int nSection, int channel);
void EQUpdate(void *workBuf, const int *LCoeff_OnChip, const int *RCoeff_OnChip, int LGain, int RGain, int SHI, int SHO, int nSection, int channel);
void clearEQSOSMem(void *workBuf);
int EQRun(void *workBuf, short *in, short *out, int npoint);
int pcm_eq_buf_len(int nSection, int channel);
void *pcm_eq_phy(void *obuf, void *dbuf, PCM_EQ_PARM *parm, void **ppsound);
void pcm_eq_updata(void *dbuf, PCM_EQ_PARM *p_parm);
Source: pcm_eq_api.h
动态设计 IIR 滤波器系数(硬件 EQ 路径)
通用框架下,App 填充 eq_seg_info 段描述后调用 design_pe() 等函数实时计算系数;eq_db2mag() 完成 dB→线性增益换算,eq_stable_check() 校验系数稳定性:
extern void design_lp(int fc, int fs, float quality_factor, float *coeff); // 低通
extern void design_hp(int fc, int fs, float quality_factor, float *coeff); // 高通
extern void design_pe(int fc, int fs, float gain, float quality_factor, float *coeff); // 峰值
extern void design_ls(int fc, int fs, float gain, float quality_factor, float *coeff); // 低频搁架
extern void design_hs(int fc, int fs, float gain, float quality_factor, float *coeff); // 高频搁架
extern void design_bp(int fc, int fs, float quality_factor, float *coeff); // 带通
extern int eq_stable_check(float *coeff);
float eq_db2mag(float x);
Source: hw_eq.h
Configuration Options
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
PCM_SW_EQ_EN | 编译宏 | 关闭(未定义) | 使能 PCM 软件 EQ;整个 pcm_eq_api.c 实现体被 #if defined(PCM_SW_EQ_EN) && (PCM_SW_EQ_EN) 包裹 |
parm.nSection | int | 10 | EQ 级联段数(EQ_SECTION_MAX 上限同为 10) |
parm.LGain / RGain | int | 1 << 20 | 左右声道总增益(定点,约 0 dB) |
parm.SHI / SHO | int | 10 / 8 | 输入/输出定点缩放移位(Q 格式) |
parm.channel | int | 运行时传入 | 声道数;> 1 时右声道复用左声道系数表 |
g_work_buf | static u32[] | 660 字节(.pcm_eq_data 段) | EQ 运行工作区;不足时初始化失败 |
EQ_MAX_POINT | 宏 | 128 | 单帧最大处理点数(EQ_BUFF.buff[EQ_MAX_POINT*2]) |
EQ_COEFF_BASE | 宏 | 0x2F0000 | EQ 系数存放地址基址(硬件侧) |
EQ_OUTQ | 宏 | 22 | 定点乘法输出 Q 格式(mul64/div64) |
| 增益范围 | - | -12 ~ +12 dB | eq_seg_info.gain 允许范围 |
| Q 值范围 | - | 0.3 ~ 30 | eq_seg_info.q 允许范围 |
| 运行模式 | 枚举 | NORMAL | NORMAL/MONO/STEREO(hw_eq.h) |
| 输入/输出格式 | 枚举 | DATI_SHORT/DATO_SHORT | short/int/float 三选一 |
| 数据排布 | 枚举 | SEQUENCE_DAT_IN/SEQUENCE_DAT_OUT | 序列模式(交错)或块模式(连续) |
API Reference
PCM EQ 层(pcm_eq_api.h / pcm_eq_api.c)
void *pcm_eq_api(void *obuf, u32 sr, u32 channel, void **ppsound) 初始化 10 段 PCM EQ,返回效果句柄;工作区不足返回 NULL。
- 参数:
obuf输入缓冲;sr采样率;channel声道数;ppsound输出下一个 sound 节点指针。 - 返回:EQ 效果句柄(可写入
sound_out_obj.effect);失败为NULL。 - 注:内部使用全局
PCM_EQ_PARM parm与静态g_work_buf。
const int *get_pcm_eq_tab(u32 sr) 按采样率返回预计算系数表首地址;未匹配的采样率回落 32 kHz 表。
- 参数:
sr采样率(8000/12000/16000/24000/32000/44100/48000 等)。 - 返回:
const int *系数表指针。
void *link_pcm_eq_sound(void *p_sound_out, void *p_dac_cbuf, void **pp_effect, u32 sr, u32 channel) 将 PCM EQ 挂入声音输出链表。
- 参数:
p_sound_out当前 sound 节点;p_dac_cbufDAC 缓存;pp_effect回传效果句柄;sr/channel采样率与声道数。 - 返回:链路中下一个 sound 节点;EQ 初始化失败时返回原节点(直通降级)。
int pcm_eq_buf_len(int nSection, int channel):计算指定段数/声道数所需工作区长度(字节)。 void *pcm_eq_phy(void *obuf, void *dbuf, PCM_EQ_PARM *parm, void **ppsound):底层物理初始化(在静态库 lib_pcm_eq.a 中实现)。 void pcm_eq_updata(void *dbuf, PCM_EQ_PARM *p_parm):运行期更新 EQ 参数。
底层 EQ 引擎(pcm_eq_api.h 前段声明)
void EQInit(void *workBuf, const int *LCoeff_OnChip, const int *RCoeff_OnChip, int LGain, int RGain, int SHI, int SHO, int nSection, int channel):初始化工作区与系数。 void EQUpdate(...):同签名,播放中替换系数/增益/移位参数(音效切换)。 void clearEQSOSMem(void *workBuf):清零滤波器状态(SOS 延迟线),用于切换音效前避免残留状态。 int EQRun(void *workBuf, short *in, short *out, int npoint):处理 npoint 个采样点;in/out 可为同址(就地处理)。
通用 EQ 框架(audio_eq.h)
const EQ_STUCT_API *get_eq_context(void) 获取 EQ 操作集(need_buf/open/run/config)。
need_buf(max_eq_seg, ch):计算所需缓冲字节数。open(ptr, para, rsio):按EQ_PARA_STRUCT打开 EQ,返回工作区指针。run(ptr, inbuf, len):处理一帧数据。config(work_buf, cmd, parm):执行EQ_CMD_SWITCH_TAB/EQ_GET_CURR_MODE等命令。
void *eq_hld_malloc():分配 EQ_HDL 句柄。 void eq_reless(void **ppeffect):释放 EQ 效果句柄。 void eq_online_sw(void *parm):在线调音切换(配合 music_eq_tool)。 void eq_mode_sw(void *parm):音效模式切换(EQ_MODE)。 extern u8 get_eq_mode_total_num():返回预设音效模式总数。 void eq_online_sw 的数据载体 struct music_eq_tool:包含 global_gain、seg_num、enable_section 与最多 10 段的 seg[EQ_SECTION_MAX],其中 music_eq_tool_online 为在线调音共享实例。
Failure Modes, Edge Cases & Concurrency
- 工作区不足导致 EQ 挂接失败:
pcm_eq_api()用pcm_eq_buf_len(10, channel)与实际静态缓冲sizeof(g_work_buf)(660 字节)比较,不足时打印"PCM EQ Work Data space is not big enough"并返回NULL。调用方link_pcm_eq_sound()检测到NULL后不置位B_DEC_EFFECT、不修改输出缓冲,链路保持直通——这是有意的降级策略:EQ 异常不阻断音频播放,仅丢失音效。若需更多段数或更多声道,必须同步扩大g_work_buf(注意其AT(.pcm_eq_data)段属性,该段位于内部 SRAM,容量有限)。 - 采样率未覆盖:
get_pcm_eq_tab()的 switch 未命中任何 case 时返回 32 kHz 表。若实际播放采样率(如 96 kHz)与 32 kHz 相差过大,系数表与采样率不匹配会造成滤波器频率响应偏移。PCM EQ 路径目前只预置了 8k/12k/16k/24k/32k(及 44.1k/48k 等)常用采样率表,使用前应确认表覆盖率。 - 声道数边界:
channel大于 1 时左右声道共用同一张系数表(RCoeff_OnChip = LCoeff_OnChip),因此 PCM EQ 路径不区分左右声道差异化曲线;需要差异化时须走通用框架(EQ_PARA_STRUCT.ch支持 1/2 声道)并自行提供双表。 - 切换音效的爆音风险:直接改写系数会使 IIR 延迟线中的历史状态与新系数不匹配,产生瞬态噪声。框架通过
EQ_PARA_STRUCT的seg/next_seg双缓冲与next_global_gain平滑逼近,且提供clearEQSOSMem()清空 SOS 状态;PCM EQ 路径切换前应调用clearEQSOSMem或pcm_eq_updata配合淡入淡出(B_EQ_FADE_IN/B_EQ_FADE_OUT)。 - 并发与中断上下文:EQ 运行(
EQRun/eq_run_api)发生在音频数据回调/任务上下文,与 App 侧参数更新(pcm_eq_updata/EQ_UPDATE/eq_mode_sw)属于生产者-消费者关系。PCM_EQ_PARM为全局变量,pcm_eq_updata直接改写其字段,若在 EQ 处理中并发更新存在数据竞争;通用框架用next_seg双缓冲缓解,PCM EQ 路径则依赖音频管理模块保证"先停止/再更新/后恢复"的串行时序。SDK 未提供互斥锁,跨任务更新参数时应遵循该时序约定。 - 定点溢出:输入经
SHI=10上移、输出经SHO=8下移,总增益1<<20提供了约 2 的净增益余量;若 App 调高LGain/RGain或输入接近满幅,级联滤波可能使中间累加值超过 int32 范围(mul64的 Q22 格式限制了定点精度),应通过eq_stable_check()与增益上限约束规避削波/溢出。
Performance & Operational Considerations
- 静态缓冲、零动态分配:PCM EQ 使用编译期分配的
g_work_buf(660 字节)与全局parm,运行期无 malloc/free,符合嵌入式实时音频的确定性要求;通用框架的eq_hld_malloc()则用于需要多实例/动态管理的场景。 - 定点查表 vs 浮点设计:PCM EQ 路径全整型系数(EQTools 离线生成),每帧仅做乘加与移位,开销极小;通用框架路径用 float 系数并缓存到
eq_oeff_tab[],open时计算一次、run时复用,避免了逐帧重算系数。 - 单帧处理点数:
EQ_MAX_POINT = 128定义了EQ_BUFF.buff与EQRun的帧粒度,实际帧长小于 128 时余量闲置;处理延时可近似为 128 点/采样率。 - 数据排布模式:
SEQUENCE_DAT_IN/OUT(交错)与BLOCK_DAT_IN/OUT(连续)两种排布在hw_eq.h中定义,audio_eq.h默认选择序列模式,与解码器/ DAC 的 DMA 缓冲格式一致时可避免额外的重排拷贝。 - 系数表内存占用:9 张表 × 10 段 × 5 个 int ≈ 1.8 KB 常量区,位于 Flash(
const),不影响运行期 RAM;如需精简可裁剪get_pcm_eq_tab中不使用的采样率分支。
Extension Points
- 新增音效模式:在
EQ_MODE枚举(EQ_ZERO/EQ_POP/EQ_ROCK/EQ_JAZZ/EQ_CLASSIC)中追加模式并同步get_eq_mode_total_num();PCM EQ 路径需为新模式生成对应的 10 段系数表(EQTools 离线生成后烧录),通用框架路径可在eq_mode_sw回调中填充music_eq_tool/EQ_PARA_STRUCT的next_seg实现动态模式。 - 在线调音(App 侧):通过
music_eq_tool_online(global_gain+ 10 段seg[]+enable_section)实时调整各段freq/gain/q,再经eq_online_sw()与EQ_CMD_SWITCH_TAB生效;enable_section可动态禁用某段(直通)。 - 扩展滤波类型:在
EQ_IIR_TYPE枚举后追加新类型,并在design_*函数族中补充对应的系数设计函数;eq_stable_check()对所有类型统一校验稳定性,新增类型应复用该检查。 - 自定义效果节点:仿照
link_pcm_eq_sound()的模式(sound_out_obj.effect+B_DEC_EFFECT+ 返回下一节点)可将其他音效(混响、压缩等)插入同一链路,多个效果可串联。
Related Links
- pcm_eq_api.c — PCM 软件 EQ 实现
- pcm_eq_api.h — PCM EQ 参数与底层引擎 API
- audio_eq.h — 通用 EQ 效果框架
- hw_eq.h — 硬件 EQ / IIR 滤波器设计
- 音频输出链路与声音管理:
sound_mge.h/sound_out_obj(EQ 节点挂接依赖的结构定义) - 解码器输出接口:
decoder_api.h(EQ 处理前的 PCM 数据来源) - 相关静态库:
lib_pcm_eq.a/ae_pcm_eq_lib.a(pcm_eq_phy、EQRun等底层实现) - 相邻目录页:音频编解码格式、DAC/ADC 驱动、音频链路同步(audio_link)