杰理 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)
  • 文档与开发资源

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

音频编解码与时钟管理

音频编解码与时钟管理(Audio Codec Clock)是 AC63 系列(bd19 平台)耳机 SDK 中负责按音频工作模式与编解码格式动态调整系统主频(sys clock)的模块,核心实现位于 cpu/bd19/audio_codec_clock.c,通过一张静态时钟映射表和一套链表式时钟请求上下文,保证解码器在实时解码时始终获得足够的 CPU 时钟。

Purpose and Scope

本页文档覆盖以下内容:

  • 音频时钟管理模块的职责与设计意图:为什么音频解码需要动态切换 sys 时钟;
  • 静态时钟映射表 audio_clock 的结构与各模式/编码格式的时钟取值逻辑;
  • audio_codec_clock_set() / audio_codec_clock_del() 的完整控制流、抢占/叠加语义与链表管理;
  • 该模块在 KWS(关键字唤醒)等场景的实际调用方式;
  • 编译期宏配置、失败模式、并发与扩展注意事项。

以下内容属于其他目录页,不在本页展开:

  • 具体的编解码器算法(SBC/AAC/mSBC/CVSD 解码器内部实现)见对应编解码器文档;
  • 音频链路整体架构(stream/decoder/encoder/effect 框架)见音频框架相关页面;
  • 时钟树整体(PLL/分频器等底层时钟驱动)见芯片平台时钟管理页面。

Overview

实时音频解码对 CPU 有硬性时序要求:解码器必须在一个采样周期(或一帧音频数据间隔)内完成解码,否则会产生断音/卡顿。不同编码格式的运算量差异很大(例如 AAC 的运算量明显高于 SBC,TONE 播放与 KWS 关键词检测也有各自的运算需求),因此系统主频 sys 需要在不同场景间动态切换:高负载场景提升主频,低负载场景降回低频以省电。

该模块的核心设计是一个 "模式 × 编码格式 → 系统时钟" 的静态查询表 + 按模式登记的时钟请求链表:

  • 每个音频模式(A2DP 播放、ESCO 通话、TONE 提示音、MIC 采集转 PCM、KWS 关键词唤醒)在启用时调用 audio_codec_clock_set() 向链表登记一个"时钟请求上下文";
  • 模块根据模式与编码格式查表得到所需时钟,并调用底层 clk_set("sys", clk) 提升系统主频;
  • 多个音频流可同时存在(例如 A2DP 播放期间叠加 KWS 或 TONE),链表保证时钟需求被叠加管理;
  • 模式退出时调用 audio_codec_clock_del() 注销请求,模块自动恢复到剩余请求中的最高时钟,或回落到基准时钟 SYS_96M。

这种"请求-登记-释放"模型把"多个音频流并发时的时钟仲裁"从每个解码器内部抽离出来,集中到单一模块管理,避免了各模块互相覆盖时钟导致的解码失败。

Architecture

flowchart TD
    subgraph sg_Sources["音频业务源"]
        A2DP["A2DP 解码器<br/>(SBC/AAC)"]
        ESCO["ESCO 通话<br/>(mSBC/CVSD)"]
        TONE["TONE 提示音<br/>(AAC/WAV/MP3)"]
        MIC["MIC 采集转 PCM"]
        KWS["KWS 关键词唤醒<br/>(mSBC)"]
    end

    subgraph sg_API["时钟管理 API 层"]
        SET["audio_codec_clock_set()"]
        DEL["audio_codec_clock_del()"]
    end

    subgraph sg_Core["核心数据"]
        TABLE["静态映射表 audio_clock<br/>[模式][编码格式] -> 时钟"]
        LIST["codec_clock_head 链表<br/>audio_codec_clk_context 节点"]
    end

    subgraph sg_Clk["底层时钟框架"]
        CLKGET["clk_get(\"sys\")"]
        CLKSET["clk_set(\"sys\", clk)"]
    end

    subgraph sg_HW["硬件"]
        CPU["CPU 核 / 解码器运算单元"]
    end

    A2DP --> SET
    ESCO --> SET
    TONE --> SET
    MIC --> SET
    KWS --> SET

    SET --> TABLE
    SET --> LIST
    SET --> CLKGET
    SET --> CLKSET
    DEL --> LIST
    DEL --> CLKSET
    CLKSET --> CPU

模块各组成部分的职责:

