杰理 SDK 文档中心
首页
首页
  • 项目概览与快速开始

    • 项目概述与芯片支持
    • 环境搭建与工具链
    • 工程与构建系统
    • 烧录与升级工具
    • 文档与硬件资料
  • 系统架构与芯片平台

    • 芯片平台与启动流程
    • 预编译库与头文件体系
    • 消息、定时器与中断服务
    • 通用外设驱动
  • 存储与文件系统

    • 文件系统实现
    • 存储设备驱动
    • VM 参数存储系统
  • 音频处理

    • 音频解码器
    • 音频编码器
    • MIDI 合成与播放
    • 音效、变速变调与降噪
  • 语音玩具应用

    • 应用框架与状态机
    • 音乐播放与外部音源
    • MIDI 乐器模式
    • 录音应用
    • 待机、电源管理与 USB 从机
  • 小音箱应用

    • 应用框架与模式管理
    • 播放源:音乐、FM、录音与 LineIn
  • 应用层与示例工程

    • 通用 MCU 应用
  • 固件更新与补丁

    • 固件升级机制
    • AD14N 主动降噪补丁

音频编码器

音频编码器(Audio Encoder)是杰理 AD15N/AD1NN MCU SDK 中负责将 ADC 采集的 PCM 音频数据实时压缩为 IMA ADPCM、MP2 等格式并写入文件的子系统。它由统一的编码器管理层(enc_obj)、可插拔的编解码操作集(ENC_OPS)以及软件中断驱动的数据搬运机制组成,是录音类应用(如 voice_toy 录音)的核心通路。

Purpose and Scope

本页面向 audio-processing/encoder 目录,完整说明音频编码器子系统的工作原理:

  • 编码器对外 API 契约(EN_FILE_IO、ENC_DATA_INFO、ENC_OPS)与对象模型(enc_obj);
  • 从 ADC 采集到文件落盘的完整数据通路(encoder_api.c);
  • 编码器的启停流程、状态标志与软件中断(kick_encode_isr / kick_wfile_isr)调度机制;
  • 支持的编码格式(IMA ADPCM、MP2、MP2 Standard)及扩展方式;
  • 配置项、故障模式与并发/中断上下文注意事项。

以下主题属于其他页面范畴,不在本页展开:音频解码器与 DAC 播放通路、sound_effect_api.h 中的音效处理、ANS 降噪算法本身(仅在编码通路中说明其接入点)、以及上层录音应用(如 voice_toy/toy_record)的业务逻辑。SDK 预编译的编码库(lib_mp2_encode.a、lib_mp2standard_encode.a、encoder_mge_lib.a)只作为二进制依赖提及,其内部实现不在源码中公开。

概述

在录音类应用里,系统需要把麦克风(MIC)输入的连续模拟信号数字化并压缩存储。编码器子系统承担以下职责:

  1. 采集接入:通过 audio_adc 注册 ADC 通道,将 ADC 中断产出的 PCM 数据送入环形缓冲区(cbuffer);
  2. 格式转换:按 ENC_DATA_INFO 指定的采样率(sr)与码率/块大小(br)配置底层编码器;
  3. 实时编码:在软件中断(软中断 1)上下文中驱动 ENC_OPS->run() 完成 PCM → 压缩码流转换;
  4. 落盘输出:在软中断 2 上下文中把压缩数据经 output_data 回调写入文件(通过 vfs 文件系统 IO);
  5. 优雅停止:stop_encode() 会逐级排空输入/输出环形缓冲,再卸载软中断并同步文件。

设计上有三个关键决策值得注意:

  • 可插拔编码器(strategy 模式):ENC_OPS 统一了 need_buf / open / run 三个操作,新增编码格式只需实现这三个函数并注册即可,管理层代码无需改动;
  • 中断驱动 + 环形缓冲解耦:ADC ISR 与编码器运行在不同上下文,通过 cbuffer 解耦,避免在中断里做耗时编码;
  • 软中断作任务调度器:kick_encode_isr() / kick_wfile_isr() 以 bit_set_swi() 触发两个软件中断,作为"编码就绪"与"写文件就绪"的调度信号,实现简单且开销低。

架构

