音频编码器框架
AD23N MCU SDK 中负责将 ADC 采集的 PCM 音频数据编码为 IMA ADPCM / MP3 等压缩格式的统一框架,涵盖物理层采集、编码器抽象、通道注册、中断驱动与文件同步的完整录音链路。
Purpose and Scope
本页文档介绍 AD23N SDK 的音频编码器框架(路径 sdk/app/bsp/common/encoder/ 与 sdk/include_lib/encoder/),包括:
- 编码器统一对象
enc_obj与抽象接口ENC_OPS/EN_FILE_IO/ENC_DATA_INFO - 录音的完整生命周期:
encoder_io初始化 → 软件中断驱动的编码循环 →stop_encode排空与收尾 - ADC 物理层采集(
rec_phy_init/rec_phy_suspend)与 ANS 降噪可选项 - 编码器实现列表:IMA ADPCM(
a_encoder.c)、MP3(mp3_encoder.c)、uMP3(ump3_encoder.c) - 编码通道注册机制与软件中断(
IRQ_SOFT1/IRQ_SOFT2)调度 - 如何扩展一个新的编码器实现
不包含:音频解码器框架(对应独立的解码器页面)、具体录音文件格式(FATFS/NORFS 存储层见录音/存储相关页面)、音频 ADC 驱动的底层寄存器细节。这些属于兄弟页面主题,本页仅在数据流中引用。
Overview
在 MCU 录音场景中,编码器需要与硬件采集(ADC)、DMA 缓冲(cbuffer)、文件系统写入(vfs)以及中断调度紧密配合。AD23N 的编码器框架将这一复杂链路抽象为三层:
- 物理层(PHY):
encoder_api.c管理 ADC 采集通道与环形缓冲cbuf_adc,通过sound_out_obj与audio_adc驱动对接,可选串联 ANS(环境降噪)效果器。 - 管理层(Manager):
encoder_mge.h定义统一的enc_obj句柄与通道注册 API,负责把某个具体编码器实例挂接到全局编码通道链表,并用两个软件中断(kick_encode_isr=IRQ_SOFT1、kick_wfile_isr=IRQ_SOFT2)驱动"编码"与"写文件"两个异步处理循环。 - 实现层(Codec List):
list/目录下每个编码器(A/MP3/uMP3)导出一个u32 encoder_api_X(void *pfile)工厂函数,返回填充完毕的enc_obj。编码核心算法通过ENC_OPS操作集(need_buf/open/set_info/init/process/get_time)与EN_FILE_IOIO 回调(enc_input/enc_output)解耦,使算法库(如 IMA 编解码库)无需关心 MCU 的缓冲与文件细节。
设计意图在于:把"采集-编码-写盘"这一高频实时链路从应用逻辑中彻底解耦。应用层只需调用 encoder_io(encoder_api_X, pfile) 一行即可启动录音,之后由中断回调 kick_encode_api 自动推进数据流;停止时 stop_encode 负责排空缓冲、释放资源并同步文件。这种以软件中断为"心跳"的设计避免了主循环轮询,适合低功耗、单核中断驱动的嵌入式场景。
Architecture
flowchart TD
subgraph sg_App["应用层 App"]
App["录音应用 / enc_in_fatfs / enc_in_norfs"]
end
subgraph sg_Manager["管理层 encoder_mge.h"]
EncIO["encoder_io(fun, pfile)"]
StopEnc["stop_encode(obj, pfile, dlen)"]
RegCh["regist_encode_channel / unregist_encode_channel"]
EncObj["enc_obj 句柄"]
end
subgraph sg_Codec["编码器实现列表 list/"]
AEnc["a_encode_api (IMA ADPCM)"]
MP3Enc["mp3_encode_api (MP3)"]
UMP3Enc["ump3_encode_api (uMP3)"]
Ops["ENC_OPS 操作集"]
FileIO["EN_FILE_IO (enc_input / enc_output)"]
end
subgraph sg_Phy["物理层 encoder_api.c"]
RecPhy["rec_phy_init / rec_phy_suspend"]
CbufADC["cbuf_adc 环形缓冲"]
ADC["audio_adc 通道"]
ANS["ANS 降噪 (可选)"]
end
subgraph sg_ISR["中断调度"]
SW1["IRQ_SOFT1 kick_encode_isr"]
SW2["IRQ_SOFT2 kick_wfile_isr"]
end
subgraph sg_Store["存储"]
VFS["vfs / FS_IOCTL_FILE_SYNC"]
File["录音文件"]
end
App -->|"encoder_io(编码器工厂, pfile)"| EncIO
App -->|"stop_encode"| StopEnc
EncIO --> RegCh
RegCh --> EncObj
EncObj --> AEnc
EncObj --> MP3Enc
EncObj --> UMP3Enc
AEnc --> Ops
MP3Enc --> Ops
UMP3Enc --> Ops
Ops --> FileIO
EncIO --> RecPhy
RecPhy --> CbufADC
CbufADC --> ADC
CbufADC --> ANS
ANS --> ADC
SW1 -->|"编码处理"| Ops
SW2 -->|"写文件"| FileIO
FileIO --> VFS
VFS --> File
架构说明:应用层只与 encoder_io / stop_encode 两个入口交互;encoder_io 先初始化物理层(rec_phy_init),再调用传入的编码器工厂函数获得 enc_obj,随后 regist_encode_channel 将实例挂入通道链表并启动 ADC。运行期,ADC 中断把 PCM 数据灌入 cbuf_adc,IRQ_SOFT1 驱动 ENC_OPS->process 从 p_ibuf 取数编码,IRQ_SOFT2 驱动 EN_FILE_IO->enc_output 把压缩数据写入文件。停止时 stop_encode 依次排空输入/输出缓冲、注销通道、卸载软中断、释放编码资源并同步文件。
编码器实现层通过 ENC_OPS(算法操作)与 EN_FILE_IO(IO 回调)双接口隔离:算法库只操作 ptr 指向的工作缓冲,数据进出通过 enc_input / enc_output 回调完成,从而可以无侵入地接入任何编解码算法。
核心数据结构
enc_obj — 编码器统一句柄
所有编码器实例共用同一对象结构,由管理层定义:
typedef struct _enc_obj {
void *p_file;
void *p_ibuf;
void *p_obuf;
void *enc_ops;
void *p_dbuf;
ENC_DATA_INFO info;
void *penc_res; //编码资源指针,用于一次性申请内存
u32(*encoder_res_release)(void *priv);
volatile u32 enable;
} enc_obj;
Source: encoder_mge.h
各字段职责:
| 字段 | 含义 |
|---|---|
p_file | 目标文件句柄(由调用方传入,经 EN_FILE_IO 间接使用) |
p_ibuf | 输入缓冲,指向 ADC 数据环形缓冲 REC_ADC_CBUF(即 enc_in_sound->p_obuf) |
p_obuf | 输出缓冲,编码器内部私有环形缓冲(如 A 编码器的 cbuf_ima_o) |
enc_ops | 指向 ENC_OPS 算法操作集 |
p_dbuf | 算法工作缓冲(计算缓冲,传给 ops->open 等) |
info | 编码参数(采样率/码率/声道数) |
penc_res | 一次性申请的资源块指针(由 D_MALLOC 分配) |
encoder_res_release | 资源释放回调,停止时统一调用 |
enable | 状态位,见下 |
状态位定义(enable 字段):
#define B_ENC_ENABLE BIT(0)
#define B_ENC_STOP BIT(1)
#define B_ENC_FULL BIT(2)
Source: encoder_mge.h
B_ENC_ENABLE 表示编码通道运行中;B_ENC_STOP 是停止请求标志(stop_encode 置位);B_ENC_FULL 表示输出缓冲已满或写盘受阻,用于让排空循环提前退出,防止死等。
ENC_OPS — 算法操作集抽象
算法层通过函数指针表暴露统一接口,定义在 audio_enc_api.h:
typedef struct _ENC_OPS {
u32(*need_buf)();
void(*open)(u8 *ptr, EN_FILE_IO *audioIO);
void(*set_info)(u8 *ptr, ENC_DATA_INFO *data_info);
u32(*init)(u8 *ptr);
...
u32(*get_time)(void *ptr);
} ENC_OPS;
Source: audio_enc_api.h
ENC_DATA_INFO 承载编码参数:
typedef struct _ENC_DATA_INFO_ {
u16 sr; ///<sample rate
...
u32 nch;
} ENC_DATA_INFO;
Source: audio_enc_api.h
sr 为采样率、nch 为声道数(另有码率字段,A 编码器示例中通过 info.br 设置)。工厂函数 get_ima_code_ops()、get_ump2_ops()、get_mp2_ops() 分别返回各算法的操作集。
EN_FILE_IO — IO 回调接口
编码算法不直接触碰文件与缓冲,而是通过回调读写:
u16 enc_input(void *priv, s16 *buf, u8 channel, u16 len);
u32 enc_output(void *priv, u8 *data, u16 len);
Source: encoder_mge.h
enc_input 从输入缓冲(ADC 环形缓冲)读取 PCM 样本;enc_output 将压缩后的数据写入输出缓冲或文件。A 编码器用 EN_FILE_IO 结构把它们包装起来传给算法库:
const EN_FILE_IO a_enc_io = {
&enc_a_hdl, //input跟output函数的第一个参数,解码器不做处理,直接回传,可以为NULL
enc_input,
enc_output,
};
Source: a_encoder.c
编码器生命周期
启动:encoder_io
所有录音的入口统一为 encoder_io(fun, pfile)——传入编码器工厂函数与目标文件,返回 enc_obj * 句柄:
enc_obj *encoder_io(u32(*fun)(void *), void *pfile)
{
s32 err;
rec_phy_init();
enc_obj *enc_hdl = (void *)fun(pfile);
if (0 != enc_hdl) {
err = regist_encode_channel(enc_hdl);
if (err) {
log_error("encode_io_err 0x%x\n", err);
return NULL;
}
enc_phy_init();
enc_hdl->enable = B_ENC_ENABLE;
start_encode();//adc_enable();
log_info("encode succ: \n");
} else {
log_info("encode fail \n");
}
return enc_hdl;
}
Source: encoder_api.c
启动顺序的设计意图:先物理后逻辑,先注册后使能。
rec_phy_init():初始化 ADC 侧环形缓冲cbuf_adc、注册 ADC 采集通道;若编译宏ANS_EN开启,则把 ANS 降噪效果器链接进数据链(link_ans_sound),此时中断回调为kick_encode_api(经stream_sound_init注册)。fun(pfile):调用具体编码器工厂(如a_encode_api),完成资源申请、参数填充、ops->open/set_info/init,返回就绪的enc_obj。regist_encode_channel(enc_hdl):将句柄挂入全局编码通道链表(失败则返回 NULL)。enc_phy_init():初始化编码中断相关的物理资源(软中断安装等)。enable = B_ENC_ENABLE后调用start_encode():置位B_DEC_RUN_EN | B_REC_RUN并audio_adc_enable(MIC_PGA_G),开始采集。
运行:双软件中断驱动
ADC 数据到达后,中断回调 kick_encode_api 通过 bit_set_swi(1) 触发 IRQ_SOFT1 编码处理;编码输出累积后由 bit_set_swi(2) 触发 IRQ_SOFT2 写文件:
#define kick_encode_isr() bit_set_swi(1)
#define kick_wfile_isr() bit_set_swi(2)
Source: encoder_mge.h
AT(.audio_a.text.cache.L2)
void kick_encode_api(void *obj)
{
kick_encode_isr();
}
Source: encoder_api.c
kick_encode_api 被放置在 L2 cache 文本段(AT(.audio_a.text.cache.L2)),属于音频实时路径的缓存优化。软中断机制让编码/写盘工作在中断上下文中完成,主循环不被音频数据流阻塞。
停止:stop_encode
停止流程承担"优雅收尾"职责——先停 ADC,再排空所有缓冲,最后释放资源并同步文件:
bool stop_encode(enc_obj *obj, void *pfile, u32 dlen)
{
enc_obj *enc_hdl = obj;
u32 err;
audio_adc_disable();
STOP_ADC_RUN;
log_info("stop encode\n");
if (NULL == enc_hdl) {
rec_phy_suspend();
return false;
}
obj->enable |= B_ENC_STOP;
log_info("stop encode A\n");
while (0 != cbuf_get_data_size(obj->p_ibuf)) {
if (obj->enable & B_ENC_FULL) {
break;
}
kick_encode_isr();
delay(100);
}
log_info("stop encode C\n");
while (0 != cbuf_get_data_size(obj->p_obuf)) {
if (obj->enable & B_ENC_FULL) {
break;
}
kick_wfile_isr();
delay(100);
}
log_info("stop encode D\n");
obj->enable &= ~B_ENC_ENABLE;
err = unregist_encode_channel(enc_hdl);
if (err) {
log_error("unregist_encode_channel 0x%x\n", err);
}
if (is_encode_channel_empty()) {
HWI_Uninstall(IRQ_SOFT1_IDX);
HWI_Uninstall(IRQ_SOFT2_IDX);
}
if (NULL != enc_hdl->encoder_res_release) {
enc_hdl->encoder_res_release(enc_hdl);
enc_hdl->encoder_res_release = NULL;
}
u32 flen = dlen;
err = vfs_ioctl(pfile, FS_IOCTL_FILE_SYNC, (int)&flen);
rec_phy_suspend();
return true;
}
Source: encoder_api.c
停止时序的设计要点:
- 先停采集:
audio_adc_disable()+STOP_ADC_RUN,避免新数据涌入。 - 两段式排空:先排空输入缓冲
p_ibuf(反复kick_encode_isr()触发编码),再排空输出缓冲p_obuf(反复kick_wfile_isr()触发写盘)。每轮delay(100)让出 CPU,防止看门狗复位;B_ENC_FULL作为逃生条件,避免写盘阻塞导致死循环。 - 条件卸载中断:仅当
is_encode_channel_empty()为真(没有其他编码通道在跑)才卸载IRQ_SOFT1/IRQ_SOFT2,支持多通道共存。 - 统一释放:通过
encoder_res_release回调释放一次性申请的编码资源,保证 A/MP3/uMP3 各自的内存管理逻辑收敛在各自实现中。 - 落盘同步:
vfs_ioctl(pfile, FS_IOCTL_FILE_SYNC, &flen)把dlen长度数据刷入存储介质,确保录音文件完整可读。
核心数据流
录音期间,PCM 数据从硬件到文件的完整路径如下:
sequenceDiagram
participant ADC as audio_adc 通道
participant CBUF as cbuf_adc 环形缓冲
participant SW1 as IRQ_SOFT1 编码中断
participant OPS as ENC_OPS 算法
participant OBUF as p_obuf 输出缓冲
participant SW2 as IRQ_SOFT2 写文件中断
participant VFS as vfs 文件层
ADC->>CBUF: DMA 写入 PCM 样本
CBUF->>SW1: kick_encode_api -> bit_set_swi(1)
SW1->>OPS: ops->process(p_dbuf)
OPS->>CBUF: enc_input 读取样本 (s16*)
OPS->>OPS: IMA/MP3 压缩编码
OPS->>OBUF: enc_output 写入压缩数据
OBUF->>SW2: kick_wfile_isr -> bit_set_swi(2)
SW2->>VFS: 读取输出缓冲并写文件
VFS->>OBUF: 消费完成,释放空间
Note over OBUF,SW2: stop_encode 时循环 kick 直至排空,<br/>随后 FS_IOCTL_FILE_SYNC 落盘
关键设计点:
- 输入缓冲复用:
p_ibuf直接指向 ADC 侧REC_ADC_CBUF(enc_in_sound->p_obuf),PCM 数据零拷贝进入编码器,见 a_encoder.c。 - 输出缓冲私有:每个编码器维护自己的环形缓冲(A 编码器为
cbuf_ima_o,1024 字节),算法产生的数据先入缓冲,再由写盘中断批量写入文件,避免频繁文件 IO。 - 双中断解耦:编码速度与写盘速度天然不同步,两个软件中断各自驱动一段流水,中间以环形缓冲吸收速率差——这正是"生产者-消费者"模型在中断级上的实现。
编码器实现列表
A 编码器(IMA ADPCM)— a_encoder.c
A 编码器是框架中最典型的实现,完整展示了"资源申请 → ops 初始化 → 句柄填充"的模板,所有编码器实现均遵循此模式:
u32 a_encode_api(void *p_file)
{
u32 buff_len;
ENC_OPS *ops;
if (enc_a_hdl.penc_res != NULL) {
log_error("enc_res_not_null 0x%x\n", enc_a_hdl.penc_res);
a_encoder_release(&enc_a_hdl);
}
D_MALLOC(enc_a_hdl.penc_res, D_THIS_ENC_RESOURCE);
if (NULL == enc_a_hdl.penc_res) {
log_error("a_enc_cann't_malloc_any_resource\n");
return E_A_ENC_RESOURCE;
}
a_enc_data *p_a_data = enc_a_hdl.penc_res;
ops = get_ima_code_ops();
buff_len = ops->need_buf();
if (buff_len > A_ENC_BUF_SIZE) {
log_error("buff_len no enough, need %d\n", buff_len);
a_encoder_release(&enc_a_hdl);
return 0;
}
cbuf_init(&p_a_data->cbuf_ima_o, &p_a_data->obuf_ima_o[0], A_ENC_OBUF_SIZE);
enc_a_hdl.p_file = p_file;
enc_a_hdl.p_ibuf = REC_ADC_CBUF; //adc_hdl.p_adc_cbuf;//&cbuf_ima_i;
enc_a_hdl.p_obuf = &p_a_data->cbuf_ima_o;
enc_a_hdl.p_dbuf = A_ENC_CAL_BUF;
enc_a_hdl.enc_ops = ops;
enc_a_hdl.info.sr = read_audio_adc_sr();
enc_a_hdl.info.br = 256;
enc_a_hdl.info.nch = 1;
enc_a_hdl.encoder_res_release = a_encoder_release;
ops->open(A_ENC_CAL_BUF, (void *)&a_enc_io); //传入io接口
ops->set_info(A_ENC_CAL_BUF, &enc_a_hdl.info);
ops->init(A_ENC_CAL_BUF);
return (u32)&enc_a_hdl;
}
Source: a_encoder.c
实现要点:
- 全局句柄:
enc_obj enc_a_hdl为静态全局变量(a_encoder.c L26),每次启动通过D_MALLOC一次性申请a_enc_data资源块(含输出环形缓冲 + 算法工作缓冲),复用句柄而只换资源。 - 资源预算:
A_ENC_BUF_SIZE = 368字节为算法工作缓冲上限,need_buf()返回值超限即报错释放——防止算法库内部越界。 - 参数注入:采样率取自
read_audio_adc_sr()(跟随 ADC 实际配置),码率br = 256(kbps),单声道nch = 1。 - 初始化序列:
open(传入 IO 回调)→set_info(编码参数)→init(就绪),此后中断通过ops->process驱动。 - 资源释放:
a_encoder_release用D_FERR(obj->penc_res)归还内存,由stop_encode统一回调。
MP3 / uMP3 编码器
mp3_encoder.c 与 ump3_encoder.c 遵循完全相同的模板(ENC_OPS + EN_FILE_IO + enc_obj),区别在于:
- 工厂函数通过
get_mp2_ops()/get_ump2_ops()获取各自算法操作集(见 audio_enc_api.h L46-L48)。 - 资源块尺寸、输出缓冲大小与编码参数(采样率/码率/声道)按 MP3 规范配置。
- 通过编译宏(如
ENCODER_A_EN、ENCODER_MP3_EN)裁剪,未使能的编码器代码完全不参与编译(a_encoder.c L9)。
编码通道注册与中断管理
encoder_mge.h 声明的三个管理函数构成多编码器共存的基础:
u32 regist_encode_channel(enc_obj *p_enc_obj);
u32 unregist_encode_channel(enc_obj *p_enc_obj);
bool is_encode_channel_empty(void);
Source: encoder_mge.h
设计意图:
regist_encode_channel把句柄加入全局通道链表(内部实现于 SDK 库),unregist_encode_channel在停止时移除;is_encode_channel_empty判定是否还有活跃通道。- 软中断
IRQ_SOFT1(编码)与IRQ_SOFT2(写文件)只在首个通道注册时安装、最后一个通道注销时卸载(stop_encode中的HWI_Uninstall逻辑),因此中断资源按需启停,空闲时不占用中断向量。 - 多通道场景下,中断服务程序遍历通道链表依次服务,各通道的输出缓冲隔离,互不干扰。
配置选项
编码器框架的行为由编译期宏与常量控制,均可在 SDK 配置中调整:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ENCODER_A_EN | 编译宏 | 0/1 | 使能 IMA ADPCM(A)编码器,见 a_encoder.c L9 |
ENCODER_MP3_EN | 编译宏 | 0/1 | 使能 MP3 编码器(mp3_encoder.c) |
ENCODER_UMP3_EN | 编译宏 | 0/1 | 使能 uMP3 编码器(ump3_encoder.c) |
ANS_EN | 编译宏 | 0/1 | 使能 ANS 环境降噪链路(link_ans_sound),见 encoder_api.c L20-L25 |
A_ENC_OBUF_SIZE | 常量 | 1024 | A 编码器输出环形缓冲字节数,见 a_encoder.c L15 |
A_ENC_BUF_SIZE | 常量 | 368 | A 编码器算法工作缓冲上限(须 ≥ need_buf()),见 a_encoder.c L16 |
adc_buff[512 * 10] | 数组 | 5120 B | ADC 采集环形缓冲(AT(.rec_data) 段),见 encoder_api.c L27-L28 |
MIC_PGA_G | 常量 | 外部定义 | ADC 输入增益,start_encode 时传入 audio_adc_enable,见 encoder_api.c L80-L85 |
A_ENC_CAL_BUF | 宏 | &p_a_data->a_encode_buff[0] | 算法计算缓冲地址(p_dbuf) |
info.br = 256 | 编码参数 | 256 | A 编码器码率(kbps),见 a_encoder.c L84 |
缓冲大小与采样率/码率需要匹配:cbuf_adc 过小会导致中断频繁触发;A_ENC_OBUF_SIZE 过小会频繁触发 B_ENC_FULL 并拖慢写盘。
API 参考
enc_obj *encoder_io(u32 (*fun)(void *), void *pfile)
录音启动入口。初始化 ADC 物理层,调用编码器工厂 fun 创建 enc_obj,注册通道并使能采集。
参数:
fun:编码器工厂函数指针,如a_encode_api/mp3_encode_api/ump3_encode_api,返回(u32)enc_obj*或错误码pfile:目标录音文件句柄(vfs 文件)
返回: 就绪的 enc_obj *;工厂失败或 regist_encode_channel 失败时返回 NULL
说明: 工厂返回错误码(如 E_A_ENC_RESOURCE)时会被当作非零句柄,需注意工厂在资源失败路径返回 0(见 a_encoder.c L61-L64)。
bool stop_encode(enc_obj *obj, void *pfile, u32 dlen)
录音停止入口。停 ADC → 排空输入/输出缓冲 → 注销通道 → 条件卸载软中断 → 释放编码资源 → 同步文件。
参数:
obj:encoder_io返回的编码句柄;传NULL时仅做rec_phy_suspend并返回falsepfile:目标文件句柄(用于FS_IOCTL_FILE_SYNC)dlen:需要落盘的字节长度
返回: 成功为 true;句柄为 NULL 时为 false
注意: 该函数是阻塞式的,内部排空循环每轮 delay(100);B_ENC_FULL 置位时提前退出排空。
u16 enc_input(void *priv, s16 *buf, u8 channel, u16 len) / u32 enc_output(void *priv, u8 *data, u16 len)
编码器 IO 回调(经 EN_FILE_IO 注入算法库)。enc_input 从输入环形缓冲取 PCM 样本,enc_output 将压缩数据写入输出环形缓冲/文件。priv 为回传的 enc_obj*(A 编码器中即 &enc_a_hdl)。
u32 regist_encode_channel(enc_obj *p_enc_obj) / u32 unregist_encode_channel(enc_obj *p_enc_obj) / bool is_encode_channel_empty(void)
通道链表管理。注册/注销编码通道,查询是否还有活跃通道(决定软中断是否卸载)。regist 失败返回非零错误码(encoder_io 中打印 encode_io_err 0x%x)。
中断 kick 宏
| 宏 | 触发 | 用途 |
|---|---|---|
kick_encode_isr() | bit_set_swi(1) → IRQ_SOFT1 | 驱动编码处理(ops->process) |
kick_wfile_isr() | bit_set_swi(2) → IRQ_SOFT2 | 驱动写文件(消费 p_obuf) |
Source: encoder_mge.h
ENC_OPS 操作集
| 方法 | 签名 | 职责 |
|---|---|---|
need_buf | u32 (*)(void) | 返回算法所需工作缓冲字节数,用于资源预算校验 |
open | void (*)(u8 *ptr, EN_FILE_IO *audioIO) | 传入工作缓冲与 IO 回调 |
set_info | void (*)(u8 *ptr, ENC_DATA_INFO *data_info) | 设置采样率/码率/声道数 |
init | u32 (*)(u8 *ptr) | 初始化编码状态机 |
process(隐含) | — | 中断中执行编码(框架经 enc_ops 调用) |
get_time | u32 (*)(void *ptr) | 获取已编码时长等统计信息 |
Source: audio_enc_api.h
失败模式、边界情况与并发
资源不足
D_MALLOC失败(penc_res == NULL):工厂返回E_A_ENC_RESOURCE并打日志a_enc_cann't_malloc_any_resource,encoder_io记录encode fail,录音不启动。need_buf()超过A_ENC_BUF_SIZE:工厂报buff_len no enough并释放资源,防止算法越界写坏内存。- 重复启动:
penc_res != NULL时先自动a_encoder_release再重新分配,避免资源泄漏。
写盘阻塞与 B_ENC_FULL
若存储介质写速慢于编码速率,输出缓冲会写满。此时:
- 编码侧感知
B_ENC_FULL,stop_encode的排空循环据此break,避免死等。 - 运行期依赖
kick_wfile_isr持续消费;若长时间无法消化,看门狗可能复位——SDK 保留了wfil_soft2_isr_hook钩子(encoder_api.c L161-L174,当前#if 0未使能)用于在写盘前喂狗,但注释明确警告"写前喂狗可能使系统无法回到主循环"。
并发与中断安全
- 编码与写盘均运行在软件中断上下文,天然互斥;主循环不直接触碰
cbuf_adc/p_obuf,避免竞争。 stop_encode在主循环(低优先级)执行,通过delay(100)与中断轮转;它先关 ADC 再排空,保证不会有新数据混入收尾阶段。- 多通道场景:每个
enc_obj拥有独立p_ibuf/p_obuf/penc_res,共享软中断与通道链表;最后一个通道退出时统一卸载中断。
边界情况
stop_encode(NULL, ...):仅挂起物理层并返回false,用于无句柄时的清理路径。- FPGA 平台差异:
FPGA宏下使用独立的adc_obj模拟(encoder_mge.h中#if FPGA分支),无真实 ADC 通道;实际芯片走sound_out_obj链路。 info.sr跟随read_audio_adc_sr():若 ADC 配置在录音中变化,编码参数不会自动跟随,需在启动前固定采样率。
性能与操作考虑
- L2 Cache 优化:
kick_encode_api与相关热路径被放置于AT(.audio_a.text.cache.L2)段(encoder_api.c L70),编码中断回调避免从慢速 Flash 取指,这是对音频实时性关键路径的显式优化。 - 缓冲内存段:
cbuf_adc/adc_buff位于AT(.rec_data)段、ANS 缓冲位于AT(.ans_data)段,确保 DMA 可达且不与普通堆数据争用 cache。 - 阻塞式停止:
stop_encode在慢速存储下可能耗时较长(排空循环每轮delay(100)),调用方应避免在关键时序(如低电检测)路径上同步调用,或提前触发。 - 吞吐调优:增大
A_ENC_OBUF_SIZE可降低IRQ_SOFT2触发频率、提升写盘批大小;增大adc_buff可吸收 ADC 中断抖动。两者都受 RAM 预算约束(AT(.rec_data)段大小)。 - 看门狗:极端写盘阻塞下依赖看门狗复位兜底;如启用
wfil_soft2_isr_hook需评估"写前喂狗导致主循环饿死"的风险。
扩展点:新增编码器实现
框架为新增编码格式提供了清晰的接入模板,按以下步骤即可挂接新算法:
- 实现
ENC_OPS:提供need_buf/open/set_info/init/process/get_time,算法只面向u8 *ptr工作缓冲与EN_FILE_IO回调编程,不感知 MCU 细节。 - 编写工厂函数:仿照
a_encode_api(a_encoder.c L51-L96)——D_MALLOC一次性资源、cbuf_init输出缓冲、填充enc_obj各字段(p_file/p_ibuf/p_obuf/p_dbuf/enc_ops/info/encoder_res_release)、执行open → set_info → init。 - 提供释放回调:
encoder_res_release归还penc_res,供stop_encode统一调用。 - 受控编译:用
ENCODER_XXX_EN宏包住整个实现(参考 a_encoder.c L9),未使能时零代码体积。 - 接入应用:调用方将新工厂函数传入
encoder_io(fun, pfile)即可,管理层与物理层无需任何改动。
上层录音应用(如 sdk/app/src/mbox_flash/record/enc_in_fatfs.c、enc_in_norfs.c)正是通过选择不同的工厂函数切换编码格式,体现了策略模式的扩展性。
Related Links
- 编码器管理层头文件 encoder_mge.h —
enc_obj结构、通道注册 API、kick 宏 - 编码器抽象接口 audio_enc_api.h —
ENC_OPS/ENC_DATA_INFO/ 算法工厂声明 - 编码器物理层实现 encoder_api.c —
encoder_io/stop_encode/ ADC 与 ANS 链路 - A 编码器实现 a_encoder.c — IMA ADPCM 编码器模板
- MP3 编码器 mp3_encoder.c / uMP3 编码器 ump3_encoder.c
- 录音存储层 enc_in_fatfs.c — 编码器在 FATFS 录音中的应用示例
- 音频解码器框架(兄弟页面,独立文档)
- 录音与文件存储(FATFS/NORFS)相关页面