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

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

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

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

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

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

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

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

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

录音应用

语音玩具(voice-toy)中的录音模式应用,负责将麦克风(ADC_MIC)采集的音频编码为 UMP3 或 A 格式文件写入内置 Flash(norfs),并支持录音回放、变速播放及消息驱动的录音状态机管理。

Purpose and Scope

本页面向 sdk/app/src/voice_toy/toy_record/ 目录下的录音应用模块,说明其完整工作机制:消息驱动的应用主循环、编码器(encoder)生命周期管理、文件系统(vfs/norfs)与设备管理、录音回放解码、按键过滤接入,以及配置宏的开关方式。

本页不涉及以下内容(属于兄弟页面):

  • 语音玩具的整体工作模式切换与主控逻辑(见 voice-toy 主应用页面)
  • 音频编解码器底层实现(如 mp3_encoder、a_encoder)
  • 底层驱动(ADC/DAC、Flash、VFS 文件系统)的具体实现

Overview

toy_record 是杰理(Jieli)AD15N 系列 SDK 中语音玩具(voice-toy)的一个子应用。它与其他玩具应用(如音乐播放、对讲)一样,以消息循环的形式运行在应用层:外部事件(按键、系统定时消息、文件系统事件、编码器事件)被统一转换成 msg 消息投递到 toy_record_app() 的 get_msg() 循环中,由 switch/case 状态机分派处理。

录音应用的核心设计要点:

  • 单一全局状态对象 record_obj(Encode_Control 类型)保存录音会话的全部上下文:编码文件系统名、设备句柄、文件句柄、最新文件号/路径、编码设备序号、编码器状态。
  • 双态编码状态机:ENC_NULL(空闲)与 ENC_ING(录音中),由 MSG_RECODE_START 消息在两者之间切换——按下录音键一次开始录音,再次按下结束录音并自动进入回放。
  • 先停后启的策略:每次开始录音前先停止正在播放的解码器并关闭旧文件,避免 ADC 采集与 DAC 播放争抢音频通路。
  • 编译期格式选择:通过 ENCODER_UMP3_EN / ENCODER_A_EN 决定录音编码格式(UMP3 或 A 格式),同一套状态机代码适配两种编码器。
  • 与按键表解耦:通过 key_table_sel(record_key_msg_filter) 将按键事件过滤函数注册进按键框架,实现"按键 → 消息 → 应用"的松耦合链路。

录音文件存放在内置 Flash(INNER_FLASH_RW)的 norfs 文件系统中;头文件中同时声明了 fatfs_enc_file_create / fatfs_enc_file_decode,为外部 U 盘/FAT 文件系统录音预留了接口。

Architecture

flowchart TD
    subgraph sg_Input["输入层"]
        Key["按键 key_table_sel<br/>record_key_msg_filter"]
        SysMsg["系统消息<br/>MSG_500MS / MSG_CHANGE_WORK_MODE"]
        FileEvent["文件事件<br/>MSG_WFILE_FULL / *_FILE_END"]
    end

    subgraph sg_App["录音应用层 toy_record_app"]
        Loop["get_msg 消息循环<br/>switch/case 状态机"]
        EncodeCtrl["Encode_Control record_obj<br/>录音会话上下文"]
        EncStart["encode_start<br/>ADC 初始化 + 编码器挂载"]
        EncStop["encode_stop<br/>stop_encode + 关闭资源"]
        DecPlay["norfs_enc_file_decode<br/>回放录音文件"]
    end

    subgraph sg_Audio["音频层"]
        Adc["audio_adc_init_api<br/>ADC_MIC @24K"]
        Encoder["encoder_io<br/>ump3_encode_api / a_encode_api"]
        Decoder["decoder_api<br/>BIT_A|BIT_UMP3|BIT_SPEED"]
    end

    subgraph sg_Storage["存储层"]
        Vfs["vfs_file_close / vfs_fs_close"]
        Dev["device_close<br/>INNER_FLASH_RW"]
        NorFs["norfs 文件系统"]
    end

    Key -->|"MSG_RECODE_START / MSG_PP / MSG_REC_SPEED_EN"| Loop
    SysMsg --> Loop
    FileEvent --> Loop
    Loop --> EncodeCtrl
    Loop --> EncStart
    Loop --> EncStop
    Loop --> DecPlay
    EncStart --> Adc
    EncStart --> Encoder
    EncStop --> Encoder
    DecPlay --> Decoder
    EncStart --> Dev
    EncStart --> Vfs
    EncStop --> Vfs
    Vfs --> NorFs
    Dev --> NorFs

