音频设备接口 (DAC / ADC / APA)
本文档介绍 AW30N BLE SDK 中音频设备接口层的完整实现,涵盖 DAC 输出接口(audio_dac_api.c)、ADC 输入接口(audio_adc_api.c)以及 APA 功率放大器抽象(APA_CTRL_HDL / auout 抽象层),包括初始化、音量控制、通道注册、数据分发、同步与关闭等全部环节。
目的与范围
音频设备接口是 SDK 音频子系统中连接"数字音频流"与"模拟/功率硬件"的桥接层,向上为解码器、录音器、USB 音频(UAC)、蓝牙音频链路提供统一的通道注册与回调机制,向下屏蔽 DAC 芯片、ADC 采样前端与 APA 功放的具体硬件差异。
本页覆盖内容:
- DAC 输出管理:
DAC_MANAGE、音量表vol_tab、静音、通道注册与 ISR 数据搬运 - ADC 输入管理:
AUDIO_ADC_MANAGE、MIC / LINEIN / ALINK 模式、ISR 数据分发 - APA 抽象:
APA_CTRL_HDL apa_ops与auout/auin统一输入输出层 - 相关配置宏(
AUOUT_USE_APA、DAC_FADE_ENABLE、AUDIO_ADC_TYPE等)
不在此页范围的内容:
- 音频解码/编码格式(见
audio_codec_format.c相关页面) - 音频链路同步与多机互联(
audio_link系列,本页仅引用其同步接口) - 音频 RF 透传(
audio_rf_trans)与 USB 音频(usb_audio_interface) - 低层寄存器级驱动(
audio_dac_cpu.c、audio_adc_cpu.c中的硬件层细节)
概述
在 AW30N 音频子系统中,声音数据以 sound_out_obj 为基本流对象,在"前级(pre)"与"后级(later)"之间通过环形缓冲(circular buffer)传递,由 kick 回调驱动消费。DAC/ADC 接口层正是在这一框架上建立的:
- DAC 侧:解码器/音效链(sound_mge)把 PCM 数据写入注册到 DAC 的通道缓冲,
dac_mge维护最多AUDAC_CHANNEL_TOTAL路通道;音量以 32 级对数表vol_tab映射到物理增益,通过auout_phy_vol()下发给硬件(DAC 或 APA)。 - ADC 侧:硬件采样中断将数据填入
fill_audio_adc_fill(),该函数把同一份采样数据广播写入所有已注册的 ADC 通道缓冲,并调用各自的kick回调;通道数固定为 2(AUDIO_ADC_CHANNEL_TOTAL),单声道/立体声由track字段决定。 - APA 抽象:代码通过
_OTP_CONST_ APA_CTRL_HDL apa_ops引用功放控制句柄,并通过编译期宏AUOUT_USE_DAC == AUOUT_USE_APA决定数字音量是否生效;运行时统一走auout_*/auin_*接口,从而支持 DAC 直出与 APA 功放两种输出方案。
设计意图:把"设备接口"做成一个多路通道 + 回调驱动 + 硬件抽象的薄层,使上层音频框架(解码、录音、UAC、音频链路)无需关心具体硬件,也无需关心通道数量的细节,从而在低功耗 BLE SoC 上保持代码复用与实时性(关键路径均放在中断上下文 AT(.audio_isr_text) 段)。
架构
flowchart TD
subgraph sg_Upper["上层音频框架"]
Decoder["解码器 / sound_mge"]
Encoder["编码器 / 录音应用"]
UAC["USB 音频 (UAC)"]
ALink["audio_link 同步"]
end
subgraph sg_Dac["DAC 接口层 (audio_dac_api.c)"]
DacMge["DAC_MANAGE dac_mge"]
RegDac["regist_dac_channel()"]
VolCtl["stereo_dac_vol() / vol_tab"]
SyncDac["audio_dac_sync_once() / uac_audio_dac_percent()"]
end
subgraph sg_Adc["ADC 接口层 (audio_adc_api.c)"]
AdcMge["AUDIO_ADC_MANAGE audio_adc_mge"]
RegAdc["regist_audio_adc_channel()"]
FillAdc["fill_audio_adc_fill() (ISR)"]
end
subgraph sg_HwAbst["硬件抽象层"]
Auout["auout_init / auout_phy_vol / auout_phy_off"]
Auin["auin_init / auadc_open_mic / linein / alink"]
ApaOps["APA_CTRL_HDL apa_ops"]
end
subgraph sg_Hw["硬件"]
DacHw["DAC 数模转换"]
AdcHw["ADC 模数采样 (MIC/LINEIN)"]
Amp["APA 功放"]
end
Decoder --> RegDac
UAC --> RegDac
ALink --> SyncDac
RegDac --> DacMge
VolCtl --> DacMge
SyncDac --> DacMge
DacMge --> Auout
Auout --> ApaOps
Auout --> DacHw
ApaOps --> Amp
AdcHw --> FillAdc
FillAdc --> AdcMge
RegAdc --> AdcMge
AdcMge --> Encoder
Auin --> AdcHw
ALink -.->|"AUDIO_ADC_TYPE == AUIN_USE_ALINK"| Auin
架构说明:
DAC_MANAGE dac_mge是 DAC 侧唯一的管理对象(全局变量,位于专用数据段.audio_dac_api.data),持有每路通道的前级/后级sound_out_obj、kick回调和通道位图ch。AUDIO_ADC_MANAGE audio_adc_mge是 ADC 侧的管理对象,结构与 DAC 侧对称,但通道总数固定为 2。auout_*/auin_*是硬件抽象入口:DAC 侧所有硬件操作(初始化、采样率、物理音量、关断)都经auout_*转发,由编译期配置决定最终落在 DAC 还是 APA;ADC 侧则经auin_*与auadc_open_*打开对应模拟前端。- 上层框架(解码器、UAC、音频链路)只与
regist_dac_channel/regist_audio_adc_channel交互,不直接触碰硬件,这是该层设计的关键解耦点。
DAC 输出接口
DAC 接口层位于 audio_dac_api.c,通过编译段指令将全部代码与数据放入 .audio_dac_api.* 段,方便链接器在低功耗场景下整体裁剪或搬移。其核心是一个全局管理结构 DAC_MANAGE dac_mge。
音量表与物理增益映射
vol_tab[] 是一张 32 级(0~31)的指数型音量查找表,从 0 到 16384(即 14 bit 满幅),每级增益约按 1.5 dB 步进递增。音量接口层只保存逻辑级数(vol_l / vol_r),真正下发给硬件的是查表得到的物理值(vol_l_phy / vol_r_phy)。这样设计的好处是:上层只关心 0~31 的抽象音量,物理映射(含 APA 非线性补偿)被隔离在接口层内。
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 ,
16384
};
Source: audio_dac_api.c
MAX_VOL_LEVEL 由表长推导((sizeof(vol_tab)/2) - 1 = 31),MAX_PHY_VOL 取表末值 16384,供上层查询满幅物理音量。
初始化流程
初始化分为三层:dac_mode_init() 负责软件状态清零与音量初始化,dac_init() / dac_init_api() 负责硬件打开,dac_sr_api() 负责采样率切换。
void dac_mode_init(u16 vol)
{
memset(&dac_mge, 0, sizeof(dac_mge));
#if (AUOUT_USE_DAC == AUOUT_USE_APA)
dac_mge.flag |= B_DAC_DIG_VOL;
#endif
#if DAC_FADE_ENABLE
if (vol > MAX_VOL_LEVEL) {
vol = MAX_VOL_LEVEL;
}
dac_mge.vol_l = vol;
dac_mge.vol_r = vol;
#else
stereo_dac_vol(0, vol, vol);
#endif
/* apa_clr_buf(); */
/* apa_resource_init((void *)&apa_ops); */
auout_mode_init();
}
Source: audio_dac_api.c
关键设计点:
- 当配置为 APA 输出(
AUOUT_USE_DAC == AUOUT_USE_APA)时置B_DAC_DIG_VOL标志——APA 场景需要数字音量参与控制;而 DAC 直出场景则由模拟增益完成。 DAC_FADE_ENABLE开启时,音量走渐入渐出(fade)机制,初始化只记录逻辑级数、不立即写物理音量(实际渐变由audio_dac_fade.c驱动);关闭时直接调用stereo_dac_vol(0, vol, vol)同步写物理音量。- 最终一律调用
auout_mode_init()完成硬件抽象层的模式初始化。
void dac_init_api(u32 sr)
{
if (false == audio_clk_open_check()) {
return;
}
dac_init(sr, 1);
}
void dac_sr_api(u32 sr)
{
if (false == audio_clk_open_check()) {
return;
}
auout_sr_api(sr);
}
u32 dac_sr_read(void)
{
/* return apa_sr_read(); */
if (false == audio_clk_open_check()) {
return E_AUDIO_CLK;
}
return auout_sr_read();
}
void dac_off_api(void)
{
if (false == audio_clk_open_check()) {
return;
}
auout_phy_off();
}
Source: audio_dac_api.c
可见所有对外 API 的第一步都是 audio_clk_open_check() 时钟门控检查:音频时钟未打开时直接短路返回(dac_sr_read() 返回错误码 E_AUDIO_CLK)。这是低功耗设计的体现——避免在音频时钟域掉电时访问硬件造成总线挂死。代码中保留了 apa_phy_init / apa_trim_api / apa_analog_open_api 等被注释掉的 APA 直调代码,说明该版本已统一收敛到 auout_* 抽象。
音量控制与静音
u8 stereo_dac_vol(char set, u8 vol_l, u8 vol_r)
{
if ('+' == set) {
dac_mge.vol_l++;
dac_mge.vol_r++;
} else if ('-' == set) {
if (0 != dac_mge.vol_l) {
dac_mge.vol_l--;
}
if (0 != dac_mge.vol_r) {
dac_mge.vol_r--;
}
} else if ('r' == set) {
return dac_mge.vol_l;
} else if ('R' == set) {
return dac_mge.vol_r;
} else {
dac_mge.vol_l = vol_l;
dac_mge.vol_r = vol_r;
}
if (dac_mge.vol_l > MAX_VOL_LEVEL) {
dac_mge.vol_l = MAX_VOL_LEVEL;
}
if (dac_mge.vol_r > MAX_VOL_LEVEL) {
dac_mge.vol_r = MAX_VOL_LEVEL;
}
if (0 == (dac_mge.flag & B_DAC_MUTE)) {
#if DAC_FADE_ENABLE
if (0 == (dac_mge.flag & B_DAC_FADE_EN))
#endif
{
dac_mge.vol_l_phy = vol_tab[dac_mge.vol_l];
dac_mge.vol_r_phy = vol_tab[dac_mge.vol_r];
}
dac_mge.flag &= ~B_DAC_MUTE;
}
__dac_vol_end:
auout_phy_vol(dac_mge.vol_l_phy, dac_mge.vol_r_phy);
return dac_mge.vol_l;
}
u8 dac_vol(char set, u8 vol)
{
return stereo_dac_vol(set, vol, vol);
}
bool dac_mute(bool mute)
{
if (mute) {
dac_mge.flag |= B_DAC_MUTE;
dac_mge.vol_l_phy = 0;
dac_mge.vol_r_phy = 0;
} else {
dac_mge.flag &= ~B_DAC_MUTE;
dac_mge.vol_l_phy = vol_tab[dac_mge.vol_l];
dac_mge.vol_r_phy = vol_tab[dac_mge.vol_r];
}
auout_phy_vol(dac_mge.vol_l_phy, dac_mge.vol_r_phy);
return true;
}
Source: audio_dac_api.c
stereo_dac_vol() 通过 set 字符实现"命令式"控制:'+'/'-' 步进加减、'r'/'R' 查询左右声道、其他值直接设定左右音量;返回值恒为左声道逻辑级数。静音不改变逻辑级数,只把物理值强制置 0,因此取消静音可无缝恢复原音量。Fade 开启时,物理值更新被 B_DAC_FADE_EN 标志抑制,改由 fade 模块逐步逼近目标值,避免爆音(pop noise)。
通道注册与多路复用
DAC 支持最多 AUDAC_CHANNEL_TOTAL 路并发流,每路由一个位 BIT(i) 标记占用。注册时逐位查找空闲通道,把前级/后级 SOUND 与 kick 回调存入 dac_mge;注销时在关中断保护下清位并解除 B_DEC_OBUF_EN。
bool regist_dac_channel(void *psound_pre, void *psound_later, void *kick)
{
u8 i;
for (i = 0; i < AUDAC_CHANNEL_TOTAL; i++) {
if (dac_mge.ch & BIT(i)) {
continue;
}
dac_mge.kick[i] = kick;
dac_mge.sound_pre[i] = psound_pre;
dac_mge.sound_later[i] = psound_later;
dac_mge.ch |= BIT(i);
return true;
}
return false;
}
Source: audio_dac_api.c
bool unregist_dac_channel(void *psound)
{
u8 i;
sound_out_obj *ps = psound;
for (i = 0; i < AUDAC_CHANNEL_TOTAL; i++) {
if (0 == (dac_mge.ch & BIT(i))) {
continue;
}
if (dac_mge.sound_later[i] == psound) {
local_irq_disable();
dac_mge.ch &= ~BIT(i);
dac_mge.sound_pre[i] = NULL;
dac_mge.sound_later[i] = NULL;
dac_mge.kick[i] = NULL;
ps->enable &= ~B_DEC_OBUF_EN;
local_irq_enable();
break;
}
}
return true;
}
Source: audio_dac_api.c
注销时用 local_irq_disable()/local_irq_enable() 包裹整个清理过程,防止 ISR(如 dac_cbuff_active)正在读取该通道时出现悬垂指针。这是典型的"中断上下文安全"写法:ISR 侧只读、管理侧在关中断下写。
dac_cbuff_active() 位于 AT(.audio_isr_text) 段,供 ISR 判断某路通道的环形缓冲是否达到可激活条件:首启(B_DEC_FIRST)或暂停(B_DEC_PAUSE)状态下,只有缓冲数据量达到一半以上才激活,避免"饿着跑"造成断续。
同步与 UAC 水位计算
uac_audio_dac_percent() 用于 USB 音频(UAC)场景下的缓冲水位估算:读取前级 SRC 输入/输出采样率与环形缓冲数据量,折算成 0~100 的百分比,送入 audio_link_sync_accumulate() 供 UAC 同步算法使用。audio_dac_sync_once() 则在每个同步周期遍历所有通道,调用 audio_link_sync_percent() 做多机(如 TWS/多音箱)播放进度对齐,再 audio_link_sync_reset() 复位统计。
输出钩子
void sound_out_hook(sound_out_obj *psound)
{
#if HAS_MIO_EN
if (NULL != psound) {
d_mio_start(psound->mio);
}
#endif
}
Source: audio_dac_api.c
当开启 MIO(多路 IO 音频)时,DAC 输出启动会联动 d_mio_start(),将 SOUND 关联的 MIO 数据流一并启动,保证多路输出的时钟同步。
ADC 输入接口
ADC 接口层位于 audio_adc_api.c,与 DAC 侧对称,但通道总数固定为 2(AUDIO_ADC_CHANNEL_TOTAL),并额外管理一个采样缓冲池与控制句柄。
数据缓冲与控制句柄
u32 audio_adc_buf[AUDIO_ADC_PACKET_SIZE * AUDIO_ADC_SP_SIZE / 4 ] ;//AT(.AUDIO_ADC_BUFFER);
_OTP_CONST_ AUDIO_ADC_CTRL_HDL c_audio_adc_hdl = {
.buf = (void *) &audio_adc_buf[0],
.pns = AUDIO_ADC_SP_PNS,
.sp_total = sizeof(audio_adc_buf) / AUDIO_ADC_SP_SIZE,
.sp_size = AUDIO_ADC_SP_SIZE,
};
Source: audio_adc_api.c
采样数据缓冲 audio_adc_buf 以"采样包"(sample packet)为组织单位,sp_size/sp_total/pns(包数/采样数)描述其容量;控制句柄 c_audio_adc_hdl 放在 OTP 常量区(_OTP_CONST_),减少 RAM 占用并保证内容只读。
初始化与模式选择
u32 audio_adc_init_api(u32 sr, AUDIO_ADC_MODE mode, u32 throw_sp_num)
{
if (sr == 0) {
return E_ADC_SR;
}
u32 res = 0;
if (AUDIO_ADC_MIC == mode) {
audio_adc_mge.track = 1;
auadc_open_mic();
} else if (AUDIO_ADC_LINEIN == mode) {
auadc_open_linein();
audio_adc_mge.track = 1;
} else if (AUDIO_LINK == mode) {
#if defined (AUDIO_ADC_TYPE) && (AUDIO_ADC_TYPE == AUIN_USE_ALINK)
auadc_open_alink();
audio_adc_mge.track = 2;
#endif
} else {
audio_adc_mge.track = 1;
}
res = auin_init((void *)&c_audio_adc_hdl, sr, AUDIO_ADC_CON_DEFAULT, throw_sp_num);
return res;
}
void audio_adc_off_api(void)
{
audio_adc_phy_off();
audio_adc_analog_off();
}
Source: audio_adc_api.c
模式语义:
AUDIO_ADC_MIC:打开麦克风模拟前端,单声道(track = 1)。AUDIO_ADC_LINEIN:打开线路输入,单声道。AUDIO_LINK:仅当编译期配置AUDIO_ADC_TYPE == AUIN_USE_ALINK时有效,打开音频链路输入(双声道track = 2)。- 采样率为 0 时直接返回错误码
E_ADC_SR,不进入硬件初始化。
throw_sp_num 参数允许调用方在启动时主动丢弃前 N 个采样包,用于避开上电/切换瞬间的模拟前端不稳定数据。
ISR 数据分发
AT(.audio_isr_text)
void fill_audio_adc_fill(u8 *buf, u32 len)
{
u32 i;
for (i = 0; i < AUDIO_ADC_CHANNEL_TOTAL; i++) {
if (0 == (audio_adc_mge.ch & BIT(i))) {
continue;
}
if (0 == (audio_adc_mge.sound_pre[i]->enable & B_DEC_RUN_EN)) {
continue;
}
u32 wlen;
wlen = cbuf_write(audio_adc_mge.sound_pre[i]->p_obuf, buf, len);
if (wlen != len) {
log_char('3');
}
if (NULL != audio_adc_mge.kick[i]) {
audio_adc_mge.kick[i](https://gitee.com/Jieli-Tech/AW30N/blob/main/audio_adc_mge.sound_pre%5Bi%5D);
}
}
}
Source: audio_adc_api.c
这是 ADC 数据流的单生产者/多消费者模型:硬件中断(或 DMA 回调)每得到一块采样数据就调用 fill_audio_adc_fill(),它把同一块数据广播写入所有已注册且处于运行态(B_DEC_RUN_EN)的通道环形缓冲,随后触发各自 kick 回调(如编码器取数)。缓冲写满时打印 '3' 字符作为水位告警(调试通道,避免中断内重负载日志)。
通道注册与声道标记
bool regist_audio_adc_channel(void *psound_pre, void *psound_later, void *kick)
{
u8 i;
for (i = 0; i < AUDIO_ADC_CHANNEL_TOTAL; i++) {
if (audio_adc_mge.ch & BIT(i)) {
if (audio_adc_mge.sound_pre[i] == psound_pre) {
log_error("current sound have been regist the same %d\n", i);
}
continue;
}
}
///--------------------------
for (i = 0; i < AUDIO_ADC_CHANNEL_TOTAL; i++) {
if (audio_adc_mge.ch & BIT(i)) {
continue;
}
if (1 == audio_adc_mge.track) {
((sound_out_obj *)psound_pre)->info &= ~B_STEREO;
} else if (2 == audio_adc_mge.track) {
((sound_out_obj *)psound_pre)->info |= B_STEREO;
}
audio_adc_mge.sound_pre[i] = psound_pre;
audio_adc_mge.sound_later[i] = psound_later;
audio_adc_mge.kick[i] = kick;
audio_adc_mge.ch |= BIT(i);
return true;
}
return false;
}
Source: audio_adc_api.c
注册时根据 track 值自动改写 SOUND 的立体声标志(B_STEREO):单声道输入清除该位,双声道(ALINK)置位。注销逻辑与 DAC 侧一致,用 local_irq_disable() 保护,防止与 fill_audio_adc_fill ISR 竞争。
核心流程
ADC 采集 → 应用消费(录音/回声参考)
sequenceDiagram
participant HW as ADC 硬件/MIC
participant ISR as fill_audio_adc_fill() (ISR)
participant MGE as AUDIO_ADC_MANAGE
participant CB as 通道环形缓冲
participant APP as 编码器/应用 kick 回调
participant PRE as sound_pre (前级)
HW->>ISR: 采样数据 buf/len
ISR->>MGE: 遍历 ch 位图
alt 通道已注册且 B_DEC_RUN_EN
ISR->>CB: cbuf_write(p_obuf, buf, len)
CB-->>ISR: wlen (可能 < len)
ISR->>APP: kick(sound_pre)
APP->>PRE: 取数/编码
else 缓冲写满
ISR->>ISR: log_char('3') 水位告警
end
DAC 播放 → 硬件输出
sequenceDiagram
participant SRC as 解码器/音效链
participant REG as regist_dac_channel()
participant MGE as DAC_MANAGE
participant CB as 通道环形缓冲
participant AOUT as auout_phy_vol/auout_sr_api
participant HW as DAC/APA 硬件
SRC->>REG: 注册前级/后级 SOUND + kick
REG->>MGE: 占用空闲通道位 BIT(i)
SRC->>CB: 写 PCM 数据
MGE->>AOUT: 音量查表 vol_tab → 物理值
AOUT->>HW: 输出增益/采样率
MGE->>MGE: audio_dac_sync_once() 多机对齐 (audio_link)
关键时序要点:
- 注册先于启动:任何数据流必须先通过
regist_*_channel取得通道位,ISR 侧才会计入广播/消费集合。 - 中断内零锁竞争:ISR 只读
ch位图与指针;注销路径用关中断保证一致性。 - 水位闭环:DAC 侧通过
uac_audio_dac_percent()把缓冲占用率反馈给 UAC 同步,ADC 侧通过kick让消费端立即取数,两者共同维持环形缓冲在稳态水位附近,避免 underrun/overrun。
使用示例
示例 1:DAC 初始化 + 音量 + 通道注册(播放链路)
/* 1. 初始化 DAC(软件状态 + 硬件打开) */
dac_mode_init(20); /* 初始逻辑音量 20/31 */
dac_init_api(44100); /* 44.1kHz 打开音频时钟与硬件 */
/* 2. 注册解码输出到 DAC 通道 */
regist_dac_channel(psound_pre, psound_later, kick_func);
/* 3. 运行时音量控制:步进、查询、设定 */
dac_vol('+', 0); /* 加一档 */
u8 lv = dac_vol('r', 0); /* 查询左声道级数 */
dac_vol(0, 25); /* 直接设为 25 级 */
/* 4. 静音/恢复 */
dac_mute(true);
dac_mute(false);
/* 5. 采样率切换与关闭 */
dac_sr_api(48000);
dac_off_api();
示例 2:ADC MIC 录音链路
/* 1. 初始化 ADC:MIC 模式,44.1kHz,丢弃前 5 个采样包 */
audio_adc_mode_init();
u32 res = audio_adc_init_api(44100, AUDIO_ADC_MIC, 5);
if (res != 0) {
/* 处理初始化失败(如 E_ADC_SR) */
}
/* 2. 注册录音通道:数据将被广播写入并触发 kick */
regist_audio_adc_channel(psound_pre, psound_later, encoder_kick);
/* 3. 停止采集 */
unregist_audio_adc_channel(psound_pre);
audio_adc_off_api();
Source: audio_adc_api.c、audio_adc_api.c
示例 3:ISR 侧数据广播(硬件中断回调内)
AT(.audio_isr_text)
void fill_audio_adc_fill(u8 *buf, u32 len)
{
u32 i;
for (i = 0; i < AUDIO_ADC_CHANNEL_TOTAL; i++) {
if (0 == (audio_adc_mge.ch & BIT(i))) {
continue;
}
if (0 == (audio_adc_mge.sound_pre[i]->enable & B_DEC_RUN_EN)) {
continue;
}
u32 wlen;
wlen = cbuf_write(audio_adc_mge.sound_pre[i]->p_obuf, buf, len);
if (wlen != len) {
log_char('3'); /* 缓冲满告警 */
}
if (NULL != audio_adc_mge.kick[i]) {
audio_adc_mge.kick[i](https://gitee.com/Jieli-Tech/AW30N/blob/main/audio_adc_mge.sound_pre%5Bi%5D);
}
}
}
Source: audio_adc_api.c
这段代码展示了 ADC 侧"一进多出"的典型用法:同一份采样数据同时喂给多个消费者(例如主录音 + 回声参考/唤醒词检测),消费者各自拥有独立环形缓冲,互不阻塞。
配置选项
| 配置宏 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
AUOUT_USE_DAC / AUOUT_USE_APA | 枚举宏 | 取决于板级配置 | 输出方案选择;AUOUT_USE_DAC == AUOUT_USE_APA 时 DAC 侧置 B_DAC_DIG_VOL(数字音量参与 APA 增益控制) |
DAC_FADE_ENABLE | bool | 0/1 | 音量渐入渐出;开启时音量修改先记逻辑级数,物理值由 fade 模块驱动 |
HAS_MIO_EN | bool | 0/1 | 多路 IO 音频联动;开启时 sound_out_hook() 调用 d_mio_start() |
AUDIO_ADC_TYPE | 宏 | AUIN_USE_ALINK 等 | ADC 输入方案;== AUIN_USE_ALINK 时启用 auadc_open_alink() 与双声道 |
AUDIO_ADC_CHANNEL_TOTAL | 常量 | 2 | ADC 通道总数(audio_adc_api.c 内定义) |
AUDIO_ADC_PACKET_SIZE | 常量 | 板级配置 | ADC 采样包大小,决定 audio_adc_buf[] 容量 |
AUDIO_ADC_SP_SIZE / AUDIO_ADC_SP_PNS | 常量 | 板级配置 | 采样包字节数与每包采样数,描述控制句柄 c_audio_adc_hdl |
AUDIO_ADC_CON_DEFAULT | 常量 | 板级配置 | 传给 auin_init() 的默认连接配置 |
AUDAC_CHANNEL_TOTAL | 常量 | 板级配置 | DAC 通道总数,决定 dac_mge 各数组长度 |
说明:以上宏在板级头文件(如
app_config.h、audio_v1.lua配置导出的头文件)中定义;本文档列出的是接口层代码实际引用到的宏。AUDAC_CHANNEL_TOTAL与AUDIO_ADC_CHANNEL_TOTAL是两套独立的通道容量,互不影响。
API 参考
DAC 接口(audio_dac_api.c)
| 函数 | 签名 | 说明 |
|---|---|---|
dac_mode_init | void dac_mode_init(u16 vol) | 清零 dac_mge、设置初始音量并调用 auout_mode_init();APA 方案时置 B_DAC_DIG_VOL |
dac_init | void dac_init(u32 sr, u32 delay_flag) | 底层初始化入口,转发到 auout_init(sr, delay_flag) |
dac_init_api | void dac_init_api(u32 sr) | 带时钟门控检查的对外初始化(内部 dac_init(sr, 1)),时钟未开则直接返回 |
dac_sr_api | void dac_sr_api(u32 sr) | 设置采样率,转发 auout_sr_api(sr) |
dac_sr_read | u32 dac_sr_read(void) | 读取当前采样率;时钟未开返回 E_AUDIO_CLK |
dac_off_api | void dac_off_api(void) | 关断输出(auout_phy_off()) |
stereo_dac_vol | u8 stereo_dac_vol(char set, u8 vol_l, u8 vol_r) | 立体声音量控制;'+'/'-' 步进、'r'/'R' 查询、其他值设定;返回左声道级数 |
dac_vol | u8 dac_vol(char set, u8 vol) | 单声道封装,stereo_dac_vol(set, vol, vol) |
dac_mute | bool dac_mute(bool mute) | 静音/取消静音;静音只清物理值,保留逻辑级数 |
regist_dac_channel | bool regist_dac_channel(void *psound_pre, void *psound_later, void *kick) | 注册 DAC 通道;通道满返回 false |
unregist_dac_channel | bool unregist_dac_channel(void *psound) | 注销通道(关中断保护) |
dac_cbuff_active | bool dac_cbuff_active(void *sound_hld) | ISR 内判断缓冲是否可激活(AT(.audio_isr_text)) |
uac_audio_dac_percent | void uac_audio_dac_percent(u8 ch) | 计算通道缓冲水位百分比并送入 audio_link_sync_accumulate() |
audio_dac_sync_once | void audio_dac_sync_once(void) | 周期遍历所有通道做 audio_link 播放进度同步 |
sound_out_hook | void sound_out_hook(sound_out_obj *psound) | 输出钩子;HAS_MIO_EN 时联动 d_mio_start() |
get_dac_max_phy_vol | u32 get_dac_max_phy_vol(void) | 返回满幅物理音量 16384(always_inline) |
ADC 接口(audio_adc_api.c)
| 函数 | 签名 | 说明 |
|---|---|---|
audio_adc_mode_init | void audio_adc_mode_init(void) | 清零 audio_adc_mge |
audio_adc_init_api | u32 audio_adc_init_api(u32 sr, AUDIO_ADC_MODE mode, u32 throw_sp_num) | 初始化:按 mode 打开 MIC/LINEIN/ALINK 并设置 track,调用 auin_init();sr == 0 返回 E_ADC_SR |
audio_adc_off_api | void audio_adc_off_api(void) | 关断:audio_adc_phy_off() + audio_adc_analog_off() |
fill_audio_adc_fill | void fill_audio_adc_fill(u8 *buf, u32 len) | ISR 数据广播分发(AT(.audio_isr_text)) |
regist_audio_adc_channel | bool regist_audio_adc_channel(void *psound_pre, void *psound_later, void *kick) | 注册 ADC 通道;按 track 改写 B_STEREO 标志 |
unregist_audio_adc_channel | bool unregist_audio_adc_channel(void *psound) | 注销通道(关中断保护) |
返回值/错误码
E_ADC_SR:ADC 初始化时采样率参数为 0(audio_adc_init_api返回)。E_AUDIO_CLK:音频时钟未打开时dac_sr_read()的返回码。false:regist_dac_channel/regist_audio_adc_channel在通道全部占用时返回。
故障模式、边界情况与并发
- 时钟未开先访问硬件:所有 DAC 对外 API 首行执行
audio_clk_open_check(),时钟域掉电时静默短路(dac_sr_read返回E_AUDIO_CLK)。上层应先保证audio_clk打开,否则初始化被吞掉。 - 通道耗尽:DAC/ADC 通道数有限(
AUDAC_CHANNEL_TOTAL/AUDIO_ADC_CHANNEL_TOTAL)。注册失败返回false,调用方应释放不用的通道(注销)或复用已注册通道。 - 重复注册:ADC 侧对已占用通道做
log_error("current sound have been regist the same %d")提示后继续跳过,不会覆盖已有通道,避免双重登记导致数据双写。 - ISR 与管理的竞争:注销路径用
local_irq_disable()/enable()包裹指针清空,而fill_audio_adc_fill/dac_cbuff_active在中断中只读位图与指针,保证并发安全。注意:local_irq_disable是全局关中断,临界区必须尽量短(仅几条赋值),不能在中断关闭期间调用阻塞函数。 - 缓冲溢出:
fill_audio_adc_fill中cbuf_write返回值小于len时打印'3'字符告警。消费端kick处理不及时会导致采样丢失(录音缺口),应保证 kick 回调足够快或加大缓冲(AUDIO_ADC_PACKET_SIZE)。 - 首启/暂停饿死:
dac_cbuff_active在B_DEC_FIRST/B_DEC_PAUSE下要求缓冲水位 ≥ 50% 才激活,若前级供给不足,输出可能长时间不启动——这是有意设计(防止断续),排查时应检查前级数据注入。 - 采样率非法:
audio_adc_init_api对sr == 0直接返回E_ADC_SR,不触碰硬件;DAC 侧由audio_clk_open_check兜底。 - 音量越界:
stereo_dac_vol对逻辑级数做> MAX_VOL_LEVEL钳位,dac_mode_init在 fade 开启时同样钳位,防止vol_tab[]数组越界。
性能与运维要点
- 中断路径极简化:
fill_audio_adc_fill与dac_cbuff_active被显式放入AT(.audio_isr_text)段,确保 ISR 代码与数据落在快速可寻址空间;循环体内仅做位图判断 +cbuf_write+ 回调,无锁、无动态分配。 - 链接段管理:DAC 接口层整体放入
.audio_dac_api.text/.data/.bss/.text.const段,配合 SDK 的段裁剪工具,可在不使用音频输出时把整段代码排除出镜像,节省 Flash/RAM。 - 音量查表 O(1):
vol_tab为 32 项常量表,运行期零计算开销;物理增益下发收敛到单个auout_phy_vol()调用,避免多次寄存器写。 - 同步开销:
audio_dac_sync_once()按同步周期遍历全部 DAC 通道做audio_link对齐,通道数越多开销越大;多通道场景应评估同步周期与通道数的乘积。 - 调试手段:缓冲溢出打印
'3'、ADC 重复注册打印log_error,属于轻量级调试通道;正式版本可关闭LOG_TAG_CONST日志级别(DAC 侧默认OFF)。
扩展点
- 硬件抽象层(auout/auin):DAC/ADC 接口层不直接操作寄存器,而是调用
auout_init/auout_sr_api/auout_phy_vol/auout_phy_off与auin_init/auadc_open_mic/auadc_open_linein/auadc_open_alink。新增输出硬件(如外置 Codec)只需在抽象层实现对应接口,DAC 接口层无需改动。 - APA 控制句柄:
extern _OTP_CONST_ APA_CTRL_HDL apa_ops是 OTP 常量句柄,芯片出厂时固化功放校准参数;上层通过AUOUT_USE_DAC == AUOUT_USE_APA编译开关决定是否启用数字音量补偿,适配不同功放特性。 - 通道注册机制:
regist_*_channel(psound_pre, psound_later, kick)三参数模式是通用流注册契约——任何数据消费者(编码器、UAC、唤醒词、回声参考、audio_link 对端)都可作为新通道接入,无需修改接口层。 - kick 回调:每个通道独立的
kick函数指针是主要的扩展钩子,可在回调中实现任意消费逻辑(编码、滤波、转发)。 - Fade 模块:
DAC_FADE_ENABLE将音量渐变交给audio_dac_fade.c,可通过该模块的策略函数定制渐变曲线与步进。
相关链接
- 音频编解码格式 (audio_codec_format):解码器侧与 DAC 通道对接的格式协商
- 音频链路 (audio_link):
audio_dac_sync_once/audio_link_sync_*所依赖的多机同步模块 - USB 音频接口 (usb_audio_interface):
uac_audio_dac_percent服务的 UAC 同步场景 - 音频 RF 透传 (audio_rf_trans):基于 ADC/DAC 通道的无线音频收发
- 硬件驱动层:audio_dac_cpu.c、audio_adc_cpu.c:寄存器级 DAC/ADC 驱动(
auout/auin的具体实现) - 板级配置:audio_v1.lua:
AUOUT_USE_DAC/APA、ADC 采样参数等板级音频配置