音频编解码与时钟管理
音频编解码与时钟管理(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
逐段控制流分析:
- 分配上下文:
zalloc清零分配一个audio_codec_clk_context; - 模式合法性校验:
mode >= MAX_CODING_TYPE_NUM时打错误日志并返回-EINVAL(注意:此处比较的是"每模式最大编码类型数"5,而语义上应比较模式总数AUDIO_MAX_MODE,当前两者恰好都等于 5,属潜在隐患,见"扩展与注意事项"); - 查表匹配:遍历该模式行的
MAX_CODING_TYPE_NUM个表项,用位与params[i].coding_type & coding_type判断请求的编码类型是否命中。AUDIO_CODING_ALL (0xffffffff)与任意值相与都命中,实现通配;多个编码类型可用|组合(如 TONE 的WAV|MP3)。未命中返回-EINVAL; - 确定目标时钟:表项
clk非 0 则直接用;为 0 则读取当前 sys 时钟clk_get("sys")沿用; - 叠加补偿(非抢占场景):当
preemption == 0(叠加模式)且链表中已有活跃请求时,若新请求时钟不大于链首请求时钟,则强制升到"链首时钟 + 12MHz"。注释点明设计意图:叠加解码的时钟不可小于在播放的音频时钟——保证新解码器不会把正在播放的音频拖垮; - 登记并生效:把解析出的
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
释放逻辑是对称的"反向仲裁":
- 遍历链表按
mode找到对应节点;找不到直接返回(幂等,重复删除安全); list_del摘除节点并free释放内存;- 若链表仍有剩余请求,取链首请求的时钟作为下一个目标时钟(
list_add头插法使链首即最近登记/优先级最高的请求); - 若链表已空,回落到基准时钟
AUDIO_CODEC_BASE_CLK = SYS_96M; 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_MAX | EQ 段数;>= 10 视为高段 EQ,需要更高时钟 | 如 5/10 |
AUDIO_OUT_EFFECT_ENABLE | 是否启用输出效果(音效处理链) | 0/1 |
AUDIO_VBASS_CONFIG / AUDIO_SURROUND_CONFIG | 虚拟低音/环绕声配置;任一开启即 SBC/AAC 升到 64M | 0/1 |
A2DP_AUDIO_PLC_ENABLE | A2DP 丢包补偿(PLC);EQ 中段档位下决定 SBC 是 64M 还是 48M | 0/1 |
TCFG_AUDIO_ANC_ENABLE | 是否启用 ANC;无效果时启用 ANC 的 SBC 可低至 24M | 0/1 |
运行时参数(audio_codec_clock_set):
| 参数 | 类型 | 取值 | 说明 |
|---|---|---|---|
mode | u8 | AUDIO_A2DP_MODE/AUDIO_ESCO_MODE/AUDIO_TONE_MODE/AUDIO_MIC2PCM_MODE/AUDIO_KWS_MODE | 音频工作模式 |
coding_type | u32 | AUDIO_CODING_SBC/AAC/MSBC/CVSD/PCM/WAV/MP3 等,可位或组合;AUDIO_CODING_ALL 通配 | 模式下的编码格式 |
preemption | u8 | 0=叠加,非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全部开启,错误路径(模式非法、类型不匹配)会输出日志,可用于定位时钟切换失败问题。
扩展点
- 新增音频模式:在
audio_codec_clock.h的枚举中追加模式(保持AUDIO_MAX_MODE为最后一个),并在audio_clock表中新增一行(按MAX_CODING_TYPE_NUM个表项补齐);同时需修正audio_codec_clock_set()中的边界检查(改用AUDIO_MAX_MODE或保持两常量同步); - 新增编码格式档位:若某模式需要支持新格式,在对应行添加
{AUDIO_CODING_XXX, SYS_YYM}表项即可,MAX_CODING_TYPE_NUM可随需求增大(注释已说明"可根据模式的解码格式需求扩展"); - 调整叠加余量:修改
audio_codec_clock_set()中ctx1->params.clk + 12 * 1000000的常数; - 跨平台复用:
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 配置:见"芯片平台时钟管理"相关目录页