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

    • SDK 概览与产品定位
    • 支持芯片平台与蓝牙认证
    • SDK 架构与目录分层
  • 快速开始

    • 环境搭建与编译工具链
    • 编译构建系统
    • 板级工程与配置
    • 烧录与固件升级工具
  • 应用工程

    • 应用选择与工程总览
    • SPP + BLE 数传应用框架
    • 透传与 AT 指令示例
    • BLE 广播/中心与定位示例
    • 2.4G 私有协议与 Dongle 示例
    • 云平台接入示例
    • HID 人机交互应用框架
    • HID 示例工程(键盘/鼠标/遥控器/手柄)
    • Bluetooth Mesh 应用框架
    • Mesh 模型与 Mesh DFU 固件升级
    • Mesh 音频编解码演示
  • 芯片平台与硬件抽象

    • 芯片平台总览与差异
    • 音频编解码与时钟管理
    • 外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)
    • 芯片配置工具与下载支持
  • 蓝牙协议栈

    • 蓝牙控制器层(btctrler)
    • 蓝牙协议栈与 Profile(btstack)
    • 蓝牙模块选择与配置
  • 媒体与音频框架

    • 音频流框架
    • 音频编解码与 A2DP 媒体
    • 音频效果处理(EQ/频谱/变调/环绕/超低音)
    • 本地 TWS 与音频同步
  • 系统服务与运行时

    • 实时操作系统与任务调度
    • 消息事件机制
    • 电源管理与低功耗
    • 存储与配置系统
    • 设备驱动框架(USB/RTC)
  • 应用公共组件

    • 音频应用组件
    • 设备外设抽象(按键/触摸/传感器/存储)
    • 蓝牙公共模块与消息联动
    • 调试与配置组件
    • 杰理关键词唤醒(jl_kws)
  • 第三方协议与云平台接入

    • 杰理 RCSP 私有协议
    • 低功耗蓝牙 Mesh 方案(llsync_mesh)
    • Sig Mesh 方案
    • 涂鸦协议接入
    • 腾讯连连接入
    • 华为 HiLink 接入
  • 固件升级与维护

    • OTA 升级机制
    • 升级补丁与版本维护
    • 升级工具链(BLE OTA / USB Dongle OTA)
  • 文档与开发资源

    • 数据手册与架构文档
    • 协议与云平台开发文档
    • 常见问题与技术支持

音频应用组件

音频应用组件是 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/编码器 → 蓝牙/扬声器输出。音频应用组件的定位是这一链路中与具体格式无关的公共处理层,解决三类共性问题:

  1. 音量控制的人性化:直接按线性等级切音量会产生"咔哒"爆音,且人耳对音量感知是非线性的。组件提供带步进淡入淡出的数字音量控制,并支持自定义音量映射表;
  2. 多路声音叠加的听感管理:提示音、按键音常叠加在背景音乐上播放,组件通过后台自动淡出(BG_DVOL_FADE_ENABLE)机制让背景声在提示音期间自动降低,结束后恢复;
  3. 性能可观测性:编解码性能直接影响蓝牙链路的实时性,测试框架通过 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

字段语义与设计意图:

字段类型含义
toggleu8数字音量总开关;为 0 时 run 直接透传数据,不做衰减
fadeu8当前是否处于淡入淡出过程中(1=正在渐变)
volu8渐变过程中的当前音量等级
vol_maxu8音量等级上限(决定 vol 的有效范围)
vol_fades16本次渐变开始时的起始音量
vol_bks16后台自动淡出前的原始音量,淡出结束后用于恢复(仅 BG_DVOL_FADE_ENABLE)
entrylist_head多路声音链表节点,用于把多个 dvol 实例串起来统一做后台淡出管理
vol_targetvolatile s16目标音量;由调用线程写入、音频线程读取
fade_stepvolatile 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

  1. 互斥保护:OS_MUTEX mutex 使同一实例可被多个任务安全访问,user_audio_digital_volume_set 等接口内部加锁;
  2. 多声道支持:run 接口显式传入 ch_num,正确处理单/双声道数据的音量缩放(每个采样独立处理,声道数只影响数据解释方式);
  3. 自定义音量表: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

各阶段职责:

  1. 解码:audio_decoder 任务从文件或蓝牙流读取压缩数据,解码为 16 位 PCM;audio_decoder_test 的 before/after 钩子在此阶段测量 IO 区间耗时;
  2. 音量处理:解码后的 PCM 送入 audio_digital_vol_run,逐样本乘以当前渐变音量 vol;vol 每帧按 fade_step 向 vol_target 逼近,直到 fade 标志清零;
  3. 多路叠加:提示音触发时调用 audio_digital_vol_bg_fade(1),模块把当前背景音量存入 vol_bk 并通过链表遍历所有 dvol 实例淡出;提示音结束后 audio_digital_vol_bg_fade(0) 恢复;
  4. 输出:处理完的 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

典型调用序列(音量调节场景):

  1. 播放器初始化时 open(0, 30, 4) 打开音量句柄(30 级音量、每帧步进 4 级);
  2. 音频回调线程每帧调用 run(dvol, pcm_buf, pcm_len, 2) 处理立体声 PCM;
  3. 用户按键调音量时,UI 线程调用 set(dvol, new_vol)——只需写目标值,渐变由音频线程完成;
  4. 若产品需要等响度曲线,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 任务栈任务表项768HID 应用任务表 {"audio_dec", 3, 0, 768, 128} 中的栈大小
audio_enc 任务栈任务表项512编码任务栈,小于解码任务栈
aec 任务栈任务表项768回声消除任务栈,与解码同级
vol_maxopen 参数调用方指定音量等级上限
fade_stepopen 参数调用方指定每帧渐变步进,决定淡入淡出时长
user_vol_tabset_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 任务栈及对应调度开销。

扩展点

  1. 自定义音量映射表:user_audio_digital_set_volume_tab 注入任意增益表,适配不同产品的人机交互手感;
  2. 音频处理管道回调:user_audio_process 的 handler 可挂接音效、频谱、录音前处理等自定义逻辑,框架保证其在音量处理之后执行;
  3. 测试钩子模式:before/after + GPIO 翻转的范式可复制到任意耗时敏感路径(编码、AEC、KWS 采集),只需成对插入宏调用;
  4. 编译期特性开关:BG_DVOL_FADE_ENABLE、TCFG_AUDIO_ENABLE 提供了从功能级到模块级的裁剪粒度,新产品可据此定制资源占用;
  5. 音频任务参数: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
Next
设备外设抽象(按键/触摸/传感器/存储)