音频编码与录音
本文档深入解析 AD16N SDK 中音频编码与录音子系统的完整实现:从 ADC 采集 PCM 数据、软件中断调度、编码器抽象层(ENC_OPS),到 A/IMA、MP3、UMP3 等具体编码器的初始化流程,以及录音数据向 FATFS/NORFS 文件系统的写入路径。
Purpose and Scope
本页覆盖音频编码与录音的完整端到端链路:
- 编码器统一管理核心(
encoder_api.c):物理层初始化、启停控制、中断调度; - 编码器抽象与句柄模型(
encoder_mge.h中的enc_obj、ENC_OPS、EN_FILE_IO); - 具体编码器实现(
list/目录下的 A 格式、MP3、UMP3); - 录音文件的写入侧(
encoder_file.c与enc_in_fatfs.c/enc_in_norfs.c)。
以下相关主题由同目录其他页面负责,不在本页展开:ADC 采样通道与 audio_adc 驱动细节(见"音频采集"页)、音频回放/解码链路(见"音频解码"页)、文件系统本身(FATFS/NORFS)的 API。
Overview
录音本质是一条"采集 → 编码 → 落盘"的流水线。AD16N 是单核 MCU,因此这条流水线被设计为中断驱动 + 软件中断(SWI)协作模型:
audio_adc硬件外设在中断中把 PCM 采样写入cbuf_adc环形缓冲;- 采样数据通过
regist_audio_adc_channel注册的回调kick_encode_api触发软件中断kick_encode_isr(); - 软件中断里调用
enc_input()从环形缓冲取数据,送入编码器(IMA/MP3/UMP3)压缩; - 编码后的数据经
output_data回调写入文件系统(FATFS 或 NORFS)。
之所以采用"先入环形缓冲、再由软件中断搬运"的两级结构,是因为 NORFS 擦写耗时最长可达约 80 ms(源码注释明确说明),若编码器直接阻塞在外设等待上,ADC 采样会丢失;环形缓冲保证了写 Flash 期间采集不中断。同理,所有缓冲都被放置在专用的内存段(.rec_data、.enc_a_data)中,以规避缓存一致性问题并满足 IRAM 时序要求。
编码器通过统一的 ENC_OPS 操作集抽象(need_buf / open / set_info / init 等),新增编码格式只需实现一组 ops 并在 list/ 目录下注册,无需改动调度框架——这是本子系统最主要的扩展点。
Architecture
flowchart TD
subgraph sg_ADC["音频采集层 (audio_adc)"]
ADC["audio_adc 硬件采样"]
CBUF_ADC["cbuf_adc 环形缓冲<br/>7680B @ .rec_data"]
end
subgraph sg_MGR["编码器管理层 (encoder_api.c)"]
REC_SOUND["rec_sound / sound_out_obj"]
KICK["kick_encode_api → kick_encode_isr<br/>(bit_set_swi(1))"]
ENC_IO["encoder_io() 通用入口"]
end
subgraph sg_CODEC["编码器实现层 (list/)"]
A_ENC["a_encode_api<br/>A/IMA ADPCM"]
MP3_ENC["mp3_encode_api<br/>MP3"]
UMP3_ENC["ump3_encode_api<br/>UMP3"]
end
subgraph sg_OUT["输出与落盘层"]
WFILE["encoder_file<br/>wfil 写文件"]
FATFS["enc_in_fatfs"]
NORFS["enc_in_norfs"]
end
ADC -->|"PCM 中断写入"| CBUF_ADC
CBUF_ADC -->|"regist_audio_adc_channel"| REC_SOUND
REC_SOUND -->|"kick_encode_api 回调"| KICK
ENC_IO -->|"enc_input() 取数"| A_ENC
ENC_IO -->|"enc_input() 取数"| MP3_ENC
ENC_IO -->|"enc_input() 取数"| UMP3_ENC
A_ENC -->|"output_data 回调"| WFILE
MP3_ENC -->|"output_data 回调"| WFILE
UMP3_ENC -->|"output_data 回调"| WFILE
WFILE --> FATFS
WFILE --> NORFS
各层职责:
- 采集层:
audio_adc负责硬件采样,cbuf_adc(encoder_api.c#L28-L29)是采样数据的第一落点,其容量注释要求"最小缓存不小于 sr*2*80ms",即至少覆盖 NORFS 最长擦写时间的两倍采样量; - 管理层:
rec_phy_init()完成流框架初始化、缓冲初始化、ADC 通道注册与可选 ANS 降噪链路插入(encoder_api.c#L45-L71);start_encode()/stop_encode_phy()控制整条流水线的启停; - 编码器层:每种格式以
ENC_OPS *ops形式暴露,a_encode_api()等入口函数负责校验缓冲、填充enc_obj句柄并调用ops->open/set_info/init完成编码器实例化; - 落盘层:编码数据经
output_data回调进入encoder_file.c的写文件逻辑,最终落到 FATFS 或 NORFS(enc_in_fatfs.c/enc_in_norfs.c)。
核心机制:编码器句柄与操作集抽象
句柄模型 enc_obj
所有编码器共享同一个运行时句柄结构,定义于 encoder_mge.h#L15-L25:
typedef struct _enc_obj {
void *p_file; // 输出文件句柄(FATFS/NORFS)
void *p_ibuf; // 输入缓冲:ADC 采样环形缓冲(REC_ADC_CBUF)
void *p_obuf; // 输出缓冲:编码后数据环形缓冲
void *enc_ops; // ENC_OPS 操作集指针
void *p_dbuf; // 编码器工作缓冲(need_buf 申请)
ENC_DATA_INFO info; // 编码参数:采样率 sr、码率 br、声道 nch
volatile u32 enable; // 状态标志位(见下)
u32 indata_kick_size; // 输入数据触发阈值
void (*wait_output_empty)(void *); // 停止时等待输出排空的钩子
} enc_obj;
状态标志位定义在 encoder_mge.h#L10-L13:
| 标志 | 值 | 含义 |
|---|---|---|
B_ENC_ENABLE | BIT(0) | 编码通道使能 |
B_ENC_STOP | BIT(1) | 停止请求置位,进入排空流程 |
B_ENC_FULL | BIT(2) | 输出缓冲满,强制停止等待 |
B_ENC_INIT | BIT(3) | 编码器已完成初始化 |
句柄指针由 a_encode_api() 等入口函数以返回值形式交给调用方(return (u32)&enc_a_hdl;),应用层后续通过该指针调用 stop_encode_file() / stop_encode_phy() 控制生命周期。
输入/输出 IO 桥接 EN_FILE_IO
编码器不直接感知文件系统,而是通过回调函数桥接。以 A 编码器为例(a_encoder.c#L72-L78):
a_enc_io.priv = &enc_a_hdl; // 回调私有参数(编码器句柄)
a_enc_io.input_data = input_func; // 读 PCM 输入回调(通常为 enc_input)
a_enc_io.output_data = output_func; // 写编码数据回调(落盘)
enc_a_hdl.p_file = p_file; // 目标文件
enc_a_hdl.p_ibuf = REC_ADC_CBUF; // 采样环形缓冲
enc_a_hdl.p_obuf = &cbuf_ima_o; // 编码输出环形缓冲
enc_a_hdl.p_dbuf = &a_encode_buff[0]; // 编码器工作区
设计意图:input_data / output_data 由调用方注入(encoder_io() 的入参),使得同一编码器既可对接文件(FATFS/NORFS),也可对接内存或通信接口,编码器本身保持零耦合。
中断调度
kick_encode_isr() 与 kick_wfile_isr() 被实现为软件中断位操作(encoder_mge.h#L46-L47):
#define kick_encode_isr() bit_set_swi(1)
#define kick_wfile_isr() bit_set_swi(2)
bit_set_swi(1):编码软件中断,负责从p_ibuf取数并送入编码器;bit_set_swi(2):写文件软件中断,负责把编码输出写入文件系统(避免在编码中断里直接做 Flash 写操作)。
编码器管理核心:encoder_api.c 控制流
物理层初始化 rec_phy_init()
encoder_api.c#L45-L71 完成录音链路装配:
memset(&rec_sound, 0, ...)清零全局sound_out_obj;stream_frame_init(IRQ_SPEAKER_IP)初始化流框架;cbuf_init(&cbuf_adc, ...)初始化 7680 字节的 ADC 环形缓冲;- 若编译宏
ANS_EN开启,则把 ANS(自适应降噪)模块以链式sound_out_obj形式插入p_curr_sound,并对cbuf_ans初始化; - 根据是否插入 ANS 选择注册路径:有 ANS 时先
regist_stream_channel(&rec_sound, kick_encode_api)再注册 ADC 通道并把 kick 作为回调参数传入;无 ANS 时直接regist_audio_adc_channel(&rec_sound, NULL, kick_encode_api); enc_in_sound = p_curr_sound保存当前声音对象,rec_sound.enable |= B_DEC_RUN_EN | B_DEC_FIRST置运行位。
REC_ADC_CBUF 宏在非 FPGA 平台展开为 enc_in_sound->p_obuf(encoder_mge.h#L62-L64),即编码器的输入缓冲始终指向当前链路末端(可能含 ANS)的输出缓冲——这是 ANS 能"透明插入"的关键。
启动 start_encode()
void start_encode(void)
{
audio_adc_enable(); // 使能 ADC 采样
mdelay(10); // 等待采样稳定
START_ADC_RUN; // rec_sound.enable |= (B_DEC_RUN_EN | B_REC_RUN)
}
START_ADC_RUN / STOP_ADC_RUN 宏(encoder_api.c#L36-L37)通过 sound_out_obj.enable 的 B_DEC_RUN_EN | B_REC_RUN 位控制采集运行。10 ms 延时保证 ADC 上电后信号稳定再开始搬运。
停止 stop_encode_phy()
encoder_api.c#L95-L120 是停止流程的核心:
- 关闭 ADC(
audio_adc_disable())并清运行位,从源头停止数据产生; - 置
obj->enable |= B_ENC_STOP; - 若
wait == NEED_WAIT,进入有界等待循环:while (0 != cbuf_get_data_size(obj->p_ibuf) && (0 != i)),循环内反复kick_encode_isr()驱动软件中断把残留数据编码完毕;若输出满(B_ENC_FULL)则提前跳出;i = 0x10000作为超时保护,防止 Flash 异常时死循环; - 清
B_ENC_ENABLE,调用wait_output_empty(obj)等待输出缓冲排空(若存在该钩子)。
设计要点:先停 ADC、再排空输入缓冲,保证停止时既不丢数据也不会在停止后仍有新数据涌入;B_ENC_FULL 与超时计数共同构成双重保护,避免"等待排空"被慢速 Flash 写拖死。
中断回调 kick_encode_api()
AT(.audio_a.text.cache.L2)
void kick_encode_api(void *obj)
{
kick_encode_isr(); // bit_set_swi(1)
}
该函数被标注 AT(.audio_a.text.cache.L2) 放入 L2 缓存文本段,且由 ADC 中断直接调用,保证在实时性要求最高的路径上无缓存 miss。整个编码调度完全由该"采集即踢一脚"的机制驱动,无需独立的定时轮询任务。
具体编码器实现
A 格式(IMA ADPCM):a_encoder.c
A 编码器由编译宏 ENCODER_A_EN 控制(a_encoder.c#L4)。其采样率/码率严格保持 4:1 关系(源码头注释),支持表见 a_encoder.c#L30-L36:
| 采样率 (SR) | 8k | 12k | 16k | 24k | 32k |
|---|---|---|---|---|---|
| 码率 (BR) | 32kbps | 48kbps | 64kbps | 96kbps | 128kbps |
入口函数 a_encode_api()(a_encoder.c#L52-L104)的关键步骤:
ops = get_ima_code_ops()获取 IMA 编码器操作集;buff_len = ops->need_buf()查询编码器所需工作区大小,与静态数组a_encode_buff[A_EBUF_SIZE/4]比对,不足则报错返回 0;- 清零句柄与输出缓冲,初始化输出环形缓冲
cbuf_ima_o(1024 B); - 填充
EN_FILE_IO回调与enc_a_hdl(p_ibuf = REC_ADC_CBUF,p_obuf = &cbuf_ima_o,p_dbuf = &a_encode_buff[0]); enc_a_hdl.info.sr = read_audio_adc_sr()读取当前 ADC 采样率,并在a_enc_sr_tab[]中查找匹配项;找不到匹配采样率则打印错误并返回 0(拒绝在不支持采样率下编码);br = sr / 1000 * 4(kbps 单位),nch = 1强制单声道;- 依次调用
ops->open(buffer, &a_enc_io)、ops->set_info(buffer, &info)、ops->init(buffer)完成编码器实例化; - 返回
(u32)&enc_a_hdl作为编码句柄。
所有静态缓冲(cbuf_ima_o、obuf_ima_o、a_encode_buff)均声明在 AT(.enc_a_data) 专用段中,与主 RAM 隔离,便于按需加载/功耗管理。
MP3 与 UMP3 编码器
mp3_encoder.c 与 ump3_encoder.c 采用与 A 编码器完全相同的骨架:mp3_encode_api() / ump3_encode_api() 同样先 get_xxx_code_ops() 取操作集、校验 need_buf()、填充 enc_mp3_hdl / enc_ump3_hdl(p_dbuf 指向各自静态编码缓冲、info.sr = read_audio_adc_sr())、最后 open/set_info/init 三步初始化(见 mp3_encoder.c#L68-L93 与 ump3_encoder.c#L73-L96)。三种格式的差异仅在于 ops 来源(IMA / MP3 / UMP3 库),调度与句柄管理完全复用,印证了 ENC_OPS 抽象的设计目标。
录音落盘:encoder_file 与 enc_in_fatfs / enc_in_norfs
编码器产出的压缩数据经 output_data 回调进入文件写入侧:
encoder_file.c负责wfil_soft2_isr_hook()(写文件软件中断钩子)与stop_encode_file()(停止时把剩余数据刷入文件、关闭文件)等文件写逻辑;enc_in_fatfs.c实现 FATFS 上的录音写入适配;enc_in_norfs.c实现 NOR Flash 文件系统上的录音写入适配,对应adc_buff注释中"norfs 擦写时间最长不超过 80ms"的时序约束。
应用层完整流程为:encoder_io(encode_api, enc_input, output_func, pfile) 建立会话 → start_encode() 启动 → 数据经"ADC 中断 → 软件中断 → 编码器 → 写文件中断"流转 → stop_encode_file() / stop_encode_phy(NEED_WAIT) 收尾。
Core Flow:一次完整录音的生命周期
sequenceDiagram
participant APP as 应用/录音任务
participant ENC as 编码器管理 (encoder_api.c)
participant ADC as audio_adc + cbuf_adc
participant CODEC as 编码器 (ENC_OPS)
participant WFILE as 写文件 (encoder_file / enc_in_xxx)
APP->>ENC: rec_phy_init() 装配链路
APP->>ENC: encoder_io(a_encode_api, enc_input, output_func, pfile)
ENC->>CODEC: a_encode_api(): ops->open / set_info / init
CODEC-->>ENC: 返回 enc_a_hdl
APP->>ENC: start_encode()
ENC->>ADC: audio_adc_enable() + START_ADC_RUN
loop 采样→编码循环
ADC->>ADC: 中断写入 cbuf_adc
ADC->>ENC: kick_encode_api (bit_set_swi(1))
ENC->>CODEC: enc_input(priv, buf, channel, len)
CODEC->>CODEC: 压缩编码 (IMA/MP3/UMP3)
CODEC-->>WFILE: output_data 回调
WFILE->>WFILE: 写 FATFS/NORFS
end
APP->>ENC: stop_encode_phy(obj, NEED_WAIT)
ENC->>ADC: audio_adc_disable() + STOP_ADC_RUN
ENC->>ENC: B_ENC_STOP 置位,循环 kick 排空 p_ibuf
ENC->>CODEC: 剩余数据编码完毕
ENC->>WFILE: wait_output_empty / 刷盘
ENC-->>APP: 返回
状态流转
stateDiagram-v2
[*] --> IDLE: rec_phy_init()
IDLE --> RUNNING: start_encode()<br/>audio_adc_enable + START_ADC_RUN
RUNNING --> RUNNING: ADC 中断 → kick_encode_isr → enc_input
RUNNING --> FULL: 输出缓冲满 (B_ENC_FULL)
FULL --> RUNNING: 数据被写文件取走
RUNNING --> STOPPING: stop_encode_phy()<br/>置 B_ENC_STOP
STOPPING --> IDLE: p_ibuf 排空 + ADC 关闭
FULL --> IDLE: stop_encode_phy 强制退出<br/>(B_ENC_FULL 提前跳出)
状态语义:IDLE 表示链路已装配但未采集;RUNNING 为稳态,编码完全由 ADC 中断"踢"动;FULL 是背压状态——写 Flash 慢于编码时输出缓冲打满,B_ENC_FULL 置位会同时让停止流程提前退出,避免无效等待;STOPPING 是"先停采集、再排空残留"的收尾态。
Usage Examples
示例 1:注册/初始化一个 A 格式编码器
以下代码展示 a_encode_api() 如何获取 IMA 操作集、校验工作区并完成编码器初始化(省略了循环缓冲清零等前置步骤的展示,完整见源码):
u32 a_encode_api(void *p_file, void *input_func, void *output_func)
{
u32 buff_len, i;
ENC_OPS *ops;
log_info("a_encode_api\n");
ops = get_ima_code_ops();
buff_len = ops->need_buf();
if (buff_len > sizeof(a_encode_buff)) {
log_info("a buff_len : %d", buff_len);
log_info("a a_encode_buff len : %d", sizeof(a_encode_buff));
return 0; // 工作区不足,初始化失败
}
...
a_enc_io.priv = &enc_a_hdl;
a_enc_io.input_data = input_func;
a_enc_io.output_data = output_func;
enc_a_hdl.p_file = p_file;
enc_a_hdl.p_ibuf = REC_ADC_CBUF;
enc_a_hdl.p_obuf = &cbuf_ima_o;
enc_a_hdl.p_dbuf = &a_encode_buff[0];
enc_a_hdl.enc_ops = ops;
enc_a_hdl.info.sr = read_audio_adc_sr();
for (i = 0; i < ARRAY_SIZE(a_enc_sr_tab); i++) {
if (enc_a_hdl.info.sr == a_enc_sr_tab[i]) {
break; // 采样率必须在支持表内
}
}
if (i == ARRAY_SIZE(a_enc_sr_tab)) {
log_error("a encode sample rate %d is not matched!\n", enc_a_hdl.info.sr);
return 0;
}
enc_a_hdl.info.br = enc_a_hdl.info.sr / 1000 * 4; // 码率 = 采样率/1000 * 4 (kbps)
enc_a_hdl.info.nch = 1; // 强制单声道
...
ops->open((void *)&a_encode_buff[0], (void *)&a_enc_io);
ops->set_info((void *)&a_encode_buff[0], &enc_a_hdl.info);
ops->init((void *)&a_encode_buff[0]);
return (u32)&enc_a_hdl; // 返回编码句柄
}
Source: a_encoder.c
示例 2:录音链路装配与启停
rec_phy_init() 展示了链路装配的两种路径(是否插入 ANS 降噪),以及 start_encode() / stop_encode_phy() 的启停控制:
void rec_phy_init(void)
{
memset(&rec_sound, 0, sizeof(rec_sound));
stream_frame_init(IRQ_SPEAKER_IP);
cbuf_init(&cbuf_adc, &adc_buff[0], sizeof(adc_buff)); // 7680B 采样缓冲
rec_sound.p_obuf = &cbuf_adc;
sound_out_obj *p_curr_sound = &rec_sound;
#if (defined(ANS_EN) && (ANS_EN))
cbuf_init(&cbuf_ans, &ans_buff[0], sizeof(ans_buff));
p_curr_sound = link_ans_sound(p_curr_sound, &cbuf_ans, read_audio_adc_sr());
#endif
if (p_curr_sound != &rec_sound) { // 插入了 ANS
void *kick = NULL;
kick = regist_stream_channel(&rec_sound, kick_encode_api);
regist_audio_adc_channel(&rec_sound, NULL, (void *)kick);
} else {
regist_audio_adc_channel(&rec_sound, NULL, (void *)kick_encode_api);
}
enc_in_sound = p_curr_sound;
rec_sound.enable |= B_DEC_RUN_EN | B_DEC_FIRST;
}
void start_encode(void)
{
audio_adc_enable();
mdelay(10); // 等待 ADC 稳定
START_ADC_RUN; // enable |= (B_DEC_RUN_EN | B_REC_RUN)
}
Source: encoder_api.c
示例 3:停止编码并等待残留数据排空
stop_encode_phy() 展示"先停 ADC → 置 STOP 标志 → 有界排空"的收尾协议,以及 B_ENC_FULL 与超时计数 i 的双重保护:
bool stop_encode_phy(enc_obj *obj, IS_WAIT wait)
{
u32 res;
audio_adc_disable(); // 1. 源头停止采样
STOP_ADC_RUN;
log_info("stop encode\n");
if (NULL == obj) { // 无编码器时只挂起物理层
rec_phy_suspend();
return false;
}
obj->enable |= B_ENC_STOP; // 2. 置停止标志
if (NEED_WAIT == wait) {
u32 i = 0x10000; // 超时保护计数
while (0 != cbuf_get_data_size(obj->p_ibuf) && (0 != i)) {
if (obj->enable & B_ENC_FULL) {
break; // 输出满则提前退出
}
kick_encode_isr(); // 3. 反复踢软件中断排空输入
delay(100);
i--;
}
obj->enable &= ~B_ENC_ENABLE; // 4. 清除使能位
if (NULL != obj->wait_output_empty) {
obj->wait_output_empty(obj); // 5. 等待输出排空钩子
}
...
}
}
Source: encoder_api.c
Configuration Options
以下编译期与运行期配置决定录音子系统的行为:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
ENCODER_A_EN | 编译宏 | 条件编译 | 使能 A/IMA 编码器(a_encoder.c 整体被 #if 包裹) |
ANS_EN | 编译宏 | 未定义 | 使能自适应降噪链路,rec_phy_init() 中通过 link_ans_sound() 将 ANS 透明插入 ADC 与编码器之间 |
FPGA | 编译宏 | 未定义 | 平台切换:非 FPGA 使用 enc_in_sound->p_obuf 作为 REC_ADC_CBUF;FPGA 平台改用 adc_obj 模型 |
A_EBUF_SIZE | 宏常量 | 由库决定 | A 编码器静态工作区大小,a_encode_api() 用 ops->need_buf() 返回值校验是否溢出 |
adc_buff[7680] | 静态数组 | 7680 B | ADC 采样环形缓冲,容量 ≥ sr×2×80ms(覆盖 NORFS 最长擦写时间) |
obuf_ima_o[1024] | 静态数组 | 1024 B | A 编码器输出环形缓冲 |
ans_buff[512] | 静态数组 | 512 B | ANS 链路缓冲(仅 ANS_EN 编译时存在) |
| A 编码采样率 | 运行期 | read_audio_adc_sr() | 必须命中 a_enc_sr_tab[](8k/12k/16k/24k/32k),否则初始化失败 |
| A 编码码率 | 运行期 | sr/1000*4 kbps | 码率与采样率固定 4:1(32kbps@8k … 128kbps@32k) |
| A 编码声道 | 运行期 | 1(单声道) | enc_a_hdl.info.nch = 1 强制单声道 |
内存段约束:采样缓冲位于 .rec_data,A 编码器缓冲位于 .enc_a_data,kick_encode_api() 位于 .audio_a.text.cache.L2——这些段属性是硬性要求,改动前需与链接脚本核对。
API Reference
void rec_phy_init(void)
装配录音物理链路:初始化流框架、ADC 环形缓冲,可选插入 ANS 降噪,并向 audio_adc 注册 kick_encode_api 回调。
Throws/错误: 无显式错误码;ANS_EN 下 cbuf_ans/ans_buff 空间不足会导致链路异常,需保证 ans_buff 尺寸匹配 read_audio_adc_sr() 采样率。
void start_encode(void)
使能 ADC 采样并置运行位(B_DEC_RUN_EN | B_REC_RUN),开始录音。调用前须完成 rec_phy_init() 与编码器初始化。
时序约束: 内置 mdelay(10) 等待 ADC 稳定。
bool stop_encode_phy(enc_obj *obj, IS_WAIT wait)
停止录音物理层。先 audio_adc_disable() 停采样,再置 B_ENC_STOP;wait == NEED_WAIT 时有界等待 p_ibuf 排空(循环 kick_encode_isr(),B_ENC_FULL 或超时 0x10000 次提前退出),随后清除 B_ENC_ENABLE 并调用 wait_output_empty 钩子。obj == NULL 时仅挂起物理层(rec_phy_suspend())并返回 false。
Returns: false 表示无编码句柄(仅物理层挂起);有编码器时返回流程结果。
enc_obj *encoder_io(u32 (*fun)(void *, void *, void *), void *input_func, void *output_func, void *pfile)
通用编码会话入口。fun 为编码器入口函数(如 a_encode_api / mp3_encode_api / ump3_encode_api),input_func / output_func 为数据回调,pfile 为目标文件。返回 enc_obj* 句柄供后续停止/关闭使用。
u16 enc_input(void *priv, s16 *buf, u8 channel, u16 len)
从编码器输入缓冲取 PCM 数据送入编码器的回调(通常作为 input_func 传入)。priv 为 enc_a_hdl 等句柄,buf 为采样数据,channel 声道号,len 长度;返回实际消费的采样数。
u32 a_encode_api(void *p_file, void *input_func, void *output_func)
A/IMA 格式编码器初始化入口。校验工作区与采样率支持表后填充 enc_a_hdl 并完成 open/set_info/init。
Returns: 编码句柄 (u32)&enc_a_hdl;工作区不足或采样率不匹配时返回 0。
u32 regist_encode_channel(enc_obj *p_enc_obj) / u32 unregist_encode_channel(enc_obj *p_enc_obj)
注册/注销编码通道到编码调度框架,供应用层在启动/停止时挂钩。
bool is_encode_channel_empty(void)
查询编码通道是否已全部释放,用于资源回收前确认。
void kick_encode_api(void *obj) / #define kick_encode_isr() bit_set_swi(1)
ADC 数据到达回调,触发软件中断执行编码;kick_wfile_isr()(bit_set_swi(2))触发写文件软件中断。
enc_obj *stop_encode_file(enc_obj *p_enc_obj, u32 dlen)
停止文件编码:将剩余 dlen 数据刷入文件后关闭文件句柄,返回更新后的句柄。
Failure Modes、边界情况与并发
采样率不匹配导致初始化失败
a_encode_api() 将当前 ADC 采样率与 a_enc_sr_tab[](8k/12k/16k/24k/32k)比对,不在表内则 log_error("a encode sample rate %d is not matched!") 并返回 0。设计意图:A 格式码率与采样率强绑定(4:1),在不支持的采样率下编码会产出不可解码数据,宁可拒绝启动也不产出坏文件。MP3/UMP3 同理依赖 read_audio_adc_sr() 的一致性。
工作区不足(buffer overflow 防护)
ops->need_buf() 返回的编码器需求与静态缓冲 a_encode_buff 比较,不足时打印实际需求与可用大小并返回 0。这是对"库版本升级导致 need_buf 增大"这类问题的编译期-运行期双重防线——静态缓冲分配在专用段,无法动态扩容,因此必须显式校验。
停止时的死等风险与三重保护
stop_encode_phy(NEED_WAIT) 的排空循环依赖三个退出条件:
cbuf_get_data_size(obj->p_ibuf) == 0——正常排空;B_ENC_FULL置位——输出缓冲满(写 Flash 慢于编码),提前退出避免无效空转;- 计数
i从0x10000递减到 0——超时保护,防止底层驱动异常导致死循环。
三者任一命中即继续收尾,保证停止流程在任何 Flash 时序下都有界。
环形缓冲并发模型
cbuf_adc 由 ADC 中断写、软件中断读,cbuf_ima_o 由编码中断写、写文件软件中断读——均为经典的单生产者单消费者环形缓冲。enc_obj.enable 为 volatile,标志位由不同中断上下文置位/清除。约束:不要在编码/写文件软件中断中调用阻塞型 API;stop_encode_phy 中的 delay(100) 只出现在任务上下文,不在中断内执行。
NORFS 擦写时序约束
源码注释明确:norfs 擦写时间最长不超过 80ms,因此 adc_buff 最小缓存不小于 sr*2*80ms(7680B 正是按此公式为常见采样率配置的)。若修改采样率或 Flash 型号导致擦写时间变化,必须同步复核该缓冲容量,否则录音会出现采样空洞(数据覆盖/丢失)。
Performance 与运维注意事项
- 零轮询调度:编码完全由 ADC 中断"踢"动(
kick_encode_api→bit_set_swi(1)),无独立采样轮询任务,CPU 占用集中在实际采样期间; - 关键路径缓存优化:
kick_encode_api()显式置于.audio_a.text.cache.L2段,避免中断路径上的缓存 miss 抖动; - 专用内存段:
adc_buff(.rec_data)与编码缓冲(.enc_a_data)与主程序数据分离,支持低功耗模式的按需加载,也避免 DMA/中断访问与 CPU 缓存冲突; - 有界停止:
stop_encode_phy的超时计数(0x10000×delay(100))保证停止操作耗时上界可控,便于上层做低功耗切换; - buffer 规模核对:每次改动采样率、缓冲尺寸或 Flash 驱动后,需重新验证
adc_buff ≥ sr×2×80ms与need_buf() ≤ A_EBUF_SIZE。
Extension Points
- 新增编码格式:在
sdk/apps/app/bsp/common/encoder/list/下仿照a_encoder.c实现xxx_encode_api():get_xxx_code_ops()取操作集 →need_buf()校验 → 填充静态enc_xxx_hdl与EN_FILE_IO→open/set_info/init。随后通过encoder_io(xxx_encode_api, enc_input, output_func, pfile)即可接入现有调度框架,无需改动encoder_api.c; - 输出目标替换:
output_data回调由调用方注入,可将编码流导向内存缓冲、UART/蓝牙传输而非文件系统(enc_in_fatfs.c/enc_in_norfs.c即该模式的落盘实现); - 前置信号处理:
ANS_EN展示了通过link_ans_sound()在 ADC 与编码器之间透明插入处理链(降噪等)的机制,可扩展 EQ、AGC 等预处理模块; - 停止钩子:
enc_obj.wait_output_empty提供排空完成通知点,可在自定义输出端(如网络发送)实现自己的刷盘语义。
Tests 与验证方式
源码仓库未在本次范围内发现针对编码器的独立单元测试工程;该子系统属嵌入式实时路径,验证主要依赖:
- 采样率/码率组合测试:对 8k~32k 各采样率启动录音,校验文件头参数与
a_enc_sr_tab一致; - 满缓冲/慢 Flash 场景:人为拉长 NORFS 擦写时间,观察
B_ENC_FULL是否置位、停止流程是否超时退出、录音文件是否有空洞; - 停止时序测试:
NEED_WAIT下反复启停,确认p_ibuf排空、B_ENC_ENABLE清除、wait_output_empty被调用; - 内存段验证:检查 map 文件中
adc_buff、a_encode_buff、kick_encode_api是否落在预期段。
注:上述验证手段为依据源码设计推导出的建议项;具体测试代码若存在,请参考仓库 test 目录。
Related Links
- 音频采集与 ADC 驱动:
sdk/apps/include_lib/audio/audio_adc.h(read_audio_adc_sr、regist_audio_adc_channel、audio_adc_enable/disable) - 音频解码与回放:见本目录"音频解码"页(
sound_out_obj/stream_frame体系同源) - 编码器头文件与结构定义:encoder_mge.h
- 编码器管理实现:encoder_api.c
- A 编码器实现:a_encoder.c
- MP3 编码器实现:mp3_encoder.c
- UMP3 编码器实现:ump3_encoder.c
- 录音写文件:encoder_file.c、enc_in_fatfs.c、enc_in_norfs.c