夜雨聆风学习资料网

ARTICLE · 1259452

AudioReach 源码分析(四):AGM 到底做了什么?从音频图管理到 DSP 配置

AudioReach 源码分析(四):AGM 到底做了什么?从音频图管理到 DSP 配置

系列第四篇。前面三篇讲了一条流从 HAL 进 PAL,再拆成 Stream、Session、Device。这一篇越过 PAL,讲它下面的 AGM:这个服务由谁启动、管理哪些对象、经什么接口跟 DSP 打交道。先说结论:AGM 是"音频图"的AP侧管理服务,但本平台的常规播放并不经过它。全文基于 SA8295P (Lemans) Android 13 Automotive 源码树,代码都标了文件名和函数名。


一、为什么 PAL 之外还需要 AGM

第三篇讲清楚了一件事:PAL 的 Stream 只管编排,Session 管会话,Device 管路由。PAL 这一层不直接碰 DSP 图。跟 DSP 图打交道的,是再往下的 AGM。

PAL 里的两个 Session 子类,代表两条完全不同的路:

  • SessionAlsaPcm:数据经 tinyalsa 的 pcm_open/pcm_write 写进 ALSA 前端,图配置经 mixer_ctl_set_array 下发给 ALSA mixer 控制。这一路 PAL 自己就能走完,不经过 AGM。
  • SessionAgm:数据经 agm_session_write_with_metadata,图经 agm_session_open/agm_session_set_metadata 直接交给 AGM 管理。这一路才用 AGM。

而第三篇已经确认:本 build 的 AIDL HAL 不产生 PAL_STREAM_NON_TUNNEL,所以常规播放(含车载 BUS 播放)都走 SessionAlsaPcm,不会进 SessionAgm,也就不调用 AGM。

那 AGM 到底给谁用?它的 AP 侧图管理能力,服务于三处:NON_TUNNEL 会话(SessionAgm)、hostless 常开音频、以及 AMS 这类直连 GPR 的低层模块。换句话说,AGM 不是"所有音频数据都要经过的统一通道",而是"直接图通路"专用的图管理服务。本章的任务,是把这个服务本身拆开讲。


二、AGM 服务从哪里启动

AGM 不是躲在 PAL 里的一个库,它是一个独立的 HwBinder 服务,由 init 拉起。

服务端在 agm/ipc/HwBinders/agm_ipc_service/。init 脚本 vendor.qti.hardware.AGMIPC@1.0-service.rc 写着:

service vendor.agm-1-0 /vendor/bin/hw/vendor.qti.hardware.AGMIPC@1.0-service  class hal  user system  interface vendor.qti.hardware.AGMIPC@1.0::IAGM default

所以服务名是 vendor.qti.hardware.AGMIPC@1.0-service,暴露的 HwBinder 接口是 IAGM。对应的 HIDL 接口定义在 agm/ipc/HwBinders/interfaces/AGMIPC/1.0/IAGM.hal,里面是一整套 ipc_agm_* 方法,跟 agm_api.h 里的 C 接口一一对应:ipc_agm_session_open、ipc_agm_session_set_metadata、ipc_agm_session_aif_connect、ipc_agm_session_write_with_metadata,等等。

请求怎么从接口进到实现,看构建文件 agm_ipc_service/Android.mk:

  • vendor.qti.hardware.AGMIPC@1.0-impl:源码 src/agm_server_wrapper.cpp,链接 libagm。这是 HIDL 实现层,把 ipc_agm_* 的 binder 调用转成 agm_* 的本地函数调用。
  • vendor.qti.hardware.AGMIPC@1.0-service:源码 src/service.cpp,链接上面的 -impl 和 libagm,是可执行服务进程。

libagm 本体在 agm/service/,源码就是 agm.c、graph.c、graph_module.c、metadata.c、session_obj.c、device.c、device_hw_ep.c、utils.c 这八个文件。agm.c 里那批 agm_session_open、agm_session_write_with_metadata 就是最终落地实现,它们再往下委托给 session_obj_*、graph_*、device_*。

