8、音频HAL层接口:HAL架构与标准接口、HAL加载与调用流程、HAL与AudioFlinger的交互、常见HAL实现

好,我们进入音频HAL层。这一层,说白了就是AudioFlinger和硬件之间的翻译官。你想想看,AudioFlinger是个纯软件框架,它不懂怎么操作具体的音频芯片。而硬件厂商的驱动,又各有各的脾气。HAL就是中间那个标准化的适配层。

我当年刚接触Android音频时,有个很深的体会:搞懂了HAL,你就搞懂了音频系统的边界。哪里是软件的事,哪里是硬件的事,全看HAL怎么划分。

8.1 HAL架构与标准接口

Android的音频HAL架构,其实经历过一次大升级。早期是legacy HAL,后来演进到了HIDL HAL,再到现在的AIDL HAL。但不管怎么变,核心思想没变:定义一套标准接口,让AudioFlinger能统一调用

我们先看最核心的接口定义。在hardware/libhardware/include/hardware/audio.h中,定义了audio_hw_device_t这个结构体。它就是HAL设备的"身份证"。

struct audio_hw_device {
    struct hw_device_t common;
    
    // 核心操作函数指针
    int (*init_check)(struct audio_hw_device *dev);
    int (*set_voice_volume)(struct audio_hw_device *dev, float volume);
    int (*get_microphones)(struct audio_hw_device *dev, 
                           struct audio_microphone_characteristic_t *mic_array,
                           size_t *mic_count);
    
    // 创建音频流
    int (*open_output_stream)(struct audio_hw_device *dev,
                              audio_io_handle_t handle,
                              audio_devices_t devices,
                              audio_output_flags_t flags,
                              struct audio_config *config,
                              struct audio_stream_out **stream_out,
                              const char *address);
    
    int (*open_input_stream)(struct audio_hw_device *dev,
                             audio_io_handle_t handle,
                             audio_devices_t devices,
                             struct audio_config *config,
                             struct audio_stream_in **stream_in,
                             audio_input_flags_t flags,
                             const char *address,
                             audio_source_t source);
    
    // 关闭流
    void (*close_output_stream)(struct audio_hw_device *dev,
                                struct audio_stream_out *stream_out);
    void (*close_input_stream)(struct audio_hw_device *dev,
                               struct audio_stream_in *stream_in);
};

这里有个关键点:HAL接口是函数指针表。AudioFlinger拿到这个结构体后,直接调用里面的函数指针。这种设计的好处是——厂商可以自由实现内部逻辑,只要把函数指针填对就行。

核心接口分类

  • 设备级接口:init_check、set_voice_volume、set_master_volume等,控制整个音频设备
  • 流级接口:open_output_stream、open_input_stream,创建具体的音频流
  • 流操作接口:write、read、get_presentation_position等,在audio_stream_out/audio_stream_in结构体中定义

我个人习惯把HAL接口想象成"三明治结构"。上层是AudioFlinger,下层是硬件驱动,中间这层HAL就是那个肉饼。肉饼怎么做,全看厂商的配方。

8.2 HAL加载与调用流程

HAL是怎么被加载进来的?这个过程,我建议你记住一个关键函数:load_audio_interface()

它位于AudioFlinger.cpp中,大致流程是这样的:

  1. 获取HAL模块:通过hw_get_module()加载AUDIO_HARDWARE_MODULE_ID对应的so库
  2. 打开设备:调用audio_hw_device_open(),拿到audio_hw_device_t指针
  3. 初始化检查:调用init_check()确认硬件是否就绪
  4. 设置参数:配置采样率、通道数等初始参数

我曾经在调试一个项目时,发现设备启动后音频一直没声音。查了半天,原来是hw_get_module()加载的so库路径不对。Android 8.0之后,HAL库的路径从/system/lib/hw/移到了/vendor/lib/hw/,厂商没更新配置文件,导致加载失败。嗯,这种坑,踩过一次就记住了。

我们来看加载的核心代码片段:

// AudioFlinger.cpp 中加载HAL的简化逻辑
status_t AudioFlinger::load_audio_interface() {
    const hw_module_t *mod;
    int rc;
    
    // 1. 获取HAL模块
    rc = hw_get_module(AUDIO_HARDWARE_MODULE_ID, &mod);
    if (rc != 0) {
        ALOGE("Failed to get audio hardware module");
        return NO_INIT;
    }
    
    // 2. 打开设备
    audio_hw_device_t *dev;
    rc = audio_hw_device_open(mod, &dev);
    if (rc != 0) {
        ALOGE("Failed to open audio hardware device");
        return NO_INIT;
    }
    
    // 3. 初始化检查
    rc = dev->init_check(dev);
    if (rc != 0) {
        ALOGE("Audio hardware not ready");
        audio_hw_device_close(dev);
        return NO_INIT;
    }
    
    // 4. 保存设备指针
    mPrimaryHardwareDev = dev;
    return OK;
}