组件作用关键点
audio_clock[][MAX_CODING_TYPE_NUM]静态查询表,定义"模式 + 编码格式"所需的 sys 时钟表项时钟为 0 表示"沿用当前时钟";支持 AUDIO_CODING_ALL 通配
codec_clock_head全局链表头,登记所有活跃模式的时钟请求内核风格 list_head,按插入顺序(新请求在头部)管理
struct audio_codec_clk_context链表节点,记录 mode + params(coding_type, clk)由 zalloc 分配,audio_codec_clock_del() 释放
clk_set("sys", clk)底层时钟切换入口,实际改变系统主频由 SYS_xxx 宏(如 SYS_48M)给出目标频率
调用方(A2DP/ESCO/TONE/MIC/KWS 业务)生命周期内成对调用 set/del不感知其他音频流的存在,仲裁由模块集中完成

设计意图:采用"集中仲裁 + 请求登记"而不是"各解码器直接切频",是因为音频流存在叠加场景(如 A2DP 播放中叠加 KWS 监听),若各自直接改 sys 时钟会相互覆盖,导致正在播放的音频因时钟被降而断音。链表 + 叠加补偿逻辑(+12MHz)正是为此设计。

核心数据结构与静态时钟映射表

模块内部定义了两个核心结构体和一个二维静态表:

#define MAX_CODING_TYPE_NUM     5 //可根据模式的解码格式需求扩展
#define AUDIO_CODEC_BASE_CLK    SYS_96M

static LIST_HEAD(codec_clock_head);

struct audio_codec_clock {
    u32 coding_type;
    u32 clk;
};

struct audio_codec_clk_context {
    u8 mode;
    struct audio_codec_clock params;
    struct list_head entry;
};

Source: audio_codec_clock.c

  • MAX_CODING_TYPE_NUM = 5:每种模式下最多登记 5 种编码格式(注释明确说明可按需扩展);
  • AUDIO_CODEC_BASE_CLK = SYS_96M:所有时钟请求全部释放后的回落基准时钟;
  • struct audio_codec_clock:一个"编码格式 → 时钟"表项;
  • struct audio_codec_clk_context:链表节点,把一次 audio_codec_clock_set() 请求(mode + 表项)挂入全局链表。

模式枚举定义在头文件中:

enum {
    AUDIO_A2DP_MODE = 0,
    AUDIO_ESCO_MODE,
    AUDIO_TONE_MODE,
    AUDIO_MIC2PCM_MODE,
    AUDIO_KWS_MODE,
    AUDIO_MAX_MODE,
};

Source: audio_codec_clock.h

静态时钟映射表 audio_clock

const struct audio_codec_clock audio_clock[AUDIO_MAX_MODE][MAX_CODING_TYPE_NUM] = {
    {
        //A2DP_MODE
        {
            AUDIO_CODING_SBC,
#if (TCFG_BT_MUSIC_EQ_ENABLE && ((EQ_SECTION_MAX >= 10) && AUDIO_OUT_EFFECT_ENABLE)) || (AUDIO_VBASS_CONFIG || AUDIO_SURROUND_CONFIG)
            SYS_64M,
#elif (TCFG_BT_MUSIC_EQ_ENABLE && ((EQ_SECTION_MAX >= 10) || AUDIO_OUT_EFFECT_ENABLE))
#if A2DP_AUDIO_PLC_ENABLE
            SYS_64M,
#else
            SYS_48M,
#endif
#else
#if (TCFG_AUDIO_ANC_ENABLE)
            SYS_24M,
#else
            SYS_48M,
#endif
#endif
        },
        {
            AUDIO_CODING_AAC,
#if (TCFG_BT_MUSIC_EQ_ENABLE && ((EQ_SECTION_MAX >= 10) || AUDIO_OUT_EFFECT_ENABLE)) | (AUDIO_VBASS_CONFIG || AUDIO_SURROUND_CONFIG)
            SYS_64M,
#else
            SYS_48M,
#endif
        }
    },
    {
        //ESCO MODE
        {AUDIO_CODING_MSBC, 0},
        {AUDIO_CODING_CVSD, 0},
    },
    {
        //TONE MODE
        {AUDIO_CODING_AAC, SYS_64M},
        {AUDIO_CODING_WAV | AUDIO_CODING_MP3, 96 * 1000000L},
        {AUDIO_CODING_ALL, 0},
    },
    {
        //MIC2PCM MODE
        {AUDIO_CODING_PCM, SYS_48M},
    },
    {
        //KWS MODE
        {AUDIO_CODING_ALL, SYS_48M},
    },
    //TODO
};