上图展示了录音应用的分层结构:输入层(按键、系统定时、文件系统事件)统一投递消息到 应用层 的 toy_record_app 消息循环;应用层通过 Encode_Control 上下文驱动音频层的 ADC 采集与编码器/解码器;编码产生的文件经由存储层的 vfs/device 接口写入 norfs 文件系统。所有路径均以 record_obj 为单一事实来源(single source of truth),避免多个模块各自维护录音状态。

关键数据结构

Encode_Control 是录音会话的载体(定义见 toy_record.h):

typedef struct _Encode_Control {
    char fs_name[8];            //编码文件系统名
    void *device;               //编码设备句柄
    void *pfs;                  //编码文件系统句柄
    void *pfile;                //编码文件句柄
    union {
        u32 rec_file_index;     //nor_fs最新文件号
        char *rec_file_path;    //fat_fs最新文件路径
    };
    u8 dev_index;               //编码设备序号
    ENC_STATUS enc_status;      //编码器工作状态
} Encode_Control;

Source: toy_record.h

设计意图:

  • fs_name 用于运行时区分 "norfs" 与(预留的)FAT 文件系统,MSG_PP 处理中据此选择 norfs_enc_file_decode 还是 fatfs_enc_file_decode。
  • rec_file_index 与 rec_file_path 组成 union:norfs 按文件号递增管理录音文件,FAT 则按路径管理,同一结构兼容两种存储语义。
  • enc_status(ENC_NULL/ENC_ING)是录音状态机的核心判据,所有录音相关消息处理都先检查它。

核心流程:消息驱动的录音状态机

toy_record_app() 是录音应用的唯一入口,其主体是一个无限消息循环(见 toy_record.c):

void toy_record_app(void)
{
    log_info("toy_record mode\n");
    int msg[2];
    u32 err = 0;
    dec_obj *p_dec_obj = 0;
    u16 norfs_decode_type = BIT_A | BIT_UMP3 | BIT_SPEED;
    key_table_sel(record_key_msg_filter);
    decoder_init();

    memset(&record_obj, 0, sizeof(record_obj));

    while (1) {
        err = get_msg(2, &msg[0]);
        bsp_loop();
        if (MSG_NO_ERROR != err) {
            msg[0] = NO_MSG;
            log_info("get msg err 0x%x\n", err);
        }

        switch (msg[0]) {
        case MSG_RECODE_START:
            if (ENC_ING == record_obj.enc_status) {
                /* 结束录音并播放录音 */
                encode_stop(&record_obj);
                post_msg(1, MSG_PP);
            } else {
                /* 开始录音 */
                decoder_stop(p_dec_obj, NEED_WAIT);
                encode_file_fs_close(&record_obj);
                encode_start(&record_obj);
                log_info("dev:%d fs_name:%s\n", record_obj.dev_index, record_obj.fs_name);
            }
            break;
        case MSG_PP:
            encode_stop(&record_obj);
            decoder_stop(p_dec_obj, NEED_WAIT);
            if (0 == (strcmp(record_obj.fs_name, "norfs"))) {
                p_dec_obj = norfs_enc_file_decode(&record_obj, norfs_decode_type);
            } else {
                log_info("record hasn't been started!\n");
                break;
            }
            if (NULL == p_dec_obj) {
                log_info("record file decode fail!\n");
                encode_file_fs_close(&record_obj);
            }
            break;
        ...
        }
    }
}

Source: toy_record.c

消息分派逻辑

