杰理 SDK 文档中心
首页
首页
  • 项目概览

    • SDK 简介与核心特性
    • 芯片平台与硬件资料
    • SDK 版本与发布信息
  • 快速开始

    • 环境搭建与工具链
    • 编译工程
    • 烧录与量产工具
  • 工程结构与构建系统

    • 工程目录布局
    • 构建与链接配置
  • 应用层开发

    • mbox_flash 应用框架
    • 板级支持包 (BSP)
    • 公共应用模块
    • UI 显示子系统
  • 蓝牙子系统

    • BLE 控制器、链路层与 HCI 传输
    • GATT 服务框架
    • BLE 应用示例:遥控器 / Dongle / 对讲机
    • 经典蓝牙支持
  • 音频子系统

    • 音频编解码器
    • 音频设备接口 (DAC / ADC / APA)
    • 音效处理与 EQ
    • 播放、录音与 MIO 工作流
  • 设备与文件系统

    • 存储设备驱动 (NorFlash / SDMMC / USB)
    • 文件系统 (FAT / nor_fs / SYDF)
    • 设备管理框架 (dev_mg)
  • 系统服务与电源管理

    • 消息机制 (msg / hot_msg)
    • 配置与参数存储 (app_config / VM)
    • 电源管理 (SOFT OFF / POWER DOWN)
  • 固件升级

    • 升级框架总览 (code_v1 / code_v2)
    • 双 Bank 升级机制
    • 升级通道:UART / 测试盒 / BLE OTA / USB / SD
  • 补丁包与版本维护

    • 版本升级补丁链 (v1.1.0 → v1.4.0)
    • 问题修复补丁
    • 固件裁剪与资源优化
  • 开发工具与支持

    • 辅助工具与脚本
    • 文档、配置说明与常见问题

音频设备接口 (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)

关键时序要点:

  1. 注册先于启动:任何数据流必须先通过 regist_*_channel 取得通道位,ISR 侧才会计入广播/消费集合。
  2. 中断内零锁竞争:ISR 只读 ch 位图与指针;注销路径用关中断保证一致性。
  3. 水位闭环: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();

Source: audio_dac_api.c、audio_dac_api.c、audio_dac_api.c

示例 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_ENABLEbool0/1音量渐入渐出;开启时音量修改先记逻辑级数,物理值由 fade 模块驱动
HAS_MIO_ENbool0/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常量2ADC 通道总数(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_initvoid dac_mode_init(u16 vol)清零 dac_mge、设置初始音量并调用 auout_mode_init();APA 方案时置 B_DAC_DIG_VOL
dac_initvoid dac_init(u32 sr, u32 delay_flag)底层初始化入口,转发到 auout_init(sr, delay_flag)
dac_init_apivoid dac_init_api(u32 sr)带时钟门控检查的对外初始化(内部 dac_init(sr, 1)),时钟未开则直接返回
dac_sr_apivoid dac_sr_api(u32 sr)设置采样率,转发 auout_sr_api(sr)
dac_sr_readu32 dac_sr_read(void)读取当前采样率;时钟未开返回 E_AUDIO_CLK
dac_off_apivoid dac_off_api(void)关断输出(auout_phy_off())
stereo_dac_volu8 stereo_dac_vol(char set, u8 vol_l, u8 vol_r)立体声音量控制;'+'/'-' 步进、'r'/'R' 查询、其他值设定;返回左声道级数
dac_volu8 dac_vol(char set, u8 vol)单声道封装,stereo_dac_vol(set, vol, vol)
dac_mutebool dac_mute(bool mute)静音/取消静音;静音只清物理值,保留逻辑级数
regist_dac_channelbool regist_dac_channel(void *psound_pre, void *psound_later, void *kick)注册 DAC 通道;通道满返回 false
unregist_dac_channelbool unregist_dac_channel(void *psound)注销通道(关中断保护)
dac_cbuff_activebool dac_cbuff_active(void *sound_hld)ISR 内判断缓冲是否可激活(AT(.audio_isr_text))
uac_audio_dac_percentvoid uac_audio_dac_percent(u8 ch)计算通道缓冲水位百分比并送入 audio_link_sync_accumulate()
audio_dac_sync_oncevoid audio_dac_sync_once(void)周期遍历所有通道做 audio_link 播放进度同步
sound_out_hookvoid sound_out_hook(sound_out_obj *psound)输出钩子;HAS_MIO_EN 时联动 d_mio_start()
get_dac_max_phy_volu32 get_dac_max_phy_vol(void)返回满幅物理音量 16384(always_inline)

ADC 接口(audio_adc_api.c)

函数签名说明
audio_adc_mode_initvoid audio_adc_mode_init(void)清零 audio_adc_mge
audio_adc_init_apiu32 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_apivoid audio_adc_off_api(void)关断:audio_adc_phy_off() + audio_adc_analog_off()
fill_audio_adc_fillvoid fill_audio_adc_fill(u8 *buf, u32 len)ISR 数据广播分发(AT(.audio_isr_text))
regist_audio_adc_channelbool regist_audio_adc_channel(void *psound_pre, void *psound_later, void *kick)注册 ADC 通道;按 track 改写 B_STEREO 标志
unregist_audio_adc_channelbool 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 采样参数等板级音频配置
Prev
音频编解码器
Next
音效处理与 EQ