Android HAL层工程案例 音频驱动适配 - 第1节
1. Android音频系统概述
Android音频系统采用经典的分层架构设计,从上到下依次为:应用层、框架层、HAL层和内核驱动层。每一层都封装了特定的职责,并通过稳定的接口与相邻层通信。
核心原则:上层不关心下层的硬件实现细节,下层向上层提供统一的抽象接口。这种分层设计使得音频硬件厂商可以独立于Android框架进行驱动开发。
1.1.1 分层架构总览
下表总结了Android音频系统各层的主要组件与职责:
| 层级 | 主要组件 | 职责描述 |
|---|---|---|
| 应用层 | MediaPlayer、AudioTrack、AudioRecord | 提供音频播放/录制的API接口,供App开发者调用 |
| 框架层 | AudioFlinger、AudioPolicyService、AudioSystem | 管理音频流、路由策略、混音处理、设备选择 |
| HAL层 | audio_hw_device、stream_in/out | 抽象硬件差异,提供标准化的设备操作接口 |
| 内核驱动层 | ALSA、ASoC、I2S控制器、DMA驱动 | 直接操作音频编解码器、DMA传输、寄存器配置 |
下面通过一张SVG架构图直观展示各层之间的调用关系:
关键理解:HAL层是Android音频系统的“分水岭”。框架层通过HAL接口调用硬件功能,而无需关心底层是ALSA、tinyalsa还是私有音频驱动。这种抽象使得同一套Android系统可以适配不同厂商的音频硬件。
1.2 HAL层在音频系统中的位置与作用
1.2.1 HAL层的定位
HAL层位于Android框架层与内核驱动层之间,是连接用户空间与内核空间的桥梁。在Android 8.0之后,HAL层逐步从传统的libhardware库迁移到Treble架构下的HIDL接口,实现了框架与HAL的分离编译和独立升级。
HAL层在音频系统中的核心作用包括:
- 硬件抽象:将不同厂商的音频编解码器、DSP、功放等硬件差异封装成统一的
audio_hw_device接口。 - 流管理:管理音频输入输出流的打开、关闭、参数配置、数据读写。
- 路由控制:根据音频策略选择正确的输出设备(扬声器、耳机、蓝牙等)和输入设备(主麦克风、耳机麦克风等)。
- 参数配置:设置采样率、位深、通道数、音量、增益等音频参数。
- 低功耗管理:配合内核驱动实现音频路径的电源管理,在不使用时关闭硬件时钟和电源。
1.2.2 HAL层与框架层的接口关系
下表展示了HAL层对外暴露的关键接口及其功能:
| 接口结构体 | 关键函数 | 功能说明 |
|---|---|---|
audio_hw_device |
open_input_stream、open_output_stream、set_parameters |
设备级操作:打开音频流、设置全局参数 |
stream_out |
write、standby、set_volume、get_presentation_position |
输出流操作:写入音频数据、进入待机、设置音量、获取播放位置 |
stream_in |
read、standby、set_gain、get_capture_position |
输入流操作:读取音频数据、进入待机、设置增益、获取录音位置 |
下面用SVG图展示HAL层在音频数据流中的具体位置:
常见误区:初学者容易将HAL层与内核驱动层混淆。HAL层运行在用户空间,通过文件操作(如
/dev/snd/pcmC0D0p)与内核驱动交互;而内核驱动运行在内核空间,直接操作硬件寄存器。HAL层不直接操作硬件,而是通过系统调用间接控制。
1.3 音频驱动适配的整体流程
音频驱动适配是指将一个新的音频硬件(如Codec芯片、DSP、功放)集成到Android系统中的过程。整体流程可以分为以下六个阶段:
1.3.1 适配流程总览
| 阶段 | 主要工作 | 输出产物 |
|---|---|---|
| 1. 硬件调研 | 阅读Codec Datasheet、确认接口类型(I2S/TDM/PDM)、确认电源时序 | 硬件连接图、寄存器配置表 |
| 2. 内核驱动开发 | 编写ALSA/ASoC驱动、配置DMA、实现Codec控制接口 | 内核模块(.ko)、设备树配置(.dts) |
| 3. HAL层实现 | 实现audio_hw_device、stream_out/stream_in接口 | audio.primary.$(platform).so |
| 4. 音频策略配置 | 配置audio_policy_configuration.xml、audio_effects.xml | 策略配置文件 |
| 5. 集成测试 | 播放/录制测试、延迟测试、多路并发测试、功耗测试 | 测试报告 |
| 6. 性能调优 | 调整buffer大小、优化DMA传输、降低延迟、消除pop噪声 | 优化后的驱动与配置 |
下面用SVG流程图展示适配的完整路径:
1.3.2 各阶段关键输出示例
在HAL层实现阶段,核心工作是实现audio_hw_device结构体中的函数指针。以下是一个简化的HAL层初始化代码片段:
// audio_hw.c - HAL层设备初始化示例
#include <hardware/audio.h>
static int adev_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 stream_out *out;
int ret;
out = (struct stream_out *)calloc(1, sizeof(struct stream_out));
if (!out)
return -ENOMEM;
// 初始化输出流
out->stream.common.set_parameters = out_set_parameters;
out->stream.write = out_write;
out->stream.standby = out_standby;
out->stream.set_volume = out_set_volume;
// 打开ALSA PCM设备
out->pcm = pcm_open(0, 0, PCM_OUT, &config->format);
if (!pcm_is_ready(out->pcm)) {
free(out);
return -EIO;
}
*stream_out = &out->stream;
return 0;
}
static int adev_open(const hw_module_t *module, const char *name,
hw_device_t **device)
{
struct audio_hw_device *adev;
adev = (struct audio_hw_device *)calloc(1, sizeof(*adev));
if (!adev)
return -ENOMEM;
adev->common.tag = HARDWARE_DEVICE_TAG;
adev->common.version = AUDIO_DEVICE_API_VERSION_2_0;
adev->common.module = (struct hw_module_t *)module;
adev->common.close = adev_close;
adev->open_output_stream = adev_open_output_stream;
adev->open_input_stream = adev_open_input_stream;
adev->set_parameters = adev_set_parameters;
adev->init_check = adev_init_check;
*device = &adev->common;
return 0;
}
实战提示:在HAL层实现中,
open_output_stream和open_input_stream是最核心的函数。你需要根据硬件能力正确配置audio_config中的采样率、通道数和格式。如果硬件不支持请求的参数,应返回-EINVAL并尝试协商最接近的配置。
1.3.3 适配过程中的常见问题
- 设备节点权限:HAL层通过
/dev/snd/pcmC*D*访问内核驱动,需要确保audio用户组有读写权限。 - 路由策略冲突:当同时插入耳机和USB音频设备时,AudioPolicyService可能选择错误的输出设备,需要在HAL层正确上报设备类型。
- 待机唤醒时序:音频流进入
standby后,再次播放时如果DMA未正确复位,可能导致无声音输出。 - 多实例并发:某些Codec不支持同时打开多个音频流,HAL层需要实现互斥逻辑或混音处理。