Android音频学习(十四)——加载音频设备

1.加载流程

在之前的章节中有提到,在Android系统启动时会启动两个服务,AudioFlinger和AudioPolicyService, 在AudioPolicyService初始化时,调用createAudioPolicyManger创建AudioPolicyManager以及进行初始化操作,总体流程如下:

loadConfig()功能是分析加载音频配置文件audio_policy_config.xml文件,在第十二节有详细的介绍。

apm->initialize中的加载Engine流程在第十三章有详细的介绍,这节我们主要分析onNewAudioModulesAvailableInt中一个重要的流程------加载硬件设备,调用流程如下:

af->loadHwModule调用到audioFlinger服务中的loadHwModule函数,然后再调用到mDevicesFactoryHal->openDevice, loadHwModule最终的目标是openDevice。

具体来说,loadHwModule加载的是实现了audio_hw_device_t结构体的动态共享库(.so文件)。每个硬件模块代表一种音频设备类型,例如:

  • primary:主音频设备(通常对应设备内置的扬声器、听筒、麦克风等)

  • a2dp:蓝牙A2DP设备

  • usb:USB音频设备

  • remote_submix:用于模拟音频输出/输入,常用于Wifi Display或Android模拟器

在解析配置文件(audio_policy_configuration.xml)时,会为每个<module>标签创建一个HwModule对象,并指定其名称(如"primary")和HAL版本。然后,在初始化过程中,APM会调用loadHwModule来加载对应的HAL库。举个例子:假设配置文件中有一个模块名为"primary",那么APM会尝试加载名为"audio.primary.<device>.so"的库,其中<device>是设备名称(由ro.product.board(这个值代表cpu型号)等属性决定)。

例如,在Pixel设备上,可能会加载"audio.primary.sailfish.so"。具体过程如下:

(1) APM在初始化时(loadConfig)解析配置文件,创建HwModule对象。

(2)当调用loadHwModule时,会通过HAL的hw_get_module_by_class()函数利用HAL层注册信息id和name,获取相应的模块函数根据模块名,主要用于id相同、name不同,即获取相同功能但厂家不同的硬件库。如"AUDIO_HARDWARE_MODULE_ID_PRIMARY"即"primary",获取对应的HAL模块。

  1. 然后调用audio_hw_device_open找到对应的open函数打开该模块,得到audio_hw_device_t结构体,用于后续操作(如打开音频流)。

例如,加载的音频库有:

  • 主音频模块:/vendor/lib/hw/audio.primary.sdm660.so(qcom)

  • 蓝牙A2DP模块:/vendor/lib/hw/audio.a2dp.default.so

所以,loadHwModule加载的是音频硬件抽象层(HAL)的模块,这些模块以动态库的形式存在,并实现了音频硬件的操作接口(如打开/关闭流、设置参数等)。

2.加载内容

(1) 硬件模块对象

在加载音频配置文件时创建 HwModule 对象,包含:

  • HAL 接口audio_hw_device_t 结构体指针

  • 设备描述DeviceDescriptor 集合(扬声器/麦克风等)

  • I/O 能力IOProfile 列表(输入/输出端口配置)

  • 路由策略AudioRoute 拓扑关系

(2)HAL 动态库

实际加载的共享库文件,命名规则:

示例路径

/vendor/lib/hw/audio.primary.${platform}.so

如高通平台

/vendor/lib/hw/audio.primary.msm8998.so

3. HAL层源码示例

cpp 复制代码
// audio.primary.msm8998.c
static struct hw_module_methods_t hal_module_methods = {
    .open = adev_open
};

struct audio_module HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .version_major = 7,
        .id = AUDIO_HARDWARE_MODULE_ID,
        .name = "QCOM Primary Audio HAL",
        .methods = &hal_module_methods,
    }
};

static int adev_open(const hw_module_t* module, 
                    const char* name,
                    hw_device_t** device) {
    struct primary_audio_dev *adev;
    adev = calloc(1, sizeof(struct primary_audio_dev));
    
    // 填充HAL操作函数
    adev->device.common.close = adev_close;
    adev->device.init_check = adev_init_check;
    adev->device.set_voice_volume = adev_set_voice_volume;
    // ...其他音频操作函数
    
    *device = &adev->device.common;
    return 0;
}

4. 调试技巧

(1)检查加载状态

adb shell dumpsys media.audio_policy | grep "HW Modules"

输出示例:

HW Modules:

  • Module primary: handle 42

  • Output profiles: primary output

  • Devices: Speaker (AUDIO_DEVICE_OUT_SPEAKER)

(2)查看 HAL 版本

// 在HAL实现中打印版本

ALOGI("HAL version: %d.%d",

module->halVersionMajor,

module->halVersionMinor);

(3)加载失败排查

常见错误日志

loadHwModule() error 0xfffffffe opening HAL "a2dp"

排查步骤:

  1. 检查 /vendor/lib/hw/audio.a2dp.default.so 是否存在

  2. 验证 SELinux 权限:avc: denied 相关日志

  3. 确认 HAL 符号导出:nm -D audio.primary.so | grep HAL_MODULE_INFO_SYM

总结:

loadHwModule 是 Android 音频系统的核心初始化函数,负责加载音频硬件抽象层(HAL)的模块实现 。它建立 AudioPolicyService 与底层硬件的桥梁,将配置文件(audio_policy_config.xml)中的 标签<module>定义转化为可操作的硬件对象,加载的是音频硬件模块(HAL实现),每个模块对应一种音频设备类型,以动态库的形式存在,负责与底层音频硬件交互。