flowchart TD
    subgraph sg_Input["采集层"]
        MIC["MIC 模拟输入"]
        ADC["audio_adc 通道<br/>rec_sound / enc_in_sound"]
        CBUF_ADC["环形缓冲 cbuf_adc<br/>(.rec_data 段)"]
        ANS["ANS 降噪(可选)<br/>cbuf_ans / ans_init"]
    end

    subgraph sg_EncMgr["编码器管理层 encoder_api.c"]
        ENC_OBJ["enc_obj 编码器对象"]
        KICK1["软中断1 kick_encode_isr"]
        KICK2["软中断2 kick_wfile_isr"]
        ENC_IN["enc_input 回调"]
        ENC_OUT["enc_output 回调"]
    end

    subgraph sg_Codect["编码器实例(可插拔)"]
        OPS["ENC_OPS 操作集<br/>need_buf / open / run"]
        IMA["IMA ADPCM<br/>get_ima_code_ops"]
        MP2["MP2<br/>get_mp2_ops"]
        MP2S["MP2 Standard<br/>get_mp2standard_ops"]
    end

    subgraph sg_File["输出层"]
        VFS["vfs 文件系统"]
        FILE["录音文件(NOR Flash)"]
    end

    MIC --> ADC
    ADC -->|"ADC ISR 填充"| CBUF_ADC
    CBUF_ADC -->|"ans_init 可选链路"| ANS
    ANS -->|"kick_encode_api"| ENC_OBJ
    CBUF_ADC -->|"kick_encode_api 注册回调"| ENC_OBJ
    ENC_OBJ -->|"p_ibuf 输入"| ENC_IN
    ENC_IN --> OPS
    OPS -->|"实例选择"| IMA
    OPS -->|"实例选择"| MP2
    OPS -->|"实例选择"| MP2S
    OPS -->|"p_obuf 输出"| ENC_OUT
    KICK1 -->|"触发编码"| OPS
    KICK2 -->|"触发写盘"| ENC_OUT
    ENC_OUT --> VFS
    VFS --> FILE

架构说明:

  • 采集层:rec_phy_init() 初始化两个 sound_out_obj(rec_sound、enc_in_sound)并共享同一 ADC 环形缓冲 cbuf_adc;当开启 ANS_EN 宏时,ADC 数据先经 ans_init() 降噪后再进入 cbuf_ans,编码输入改为从 cbuf_ans 读取(见 encoder_api.c)。
  • 编码器管理层:enc_obj 是贯穿全程的状态载体,持有文件句柄、输入/输出缓冲、编码操作集与 enable 状态位(见 encoder_mge.h)。
  • 编码器实例层:ENC_OPS 策略接口由各编码库实现(IMA ADPCM 内置于 SDK,MP2 系列为预编译静态库),通过 get_ima_code_ops() / get_mp2_ops() / get_mp2standard_ops() 获取(见 audio_enc_api.h)。
  • 输出层:编码后的数据经 enc_output 回调、vfs_ioctl 文件同步写入 Flash,避免数据丢失。

核心数据契约

EN_FILE_IO — 编码器与外部世界的 IO 桥

编码器本身不关心数据来自哪里、写到哪去,只依赖两个回调完成数据搬运(见 audio_enc_api.h):

typedef struct _EN_FILE_IO_ {
    void *priv;
    u16(*input_data)(void *priv, s16 *buf, u16 len);
    u32(*output_data)(void *priv, u8 *buf, u16 len);
} EN_FILE_IO;
  • input_data:从 priv 指向的源(这里是 enc_obj 的输入环形缓冲)读取 len 个 16 位 PCM 采样;
  • output_data:把编码后的 len 字节压缩码流写入 priv 指向的目的地(文件)。

priv 的设计让同一套 ENC_OPS 可以被多个录音实例复用——只需传入不同的 priv 上下文。SDK 中 enc_input / enc_output 即作为该回调的桥接实现。

ENC_DATA_INFO — 编码参数

typedef  struct   _ENC_DATA_INFO_ {
    u16 sr;            ///<sample rate
    u16 br;            ///<mp2的时候它是bitrate,但是adpcm的时候,它是blockSize,一般配成256/512/1024/2048,超过2048会被限制成2048
} ENC_DATA_INFO;

