音频应用组件
音频应用组件是 AC63 蓝牙 SoC SDK 中位于 apps/common/audio/ 目录下的一组可复用的音频处理模块集合,涵盖数字音量调节(含淡入淡出与多路声音叠加管理)、数字信号处理工具(如相位反相器)、噪声门限、丢包补偿(PLC),以及编解码器性能测试框架等,为上层蓝牙应用(HID、Mesh 等)提供与具体编解码格式解耦的音频处理能力。
Purpose and Scope
本页面系统性地介绍 SDK 中音频应用层组件的设计与使用,包括:
- 数字音量调节组件(
audio_digital_vol):核心音量控制、淡入淡出机制、多路声音后台淡出、支持重入的自定义音量表 API 与音频处理管道; - 数字信号处理工具(
audio_utils):数字相位反相器等常用 DSP 模块; - 编解码器测试框架(
demo/audio_decoder_test.c、demo/audio_encoder_test.c):基于 before/after 钩子 + IO 翻转的耗时测量方法; - 音频相关任务(
audio_dec/audio_enc/aec)在应用中的注册与初始化方式(TCFG_AUDIO_ENABLE)。
以下主题属于兄弟页面的范畴,本页不做展开:
- 具体编解码器实现(SBC、AAC、MP3 等)属于底层
media库,请参见对应编解码文档; - 回声消除(AEC)算法实现位于
apps/hid/modules/aec/下的平台目录(br23/br25/br30/br34),属于算法模块文档; - 关键词唤醒(KWS)的音频采集封装见
apps/common/jl_kws/jl_kws_audio.c,属于语音识别文档。
Overview
在蓝牙音频设备(音箱、耳机、遥控器、Mesh 节点等)中,音频链路通常为:文件/流 → 解码器 → 后处理(音量、相位等) → DAC/编码器 → 蓝牙/扬声器输出。音频应用组件的定位是这一链路中与具体格式无关的公共处理层,解决三类共性问题:
- 音量控制的人性化:直接按线性等级切音量会产生"咔哒"爆音,且人耳对音量感知是非线性的。组件提供带步进淡入淡出的数字音量控制,并支持自定义音量映射表;
- 多路声音叠加的听感管理:提示音、按键音常叠加在背景音乐上播放,组件通过后台自动淡出(
BG_DVOL_FADE_ENABLE)机制让背景声在提示音期间自动降低,结束后恢复; - 性能可观测性:编解码性能直接影响蓝牙链路的实时性,测试框架通过 IO 翻转钩子让开发者用示波器精确测量解码各阶段的耗时。
这些组件全部运行在音频实时处理上下文中(由 audio_dec/audio_enc 任务承载),因此设计上强调:跨任务共享变量使用 volatile、重入 API 使用互斥锁、处理函数逐帧/逐块调用以保证实时性。
Architecture
下图展示音频应用组件在整个 SDK 音频链路中的位置及其内部模块关系:
flowchart TD
subgraph sg_App["应用层 (apps)"]
HID["HID 应用<br/>voice_remote_control"]
MESH["Mesh 应用<br/>audio_codec_demo"]
MAIN["app_main.c<br/>TCFG_AUDIO_ENABLE"]
end
subgraph sg_AudioComp["音频应用组件 (apps/common/audio)"]
DVOL["audio_digital_vol<br/>数字音量/淡入淡出"]
UTILS["audio_utils<br/>相位反相器等 DSP"]
NG["audio_noise_gate<br/>噪声门限"]
PLC["audio_plc<br/>丢包补偿"]
DEC_TEST["demo/audio_decoder_test<br/>解码耗时测试钩子"]
ENC_TEST["demo/audio_encoder_test<br/>编码耗时测试钩子"]
end
subgraph sg_Media["媒体底层 (media / asm)"]
DEC["audio_decoder<br/>解码任务"]
ENC["audio_encoder<br/>编码任务"]
AEC_TASK["aec 任务"]
DAC["DAC 输出"]
end
MAIN -->|"注册任务<br/>audio_dec_init/audio_enc_init"| DEC
MAIN --> ENC
HID --> DEC_TEST
HID --> ENC_TEST
MESH --> ENC
DEC --> DVOL
DEC --> UTILS
ENC --> NG
DEC --> PLC
DVOL --> DAC
DEC_TEST -->|"before/after 钩子"| DEC
架构说明:
- 应用层通过宏
TCFG_AUDIO_ENABLE决定是否注册音频解码/编码/AEC 任务,任务表位于apps/hid/app_main.c; - 音频应用组件位于应用与底层
media库之间:audio_digital_vol挂在解码输出到 DAC 之间做音量处理;audio_utils提供逐样本 DSP 工具;audio_noise_gate、audio_plc分别处理噪声门限与丢包补偿; - 测试框架不改变数据流,只在解码/编码的关键阶段插入 before/after 钩子并翻转 GPIO,供示波器测量,是典型的"零侵入性能观测"设计;
- 底层媒体库(
media/includes.h)提供audio_decoder/audio_encoder任务框架,audio_aec等算法模块按芯片平台(br23/br25/br30/br34)分别实现。
数字音量调节组件(audio_digital_vol)
数字音量调节是音频应用组件中最核心的模块,头文件 audio_digital_vol.h 同时暴露了两套 API:一套是系统内部使用的全局单例接口(audio_digital_vol_*),另一套是支持重入的用户接口(user_audio_digital_volume_*),后者允许同一应用内同时管理多路独立的音量处理实例。
核心数据结构:dvol_handle
#define BG_DVOL_FADE_ENABLE 1 /*多路声音叠加,背景声音自动淡出小声*/
typedef struct {
u8 toggle; /*数字音量开关*/
u8 fade; /*淡入淡出标志*/
u8 vol; /*淡入淡出当前音量(level)*/
u8 vol_max; /*淡入淡出最大音量(level)*/
s16 vol_fade; /*淡入淡出对应的起始音量*/
#if BG_DVOL_FADE_ENABLE
s16 vol_bk; /*后台自动淡出前音量值*/
struct list_head entry;
#endif
volatile s16 vol_target; /*淡入淡出对应的目标音量*/
volatile u16 fade_step; /*淡入淡出的步进*/
} dvol_handle;
Source: audio_digital_vol.h
字段语义与设计意图:
| 字段 | 类型 | 含义 |
|---|---|---|
toggle | u8 | 数字音量总开关;为 0 时 run 直接透传数据,不做衰减 |
fade | u8 | 当前是否处于淡入淡出过程中(1=正在渐变) |
vol | u8 | 渐变过程中的当前音量等级 |
vol_max | u8 | 音量等级上限(决定 vol 的有效范围) |
vol_fade | s16 | 本次渐变开始时的起始音量 |
vol_bk | s16 | 后台自动淡出前的原始音量,淡出结束后用于恢复(仅 BG_DVOL_FADE_ENABLE) |
entry | list_head | 多路声音链表节点,用于把多个 dvol 实例串起来统一做后台淡出管理 |
vol_target | volatile s16 | 目标音量;由调用线程写入、音频线程读取 |
fade_step | volatile u16 | 每帧渐变步进,决定淡入/淡出速度 |
值得注意的设计点:vol_target 与 fade_step 被声明为 volatile。这是因为音量设置通常来自 UI/控制线程,而音量实际生效发生在音频处理线程的 run 调用中,跨任务共享这两个变量必须保证可见性。这种"设置端写目标值、处理端逐步逼近"的模式避免了在音频实时线程中直接加锁。
BG_DVOL_FADE_ENABLE 编译期宏开启后,结构体额外携带 vol_bk 与链表节点 entry:当多路声音(例如提示音 + 背景音乐)叠加时,组件自动把背景声音量淡出到较小值,提示音结束后再淡回原值,保证提示音的可听性。
核心 API 行为
系统级单例接口:
| 函数 | 职责 |
|---|---|
audio_digital_vol_init() | 初始化模块(创建后台淡出管理所需的资源) |
audio_digital_vol_open(vol, vol_max, fade_step) | 打开一路数字音量,返回句柄 |
audio_digital_vol_close(dvol) | 关闭并释放句柄 |
audio_digital_vol_set(dvol, vol) | 设置目标音量(触发淡入淡出) |
audio_digital_vol_get() | 读取当前音量等级 |
audio_digital_vol_run(dvol, data, len) | 在音频线程中对 PCM 数据逐帧执行音量处理 |
audio_digital_vol_reset_fade(dvol) | 立即结束渐变、把音量拉到目标值 |
audio_digital_vol_bg_fade(fade_out) | 触发/解除全局后台声音淡出 |
支持重入的用户接口(user_ 系列)
/*************************自定义支持重入的数字音量调节****************************/
void *user_audio_digital_volume_open(u8 vol, u8 vol_max, u16 fade_step);
int user_audio_digital_volume_close(void *_d_volume);
u8 user_audio_digital_volume_get(void *_d_volume);
int user_audio_digital_volume_set(void *_d_volume, u8 vol);
int user_audio_digital_volume_reset_fade(void *_d_volume);
int user_audio_digital_volume_run(void *_d_volume, void *data, u32 len, u8 ch_num);
void user_audio_digital_handler_run(void *_d_volume, void *data, u32 len);
void user_audio_digital_set_volume_tab(void *_d_volume, u16 *user_vol_tab, u8 user_vol_max);
void *user_audio_process_open(void *parm, void *priv, void (*handler)(void *priv, void *data, int len, u8 ch_num));
int user_audio_process_close(void *_uparm_hdl);
void user_audio_process_handler_run(void *_uparm_hdl, void *data, u32 len, u8 ch_num);
Source: audio_digital_vol.h
该系列接口与系统单例接口相比有三个关键增强,体现在内部结构体 struct digital_volume 与 struct user_audio_parm 中:
struct digital_volume {
u8 toggle; /*数字音量开关*/
u8 fade; /*淡入淡出标志*/
u8 vol; /*淡入淡出当前音量*/
u8 vol_max; /*淡入淡出最大音量*/
s16 vol_fade; /*淡入淡出对应的起始音量*/
volatile s16 vol_target; /*淡入淡出对应的目标音量*/
volatile u16 fade_step; /*淡入淡出的步进*/
OS_MUTEX mutex; /*重入保护互斥锁*/
u8 ch_num; /*声道数(1=单声道,2=立体声)*/
void *priv;
u8 user_vol_max; /*自定义音量表级数*/
volatile s16 *user_vol_tab; /*自定义音量表*/
};
struct user_audio_parm {
void *priv;
void (*handler)(void *priv, void *data, int len, u8 ch_num);/*用户自定义回调处理*/
struct digital_volume *dvol_hdl;
};
Source: audio_digital_vol.h
- 互斥保护:
OS_MUTEX mutex使同一实例可被多个任务安全访问,user_audio_digital_volume_set等接口内部加锁; - 多声道支持:
run接口显式传入ch_num,正确处理单/双声道数据的音量缩放(每个采样独立处理,声道数只影响数据解释方式); - 自定义音量表:
user_audio_digital_set_volume_tab允许注入任意长度的音量映射表user_vol_tab。设计意图是补偿人耳听觉的非线性——线性递增的vol等级通过映射表转换为符合等响度曲线的实际增益值,从而让用户在低音量区间获得更细腻的调节手感。
user_audio_process_* 则进一步抽象出音频处理管道:user_audio_process_open 把用户回调 handler 与一路数字音量 dvol_hdl(位于 struct user_audio_parm 中)绑定,user_audio_process_handler_run 每帧被调用时先执行音量处理、再回调用户函数,形成"音量 → 用户自定义后处理"的固定流水线,避免每个应用重复编写样板代码。
淡入淡出与后台淡出机制
flowchart LR
A["audio_digital_vol_set<br/>(设置目标音量)"] --> B{"vol_target != vol ?"}
B -->|"是"| C["设置 fade=1<br/>vol_fade=当前音量"]
C --> D["run 每帧执行<br/>vol 按 fade_step 步进逼近 vol_target"]
D --> E{"vol == vol_target ?"}
E -->|"否"| D
E -->|"是"| F["fade=0<br/>渐变完成"]
B -->|"否"| G["无操作"]
渐变方向(淡入或淡出)由 vol_target 与当前 vol 的大小关系决定,audio_digital_vol_run 每次调用只步进 fade_step 个单位,因此渐变总时长 ≈ |vol_target - vol| / fade_step 帧。相比一次性切到目标音量,步进渐变把能量突变分摊到多帧,从根源上消除数字音量切换时的爆音(click/pop)。
后台淡出场景下,audio_digital_vol_bg_fade 会把当前音量存入 vol_bk、设置较低的目标音量;提示音结束后调用 audio_digital_vol_bg_fade(0) 恢复。多路声音通过 list_head entry 串成链表,由模块统一遍历执行淡出,避免各路声音各自为政导致的管理混乱。
数字信号处理工具(audio_utils)
audio_utils 定位为"数字信号处理常用模块合集"(源文件注释原话),当前提供数字相位反相器。头文件仅暴露一个接口:
#ifndef _AUDIO_UTILS_H_
#define _AUDIO_UTILS_H_
#include "generic/typedef.h"
/*
*********************************************************************
* Audio Digital Phase Inverter
* Description: 数字反相器,用来反转数字音频信号的相位
* Arguments : dat 数据buf地址
* len 数据长度(unit:byte)
* Return : None.
* Note(s) : None.
*********************************************************************
*/
void digital_phase_inverter_s16(s16 *dat, int len);
#endif/*_AUDIO_UTILS_H_*/
Source: audio_utils.h
实现细节与边界处理
void digital_phase_inverter_s16(s16 *dat, int len)
{
for (int i = 0; i < len / 2; i++) {
dat[i] = (dat[i] == -32768) ? 32767 : -dat[i];
/* dat[i] = -1 - dat[i]; */
}
}
Source: audio_utils.c
逐行分析:
- 函数以
s16(16 位有符号 PCM 样本)为单位处理数据,len的单位是字节,因此循环次数为len / 2; - 对每个样本取相反数即实现 180° 相位反转,这是数字音频相位反转的标准实现;
- 关键边界处理:
s16的取值范围是[-32768, 32767],而-(-32768) = 32768超出了s16的表示范围,会产生有符号溢出。代码显式判断dat[i] == -32768时输出32767(饱和到最大正值),避免未定义行为; - 被注释掉的
dat[i] = -1 - dat[i]是另一种等价实现(二进制补码取反),保留作为备选方案,说明作者在两种实现间做过权衡:-dat[i]语义更直观,-1 - dat[i]无分支、对流水线更友好,但两者对-32768的处理一致(-1 - (-32768) = 32767)。
典型应用场景:扬声器/麦克风极性接反时,通过软件相位反转纠正;或两路声音反相叠加实现声学抵消(如主动降噪中的参考信号处理)。
编解码器测试框架(demo)
apps/common/audio/demo/ 下的 audio_decoder_test.c 与 audio_encoder_test.c 提供了零侵入的性能测量范式:在解码/编码关键阶段插入 before/after 钩子,用 GPIO 翻转配合示波器/逻辑分析仪测量各阶段耗时。文件头注释明确说明了用法:
解码测试——before和after之间就是解码对应功能的处理,可以在before和after中分别翻转IO来卡一下处理时间
解码测试钩子
#define DEC_IO_DEBUG_1(i,x) {JL_PORT##i->DIR &= ~BIT(x), JL_PORT##i->OUT |= BIT(x);}
#define DEC_IO_DEBUG_0(i,x) {JL_PORT##i->DIR &= ~BIT(x), JL_PORT##i->OUT &= ~BIT(x);}
void audio_decoder_test_out_before(struct audio_decoder *dec, void *buff, int len)
{
DEC_IO_DEBUG_1(C, 3);
}
void audio_decoder_test_out_after(struct audio_decoder *dec, int wlen)
{
DEC_IO_DEBUG_0(C, 3);
}
Source: audio_decoder_test.c
机制说明:
DEC_IO_DEBUG_1(i, x)/DEC_IO_DEBUG_0(i, x)宏直接操作JL_PORT##i的DIR与OUT寄存器,把指定 IO 配置为输出并拉高/拉低。JL_PORT是芯片寄存器映射宏,BIT(x)为位操作;- 每个测量点成对出现:
*_before拉高 IO,*_after拉低 IO,两钩子之间的代码就是被测对象,IO 高电平宽度即该阶段耗时; - 测试点覆盖了解码的完整流水线:
out_before/out_after(解码输出)、read_before/read_after(从文件读取数据)、get_frame_before/get_frame_after(解析帧数据)等。
sequenceDiagram
participant APP as 应用/解码任务
participant DEC as audio_decoder
participant IO as GPIO (JC2/JC3)
participant SCOPE as 示波器
APP->>DEC: 开始解码一帧
DEC->>IO: read_before → JC2 拉高
DEC->>DEC: 从文件读取数据 (被测区间 1)
DEC->>IO: read_after → JC2 拉低
DEC->>IO: get_frame_before → JC2 拉高
DEC->>DEC: 解析帧数据 (被测区间 2)
DEC->>IO: get_frame_after → JC2 拉低
DEC->>IO: out_before → JC3 拉高
DEC->>DEC: 解码输出 PCM (被测区间 3)
DEC->>IO: out_after → JC3 拉低
SCOPE->>SCOPE: 测量各高电平宽度<br/>得到阶段耗时
设计意图:蓝牙音频是强实时性场景,解码耗时必须远小于音频帧周期(如 SBC 帧约 2.5ms~23ms),否则会产生卡顿。该测试框架让开发者在不改动解码器内部逻辑的前提下,用外部仪器量化瓶颈(文件 IO?帧解析?还是 PCM 输出?),并支持同目录下的编码器测试(audio_encoder_test.c)做对称测量。钩子函数以 struct audio_decoder *dec 为参数,说明它们会被注册进解码器框架的回调点,而不是被外部手动调用。
音频任务注册与初始化
音频解码/编码/AEC 任务由应用宏 TCFG_AUDIO_ENABLE 控制注册。在 HID 应用的任务表中:
#if TCFG_AUDIO_ENABLE
{"audio_dec", 3, 0, 768, 128 },
{"audio_enc", 4, 0, 512, 128 },
{"aec", 2, 0, 768, 128 },
#endif/*TCFG_AUDIO_ENABLE*/
Source: app_main.c
任务表每项包含任务名、优先级、CPU 核、栈大小等参数。可以看到三个音频任务均运行在 CPU0,其中 audio_dec(解码)与 aec(回声消除)分配了 768 的栈空间,audio_enc(编码)为 512——解码与 AEC 的数据量更大、调用链更深,因此栈也更大。应用初始化时按同一宏开关调用初始化函数:
#if TCFG_AUDIO_ENABLE
extern int audio_dec_init();
extern int audio_enc_init();
audio_dec_init();
audio_enc_init();
#endif/*TCFG_AUDIO_ENABLE*/
Source: app_main.c
设计意图:把音频能力做成编译期可裁剪的模块(TCFG_AUDIO_ENABLE),使得不需要音频的遥控器/传感器类产品可以彻底关闭,节省 RAM(任务栈)与 Flash;需要音频的产品(音箱、对讲等)则统一走 audio_dec_init/audio_enc_init 标准初始化路径。Mesh 应用则通过 apps/mesh/audio_codec_demo.c 展示了另一种集成方式——直接使用 struct audio_encoder 与外部声明的 encode_task,配合信号量(OS_SEM pcm_frame_sem)与双缓冲(ENC_BUF_NUM 2)做流式编码。
核心数据流
音频应用组件贯穿的端到端数据流(以"播放带提示音的音乐"为例)如下:
flowchart TD
SRC["音频源(文件/蓝牙流)"] -->|"read 钩子<br/>读取压缩数据"| DEC["audio_decoder 任务<br/>解码出 PCM"]
DEC -->|"out_before/after<br/>测量解码输出耗时"| VOL["audio_digital_vol_run<br/>淡入淡出音量处理"]
VOL -->|"PCM 数据"| TIP{"有提示音叠加?"}
TIP -->|"是"| BGFADE["audio_digital_vol_bg_fade(1)<br/>背景音量存入 vol_bk 并淡出"]
BGFADE --> MIX["提示音与背景混合播放"]
TIP -->|"否"| MIX
MIX -->|"提示音结束<br/>bg_fade(0) 恢复"| RESTORE["背景淡回 vol_bk 原音量"]
RESTORE --> DAC["DAC 输出"]
MIX --> DAC
各阶段职责:
- 解码:
audio_decoder任务从文件或蓝牙流读取压缩数据,解码为 16 位 PCM;audio_decoder_test的 before/after 钩子在此阶段测量 IO 区间耗时; - 音量处理:解码后的 PCM 送入
audio_digital_vol_run,逐样本乘以当前渐变音量vol;vol每帧按fade_step向vol_target逼近,直到fade标志清零; - 多路叠加:提示音触发时调用
audio_digital_vol_bg_fade(1),模块把当前背景音量存入vol_bk并通过链表遍历所有 dvol 实例淡出;提示音结束后audio_digital_vol_bg_fade(0)恢复; - 输出:处理完的 PCM 送 DAC 或蓝牙编码器(此时可能进入
audio_enc任务编码后发送)。
使用示例
示例一:解码器耗时测量(测试框架的挂载方式)
audio_decoder_test.c 演示了如何为解码器挂接前后钩子——每个钩子成对翻转一个 GPIO 引脚,示波器测得的脉冲宽度即对应阶段的处理耗时:
#define DEC_IO_DEBUG_1(i,x) {JL_PORT##i->DIR &= ~BIT(x), JL_PORT##i->OUT |= BIT(x);}
#define DEC_IO_DEBUG_0(i,x) {JL_PORT##i->DIR &= ~BIT(x), JL_PORT##i->OUT &= ~BIT(x);}
/* 解码输出:before 拉高 IO,after 拉低 IO,高电平宽度 = 解码输出耗时 */
void audio_decoder_test_out_before(struct audio_decoder *dec, void *buff, int len)
{
DEC_IO_DEBUG_1(C, 3);
}
void audio_decoder_test_out_after(struct audio_decoder *dec, int wlen)
{
DEC_IO_DEBUG_0(C, 3);
}
/* 文件读取:测量从文件/流读取压缩数据的耗时 */
void audio_decoder_test_read_before(struct audio_decoder *dec, int len, u32 offset)
{
DEC_IO_DEBUG_1(C, 2);
}
void audio_decoder_test_read_after(struct audio_decoder *dec, u8 *data, int rlen)
{
DEC_IO_DEBUG_0(C, 2);
}
Source: audio_decoder_test.c
示例二:数字音量句柄与自定义音量表(扩展点)
以下代码展示 user_ 系列接口的完整使用契约:打开实例、设置目标音量、逐帧处理、注入自定义音量映射表:
/* 打开一路可重入的数字音量(初始音量 vol、上限 vol_max、渐变步进 fade_step) */
void *user_audio_digital_volume_open(u8 vol, u8 vol_max, u16 fade_step);
/* 设置目标音量:触发淡入淡出,内部有 OS_MUTEX 保护,可跨任务调用 */
int user_audio_digital_volume_set(void *_d_volume, u8 vol);
/* 音频线程逐帧调用:对 data 指向的 PCM 数据执行音量处理,ch_num 为声道数 */
int user_audio_digital_volume_run(void *_d_volume, void *data, u32 len, u8 ch_num);
/* 注入自定义音量表:user_vol_tab 为增益映射数组,user_vol_max 为表项数 */
void user_audio_digital_set_volume_tab(void *_d_volume, u16 *user_vol_tab, u8 user_vol_max);
Source: audio_digital_vol.h
典型调用序列(音量调节场景):
- 播放器初始化时
open(0, 30, 4)打开音量句柄(30 级音量、每帧步进 4 级); - 音频回调线程每帧调用
run(dvol, pcm_buf, pcm_len, 2)处理立体声 PCM; - 用户按键调音量时,UI 线程调用
set(dvol, new_vol)——只需写目标值,渐变由音频线程完成; - 若产品需要等响度曲线,
set_volume_tab(dvol, loudness_tab, 30)注入自定义映射表。
示例三:音频处理管道(音量 + 用户回调组合)
struct user_audio_parm {
void *priv;
void (*handler)(void *priv, void *data, int len, u8 ch_num);/*用户自定义回调处理*/
struct digital_volume *dvol_hdl;
};
/* 打开管道:parm 中绑定 dvol_hdl 与 handler,priv 为用户上下文 */
void *user_audio_process_open(void *parm, void *priv, void (*handler)(void *priv, void *data, int len, u8 ch_num));
Source: audio_digital_vol.h
user_audio_process_handler_run 每帧被调用时,框架先执行绑定的数字音量处理,再调用用户 handler 做自定义后处理(如音效、频谱采集)。这把"音量 + 后处理"组合成固定流水线,用户只需实现自己的回调函数,无需关心音量逻辑的调用时机。
配置选项
音频应用组件的可配置项分散在编译期宏与运行时参数两个层面:
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
BG_DVOL_FADE_ENABLE | 编译期宏 | 1(开启) | 多路声音叠加时,背景声音自动淡出;开启后 dvol_handle 额外占用 vol_bk + list_head entry 字段 |
TCFG_AUDIO_ENABLE | 编译期宏 | 由具体产品决定 | 应用级总开关:决定是否注册 audio_dec/audio_enc/aec 任务并调用 audio_dec_init()/audio_enc_init() |
audio_dec 任务栈 | 任务表项 | 768 | HID 应用任务表 {"audio_dec", 3, 0, 768, 128} 中的栈大小 |
audio_enc 任务栈 | 任务表项 | 512 | 编码任务栈,小于解码任务栈 |
aec 任务栈 | 任务表项 | 768 | 回声消除任务栈,与解码同级 |
vol_max | open 参数 | 调用方指定 | 音量等级上限 |
fade_step | open 参数 | 调用方指定 | 每帧渐变步进,决定淡入淡出时长 |
user_vol_tab | set_volume_tab 参数 | NULL(线性) | 自定义音量映射表,补偿人耳等响度感知 |
API 参考
void digital_phase_inverter_s16(s16 *dat, int len)
对 16 位 PCM 缓冲做 180° 相位反转。
参数:
dat(s16*):PCM 数据缓冲地址(原地处理)len(int):数据长度,单位字节
返回: 无
说明: 逐样本取反;-32768 饱和为 32767 以避免有符号溢出。复杂度 O(n/2),适合低开销 DSP 场景。
dvol_handle *audio_digital_vol_open(u8 vol, u8 vol_max, u16 fade_step)
打开系统级数字音量实例。
参数: vol 初始音量、vol_max 最大音量、fade_step 渐变步进。 返回: 音量句柄;失败返回 NULL。
int audio_digital_vol_run(dvol_handle *dvol, void *data, u32 len)
在音频线程中对 PCM 数据执行音量处理(含淡入淡出逼近逻辑)。必须在音频实时线程中逐帧调用。
参数: dvol 句柄、data PCM 缓冲、len 字节长度。 返回: 0 表示成功。
void audio_digital_vol_set(dvol_handle *dvol, u8 vol)
设置目标音量,触发淡入淡出。
参数: dvol 句柄、vol 目标音量等级。
void audio_digital_vol_bg_fade(u8 fade_out)
全局后台声音淡出控制。
参数: fade_out 非 0 淡出(存入 vol_bk)、0 恢复(淡回 vol_bk)。
void *user_audio_digital_volume_open(u8 vol, u8 vol_max, u16 fade_step) 及 user_ 系列
可重入音量实例接口族。与系统接口对应,区别在于:实例私有、OS_MUTEX 保护、run 支持 ch_num 声道参数、可通过 user_audio_digital_set_volume_tab 注入自定义音量表。
void *user_audio_process_open(void *parm, void *priv, void (*handler)(void *priv, void *data, int len, u8 ch_num))
打开"音量 + 用户回调"音频处理管道。parm 需指向填充了 dvol_hdl 的 struct user_audio_parm;handler 每帧在音量处理之后被调用。
故障模式、边界情况与并发
有符号溢出边界
digital_phase_inverter_s16 对 s16 最小值 -32768 做了饱和处理(输出 32767)。若忽略此分支,-dat[i] 将产生 32768 的有符号溢出,导致单样本出现大幅错误的输出,听感上表现为爆音或咔哒声。
跨任务共享的可见性
vol_target、fade_step 声明为 volatile,因为音量设置(UI 线程)与音量执行(音频线程)分属不同任务。若去掉 volatile,音频线程可能长期读到寄存器缓存中的旧目标值,渐变永不完成或方向错误。用户系列 API 在此基础上增加 OS_MUTEX,支持多任务同时操作同一实例。
淡入淡出被打断
渐变过程中再次调用 set() 会以当前 vol 为新起点重新渐变(vol_fade 被刷新),这是有意的设计——快速连按音量键时音量能连续响应,而不是排队执行旧渐变。reset_fade() 则用于需要立即生效的场景(如静音开关)。
实时线程中的阻塞风险
run 在音频实时线程中执行,内部不应出现阻塞操作;自定义 handler(user_audio_process)也必须保持轻量,否则会拉长音频帧处理时间,造成蓝牙传输欠载或 DAC 欠采样。这是音频实时系统通用的性能红线。
栈资源紧张
audio_enc 任务栈仅 512(相对 audio_dec/aec 的 768),编码路径若引入深层调用(如自定义 DSP 链),可能栈溢出。扩展编码处理时建议先加大任务栈或拆出独立任务。
性能与运维建议
- 耗时测量先行:使用
audio_decoder_test的 IO 钩子模式量化解码/读取/帧解析各阶段耗时,确认瓶颈后再优化,避免盲改; - 渐变步进选择:
fade_step决定淡入淡出时长。步进过小则渐变太长(用户感觉音量迟钝),过大则失去防爆音意义;典型做法是按帧率折算,使渐变总时长在 20~50ms; - 音量表精度:自定义音量表使用
u16增益值,低音量区间表项应更密集以匹配等响度曲线; - 编译期裁剪:不需要音频的产品保持
TCFG_AUDIO_ENABLE = 0,可节省约 2KB 任务栈及对应调度开销。
扩展点
- 自定义音量映射表:
user_audio_digital_set_volume_tab注入任意增益表,适配不同产品的人机交互手感; - 音频处理管道回调:
user_audio_process的handler可挂接音效、频谱、录音前处理等自定义逻辑,框架保证其在音量处理之后执行; - 测试钩子模式:
before/after+ GPIO 翻转的范式可复制到任意耗时敏感路径(编码、AEC、KWS 采集),只需成对插入宏调用; - 编译期特性开关:
BG_DVOL_FADE_ENABLE、TCFG_AUDIO_ENABLE提供了从功能级到模块级的裁剪粒度,新产品可据此定制资源占用; - 音频任务参数:
app_main.c任务表(优先级/栈)可按产品负载调整,是无需改代码的性能调优入口。
测试覆盖说明
组件源码目录中的测试以性能观测型为主(demo/audio_decoder_test.c、demo/audio_encoder_test.c),通过钩子验证解码/编码各阶段耗时,而非单元断言型测试。audio_decoder_test.c 中可见的测试点包括:解码输出(out_before/out_after)、文件读取(read_before/read_after)、帧获取(get_frame_before/get_frame_after)等,覆盖了解码主循环的全部 IO 密集阶段。音量组件的正确性验证依赖集成场景(播放 + 提示音叠加)的听感与仪器测量,未在源码中发现独立的自动化测试用例。
Related Links
- 数字音量头文件 audio_digital_vol.h
- DSP 工具 audio_utils.c
- 解码测试框架 audio_decoder_test.c
- 编码测试框架 audio_encoder_test.c
- HID 应用任务注册 app_main.c
- Mesh 编码演示 audio_codec_demo.c
- 噪声门限与丢包补偿模块:
apps/common/audio/audio_noise_gate.h、apps/common/audio/audio_plc.h(实现细节见对应文件) - AEC 回声消除算法(按芯片平台):
apps/hid/modules/aec/br23|br25|br30|br34/audio_aec.c - KWS 音频采集封装:
apps/common/jl_kws/jl_kws_audio.c