Source: audio_codec_clock.c

这张表的取值逻辑体现了明显的"按需供电"设计:

  • A2DP_MODE(音乐播放):SBC 的时钟需求随编译期音效配置变化——开启高段 EQ(≥10 段)或 VBASS/SURROUND 效果时需要 SYS_64M;开启 EQ(<10 段)或输出效果时,若使能 PLC 丢包补偿则需 SYS_64M,否则 SYS_48M;无效果时若开启 ANC(主动降噪)仅需 SYS_24M(ANC 场景解码负载低),否则 SYS_48M。AAC 的运算量大于 SBC,因此只要带效果配置就需要 SYS_64M,否则 SYS_48M;
  • ESCO_MODE(通话):mSBC 与 CVSD 表项时钟均为 0——通话编解码对 sys 主频无额外要求(沿用当前时钟),语音数据走 SCO 链路,不占用解码 CPU 负载;
  • TONE_MODE(提示音):AAC 提示音 SYS_64M;WAV/MP3 提示音(用位或 | 合并编码类型)需要 96MHz(比 A2DP 的 AAC 还高,因为 TONE 通常与主音频流叠加播放,需要额外运算余量);AUDIO_CODING_ALL 兜底项时钟 0 表示保持当前;
  • MIC2PCM_MODE:PCM 采集转码 SYS_48M;
  • KWS_MODE:AUDIO_CODING_ALL 通配 SYS_48M,即无论传任何编码类型都命中该表项。

注意表项中 0 的特殊语义:clk 为 0 表示"沿用当前 sys 时钟",在 audio_codec_clock_set() 中通过 clk_get("sys") 读取现值。

时钟请求的登记与释放

audio_codec_clock_set():登记时钟请求

int audio_codec_clock_set(u8 mode, u32 coding_type, u8 preemption)
{
    struct audio_codec_clk_context *ctx = (struct audio_codec_clk_context *)zalloc(sizeof(struct audio_codec_clk_context));
    u32 new_clk = 0;

    if (mode >= MAX_CODING_TYPE_NUM) {
        log_error("Not support this mode : %d", mode);
        return -EINVAL;
    }

    const struct audio_codec_clock *params = audio_clock[mode];
    int i = 0;

    for (i = 0; i < MAX_CODING_TYPE_NUM; i++) {
        if (params[i].coding_type & coding_type) {
            goto match_clock;
        }
    }

    log_error("Not found right coding type : %d", coding_type);
    return -EINVAL;

match_clock:
    new_clk = params[i].clk ? params[i].clk : clk_get("sys");
    if (!preemption) {
        struct audio_codec_clk_context *ctx1;
        if (!list_empty(&codec_clock_head)) {
            ctx1 = list_first_entry(&codec_clock_head, struct audio_codec_clk_context, entry);
            if (ctx1 && new_clk <= ctx1->params.clk) {
                /*叠加解码的时钟不可小于在播放的音频时钟*/
                new_clk = ctx1->params.clk + 12 * 1000000;
            }
        }
    }
    ctx->mode = mode;
    ctx->params.coding_type = coding_type;
    ctx->params.clk = new_clk;
    clk_set("sys", new_clk);
    list_add(&ctx->entry, &codec_clock_head);

    return 0;
}

Source: audio_codec_clock.c

逐段控制流分析:

  1. 分配上下文:zalloc 清零分配一个 audio_codec_clk_context;
  2. 模式合法性校验:mode >= MAX_CODING_TYPE_NUM 时打错误日志并返回 -EINVAL(注意:此处比较的是"每模式最大编码类型数"5,而语义上应比较模式总数 AUDIO_MAX_MODE,当前两者恰好都等于 5,属潜在隐患,见"扩展与注意事项");
  3. 查表匹配:遍历该模式行的 MAX_CODING_TYPE_NUM 个表项,用位与 params[i].coding_type & coding_type 判断请求的编码类型是否命中。AUDIO_CODING_ALL (0xffffffff) 与任意值相与都命中,实现通配;多个编码类型可用 | 组合(如 TONE 的 WAV|MP3)。未命中返回 -EINVAL;
  4. 确定目标时钟:表项 clk 非 0 则直接用;为 0 则读取当前 sys 时钟 clk_get("sys") 沿用;
  5. 叠加补偿(非抢占场景):当 preemption == 0(叠加模式)且链表中已有活跃请求时,若新请求时钟不大于链首请求时钟,则强制升到"链首时钟 + 12MHz"。注释点明设计意图:叠加解码的时钟不可小于在播放的音频时钟——保证新解码器不会把正在播放的音频拖垮;
  6. 登记并生效:把解析出的 new_clk 写入上下文,调用 clk_set("sys", new_clk) 实际切换主频,再把节点 list_add 到链表头,返回 0。