(见 audio_enc_api.h)

  • sr:采样率(如 8000/16000 Hz);
  • br:语义随编码格式变化——MP2 系列表示码率(bitrate);IMA ADPCM 表示块大小(blockSize),常用 256/512/1024/2048,超过 2048 会被底层钳制到 2048。这一"一字段两义"的设计是编码器层为统一接口付出的代价:不同编码器对同一字段的解释不同,上层配置时必须知道当前选用哪种编码器。

ENC_OPS — 编码器策略接口

typedef struct _ENC_OPS {
    u32(*need_buf)();
    void (*open)(u8 *ptr, ENC_DATA_INFO *data_info, EN_FILE_IO *audioIO);
    u32(*run)(u8 *ptr);
} ENC_OPS;

(见 audio_enc_api.h)

三个操作构成编码器完整生命周期:

  1. need_buf():返回该编码器工作所需的私有缓冲区字节数,管理层据此分配 p_dbuf;
  2. open(ptr, data_info, audioIO):用数据指针、编码参数和 IO 桥完成初始化;
  3. run(ptr):执行一次编码,从 input_data 取 PCM、向 output_data 吐码流,返回本次处理长度。run 被设计成"可反复被软中断调用",天然适配无阻塞的中断驱动模型。

enc_obj 与状态标志

#define B_ENC_ENABLE  BIT(0)
#define B_ENC_STOP    BIT(1)
#define B_ENC_FULL    BIT(2)

typedef struct _enc_obj {
    void *p_file;
    void *p_ibuf;
    void *p_obuf;
    void *enc_ops;
    void *p_dbuf;
    ENC_DATA_INFO info;
    volatile u32 enable;
} enc_obj;

(见 encoder_mge.h)

enable 是 volatile 位掩码,表示编码器的运行状态:

标志含义
B_ENC_ENABLE编码器处于运行状态(encoder_io 成功后置位)
B_ENC_STOP收到停止请求(stop_encode 置位),排空流程据此推进
B_ENC_FULL缓冲已满/异常,允许排空循环提前退出,防止死循环

volatile 关键字说明这些标志会被中断上下文读写,编译器不得对其优化缓存。

实现走读(encoder_api.c)

1. 物理通路初始化 rec_phy_init()

void rec_phy_init(void)
{
    memset(&rec_sound, 0, sizeof(rec_sound));
    memset(&enc_in_sound, 0, sizeof(enc_in_sound));
    cbuf_init(&cbuf_adc, &adc_buff[0], sizeof(adc_buff));
    enc_in_sound.p_obuf = &cbuf_adc;
    rec_sound.p_obuf = &cbuf_adc;

#if (defined(ANS_EN) && (ANS_EN))
    cbuf_init(&cbuf_ans, &ans_buff[0], sizeof(ans_buff));
    enc_in_sound.p_obuf = &cbuf_ans;

    ans_init(&cbuf_adc, &cbuf_ans, kick_encode_api);
    regist_audio_adc_channel(&rec_sound, (void *) ans_check_kick_start); //注册到ADC;
#else
    regist_audio_adc_channel(&rec_sound, (void *) kick_encode_api); //注册到ADC;
#endif
}

(见 encoder_api.c)

要点:

  • ADC 数据缓冲 adc_buff[512 * 10 / 4] 与 ANS 缓冲 ans_buff[512 * 5 / 4] 分别放在 .rec_data / .ans_data 内存段,便于链接脚本做 RAM 布局(中断频繁访问的数据尽量靠近 CPU);
  • regist_audio_adc_channel() 把 ADC 通道与"kick 回调"绑定:每有新的 ADC 数据,就调用一次 kick_encode_api;
  • 开启 ANS 时数据路径变为 cbuf_adc → ans_init 处理 → cbuf_ans → 编码器,且 ADC 通道注册的回调换成 ans_check_kick_start——由 ANS 模块决定何时有足够数据可编码。

2. 启动 encoder_io() 与 start_encode()

void encoder_io(u32(*fun)(void *), void *pfile)
{
    s32 err;
    rec_phy_init();

    enc_hdl = (void *)fun(pfile);
    if (0 != enc_hdl) {
        enc_phy_init();
        enc_hdl->enable = B_ENC_ENABLE;
        start_encode();//adc_enable();
        log_info("encode succ: \n");
    } else {
        log_info("encode fail \n");
    }
}

