音频解码与 APA 语音播报
AC82N GP-MCU SDK 应用层的音频子系统,涵盖文件音频解码框架(if_decoder_ctrl 解码器操作集)、语音提示音(TONE)播放接口、血压数值语音拼读逻辑,以及 APA(音频功率放大器)PWM/DAC 输出驱动,构成从解码数据源到扬声器发声的完整链路。
Purpose and Scope
本页面向 AC82N 血压计 MCU SDK 的音频解码与语音播报能力,覆盖:
- 解码控制框架:
decoder_ops_t解码器操作集、if_decoder_io数据通道、播放控制命令; - 语音播报:
voice.c的 TONE PLAYER API、数字拆解拼读(百/十/个位)逻辑; - 语音资源:
decode.c中tone_table[]提示音文件表; - 音频输出:
apa_api.h中 APA 驱动接口(apa_driver、APA_INF、ioctrl 命令)。
不属于本页范围(由其他目录页承载):USB UAC 音频设备枚举与流传输(参见 uac_audio.h / uac_stream.h)、具体解码器编解码算法实现、DAC 模拟电路细节。本页聚焦于应用层如何组织解码与播报,以及 APA 驱动提供的对外契约。
Overview
在 AC82N 血压计方案中,音频能力被抽象为三层协作模型:
- 播报发起层(应用):当测量完成、按键触发或事件发生时,应用调用 TONE 播放 API,按索引或按文件播放提示音;播报血压数值时,将数字拆解为"数字 + 单位"的音素序列。
- 解码控制层(框架):
if_decoder_ctrl.h定义统一的解码器操作集decoder_ops_t与数据通道if_decoder_io。解码器(如 UMP3)通过操作集暴露 open/run/format_check 等能力,框架通过io回调从存储介质取数据、向输出侧送 PCM 数据。 - 输出驱动层(APA):解码得到的 PCM 数据经
apa_drv的open/vol_ctrl/ioctrl送到 APA 模块,以 PWM 调制方式驱动扬声器;APA_INF配置采样率、PWM 模式与时钟。
这一分层设计的目的:解码算法与播报业务解耦、数据源与输出设备解耦。播报逻辑只关心"播哪个 tone 文件",不关心文件来自 flash 还是文件系统;输出驱动只关心"送多少 PCM 数据",不关心数据是语音、音乐还是提示音。
关键术语:
- TONE:一段短音频资源(提示音),如"请坐"、"测量结束"、数字"0~9"、"百/十"、"毫米汞柱"等,见
tone_table[]; - APA:Audio Power Amplifier,SDK 中通过
apa_drv驱动的音频功率放大/输出模块,支持 PWM 或 IO 两种输出模式; - decoder_ops_t:解码器能力抽象,
get_ump3_ops()返回 UMP3 解码器实例。
Architecture
下图展示音频解码与语音播报的分层架构及各层间的数据/控制流:
flowchart TD
subgraph sg_App["应用层 (sdk/apps)"]
App["应用事件/测量流程"]
Voice["voice.c TONE PLAYER<br/>a_player_tonebyindex / 数字拼读"]
ToneTable["decode.c tone_table[]<br/>提示音资源表"]
end
subgraph sg_Decode["解码控制层 (if_decoder_ctrl.h)"]
DecoderOps["decoder_ops_t<br/>open / run / format_check / get_playtime"]
DecIO["if_decoder_io<br/>input / output / check_buf"]
UMP3["get_ump3_ops()<br/>UMP3 解码器"]
end
subgraph sg_APA["音频输出层 (apa_api.h)"]
ApaDrv["apa_drv<br/>open / close / ioctrl / vol_ctrl"]
ApaInf["APA_INF<br/>采样率 / PWM 模式 / 时钟"]
Hw["硬件: DAC/PWM → 扬声器"]
end
App -->|"播报请求"| Voice
Voice -->|"tone 索引/文件"| ToneTable
ToneTable -->|"音频文件数据"| DecoderOps
DecoderOps -->|"驱动"| UMP3
UMP3 -->|"PCM 输出"| DecIO
Voice -->|"读文件数据"| DecIO
DecoderOps -->|"PCM 数据"| ApaDrv
ApaDrv -->|"配置"| ApaInf
ApaInf -->|"PWM/音量控制"| Hw
架构要点说明:
voice.c与decode.c同属sdk/apps/common/decode目录,voice.c负责"播什么"(tone 序列),decode.c负责"怎么解码"(驱动解码器、管理播放列表);if_decoder_io是解码器与数据源之间的桥梁:input支持同步/异步两种读取方式(type0/1),output把解码后的数据推给 APA 等输出端;decoder_ops_t通过need_dcbuf_size/need_rdbuf_size/need_bpbuf_size让上层按需分配工作缓冲区,解码器自身不持有内存所有权,便于在 RAM 受限的 MCU 上复用缓冲;- APA 驱动是全局单例
apa_drv(const指针),以ioctrl(parm, cmd)命令字方式扩展控制面(音量表、采样率、中断使能等),保持函数指针接口稳定。
音频解码框架详解
解码器操作集 decoder_ops_t
if_decoder_ctrl.h 定义了所有解码器必须实现的操作集合,这是 SDK 支持多格式音频的核心抽象:
typedef struct __audio_decoder_ops {
char *name; ///< 解码器名称
u32(*open)(void *work_buf, const struct if_decoder_io *decoder_io, u8 *bk_point_ptr); ///<打开解码器
u32(*format_check)(void *work_buf); ///<格式检查
u32(*run)(void *work_buf, u32 type); ///<主循环
dec_inf_t *(*get_dec_inf)(void *work_buf); ///<获取解码信息
u32(*get_playtime)(void *work_buf); ///<获取播放时间
u32(*get_bp_inf)(void *work_buf); ///<获取断点信息
u32(*need_dcbuf_size)(); ///<获取解码需要的buffer
u32(*need_rdbuf_size)(); ///<获取解码读数buf的读文件缓存buf的大小
u32(*need_bpbuf_size)(); ///<获取保存断点信息需要的buffer
void (*set_step)(void *work_buf, u32 step); ///<设置快进快进步长
void (*set_err_info)(void *work_buf, u32 cmd, u8 *ptr, u32 size); ///<设置解码的错误条件
u32(*dec_confing)(void *work_buf, u32 cmd, void *parm);
} audio_decoder_ops, decoder_ops_t;
设计意图:
- 缓冲区所有权归上层:
need_*_size()系列函数只报告所需字节数,由调用方分配 work_buf,这对 MCU 上固定内存池的静态分配策略至关重要; - 断点续播内建:
open接收bk_point_ptr,配合get_bp_inf与CMD_SET_CONTINUE_BK(0x90)实现断电/暂停后续播; - 播放模式三态:
PLAY_MOD_NORMAL(0x00)/PLAY_MOD_FF(0x01)/PLAY_MOD_FB(0x02)对应正常、快进、快退,set_step控制步进粒度; - 统一配置通道:
dec_confing用命令字扩展,SET_DECODE_MODE(0x80)与CMD_SET_FADEOUT(0x93)走同一入口,避免接口随需求膨胀。
数据通道 if_decoder_io
解码器不直接接触存储介质,而是通过回调结构体获取数据、送出结果:
struct if_decoder_io {
void *priv;
int (*input)(void *priv, u32 addr, void *buf, int len, u8 type);
int(*check_buf)(void *priv, u32 addr, void *buf);
u32(*output)(void *priv, void *data, int len);
u32(*get_lslen)(void *priv);
u32(*store_rev_data)(void *priv, u32 addr, int len);
};
接口语义(头文件注释明确约定):
input(priv, addr, buf, len, type):addr为文件位置,len是 512 的整数倍,type为 0 表示同步读(数据回来后函数才返回),为 1 表示异步读(立即返回,数据由上层稍后处理)——异步模式用于 DMA/中断驱动的读取路径,避免解码循环被 IO 阻塞;output(priv, data, len):解码器把 PCM 数据推给输出端(如 APA 播放通道),返回值通常表示实际接收长度;get_lslen:查询数据源剩余长度,供解码器判断是否读到文件尾;store_rev_data:将接收数据存入指定位置,用于流式写入场景。
解码信息的载体是 dec_inf_t,包含采样率 sr、比特率 br、声道数 nch、总时长 total_time,解码完成后由 get_dec_inf 返回,供上层做音量匹配或进度显示。
播放控制命令
框架层用宏定义统一播放控制字:
//play control
#define PLAY_FILE 0x80000000
#define PLAY_CONTINUE 0x80000001
#define PLAY_NEXT 0x80000002
PLAY_FILE(重播当前文件)、PLAY_CONTINUE(从断点续播)、PLAY_NEXT(切下一首)构成解码器的基本控制面,配合 AUDIO_DECODE_PARA.mode 与 AUDIO_FADE_PAR.mode 使用——淡入淡出参数的存在说明播报通道支持音量渐变,用于避免提示音突兀起停。
语音播报机制(TONE PLAYER)
语音资源表 tone_table[]
decode.c 维护一份提示音文件索引表,把语义化名称映射到具体音频文件宏:
//音频文件列表
const char *const tone_table[] = {
TONE_NUM_0, TONE_NUM_1, TONE_NUM_2, TONE_NUM_3, TONE_NUM_4,
TONE_NUM_5, TONE_NUM_6, TONE_NUM_7, TONE_NUM_8, TONE_NUM_9,
TONE_ALARM, TONE_AVERAGE, TONE_BAI, TONE_BASEWORLD, TONE_BEGIN,
TONE_CONTINUEHIGH, TONE_DIAN, TONE_END, TONE_ERROR, TONE_HIGH,
TONE_KP, TONE_LOW, TONE_LOWPOWER, TONE_MAIBO, TONE_MMHG,
TONE_NORMAL, TONE_NORMALHIGH, TONE_PLEASESEAT, TONE_REGISTER, TONE_SHI,
TONE_SHOUSUOYA, TONE_SHUZHANGYA, TONE_THANKS, TONE_ZHUYI,
};
来源:decode.c
从资源命名可以反推出血压计方案的播报词汇表:数字 TONE_NUM_0~9、单位词 TONE_BAI(百)/TONE_SHI(十)/TONE_DIAN(点)/TONE_MMHG(毫米汞柱)、体征词 TONE_SHOUSUOYA(收缩压)/TONE_SHUZHANGYA(舒张压)/TONE_MAIBO(脉搏)、引导词 TONE_PLEASESEAT(请坐)/TONE_REGISTER(注册)/TONE_BEGIN/TONE_END、状态词 TONE_LOWPOWER(低电量)/TONE_ERROR(错误)/TONE_NORMAL 等。这种"语义宏 + 表"的组织方式让播报逻辑与具体音频资源解耦——更换语音包只需重定义宏对应的文件路径。
数字拼读逻辑
voice.c 提供把数值转成语音索引序列的算法,播报血压值(如 128)时按"百/十/个"逐段发出:
*str++ = num / 100 + INDEX_TONE_NUM_0; // 百位数字
*str++ = INDEX_TONE_BAI; // “百”
if (num >= 10) {
*str++ = num / 10 + INDEX_TONE_NUM_0; // 十位数字
*str++ = INDEX_TONE_SHI; // “十”
}
来源:voice.c
算法要点(结合 Grep 上下文推断):
- 百位单独处理:
num / 100 + INDEX_TONE_NUM_0把百位数字换算为数字 tone 的索引,随后拼接INDEX_TONE_BAI; - 十位存在时输出"数字 + 十";当十位为 0 而个位非 0 时(如 105),插入
INDEX_TONE_NUM_0发"零",符合中文读数习惯; - 个位
num > 0时补发个位数字 tone,num == 0时省略,避免"一百二十零"这类冗余播报。
播放入口为 a_player_tonebyindex(),配合播放列表 playfilelist[] 与 currentfile 游标顺序播放一组 tone:
if (currentfile < allplayfile) {
a_player_tonebyindex(playfilelist[currentfile]);
return 1;
}
来源:voice.c
这一"先组列表、逐条触发、播完再取下一个"的模式,保证多段语音(如"收缩压 128 毫米汞柱")按序播报且中间无停顿丢失。
APA 音频输出驱动
驱动结构 apa_driver
APA 驱动以全局常量单例 apa_drv 暴露,接口风格与常见 DAC 驱动一致(头文件注释示例即 dac_drv->...):
struct apa_driver {
s32(*open)(APA_INF *info); // 打开 dac 模块,info 为初始化参数
s32(*close)(void); // 关闭 dac 模块
s32(*analog_open)(void); // 打开 dac 模拟模块
s32(*analog_close)(void); // 关闭 dac 模拟模块
s32(*ioctrl)(void *parm, u32 cmd);// 命令控制面
s32(*vol_ctrl)(void *buf, u32 len);// 按音量表转换 DAC 数据
};
来源:apa_api.h
open/close 管理数字与模拟通道生命周期(analog_open/close 单独控制模拟部分,便于低功耗待机时仅关闭模拟功放),vol_ctrl 在数据送入前按音量表做增益处理,ioctrl 承载全部参数化控制。
初始化参数 APA_INF
typedef struct _APA_INF {
u8 ac_dit; // AC 直通使能
u8 apa_p_mode; // APA P 通道输出模式
u8 apa_n_mode; // APA N 通道输出模式
u8 pwm_mode; // PWM 调制模式
u8 pwm_clk; // PWM 时钟分频
u16 def_sr; // 默认采样率
apa_func apa_input;// 输入回调
u8 dcc_en; // DCC(直流耦合)使能
void *priv; // 私有数据
} APA_INF;
来源:apa_api.h
apa_input 是函数指针类型 int (*)(void *priv, void *buf, u32 len),播放链路(解码输出 → APA)通过它把 PCM 数据灌入 APA,与 if_decoder_io.output 形成上下游衔接。pwm_mode 取值 APA_PWM_MODE1(单端/差分,推荐单端)或 APA_PWM_MODE2(差分);pwm_clk 取值 APA_CLK_320M / APA_CLK_240M(默认)/ APA_CLK_HSB。输出模式上,APA_PWM_MODE 输出音频信号,APA_IO_MODE 则将 APA 作为普通 IO 输出高低电平(用于指示灯等复用场景)。
ioctrl 命令面
enum { // 传入参数 功能解释
APA_REG_VOL_TAB, // u16 * 注册音量表
APA_GET_MAX_VOL, // u8 * 获取最大音量
APA_SET_MAX_VOL, // u8 * 设置最大音量
APA_GET_CUR_VOL, // u8 * 获取当前音量
APA_SET_CUR_VOL, // u8 * 设置当前音量
APA_CLR_DMA_BUF, // NULL 清除 DMABUF 数据
APA_SET_SR, // u16 * 设置采样率
APA_GET_SR, // u16 * 获取采样率
APA_IE_CTL, // u8 * 控制 IE
};
来源:apa_api.h
命令面覆盖三类需求:音量管理(注册音量表 + 最大/当前音量读写)、数据通路维护(APA_CLR_DMA_BUF 在停播时清空 DMA 缓冲,防止残留数据爆音;APA_SET_SR/APA_GET_SR 让采样率与解码器输出对齐)、中断控制(APA_IE_CTL 控制 DMA 中断使能,配合 apa_input 回调实现中断驱动的数据补给)。另有全局接口 set_apap_output_status / set_apan_output_status 分别控制 P/N 通道输出状态,用于静音或待机切换。
核心流程
语音播报端到端时序
一次"播报血压值"的完整调用链如下:应用把数值拆成 tone 序列,解码器从数据源读文件、解码出 PCM,APA 驱动完成增益与 PWM 输出。
sequenceDiagram
participant App as 应用/测量流程
participant V as voice.c (TONE PLAYER)
participant DC as decode.c (解码控制)
participant D as decoder_ops_t (UMP3)
participant IO as if_decoder_io
participant A as apa_drv (APA)
App->>V: 播报请求(数值/索引)
V->>V: 数字拆解 → tone 索引序列
V->>DC: a_player_tonebyindex(index)
DC->>D: open(work_buf, io, bk_ptr)
D->>IO: input(addr, buf, len, 0/1) 同步/异步读
IO-->>D: 音频文件数据
D->>D: format_check / run 解码循环
D->>IO: output(pcm, len)
IO->>A: apa_input(pcm, len) 回调
A->>A: vol_ctrl 按音量表增益
A->>A: PWM 调制输出
A-->>D: 返回已接收长度
D->>DC: get_playtime / 播完回调
DC->>V: 播放下一个 tone 或结束
流程要点:
input的同步/异步选择决定解码循环是否被 IO 阻塞:血压计播放 flash 内短语音时常用同步读简化时序,而大文件/流式场景用异步读配合 DMA;output与apa_input是软衔接:解码器把 PCM 交给if_decoder_io.output的实现方,实现方再调用apa_drv的输入回调,中间可插入采样率转换、音量缩放;- 每个 tone 播完返回后,
voice.c检查currentfile < allplayfile决定是否触发下一个,实现多段语音串播。
播放控制状态流
flowchart TD
Start([播报请求]) --> Open["decoder open(work_buf)"]
Open --> Chk{"format_check 通过?"}
Chk -->|"否"| Err["返回错误/播 TONE_ERROR"]
Chk -->|"是"| Run["run() 主循环"]
Run --> Cmd{"收到控制命令?"}
Cmd -->|"PLAY_CONTINUE"| Run
Cmd -->|"PLAY_NEXT"| Next["切换文件后 Run"]
Cmd -->|"SET_DECODE_MODE/FF/FB"| Step["set_step 调整步长"]
Step --> Run
Run --> EOF{"数据读完?"}
EOF -->|"否"| Run
EOF -->|"是"| Stop["close + APA_CLR_DMA_BUF"]
Stop --> End([播报完成])
使用示例
示例一:初始化并打开 APA 输出通道
按 apa_api.h 头部注释给出的推荐调用顺序(先注册音量表、设置音量,再 open):
//method of application
1.dac_drv->ioctrl(&31,DAC_SET_MAX_VOL);
2.dac_drv->ioctrl(&31,DAC_SET_CUR_VOL);
3.dac_drv->ioctrl(vol_tab,DAC_REG_VOL_TAB);
4.dac_drv->open(info);
5.dac_drv->close();
来源:apa_api.h
调用顺序的设计意图:先建立音量表与当前音量,再打开 DAC,可避免开通道瞬间以默认(可能过大)音量爆音;APA_REG_VOL_TAB 注册的音量表决定 vol_ctrl 的增益映射曲线。
示例二:配置 APA_INF 并注册输入回调
typedef int (*apa_func)(void *priv, void *buf, u32 len);
typedef struct _APA_INF {
u8 ac_dit;
u8 apa_p_mode;
u8 apa_n_mode;
u8 pwm_mode;
u8 pwm_clk;
u16 def_sr; // 默认采样率
apa_func apa_input; // 解码输出数据入口
u8 dcc_en;
void *priv;
} APA_INF;
来源:apa_api.h
实现方通常把 apa_input 接到 DMA 缓冲搬运逻辑:每次调用把 PCM 数据写入 APA 的 DMA 缓冲并启动一次传输,APA_IE_CTL 开启中断后在传输完成中断里继续喂数据,形成流水线。
示例三:数值转语音索引序列
血压值(如 128)播报前先拼出 tone 索引串:
*str++ = num / 100 + INDEX_TONE_NUM_0; // 百位
*str++ = INDEX_TONE_BAI; // “百”
if (num >= 10) {
*str++ = num / 10 + INDEX_TONE_NUM_0; // 十位
*str++ = INDEX_TONE_SHI; // “十”
}
if (num > 0) {
*str++ = num + INDEX_TONE_NUM_0; // 个位
}
来源:voice.c
拼好的索引串送入 playfilelist[],由 a_player_tonebyindex() 逐条播放;currentfile 越界即整组播完,返回值 1 表示还有后续、0 表示结束。
示例四:解码器操作集的典型实现骨架
实现一个新解码器只需填满 decoder_ops_t 并暴露工厂函数(UMP3 即为 get_ump3_ops()):
typedef struct __audio_decoder_ops {
char *name;
u32(*open)(void *work_buf, const struct if_decoder_io *decoder_io, u8 *bk_point_ptr);
u32(*format_check)(void *work_buf);
u32(*run)(void *work_buf, u32 type);
dec_inf_t *(*get_dec_inf)(void *work_buf);
u32(*get_playtime)(void *work_buf);
u32(*get_bp_inf)(void *work_buf);
u32(*need_dcbuf_size)();
u32(*need_rdbuf_size)();
u32(*need_bpbuf_size)();
void (*set_step)(void *work_buf, u32 step);
void (*set_err_info)(void *work_buf, u32 cmd, u8 *ptr, u32 size);
u32(*dec_confing)(void *work_buf, u32 cmd, void *parm);
} audio_decoder_ops, decoder_ops_t;
dec_confing 是解码器的命令分发入口:cmd == SET_DECODE_MODE 时切换播放模式(PLAY_MOD_FF/FB),cmd == CMD_SET_FADEOUT 时配置淡出,cmd == CMD_SET_CONTINUE_BK 时设置断点续播信息。
配置选项
APA 输出配置(APA_INF)
| 字段 | 类型 | 默认值/推荐值 | 说明 |
|---|---|---|---|
pwm_clk | u8 | APA_CLK_240M | PWM 时钟源:320M / 240M(默认)/ HSB,影响调制频率与功耗 |
pwm_mode | u8 | APA_PWM_MODE1 | PWM 调制模式:单端/差分(推荐单端),APA_PWM_MODE2 为差分 |
apa_p_mode / apa_n_mode | u8 | 由驱动决定 | P/N 通道输出模式,配合 set_apap_output_status/set_apan_output_status 控制 |
def_sr | u16 | 与解码采样率一致 | 默认采样率,可通过 APA_SET_SR 动态调整 |
dcc_en | u8 | 0 | 直流耦合使能,开启后输出包含直流分量(通常用于简化模拟电路) |
apa_input | apa_func | 必填 | PCM 数据输入回调,解码输出由此进入 APA |
ac_dit | u8 | 0 | AC 直通使能 |
输出模式宏
| 宏 | 值 | 用途 |
|---|---|---|
APA_PWM_MODE | 0 | APA 输出音频信号(正常播放) |
APA_IO_MODE | 1 | APA 作为普通 IO 输出高低电平(指示灯等复用场景) |
解码控制命令
| 命令 | 值 | 参数 | 用途 |
|---|---|---|---|
SET_DECODE_MODE | 0x80 | AUDIO_DECODE_PARA* | 切换解码模式(normal/FF/FB) |
CMD_SET_CONTINUE_BK | 0x90 | 断点信息 | 设置断点续播 |
CMD_SET_FADEOUT | 0x93 | AUDIO_FADE_PARA* | 设置淡出 |
PLAY_FILE | 0x80000000 | - | 重播当前文件 |
PLAY_CONTINUE | 0x80000001 | - | 断点续播 |
PLAY_NEXT | 0x80000002 | - | 播放下一个文件 |
API 参考
struct apa_driver(全局单例 apa_drv)
s32(*open)(APA_INF *info)
打开 APA/DAC 模块。info 携带采样率、PWM 模式、输入回调等初始化参数;返回 0 表示成功,非 0 表示失败。必须先完成 APA_REG_VOL_TAB 与音量设置再调用,避免默认音量爆音。
s32(*close)(void)
关闭 APA/DAC 数字通道。播放结束时应配合 APA_CLR_DMA_BUF 清空残留数据。
s32(*analog_open)(void) / s32(*analog_close)(void)
单独开关模拟功放部分。低功耗待机时关闭模拟部分可显著省电,数字通道可保持配置。
s32(*ioctrl)(void *parm, u32 cmd)
命令控制面。cmd 取值 APA_REG_VOL_TAB、APA_GET/SET_MAX_VOL、APA_GET/SET_CUR_VOL、APA_CLR_DMA_BUF、APA_SET/GET_SR、APA_IE_CTL;parm 按命令传入对应类型指针(音量表 u16*、音量/采样率 u8*/u16*、DMA 清除传 NULL)。
s32(*vol_ctrl)(void *buf, u32 len)
按注册的音量表对 PCM 数据做增益转换,buf 为待处理数据、len 为长度。
audio_decoder_ops(解码器工厂入口)
audio_decoder_ops *get_ump3_ops()
返回 UMP3 解码器操作集。上层拿到后调用 need_*_size() 分配缓冲、open() 打开数据源、循环 run() 驱动解码。
u32(*run)(void *work_buf, u32 type)
解码主循环,type 传入播放模式(PLAY_MOD_NORMAL/PLAY_MOD_FF/PLAY_MOD_FB)。每次调用推进一帧解码并把 PCM 经 if_decoder_io.output 送出。
dec_inf_t *(*get_dec_inf)(void *work_buf)
返回解码信息结构:sr(采样率)、br(比特率)、nch(声道数)、total_time(总时间,单位由实现决定),供上层校准 APA_SET_SR 或显示进度。
if_decoder_io(数据通道回调)
int (*input)(void *priv, u32 addr, void *buf, int len, u8 type)
从 addr 处读取 len(512 的整数倍)字节到 buf。type=0 同步读(阻塞至数据就绪),type=1 异步读(立即返回,数据后续就绪)。
u32 (*output)(void *priv, void *data, int len)
接收解码后的 PCM 数据,返回实际接收长度。实现方通常转调 APA_INF.apa_input 或 DMA 搬运。
u32 (*get_lslen)(void *priv)
返回数据源剩余长度,解码器据此判断文件尾。
失败模式、边界情况与并发注意
- 音量表未注册即播放:
vol_ctrl依赖APA_REG_VOL_TAB建立的映射表,未注册时增益行为未定义,可能导致音量异常或无声。约定顺序为注册音量表 → 设置音量 →open→ 播放。 - 采样率不匹配:解码器输出的采样率与
def_sr/APA_SET_SR不一致时,音调会偏高/偏低。血压计播报前应通过get_dec_inf()->sr读取实际采样率并同步给 APA。 - 停播爆音:解码中断时 DMA 缓冲残留数据,若不清空直接关通道会产生爆音。应依次执行:
APA_CLR_DMA_BUF→close()。 - 异步读的竞态:
input(type=1)立即返回后,解码器可能再次请求数据,若数据源回写store_rev_data的时机与解码循环访问buf冲突,需由实现方保证双缓冲或互斥。同步读(type=0)不存在该问题,但会阻塞解码循环。 - 断点续播一致性:
open的bk_point_ptr与CMD_SET_CONTINUE_BK必须配套使用,且need_bpbuf_size分配的缓冲区大小不足时会截断断点信息,导致续播位置偏移。 - 多段语音串播:
voice.c以currentfile < allplayfile顺序触发 tone,任一文件打开失败(如资源缺失)应播TONE_ERROR并跳过,避免播报链卡死。 - 播放中抢占:血压测量流程中可能高频触发播报(如按键音 + 测量结果),若 APA 通道被前一播放占用,新请求需等待或打断;打断路径应走
APA_CLR_DMA_BUF+ 重新open,保证状态干净。 APA_IO_MODE与APA_PWM_MODE混用:同一引脚在两种模式间切换时,必须先close再切换模式并重新open,否则输出状态机残留。
性能与操作注意事项
- 缓冲区规划:解码器通过
need_dcbuf_size()/need_rdbuf_size()/need_bpbuf_size()上报需求,上层应在系统初始化阶段静态分配(MCU 通常不用动态堆),三个缓冲可复用同一内存池的不同分区;rdbuf大小影响 IO 效率——if_decoder_io.input要求长度是 512 的整数倍,rdbuf应为 512 的整数倍以匹配底层 flash/文件系统扇区。 - IO 阻塞权衡:同步读(
type=0)实现简单但解码吞吐受存储介质延迟限制;对短提示音(单个 tone 通常 < 100KB)影响可忽略。异步读(type=1)适合长文件,但需要额外的完成通知机制(中断/信号量)。 - PWM 时钟与功耗:
APA_CLK_320M提供更高调制频率(噪声更低)但功耗更大;电池供电的血压计建议保持默认APA_CLK_240M,仅在音质敏感场景升级到 320M 或 HSB。 - 低功耗配合:播报完成后应依次
vol_ctrl归零 →APA_CLR_DMA_BUF→analog_close(),保留数字通道配置以便快速唤醒再播。 - 音量表曲线:
APA_REG_VOL_TAB注册的u16音量表是 DAC 值到实际增益的映射,应根据扬声器特性和听感测试定制;血压计场景建议 0~31 级指数曲线,低音量段步进更细腻。
扩展点
- 新增解码格式:实现
decoder_ops_t(name/open/format_check/run/get_dec_inf/need_*_size/set_step/set_err_info/dec_confing)并暴露工厂函数,参照get_ump3_ops()。dec_confing内用cmd分发SET_DECODE_MODE、CMD_SET_FADEOUT、CMD_SET_CONTINUE_BK。 - 新增语音词条:在语音资源表中追加语义宏(如
TONE_WEIGHT),并在语音包中提供对应音频文件;tone_table[]的顺序即索引号,数字拼读逻辑依赖INDEX_TONE_NUM_0、INDEX_TONE_BAI、INDEX_TONE_SHI等索引常量,新增词条不应破坏这些索引的数值。 - 替换音频输出后端:APA 只是
if_decoder_io.output的一种实现;接入 I2S 外部 codec 或 UAC 设备时,只需实现output回调把 PCM 转送目标设备(参考uac_audio.h/uac_stream.h的 USB 音频设备),解码框架无需改动。 - 播报策略定制:
voice.c的"组列表 + 顺序触发"模式可扩展为带优先级的播报队列(高优先级打断低优先级),并在打断时走APA_CLR_DMA_BUF清理路径。 - IO 模式复用:
APA_IO_MODE允许 APA 引脚退化为 GPIO 输出,可用于 LED/蜂鸣器复用,扩展时注意与 PWM 模式的互斥切换。
测试与验证要点
仓库中音频能力以接口契约形式存在,验证重点应覆盖:
- 解码链路:对每个 tone 文件执行
format_check→open→run直至get_lslen归零,校验get_dec_inf()->sr/br/nch与文件头一致; - 拼读正确性:边界值 0、9、10、99、100、105、120、999 的语音序列拼装,重点检查十位为 0 时插入"零"、个位为 0 时省略的逻辑;
- APA 状态切换:open/close/analog_open/analog_close 反复交替,验证无爆音、无残留输出;
APA_SET_SR切换采样率后音调正确; - 播放中断:播报中途打断,验证
APA_CLR_DMA_BUF后重新 open 无异常,且断点续播(CMD_SET_CONTINUE_BK)位置正确; - 低功耗:播报结束后
analog_close,测量待机电流回落,确认数字通道未误关导致无法唤醒。
Related Links
- APA 驱动接口头文件 —
apa_drv、APA_INF、ioctrl 命令定义 - 解码器控制接口头文件 —
decoder_ops_t、if_decoder_io、播放控制命令 - 音频解码应用实现 —
tone_table[]语音资源表与解码控制 - 语音播报接口实现 — TONE PLAYER API 与数字拼读
- UAC 音频设备头文件 — USB 音频设备(相关能力,另见 USB 音频页面)
- UAC 流事件头文件 — USB 音频播放/暂停事件(相关能力)
相关目录页:设备驱动层(DAC/时钟)、USB 音频设备、电源管理(低功耗待机)承载了本页仅引用未展开的内容。