2. 搭建音频HAL开发与编译环境

任务说明

本案例要求实现一个最简可加载的音频HAL模块,提供open、close等基础接口,并能在Android系统中通过HAL加载机制成功加载。为聚焦核心目标,本案例不涉及音频数据通路,仅实现模块生命周期管理接口。

模块结构设计

音频HAL模块遵循Android统一HAL规范,需要导出hw_module_t和hw_device_t两个关键结构。模块加载时,系统通过HAL_MODULE_INFO_SYM符号找到模块入口,调用open方法创建设备实例,close方法释放资源。以下代码实现了一个最小但完整的音频HAL模块:


// audio_hal_minimal.c
#include <cutils/log.h>
#include <hardware/hardware.h>
#include <hardware/audio.h>

/* 设备open回调:创建并初始化音频设备实例 */
static int audio_device_open(const struct hw_module_t* module,
                             const char* name,
                             struct hw_device_t** device) {
    struct audio_device_t* dev = calloc(1, sizeof(struct audio_device_t));
    if (!dev) {
        ALOGE("audio_device_open: memory allocation failed");
        return -ENOMEM;
    }

    dev->common.tag = HARDWARE_DEVICE_TAG;
    dev->common.version = AUDIO_DEVICE_API_VERSION_2_0;
    dev->common.module = (struct hw_module_t*)module;
    dev->common.close = audio_device_close;

    /* 此处仅实现模块加载验证,不注册音频数据接口 */
    *device = &dev->common;
    ALOGI("audio_device_open: device opened successfully");
    return 0;
}

/* 设备close回调:释放设备实例 */
static int audio_device_close(struct hw_device_t* device) {
    if (device) {
        free(device);
        ALOGI("audio_device_close: device closed");
    }
    return 0;
}

/* 模块方法表 */
static struct hw_module_methods_t audio_module_methods = {
    .open = audio_device_open,
};

/* 模块入口符号 */
struct audio_module_t HAL_MODULE_INFO_SYM = {
    .common = {
        .tag = HARDWARE_MODULE_TAG,
        .module_api_version = AUDIO_MODULE_API_VERSION_0_1,
        .hal_api_version = HARDWARE_HAL_API_VERSION,
        .id = AUDIO_HARDWARE_MODULE_ID,
        .name = "Minimal Audio HAL Module",
        .author = "Android HAL Course",
        .methods = &audio_module_methods,
    },
};

编译配置

在Android源码树中创建device/yourcompany/yourdevice/audio/目录,将上述代码保存为audio_hal_minimal.c,并添加以下Android.bp编译脚本:


// Android.bp
cc_library_shared {
    name: "audio.primary.minimal",
    relative_install_path: "hw",
    proprietary: true,
    srcs: ["audio_hal_minimal.c"],
    shared_libs: [
        "liblog",
        "libcutils",
        "libhardware",
    ],
}

模块加载验证

编译后将生成的audio.primary.minimal.so推送至设备/vendor/lib64/hw/目录(64位系统)或/system/lib/hw/(32位系统)。通过以下命令验证模块能否被系统正确加载:


# 在设备上执行
adb shell
# 检查模块文件是否存在
ls -l /vendor/lib64/hw/audio.primary.minimal.so
# 使用HAL查询工具验证模块加载(需root权限)
su
# 通过dlopen直接加载并检查符号
cat > /data/local/tmp/test_hal_load.c << 'EOF'
#include <stdio.h>
#include <dlfcn.h>
#include <hardware/hardware.h>

int main() {
    void* handle = dlopen("/vendor/lib64/hw/audio.primary.minimal.so", RTLD_NOW);
    if (!handle) {
        printf("dlopen failed: %s\n", dlerror());
        return 1;
    }
    
    struct audio_module_t* module = (struct audio_module_t*)dlsym(handle, HAL_MODULE_INFO_SYM_AS_STR);
    if (!module) {
        printf("dlsym failed: %s\n", dlerror());
        return 1;
    }
    
    printf("Module ID: %s\n", module->common.id);
    printf("Module API version: %d\n", module->common.module_api_version);
    
    struct hw_device_t* dev = NULL;
    int ret = module->common.methods->open(&module->common, AUDIO_HARDWARE_MODULE_ID, &dev);
    if (ret == 0 && dev) {
        printf("open success, tag=0x%x\n", dev->tag);
        dev->close(dev);
        printf("close success\n");
    } else {
        printf("open failed: %d\n", ret);
    }
    
    dlclose(handle);
    return 0;
}
EOF
# 编译并运行验证程序
cd /data/local/tmp
# 使用NDK工具链交叉编译后推送,或直接在设备端用tcc编译
# 此处假设已编译为test_hal_load可执行文件
./test_hal_load

验证判定标准

满足以下条件即可判定案例通过:

  1. 编译无错误,生成audio.primary.minimal.so文件;
  2. 模块文件成功推送至目标目录,文件权限为644;
  3. 验证程序输出包含Module ID: audio,表明模块符号正确导出;
  4. 验证程序输出open success和close success,表明open/close接口调用成功;
  5. 系统日志(logcat -s audio_hal_minimal)中可见模块加载和打开的设备日志。
音频HAL模块加载与调用时序 AudioFlinger hw_get_module() audio.primary.minimal.so audio_device_t 1. 调用hw_get_module("audio") 2. dlopen + dlsym(HAL_MODULE_INFO_SYM) 3. 返回module指针 4. module->methods->open() 5. 创建设备实例并返回 设备实例(含close回调)

关键实现说明

本模块的核心在于正确导出HAL_MODULE_INFO_SYM符号,该符号必须为audio_module_t类型且首字段为hw_module_t common。模块的id字段必须为AUDIO_HARDWARE_MODULE_ID(即"audio"),这是系统查找模块的关键匹配项。open回调中分配设备内存并填充hw_device_t公共字段,其中close函数指针指向设备关闭回调。由于本案例仅验证模块加载机制,设备结构体中未注册任何音频数据接口函数,这是刻意的最小化设计。

编译时需确保Android.bp中relative_install_path: "hw"使模块安装到正确的HAL子目录。验证程序通过dlopen直接加载模块并调用open/close,绕过了AudioFlinger的完整调用链,能够独立验证模块接口的正确性。若系统集成时AudioFlinger无法加载此模块,需检查模块文件名是否匹配audio.primary.*.so模式,以及设备树中ro.hardware.audio.primary属性是否指向"minimal"。