消息触发场景处理行为
MSG_RECODE_START录音键按下录音中→停止录音并投递 MSG_PP;空闲→停解码器、关旧文件、开始新录音
MSG_PP录音结束 / 播放键停止编码,关闭文件,按 fs_name 选择解码路径回放刚录的文件
MSG_REC_SPEED_EN变速键切换 norfs_decode_type 中的 BIT_SPEED 位,实现正常/变速回放
MSG_WFILE_FULL文件系统写满立即停止编码,防止数据丢失
MSG_WAV_FILE_END / MSG_MP3_FILE_END / MSG_A_FILE_END回放结束停止解码器并关闭编码文件
MSG_CHANGE_WORK_MODE切换玩具模式goto __record_app_exit 退出录音应用
MSG_500MS系统 500ms 定时录音中打印 'R' 指示灯字符;空闲且未播放时擦除 VM 并进入系统空闲
其它—交给 common_msg_deal(&msg[0]) 统一处理

状态转换

stateDiagram-v2
    [*] --> ENC_NULL: 应用启动 memset(record_obj)
    ENC_NULL --> ENC_ING: MSG_RECODE_START → encode_start 成功
    ENC_ING --> ENC_NULL: MSG_RECODE_START → encode_stop + post MSG_PP
    ENC_ING --> ENC_NULL: MSG_WFILE_FULL → encode_stop
    ENC_NULL --> ENC_NULL: MSG_PP → 回放录音 (decoder 播放)
    ENC_NULL --> [*]: MSG_CHANGE_WORK_MODE → 退出应用

ENC_ING 表示 ADC 采集与编码器正在工作;MSG_RECODE_START 是唯一的录音启停触发器,且同一消息依据当前状态执行相反动作(toggle 语义)——这是"按一次录、再按一次停并回放"交互的基础。

录音启停的关键实现

encode_start(toy_record.c)按固定顺序完成:初始化 ADC → 选择编码器 → 创建文件 → 挂载编码器 → 置状态:

static int encode_start(Encode_Control *obj)
{
    u32 sr = RECORD_AUDIO_ADC_SR;
    /* u32 sr = dac_sr_read(); */
    /* log_info("adc sr:%d\n", sr); */
    int err = audio_adc_init_api(sr, ADC_MIC, BIT(1));
    if (0 != err) {
        log_info(" audio adc init fail : 0x%x\n");
        return err;
    }

#if ENCODER_UMP3_EN
    obj->dev_index = INNER_FLASH_RW;//内置flash录音
    strcpy(obj->fs_name, "norfs");
    err = norfs_enc_file_create(obj);
    if (0 != err) {
        log_info("vfs create 0x%x!\n", err);
        return err;
    }
    encoder_io(ump3_encode_api, obj->pfile);
#elif ENCODER_A_EN
    obj->dev_index = INNER_FLASH_RW;//内置flash录音
    strcpy(obj->fs_name, "norfs");
    err = norfs_enc_file_create(obj);
    if (0 != err) {
        log_info("vfs create 0x%x!\n", err);
        return err;
    }
    encoder_io(a_encode_api, obj->pfile);
#else
    log_info("no ump3 & a format encoder!\n");
    return -1;
#endif

    obj->enc_status = ENC_ING;
    return 0;
}

Source: toy_record.c

设计要点:

  • 采样率固定为 RECORD_AUDIO_ADC_SR(即 RECORD_ADC_SR_24K = 24000Hz,见 toy_record.h),注释中保留了 dac_sr_read() 的备选方案——若需要录音与 DAC 播放采样率一致,可切换该行。
  • ADC_MIC 指定麦克风通道,BIT(1) 使能对应 ADC 通道。
  • 编译期通过 ENCODER_UMP3_EN / ENCODER_A_EN 二选一挂载编码器,encoder_io() 把编码器绑定到 obj->pfile 输出句柄;两个分支都不使能时返回 -1,应用保持空闲。
  • 只有在所有初始化成功后,enc_status 才置为 ENC_ING,保证状态与资源状态一致。

encode_stop 是对称的收尾(toy_record.c):

static void encode_stop(Encode_Control *obj)
{
    if (ENC_ING == obj->enc_status) {
        stop_encode(obj->pfile, 0);
        encode_file_fs_close(obj);
        obj->enc_status = ENC_NULL;
    }
    audio_adc_off_api();
}

Source: toy_record.c