(见 encoder_api.c)

void start_encode(void)
{
    START_ADC_RUN;
    audio_adc_enable(MIC_PGA_G);
}

(见 encoder_api.c)

encoder_io 是录音应用的统一入口:传入一个创建编码器对象的工厂函数 fun(pfile)(返回非零即成功),成功后:

  1. enc_phy_init() 安装编码器专用软中断(软中断 1 负责编码、软中断 2 负责写文件);
  2. enc_hdl->enable = B_ENC_ENABLE 置运行位;
  3. start_encode() 置 B_DEC_RUN_EN | B_REC_RUN 使能位并打开 audio_adc_enable,让 ADC 开始采样。

3. 停止 stop_encode()

void stop_encode(void *pfile, u32 dlen)
{
    enc_obj *obj = enc_hdl;
    u32 err;
    audio_adc_disable();
    STOP_ADC_RUN;
    log_info("stop encode\n");
    if (NULL == enc_hdl) {
        rec_phy_suspend();
        return;
    }
    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;
    HWI_Uninstall(IRQ_SOFT1_IDX);
    HWI_Uninstall(IRQ_SOFT2_IDX);

    u32 flen = dlen;
    err = vfs_ioctl(pfile, FS_IOCTL_FILE_SYNC, (int)&flen);
    rec_phy_suspend();
    enc_hdl = 0;
}

(见 encoder_api.c)

停止流程设计为两级排空,保证不留半帧数据:

  1. 先停 ADC(audio_adc_disable + 清 B_DEC_RUN_EN | B_REC_RUN),不再产生新数据;
  2. 置 B_ENC_STOP 后循环 kick_encode_isr() + delay(100) 排空输入缓冲(p_ibuf),直到 cbuf_get_data_size()==0;
  3. 再循环 kick_wfile_isr() + delay(100) 排空输出缓冲(p_obuf);
  4. 两个排空循环都以 B_ENC_FULL 作为"异常但必须退出"的保护条件,避免硬件故障时看门狗复位;
  5. 清 B_ENC_ENABLE、卸载两个软中断、vfs_ioctl(FS_IOCTL_FILE_SYNC) 把缓冲落盘、rec_phy_suspend() 反注册 ADC 通道、置 enc_hdl = 0 释放句柄。

4. 中断调度宏

#define  kick_encode_isr()   bit_set_swi(1)
#define  kick_wfile_isr()    bit_set_swi(2)

(见 encoder_mge.h)

kick_encode_api()(ADC 回调,见 encoder_api.c)在 ADC 上下文里被调用,仅做 kick_encode_isr() 触发软中断 1,把耗时编码延迟到软中断上下文执行——这是"中断里只做最轻量工作"的经典嵌入式实践。

核心流程

sequenceDiagram
    participant APP as 录音应用
    participant MGR as encoder_io / enc_obj
    participant ADC as audio_adc 通道
    participant CB as 环形缓冲 cbuffer
    participant OPS as ENC_OPS 编码器实例
    participant VFS as vfs 文件系统

    APP->>MGR: encoder_io(fun, pfile)
    MGR->>MGR: rec_phy_init() 初始化 cbuf_adc
    MGR->>MGR: fun(pfile) 创建 enc_obj(含 enc_ops/info)
    MGR->>MGR: enc_phy_init() 安装软中断1/2
    MGR->>MGR: enable = B_ENC_ENABLE
    MGR->>ADC: start_encode() → audio_adc_enable()
    Note over ADC,CB: 周期采样开始
    loop 录音期间
        ADC->>CB: ADC ISR 填充 cbuf_adc
        CB->>MGR: kick_encode_api()(或 ans_check_kick_start)
        MGR->>MGR: kick_encode_isr() → 软中断1
        MGR->>OPS: ENC_OPS->run(ptr)
        OPS->>CB: input_data 读取 PCM
        OPS->>MGR: output_data 输出码流 → p_obuf
        MGR->>MGR: kick_wfile_isr() → 软中断2
        MGR->>VFS: 写文件
    end
    APP->>MGR: stop_encode(pfile, dlen)
    MGR->>ADC: audio_adc_disable() + STOP_ADC_RUN
    MGR->>MGR: enable |= B_ENC_STOP
    loop 排空 p_ibuf
        MGR->>OPS: kick_encode_isr() 直到缓冲为空
    end
    loop 排空 p_obuf
        MGR->>MGR: kick_wfile_isr() 直到缓冲为空
    end
    MGR->>MGR: 卸载软中断、vfs_ioctl 文件同步
    MGR->>MGR: enc_hdl = 0

