杰理 SDK 文档中心
首页
首页
  • 项目概览

    • AD16N 系列芯片与 SDK 能力总览
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建指南
    • 烧录与固件升级
  • SDK 工程架构

    • SDK 目录结构与模块分层
    • 构建系统与批处理工具
    • BSP 板级支持包
  • mbox_flash 小音箱应用

    • 应用初始化与启动流程
    • 应用配置系统
    • 按键、UI 与用户交互
  • 音频子系统

    • 音频解码框架与调度
    • 音频格式解码器实现
    • MIDI 合成与播放
    • 音频编码与录音
    • EQ/DRC 与音效处理
    • DAC/ADC 音频接口与采样
  • 存储与文件系统

    • 媒体 IO 抽象层 MIO
    • 存储设备驱动
    • 文件系统支持
  • 平台系统库

    • 系统基础服务
    • CPU 平台与运行库
    • 固件升级与更新机制
    • 蓝牙与扩展连接接口
  • 电源与低功耗管理

    • 电源管理与低功耗设计
    • 锂电池充电管理
  • 硬件与文档参考

    • SDK 文档中心与版本发布记录
    • 芯片数据手册与硬件设计参考

音频编码与录音

本文档深入解析 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)协作模型:

  1. audio_adc 硬件外设在中断中把 PCM 采样写入 cbuf_adc 环形缓冲;
  2. 采样数据通过 regist_audio_adc_channel 注册的回调 kick_encode_api 触发软件中断 kick_encode_isr();
  3. 软件中断里调用 enc_input() 从环形缓冲取数据,送入编码器(IMA/MP3/UMP3)压缩;
  4. 编码后的数据经 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_ENABLEBIT(0)编码通道使能
B_ENC_STOPBIT(1)停止请求置位,进入排空流程
B_ENC_FULLBIT(2)输出缓冲满,强制停止等待
B_ENC_INITBIT(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 完成录音链路装配:

  1. memset(&rec_sound, 0, ...) 清零全局 sound_out_obj;
  2. stream_frame_init(IRQ_SPEAKER_IP) 初始化流框架;
  3. cbuf_init(&cbuf_adc, ...) 初始化 7680 字节的 ADC 环形缓冲;
  4. 若编译宏 ANS_EN 开启,则把 ANS(自适应降噪)模块以链式 sound_out_obj 形式插入 p_curr_sound,并对 cbuf_ans 初始化;
  5. 根据是否插入 ANS 选择注册路径:有 ANS 时先 regist_stream_channel(&rec_sound, kick_encode_api) 再注册 ADC 通道并把 kick 作为回调参数传入;无 ANS 时直接 regist_audio_adc_channel(&rec_sound, NULL, kick_encode_api);
  6. 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()

encoder_api.c#L89-L94:

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 是停止流程的核心:

  1. 关闭 ADC(audio_adc_disable())并清运行位,从源头停止数据产生;
  2. 置 obj->enable |= B_ENC_STOP;
  3. 若 wait == NEED_WAIT,进入有界等待循环:while (0 != cbuf_get_data_size(obj->p_ibuf) && (0 != i)),循环内反复 kick_encode_isr() 驱动软件中断把残留数据编码完毕;若输出满(B_ENC_FULL)则提前跳出;i = 0x10000 作为超时保护,防止 Flash 异常时死循环;
  4. 清 B_ENC_ENABLE,调用 wait_output_empty(obj) 等待输出缓冲排空(若存在该钩子)。

设计要点:先停 ADC、再排空输入缓冲,保证停止时既不丢数据也不会在停止后仍有新数据涌入;B_ENC_FULL 与超时计数共同构成双重保护,避免"等待排空"被慢速 Flash 写拖死。

中断回调 kick_encode_api()

encoder_api.c#L79-L83:

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)8k12k16k24k32k
码率 (BR)32kbps48kbps64kbps96kbps128kbps

入口函数 a_encode_api()(a_encoder.c#L52-L104)的关键步骤:

  1. ops = get_ima_code_ops() 获取 IMA 编码器操作集;
  2. buff_len = ops->need_buf() 查询编码器所需工作区大小,与静态数组 a_encode_buff[A_EBUF_SIZE/4] 比对,不足则报错返回 0;
  3. 清零句柄与输出缓冲,初始化输出环形缓冲 cbuf_ima_o(1024 B);
  4. 填充 EN_FILE_IO 回调与 enc_a_hdl(p_ibuf = REC_ADC_CBUF,p_obuf = &cbuf_ima_o,p_dbuf = &a_encode_buff[0]);
  5. enc_a_hdl.info.sr = read_audio_adc_sr() 读取当前 ADC 采样率,并在 a_enc_sr_tab[] 中查找匹配项;找不到匹配采样率则打印错误并返回 0(拒绝在不支持采样率下编码);
  6. br = sr / 1000 * 4(kbps 单位),nch = 1 强制单声道;
  7. 依次调用 ops->open(buffer, &a_enc_io)、ops->set_info(buffer, &info)、ops->init(buffer) 完成编码器实例化;
  8. 返回 (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 BADC 采样环形缓冲,容量 ≥ sr×2×80ms(覆盖 NORFS 最长擦写时间)
obuf_ima_o[1024]静态数组1024 BA 编码器输出环形缓冲
ans_buff[512]静态数组512 BANS 链路缓冲(仅 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) 的排空循环依赖三个退出条件:

  1. cbuf_get_data_size(obj->p_ibuf) == 0——正常排空;
  2. B_ENC_FULL 置位——输出缓冲满(写 Flash 慢于编码),提前退出避免无效空转;
  3. 计数 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

  1. 新增编码格式:在 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;
  2. 输出目标替换:output_data 回调由调用方注入,可将编码流导向内存缓冲、UART/蓝牙传输而非文件系统(enc_in_fatfs.c / enc_in_norfs.c 即该模式的落盘实现);
  3. 前置信号处理:ANS_EN 展示了通过 link_ans_sound() 在 ADC 与编码器之间透明插入处理链(降噪等)的机制,可扩展 EQ、AGC 等预处理模块;
  4. 停止钩子: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
Prev
MIDI 合成与播放
Next
EQ/DRC 与音效处理