encode_stop 是幂等的:仅当 ENC_ING 时才真正停止编码器、关闭文件并复位状态,随后无条件关闭 ADC。MSG_WFILE_FULL、MSG_RECODE_START(录音中再次触发)都会走这里,保证任何路径退出录音都不会泄漏资源或遗留"伪录音中"状态。

录音回放流程

MSG_PP 分支演示了"录完即播"的闭环:先 encode_stop 落盘,再 decoder_stop(p_dec_obj, NEED_WAIT) 等待旧解码器退出,然后判断 fs_name:

  • 为 "norfs" 时调用 norfs_enc_file_decode(&record_obj, norfs_decode_type),解码类型为 BIT_A | BIT_UMP3 | BIT_SPEED(支持 A 格式、UMP3 及变速)。
  • 否则打印 "record hasn't been started!" 并跳过(此时 fs_name 仍为上次 memset 后的空值或旧值)。
  • 解码对象创建失败(返回 NULL)时关闭文件资源并打印 "record file decode fail!"。

MSG_REC_SPEED_EN 通过翻转 norfs_decode_type 的 BIT_SPEED 位,在同一份录音文件上切换正常/变速回放,无需重新编码,是一种低成本的"趣味变速"实现。

端到端时序:一次完整的"录音→回放"会话

sequenceDiagram
    participant U as 用户
    participant K as 按键框架<br/>record_key_msg_filter
    participant A as toy_record_app<br/>消息循环
    participant ADC as audio_adc
    participant ENC as encoder<br/>(ump3/a_encode)
    participant FS as vfs/norfs<br/>INNER_FLASH_RW

    U->>K: 第一次按下录音键
    K->>A: MSG_RECODE_START
    A->>A: enc_status==ENC_NULL → 走开始分支
    A->>A: decoder_stop(旧解码器)
    A->>A: encode_file_fs_close(旧文件)
    A->>ADC: audio_adc_init_api(24K, ADC_MIC)
    A->>FS: norfs_enc_file_create
    A->>ENC: encoder_io(ump3_encode_api, pfile)
    A->>A: enc_status = ENC_ING
    ENC->>FS: 持续写入录音数据

    U->>K: 第二次按下录音键
    K->>A: MSG_RECODE_START
    A->>A: enc_status==ENC_ING → 走停止分支
    A->>ENC: stop_encode(pfile, 0)
    A->>FS: encode_file_fs_close
    A->>A: enc_status = ENC_NULL; post_msg(MSG_PP)
    A->>ADC: audio_adc_off_api
    A->>A: MSG_PP → norfs_enc_file_decode
    Note over A: decoder 回放刚录制的文件<br/>(BIT_A|BIT_UMP3|BIT_SPEED)
    A->>A: MSG_A_FILE_END / MSG_MP3_FILE_END → 收尾

文件系统与设备管理

录音文件的生命周期由 encode_file_fs_close 统一收口(toy_record.c):

void encode_file_fs_close(Encode_Control *obj)
{
    vfs_file_close(&obj->pfile);
    vfs_fs_close(&obj->pfs);
    device_close(obj->dev_index);
}

Source: toy_record.c

关闭顺序为 文件 → 文件系统 → 设备(由内到外),与创建顺序相反;obj->pfile、obj->pfs 均为 void* 句柄,由 vfs 层按 fs_name 对应的文件系统类型解释。该函数被 encode_stop、MSG_PP 失败分支、MSG_*_FILE_END 及应用退出路径多处调用,是防句柄泄漏的统一出口。

norfs_enc_file_create / fatfs_enc_file_create 声明于 toy_record.h:

int norfs_enc_file_create(Encode_Control *obj);
dec_obj *norfs_enc_file_decode(Encode_Control *obj, u16 dec_type);
int fatfs_enc_file_create(Encode_Control *obj);
dec_obj *fatfs_enc_file_decode(Encode_Control *obj, u16 dec_type);

Source: toy_record.h

create 系列负责在存储介质上建立新录音文件并回填 obj->device/pfs/pfile/rec_file_index;decode 系列返回 dec_obj* 解码对象供 decoder 播放。当前实现(encode_start)只走 norfs 路径(内置 Flash),FAT 接口为外部存储扩展预留。rec_file_index(norfs 最新文件号)由 Encode_Control 的 union 承载,FAT 场景则复用为 rec_file_path 指针。

