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

    • AD23N SDK 概述与芯片平台
    • 工程结构与模块划分
  • 快速开始

    • 开发环境搭建与工具链
    • 编译构建指南
    • 烧录与固件升级工具
  • 应用框架与产品工作流

    • 应用入口与模式调度
    • 音乐播放应用
    • MIDI 解码与键盘演奏
    • 录音应用
    • LINEIN 与扩音应用
    • USB 从设备应用
    • 待机、软关机与空闲检测
    • 公共 UI 与 LED 显示
  • 音频子系统

    • 音频解码器框架
    • 音频编码器框架
    • 音效算法库
    • 音频管理与输出通路
  • 存储与文件系统

    • 文件系统层
    • NOR Flash 与虚拟机存储
    • 设备与设备管理
  • 系统服务与运行时

    • 消息机制与事件分发
    • 按键扫描与输入处理
    • 电源管理与低功耗控制
    • 定时器与系统任务
  • 外设驱动与平台

    • CPU 平台与启动流程
    • USB 协议栈与主机/设备驱动
    • SPI 与通用外设接口
  • 固件升级与构建工具

    • 固件升级机制
    • 编译后处理与镜像打包
    • 构建系统与命令行工具

音频编码器框架

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 的编码器框架将这一复杂链路抽象为三层:

  1. 物理层(PHY):encoder_api.c 管理 ADC 采集通道与环形缓冲 cbuf_adc,通过 sound_out_obj 与 audio_adc 驱动对接,可选串联 ANS(环境降噪)效果器。
  2. 管理层(Manager):encoder_mge.h 定义统一的 enc_obj 句柄与通道注册 API,负责把某个具体编码器实例挂接到全局编码通道链表,并用两个软件中断(kick_encode_isr = IRQ_SOFT1、kick_wfile_isr = IRQ_SOFT2)驱动"编码"与"写文件"两个异步处理循环。
  3. 实现层(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_IO IO 回调(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

启动顺序的设计意图:先物理后逻辑,先注册后使能。

  1. rec_phy_init():初始化 ADC 侧环形缓冲 cbuf_adc、注册 ADC 采集通道;若编译宏 ANS_EN 开启,则把 ANS 降噪效果器链接进数据链(link_ans_sound),此时中断回调为 kick_encode_api(经 stream_sound_init 注册)。
  2. fun(pfile):调用具体编码器工厂(如 a_encode_api),完成资源申请、参数填充、ops->open/set_info/init,返回就绪的 enc_obj。
  3. regist_encode_channel(enc_hdl):将句柄挂入全局编码通道链表(失败则返回 NULL)。
  4. enc_phy_init():初始化编码中断相关的物理资源(软中断安装等)。
  5. 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 落盘

关键设计点:

  1. 输入缓冲复用:p_ibuf 直接指向 ADC 侧 REC_ADC_CBUF(enc_in_sound->p_obuf),PCM 数据零拷贝进入编码器,见 a_encoder.c。
  2. 输出缓冲私有:每个编码器维护自己的环形缓冲(A 编码器为 cbuf_ima_o,1024 字节),算法产生的数据先入缓冲,再由写盘中断批量写入文件,避免频繁文件 IO。
  3. 双中断解耦:编码速度与写盘速度天然不同步,两个软件中断各自驱动一段流水,中间以环形缓冲吸收速率差——这正是"生产者-消费者"模型在中断级上的实现。

编码器实现列表

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常量1024A 编码器输出环形缓冲字节数,见 a_encoder.c L15
A_ENC_BUF_SIZE常量368A 编码器算法工作缓冲上限(须 ≥ need_buf()),见 a_encoder.c L16
adc_buff[512 * 10]数组5120 BADC 采集环形缓冲(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编码参数256A 编码器码率(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 并返回 false
  • pfile:目标文件句柄(用于 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_bufu32 (*)(void)返回算法所需工作缓冲字节数,用于资源预算校验
openvoid (*)(u8 *ptr, EN_FILE_IO *audioIO)传入工作缓冲与 IO 回调
set_infovoid (*)(u8 *ptr, ENC_DATA_INFO *data_info)设置采样率/码率/声道数
initu32 (*)(u8 *ptr)初始化编码状态机
process(隐含)—中断中执行编码(框架经 enc_ops 调用)
get_timeu32 (*)(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 需评估"写前喂狗导致主循环饿死"的风险。

扩展点:新增编码器实现

框架为新增编码格式提供了清晰的接入模板,按以下步骤即可挂接新算法:

  1. 实现 ENC_OPS:提供 need_buf/open/set_info/init/process/get_time,算法只面向 u8 *ptr 工作缓冲与 EN_FILE_IO 回调编程,不感知 MCU 细节。
  2. 编写工厂函数:仿照 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。
  3. 提供释放回调:encoder_res_release 归还 penc_res,供 stop_encode 统一调用。
  4. 受控编译:用 ENCODER_XXX_EN 宏包住整个实现(参考 a_encoder.c L9),未使能时零代码体积。
  5. 接入应用:调用方将新工厂函数传入 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)相关页面
Prev
音频解码器框架
Next
音效算法库