音频输出、功放与硬件重采样
本文档介绍 AD24N SDK 中音频输出(DAC)子系统的完整实现:DAC 输出通道管理、功放(APA)控制、采样率配置与硬件重采样机制,以及音量、静音、淡入淡出、多通道混音等核心行为。
Purpose and Scope
本页覆盖音频链路末端的 DAC 输出通路:从解码器/音效处理后的 PCM 数据经环形缓冲进入 DAC 引擎,到硬件数模转换、功放放大并驱动扬声器的完整过程,包括:
- DAC 通道注册与数据填充(
regist_dac_channel/fill_dac_fill) - 数字音量曲线(
vol_tab)与硬件音量(DAC_VOL、rdac_phy_vol) - 输出采样率配置与硬件重采样(
AUDAC_SR*、adda_clk_open) - 功放(APA)使能与时钟选择(
DAC0_APA_SEL、apa_clk_sel) - 静音与淡入淡出(
dac_mute、dac_fade_*)
以下相关主题由兄弟页面覆盖,不在本页展开:音频解码与播放管理(decoder/player 子系统)、音效处理(sound_effect)、ADC/录音通路。本文只引用与 DAC 输出直接相关的源码。
概述
在 AD24N 这样的蓝牙/音频 SoC 中,DAC(Digital-to-Analog Converter)是音频回放链路的最后一级数字处理单元:它接收各音频源(解码器、提示音、USB 等)写入的 PCM 数据,在内部完成多通道混音、数字音量缩放、抗混叠/去直流处理,输出模拟信号经功放(APA)驱动扬声器。
SDK 通过三层封装管理这条通路:
- 应用层 API(
dac_api.h/dac_api.c):提供dac_*/auout_*接口,负责通道注册、音量、采样率、静音等策略; - 驱动/硬件层(
audio_dac_cpu.h及对应实现):直接操作JL_AUDDAC寄存器,配置采样率、DMA、去直流滤波器、APA 输出; - 模拟前端:
audio_analog/adda_*负责模拟电路上电、时钟开闭。
其中"硬件重采样"体现在:DAC 引擎本身支持从 8K 到 96K 的一族固定输出采样率(AUDAC_SR*),当解码器输出采样率与 DAC 目标采样率不一致时,由驱动层的时钟与采样率管理(adda_clk_open(sr)、audio_dac_sr_api)完成重采样/时钟切换,保证输出时钟与解码数据速率匹配。
架构
flowchart TD
subgraph sg_Source["音频源 (Source)"]
Decoder["解码器 / 播放器"]
KeyVoice["提示音 d_key_voice"]
MIO["MIO 混音输入"]
end
subgraph sg_App["应用层 API (dac_api)"]
Regist["regist_dac_channel / unregist_dac_channel"]
Fill["fill_dac_fill_phy"]
Vol["dac_vol / phy_vol_set"]
SrApi["dac_sr_api / auout_sr_api"]
Mute["dac_mute / dac_fade"]
end
subgraph sg_Chan["DAC 通道管理 (DAC_MANAGE)"]
Sound["sound_out_obj *sound[]"]
Cbuf["环形缓冲 p_obuf"]
Kick["kick 回调"]
end
subgraph sg_Hw["硬件层 (audio_dac_cpu / analog)"]
AUDDAC["JL_AUDDAC 寄存器"]
AddaClk["adda_clk_open / adda_clk_close"]
APA["功放 APA (DAC0_APA_SEL)"]
end
Decoder -->|"注册通道"| Regist
Regist --> Sound
Sound --> Cbuf
KeyVoice --> Fill
MIO --> Fill
Fill -->|"混音/音量/钳位"| AUDDAC
Vol -->|"vol_tab / DAC_VOL"| AUDDAC
SrApi --> AddaClk
AddaClk -->|"采样率/重采样"| AUDDAC
Mute --> AUDDAC
AUDDAC -->|"模拟输出"| APA
APA --> Speaker["扬声器"]
架构说明:
- 音频源:解码器通过
regist_dac_channel把sound_out_obj(内含环形缓冲与 kick 回调)注册进 DAC 管理器;提示音、MIO 等其他来源直接写入 DAC 填充缓冲。 - DAC_MANAGE:
dac_api.c中的全局管理结构,保存最多DAC_CHANNEL_NUMBER路sound_out_obj指针与 kick 回调、当前数字音量、物理音量设置函数等,存放于.DAC_BUFFER内存段。 - 硬件层:
JL_AUDDAC寄存器组控制采样率(DAC_SR*)、通道使能、DMA、去直流(DAC0_CHPHSET)、抖动(DAC0_DITHER)与 APA 输出;adda_clk_open(sr)按目标采样率配置模拟时钟,即硬件重采样的时钟基准。 - 功放:
DAC0_APA_SEL位与apa_clk_sel()控制片内功放的使能与时钟,最终驱动扬声器。
关键实现
DAC 通道注册与环形缓冲
解码器要发声必须先注册通道。regist_dac_channel 接收 sound_out_obj(含数据缓冲 p_obuf)与 kick 回调;DAC 中断/填充逻辑按需从各通道环形缓冲取数。dac_cbuff_active 判断通道是否"就绪":当通道处于 B_DEC_PAUSE(暂停)或 B_DEC_FIRST(首包未满)状态时,需等环形缓冲填充过半才真正放行,防止启动瞬间输出半包数据:
SEC(.audio_d.text.cache.L2)
bool dac_cbuff_active(void *sound_hld)
{
sound_out_obj *psound = sound_hld;
if (psound->enable & (B_DEC_PAUSE | B_DEC_FIRST)) {
if (cbuf_get_data_size(psound->p_obuf) >= (cbuf_get_space(psound->p_obuf) / 2)) {
psound->enable &= ~B_DEC_FIRST;
}
return false;
} else {
return true;
}
}
来源:dac_api.c
设计意图:B_DEC_FIRST 是"首包欠载保护"——播放刚开始时缓冲内数据不足,若立刻读取会输出断续/噪声;待缓冲过半后再清除该标志,保证连续稳定输出。
多通道混音与数据填充
fill_dac_fill_phy 是 DAC 中断驱动的核心填充函数:它对所有已使能且活跃的通道执行"读环形缓冲 → 逐样点累加混音 → 软件音量缩放 → 防直流偏移 → 饱和钳位 → 写回输出缓冲"。
for (sp_cnt = 0; sp_cnt < sp_number; sp_cnt++) {
t_sp = 0;
for (i = 0; i < DAC_CHANNEL_NUMBER; i++) {
if (0 == rptr[i]) {
continue;
}
if (0 == olen[i]) {
rptr[i] = cbuf_read_alloc(dac_mge.sound[i]->p_obuf, &olen[i]);
if (0 == olen[i]) {
log_char('x');
rptr[i] = 0;
}
continue;
}
p_cnt[i]++;
t_sp += *rptr[i];
rptr[i]++;
if ((p_cnt[i] * 2) >= olen[i]) {
cbuf_read_updata(dac_mge.sound[i]->p_obuf, p_cnt[i] * 2);
olen[i] = 0;
p_cnt[i] = 0;
rptr[i] = cbuf_read_alloc(dac_mge.sound[i]->p_obuf, &olen[i]);
}
}
if ((NULL == dac_mge.phy_vol_set) && (MAX_PHY_VOL != dac_mge.vol_phy)) {
t_sp = (t_sp * dac_mge.vol_phy) / (MAX_PHY_VOL + 1);
}
t_sp += sp_buf[sp_cnt];
dac_sp_handle(t_sp);
if (t_sp > 32767) {
t_sp = 32767;
} else if (t_sp < -32768) {
t_sp = -32768;
}
sp_buf[sp_cnt] = t_sp;
}
来源:dac_api.c
要点:
- 每路通道通过
cbuf_read_alloc零拷贝取读指针,按样点累加,耗尽的段用cbuf_read_updata推进;log_char('x'/'y'/'z')打印欠载诊断字符。 - 软件音量:仅当未注册
phy_vol_set(即不采用硬件音量)且当前物理音量非最大时,对混音结果按vol_phy缩放,避免整数溢出。 dac_sp_handle(t_sp)是硬件辅助宏:每次调用在 ±1 间交替加到样点上,形成极小幅度的直流偏移补偿(配合去直流滤波器使用)。- 最终样点做 16-bit 饱和钳位(32767/-32768),防止多路混音溢出产生爆音。
音量控制:对数曲线与硬件音量
DAC 音量采用 0~31 级指数(近似对数听觉)曲线 vol_tab,从 0 到满幅 16383 递增,每级约 1.5~1.7 倍,保证人耳听感线性:
const u16 vol_tab[] = {
0, 164, 191, 223, 260, 303, 353, 412, 480, 559, 652, 760, 887,
1034, 1205, 1405, 1638, 1910, 2227, 2597, 3028, 3530, 4115,
4798, 5594, 6523, 7605, 8867, 10338, 12053, 14052, 16383
};
#define MAX_VOL_LEVEL ((sizeof(vol_tab)/2) - 1)
#define MAX_PHY_VOL vol_tab[MAX_VOL_LEVEL]
来源:dac_api.c
dac_mode_init(vol, phy_vol_set_func) 是音量初始化入口:若传入 phy_vol_set_func,后续音量由该函数(硬件写 DAC_VOL 寄存器,如 rdac_phy_vol)执行,软件只保存当前级数;否则直接写 JL_AUDDAC->DAC_VOL 为固定值 16384,音量完全由软件样点缩放完成。代码注释明确给出设计取向:"调节音量建议固定硬件数字音量,只调节软件样点幅值来改变音量"——即硬件音量固定在中间值,用软件乘法缩放避免低音量下信噪比恶化。
void dac_mode_init(u16 vol, void *phy_vol_set_func)
{
memset(&dac_mge, 0, sizeof(dac_mge));
/* 调节音量建议固定硬件数字音量,只调节软件样点幅值来改变音量 */
if (phy_vol_set_func) {
dac_mge.phy_vol_set = phy_vol_set_func;
} else {
SFR(JL_AUDDAC->DAC_VOL, 0, 16, 16384);
}
#if DAC_FADE_ENABLE
dac_mge.vol = vol;
if (dac_mge.vol > MAX_VOL_LEVEL) {
dac_mge.vol = MAX_VOL_LEVEL;
}
#else
dac_vol(0, vol);
#endif
auout_mode_init();
}
来源:dac_api.c
采样率配置与硬件重采样
DAC 引擎支持固定的输出采样率族,由 audio_dac_cpu.h 中的 AUDAC_SR* 宏定义(8K ~ 96K,含 44.1K/48K 等标准率):
//dac 输出采样率
#define AUDAC_SR96K (0b0000)
#define AUDAC_SR88K2 (0b0001)
#define AUDAC_SR64K (0b0010)
#define AUDAC_SR48K (0b0100)
#define AUDAC_SR44K1 (0b0101)
#define AUDAC_SR32K (0b0110)
#define AUDAC_SR24K (0b1000)
#define AUDAC_SR22K05 (0b1001)
#define AUDAC_SR16K (0b1010)
#define AUDAC_SR12K (0b1100)
#define AUDAC_SR11K025 (0b1101)
#define AUDAC_SR8K (0b1110)
#define AUDAC_SRBITS (0b1111)
应用层通过 dac_sr_api(sr)(宏映射为 auout_sr_api → audio_dac_sr_api)切换输出采样率;驱动层 adda_clk_open(u32 sr) 负责为该采样率配置模拟时钟(PLL/分频),adda_clk_close 在关闭时释放。这即是"硬件重采样"的时钟侧实现:DAC 输出率独立于解码率,由硬件时钟与数据速率匹配完成转换,应用只需在切换播放内容时调用 dac_sr_api 一次。初始化时 dac_init_api(sr, delay_flag) 完成 DAC 上电初始化——注释强调"上电开机调用需要延时至少 1.2ms",用于等待模拟电路稳定。
//1:有延时 0:没有延时, 上电开机调用需要延时至少1.2ms
void dac_init_api(u32 sr, bool delay_flag)
{
auout_init(sr, delay_flag);
}
void dac_sr_api(u32 sr)
{
auout_sr_api(sr);
}
void dac_off_api(void)
{
auout_off_api();
}
来源:dac_api.c
功放(APA)与模拟输出控制
功放使能与输出控制集中在 audio_dac_cpu.h 的寄存器宏中。DAC0_APA_SEL (1 << 9) 选择片内功放(APA)输出路径;DAC_CON0_DEFAULT 是默认配置,含中断使能(DAC0_IE)、DEM 抗混叠(DAC0_DEM_EN + DAC0_DEM_LS(3))、通道使能(DAC0_CHEN)、DMA 使能(DAC0_DMA_EN)、去直流滤波器(DAC0_CHPHSET,注释建议 14)与抖动(DAC0_DITHER(3),分频 8):
#define DAC0_APA_SEL (1 << 9)
#define DAC0_RELEASE_RST (1 << 8)
#define DAC0_PND (1 << 7)
#define DAC0_CPND (1 << 6)
#define DAC0_IE (1 << 5)
#define DAC0_DMA_EN (1 << 4)
#define DAC_CON0_DEFAULT \
( \
DAC0_IE |\
DAC0_DEM_EN |\
DAC0_DEM_LS(3) |\
DAC0_CHEN |\
DAC0_DMA_EN |\
DAC0_CHPHSET |\
DAC0_DITHER(3) \
)
驱动层通过 au_const_apa_en / au_const_dac_digital_en / au_const_dac_analog_en / au_const_adda_common_en 等常量控制数字/模拟/公共模块的电源门控顺序,apa_clk_sel() 选择功放时钟,audio_dac_analog_init() / adda_dac_analog_init() 完成模拟电路上电,adda_dac_analog_close() 关闭。这些步骤保证"先开数字 → 再开模拟 → 最后开功放"的上电次序,避免 pop 音。
静音与淡入淡出
dac_mute(bool) 提供硬件级静音(B_DAC_MUTE 标志位)。淡入淡出仅在 D_MBOX_SDK 配置下编译启用(DAC_FADE_ENABLE),通过 dac_fade_in() / dac_fade_out(delay) 实现;未启用时这些调用被展开为空宏,零开销:
#ifdef D_MBOX_SDK
#define DAC_FADE_ENABLE 1
#else
#define DAC_FADE_ENABLE 0
#endif
#if DAC_FADE_ENABLE
void dac_fade(void);
void dac_fade_in(void);
void dac_fade_out(u32 delay);
#define dac_fade_api dac_fade
#define dac_fade_in_api dac_fade_in
#define dac_fade_out_api dac_fade_out
#else
#define dac_fade_api()
#define dac_fade_in_api()
#define dac_fade_out_api(...)
#endif
来源:dac_api.h
硬件层面,DAC_CON1 寄存器提供 DAC1_FADE_EN、DAC1_FADE_STEP(n)(每步幅值)、DAC1_FADE_SLOW(n)(步进慢速分频)与 DAC1_FADE_BUSY,即淡入淡出可由硬件自动执行,CPU 只需配置步进参数并查询忙标志。该机制用于播放开始/结束、切歌或蓝牙断开时平滑过渡音量,消除突变的咔嗒声。
核心流程
sequenceDiagram
participant App as 应用/播放器
participant DAC as DAC_MANAGE (dac_api)
participant HW as JL_AUDDAC 驱动
participant APA as 功放/扬声器
App->>DAC: dac_mode_init(vol, phy_vol_set)
DAC->>DAC: 保存音量表与音量函数
DAC->>HW: auout_mode_init() 配置默认寄存器
App->>DAC: dac_init_api(sr, delay)
DAC->>HW: auout_init: 上电 + 1.2ms 延时
App->>DAC: regist_dac_channel(sound, kick)
DAC->>DAC: 登记 sound_out_obj 与 kick
App->>DAC: dac_sr_api(sr) 切换采样率
DAC->>HW: audio_dac_sr_api / adda_clk_open(sr)
HW-->>DAC: 时钟就绪 (硬件重采样基准)
loop DAC 中断周期
DAC->>DAC: fill_dac_fill_phy: 多通道混音 + 音量 + 防直流
DAC->>HW: DMA 填充输出缓冲
HW->>APA: 模拟输出 → 功放 → 扬声器
end
App->>DAC: dac_mute(true) / dac_fade_out
App->>DAC: unregist_dac_channel / dac_off_api
流程要点: 初始化顺序为"音量表 → 模式初始化 → DAC 上电(含 1.2ms 延时)→ 注册通道 → 采样率切换";此后 DAC 中断驱动的 fill_dac_fill_phy 每周期拉取各通道数据、混音并送入 DMA;关断顺序与上电相反(先淡出/静音、注销通道、最后 dac_off_api),以保证无 pop 音。
使用示例
基本使用:初始化与注册输出通道
以下序列是播放器打开 DAC 输出的标准做法:设置音量模式 → 初始化 DAC → 注册通道 → 设置采样率。
/* 初始化:0~31 级音量,phy_vol_set 传 NULL 则使用软件音量 */
dac_mode_init(25, NULL);
/* 上电初始化,delay_flag=1 表示上电需 1.2ms 延时 */
dac_init_api(44100, 1);
/* 注册解码器输出通道 */
regist_dac_channel(sound, kick);
/* 切换输出采样率(硬件重采样时钟跟随) */
dac_sr_api(44100);
音量与静音控制
/* 设置音量 0~31,返回实际生效等级 */
u8 cur = dac_vol(0, 20);
/* 静音 / 取消静音 */
dac_mute(true);
dac_mute(false);
/* 播放结束淡出后关闭 */
dac_fade_out_api(100);
dac_off_api();
来源:dac_api.h
硬件音量模式(注册物理音量函数)
若板级驱动提供硬件音量函数(如 rdac_phy_vol),将其注册给 DAC 管理器,音量由硬件寄存器执行:
#define D_PHY_VOL_SET_FUNC rdac_phy_vol
/* 传入硬件音量设置函数,软件不再缩放样点 */
dac_mode_init(30, D_PHY_VOL_SET_FUNC);
配置选项
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
DAC_PACKET_SIZE | 宏 | 32*8(SH57 为 32*2) | DAC 每次填充的数据包大小(采样点×字节) |
DAC_DECODER_BUF_SIZE | 宏 | DAC_PACKET_SIZE * 5 | 解码器侧 DAC 缓冲大小 |
SR_WORKMODE | 枚举 | SR_NORMAL | SR_NORMAL=随解码率切换;SR_CONSTANT=固定输出率 |
DAC_FADE_ENABLE | 宏 | 0(D_MBOX_SDK 时为 1) | 是否启用淡入淡出 API |
vol_tab | const u16[32] | 0~16383 指数表 | 0~31 级数字音量映射表 |
MAX_VOL_LEVEL | 宏 | 31 | 最大音量级数(vol_tab 元素数-1) |
MAX_PHY_VOL | 宏 | 16383 | 最大物理音量 |
DAC0_CHPHSET | 寄存器位 | 14 | 去直流滤波器配置(建议值 14) |
DAC0_DITHER(n) | 寄存器位 | 3(分频 8) | 抖动分频选择 |
SR_DEFAULT | 宏 | 32000 | DAC 默认输出采样率 |
AUDAC_SR* | 宏 | — | 8K~96K 固定输出采样率编码 |
API 参考
bool dac_mute(bool mute)
设置/取消 DAC 静音(硬件 B_DAC_MUTE 标志)。
- 参数:
mute— true 静音,false 取消。 - 返回:操作后的静音状态。
void dac_mode_init(u16 vol, void *phy_vol_set_func)
初始化 DAC 管理模式与音量。
- 参数:
vol— 初始音量级(0~31,超出则钳位到MAX_VOL_LEVEL);phy_vol_set_func— 硬件音量设置函数,NULL 表示软件音量并固定DAC_VOL=16384。 - 说明:内部清零
dac_mge,随后调用auout_mode_init()。
void dac_init_api(u32 sr, bool delay_flag)
DAC 上电初始化。
- 参数:
sr— 初始输出采样率(Hz);delay_flag— 1 表示上电需 ≥1.2ms 延时。 - 说明:透传
auout_init(sr, delay_flag)。
void dac_sr_api(u32 sr)
切换 DAC 输出采样率(触发硬件重采样时钟切换)。
- 参数:
sr— 目标采样率(如 44100/48000)。 - 说明:透传
auout_sr_api→audio_dac_sr_api,配合adda_clk_open配置时钟。
void dac_off_api(void)
关闭 DAC 输出,透传 auout_off_api()。
u32 fill_dac_fill(u8 *buf, u32 len, AUDIO_TYPE type)
DAC 数据填充入口(中断路径),内部调用 fill_dac_fill_phy 完成混音、音量缩放、防直流与钳位。
- 参数:
buf— 输出 PCM 缓冲;len— 字节数;type— 音频类型。 - 返回:填充字节数。
u8 dac_vol(char set, u8 vol)
设置/读取音量级。
- 参数:
set— 非 0 写入、0 读取;vol— 目标音量级 0~31。 - 返回:实际生效音量级。
bool regist_dac_channel(void *sound, void *kick) / bool unregist_dac_channel(void *dec_hld)
注册/注销一路 DAC 输出通道(sound_out_obj + kick 回调)。
- 参数:
sound—sound_out_obj*(含环形缓冲);kick— 数据请求回调;dec_hld— 解码器句柄。 - 返回:是否成功。
bool dac_cbuff_active(void *sound_hld)
查询通道环形缓冲是否就绪(首包/暂停保护逻辑,见上文实现)。
淡入淡出(DAC_FADE_ENABLE=1 时)
void dac_fade(void)/void dac_fade_in(void)/void dac_fade_out(u32 delay)- 未启用时对应宏展开为空。
故障模式与边界情况
- 缓冲欠载:
fill_dac_fill_phy在环形缓冲无数据时打印诊断字符x/y/z(对应首段耗尽、重取失败、初始取数失败),并输出静音样点;长时间欠载表现为"沙沙/断续"声,应检查解码器 kick 节奏与缓冲容量(DAC_DECODER_BUF_SIZE)。 - 启动半包:
B_DEC_FIRST保护——首包必须填满一半缓冲才放行,否则跳过该通道,避免起始爆音。 - 混音溢出:多通道累加可能超过 16-bit 范围,
fill_dac_fill_phy末尾做 32767/-32768 饱和钳位;叠加dac_sp_handle的 ±1 补偿不会破坏钳位。 - 上电时序:DAC 上电必须满足 ≥1.2ms 延时(
delay_flag),否则模拟电路未稳定即输出会产生异常;关断顺序颠倒也会引入 pop 音。 - 采样率不匹配:若解码率与
dac_sr_api设置不一致且未切换时钟,可能出现音调偏移;应在切换播放内容时同步调用dac_sr_api(SR_NORMAL模式)。 - 并发/中断:
dac_mge为全局单例,填充逻辑运行在 DAC 中断上下文;音量/通道注册等 API 应在任务上下文调用,避免在中断内修改共享结构导致竞态。
性能与运维
- 填充函数与活跃判断标注
SEC(.audio_d.text.cache.L2)/AT(.audio_d.text.cache.L2),将热路径代码/数据放入 L2 缓存段,减少中断时延;dac_mge位于.DAC_BUFFER段。 - 混音循环对每路通道逐样点累加,通道数越多 CPU 开销越大;
dac_cbuff_active提前跳过非活跃通道可降低空转开销。 - 硬件音量模式下软件仅保存级数,样点缩放由
DAC_VOL完成,节省 DSP 周期;建议按注释"固定硬件数字音量、软件调幅"的组合。 - 淡入淡出建议交给硬件
DAC1_FADE_*寄存器(步进/慢速/忙标志)执行,避免在中断中做软件包络。
扩展点
- 物理音量函数:
dac_mode_init的phy_vol_set_func参数是板级音量扩展点,可注册自定义硬件音量实现(默认rdac_phy_vol)。 - 新增输出通道:通过
regist_dac_channel可挂接任意sound_out_obj数据源(解码器、提示音、MIO 等),混音逻辑自动合并。 - 采样率族:
AUDAC_SR*枚举与adda_clk_open驱动层支持 8K~96K 全族输出率,新增采样率只需驱动层补充时钟表。 - 淡入淡出策略:
DAC_FADE_ENABLE编译开关控制 API 面,可在D_MBOX_SDK下扩展淡入淡出策略。
相关链接
- DAC 硬件寄存器与采样率定义:audio_dac_cpu.h
- DAC 应用层接口:dac_api.h
- DAC 实现与混音/音量逻辑:dac_api.c
- 平台 DAC 驱动实现:dac_cpu.c
- 相关主题:解码/播放子系统(decoder/player)、音效处理(sound_effect)、ADC 录音通路,请参阅音频子系统下对应页面。