ARTICLE · 1259452
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 | sess_idstate、sess_meta(gkv/ckv)、aif_pool、graph 指针、媒体/缓冲配置 | ||
aif | aif_iddev_obj、state、sess_aif_meta、params、tag_config | ||
device_obj | namecard_id、pcm_id、hw_ep_info(接口类型)、media_config、metadata | ||
graph_obj | graph_handlestate、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),它的参数是合并后的元数据、会话对象、设备对象。函数内部走了这几步:
get_tags_with_module_info(&gkv)调gsl_get_tags_with_module_info,问 GSL:给定这个图键,图里有哪些 tag,每个 tag 对应哪个模块实例(miid/mid)。拿 AGM 内置的默认流模块列表 get_stream_module_list_array和默认硬件端点模块列表get_hw_ep_module_list_array,把 tag 匹配上的模块填进本地模块链表。gsl_open(&gkv, &ckv, &graph_handle)把图键和标定键交给 GSL,把图加载到 DSP,拿到graph_handle。gsl_register_event_cb(graph_handle, gsl_callback_func, graph_obj)注册事件回调。
所以 AGM 开图前会先问一遍"这张图里有哪些 tag、哪些模块",据此在AP 侧建一份模块镜像,之后 graph_set_config、graph_set_cal 才能按 tag 找到目标模块实例。
agm_session_aif_connect | ||||
device_open | ||||
graph_open | ||||
gsl_get_tags_with_module_info | ||||
gsl_open | ||||
gsl_register_event_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 里能直接看到这层薄封装。把关键函数的落点列出来:
graph_init | gsl_init | |
graph_open | gsl_open | |
graph_prepare | gsl_ioctl(GSL_CMD_PREPARE) | |
graph_start | gsl_ioctl(GSL_CMD_START) | |
graph_stop | gsl_ioctl(GSL_CMD_STOP) | |
graph_add | gsl_ioctl(GSL_CMD_ADD_GRAPH) | |
graph_change | gsl_ioctl(GSL_CMD_CHANGE_GRAPH) | |
graph_write | gsl_write | |
graph_read | gsl_read | |
graph_set_config | gsl_set_config | |
graph_set_cal | gsl_set_cal | |
graph_close | gsl_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。能区分的四层是:
文件里存在:ACDB 文件、XML 里写了某个用例图。 代码会读取: graph_init读了 ACDB 目录,ResourceManager解析了 XML。运行时被选中:某条流在运行时用特定 gkv 调了 gsl_open,命中了某个图。这一步要日志才看得到。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 系统的每一寸深度!
精彩合集
专栏推荐















































