audio_codec_clock_del():释放时钟请求

void audio_codec_clock_del(u8 mode)
{
    struct audio_codec_clk_context *ctx;
    u32 next_clk = 0;

    list_for_each_entry(ctx, &codec_clock_head, entry) {
        if (ctx->mode == mode) {
            goto clock_del;
        }
    }

    return;
clock_del:
    list_del(&ctx->entry);
    free(ctx);

    if (!list_empty(&codec_clock_head)) {
        ctx = list_first_entry(&codec_clock_head, struct audio_codec_clk_context, entry);
        if (ctx) {
            next_clk = ctx->params.clk;
        }
    }

    if (!next_clk) {
        next_clk = AUDIO_CODEC_BASE_CLK;
    }
    clk_set("sys", next_clk);
}

Source: audio_codec_clock.c

释放逻辑是对称的"反向仲裁":

  1. 遍历链表按 mode 找到对应节点;找不到直接返回(幂等,重复删除安全);
  2. list_del 摘除节点并 free 释放内存;
  3. 若链表仍有剩余请求,取链首请求的时钟作为下一个目标时钟(list_add 头插法使链首即最近登记/优先级最高的请求);
  4. 若链表已空,回落到基准时钟 AUDIO_CODEC_BASE_CLK = SYS_96M;
  5. clk_set("sys", next_clk) 完成切换。

抢占(preemption)与叠加语义

audio_codec_clock_set() 的第三个参数 preemption 控制两种行为:

preemption语义行为
0(叠加)新音频流与已有音频流同时工作(如 A2DP 播放中叠加 KWS/TONE)执行"不小于播放时钟 + 12MHz"的补偿逻辑,保证叠加解码不拖垮已有流
非 0(抢占)新音频流独占 CPU,替换原有场景(如从播放切换到通话)跳过补偿逻辑,直接按表项时钟切换

链表采用头插法,audio_codec_clock_del() 总是取链首请求作为恢复目标,因此"最近登记的请求"在释放时优先成为新的时钟基准。这种设计假设调用方按"先 set 后 del、LIFO 顺序释放"的规范使用 API——头文件注释也明确要求 audio_codec_clock_del 需与 audio_codec_clock_set 结对使用。

核心流程

叠加场景时序(A2DP 播放 + KWS 监听)

sequenceDiagram
    participant A2DP as A2DP 解码器
    participant KWS as KWS 模块
    participant API as 时钟管理模块
    participant CLK as sys 时钟框架

    A2DP->>API: audio_codec_clock_set(A2DP_MODE, SBC, 0)
    API->>API: 查表 -> SYS_48M,链表空无需补偿
    API->>CLK: clk_set("sys", 48M)
    Note over API: 链表: [A2DP:48M]

    KWS->>API: audio_codec_clock_set(KWS_MODE, MSBC, 0)
    API->>API: 查表 -> SYS_48M;new_clk(48M) <= 链首(48M)<br/>叠加补偿: 48M + 12M = 60M
    API->>CLK: clk_set("sys", 60M)
    Note over API: 链表: [KWS:60M, A2DP:48M]

    KWS->>API: audio_codec_clock_del(KWS_MODE)
    API->>API: 删除 KWS 节点,取链首 A2DP:48M
    API->>CLK: clk_set("sys", 48M)

    A2DP->>API: audio_codec_clock_del(A2DP_MODE)
    API->>API: 链表空,回落基准
    API->>CLK: clk_set("sys", 96M)

audio_codec_clock_set() 决策流程