流程要点:

  • 数据只在"ADC ISR → 环形缓冲"、"软中断 → 编码器 → 环形缓冲"、"软中断 → 文件"三段之间流动,任一段都不会长时间占用中断;
  • B_ENC_STOP 置位后编码器仍可被 run 驱动以排空残留数据,直到缓冲清空——这是"停止但不丢数据"的关键;
  • 写文件通过 vfs(VFS 抽象层)完成,文件实际落在 NOR Flash 等存储介质上。

用法示例

以下示例均取自仓库真实源码。

示例 1:创建编码器对象(上层工厂函数模式)

encoder_io 的第一个参数是一个"以文件句柄创建编码器对象"的工厂函数。上层录音模块(如 voice_toy/toy_record)按此模式构造 enc_obj,并填写编码参数与 IO 回调:

static void *enc_create(void *pfile)
{
    // ... 分配 enc_obj,设置 p_file / p_ibuf / p_obuf / info 等字段
    // info.sr = 16000; info.br = 512;  // ADPCM 时 br 为 blockSize
    // obj->enc_ops = get_ima_code_ops(); // 选择 IMA ADPCM 编码器
    return obj;
}

工厂返回非零句柄后,encoder_io 内部会依次完成 enc_phy_init()、置 B_ENC_ENABLE 并启动 ADC(见 encoder_api.c)。上层只需在需要开始时调用 encoder_io(enc_create, pfile)。

示例 2:启动与停止编码

void start_encode(void)
{
    START_ADC_RUN;
    audio_adc_enable(MIC_PGA_G);
}
void stop_encode(void *pfile, u32 dlen)
{
    enc_obj *obj = enc_hdl;
    u32 err;
    audio_adc_disable();
    STOP_ADC_RUN;
    // ... 排空 p_ibuf / p_obuf 后:
    obj->enable &= ~B_ENC_ENABLE;
    HWI_Uninstall(IRQ_SOFT1_IDX);
    HWI_Uninstall(IRQ_SOFT2_IDX);
    err = vfs_ioctl(pfile, FS_IOCTL_FILE_SYNC, (int)&flen);
    rec_phy_suspend();
    enc_hdl = 0;
}

(见 encoder_api.c 与 encoder_api.c)

start_encode / stop_encode 是录音应用最常用的两个对外函数:开始录音调用前者,结束录音调用后者,并传入最终文件长度 dlen 以便文件系统同步。

示例 3:ADC 数据就绪回调

AT(.audio_a.text.cache.L2)
void kick_encode_api(void *obj)
{
    kick_encode_isr();
}

(见 encoder_api.c)

该函数被 regist_audio_adc_channel 注册为 ADC 通道回调,并用 AT(.audio_a.text.cache.L2) 链接属性放到 L2 cache 文本段以加快取速度。回调体只触发软中断 1,绝不在 ADC 上下文做编码。

配置选项

编码器子系统的配置分散在编译宏、结构体字段与运行时标志三层:

编译期宏(config.h / 构建系统)

选项类型默认说明
ANS_EN宏未定义定义并置 1 时启用 ANS 降噪链路,编码输入从 cbuf_ans 读取
FPGA宏0为 1 时使用 FPGA 平台的 adc_obj 替代 sound_out_obj(见 encoder_mge.h)
MIC_PGA_G常量外部定义MIC 前置增益,传入 audio_adc_enable

运行时参数(ENC_DATA_INFO)

字段类型取值范围说明
sru16如 8000/16000采样率
bru16MP2:码率;ADPCM:256/512/1024/2048超过 2048 会被限制为 2048(ADPCM 块大小)

状态标志(enc_obj.enable)

