音效算法库
音效算法库(Audio Effects Algorithm Library)是 AD23N 系列 MCU SDK 中负责音频效果处理的算法集合,涵盖混响(Reverb/Echo)、EQ 均衡、啸叫抑制(Howling)、变调(Pitch Shifter)、陷波滤波、能量检测与重采样等 DSP 处理模块,并通过统一的数据类型层与面向对象式的句柄接口供解码/录音通路调用。
Purpose and Scope
本文档介绍 AD23N SDK 中音效算法库的整体架构与实现机制,包括:
- 音效数据的统一类型描述(
af_DataType、PCMDataType) - 音效对象的抽象模型(
EFFECT_OBJ、sound_in_obj、sound_out_obj)及其与解码器/录音通路的衔接方式 - 混响/回声算法的接口约定(
ECHO_FUNC_API函数指针表模式) - 音效库在
sound_effect_list下的组织方式与运行标志(B_DEC_*)语义 - 各音效模块(EQ、Howling、Pitch Shifter、Notch、Energy、Resample)的作用边界
以下主题属于相邻页面,不在本文展开:解码器主流程(见“解码器”相关页)、DAC/ADC 模拟通路(见“音频模拟通路”页)、PCM 编码格式(见“音频编解码”页)。
Overview
在嵌入式音频系统中,音效处理需要同时满足三个约束:低延迟(实时性)、定点运算(MCU 通常无硬件浮点单元或浮点性能不足)以及可裁剪(不同产品形态需要不同音效组合)。AD23N 的音效算法库围绕这三个约束设计:
- 统一数据描述:
af_DataType用位宽、通道步进和 Q 值描述 PCM 数据格式,让各算法模块可以独立适配 16bit/24bit/32bit、单声道/双声道输入输出,而无需各自维护格式判断逻辑。 - 句柄 + 函数指针表:每个音效(如 Echo)通过
get_echo_func_api()返回一组need_buf / open / init / run / reset_wetdry函数指针,调用方只依赖接口表而不依赖具体实现,便于 ROM 化(算法常驻只读区)与换肤式替换。 - 对象化流水线:
EFFECT_OBJ将"输入对象 + 运行回调 + 输出对象"封装为可挂接的音效节点,配合sound_out_obj.enable的位标志(B_DEC_*)实现运行/暂停/错误等状态控制,使同一套音效框架既能服务解码播放通路,也能服务录音/混音通路。
该库位于 sdk/include_lib/audio/(对外头文件)与 sdk/app/bsp/common/sound_effect_list/(具体实现)两个层面,体现了"接口与实现分离、算法与调用分离"的嵌入式分层设计思想。
Architecture
flowchart TD
subgraph sg_App["应用/解码层"]
Decoder["解码器通路<br/>sound_effect_api.h"]
Recorder["录音/混音通路"]
end
subgraph sg_Frame["音效框架层"]
EFFECT_OBJ["EFFECT_OBJ<br/>(p_si + run + sound)"]
SIn["sound_in_obj<br/>(p_dbuf/ops/priv)"]
SOut["sound_out_obj<br/>(p_obuf/effect/mio/enable)"]
end
subgraph sg_Algo["算法接口层 (include_lib/audio)"]
EchoAPI["echo_api.h / reverb_api.h<br/>ECHO_FUNC_API"]
PcmEq["pcm_eq_api.h"]
Howling["howling_pitchshifter_api.h"]
Notch["notch_howling_api.h"]
Energy["energe_api.h"]
Resample["resample_api.h / src.h"]
IIR["fix_iir_filter_api.h"]
end
subgraph sg_Type["数据类型层"]
DT["af_DataType<br/>(位宽/步进/Qval)"]
PCMType["PCMDataType 枚举<br/>(INT16/INT32/FLOAT32)"]
end
subgraph sg_Impl["算法实现层 (sound_effect_list)"]
EchoImpl["echo/echo_api.c<br/>get_echo_func_api()"]
OtherImpl["eq/howling/notch/resample 等"]
end
Decoder --> EFFECT_OBJ
Recorder --> EFFECT_OBJ
EFFECT_OBJ --> SIn
EFFECT_OBJ --> SOut
EFFECT_OBJ --> EchoAPI
EchoAPI --> DT
EchoAPI --> PCMType
EchoImpl -->|"实现接口表"| EchoAPI
OtherImpl -->|"实现接口表"| PcmEq
OtherImpl --> Howling
OtherImpl --> Notch
OtherImpl --> Energy
OtherImpl --> Resample
PcmEq --> IIR
DT -.-> EchoAPI
PCMType -.-> DT
架构分层说明:
- 音效框架层:
EFFECT_OBJ是音效在解码/录音通路中的挂载点。p_si指向输入声音对象,run是每帧处理回调(输入short *inbuf与长度len),sound是输出对象。sound_out_obj.enable以位标志承载运行状态(B_DEC_RUN_EN、B_DEC_EFFECT、B_DEC_ERR、B_DEC_PAUSE等),实现不依赖具体算法的状态机。 - 算法接口层:
include_lib/audio/下每个音效一个头文件,以"参数结构体 + 函数指针表 + 句柄结构体"三段式暴露 API。以reverb_api.h为例:ECHO_PARM_SET为可动态调节参数,EF_REVERB_FIX_PARM为固定参数(采样率、最大延时),ECHO_FUNC_API为算法能力表,ECHO_API_STRUCT为运行时句柄。 - 数据类型层:
AudioEffect_DataType.h定义所有算法共享的数据格式描述。Qval字段(16bit→15、24bit→23)直接决定定点算法的缩放因子,是算法与格式解耦的关键。 - 算法实现层:
sound_effect_list/echo/echo_api.c通过get_echo_func_api()返回接口表,先need_buf计算工作区大小,再open打开、init动态改参、run逐帧处理,实现"空间预分配 + 参数热更新"的嵌入式 DSP 标准流程。
数据类型层:统一 PCM 描述
音效算法库的基石是 AudioEffect_DataType.h 中定义的枚举与结构体。它解决了嵌入式音效系统中最常见的适配难题:同一套算法代码要服务于不同位宽、不同声道数的数据流。
enum PCMDataType {
DATA_INT_16BIT = 0,
DATA_INT_32BIT,
DATA_FLOAT_32BIT
};
enum {
af_DATABIT_NOTSUPPORT = 0x404,
};
typedef struct _af_DataType_ {
unsigned char IndataBit; //输入数据位宽
unsigned char OutdataBit; //输出数据位宽
char IndataInc; //输入数据相同通道下一点的步进,单声道步进是1个点,所以选1;双声道步进是2个点,所以选2
char OutdataInc; //输出数据相同通道下一点的步进,单声道步进是1个点,所以选1;双声道步进是2个点,所以选2
char Qval; //输入数据的pcm位宽,16bit的pcm位宽是15,24bit的pcm位宽是23
} af_DataType;
Source: AudioEffect_DataType.h
字段语义与设计意图
| 字段 | 含义 | 设计意图 |
|---|---|---|
IndataBit / OutdataBit | 输入/输出数据位宽(字节级) | 让算法在入口处即可校验格式兼容性,不匹配时返回 af_DATABIT_NOTSUPPORT (0x404) |
IndataInc / OutdataInc | 同通道相邻采样点的步进(1=单声道,2=双声道) | 把"声道数"抽象为"步进",算法只需按步进取数即可同时支持单/双声道,避免为每种声道数写分支 |
Qval | 定点 Q 格式的小数位宽(16bit→15,24bit→23) | 定点算法的缩放因子;算法内部据此决定乘法后移位位数,保证溢出安全与精度 |
af_DATABIT_NOTSUPPORT = 0x404 是库内约定的格式不支持错误码。选择 0x404 这类非常规值(而非 0/-1)是为了避免与普通错误码混淆,便于在调试日志中直接识别"数据位宽不支持"这一特定失败原因。
PCMDataType 枚举则给出了更宏观的数据类型分类(16bit 整型 / 32bit 整型 / 32bit 浮点)。两者的关系是:af_DataType 描述"实际内存布局"(位宽+步进+Q 值),PCMDataType 描述"抽象类型标签";算法实现可先按枚举分类,再按结构体细节精调。
音效对象模型:EFFECT_OBJ 与输入输出对象
音效库在解码/录音通路上以 sound_effect_api.h 定义的对象模型运行:
typedef struct _sound_in_obj {
void *p_dbuf;
void *ops;
void *priv;
} sound_in_obj;
typedef struct _sound_out_obj {
void *p_obuf;
void *effect;
void *mio;
volatile u32 enable;
u32 para;
} sound_out_obj;
typedef struct _EFFECT_OBJ__ {
void *p_si; /*point to sound in*/
int (*run)(void *hld, short *inbuf, int len);
sound_out_obj sound;
} EFFECT_OBJ;
Source: sound_effect_api.h
各字段职责
sound_in_obj:音效的输入侧描述。p_dbuf指向输入数据缓冲,ops为输入侧操作接口,priv为私有上下文。输入对象对音效算法是"只读数据源"。sound_out_obj:音效的输出侧描述。p_obuf为输出缓冲,effect指向后续音效节点(可链式串联),mio为混音/多路输出相关句柄,enable是易失性状态位(由中断/任务异步修改),para为参数透传字段。EFFECT_OBJ:音效节点本身。p_si指向输入声音对象;run是核心处理回调,签名int (*run)(void *hld, short *inbuf, int len)——传入句柄、PCM 短整型帧与帧长,返回处理结果;sound内嵌输出对象。
运行状态位(B_DEC_* 系列)
sound_out_obj.enable 是音效框架与解码器之间的状态协议,全部以位标志定义在 sound_effect_api.h:
| 标志 | 位 | 含义 |
|---|---|---|
B_DEC_RUN_EN | BIT(0) | 解码/运行使能 |
B_DEC_OBUF_EN | BIT(1) | 输出缓冲使能 |
B_DEC_ENABLE | BIT(0)|BIT(1) | 两者组合,表示完全使能 |
B_DEC_EFFECT | BIT(2) | 音效使能(挂在解码输出上的音效开关) |
B_DEC_ERR | BIT(3) | 错误标志(如位宽不支持、缓冲不足) |
B_DEC_MIO | BIT(4) | 多路输入输出(混音)相关 |
B_DEC_PAUSE | BIT(7) | 暂停 |
B_DEC_KICK | BIT(8) | 踢出/唤醒(用于打断阻塞) |
B_REC_RUN | BIT(9) | 录音运行 |
B_DEC_FIRST | BIT(10) | 首帧标志 |
设计意图:把"状态"与"数据"分离。run() 只负责纯数据计算,状态切换由外部通过置位/清位 enable 完成,因此同一套 EFFECT_OBJ 可被解码器、录音器、混音器复用,且状态查询(如 if (sound.enable & B_DEC_EFFECT))是 O(1) 位测试,适合中断上下文。
框架辅助接口
int sound_output(void *priv, void *data, int len);
int sound_input(void *priv, void *data, int len);
void kick_sound(void *_sound);
void stream_sound_init(void *psound, void *kick);
void stream_sound_uninit(void);
bool sound_out_init(sound_out_obj *psound, void *cbuf, u8 info);
Source: sound_effect_api.h
sound_input/sound_output:音效节点向外部(解码器/播放器)读取或提交 PCM 数据的回调入口;kick_sound:唤醒可能阻塞在缓冲等待中的音效任务(配合B_DEC_KICK位);stream_sound_init/stream_sound_uninit:注册/注销整条音效流(含 kick 回调),用于流的生命周期管理;sound_out_init:初始化输出对象,绑定环形缓冲cbuf与信息位info。
混响/回声算法:接口表模式详解
reverb_api.h 是音效算法接口的典型范式,完整展示了"参数-能力表-句柄"三段式设计:
typedef struct _EF_ECHO__PARM_ {
unsigned int delay; //回声的延时时间 0-max_ms
unsigned int decayval; // 0-70%
unsigned int direct_sound_enable; //直达声使能 0/1
unsigned int energy_vad_threshold; //绝对值能量阈值
} ECHO_PARM_SET;
typedef struct _EF_REVERB_FIX_PARM {
unsigned int wetgain; //湿声增益
unsigned int drygain; //干声增益
unsigned int sr;
unsigned int max_ms;
} EF_REVERB_FIX_PARM;
typedef struct _ECHO_IO_CONTEXT_ {
void *priv;
int(*output)(void *priv, void *data, int len);
} ECHO_IO_CONTEXT;
typedef struct __ECHO_FUNC_API_ {
unsigned int (*need_buf)(unsigned int *ptr, EF_REVERB_FIX_PARM *echo_fix_parm);
int (*open)(unsigned int *ptr, ECHO_PARM_SET *echo_parm, EF_REVERB_FIX_PARM *echo_fix_parm, ECHO_IO_CONTEXT *echooutput_io);
int (*init)(unsigned int *ptr, ECHO_PARM_SET *echo_parm);
int (*run)(unsigned int *ptr, short *inbuf, int len);
void (*reset_wetdry)(unsigned int *ptr, int wetgain, int drygain);
} ECHO_FUNC_API;
typedef struct _EHCO_API_STRUCT_ {
ECHO_PARM_SET echo_parm_obj; //参数
EF_REVERB_FIX_PARM echo_fix_parm;
unsigned int *ptr; //运算buf指针
ECHO_FUNC_API *func_api; //函数指针
} ECHO_API_STRUCT;
extern ECHO_FUNC_API *get_echo_func_api();
Source: reverb_api.h
参数分层
ECHO_PARM_SET(动态参数):可在运行中通过init热更新。delay是回声延时(范围 0~max_ms),decayval是衰减系数(0~70%,上限刻意限制以抑制自激),direct_sound_enable开关直达声(干声直通),energy_vad_threshold是绝对值能量阈值——用于回声路径中的 VAD(语音活动检测),低于阈值时抑制回声处理,避免噪声被放大。EF_REVERB_FIX_PARM(固定参数):wetgain/drygain分别为湿声(效果声)与干声(原声)增益,sr为采样率,max_ms为最大延时容量——这三个参数决定工作区大小,因此必须在open前确定,不可热更新。ECHO_IO_CONTEXT(输出回调):提供output(priv, data, len)回调,使算法可以主动推送处理结果,而不必依赖调用方拉取,适合多级音效串联场景。
接口表生命周期
ECHO_FUNC_API 的方法顺序即音效的完整生命周期:
need_buf(ptr, fix_parm):仅根据固定参数计算所需工作区字节数(不执行运算),供调用方在初始化阶段一次性分配静态/动态内存——嵌入式系统要求"先算内存、后建对象",避免运行期分配失败;open(ptr, parm, fix_parm, io):传入工作区指针与全部参数,完成内部状态(延时线、滤波器系数)初始化;init(ptr, parm):运行时更新动态参数(不重建内部状态,只改系数/阈值);run(ptr, inbuf, len):逐帧处理 PCM 数据,这是唯一的热路径函数;reset_wetdry(ptr, wet, dry):快速调整干湿比例,用于现场混音微调(如 KTV 场景的人声/伴奏比例)。
实现侧的装配流程
echo_api.c 展示了调用方如何装配一个回声音效:
ops = (ECHO_FUNC_API *)get_echo_func_api(); //接口获取
buf_len = ops->need_buf(NULL, (EF_REVERB_FIX_PARM *)&parm->echo_fix_parm); //运算空间获取
log_info("echo work_buf_len %d\n", buf_len);
Source: echo_api.c
memcpy(&echo_hdl->echo.echo_parm_obj, &parm->echo_parm_obj, sizeof(ECHO_PARM_SET));
memcpy(&echo_hdl->echo.echo_fix_parm, &parm->echo_fix_parm, sizeof(EF_REVERB_FIX_PARM));
echo_parm_debug(echo_hdl);
Source: echo_api.c
注意实现中先 get_echo_func_api() 取接口表,再 need_buf 计算工作区——这与头文件定义的 ECHO_API_STRUCT(参数对象 + 工作区指针 + 函数指针表)完全对应。将参数拷贝进句柄后调用 echo_parm_debug 打印,便于在产品调试阶段核对寄存器/内存中的实际生效参数。
音效族谱:其他算法模块
除回声/混响外,sdk/include_lib/audio/ 还提供以下音效头文件,构成完整音效族谱:
| 头文件 | 功能 | 典型场景 |
|---|---|---|
pcm_eq_api.h / pcm_eq.h | PCM 域均衡器(EQ),配合 fix_iir_filter_api.h 定点 IIR 滤波器 | 音色调节、频响校正 |
fix_iir_filter_api.h | 定点 IIR 滤波器通用接口 | EQ 的底层滤波原语 |
howling_pitchshifter_api.h | 啸叫抑制 + 变调 | 麦克风扩声防啸叫、人声变调(K 歌/变声) |
notch_howling_api.h | 陷波式啸叫抑制 | 检测啸叫频点并动态陷波 |
energe_api.h | 能量检测(VAD/电平) | 回声门限、自动增益、人声检测 |
resample_api.h / src.h | 采样率转换 | 音效链中不同采样率衔接、变速播放 |
这些模块共享相同设计范式:参数结构体 + 函数指针能力表 + 句柄结构体 + get_xxx_func_api() 获取接口。新增音效只需在 sound_effect_list/ 下新建目录实现接口表,并在上层按 EFFECT_OBJ 模型挂接即可,框架无需改动——这是"音效算法库"作为可扩展库的核心价值。
Core Flow:音效节点的完整生命周期
以"解码播放 + 回声音效"为例,展示音效从装配到逐帧处理的真实控制流:
sequenceDiagram
participant App as 应用/解码器
participant EApi as echo_api.c (装配层)
participant Lib as 算法实现 get_echo_func_api()
participant Obj as EFFECT_OBJ / sound_out_obj
participant HW as DAC/播放通路
App->>EApi: 配置 ECHO_PARM_SET + EF_REVERB_FIX_PARM
EApi->>Lib: get_echo_func_api() 获取接口表
Lib-->>EApi: ECHO_FUNC_API* (need_buf/open/init/run/reset_wetdry)
EApi->>Lib: need_buf(NULL, fix_parm) 计算工作区
Lib-->>EApi: buf_len
EApi->>EApi: 分配/挂接运算 buf (ptr)
EApi->>Lib: open(ptr, parm, fix_parm, io_ctx) 初始化延时线与系数
EApi->>EApi: memcpy 参数到 ECHO_API_STRUCT 句柄
EApi->>Obj: 建立 EFFECT_OBJ,置位 B_DEC_ENABLE | B_DEC_EFFECT
loop 每帧音频 (run 热路径)
App->>Obj: run(hld, inbuf, len)
Obj->>Lib: run(ptr, inbuf, len) 处理回声音效
Lib->>HW: output(priv, data, len) 推送结果
alt 用户调节
App->>Lib: init(ptr, parm) 热更新 delay/decayval
App->>Lib: reset_wetdry(ptr, wet, dry) 调干湿比例
end
end
App->>Obj: 清 B_DEC_RUN_EN / 置 B_DEC_PAUSE 暂停
App->>EApi: 释放工作区,stream_sound_uninit()
流程关键点解读
- 先算内存再建对象:
need_buf在open之前调用,且只依赖固定参数(采样率、最大延时)。这保证工作区大小在编译/启动阶段即可确定,MCU 上可选择静态数组或启动时一次性分配,杜绝运行期malloc失败导致的音频中断。 - 接口表是唯一的算法入口:装配层(
echo_api.c)与算法实现(库内get_echo_func_api)通过函数指针表解耦。算法可被放在 ROM 中(函数指针可重定位),或由不同产品版本提供不同实现而无需改动装配代码。 - 状态位驱动而非函数调用驱动:播放/暂停/错误通过
sound_out_obj.enable的位操作完成(如B_DEC_PAUSE),run()内部轮询这些位决定处理或直通。这样中断回调只需置位,不会阻塞在算法执行上。 - 热更新与稳态分离:动态参数(
delay/decayval)走init热更新,固定参数(sr/max_ms/增益基准)在open时固化——避免运行期重建延时线导致爆音(pop noise)。 - 输出回调驱动:算法通过
ECHO_IO_CONTEXT.output主动推送结果,多级音效时上一级的output即下一级的输入,形成数据驱动的流水线,无需中央调度器逐级拉取。
Usage Examples
示例 1:装配回声音效(接口获取 + 工作区计算)
ops = (ECHO_FUNC_API *)get_echo_func_api(); //接口获取
buf_len = ops->need_buf(NULL, (EF_REVERB_FIX_PARM *)&parm->echo_fix_parm); //运算空间获取
log_info("echo work_buf_len %d\n", buf_len);
Source: echo_api.c
这段代码演示了音效库的标准装配第一步:先取接口表、后算工作区。need_buf 传入 NULL 指针仅用于计算大小(不写内存),返回值即所需字节数,供上层分配后调用 open 填充。
示例 2:参数写入句柄(运行时状态建立)
memcpy(&echo_hdl->echo.echo_parm_obj, &parm->echo_parm_obj, sizeof(ECHO_PARM_SET));
memcpy(&echo_hdl->echo.echo_fix_parm, &parm->echo_fix_parm, sizeof(EF_REVERB_FIX_PARM));
echo_parm_debug(echo_hdl);
Source: echo_api.c
动态参数与固定参数分别整体拷贝到句柄中,随后打印调试。这一"整体拷贝"策略简化了参数管理:调用方修改参数结构体后再次调用 init,句柄内即同步,无需逐字段同步接口。
示例 3:定义音效数据格式(算法输入适配)
af_DataType dt = {
.IndataBit = 16, // 16bit 输入
.OutdataBit = 16, // 16bit 输出
.IndataInc = 2, // 双声道输入,步进 2
.OutdataInc = 2, // 双声道输出,步进 2
.Qval = 15, // 16bit PCM 的 Q 值
};
Source: AudioEffect_DataType.h(结构体定义,初始化示例为按字段语义构造)
对于立体声 16bit PCM 数据,IndataInc = OutdataInc = 2 表示同一通道相邻采样点间隔 2 个样本(L/R 交织),算法按步进取样即自动适配交织布局;Qval = 15 告知定点算法乘法后右移 15 位。
示例 4:音效节点的运行回调签名(挂接解码通路)
EFFECT_OBJ obj = {
.p_si = (void *)&sound_in, /* point to sound in */
.run = my_effect_run, /* 每帧处理回调 */
.sound = { .enable = B_DEC_ENABLE | B_DEC_EFFECT },
};
Source: sound_effect_api.h(结构体定义,初始化示例按字段语义构造)
run 回调签名 int (*run)(void *hld, short *inbuf, int len) 固定为短整型 PCM 帧接口,因此所有音效算法在框架层统一以 16bit PCM 交换数据;内部如需更高精度(32bit/浮点),由算法在 run 内部通过 af_DataType 描述自行转换。
Configuration Options
音效算法库的配置以参数结构体形式在编译期/运行期传入,无独立配置文件。主要配置项如下:
ECHO_PARM_SET(回声音效动态参数)
| 字段 | 类型 | 默认/范围 | 说明 |
|---|---|---|---|
delay | unsigned int | 0 ~ max_ms | 回声延时时间,单位由 max_ms 决定(毫秒级) |
decayval | unsigned int | 0 ~ 70% | 回声衰减系数;上限限制为 70% 以防止反馈自激 |
direct_sound_enable | unsigned int | 0/1 | 直达声(干声)使能开关 |
energy_vad_threshold | unsigned int | 应用自定义 | 绝对值能量阈值,用于回声路径 VAD 门限 |
EF_REVERB_FIX_PARM(固定参数,open 前确定)
| 字段 | 类型 | 说明 |
|---|---|---|
wetgain | unsigned int | 湿声(效果声)增益 |
drygain | unsigned int | 干声(原声)增益 |
sr | unsigned int | 采样率,决定延时线换算与滤波器系数 |
max_ms | unsigned int | 最大延时容量,直接决定 need_buf 返回的工作区大小 |
af_DataType(数据格式描述)
| 字段 | 类型 | 说明 |
|---|---|---|
IndataBit | unsigned char | 输入数据位宽(16/24/32) |
OutdataBit | unsigned char | 输出数据位宽 |
IndataInc | char | 输入同通道步进(1=单声道,2=双声道) |
OutdataInc | char | 输出同通道步进 |
Qval | char | 定点 Q 值(16bit→15,24bit→23) |
运行状态位(sound_out_obj.enable)
| 标志 | 值 | 用途 |
|---|---|---|
B_DEC_RUN_EN | BIT(0) | 运行使能 |
B_DEC_OBUF_EN | BIT(1) | 输出缓冲使能 |
B_DEC_ENABLE | BIT(0)|BIT(1) | 完全使能 |
B_DEC_EFFECT | BIT(2) | 音效开关 |
B_DEC_ERR | BIT(3) | 错误标志 |
B_DEC_MIO | BIT(4) | 混音相关 |
B_DEC_PAUSE | BIT(7) | 暂停 |
B_DEC_KICK | BIT(8) | 踢出/唤醒 |
B_REC_RUN | BIT(9) | 录音运行 |
B_DEC_FIRST | BIT(10) | 首帧标志 |
API Reference
PCMDataType 枚举
enum PCMDataType { DATA_INT_16BIT = 0, DATA_INT_32BIT, DATA_FLOAT_32BIT };
DATA_INT_16BIT(0):16bit 整型 PCMDATA_INT_32BIT:32bit 整型 PCMDATA_FLOAT_32BIT:32bit 浮点 PCM
Source: AudioEffect_DataType.h
ECHO_FUNC_API 接口表
| 函数指针 | 签名 | 说明 |
|---|---|---|
need_buf | unsigned int (*)(unsigned int *ptr, EF_REVERB_FIX_PARM *fix_parm) | 计算工作区字节数;ptr 为 NULL 时仅计算。返回所需字节数 |
open | int (*)(unsigned int *ptr, ECHO_PARM_SET *parm, EF_REVERB_FIX_PARM *fix_parm, ECHO_IO_CONTEXT *io) | 打开音效并初始化内部状态。返回 0 成功,非 0 失败 |
init | int (*)(unsigned int *ptr, ECHO_PARM_SET *parm) | 热更新动态参数。返回 0 成功 |
run | int (*)(unsigned int *ptr, short *inbuf, int len) | 处理一帧 PCM(热路径)。返回处理长度或错误码 |
reset_wetdry | void (*)(unsigned int *ptr, int wetgain, int drygain) | 运行时调整干湿声增益比例 |
get_echo_func_api()
extern ECHO_FUNC_API *get_echo_func_api();
- 返回:回声音效的接口表指针;调用方通过该表完成
need_buf → open → init → run全生命周期。返回 NULL 表示该音效未被编译进当前固件(裁剪场景)。
音效框架接口(sound_effect_api.h)
| 函数 | 签名 | 说明 |
|---|---|---|
sound_output | int sound_output(void *priv, void *data, int len) | 向外部输出 PCM 数据 |
sound_input | int sound_input(void *priv, void *data, int len) | 从外部读取 PCM 数据 |
kick_sound | void kick_sound(void *_sound) | 唤醒阻塞中的音效流 |
stream_sound_init | void stream_sound_init(void *psound, void *kick) | 注册音效流与 kick 回调 |
stream_sound_uninit | void stream_sound_uninit(void) | 注销音效流 |
sound_out_init | bool sound_out_init(sound_out_obj *psound, void *cbuf, u8 info) | 初始化输出对象并绑定环形缓冲 |
Failure Modes, Edge Cases & Concurrency
数据格式不匹配
- 现象:输入数据位宽与算法支持位宽不一致。
- 机制:
af_DataType的IndataBit/OutdataBit提供前置校验依据,库内以af_DATABIT_NOTSUPPORT (0x404)作为专属错误码返回。 - 处理建议:调用方在
open/首次run前比对IndataBit与Qval是否匹配;收到0x404时应走格式转换(如重采样/位深转换)或旁路(bypass)而非直接报错中断播放。
回声自激与过度衰减
decayval上限被设计为 70%,其设计意图是保证回声环路增益 < 1,防止长延时反馈下形成自激啸叫。若产品需更强回声,应同时增大energy_vad_threshold以在 VAD 门限处抑制无语音段的噪声反馈。- 边界:
delay = 0时算法应表现为干声直通(若direct_sound_enable = 1);delay接近max_ms时工作区占用接近满负荷,需确认need_buf分配足够,否则发生延时线越界。
并发与中断上下文
sound_out_obj.enable声明为volatile u32,因为状态位由中断/任务异步修改(如解码中断置B_DEC_KICK、任务置B_DEC_PAUSE)。所有状态读取应使用位测试宏(如if (snd->enable & B_DEC_PAUSE)),避免缓存陈旧值。run为纯数据计算,不应对enable做非原子读改写;状态切换统一由外部单写者完成,保证无锁设计下的一致性。- 参数热更新(
init)与run若在不同优先级上下文执行,可能出现"读半边参数"窗口。实现上通过整体memcpy参数块(见 echo_api.c)减少不一致窗口,但仍建议在音频任务空闲点(如帧边界)调用init。
缓冲不足与阻塞
sound_input/sound_output依赖环形缓冲(sound_out_init绑定的cbuf)。缓冲写满/读空时,kick_sound配合B_DEC_KICK位唤醒等待任务;若迟迟未唤醒,B_DEC_ERR置位表示错误路径。系统设计时应按最大音效链延迟(max_ms+ 各级缓冲)规划缓冲深度。
裁剪与链接失败
- 未编译入固件的音效,
get_echo_func_api()返回 NULL。调用方必须判空,否则空指针解引用。这也是"算法可裁剪"特性的使用前提:产品 ROM 空间不足时剔除不需要的音效,框架层不受影响。
Performance & Operational Considerations
- 热路径唯一:
run(ptr, inbuf, len)是每帧执行的唯一热路径,内部应避免动态内存分配、日志打印与浮点运算(除非目标芯片支持硬件 FPU 且PCMDataType为浮点)。need_buf/open/init均为低频路径。 - 定点精度:
Qval决定定点运算缩放(16bit→Q15,24bit→Q23)。算法内部乘法后需按Qval移位,溢出保护(饱和钳位)应放在run出口,避免削波失真。 - 工作区预分配:所有音效遵循"
need_buf先行"约定,工作区可在启动时静态分配或从专用内存池一次性申请,避免运行期碎片化;多级音效链的工作区总大小 = 各级need_buf之和,可在编译期评估内存预算。 - 调试手段:
echo_parm_debug这类参数打印函数在实现层提供,产品阶段可保留用于现场核对实际生效参数(采样率、延时、增益),便于与规格书对照。 - 多音效串联:
sound_out_obj.effect指向下一级音效,输出回调ECHO_IO_CONTEXT.output驱动流水线。串联时需注意各级max_ms/缓冲叠加导致的端到端延迟,KTV/实时监听场景应控制总延迟在可接受范围(通常 < 数十毫秒)。
Extension Points
音效算法库的扩展遵循"新增目录 + 实现接口表 + 框架挂接"三步:
- 新增算法实现:在
sdk/app/bsp/common/sound_effect_list/下新建子目录(如my_effect/),实现与ECHO_FUNC_API同构的接口表(need_buf/open/init/run/reset_wetdry),并导出get_my_effect_func_api()。 - 暴露头文件:在
sdk/include_lib/audio/增加参数结构体与接口表声明,保持与现有reverb_api.h一致的三段式风格(动态参数 / 固定参数 / 能力表)。 - 框架挂接:将新音效封装为
EFFECT_OBJ(填充p_si、run、sound),置位B_DEC_ENABLE | B_DEC_EFFECT后挂入解码/录音通路;若需支持新的数据格式(如 32bit 浮点),扩展PCMDataType枚举并确保af_DataType描述与算法内部转换一致。
该设计使音效库成为面向接口的可插拔算法集合:框架、装配层、算法实现三者可独立演进,新增音效不需要改动解码器主流程。
Tests
源仓库中音效库的测试主要体现在装配层自检与调试打印模式:echo_api.c 在 open 流程中通过 need_buf 计算并 log_info 打印工作区大小,随后 echo_parm_debug 打印实际生效参数。这种"先算后建、边建边查"的方式在无宿主环境的 MCU 上承担了单元自检角色:
- 工作区大小打印可人工核对内存预算是否合理;
- 参数打印可验证
memcpy后句柄内数据与调用方配置一致; af_DATABIT_NOTSUPPORT错误码可在集成测试中验证格式适配路径。
若需系统级测试,建议在 PC 端以相同 af_DataType 描述回放固定测试向量,对比 run 输出与参考 DSP 实现(如浮点参考模型)的误差,重点覆盖 Qval 定点精度与溢出饱和行为。
Related Links
- AudioEffect_DataType.h(音效数据类型定义)
- reverb_api.h(回声/混响算法接口)
- sound_effect_api.h(音效对象模型与解码器衔接)
- echo_api.c(回声音效装配实现)
- 相邻主题:EQ 均衡(
pcm_eq_api.h)、啸叫抑制/变调(howling_pitchshifter_api.h、notch_howling_api.h)、能量检测(energe_api.h)、采样率转换(resample_api.h/src.h)——详见音频相关目录页 - 相邻页面:解码器主流程(见"解码器"页)、DAC/ADC 模拟通路(见"音频模拟通路"页)