客户端这一侧,PAL 链接的是 libagmclient(pal/Android.mk),不是 libagm。libagmclient 是 HwBinder 客户端封装,对上层暴露同样的 agm_session_* 符号,内部把调用封成 ipc_agm_* 走 binder。

AGM 目录里还有 DBus 和 SwBinders 两套 IPC。本平台用到的是 HwBinders 这套(有 init .rc、VINTF 清单,且 PAL 链接的是 libagmclient)。三套 IPC 并存,说明 AGM 服务本身是传输无关的,选哪套由构建和部署决定。


三、AGM 如何管理 Session 和 Graph

AGM 内部有四个对象,定义在 service/inc/private/agm/ 下:

对象
定义位置
代表什么
关键字段
session_obj
session_obj.h
一次 AGM 会话
sess_id
、state、sess_meta(gkv/ckv)、aif_pool、graph 指针、媒体/缓冲配置
aif
session_obj.h
挂到会话上的一个音频接口
aif_id
、dev_obj、state、sess_aif_meta、params、tag_config
device_obj
device.h
硬件端点
name
、card_id、pcm_id、hw_ep_info(接口类型)、media_config、metadata
graph_obj
graph.h
一个 DSP 图的封装
graph_handle
(即 GSL 句柄)、state、sess_obj 指针、模块列表

几个要点:

session_obj 是一个会话,它通过 aif_pool 挂着若干 aif(Audio InterFace,音频接口),每个 aif 指向一个 device_obj。会话还持有一个 graph 指针,指向它开的图。

device_obj 是硬件端点。device_init() 从 /proc/asound/pcm 枚举设备,名字形如 TDM-LPAIF_WSA-RX-SECONDARY。hw_ep_info 里的接口类型是枚举:CODEC_DMA、MI2S、TDM、AUXPCM、SLIMBUS、DISPLAY_PORT、USB_AUDIO、PCM_RT_PROXY、AUDIOSS_DMA。所以 AGM 的 Device 是"内核里看得见的那个硬件接口",跟第三篇 PAL 的 Device(逻辑输出端点)不是一回事,层级更低。

graph_obj 是图的封装,它手里的 graph_handle 就是 GSL 的句柄。AGM 的 graph.c 是一层薄封装,几乎每个函数都直通 GSL,这个在第五节展开。

元数据由 agm_meta_data_gsl(agm_priv.h)承载,装的是三类 key 向量:gkv(图键)、ckv(标定键)、sg_props(子图属性)。会话有自己的元数据,设备有自己的元数据,开图前用 metadata_merge()(metadata.h)把两者合并成一份复合 gkv/ckv。

对象生命周期在 graph.h 的注释里写得很明确:

The order in which the objects should be created is session, device in any order then the last object to be created is graph. Similarly when closing the session, the first object to be closed is the graph object and then the session/device objects.

创建顺序:先建 session、device,最后开 graph。销毁顺序反过来:先关 graph,再关 session/device。这个顺序对应下面第四节的真实调用链。


四、从接口调用到音频图配置

挑一条证据充分的链来看:会话连接音频接口,进而开出图。

客户端先 agm_session_set_metadata 给会话设元数据,agm_aif_set_metadata 给设备设元数据,然后 agm_session_aif_connect(session_id, aif_id, state) 把会话和设备接起来。这个调用在服务端落到 session_obj_sess_aif_connect(session_obj.c),它干两件事:打开设备,再开图。