按键接入与消息过滤

应用通过 key_table_sel(record_key_msg_filter) 注册按键过滤回调(见 toy_record.c),退出时用 key_table_sel(NULL) 注销(toy_record.c)。过滤函数原型声明在 toy_record.h:

extern u16 record_key_msg_filter(u8 key_status, u8 key_num, u8 key_type);

Source: toy_record.h

该回调(实现在 toy_record_key.c)接收按键状态、键号和键类型,返回按键事件对应的消息(如 MSG_RECODE_START、MSG_REC_SPEED_EN、MSG_PP),由按键框架投递进应用消息队列。这一设计的价值在于:

  • 应用与按键硬件解耦:录音应用只关心"收到了什么消息",不关心"哪个 GPIO 被按下";
  • 可组合性:同一按键表机制被 voice-toy 的其它子应用复用,切换应用时只需更换过滤函数;
  • 退出安全:__record_app_exit 先注销按键表再清理资源,避免模式切换期间按键回调指向已释放的应用上下文。

Usage Examples

示例 1:录音模式完整消息循环(核心骨架)

while (1) {
    err = get_msg(2, &msg[0]);
    bsp_loop();
    if (MSG_NO_ERROR != err) {
        msg[0] = NO_MSG;
        log_info("get msg err 0x%x\n", err);
    }

    switch (msg[0]) {
    case MSG_RECODE_START:
        if (ENC_ING == record_obj.enc_status) {
            /* 结束录音并播放录音 */
            encode_stop(&record_obj);
            post_msg(1, MSG_PP);
        } else {
            /* 开始录音 */
            decoder_stop(p_dec_obj, NEED_WAIT);
            encode_file_fs_close(&record_obj);
            encode_start(&record_obj);
            log_info("dev:%d fs_name:%s\n", record_obj.dev_index, record_obj.fs_name);
        }
        break;
    ...

Source: toy_record.c

注意 MSG_RECODE_START 的 toggle 语义:同一消息依据 enc_status 决定"开始"还是"停止+回放",这是玩具"按一下录、再按一下播"交互的核心。

示例 2:变速回放开关

case MSG_REC_SPEED_EN:
    if (norfs_decode_type & BIT_SPEED) {
        log_info("record normal mode \n");
        norfs_decode_type &= ~BIT_SPEED;
    } else {
        log_info("record speed mode \n");
        norfs_decode_type |= BIT_SPEED;
    }
    break;

Source: toy_record.c

通过按位翻转 BIT_SPEED 实现正常/变速两种回放模式,且每次进入 toy_record_app 时 norfs_decode_type 默认初始化为 BIT_A | BIT_UMP3 | BIT_SPEED(变速模式)。若需默认正常速,可修改初始化值去掉 BIT_SPEED。

示例 3:录音中 500ms 心跳与空闲处理

case MSG_500MS:
    if (record_obj.enc_status == ENC_ING) {
        log_char('R');
    }
    if ((MUSIC_PLAY != get_decoder_status(p_dec_obj)) && \
        (record_obj.enc_status == ENC_NULL)) {
        vm_pre_erase();
        sys_idle_deal(-2);
    } else {
    }

Source: toy_record.c

录音中每 500ms 打印 'R' 字符作为指示灯/调试输出;当解码器未处于 MUSIC_PLAY 且录音空闲时,擦除 VM 预置区域并调用 sys_idle_deal(-2) 进入系统低功耗空闲。-2 表示无条件空闲处理,是玩具设备节电的关键路径。

Configuration Options

录音应用的配置全部为编译期宏,定义在头文件与编译选项中:

宏类型默认值说明
RECORD_MODE_EN宏开关0(未定义)录音应用整体使能开关,toy_record.c 的整个实现体被 #if defined(RECORD_MODE_EN) && (RECORD_MODE_EN) 包裹
RECORD_AUDIO_ADC_SR采样率常量RECORD_ADC_SR_24K (24000)录音 ADC 采样率,定义于 toy_record.c,取值来自 toy_record.h 的 RECORD_ADC_SR_* 系列
ENCODER_UMP3_EN宏开关依编译配置使能 UMP3 编码器时录音格式为 UMP3,encode_start 优先走该分支
ENCODER_A_EN宏开关依编译配置使能 A 格式编码器时录音格式为 A 格式,#elif 分支
RECORD_ADC_SR_8K ~ RECORD_ADC_SR_24K采样率常量8000/11025/12000/16000/22050/24000可选的 ADC 采样率,RECORD_AUDIO_ADC_SR 可改为其中任意值(见 toy_record.h)

采样率宏定义(toy_record.h):

#define RECORD_ADC_SR_8K        8000
#define RECORD_ADC_SR_11K025    11025
#define RECORD_ADC_SR_12K       12000
#define RECORD_ADC_SR_16K       16000
#define RECORD_ADC_SR_22K05     22050
#define RECORD_ADC_SR_24K       24000

Source: toy_record.h

配置注意点:

  • RECORD_MODE_EN 关闭时整个 toy_record.c 实现不参与编译(#endif 在文件末尾),头文件声明仍可用,调用方需自行用 #if 保护。
  • ENCODER_UMP3_EN 与 ENCODER_A_EN 同时使能时按 #if/#elif 顺序只选 UMP3;两者都不使能时 encode_start 返回 -1,录音不可用——这是编译期防呆设计。
  • 采样率选择影响录音文件大小与音质:24K 音质最佳但文件更大;玩具低存储场景可下调至 8K/12K。

API Reference

应用入口与生命周期

void toy_record_app(void)

录音应用主入口,由 voice-toy 模式管理在进入录音模式时调用。内部注册按键表、初始化 decoder、进入消息循环;收到 MSG_CHANGE_WORK_MODE 时退出循环、注销按键表、清理编码/解码资源后返回。

注意:该函数不返回(除模式切换),由调用方保证调用后接管控制权。

void encode_file_fs_close(Encode_Control *obj)

按 文件→文件系统→设备 的顺序关闭编码会话的全部句柄。任何结束录音的路径都应调用它以释放资源。

参数:obj — 录音会话控制块。 返回:无。注意:函数内部假定句柄有效;未初始化对象调用可能导致空指针解引用(正常流程中由 enc_status 门控保证先创建后关闭)。

文件创建与回放

int norfs_enc_file_create(Encode_Control *obj)

在 norfs(内置 Flash)创建新的录音文件,回填 obj->device/pfs/pfile/rec_file_index。

返回:0 成功;非 0 为 vfs 错误码(调用处打印 "vfs create 0x%x!")。

dec_obj *norfs_enc_file_decode(Encode_Control *obj, u16 dec_type)

打开最近录制的 norfs 文件并创建解码对象。

参数:dec_type — 解码类型位掩码,如 BIT_A | BIT_UMP3 | BIT_SPEED。 返回:dec_obj* 解码对象;失败返回 NULL(调用处打印 "record file decode fail!" 并关闭文件)。

int fatfs_enc_file_create(Encode_Control *obj) / dec_obj *fatfs_enc_file_decode(Encode_Control *obj, u16 dec_type)

FAT 文件系统版本的创建/回放接口,为外部存储(U 盘等)预留;当前 encode_start 未调用。

内部静态函数

函数可见性作用
static int encode_start(Encode_Control *obj)文件内ADC 初始化 → 选择编码器 → 建文件 → 挂编码器 → 置 ENC_ING
static void encode_stop(Encode_Control *obj)文件内stop_encode → 关文件 → 置 ENC_NULL → audio_adc_off_api

外部协作接口

符号声明位置作用
u16 record_key_msg_filter(u8 key_status, u8 key_num, u8 key_type)toy_record.h,实现于 toy_record_key.c按键过滤回调,返回按键对应消息
void sys_idle_deal(u32 usec)toy_record.h(extern)系统空闲处理,MSG_500MS 中用于低功耗
void toy_record_app(void)toy_record.h模式入口(见上)

失败模式与边界情况

  • ADC 初始化失败(audio_adc_init_api 返回非 0):encode_start 提前返回错误码,不创建文件、不置 ENC_ING,录音静默失败,仅日志提示 "audio adc init fail"。不会产生半成品文件。
  • 文件创建失败(norfs_enc_file_create 非 0):同样提前返回,enc_status 保持 ENC_NULL,避免"无句柄却标记录音中"的不一致。
  • 编码器未编译:ENCODER_UMP3_EN 与 ENCODER_A_EN 均未使能时返回 -1,录音功能不可用但有明确日志。
  • Flash 写满(MSG_WFILE_FULL):encode_stop 立即停止编码并关闭文件,已录部分保留可回放;不会覆盖旧文件或无限重试。
  • 回放解码失败(norfs_enc_file_decode 返回 NULL):关闭文件资源,应用回到空闲,等待下一次按键。
  • 未开始录音就播放(fs_name 非 "norfs"):MSG_PP 打印 "record hasn't been started!" 并跳过回放,防止空文件解码。
  • 模式切换中断录音:MSG_CHANGE_WORK_MODE 触发 goto 退出;退出前检查 enc_status——录音中走 encode_stop,否则 decoder_stop + encode_file_fs_close,保证两种状态都能干净收尾(toy_record.c)。

并发与一致性

  • 单线程消息模型:所有状态变更都在 toy_record_app 的循环内串行执行,无锁设计;record_obj 只被该循环访问,天然免竞争。
  • 先停后启的串行化:开始录音前 decoder_stop(p_dec_obj, NEED_WAIT) 阻塞等待解码器退出,避免 ADC 录音与 DAC 播放同时占用音频通路导致的采样率/通道冲突。
  • MSG_500MS 空闲判定:用 get_decoder_status(p_dec_obj) != MUSIC_PLAY && enc_status == ENC_NULL 双条件判断"可进入空闲",防止录音中或播放中被 sys_idle_deal 打断。
  • 幂等收尾:encode_stop 与 encode_file_fs_close 由 enc_status 门控/多次调用路径设计保证重复调用安全(stop_encode 仅在 ENC_ING 时执行)。

性能与功耗说明

  • 录音路径:24K 采样率 + 编码器实时写入 norfs;MSG_500MS 心跳仅打印一个字符,开销极小,避免高频日志干扰编码实时性。
  • 功耗:空闲且未播放时通过 sys_idle_deal(-2) 进入系统空闲,是玩具电池续航的关键;录音期间 audio_adc_off_api 在每次 encode_stop 后调用,确保 ADC 不空转。
  • 存储:录音文件按 norfs 文件号递增管理(rec_file_index),多次录音产生多个文件;MSG_WFILE_FULL 防写满导致文件系统损坏。

扩展点

  1. 外部存储录音(FAT):encode_start 中增加 #elif 分支调用 fatfs_enc_file_create,MSG_PP 中按 fs_name 增加 "fatfs" 分支调用 fatfs_enc_file_decode 即可支持 U 盘录音——头文件已预留接口与 rec_file_path 字段。
  2. 采样率可配:将 RECORD_AUDIO_ADC_SR 改为 dac_sr_read()(源码注释中已留)或按需选择 RECORD_ADC_SR_* 宏,可在音质与容量间权衡。
  3. 新增回放模式:在 norfs_decode_type 上叠加/翻转其它 BIT_* 解码能力位,即可扩展更多趣味播放效果。
  4. 编码格式扩展:新增 ENCODER_XXX_EN 宏与对应 xxx_encode_api 分支,复用现有 encode_start/encode_stop 生命周期。

测试说明

仓库中未发现针对 toy_record 的独立单元测试文件;该模块的验证主要依赖:

  • 编译期宏组合验证(RECORD_MODE_EN、ENCODER_UMP3_EN/ENCODER_A_EN 的开关组合);
  • 板级功能验证:按键两次触发"录→停→回放"闭环、变速切换、Flash 写满行为、模式切换中断录音的资源回收。

Related Links

  • 录音应用头文件 toy_record.h
  • 录音应用实现 toy_record.c
  • 录音按键过滤 toy_record_key.c
  • 语音玩具主控应用(toy_main,负责模式切换与 MSG_CHANGE_WORK_MODE 投递)
  • 音频编码器模块(mp3_encoder / a_encoder)与解码器 API(decoder_api)
  • VFS 文件系统与设备管理(vfs / device_mge / device)
Prev
MIDI 乐器模式
Next
待机、电源管理与 USB 从机