避坑指南:我曾经遇到过一个问题——init_check()返回了非零值,但硬件明明已经就绪了。后来发现是厂商的HAL实现里,init_check()检查了一个不存在的设备节点。所以,HAL的健壮性很大程度上取决于厂商的实现质量

8.3 HAL与AudioFlinger的交互

AudioFlinger和HAL的交互,本质上就是函数调用。但这里有个设计模式值得注意——回调机制

AudioFlinger不会主动去轮询HAL的状态。相反,HAL通过回调函数,把事件通知给AudioFlinger。比如:

  • 音频设备插拔:耳机插入时,HAL调用回调通知AudioFlinger
  • 音频焦点变化:某些HAL实现会通知上层音频焦点变化
  • 参数变化:比如采样率变化、音量变化等

这个回调机制,是通过audio_hw_device_t中的set_parametersget_parameters实现的。但更重要的,是create_audio_patchrelease_audio_patch这些新接口。

我画了一张图,帮你理解这个交互流程:

AudioFlinger与HAL交互流程 AudioFlinger 音频策略管理 Audio HAL 硬件抽象层 硬件驱动 open_output_stream() write() / read() 回调通知 ALSA / 私有接口 数据流方向 播放:AudioFlinger → HAL → 驱动 → 扬声器/耳机 录音:麦克风 → 驱动 → HAL → AudioFlinger 注:HAL层是AudioFlinger与硬件驱动之间的桥梁,负责格式转换和硬件控制

你看这张图,数据流是双向的。播放时,AudioFlinger把PCM数据往下推;录音时,数据从硬件往上送。HAL在中间做格式转换、音量控制、路由切换这些脏活累活。

8.4 常见HAL实现

不同的硬件平台,HAL实现差异很大。我接触过的几种常见实现:

实现类型 典型平台 特点
ALSA HAL 高通、MTK 基于Linux ALSA驱动,通过tinyalsa库操作
QCOM HAL 高通平台 深度定制,支持DSP卸载、低功耗音频
MTK HAL 联发科平台 私有接口多,与硬件耦合度高
USB HAL 通用 基于USB Audio Class,支持外接设备
模拟HAL 模拟器 基于文件或内存模拟,用于测试

ALSA HAL为例,它的核心实现思路是:

// 简化的ALSA HAL实现思路
static int alsa_open_output_stream(struct audio_hw_device *dev,
                                    audio_io_handle_t handle,
                                    audio_devices_t devices,
                                    audio_output_flags_t flags,
                                    struct audio_config *config,
                                    struct audio_stream_out **stream_out,
                                    const char *address) {
    struct alsa_stream_out *out;
    
    // 1. 分配流对象
    out = (struct alsa_stream_out *)calloc(1, sizeof(*out));
    if (!out) return -ENOMEM;
    
    // 2. 打开ALSA设备
    out->pcm = pcm_open(0, 0, PCM_OUT, &config->format);
    if (!out->pcm || !pcm_is_ready(out->pcm)) {
        free(out);
        return -EIO;
    }
    
    // 3. 设置函数指针
    out->stream.write = alsa_write;
    out->stream.get_presentation_position = alsa_get_presentation_position;
    
    *stream_out = &out->stream;
    return 0;
}

注意事项:不同平台的HAL实现,对音频路由的处理差异很大。高通平台有audio_route机制,通过Mixer Paths配置文件控制音频路径。而MTK平台则使用自己的AudioPolicyManager。如果你做跨平台移植,这部分是最容易出问题的。

我记得有一次,在MTK平台上调试录音功能。明明HAL的open_input_stream返回成功了,但读出来的数据全是静音。后来发现,MTK的HAL要求先设置input_source参数,才能正确路由到麦克风。这个参数在标准HAL接口里没有强制要求,但MTK的实现依赖它。嗯,这就是厂商定制的"坑"。

最后说一句,HAL层的调试,最常用的手段就是加日志。在audio_hw_device_t的每个函数入口加ALOGD,基本能定位90%的问题。剩下的10%,可能是硬件时序问题,那就得用示波器了。


公众号:蓝海资料掘金营,微信deep3321