开图的入口是 graph_open(graph.c),它的参数是合并后的元数据、会话对象、设备对象。函数内部走了这几步:

  1. get_tags_with_module_info(&gkv) 调 gsl_get_tags_with_module_info,问 GSL:给定这个图键,图里有哪些 tag,每个 tag 对应哪个模块实例(miid/mid)。
  2. 拿 AGM 内置的默认流模块列表 get_stream_module_list_array 和默认硬件端点模块列表 get_hw_ep_module_list_array,把 tag 匹配上的模块填进本地模块链表。
  3. gsl_open(&gkv, &ckv, &graph_handle) 把图键和标定键交给 GSL,把图加载到 DSP,拿到 graph_handle。
  4. gsl_register_event_cb(graph_handle, gsl_callback_func, graph_obj) 注册事件回调。

所以 AGM 开图前会先问一遍"这张图里有哪些 tag、哪些模块",据此在AP 侧建一份模块镜像,之后 graph_set_config、graph_set_cal 才能按 tag 找到目标模块实例。

步骤
调用者
被调用接口
关键参数
结果
1
客户端
agm_session_aif_connect
session_id, aif_id, state
进入 session_obj_sess_aif_connect
2
session_obj
device_open
dev_obj
打开硬件端点
3
session_obj
graph_open
merged_metadata, sess_obj, dev_obj
建 graph_obj
4
graph
gsl_get_tags_with_module_info
gkv
拿 tag→模块映射
5
graph
gsl_open
gkv, ckv
图加载到 DSP,得 graph_handle
6
graph
gsl_register_event_cb
graph_handle, cb
注册事件回调

后续的 prepare/start/write 也都在这一层接力。session_obj_prepare 调 graph_prepare,session_obj_start 调 graph_start,session_obj_write 调 graph_write,session_obj_close 先 graph_close 再关 session/device。session 是面向客户端的门面,具体操作都在 graph 这一层,session 把生命周期原样转给 graph。


五、AGM 如何与底层交互

AGM 下面不是直接到 ADSP,中间还有 GSL 和 GPR 两层。libagm 的构建文件(agm/service/Android.mk)写死了这个依赖:LOCAL_SHARED_LIBRARIES += libar-gsl。

graph.c 里能直接看到这层薄封装。把关键函数的落点列出来:

AGM 函数(graph.c)
GSL 接口(gsl_intf.h)
用途
graph_initgsl_init
初始化 GSL,加载 ACDB 文件
graph_opengsl_open
按 gkv/ckv 加载图到 DSP
graph_preparegsl_ioctl(GSL_CMD_PREPARE)
预初始化图模块
graph_startgsl_ioctl(GSL_CMD_START)
启动图
graph_stopgsl_ioctl(GSL_CMD_STOP)
停止图
graph_addgsl_ioctl(GSL_CMD_ADD_GRAPH)
加子图(如 SSSD→SSMD 加设备腿)
graph_changegsl_ioctl(GSL_CMD_CHANGE_GRAPH)
换图(如设备切换)
graph_writegsl_write
写数据到图
graph_readgsl_read
从图读数据
graph_set_configgsl_set_config
按 tag+tkv 配模块参数
graph_set_calgsl_set_cal
按 ckv 配标定
graph_closegsl_close
关图

这张表就是三类通路的清晰分界:

  • 数据通路:graph_write/graph_read → gsl_write/gsl_read。
  • 控制通路:graph_prepare/start/stop/add/change → gsl_ioctl 的 GSL_CMD_* 命令。
  • 配置通路:graph_set_config/graph_set_cal → gsl_set_config/gsl_set_cal,还有 graph_set_config_with_tag 走 gsl_set_tagged_custom_config 这类带 tag 的变体。

GSL 再往下还有依赖。libar-gsl 的构建文件(args/gsl/Android.mk)声明它链接 libar-gpr、libar-acdb、libspf-headers、liblx-osal。GPR(Generic Packet Router)是传输层,源码在 args/gpr/,它的 datalink 有两个变体:gpr_lx(gpr_lx.c)和 gpr_lrh(gpr_glink.c,走 GLINK)。本构建默认用 gpr_lx。libar-acdb 是标定数据库库,libspf-headers 是 SPF 消息结构定义。

