【车载Audio】【AudioHal 04】【高通音频架构】【从 AHAL adev_open 到 PAL XML 解析:30微秒内的调用链深度追踪】

从 AHAL adev_open 到 PAL XML 解析:30微秒内的调用链深度追踪

有兴趣可以先看 深入解析 Android 音频策略:onNewAudioModulesAvailableInt 的全链路探索

1. 问题的引入

在分析 Android 音频系统启动日志时,我们经常会看到两条紧挨着的日志,时间间隔极其微小(本例中仅为 30 微秒):

log 复制代码
02-11 10:35:14.248370   698   698 I AHAL: AudioDevice: adev_open 2650 ahal_log_lvl 0xf
02-11 10:35:14.248400   698   698 I PAL: ResourceManager: XmlParser: 10897: XML parsing started - file name /vendor/etc/card-defs.xml
02-11 10:35:14.251292   698   698 I PAL: ResourceManager: processDeviceIdProp: 9964: processDeviceIdProp find bus card BUS00_MEDIA bind to 124
02-11 10:35:14.251347   698   698 I PAL: ResourceManager: processDeviceIdProp: 9964: processDeviceIdProp find bus card BUS01_SYS_NOTIFICATION bind to 125
02-11 10:35:14.251371   698   698 I PAL: ResourceManager: processDeviceIdProp: 9964: processDeviceIdProp find bus card BUS02_NAV_GUIDANCE bind to 126

AHAL (Audio HAL) 刚说要 openPAL (Platform Audio Layer) 紧接着就开始解析 XML 配置文件了。这中间到底发生了什么?是谁触发了 PAL 的加载?本文将通过源码深度揭秘。


2. 核心调用链路总览

首先,我们通过一张时序图直观地看下整个调用过程:
ResourceManager (PAL Core) Pal.cpp (PAL API) AudioDevice (AHAL) AudioFlinger ResourceManager (PAL Core) Pal.cpp (PAL API) AudioDevice (AHAL) AudioFlinger 打印日志: adev_open 2650... 触发单例构造函数 打印日志: XML parsing started... adev_open() (通过 hw_module->>methods->>open) 1 adevice->>Init() 2 pal_init() 3 ResourceManager::getInstance() 4 ResourceManager::ResourceManager() 5 ResourceManager::XmlParser("/vendor/etc/card-defs.xml") 6


3. 源码级解析

第一阶段:AHAL 的入口 adev_open

当 Android 的 AudioFlinger 通过 audio hal 加载厂商的 audio.primary.xxx.so 时,会通过 HAL 框架调用 .open 接口。在高通的实现中,这个函数位于 AudioDevice.cpp

文件路径vendor/qcom/opensource/audio-hal-ar/primary-hal/hal-pal/AudioDevice.cpp

cpp 复制代码
// 源码位置:AudioDevice.cpp 约 2622 行
static int adev_open(const hw_module_t *module, const char *name __unused,
                     hw_device_t **device) {
    // ...
    // 关键点 1:打印第一条日志
    property_get("vendor.audio.hal.log_type", ahal_log_type, "");
    ALOGI("%s %d ahal_log_lvl 0x%x", __func__,__LINE__,ahal_log_lvl); 

    std::shared_ptr<AudioDevice> adevice = AudioDevice::GetInstance();
    
    // ...
    // 关键点 2:调用 Init 函数
    ret = adevice->Init(device, module); 
    // ...
}

第二阶段:从 AHAL 跨越到 PAL

AudioDevice::Init 函数中,高通 AHAL 会初始化其核心引擎------PAL (Platform Audio Layer)

文件路径vendor/qcom/opensource/audio-hal-ar/primary-hal/hal-pal/AudioDevice.cpp

cpp 复制代码
// 源码位置:AudioDevice.cpp 约 1077 行
int AudioDevice::Init(hw_device_t **device, const hw_module_t *module) {
    int ret = 0;
    
    // ...
    // 关键点 3:调用 PAL 库的初始化入口
    ret = pal_init(); 
    if (ret) {
        AHAL_ERR("pal_init failed ret=(%d)", ret);
        return -EINVAL;
    }
    // ...
}

第三阶段:PAL 内部单例触发

