录音应用
录音应用(Record Mode)是 AD23N GP-MCU SDK 中负责麦克风录音与录音回放的独立工作模式。它基于 record_mode.c 的主循环调度,通过 Encode_Control 控制结构统一管理编码器生命周期,支持将 MIC 输入以 32kHz 采样率编码为 MP3/A 格式文件,并自动选择 FAT 文件系统(U 盘/SD 卡)或 NOR Flash 文件系统作为存储介质,同时提供录音变速播放等回放能力。
Purpose and Scope
本页面完整记录录音应用(sdk/app/src/mbox_flash/record/)的实现机制,包括:
- 应用入口
record_app()与消息驱动主循环; - 录音启动/停止流程(
encode_start/encode_stop)与 ADC 初始化; - 编码器 API 的选择(MP3 / UMP3 / A 编码);
- 存储介质适配:FATFS(
enc_in_fatfs.c)与 NORFS(enc_in_norfs.c)两条文件创建/解码路径; - 录音回放(
MSG_PP)与变速播放(MSG_REC_SPEED_EN); - 按键消息映射(
record_mode_key.c); - 关键数据结构
Encode_Control与状态机(ENC_NULL/ENC_ING)。
本页面聚焦录音模式本身的实现。以下主题属于其他页面,不在本页展开:工作模式切换与系统菜单管理(见"系统工作模式")、音频解码器框架(见"解码器")、VFS 文件系统与设备管理(见"VFS/设备管理")、电源管理与低功耗流程(见"电源管理")。
Overview
录音应用是 AD23N 平台上典型的"按键触发、消息驱动"的单片机应用。其核心设计思想是:
- 消息驱动:所有操作(开始录音、停止、回放、退出)都以
MSG_*消息的形式进入record_app()的get_msg主循环,应用本身不做阻塞等待,便于与系统事件(如 500ms 定时、文件满、工作模式切换)并发协作。 - 统一控制块:
Encode_Control把设备句柄、文件系统句柄、文件句柄、编码器对象、存储介质序号和编码状态封装在一起,使 FATFS 与 NORFS 两条路径共享同一套启动/停止逻辑。 - 存储介质自动探测:
encode_start()通过device_online()按 U 盘 → SD 卡 → 内置 Flash 的优先级选择录音目标,从而无需用户干预即可在不同硬件配置下录音。 - 编码器可插拔:通过编译宏
ENCODER_MP3_EN/ENCODER_UMP3_EN/ENCODER_A_EN在编译期选择mp3_encode_api/ump3_encode_api/a_encode_api,同一套应用逻辑可产出不同格式的录音文件。
录音文件在 FATFS 上存放于根目录的 /JL_REC 文件夹,文件名为 AD****.mp3(**** 为 4 位递增编号,超过 9999 回绕);在 NORFS 上则通过文件号索引管理。录音过程中 500ms 定时器会输出字符 R 作为运行指示,并在空闲时触发 app_powerdown_deal() 进入低功耗流程。
Architecture
录音应用位于 sdk/app/src/mbox_flash/record/ 目录,其架构如下:
flowchart TD
subgraph sg_App["应用层 (mbox_flash/record)"]
AppMain["record_app()<br/>消息主循环"]
EncodeCtrl["Encode_Control<br/>录音控制块"]
KeyFilter["record_key_msg_filter()<br/>按键过滤"]
end
subgraph sg_Enc["编码层"]
EncoderSel["ENCODER_API<br/>mp3/ump3/a_encode_api"]
EncIOMgr["encoder_io / stop_encode<br/>encoder_mge"]
end
subgraph sg_Fs["存储适配层"]
FatFs["enc_in_fatfs.c<br/>fatfs_enc_file_create/decode"]
NorFs["enc_in_norfs.c<br/>norfs_enc_file_create/decode"]
end
subgraph sg_Sys["系统服务层"]
AudioADC["audio_adc_init_api<br/>ADC_MIC 采集"]
VFS["vfs_mount/vfs_openbypath<br/>vfs_createfile"]
DevMge["device_open/device_online<br/>device_mge"]
Decoder["decoder_init/decoder_io<br/>decoder_api"]
MsgBus["get_msg / post_msg<br/>msg 总线"]
end
AppMain -->|"get_msg 循环"| MsgBus
AppMain --> EncodeCtrl
KeyFilter -->|"MSG_RECODE_START / MSG_PP"| AppMain
AppMain --> EncoderSel
EncoderSel --> EncIOMgr
EncIOMgr -->|"写 PCM 数据"| FatFs
EncIOMgr -->|"写 PCM 数据"| NorFs
AppMain -->|"fs_name: fat/norfs"| FatFs
AppMain -->|"fs_name: fat/norfs"| NorFs
FatFs --> VFS
NorFs --> VFS
FatFs --> DevMge
NorFs --> DevMge
AppMain --> AudioADC
AppMain --> Decoder
Decoder --> VFS
架构分层说明:
- 应用层:
record_app()是唯一入口,运行在独立任务/循环中,负责把系统消息翻译为录音动作;record_key_msg_filter由key_table_sel()挂接,把按键事件翻译为MSG_RECODE_START、MSG_PP等消息。 - 编码层:编译期宏决定
ENCODER_API,通过encoder_mge的encoder_io()创建编码器实例,编码数据由编码器内部回调写入文件句柄。 - 存储适配层:
enc_in_fatfs.c与enc_in_norfs.c分别实现"创建录音文件"与"打开录音文件供解码"两个函数,二者接口签名一致(int (*enc_file_create)(Encode_Control*)/dec_obj *(*enc_file_decode)(Encode_Control*, u16)),因此record_app只需按fs_name字符串选择实现。 - 系统服务层:音频 ADC、VFS、设备管理、解码器与消息总线都是 SDK 全局服务,录音应用是其消费者。
来源:record_mode.c、record_mode.h、enc_in_fatfs.c
应用入口与消息主循环
record_app() 是录音模式的工作模式入口,由系统工作模式调度(切换工作模式时通过 vm_write(VM_INDEX_SYSMODE, &work_mode) 持久化当前模式,退出时由 MSG_CHANGE_WORK_MODE 触发跳转离开)。入口首先完成三项初始化:
void record_app(void)
{
vm_write(VM_INDEX_SYSMODE, &work_mode, sizeof(work_mode));
log_info("record_app\n");
int msg[2];
u32 err = 0;
dec_obj *p_dec_obj = 0;
/* u16 decode_type = (BIT_A | BIT_UMP3 | BIT_SPEED); */
u16 decode_type = (BIT_MP3_ST | BIT_A | BIT_UMP3);
key_table_sel(record_key_msg_filter);
decoder_init();
memset(&record_obj, 0, sizeof(record_obj));
...
}
key_table_sel(record_key_msg_filter)将按键处理表切换为录音模式专用表;decoder_init()初始化解码器框架,供录音回放使用;decode_type声明回放支持的格式为BIT_MP3_ST | BIT_A | BIT_UMP3(即 MP3、A、UMP3),注释中保留了可选的BIT_SPEED变速位;memset(&record_obj, 0, ...)清零全局控制块,enc_status因此初始为ENC_NULL。
主循环是一个无限 while(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);
u32 enc_err = 0;
enc_err = encode_start(&record_obj);
if (enc_err) {
log_error("enc_err 0x%x\n", enc_err);
break;
}
log_info("dev:%d fs_name:%s\n", record_obj.dev_index, record_obj.fs_name);
}
break;
...
}
}
每次循环先调用 get_msg(2, &msg[0]) 获取系统消息并执行 bsp_loop() 维护底层外设;随后按消息类型分派。MSG_RECODE_START 是一个双态开关:正在录音(ENC_ING)时再次触发会停止录音并自动投递 MSG_PP 进入回放;空闲时则先停止可能的解码器、关闭旧文件,再启动新录音。这种"按下即切换录/放"的交互符合录音笔的典型使用习惯。
其余关键消息处理:
MSG_PP:停止编码,按fs_name选择fatfs_enc_file_decode或norfs_enc_file_decode打开刚录好的文件,得到dec_obj交给解码器播放;若文件尚未创建(fs_name为空),打印提示并直接退出。MSG_REC_SPEED_EN:切换decode_type中的BIT_SPEED位,实现录音的变速(倍速)播放模式。MSG_WFILE_FULL:编码文件写满(FATFS 空间不足)时停止录音,保证文件完整性。MSG_WAV_FILE_END / MSG_MP3_FILE_END / MSG_A_FILE_END及对应_ERR:解码播放结束或出错时停止解码并关闭文件,回到待机。MSG_CHANGE_WORK_MODE:goto __record_app_exit退出录音模式。MSG_500MS:周期心跳。录音中每 500ms 输出字符R(便于串口观察);若既不播放也不录音,则执行vm_pre_erase()并调用app_powerdown_deal(0)进入关机前处理,否则调用app_powerdown_deal(1)阻止关机。default:交给ap_handle_hotkey(msg[0])处理热键等未识别消息。
退出标签 __record_app_exit 负责清理:摘除按键表 key_table_sel(NULL);若仍在录音则 encode_stop(),否则停止解码并关闭文件,然后返回系统调度。
录音启动流程
录音启动由静态函数 encode_start(Encode_Control *obj) 完成,分为"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, audio_adc_mic_input_port);
if (0 != err) {
log_info(" audio adc init fail : 0x%x\n");
return err;
}
u32 online_dev = device_online();
u8 index = -1;
for (u8 i = 0; i <= sizeof(recoder_device_tab); i++) {
index = recoder_device_tab[i];
if (online_dev & BIT(index)) {
break;
}
}
const char *p_fs_name = NULL;
int (*enc_file_create)(Encode_Control * obj) = NULL;
if (index > SD0_INDEX) {
p_fs_name = "norfs";//内置flash录音
enc_file_create = norfs_enc_file_create;
} else {
p_fs_name = "fat";//SD卡、U盘录音
enc_file_create = fatfs_enc_file_create;
}
obj->dev_index = index;
strcpy(obj->fs_name, p_fs_name);
err = enc_file_create(obj);
if (0 != err) {
log_error("%s create 0x%x!\n", p_fs_name, err);
return err;
}
record_obj.enc_obj = encoder_io(ENCODER_API, obj->pfile);
/* record_obj.enc_obj = encoder_io(a_encode_api, obj->pfile); */
if (NULL == record_obj.enc_obj) {
return E_ENC_HDL_NULL;
}
obj->enc_status = ENC_ING;
return 0;
}
ADC 初始化:RECORD_AUDIO_ADC_SR 定义为 RECORD_ADC_SR_32K(32kHz),输入源固定为 ADC_MIC,端口为 audio_adc_mic_input_port。注释中保留了 dac_sr_read() 读取 DAC 采样率的备选方案(即录音采样率跟随播放采样率),当前实现固定 32kHz。
介质选择:recoder_device_tab 定义了录音目标设备的优先级:
const u8 recoder_device_tab[3] = {
UDISK_INDEX,
SD0_INDEX,
INNER_FLASH_RW
};
遍历该表,取第一个 device_online() 返回的在线设备作为录音介质。判定规则是:index > SD0_INDEX 说明是 INNER_FLASH_RW(内置 Flash),走 norfs;否则(U 盘或 SD 卡)走 fat。fs_name 字符串同时被 record_app 的 MSG_PP 分支用作选择解码入口的依据,因此它承担了"运行时多态"的标记作用。
编码器绑定:encoder_io(ENCODER_API, obj->pfile) 创建编码器实例,后续 ADC 采集的 PCM 数据经编码器内部任务写入 pfile。若创建失败返回 E_ENC_HDL_NULL。全部成功后才把 enc_status 置为 ENC_ING——这是所有后续逻辑判断"是否正在录音"的唯一依据。
编码器 API 选择
编码器通过编译期宏选择,位于 record_mode.c 顶部:
#include "mp3_encoder.h"
#include "a_encoder.h"
#if ENCODER_MP3_EN
#define ENCODER_API mp3_encode_api
#elif ENCODER_UMP3_EN
#define ENCODER_API ump3_encode_api
#elif ENCODER_A_EN
#define ENCODER_API a_encode_api
#endif
优先级为 ENCODER_MP3_EN > ENCODER_UMP3_EN > ENCODER_A_EN,即标准 MP3 编码器优先,其次 UMP3(杰理自定义 MP3 变体),最后是 A 格式编码器。这允许同一个 record_app 代码在不同固件配置下产出不同格式的录音文件,而 FATFS 侧的文件名模板 AD****.mp3 与 vfs_get_encfolder_info(..., "MP3", ...) 的扩展名过滤也与此对应。整个录音应用受 #if RECORD_MODE_EN 编译开关控制,未使能时整段代码不参与编译。
存储适配:FATFS 路径
enc_in_fatfs.c 实现 U 盘/SD 卡上的录音文件管理,关键常量为:
#define REC_FOLDER_PATH "/JL_REC"
#define REC_FILE_NAME "AD****.mp3"
static char path_buff[25];
文件创建 fatfs_enc_file_create() 的算法要点:
device_open(obj->dev_index)打开设备,vfs_mount(&obj->pfs, obj->device, "fat")挂载 FAT 文件系统;- 拼接目标路径
/JL_REC/AD****.mp3,调用vfs_get_encfolder_info(pfs, "/JL_REC", "MP3", &last_num, &total_rec_file)查询该目录下已存在的 MP3 录音数量与最新文件号; - 若目录为空则
vfs_mk_dir创建/JL_REC目录; - 用
numtostr()把last_num格式化为 4 位数字填入文件名,last_num > 9999时回绕为 0; vfs_openbypath探测该编号文件是否已存在,存在则last_num++并跳回重试(goto __fat_rec_file_name),保证文件名唯一;- 最终
vfs_createfile创建新文件,并把路径保存到obj->rec_file_path供回放使用。
__fat_rec_file_name:
if (last_num > 9999) {
last_num = 0;
}
numtostr(file_num_ptr, last_num, 4);
/* log_info("path_buff:%s\n", path_buff); */
err = vfs_openbypath(obj->pfs, &obj->pfile, path_buff);
if (0 == err) {
log_info("file %d exist!\n", last_num);
vfs_file_close(&obj->pfile);
last_num++;
goto __fat_rec_file_name;
} else {
log_info("REC FILE NAME:%s \n", path_buff);
void *path = path_buff;
err = vfs_createfile(obj->pfs, &obj->pfile, (u32 *)&path[0]);
...
}
文件解码 fatfs_enc_file_decode() 则重新打开设备与挂载文件系统,按 rec_file_path 打开文件后调用 decoder_io(obj->pfile, dec_type, NULL, 0) 生成解码对象,交给录音应用回放。注意解码播放是"重开文件"而不是复用编码句柄,因为编码器写文件与解码器读文件是两个互斥的生命周期。
存储适配:NORFS 路径
enc_in_norfs.c 对应内置 Flash(INNER_FLASH_RW)上的录音。与 FATFS 的"路径 + 文件名"不同,NORFS 通过文件号管理:Encode_Control 联合体中的 rec_file_index 保存最新文件号。norfs_enc_file_create() 负责创建新文件号并初始化编码文件,norfs_enc_file_decode() 按文件号打开文件生成解码对象。两个函数的签名与 FATFS 版本完全一致,因此 record_app 内部通过函数指针 enc_file_create / enc_file_decode 统一调用,无需关心底层介质差异。
录音回放与变速
回放由 MSG_PP 驱动:先 encode_stop(&record_obj) 关闭编码器与 ADC,再 decoder_stop(p_dec_obj, NEED_WAIT) 停止旧的解码器,然后按 fs_name 选择解码入口:
case MSG_PP:
encode_stop(&record_obj);
decoder_stop(p_dec_obj, NEED_WAIT);
dec_obj *(*enc_file_decode)(Encode_Control * obj, u16 dec_type) = NULL;
if (0 == (strcmp(record_obj.fs_name, "fat"))) {
enc_file_decode = fatfs_enc_file_decode;
} else if (0 == (strcmp(record_obj.fs_name, "norfs"))) {
enc_file_decode = norfs_enc_file_decode;
} else {
log_info("record hasn't been started!\n");
break;
}
p_dec_obj = enc_file_decode(&record_obj, decode_type);
if (NULL == p_dec_obj) {
log_info("record file decode fail!\n");
encode_file_fs_close(&record_obj);
}
break;
decode_type 决定解码器支持的解码格式与特性:BIT_MP3_ST(MP3)、BIT_A(A 格式)、BIT_UMP3(UMP3),MSG_REC_SPEED_EN 可切换 BIT_SPEED 实现变速播放。若解码对象创建失败(文件损坏或设备拔出),会立即关闭文件系统与设备,避免资源泄漏。
录音停止与资源释放
encode_stop() 与 encode_file_fs_close() 构成资源释放的完整闭环:
void encode_file_fs_close(Encode_Control *obj)
{
vfs_file_close(&obj->pfile);
vfs_fs_close(&obj->pfs);
device_close(obj->dev_index);
}
static void encode_stop(Encode_Control *obj)
{
if (ENC_ING == obj->enc_status) {
stop_encode(obj->enc_obj, obj->pfile, 0);
encode_file_fs_close(obj);
obj->enc_status = ENC_NULL;
}
audio_adc_off_api();
}
encode_stop() 先调用 stop_encode() 结束编码器(把缓冲区中的残余数据刷入文件,参数 0 表示正常结束),再关闭文件/文件系统/设备,最后关闭 ADC。enc_status 置回 ENC_NULL 使应用回到待机态。该函数在 MSG_RECODE_START(双态切换)、MSG_PP、MSG_WFILE_FULL、MSG_CHANGE_WORK_MODE 退出路径中都会被调用,是录音生命周期收敛的唯一出口。
Core Flow:录音与回放的完整时序
下图展示了从按键触发开始录音、到回放录音文件、再到退出模式的完整时序:
sequenceDiagram
participant Key as 按键扫描(Key)
participant App as record_app()
participant ADC as Audio ADC
participant Enc as 编码器(encoder_io)
participant FS as 存储适配(fat/norfs)
participant VFS as VFS/设备管理
participant Dec as 解码器(decoder)
Note over Key,App: 第一次触发 MSG_RECODE_START(空闲态)
Key->>App: MSG_RECODE_START
App->>App: decoder_stop(旧解码)
App->>App: encode_file_fs_close(旧文件)
App->>ADC: audio_adc_init_api(32K, MIC)
App->>App: device_online() 选择介质
alt 选择 FAT (UDISK/SD0)
App->>FS: fatfs_enc_file_create(obj)
FS->>VFS: device_open + vfs_mount("fat")
FS->>VFS: vfs_get_encfolder_info("/JL_REC","MP3")
FS->>VFS: vfs_createfile("ADxxxx.mp3")
else 选择 NORFS (内置Flash)
App->>FS: norfs_enc_file_create(obj)
end
App->>Enc: encoder_io(ENCODER_API, pfile)
App->>App: enc_status = ENC_ING
ADC-->>Enc: PCM 数据流入编码器
Note over Key,App: 第二次触发 MSG_RECODE_START(录音中)
Key->>App: MSG_RECODE_START
App->>App: encode_stop() -> stop_encode + 关文件 + ADC 关闭
App->>App: post_msg(MSG_PP)
Note over App,Dec: MSG_PP 回放
App->>App: encoder 已停,fs_name 选择解码入口
App->>FS: fatfs/norfs_enc_file_decode(obj)
FS->>VFS: device_open + vfs_mount + 打开文件
FS->>Dec: decoder_io(pfile, dec_type)
Dec-->>App: dec_obj
Note over App,Dec: MSG_MP3_FILE_END / MSG_A_FILE_END 等
App->>App: decoder_stop + encode_file_fs_close
时序要点:
MSG_RECODE_START是双态开关:第一次进入启动录音(ENC_NULL → ENC_ING),第二次进入停止并投递MSG_PP播放;- 录音期间 ADC 以 32kHz 采集 MIC 信号,PCM 数据经编码器写入存储介质上的文件;
- 回放通过"重开文件 + decoder_io"实现,与编码句柄解耦,保证读写互不干扰;
- 解码结束/出错消息(
MSG_*_FILE_END/MSG_*_FILE_ERR)或 500ms 心跳检测都会推动资源释放,确保模式可随时切换。
数据结构:Encode_Control
Encode_Control 是贯穿整个录音应用的核心控制结构,定义于 record_mode.h:
typedef struct _Encode_Control {
char fs_name[8]; //编码文件系统名
void *device; //编码设备句柄
void *pfs; //编码文件系统句柄
void *pfile; //编码文件句柄
void *enc_obj;
union {
u32 rec_file_index; //nor_fs最新文件号
char *rec_file_path; //fat_fs最新文件路径
};
u8 dev_index; //编码设备序号
ENC_STATUS enc_status; //编码器工作状态
} Encode_Control;
| 字段 | 类型 | 作用 |
|---|---|---|
fs_name | char[8] | 文件系统标识,取值 "fat" 或 "norfs",同时作为回放入口分派依据 |
device | void* | 录音设备句柄(由 device_open 返回) |
pfs | void* | 已挂载文件系统句柄(由 vfs_mount 返回) |
pfile | void* | 录音文件句柄(编码写入或解码读取) |
enc_obj | void* | 编码器实例(由 encoder_io 创建) |
rec_file_index / rec_file_path | union | NORFS 用最新文件号;FATFS 用最新文件路径,二者共用存储 |
dev_index | u8 | 设备序号(UDISK_INDEX / SD0_INDEX / INNER_FLASH_RW) |
enc_status | ENC_STATUS | 编码状态:ENC_NULL(空闲)/ ENC_ING(录音中) |
状态枚举与采样率常量:
typedef enum {
ENC_NULL = 0,
ENC_ING,
} ENC_STATUS;
#define RECORD_ADC_SR_8K 8000
...
#define RECORD_ADC_SR_32K 32000
...
#define RECORD_ADC_SR_48K 48000
状态机非常简单——只有空闲与录音两个状态:
stateDiagram-v2
[*] --> ENC_NULL : record_app 入口 memset
ENC_NULL --> ENC_ING : MSG_RECODE_START + encode_start 成功
ENC_ING --> ENC_NULL : encode_stop (MSG_RECODE_START/MSG_PP/MSG_WFILE_FULL/退出)
ENC_ING --> ENC_ING : ADC 持续采集,编码器写入文件
设计意图:用单一 enc_status 字段驱动所有分支判断(MSG_RECODE_START 双态、MSG_500MS 心跳、退出清理),避免引入额外的标志位组合,使状态可被全局追踪和串口观察。
按键消息映射
录音模式通过 key_table_sel(record_key_msg_filter) 挂接按键过滤函数,record_key_msg_filter 在 record_mode_key.c 中实现。IO 按键表 iokey_msg_record_table 定义短按、短按抬起、长按、连按、长按抬起(以及可选的双击/三击)各动作:
const u16 iokey_msg_record_table[][IO_KEY_MAX_NUM] = {
/*短按*/ {IOKEY_RECORD_SHORT},
/*短按抬起*/ {IOKEY_RECORD_SHORT_UP},
/*长按*/ {IOKEY_RECORD_LONG},
/*连按*/ {IOKEY_RECORD_HOLD},
/*长按抬起*/ {IOKEY_RECORD_LONG_UP},
#if (KEY_DOUBLE_CLICK_EN)
/*双击*/ {IOKEY_RECORD_DOUBLE_KICK},
/*三击*/ {IOKEY_RECORD_TRIPLE_KICK},
#endif
};
IO 按键默认配置中:KEY0 短按抬起为 MSG_VOL_UP,长按 KEY0 为 MSG_VOL_UP、KEY1 长按为 MSG_NEXT_WORKMODE(切换工作模式)。而 AD 按键表 才是录音操作的主入口(ADC 按键适用于低成本按键方案):
#define ADKEY_RECORD_SHORT_UP \
/*00*/ MSG_RECODE_START, \
/*01*/ MSG_PP, \
/*02*/ MSG_REC_SPEED_EN, \
/*03*/ MSG_VOL_DOWN, \
/*04*/ MSG_VOL_UP, \
AD 按键的 KEY0 短按抬起 → MSG_RECODE_START(开始/停止录音),KEY1 → MSG_PP(播放/暂停录音),KEY2 → MSG_REC_SPEED_EN(变速切换),KEY3/KEY4 → 音量加减。整个表按"动作(短按/长按/连按等)× 按键序号"组织,宏展开后填充为二维数组,由按键扫描层在过滤函数中查询并投递对应消息。
Configuration Options
录音应用的行为由编译期宏控制,配置项集中在 app_config.h 与 record_mode.h:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
RECORD_MODE_EN | 宏 | 由工程配置 | 录音模式总开关;为 0 时 record_mode.c 整段不编译 |
RECORD_AUDIO_ADC_SR | 宏 | RECORD_ADC_SR_32K (32000) | 录音 ADC 采样率,可选 8K/11K025/12K/16K/22K05/24K/32K/44K1/48K |
ENCODER_MP3_EN | 宏 | 按工程配置 | 使能标准 MP3 编码器,优先级最高 |
ENCODER_UMP3_EN | 宏 | 按工程配置 | 使能 UMP3 编码器,次优先 |
ENCODER_A_EN | 宏 | 按工程配置 | 使能 A 格式编码器,兜底 |
KEY_IO_EN / KEY_AD_EN | 宏 | 按工程配置 | 使能 IO 按键表 / AD 按键表 |
KEY_DOUBLE_CLICK_EN | 宏 | 按工程配置 | 使能双击/三击按键动作表 |
REC_FOLDER_PATH | 宏 | "/JL_REC" | FATFS 录音文件存放目录(enc_in_fatfs.c) |
REC_FILE_NAME | 宏 | "AD****.mp3" | FATFS 录音文件名模板,**** 为 4 位编号 |
运行期设备优先级由 recoder_device_tab 决定(UDISK_INDEX → SD0_INDEX → INNER_FLASH_RW),修改该数组即可调整录音介质的探测顺序。
API Reference
录音模式对外暴露的接口集中在 record_mode.h:
void record_app(void)
录音模式工作模式入口。由系统模式调度调用,内部初始化按键表与解码器后进入 get_msg 主循环,收到 MSG_CHANGE_WORK_MODE 时清理资源并返回。
void encode_file_fs_close(Encode_Control *obj)
关闭录音文件、文件系统与设备句柄。
- 参数
obj:Encode_Control控制块指针。
int fatfs_enc_file_create(Encode_Control *obj)
在 FAT 文件系统(U 盘/SD 卡)上创建录音文件 /JL_REC/ADxxxx.mp3,自动递增编号避免覆盖。
- 参数
obj:控制块;成功后obj->pfile/obj->rec_file_path有效。 - 返回
0成功;E_DEV_NULL(设备打开失败)或挂载/创建错误码。 - 实现见 enc_in_fatfs.c。
dec_obj *fatfs_enc_file_decode(Encode_Control *obj, u16 dec_type)
打开 FATFS 上刚录制的文件并生成解码对象供回放。
- 参数
obj:控制块;dec_type:解码格式位域(BIT_MP3_ST|BIT_A|BIT_UMP3,可含BIT_SPEED)。 - 返回
dec_obj*解码对象;文件/设备异常时返回NULL。
int norfs_enc_file_create(Encode_Control *obj)
在内置 Flash(NORFS)上创建录音文件,使用文件号 rec_file_index 管理。
dec_obj *norfs_enc_file_decode(Encode_Control *obj, u16 dec_type)
按文件号打开 NORFS 录音文件并生成解码对象。
u16 record_key_msg_filter(u8 key_status, u8 key_num, u8 key_type)
按键过滤回调,由 key_table_sel() 挂接。将按键事件(状态、序号、类型)映射为 MSG_RECODE_START / MSG_PP / MSG_REC_SPEED_EN 等消息,返回 NO_MSG 表示忽略。实现见 record_mode_key.c。
内部静态函数
static int encode_start(Encode_Control *obj):初始化 ADC、探测介质、创建文件、绑定编码器并置ENC_ING;失败返回错误码(含E_ENC_HDL_NULL)。static void encode_stop(Encode_Control *obj):stop_encode刷盘、关闭全部句柄、关闭 ADC、置回ENC_NULL。
Failure Modes, Edge Cases & Concurrency
介质不可用(无 U 盘/SD 卡/Flash 异常):device_online() 遍历 recoder_device_tab 后若全部离线,index 会越过数组末尾,导致 obj->dev_index 非法。实际产品中通常保证至少内置 Flash 在线;若 enc_file_create 返回错误,encode_start 直接返回错误码,主循环仅打印 enc_err 0x%x 后 break,不改变 enc_status,系统保持空闲——不会出现"半启动"的中间状态。
设备中途拔出:编码过程中介质拔出会使底层写文件返回错误,编码器最终以 MSG_WFILE_FULL 或文件错误消息上报,record_app 调用 encode_stop 关闭句柄。注意 encode_file_fs_close 假定 pfile/pfs/device 均为有效句柄,若在未启动录音时直接调用(如重复触发 MSG_RECODE_START 且从未成功启动),memset 清零后的空指针由 vfs_file_close 内部容错处理。
文件写满:MSG_WFILE_FULL 触发 encode_stop(),保证已写入内容完整落盘,录音文件可正常回放,不会产生截断的坏文件。
回放失败:decoder_io 返回 NULL(文件损坏/格式不支持)时,MSG_PP 分支调用 encode_file_fs_close 释放资源并打印 record file decode fail!,应用回到待机,不进入死循环。
并发与重入:录音应用是单任务消息驱动模型,所有操作在 record_app 一个循环内串行完成,无多线程竞争;但编码器与解码器内部各有异步任务(encoder_io/decoder_io 创建),因此状态切换点(MSG_RECODE_START、MSG_PP)都先 decoder_stop(..., NEED_WAIT) 或 stop_encode 同步等待内部任务退出,再操作文件句柄,避免读写冲突。
500ms 心跳与低功耗:MSG_500MS 中,若既不录音也不播放,执行 vm_pre_erase() 与 app_powerdown_deal(0) 允许关机;录音或播放中则调用 app_powerdown_deal(1) 阻止关机——这是"录音过程不能因低功耗中断"的关键保证。
Performance & Operational Notes
- 采样率 32kHz:
RECORD_AUDIO_ADC_SR在编码带宽、文件体积与音质间取得平衡;若需更小文件可下调至 8K/16K,需同时确认编码器支持该采样率。 - 文件名回绕:FATFS 路径中文件号超过 9999 回绕为 0,会覆盖最早的同名文件,长期使用需定期清理
/JL_REC目录。 - 调试手段:录音中 500ms 心跳输出
'R'字符;所有关键路径均有log_info/log_error(LOG_TAG "[rec]"),包括设备号、文件系统名、文件路径与错误码,可串口直接观察。 - 内存段定制:
record_mode.c/enc_in_fatfs.c通过#pragma把代码/数据/常量放入专用段(如.record_mode.data.bss),便于在 linker 脚本中为录音功能分配独立 RAM/Flash 区域,避免与其他模式争用大块内存。
Extension Points
- 新增编码格式:在
record_mode.c的#if/#elif链中增加ENCODER_XXX_EN分支即可接入新编码器,无需改动主循环。 - 调整录音介质策略:修改
recoder_device_tab数组内容与顺序,即可改变介质探测优先级或增删候选设备。 - 自定义按键布局:修改
record_mode_key.c中的IOKEY_*/ADKEY_*宏展开表,可自由重新映射录音相关按键。 - UI 接入:
record_app中预留了UI_menu(MENU_MAIN, 0)(当前被注释)与ap_handle_hotkey()热键处理钩子,可接入显示界面与语音提示。
Related Links
- record_mode.c — 录音模式主逻辑
- record_mode.h — 控制块与接口定义
- record_mode_key.c — 录音按键映射
- enc_in_fatfs.c — FATFS 录音文件管理
- enc_in_norfs.c — NORFS 录音文件管理
相关主题页:解码器框架(decoder_api)、编码器管理(encoder_mge)、VFS/设备管理(vfs/device_mge)、工作模式与电源管理,请参见各自目录页。