AGM(libagm) → GSL(libar-gsl) → GPR(libar-gpr) → datalink(gpr_lx / gpr_lrh-GLink) → ADSP SPF(闭源)                    └→ ACDB(libar-acdb,标定/图/tag 查询)

六、资源 XML、Graph 和参数到底是什么关系

一个容易混的地方:resourcemanager_gvmauto8295_adp_star.xml 是 PAL 的配置,不是 AGM 的。

第三篇和之前的 research 已经确认,这个 XML 由 ResourceManager.cpp 解析,文件名由 ALSA 声卡名派生,里面是 native_audio_mode、max_sessions、bt_codecs、controls 和各个 PAL_STREAM_* 用例图。它定义的是"PAL 这一层怎么组织流和会话",不直接是"AGM 开哪张图"。

AGM 读的是另一套东西:ACDB。graph_init()(graph.c)里拼出 ACDB 路径 /vendor/etc/acdbdata/<声卡后缀>,get_acdb_files_from_directory 列目录里的 ACDB 文件,塞进 gsl_init_data,再 gsl_init(&init_data) 交给 GSL。ACDB(Audio Calibration Database)是图、tag、标定的 key→数据 数据库,libar-acdb 在 args/acdb/。

所以两套配置的边界是:resourcemanager XML 管 PAL 的流/会话组织,ACDB 管 DSP 图与标定。二者不在一个进程里读,也不该被当成同一件事。

这里要守一条纪律:配置文件里存在某个 Graph 或 tag,不等于运行时选中了它,也不等于参数下到了 DSP。能区分的四层是:

  1. 文件里存在:ACDB 文件、XML 里写了某个用例图。
  2. 代码会读取:graph_init 读了 ACDB 目录,ResourceManager 解析了 XML。
  3. 运行时被选中:某条流在运行时用特定 gkv 调了 gsl_open,命中了某个图。这一步要日志才看得到。
  4. DSP 侧成功执行:图在 ADSP 上跑起来。

7、GKV、CKV、TKV、MFC 在整条链路中的位置

这三类 key 向量在第三篇已经讲过它们的含义,这里只补它们在 AGM 这一层的落点。

AGM 侧的元数据结构 agm_meta_data_gsl(agm_priv.h)装着 gkv 和 ckv。graph_open 把这两者原样传给 gsl_open(graph_key_vect, cal_key_vect, ...),也就是:GKV 选图,CKV 查标定,在"开图"这一步就一起交给了 GSL。

TKV 走另一条路。agm_tag_config(agm_api.h)是 tag + 一组 key=value,经 agm_set_params_with_tag 或 agm_session_aif_set_cal 这类接口下发,最终落到 graph_set_config_with_tag → gsl_set_config(graph_handle, gkv, tag, tag_key_vect)。所以 TKV 是"图开好之后,按 tag 给模块配参数"。

MFC 是图内的一个模块,它的采样率、声道、位宽参数在 PAL 侧被编成 MFC_SR_*、CHS_*、BW_* 这些 tag,走的就是 TKV 机制。


八、总结与下一篇预告

AGM 的职责,本文能确认的有三点:

  • 它是一个 HwBinder 服务,对外是 IAGM 接口,对内用 libagm 的 agm_* 函数管理 session、aif、device、graph 四类对象。
  • 它是AP侧图管理器,graph.c 把开图、启动、停止、加子图、配参数、查标定全部转成 GSL 接口。
  • 它的下层是 GSL → GPR → GLink/GLINK → ADSP,中间还有 ACDB 提供图与标定查询。

它的边界同样清楚:本平台的常规播放走 SessionAlsaPcm,数据经 ALSA PCM、配置经 ALSA mixer,不经过 AGM。AGM 的图管理服务于 NON_TUNNEL、hostless、AMS 这些直接图通路。

结语

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

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

精彩合集

  专栏推荐

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

相关学习资料