录音应用
语音玩具(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防写满导致文件系统损坏。
扩展点
- 外部存储录音(FAT):
encode_start中增加#elif分支调用fatfs_enc_file_create,MSG_PP中按fs_name增加"fatfs"分支调用fatfs_enc_file_decode即可支持 U 盘录音——头文件已预留接口与rec_file_path字段。 - 采样率可配:将
RECORD_AUDIO_ADC_SR改为dac_sr_read()(源码注释中已留)或按需选择RECORD_ADC_SR_*宏,可在音质与容量间权衡。 - 新增回放模式:在
norfs_decode_type上叠加/翻转其它BIT_*解码能力位,即可扩展更多趣味播放效果。 - 编码格式扩展:新增
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)