杰理 SDK 文档中心
首页
首页
  • 概述与入门

    • 项目概述与芯片平台
    • 环境搭建与工具链安装
    • 编译与烧录指南
    • 工程结构总览
  • 应用层与公共模块

    • GP MCU 主应用入口
    • AT 指令与调试模块
    • 电池检测与电源管理
    • EEPROM 与参数存储
    • 按键与 USB 设备驱动
    • 音频解码与 APA 语音播报
  • 外设驱动与示例

    • 高精度 ADC(HADC)
    • 通用 ADC 与定时器
    • UART / SPI / IIC 通信外设
    • MCPWM 与电机控制
    • RTC 与低功耗唤醒
    • 段码 LCD 驱动
    • NOR Flash 与红外编解码组件
  • 显示与 UI 系统

    • LCD 驱动与字库引擎
    • UI 平台与控件绘制
    • UI 工程与资源生成工具
  • 系统底层与芯片平台

    • cd09 芯片平台与预编译库
    • GPIO 与 IIC 底层驱动
    • 系统文件系统与设备模型
  • 启动引导与固件升级

    • UBOOT 引导工程
    • 固件升级机制
  • 开发工具与资源

    • 编译脚本与命令行工具
    • 音频文件转换工具
    • 硬件资料与文档资源

音频解码与 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 血压计方案中,音频能力被抽象为三层协作模型:

  1. 播报发起层(应用):当测量完成、按键触发或事件发生时,应用调用 TONE 播放 API,按索引或按文件播放提示音;播报血压数值时,将数字拆解为"数字 + 单位"的音素序列。
  2. 解码控制层(框架):if_decoder_ctrl.h 定义统一的解码器操作集 decoder_ops_t 与数据通道 if_decoder_io。解码器(如 UMP3)通过操作集暴露 open/run/format_check 等能力,框架通过 io 回调从存储介质取数据、向输出侧送 PCM 数据。
  3. 输出驱动层(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 支持同步/异步两种读取方式(type 0/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;

来源:if_decoder_ctrl.h

设计意图:

  • 缓冲区所有权归上层: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);
};

来源:if_decoder_ctrl.h

接口语义(头文件注释明确约定):

  • 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

来源:if_decoder_ctrl.h

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;

来源:if_decoder_ctrl.h

dec_confing 是解码器的命令分发入口:cmd == SET_DECODE_MODE 时切换播放模式(PLAY_MOD_FF/FB),cmd == CMD_SET_FADEOUT 时配置淡出,cmd == CMD_SET_CONTINUE_BK 时设置断点续播信息。

配置选项

APA 输出配置(APA_INF)

字段类型默认值/推荐值说明
pwm_clku8APA_CLK_240MPWM 时钟源:320M / 240M(默认)/ HSB,影响调制频率与功耗
pwm_modeu8APA_PWM_MODE1PWM 调制模式:单端/差分(推荐单端),APA_PWM_MODE2 为差分
apa_p_mode / apa_n_modeu8由驱动决定P/N 通道输出模式,配合 set_apap_output_status/set_apan_output_status 控制
def_sru16与解码采样率一致默认采样率,可通过 APA_SET_SR 动态调整
dcc_enu80直流耦合使能,开启后输出包含直流分量(通常用于简化模拟电路)
apa_inputapa_func必填PCM 数据输入回调,解码输出由此进入 APA
ac_ditu80AC 直通使能

输出模式宏

宏值用途
APA_PWM_MODE0APA 输出音频信号(正常播放)
APA_IO_MODE1APA 作为普通 IO 输出高低电平(指示灯等复用场景)

解码控制命令

命令值参数用途
SET_DECODE_MODE0x80AUDIO_DECODE_PARA*切换解码模式(normal/FF/FB)
CMD_SET_CONTINUE_BK0x90断点信息设置断点续播
CMD_SET_FADEOUT0x93AUDIO_FADE_PARA*设置淡出
PLAY_FILE0x80000000-重播当前文件
PLAY_CONTINUE0x80000001-断点续播
PLAY_NEXT0x80000002-播放下一个文件

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 级指数曲线,低音量段步进更细腻。

扩展点

  1. 新增解码格式:实现 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。
  2. 新增语音词条:在语音资源表中追加语义宏(如 TONE_WEIGHT),并在语音包中提供对应音频文件;tone_table[] 的顺序即索引号,数字拼读逻辑依赖 INDEX_TONE_NUM_0、INDEX_TONE_BAI、INDEX_TONE_SHI 等索引常量,新增词条不应破坏这些索引的数值。
  3. 替换音频输出后端:APA 只是 if_decoder_io.output 的一种实现;接入 I2S 外部 codec 或 UAC 设备时,只需实现 output 回调把 PCM 转送目标设备(参考 uac_audio.h/uac_stream.h 的 USB 音频设备),解码框架无需改动。
  4. 播报策略定制:voice.c 的"组列表 + 顺序触发"模式可扩展为带优先级的播报队列(高优先级打断低优先级),并在打断时走 APA_CLR_DMA_BUF 清理路径。
  5. 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 音频设备、电源管理(低功耗待机)承载了本页仅引用未展开的内容。

Prev
按键与 USB 设备驱动