pal_init() 是 PAL 库暴露给 AHAL 的标准 API。它的职责是拉起 PAL 内部的资源管理器 ResourceManager

文件路径vendor/qcom/opensource/pal/Pal.cpp

cpp 复制代码
// 源码位置:Pal.cpp
int32_t pal_init(void)
{
    int32_t ret = 0;
    std::shared_ptr<ResourceManager> ri = NULL;
    try {
        // 关键点 4:获取 ResourceManager 单例
        // 如果是开机后第一次调用,将触发 ResourceManager 的构造函数
        ri = ResourceManager::getInstance(); 
    } catch (const std::exception& e) {
        PAL_ERR(LOG_TAG, "pal init failed: %s", e.what());
        return -EINVAL;
    }
    // ...
}

第四阶段:ResourceManager 构造与 XML 解析

由于 ResourceManager 是单例模式,getInstance() 会执行 new ResourceManager()。在这个构造函数中,系统会根据配置文件初始化所有的音频路由、设备和策略。

文件路径vendor/qcom/opensource/pal/resource_manager/src/ResourceManager.cpp

cpp 复制代码
// 源码位置:ResourceManager.cpp 约 683 行
ResourceManager::ResourceManager()
{
    int ret = 0;
    // ... 各种属性初始化 ...

    // 关键点 5:解析第一个 XML (通常是 resourcemanager.xml)
    ret = ResourceManager::XmlParser(SNDPARSER); 

    // 关键点 6:解析第二个 XML (即日志中看到的 card-defs.xml)
    // rmngr_xml_file 的路径通常指向 /vendor/etc/card-defs.xml
    ret = ResourceManager::XmlParser(rmngr_xml_file); 
    if (ret) {
        PAL_ERR(LOG_TAG, "error in resource xml parsing ret %d", ret);
        throw std::runtime_error("error in resource xml parsing");
    }
    // ...
}

// 真正打印日志的地方
int ResourceManager::XmlParser(std::string xmlFile) {
    // 源码位置:ResourceManager.cpp 约 10897 行
    PAL_INFO(LOG_TAG, "XML parsing started - file name %s", xmlFile.c_str());
    // ... 开始解析逻辑 ...
}

4. 深度解析 card-defs.xml:PAL 的硬件资源底座

在上面的 Log 2 中,我们看到了 card-defs.xml 被解析。这个文件到底起到了什么作用?

  • card-defs.xml
xml 复制代码
<defs>
<card>
    <id>100</id>
    <name>gvmauto-virtual,sa8155adpstarsn</name>


    <pcm-device>
        <id>124</id>
        <name>PCM124</name>
        <bus_name>BUS00_MEDIA</bus_name>
        <pcm_plugin>
            <so-name>libagm_pcm_plugin.so</so-name>
        </pcm_plugin>
        <props>
            <playback>1</playback>
            <capture>0</capture>
            <session_mode>0</session_mode>
        </props>
    </pcm-device>

    <pcm-device>
        <id>125</id>
        <name>PCM125</name>
        <bus_name>BUS01_SYS_NOTIFICATION</bus_name>
        <pcm_plugin>
            <so-name>libagm_pcm_plugin.so</so-name>
        </pcm_plugin>
        <props>
            <playback>1</playback>
            <capture>0</capture>
            <session_mode>0</session_mode>
        </props>
    </pcm-device>

    <pcm-device>
        <id>126</id>
        <name>PCM126</name>
        <bus_name>BUS02_NAV_GUIDANCE</bus_name>
        <pcm_plugin>
            <so-name>libagm_pcm_plugin.so</so-name>
        </pcm_plugin>
        <props>
            <playback>1</playback>
            <capture>0</capture>
            <session_mode>0</session_mode>
        </props>
    </pcm-device>
    


    <mixer>
        <id>1</id>
        <name>agm_mixer</name>
        <mixer_plugin>
            <so-name>libagm_mixer_plugin.so</so-name>
        </mixer_plugin>
    </mixer>

</card>
</defs>

4.1 XML 标签与源码 C++ 映射关系

ResourceManager::XmlParser 被调用后,它会利用 libxml2 遍历文件。核心的解析逻辑位于 ResourceManager::processDeviceIdProp 等函数中。