flowchart TD
    Start([audio_codec_clock_set]) --> Alloc["zalloc 上下文"]
    Alloc --> CheckMode{"mode >= 5 ?"}
    CheckMode -->|"是"| ErrMode["log_error + return -EINVAL"]
    CheckMode -->|"否"| Lookup["遍历 audio_clock[mode] 查编码类型"]
    Lookup --> Found{"coding_type 命中?"}
    Found -->|"否"| ErrType["log_error + return -EINVAL"]
    Found -->|"是"| Calc{"表项 clk == 0 ?"}
    Calc -->|"是"| Keep["new_clk = clk_get(\"sys\")"]
    Calc -->|"否"| UseTable["new_clk = 表项 clk"]
    Keep --> Overlay{"preemption==0 且链表非空?"}
    UseTable --> Overlay
    Overlay -->|"是"| Cmp{"new_clk <= 链首 clk ?"}
    Cmp -->|"是"| Bump["new_clk = 链首 clk + 12MHz"]
    Cmp -->|"否"| Commit
    Bump --> Commit["写入 ctx,clk_set(\"sys\", new_clk)<br/>list_add 到链表头"]
    Overlay -->|"否"| Commit
    ErrMode --> End([返回 -EINVAL])
    ErrType --> End
    Commit --> Ret0([返回 0])

使用示例

头文件中的 API 声明

/*************************************************************************
 * 音频编解码时钟设置
 *
 * Input    :  mode - 音频模式,coding_type - 模式下的解码格式
 *             preemption - 打断/叠加.
 * Output   :  0 - 成功, 非0 - 出错.
 * Notes    :  目前在耳机SDK用于A2DP/ESCO/TONE三个模式的时钟切换.
 * History  :  2021/06/03 初始版本
 *=======================================================================*/
int audio_codec_clock_set(u8 mode, u32 coding_type, u8 preemption);

/*************************************************************************
 * 音频编解码时钟删除
 *
 * Input    :  mode - 音频模式
 * Output   :  无.
 * Notes    :  需和audio_codec_clock_set结对使用.
 * History  :  2021/06/03 初始版本
 *=======================================================================*/
void audio_codec_clock_del(u8 mode);

Source: audio_codec_clock.h

实际调用:KWS 模块启用/停用时钟

KWS(关键词唤醒)模块在启动音频采集时登记 KWS 模式的时钟请求,在退出时注销,是"set/del 结对使用"的典型范例:

// jl_kws_audio.c 中启动 KWS 音频时:
    audio_codec_clock_set(AUDIO_KWS_MODE, AUDIO_CODING_MSBC, 0);
    return ret;

Source: jl_kws_audio.c

// jl_kws_audio.c 中停止 KWS 音频时:
        audio_codec_clock_del(AUDIO_KWS_MODE);

Source: jl_kws_audio.c

此处 AUDIO_CODING_MSBC 作为 coding_type 传入,命中 KWS_MODE 行的 AUDIO_CODING_ALL 通配表项(0xffffffff & MSBC 恒非 0),因此无论传何种编码类型都会匹配 SYS_48M。preemption = 0 表示叠加模式:若当前已有 A2DP 播放(链首 48M),新请求会被自动补偿到 60MHz,保证 KWS 解码不拖垮音乐播放。

典型使用范式(伪代码归纳自调用约定)

/* 场景:A2DP 音乐播放开启(SBC,48M 或按配置更高) */
audio_codec_clock_set(AUDIO_A2DP_MODE, AUDIO_CODING_SBC, 0);
...
/* 播放期间叠加 TONE 提示音(WAV/MP3 需要 96M,自动补偿) */
audio_codec_clock_set(AUDIO_TONE_MODE, AUDIO_CODING_WAV | AUDIO_CODING_MP3, 0);
...
audio_codec_clock_del(AUDIO_TONE_MODE);
...
/* 播放结束,链表清空后回落基准 96M */
audio_codec_clock_del(AUDIO_A2DP_MODE);

说明:A2DP/TONE/ESCO 三个模式的实际调用位于耳机业务层(当前仓库中 KWS 调用已在本页列出),上述范式严格遵循头文件"结对使用"契约与 audio_codec_clock_del 的 LIFO 恢复逻辑。

配置选项

该模块的时钟取值受多个编译期宏控制(均定义于各板级 app_config.h / 编译配置中),直接影响 A2DP 模式下 SBC/AAC 的时钟档位:

