夜雨聆风学习资料网

ARTICLE · 1279510

AudioReach 源码分析(五):GKV、CKV、TKV 到底是什么?从 XML 配置追踪到 DSP 参数

AudioReach 源码分析(五):GKV、CKV、TKV 到底是什么?从 XML 配置追踪到 DSP 参数

系列第五篇。前面四篇走完了 HAL、PAL、AGM 这一路。这篇从 SA8295P 的两份真实 XML 出发,把 GKV、CKV、TKV 这三个键值追踪到源码,讲清楚它们各自选什么、查什么、配什么,以及配置到底有没有真正下到 DSP。全文基于本工程源码,代码都标了文件名和行号。


一、为什么音频配置改了,却不一定产生预期效果

先摆一个工程里最常见的现象:改了 resourcemanager_*.xml 或 usecaseKvManager.xml 里的某个值,重新烧录,声音没有变化。或者反过来,参数改坏了,无声、失真、串音都来了,却不知道是哪一层出的问题。

根因是,配置和运行时之间隔了三层,每一层都可能断:

  1. 解析:配置文件被哪个进程读,读没读进去,有没有解析失败。
  2. 构造:解析出来的值,有没有被运行时按流、按设备组装成正确的键值对。
  3. 下发:组装好的键值,有没有走到 DSP 那一侧,DSP 有没有真的应用。

这三层对应了三种键值,也就是这篇的主角:GKV(选图)、CKV(查校准)、TKV(配模块参数)。先把它们各自管的事分开,再一层层往下追。


二、先区分三件事:选图、匹配校准、配置模块

AudioReach 的 DSP 侧是一张一张的图(graph),图里是模块(module)。AP 侧要跟 DSP 打交道的,就三件事:

键值
全称
回答的问题
什么时候用
GKV
Graph Key-Value
跑哪张图
开图时选定图
CKV
Calibration Key-Value
这张图用哪套校准
开图/配参数时查标定
TKV
Tag Key-Value
图里某个模块的参数设成什么
运行中按 tag 配参数

三者的底层都是「key 和 value 两个整数组成的对」。PAL 侧定义在 PalDefs.h 的 pal_key_value_pair_t,AGM 侧定义在 agm_api.h 的 agm_key_value,两边长得一样。区别不在结构,在用途,以及由谁来填。

下面三节各自讲清:谁定义的、谁填的、谁消费的。


三、GKV:运行时如何确定要使用的图

GKV 的 key 和 value 不是凭空来的,它们在一份头文件里有一个权威定义:mm-audio-headers/acdbdata/kvh2xml.h。这个文件叫「KV Header to XML」,意思是它定义了 key/value,XML 里的值要跟它保持一致。

看它的 AllKeyIds 枚举(kvh2xml.h:23):