XML 标签 源码解析函数 对应的 C++ 数据结构 / 逻辑
<card> startTagHandler 开启一个新的声卡定义(虚拟声卡概念)。
<pcm-device> processDeviceIdProp 创建一个新的 deviceCap 结构体,存入 devInfo 容器。
<id> processDeviceIdProp 存储为 deviceId(如 124),对应底层的 PCM 端口 ID。
<bus_name> processDeviceIdProp 关键映射点 :将逻辑 Bus(如 BUS00_MEDIA)与物理 ID 绑定。
<props> processDeviceCapability 解析播放(playback)、录音(capture)及会话模式(session_mode)。

4.2 标签背后的源码逻辑实现

以解析 <bus_name> 为例:

cpp 复制代码
// ResourceManager.cpp
void ResourceManager::processDeviceIdProp(struct xml_userdata *data, const XML_Char *tag_name) {
    // ...
    } else if (!strcmp(tag_name, "bus_name")) {
        // 将 XML 里的 "BUS00_MEDIA" 拷贝到 ext_name 字段
        strlcpy(devInfo[size].ext_name, data->data_buf, strlen(data->data_buf)+1);
        // 设置标志位,标识这是一个 Bus 类型的设备
        devInfo[size].device_flag = BUS_NAME_FLAG; 
        PAL_INFO(LOG_TAG, "find bus card %s bind to %d", data->data_buf, devInfo[size].deviceId);
    }
    // ...
}

4.3 card-defs.xml 的核心作用:桥梁与清单

  1. 作为"硬件配置清单":它告知 PAL 这一代芯片(如 sa8295)到底开放了多少个虚拟 PCM 端口(100号到155号)。
  2. 实现"逻辑到物理"的路由映射
    • Android 框架层通过 Audio Bus 寻址(逻辑地址)。
    • 底层驱动通过 PCM ID 传输数据(物理 ID)。
    • card-defs.xml 里的 <bus_name> 标签正是这两者之间的强绑定关系。当 AudioPolicy 请求 Media 播放时,系统通过查这张"表",知道该去敲 124 号 PCM 的大门。
  3. 定义"会话模式" :通过 session_mode 标签,区分了普通播放通道(DEFAULT)和用于特殊处理的通道(如 HOSTLESS:不需要 CPU 参与的直接通路)。

5. 总结

那 30 微秒的跨度,完成了从 Android 原生 HAL 接口调用厂商私有硬件描述解析 的华丽转身。

  • AHAL 只负责"迎宾",它遵守 Android 的标准协议。
  • PAL 才是"内管家",它通过解析 card-defs.xml 来掌控整个声卡的硬件拓扑。

通过这种"XML 定义拓扑,源码执行逻辑"的架构,高通实现了同一套代码支持不同变体芯片(如 sa8155 vs sa8295)的灵活性。


参考源码路径

  • vendor/qcom/opensource/audio-hal-ar/primary-hal/hal-pal/AudioDevice.cpp
  • vendor/qcom/opensource/pal/Pal.cpp
  • vendor/qcom/opensource/pal/resource_manager/src/ResourceManager.cpp
相关推荐
REDcker3 小时前
FFmpeg完整文档
linux·服务器·c++·ffmpeg·音视频·c·后端开发
linux_cfan3 小时前
WordPress 视频播放痛点解决方案:支持 RTSP/WebRTC 与字幕检索的 ZWPlayer 插件实测
php·音视频·webrtc
AI周红伟17 小时前
周红伟:字节官方发布Seedance 2.0 视频模型,技术实现过程和 技术分解
音视频
查无此人byebye20 小时前
实战DDPM扩散模型:MNIST手写数字生成+FID分数计算(完整可运行版)
人工智能·pytorch·python·深度学习·音视频
九丝城主1 天前
1V1音视频对话2--Web 双浏览器完整通话测试(强制 relay)
前端·音视频
哈__1 天前
基础入门 Flutter for OpenHarmony:video_player 视频播放组件详解
flutter·音视频
小陈Coding1 天前
互联网大厂Java面试实录:电商音视频内容社区场景深度解析
aigc·音视频·java面试·电商·技术面试·互联网大厂·内容社区
TEC_INO1 天前
Linux_22:音频AAC编码
音视频·aac