配置宏影响典型取值
TCFG_BT_MUSIC_EQ_ENABLE是否启用 BT 音乐 EQ;与 EQ_SECTION_MAX、AUDIO_OUT_EFFECT_ENABLE 组合决定 SBC 档位(64M/48M)0/1
EQ_SECTION_MAXEQ 段数;>= 10 视为高段 EQ,需要更高时钟如 5/10
AUDIO_OUT_EFFECT_ENABLE是否启用输出效果(音效处理链)0/1
AUDIO_VBASS_CONFIG / AUDIO_SURROUND_CONFIG虚拟低音/环绕声配置;任一开启即 SBC/AAC 升到 64M0/1
A2DP_AUDIO_PLC_ENABLEA2DP 丢包补偿(PLC);EQ 中段档位下决定 SBC 是 64M 还是 48M0/1
TCFG_AUDIO_ANC_ENABLE是否启用 ANC;无效果时启用 ANC 的 SBC 可低至 24M0/1

运行时参数(audio_codec_clock_set):

参数类型取值说明
modeu8AUDIO_A2DP_MODE/AUDIO_ESCO_MODE/AUDIO_TONE_MODE/AUDIO_MIC2PCM_MODE/AUDIO_KWS_MODE音频工作模式
coding_typeu32AUDIO_CODING_SBC/AAC/MSBC/CVSD/PCM/WAV/MP3 等,可位或组合;AUDIO_CODING_ALL 通配模式下的编码格式
preemptionu80=叠加,非0=抢占是否执行叠加时钟补偿

API 参考

int audio_codec_clock_set(u8 mode, u32 coding_type, u8 preemption)

登记一个音频模式的时钟请求,并立即将系统主频切换到查表(及叠加补偿)后的目标值。

参数:

  • mode (u8):音频模式,见 AUDIO_*_MODE 枚举(AUDIO_MAX_MODE 为边界);
  • coding_type (u32):该模式下的解码格式标志,多个格式可用 | 合并,AUDIO_CODING_ALL (0xffffffff) 可匹配任意格式;
  • preemption (u8):0 表示叠加(若新时钟不大于链首请求时钟则自动加 12MHz),非 0 表示抢占(直接切换)。

返回:

  • 0:成功,请求已登记并生效;
  • -EINVAL:mode 越界(>= MAX_CODING_TYPE_NUM)或该模式下找不到匹配的编码类型。

行为要点:

  • 每次调用都会 zalloc 一个新上下文并头插链表,允许同一模式重复登记(需由调用方保证释放次数对称);
  • 表项时钟为 0 时沿用 clk_get("sys") 当前值;
  • 叠加补偿规则:new_clk <= 链首 clk 时,new_clk = 链首 clk + 12MHz。

void audio_codec_clock_del(u8 mode)

注销指定模式的时钟请求并恢复系统时钟。

参数:

  • mode (u8):要注销的音频模式。

返回: 无。

行为要点:

  • 按 mode 查找链表(首个匹配项)并摘除释放;
  • 未找到匹配项时静默返回(幂等);
  • 剩余请求非空时恢复为链首请求的时钟,否则回落到 AUDIO_CODEC_BASE_CLK (SYS_96M);
  • 需与 audio_codec_clock_set 结对使用。

失败模式、边界情况与并发

失败模式

失败场景表现处理
mode 越界打 [CODEC_CLK] Not support this mode : %d 错误日志,返回 -EINVAL调用方需检查返回值;无副作用(上下文已分配但未入链表,注意此处 zalloc 的内存未释放,属轻微泄漏点)
编码类型不匹配打 Not found right coding type : %d 错误日志,返回 -EINVAL同上;需确保 coding_type 与模式表项兼容(可用 AUDIO_CODING_ALL 兜底)
链表为空时删除list_for_each_entry 无匹配,静默返回幂等安全
zalloc 失败空指针解引用风险源码未做空指针检查,低内存场景下存在隐患

