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
验证判定标准
满足以下条件即可判定案例通过:
- 编译无错误,生成
audio.primary.minimal.so文件; - 模块文件成功推送至目标目录,文件权限为644;
- 验证程序输出包含
Module ID: audio,表明模块符号正确导出; - 验证程序输出
open success和close success,表明open/close接口调用成功; - 系统日志(
logcat -s audio_hal_minimal)中可见模块加载和打开的设备日志。
关键实现说明
本模块的核心在于正确导出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"。