enumAllKeyIds{    STREAMRX     = 0xA1000000,   // Rx 流类型    DEVICERX     = 0xA2000000,   // Rx 设备类型    DEVICETX     = 0xA3000000,   // Tx 设备类型    INSTANCE     = 0xAB000000,   // 实例编号    DEVICEPP_RX  = 0xAC000000,   // Rx 设备后处理    DEVICEPP_TX  = 0xAD000000,   // Tx 设备后处理    STREAMTX     = 0xB1000000,   // Tx 流类型};

每个 key 下面又有一套 value 枚举。以 STREAMRX 为例(kvh2xml.h:80),value 里有 PCM_LL_PLAYBACK = 0xA100000E,还有车载专属的:

BUS00_MEDIA            = 0xA100F015,BUS01_SYS_NOTIFICATION = 0xA100F016,BUS02_NAV_GUIDANCE     = 0xA100F017,BUS03_PHONE            = 0xA100F018,

所以 GKV 的一个 key=value 对,就是「某个维度 + 具体取值」,比如 {STREAMRX, BUS00_MEDIA} 就是「Rx 流类型 = 媒体总线」。

那这些值是在哪里配的?在 usecaseKvManager.xml。看 PAL_STREAM_PLAYBACK_BUS 这一段(usecaseKvManager.xml:269):

<streamtype="PAL_STREAM_PLAYBACK_BUS"><keys_and_valuesDirection="RX"BUS_address="BUS00_MEDIA"><!-- STREAMRX - BUS00_MEDIA --><graph_kvkey="0xA1000000"value="0xA100F015"/><!-- INSTANCE - INSTANCE_1 --><graph_kvkey="0xAB000000"value="0x1"/></keys_and_values>

再往下,设备维度的一段(usecaseKvManager.xml:592):

<keys_and_valuesStreamType="PAL_STREAM_PLAYBACK_BUS"BUS_address="BUS00_MEDIA"><!-- DEVICERX - SPEAKER --><graph_kvkey="0xA2000000"value="0xA2000001"/><!-- DEVICEPP_RX - DEVICEPP_RX_BUS00_MEDIA --><graph_kvkey="0xAC000000"value="0xAC000006"/></keys_and_values>

把这两段合起来,一条 BUS00_MEDIA 播放流的 GKV 是由四对组成的:

{ STREAMRX=0xA1000000,   BUS00_MEDIA=0xA100F015 }{ INSTANCE=0xAB000000,   1 }{ DEVICERX=0xA2000000,   SPEAKER=0xA2000001 }{ DEVICEPP_RX=0xAC000000, BUS00_MEDIA=0xAC000006 }

这一组键值,就是「选图」的全部输入:流是媒体总线、第 1 个实例、输出设备是扬声器、设备后处理走 BUS00_MEDIA 那套 PP。四对合起来,唯一确定一张 DSP 图。

运行时把这四对找出来,分三步。populateStreamKV(PayloadBuilder.cpp:2386)先调 retrieveSelectors 收集 <keys_and_values> 标签上的属性名(Direction、BUS_address 这些),getSelectorValues 再用这条流的实际方向、bus 地址把属性名填成值,最后 retrieveKVs 拿这些值去匹配条目,取出该条目的 graph_kv,也就是那四对 GKV。

这里要分清两层:selector_names 是标签上的属性名(字符串,用来筛选),而 graph_kv 才是真正的 GKV,它在解析时就用 convertCharToHex 转成了数值 key/value(0xA1000000),跟 AGM/ACDB 侧是同一套数值。所以选图、查校准用的都是数值 key,不是字符串。


四、CKV:校准数据如何匹配

CKV 和 GKV 的区别在于:GKV 是静态配死的(XML 里写好的 graph_kv),CKV 是运行时根据流和设备的实际属性现算的。

算 CKV 的函数在 PayloadBuilder.cpp 里,有两个入口:populateStreamCkv(流级,2592 行)和 populateDevicePPCkv(设备 PP 级,2656 行)。

看 populateDevicePPCkv 里对 BUS 播放的处理(PayloadBuilder.cpp:2705):

case PAL_STREAM_PLAYBACK_BUS:if (rm->isSpeakerProtectionEnabled == true &&        rm->isRasEnabled == true &&        dAttr.id == PAL_DEVICE_OUT_SPEAKER) {if (dAttr.config.ch_info.channels == 2)            keyVector.push_back(std::make_pair(RAS_SWITCH, RAS_ON));else            keyVector.push_back(std::make_pair(RAS_SWITCH, RAS_OFF));    }if (dAttr.id == PAL_DEVICE_OUT_SPEAKER ||        dAttr.id == PAL_DEVICE_OUT_WIRED_HEADSET ||        dAttr.id == PAL_DEVICE_OUT_WIRED_HEADPHONE) {        keyVector.push_back(std::make_pair(GAIN, GAIN_0));    }

也就是说,CKV 里的 key 是 RAS_SWITCH、GAIN、CHANNELS、SAMPLINGRATE 这些,value 是从流的媒体配置、设备属性、ResourceManager 的开关状态里现取的。比如扬声器保护开了、RAS 开了、设备是双声道,就填 {RAS_SWITCH, RAS_ON};是扬声器/耳机就再补一个 {GAIN, GAIN_0}。

populateStreamCkv 同样,默认先填 {VOLUME, LEVEL_15},语音/回环/蓝牙 SCO 场景再按采样率补 {SAMPLINGRATE, 16K} 之类。

这些键值对填好之后,SessionAlsaUtils::getCalMetadata(SessionAlsaUtils.cpp:262)把它们打包进 agm_cal_config 结构(num_ckvs + kv[]),再经 ALSA mixer 控制 setCalibration 下发。SessionAgm 那条路则经 agm_session_set_metadata 下发。

另一个和 CKV 相关的静态配置在 resourcemanager XML 里。in-device(麦克风)段有 devicePP-metadata,里面是:

<devicePP-metadata><kvpairkey="0xAD000000"value="0xAD000002"/><!-- AUDIO_FLUENCE_SMECNS --></devicePP-metadata>

0xAD000000 是 DEVICEPP_TX,0xAD000002 是 DEVICEPP_TX_AUDIO_FLUENCE_SMECNS(kvh2xml.h:261),即单麦 ECNS 那套设备 PP。这段配置决定的是「麦克风用哪套设备 PP 算法」,是静态选型,跟运行时现算的 {RAS_SWITCH, RAS_ON} 是两类东西,别混。

这里要守一条边界:XML 里写了一份校准配置,只证明「文件里有这个值」。它有没有被读、被选中、下到 DSP,是另外三步。


五、TKV:运行时如何识别和配置模块

TKV 比前两者多一个维度:tag。前两者的 key 直接描述「查什么」,TKV 的 tag 描述「这个参数作用在哪个槽位」,key=value 才是具体值。

tag 的定义也在 kvh2xml.h,是 0xC0 开头的一批:

#define TAG_PAUSE         0xC0000006#define TAG_MUTE          0xC0000007#define TAG_STREAM_MFC    0xC000000B#define TAG_DEVICE_PP_MFC 0xC0000011

填 TKV 的是 PayloadBuilder::populateTagKeyVector(PayloadBuilder.cpp:2850)。它接收一个 tag,翻成一对 key=value,同时把 *gsltag 填成 AGM 认识的 tag id。拿静音举例:

case MUTE_TAG:    tkv.push_back(std::make_pair(MUTE, ON));    *gsltag = TAG_MUTE;break;

MFC 是 TKV 的典型用例。采样率、声道数、位宽各有一个 tag(PAL 侧在 Stream.h 里定义为 MFC_SR_*、CHS_*、BW_*),对应到 kvh2xml.h 的 TAG_STREAM_MFC、TAG_DEVICE_PP_MFC。一条流的采样率是 48K,就被翻成 {SAMPLINGRATE, SAMPLINGRATE_48K},带上 TAG_STREAM_MFC 或 TAG_DEVICE_PP_MFC,走 TKV 下发。

填好的 TKV 由 SessionAlsaUtils::getTagMetadata(SessionAlsaUtils.cpp:240)打包进 agm_tag_config(tag + num_tkvs + kv[]),经 ALSA mixer 控制 setParamTag 下发。

  1. 找到目标模块(tag 命中模块实例,这一步在 ACDB 里)。
  2. 识别模块 Tag(TAG_STREAM_MFC 等)。
  3. 构造参数负载(populateTagKeyVector → getTagMetadata)。
  4. 把参数传给服务(mixer_ctl_set_array / agm 接口)。
  5. DSP 接收并应用。

六、回到 SA8295P:沿真实源码追踪一条配置链路

把前面几节串起来,用「BUS00_MEDIA 播放」这个真实用例走一遍。这是本平台 Car Audio 的多媒体总线,第三篇已经确认它走 PAL_STREAM_PLAYBACK_BUS → StreamPCM → SessionAlsaPcm。

证据链六步:

第 1 步:配置文件里的原始配置。

usecaseKvManager.xml,BUS_address 为 BUS00_MEDIA 的两段,graph_kv 分别是 {0xA1000000, 0xA100F015}、{0xA2000000, 0xA2000001}、{0xAC000000, 0xAC000006}、{0xAB000000, 0x1}`。

第 2 步:key 和 value 的定义。

kvh2xml.h:STREAMRX=0xA1000000、BUS00_MEDIA=0xA100F015、DEVICERX=0xA2000000、DEVICEPP_RX=0xAC000000、DEVICEPP_RX_BUS00_MEDIA=0xAC000006、INSTANCE=0xAB000000。

第 3 步:读取与解析。

PayloadBuilder::retrieveSelectors(PayloadBuilder.cpp)遍历 keys_and_values,把 selector 收进 gkv_selectors。

第 4 步:运行时构造。

gkv_selectors 组装成上文的四对 GKV;同时 populateDevicePPCkv按扬声器/耳机、RAS 开关填 CKV;需要配 MFC 时 populateTagKeyVector 填 TKV。

第 5 步:下游接口调用。

SessionAlsaPcm 路径经 mixer_ctl_set_array 把 CKV/TKV 写到 ALSA mixer 控制 setCalibration/setParamTag;SessionAgm 路径经 agm_session_set_metadata 下发。GKV 在 AGM 路径里最终走到 gsl_open(graph_key_vect, ...) 选定图(第四篇已证)。


七、PCM 数据流与参数控制流为什么不能混淆

这是系列反复强调的一点,第五篇再落到 GKV/CKV/TKV 上。

PCM 数据流:AudioFlinger 的采样数据 → pal_stream_write → SessionAlsaPcm::write → pcm_write。pcm_open 打开的是虚拟声卡,而 card-defs.xml 把虚拟声卡的 PCM 设备定义成了 AGM 插件,所以 pcm_write 会经 tinyalsa 的插件层转到 agm_session_write,再走 AGM 服务 → GSL → GPR → ADSP。这条路走的是 PCM 数据,跟键值无关。

参数控制流:GKV/CKV/TKV 的键值 → 打包成 agm_cal_config/agm_tag_config → mixer 控制(setParamTag/setCalibration)或 AGM 接口下发 → 落到 DSP 图。这条路走的是配置命令,跟 PCM 数据是两条独立的通道。

两条路都从 SessionAlsaPcm 出发,但数据走 PCM(经 AGM 插件转到 AGM 服务),配置走 mixer 控制或 AGM 接口。把 mixer_ctl_set_array 和 pcm_write 画成一条箭头,是这系列最容易犯的错,也会直接导致排障时看错方向。


八、实际排障:配置不生效时如何逐层定位

三个最常见的场景,每个都从「哪一层可能断了」往下查。

问题一:改了配置文件没变化

先确认改的是不是运行时真正读的那份。本平台配置文件按声卡名动态选:gvmauto8295-adp-star-snd-card → 后缀 gvmauto8295_adp_star → resourcemanager_gvmauto8295_adp_star.xml 和 usecaseKvManager.xml(派生逻辑见第三篇 research)。改错了文件、或改的文件没进镜像,等于白改。

再确认解析有没有失败。ResourceManager 用 Expat 解析,device_profile/in-device/out-device/back_end_name/snd_device_name 这些 tag 在 ResourceManager.cpp:9975 和 :9815 附近处理。日志里搜这些 tag 名和解析报错。

问题二:音频能播,但 DSP 效果没变

能播说明数据流是通的,问题在控制流。按顺序查:

  1. 参数接口有没有被执行:getTagMetadata/getCalMetadata 有没有被调用,mixer_ctl_set_array 有没有返回成功。
  2. tag 和目标模块匹不匹配:populateTagKeyVector 里 tag 是否对应上了 TAG_STREAM_MFC/TAG_DEVICE_PP_MFC。
  3. 负载构造对不对:agm_tag_config/agm_cal_config 的 num 和 kv 是否和预期一致。
  4. 下发是否成功:mixer 控制名对不对(setParamTag/setCalibration)。
  5. DSP 是否应用:这一步 HLOS 侧看不到,只能看 DSP 侧日志或 QACT 状态。

问题三:改参数后无声、失真、路由异常

这种要逐层回退:先看配置解析有没有错,再看 usecase 匹配对不对(流类型 + BUS_address 是否命中),再看图选择(GKV 四对是否拼对),再看校准匹配(CKV 的通道数、采样率是否和实际一致),再看模块参数(TKV 的 tag/value 是否越界),最后看底层路由(back_end_name/snd_device_name 是否把设备连到了错误的 AIF)。


九、总结

  1. GKV 选图:key/value 定义在 kvh2xml.h,配置在 usecaseKvManager.xml 的 graph_kv,运行时由 retrieveSelectors 组装。一条 BUS 流的 GKV 由 STREAMRX、DEVICERX、DEVICEPP_RX、INSTANCE 四对组成。
  2. CKV 查校准:key 是 CHANNELS/SAMPLINGRATE/RAS_SWITCH/GAIN 等,value 运行时从流和设备属性现算,经 getCalMetadata 打包。
  3. TKV 配参数:多一个 tag 维度,MFC 经 TAG_STREAM_MFC/TAG_DEVICE_PP_MFC,经 getTagMetadata 打包。

结语

👉 欢迎关注我们的公众号【快乐程序猿】,我们将持续分享Android 系统核心技术,助你成为真正的Android 深度开发专家!

如有疑问,欢迎留言交流,让我们一起攻克技术难题,探索 Android 系统的每一寸深度!

精彩合集

  专栏推荐

☞【专栏一】audio-ALSA架构
☞【专栏二】Android-audio系列
☞【专栏三】c++设计模式
☞【专栏四】linux内核探索
☞【专栏五】c++八股文

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料