标志值置位时机
B_ENC_ENABLEBIT(0)encoder_io 成功后
B_ENC_STOPBIT(1)stop_encode 进入排空阶段
B_ENC_FULLBIT(2)缓冲满/异常,由编码器层置位以中断排空循环

API 参考

ENC_OPS *get_ima_code_ops(void)

获取 IMA ADPCM 编码器操作集(内置实现)。返回的 ENC_OPS 包含 need_buf / open / run,用法与 MP2 系列一致。

ENC_OPS *get_mp2_ops(void)

获取 MP2 编码器操作集(由预编译库 lib_mp2_encode.a 提供)。

ENC_OPS *get_mp2standard_ops(void)

获取 MP2 Standard 编码器操作集(由预编译库 lib_mp2standard_encode.a 提供)。

(三个获取函数声明见 audio_enc_api.h)

ENC_OPS 生命周期方法

方法签名说明
need_bufu32(*)(void)返回编码器私有缓冲所需字节数
openvoid(*)(u8 *ptr, ENC_DATA_INFO *data_info, EN_FILE_IO *audioIO)初始化编码器:ptr 为 need_buf 分配的缓冲,data_info 携带 sr/br,audioIO 为 IO 桥
runu32(*)(u8 *ptr)执行一次编码,返回处理字节数;内部经 audioIO 回调取 PCM、写码流

管理层对外函数(encoder_mge.h)

函数签名说明
enc_inputu16 enc_input(void *priv, s16 *buf, u16 len)供编码器从输入缓冲读取 PCM(EN_FILE_IO.input_data 桥)
enc_outputu32 enc_output(void *priv, u8 *data, u16 len)供编码器把码流写入文件(EN_FILE_IO.output_data 桥)
encoder_iovoid encoder_io(u32(*fun)(void *), void *pfile)统一入口:初始化物理通路、创建编码器、启动 ADC
stop_encodevoid stop_encode(void *pfile, u32 dlen)停 ADC、排空缓冲、卸载软中断、文件同步
enc_phy_initvoid enc_phy_init(void)安装编码软中断(软中断 1/2)
kick_encode_apivoid kick_encode_api(void *obj)ADC 数据就绪回调,触发软中断 1
wfil_soft2_isr_hookvoid wfil_soft2_isr_hook(enc_obj *hdl)写文件软中断钩子(当前为空实现,预留喂狗等用途)

(声明见 encoder_mge.h)

故障模式与边界情况

1. 创建失败

encoder_io 中工厂函数 fun(pfile) 返回 0 时,只打印 "encode fail" 且不启动任何硬件——这是合理的防御:编码器对象创建失败(通常因内存不足或文件打开失败)时,绝不让 ADC 空转。

2. 停止时缓冲排空死循环保护

stop_encode 的两个排空循环:

while (0 != cbuf_get_data_size(obj->p_ibuf)) {
    if (obj->enable & B_ENC_FULL) {
        break;
    }
    kick_encode_isr();
    delay(100);
}

如果编码器或写盘异常导致缓冲永不排空,B_ENC_FULL 提供逃生出口,防止系统卡死在 delay 循环中触发看门狗复位。这是"宁可丢数据、不可挂死系统"的嵌入式取舍。

3. 空句柄防御

stop_encode 开头检查 enc_hdl == NULL:若编码从未成功启动(例如创建失败),则只做 rec_phy_suspend() 直接返回,避免解引用空指针。

4. ADPCM 块大小越界

br 超过 2048 会被编码器内部限制为 2048(见 audio_enc_api.h)。上层配置时无需校验,但要知道设置超大块不会报错、只会被钳制——这可能影响码流帧结构与播放兼容性。

5. 写盘慢导致的系统压力