边界情况

  • 表项时钟为 0 的语义:ESCO 模式(mSBC/CVSD)与 AUDIO_CODING_ALL 兜底项不主动切频,沿用 clk_get("sys") 当前值。因此 ESCO 通话本身不会改变系统主频;
  • 编码类型位或组合:TONE 模式用 AUDIO_CODING_WAV | AUDIO_CODING_MP3 合并两个格式共享 96MHz 档位——匹配逻辑是"请求类型与表项做位与",所以任一格式都能命中该表项;
  • 通配符 AUDIO_CODING_ALL:0xffffffff 与任何 coding_type 位与非零,KWS/TONE 的兜底项因此对所有格式生效;
  • MAX_CODING_TYPE_NUM 与 AUDIO_MAX_MODE 数值巧合:模式校验用的是 MAX_CODING_TYPE_NUM(5),与模式总数 AUDIO_MAX_MODE(当前也是 5)恰好相等;一旦未来新增模式枚举(如 AUDIO_MAX_MODE 变 6)而 MAX_CODING_TYPE_NUM 不变,第 5 个模式将被误判为非法。扩展时务必同步修正该边界检查;
  • 重复登记同模式:set 允许同 mode 多次登记(每次新建节点),但 del 只摘除首个匹配节点;若同一模式被登记两次而只删除一次,链表会残留旧请求,时钟无法回落。

并发与一致性

  • 源码中没有任何锁或原子操作保护 codec_clock_head 链表,clk_set 也是全局性操作——该模块假定所有调用都发生在单一音频任务/线程上下文中(耳机 SDK 的音频事件处理通常是串行化的);
  • 若未来引入多任务并发调用 set/del,需要为链表操作加互斥保护,否则会产生链表损坏与时序竞态;
  • clk_set 的全局副作用意味着"最后一个生效的请求"决定全系统主频,链表只保存恢复信息,不参与实时仲裁。

性能与运维注意事项

  • 切换开销:每次 set/del 都会立即 clk_set 切换主频。频繁切换(如 KWS 周期性启停)会引入时钟稳定开销与额外功耗,调用方应尽量在模式生命周期边界调用,而非逐帧调用;
  • 省电设计:静态表中低档位(SYS_24M/SYS_48M)与 ESCO 不切频的设计,本质是"在满足实时解码的前提下把主频压到最低"以省电——ANC 场景 SBC 仅需 24M 即是最低档位的例子;
  • 叠加补偿的 12MHz 余量:补偿值是经验常数,为叠加解码预留额外运算余量;若业务出现叠加解码卡顿,可优先检查此余量是否足够;
  • 调试手段:文件头 #define LOG_TAG "[CODEC_CLK]" 且 LOG_INFO/DEBUG/DUMP/ERROR/WARN_ENABLE 全部开启,错误路径(模式非法、类型不匹配)会输出日志,可用于定位时钟切换失败问题。

扩展点

  1. 新增音频模式:在 audio_codec_clock.h 的枚举中追加模式(保持 AUDIO_MAX_MODE 为最后一个),并在 audio_clock 表中新增一行(按 MAX_CODING_TYPE_NUM 个表项补齐);同时需修正 audio_codec_clock_set() 中的边界检查(改用 AUDIO_MAX_MODE 或保持两常量同步);
  2. 新增编码格式档位:若某模式需要支持新格式,在对应行添加 {AUDIO_CODING_XXX, SYS_YYM} 表项即可,MAX_CODING_TYPE_NUM 可随需求增大(注释已说明"可根据模式的解码格式需求扩展");
  3. 调整叠加余量:修改 audio_codec_clock_set() 中 ctx1->params.clk + 12 * 1000000 的常数;
  4. 跨平台复用:cpu/br30/audio_codec_clock.c、cpu/br34/audio_codec_clock.c 存在同名同构实现(表项取值按平台调整),新平台可复制该文件并修改静态表与 SYS_* 宏。

测试情况

仓库内未发现针对 audio_codec_clock 的独立单元测试文件;其行为正确性主要依赖业务层(A2DP/ESCO/TONE/KWS 音频流程)的集成验证。apps/common/jl_kws/jl_kws_audio.c 中的实际调用(set → 采集 → del)可作为集成测试的验证路径:通过日志确认 KWS 启动时 sys 时钟升至 48M(叠加时 60M),停止时回落。

Related Links

  • audio_codec_clock.c(bd19 实现)
  • audio_codec_clock.h(API 与模式枚举)
  • br30 平台同构实现
  • br34 平台同构实现
  • KWS 模块调用示例(jl_kws_audio.c)
  • 音频框架与编解码器细节:见"音频编解码框架"相关目录页
  • 底层时钟树/PLL 配置:见"芯片平台时钟管理"相关目录页
Prev
芯片平台总览与差异
Next
外设驱动接口(ADC/IIC/SPI/PWM/LED/充电)