源码注释(wfil_soft2_isr_hook 的 #if 0 块)明确指出:若设备写入速度较慢导致看门狗复位,可通过该钩子在写入前喂狗。即写文件路径是录音链路上最可能成为瓶颈的环节,SDK 预留了钩子函数但默认空实现。

并发与中断上下文注意事项

  • 三段式上下文隔离:ADC ISR、软中断 1(编码)、软中断 2(写文件)工作在不同优先级上下文,共享数据全部经过环形缓冲(cbuffer),生产者/消费者模型天然避免竞争;
  • volatile enable:enc_obj.enable 被 encoder_io(任务上下文)与编码软中断(中断上下文)同时读写,声明为 volatile 防止编译器缓存优化导致状态不可见;
  • 排空循环中的 delay(100):stop_encode 在任务上下文忙等排空,期间系统仍响应中断,因此不会死锁;但每次轮询间隔 100ms,停止延迟最大取决于缓冲深度;
  • 软中断卸载时序:必须先等两个排空循环结束,再 HWI_Uninstall 两个软中断——若先卸载,未排空的数据将永远留在缓冲里丢失。

性能与运行注意

  • 内存段布局:ADC 缓冲放 .rec_data、ANS 缓冲放 .ans_data、kick_encode_api 放 .audio_a.text.cache.L2(L2 cache 文本段),均为高频访问路径,链接脚本的段分配直接影响中断响应与功耗;
  • 缓冲容量:adc_buff[512 * 10 / 4](约 1280 字)、ans_buff[512 * 5 / 4](约 640 字)。在 8kHz/16kHz 采样下,缓冲深度决定"ADC 中断频率"与"编码器批量大小"的平衡:缓冲越大,编码器可批量处理的数据越多,但 RAM 占用与停止延迟也越大;
  • 软中断开销:每个 ADC 数据块触发一次软中断 1,编码完成后触发一次软中断 2,中断频率 = ADC 块率,属可接受的轻量调度开销;
  • 文件同步:stop_encode 末尾的 vfs_ioctl(FS_IOCTL_FILE_SYNC) 是确保录音数据落盘的关键,必须在卸载中断之后、释放句柄之前完成。

扩展点

新增编码格式

  1. 实现 ENC_OPS 的三个方法(need_buf / open / run);
  2. 在 audio_enc_api.h 中仿照 get_ima_code_ops 声明并导出获取函数;
  3. 在上层 enc_obj 创建时把 obj->enc_ops 指向新操作集。

管理层(encoder_api.c)完全不用改动——这就是策略模式带来的扩展性。仓库中 sdk/app/bsp/common/encoder/list/ 下的 a_encoder.c、mp3_encoder.c、ump3_encoder.c 即按此模式组织的编码器实例列表(对应 a_encoder.h、mp3_encoder.h)。

自定义数据源/目的地

EN_FILE_IO.priv 是任意指针,input_data / output_data 可指向任意存储介质。当前实现写 NOR Flash,理论上可替换为 SD 卡、网络等,只需提供相应回调。

写盘前钩子

wfil_soft2_isr_hook(enc_obj *hdl) 为写文件软中断预留钩子,可用于喂狗或统计写入量(当前为空实现,见 encoder_api.c)。

测试与验证路径

仓库中与编码器直接相关的可执行/验证材料包括:

  • 预编译编码库(lib_mp2_encode.a、lib_mp2standard_encode.a)——MP2 系列编码器的二进制交付物,编码结果正确性由库内部保证;
  • sdk/app/bsp/common/encoder/list/ 下各编码器实例源码(a_encoder.c / mp3_encoder.c / ump3_encoder.c)——可通过替换 enc_ops 做格式对比测试;
  • 录音应用 sdk/app/src/voice_toy/toy_record/enc_in_norfs.c——演示"编码器 + NOR Flash 文件"的完整用法,是验证 encoder_io / stop_encode 时序的最直接样例。

实际验证方法:录音后回读文件,用 PC 端解码器(IMA ADPCM / MP2)校验码流帧头与采样率/块大小是否与 ENC_DATA_INFO 配置一致。

相关链接

  • audio_enc_api.h — 编码器 API 契约(EN_FILE_IO / ENC_DATA_INFO / ENC_OPS / 编码器获取函数)
  • encoder_mge.h — 编码器管理对象 enc_obj、状态标志与软中断调度宏
  • encoder_api.c — 编码器管理层实现(初始化/启动/停止/中断回调)
  • audio_enc_api.h 编码器列表 — IMA/MP3/ump3 编码器实例目录
  • 相邻主题:音频解码与 DAC 播放通路、音效处理(sound_effect_api.h)、ANS 降噪模块(ans_api.h)——详见各自目录页
Prev
音频解码器
Next
MIDI 合成与播放