引言:最后一公里的旅程
如果把 Android 音频系统比作一条物流网络,那么 AudioFlinger 是"中央分拣中心",AudioPolicy 是"路由规划师",而 Audio HAL(Hardware Abstraction Layer)就是最终把包裹送到用户手里的"快递员"。
前几篇我们聊了 AudioFlinger 的混音魔法(第一篇)、AudioTrack 的数据传输(第二篇)、录音链路(第三篇)、音效系统(第四篇)、AudioPolicy 的路由决策(第五篇)、音频焦点(第六篇)和音量控制(第七篇)。今天我们来到整个系列的终点------Audio HAL 与硬件接口。
这是音频数据真正"触地"的地方:PCM 数据从 AudioFlinger 的内存缓冲区,经过 HAL 接口、TinyALSA,最终通过 DMA 搬运到音频编解码器,再经 I2S 总线送到扬声器振膜,化为你耳中听到的声音。
整个过程涉及三个关键问题:
- HAL 是什么:framework 与 vendor 驱动之间的标准接口契约
- HAL 怎么进化:从 Legacy .so 直接加载到 HIDL 跨进程调用,再到 AIDL 的现代化设计
- HAL 怎么工作:openOutput → write → pcm_write → DMA 的完整数据链路
让我们一层一层揭开这"最后一公里"的神秘面纱。
一、Audio HAL 的历史演进:三代接口的进化史
1.1 Legacy HAL(Android 2.3 ~ 7.x):简单粗暴的直接加载
在 Project Treble 之前,Audio HAL 是最"朴素"的实现方式------一个 .so 动态库,由 audioserver 进程用 dlopen() 直接加载。
bash
audioserver 进程
│
├── dlopen("audio.primary.default.so")
│ ↓
│ audio_hw.c / audio_hw.cpp
│ ↓
│ TinyALSA / ALSA ioctl
│ ↓
│ /dev/snd/pcmC0D0p
Legacy HAL 的接口定义在 hardware/libhardware/include/hardware/audio.h:
c
// hardware/libhardware/include/hardware/audio.h
struct audio_hw_device {
struct hw_device_t common;
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);
// ...
};
struct audio_stream_out {
struct audio_stream common;
ssize_t (*write)(struct audio_stream_out* stream,
const void* buffer, size_t bytes);
int (*get_render_position)(const struct audio_stream_out *stream,
uint32_t *dsp_frames);
// ...
};
这种方式的问题很明显:system 分区和 vendor 分区紧耦合。每次 Android 大版本升级,OEM 厂商都需要重新编译整个 HAL 库,导致碎片化严重,OTA 更新成本极高。这就是为什么 Google 在 Android 8 推出了 Project Treble。
1.2 HIDL HAL(Android 8.0 ~ 12.x):Treble 的第一步
Project Treble 的核心目标是把 Android framework(system 分区)与 vendor 实现(vendor 分区)彻底分离。Audio HAL 迁移到 HIDL(Hardware Interface Definition Language)接口:
less
audioserver (system 进程) vendor HAL 进程 (android.hardware.audio@X.Y-service)
│ │
│ HwBinder IPC │
│ ◄─────────────────────────────────► │
│ │
IDevicesFactory audio.primary.so (vendor 实现)
│ │
└── openDevice() └── TinyALSA
主要接口层次(以 HIDL 7.0 为例):
| 接口 | 职责 |
|---|---|
IDevicesFactory |
工厂,负责创建 IDevice 实例 |
IDevice |
代表一个音频硬件设备,管理 stream |
IStreamOut |
播放输出流,提供 write() 方法 |
IStreamIn |
录音输入流,提供 read() 方法 |
IStream |
IStreamOut / IStreamIn 的基接口 |
hidl
// hardware/interfaces/audio/7.0/IDevice.hal
interface IDevice {
openOutputStream(
AudioIoHandle ioHandle,
DeviceAddress device,
AudioConfig config,
bitfield<AudioOutputFlag> flags,
SourceMetadata sourceMetadata
) generates (
Result retval,
IStreamOut outStream,
AudioConfig suggestedConfig
);
openInputStream(
AudioIoHandle ioHandle,
DeviceAddress device,
AudioConfig config,
bitfield<AudioInputFlag> flags,
SinkMetadata sinkMetadata
) generates (
Result retval,
IStreamIn inStream,
AudioConfig suggestedConfig
);
};
HIDL 的 HwBinder 与普通 Binder 相比,针对大数据传输有优化,但本质上仍是跨进程调用,每次 write() 都要经历一次进程间通信,带来额外延迟。
1.3 AIDL HAL(Android 13+ / Android 15 默认):现代化重设计
Android 13 引入了基于 AIDL(Android Interface Definition Language)的新版 Audio HAL,Android 15 中所有 GRF(Google Reference Framework)设备必须支持。AIDL HAL 相比 HIDL 有以下优势:
- 统一的 Binder:使用标准 Binder 而非 HwBinder,工具链统一
- 更简洁的接口:移除了历史包袱,重新设计了 API
- Stable AIDL:接口稳定性由 ABI 保证,支持向前兼容
- 更好的性能:减少了不必要的数据拷贝
| 对比项 | Legacy | HIDL | AIDL |
|---|---|---|---|
| 接口语言 | C 头文件 | .hal |
.aidl |
| 通信方式 | dlopen 直接调用 | HwBinder IPC | Binder IPC |
| 进程隔离 | 无(同进程) | 有 | 有 |
| Android 版本 | ≤ 7.x | 8.0 ~ 12.x | 13+ |
| 稳定性保证 | 无 | HIDL ABI | Stable AIDL |
二、Treble 架构:system 与 vendor 的护城河
理解 Audio HAL 的现代实现,必须先理解 Treble 的架构边界。
bash
┌─────────────────────────────────────────────────────────┐
│ system 分区 │
│ │
│ audioserver (audioflinger + audiopolicy) │
│ frameworks/av/services/audioflinger/ │
│ │
│ ↕ Binder IPC │
├─────────────────────────────────────────────────────────┤
│ Stable AIDL 接口边界 │
│ android.hardware.audio.core.IModule │
│ android.hardware.audio.core.IStreamOut │
├─────────────────────────────────────────────────────────┤
│ vendor 分区 │
│ │
│ android.hardware.audio.service (vendor HAL 进程) │
│ vendor/qcom/audio/ 或 vendor/mediatek/audio/ │
│ │
│ audio.primary.so → TinyALSA → /dev/snd/ │
└─────────────────────────────────────────────────────────┘
关键特性:
audioserver运行在 system 进程中,属于 system 分区android.hardware.audio.service运行在独立的 vendor 进程中- 两者通过稳定的 AIDL 接口通信,接口变更受到严格版本控制
- vendor 进程崩溃不会影响 audioserver,增强了系统稳定性
2.1 HAL 服务注册与发现
HAL 服务通过 servicemanager 注册,audioserver 通过 AIDL 代理获取:
cpp
// frameworks/av/services/audioflinger/AudioFlinger.cpp
// AudioFlinger 初始化时获取 HAL 工厂
auto service = IServiceManager::getService("android.hardware.audio.core.IConfig/default");
// 或者对于 AIDL:
sp<IAudioHalService> halService;
auto status = AServiceManager_getService(
"android.hardware.audio.service", &halService);
vendor HAL 进程在启动时注册自身:
cpp
// vendor/xxx/audio/service/main.cpp
int main() {
ABinderProcess_setThreadPoolMaxThreadCount(16);
auto service = ndk::SharedRefBase::make<AudioHalService>();
auto binder = service->asBinder();
const std::string instance =
std::string(IConfig::descriptor) + "/default";
AServiceManager_addService(binder.get(), instance.c_str());
ABinderProcess_joinThreadPool();
return EXIT_FAILURE;
}
三、AIDL HAL 接口设计(Android 13+/15)
Android 15 的 Audio HAL AIDL 接口位于 hardware/interfaces/audio/aidl/,核心接口如下:
3.1 IModule:硬件模块抽象
IModule 是 AIDL HAL 的顶层接口,对应 HIDL 中的 IDevice,代表一个音频硬件模块:
aidl
// hardware/interfaces/audio/aidl/android/hardware/audio/core/IModule.aidl
@VintfStability
interface IModule {
// 打开输出流
IStreamOut openOutputStream(in OpenOutputStreamArguments args,
out OpenOutputStreamReturn ret);
// 打开输入流
IStreamIn openInputStream(in OpenInputStreamArguments args,
out OpenInputStreamReturn ret);
// 设置/获取音频端口配置
AudioPortConfig[] getAudioPortConfigs();
void setAudioPortConfig(in AudioPortConfig portConfig);
// 连接/断开外部设备(如耳机插拔)
void connectExternalDevice(in AudioPort templateIdAndAdditionalData);
void disconnectExternalDevice(int portId);
// 获取支持的音频格式
AudioPort[] getAudioPorts();
AudioRoute[] getAudioRoutes();
// Master 音量/静音
void setMasterVolume(float volume);
float getMasterVolume();
void setMasterMute(boolean mute);
boolean getMasterMute();
}
OpenOutputStreamArguments 封装了所有打开输出流所需的参数:
aidl
parcelable OpenOutputStreamArguments {
// 流的唯一标识,对应 AudioFlinger 的 io_handle
int portConfigId;
// 源元数据(描述将要播放的内容)
SourceMetadata sourceMetadata;
// 音频配置(采样率、格式、声道数)
@nullable AudioOffloadInfo offloadInfo;
// Buffer 大小(帧数)
long bufferSizeFrames;
// 输出标志
AudioOutputFlags[] flags;
}
3.2 IStreamOut:输出流接口
aidl
// hardware/interfaces/audio/aidl/android/hardware/audio/core/IStreamOut.aidl
@VintfStability
interface IStreamOut {
// 写入 PCM 数据(核心方法)
void write(in byte[] buffer);
// 获取当前播放位置(帧数)
long getPlaybackRateParameters();
// 暂停/恢复/冲刷/关闭
void pause();
void resume();
void flush();
void drain(AudioDrain type);
// Android 15 新增:精确的呈现位置
// 用于 A/V 同步
void updateOffloadPresentationPosition(in PresentationPosition position);
// 获取延迟(毫秒)
int getLatency();
// 音量控制(用于 direct/offload stream)
void setVolume(float left, float right);
}
3.3 IStreamIn:输入流接口
aidl
@VintfStability
interface IStreamIn {
// 读取 PCM 数据
void read(inout byte[] buffer);
// 获取输入增益(麦克风增益)
float getInputFramesLost();
// 获取捕获位置
MmapPosition getCapturePosition();
// Android 15 新增:多通道麦克风支持
MicrophoneInfo[] getActiveMicrophones();
}
3.4 Android 15 AIDL HAL 的新特性
Android 15(AIDL version 3)引入了几个值得关注的新特性:
1. Bluetooth A2DP/LE Audio HAL 统一
Android 15 将蓝牙音频 HAL 从独立的 IBluetoothAudioProvider 整合进标准 Audio HAL 框架:
aidl
// 新增 BluetoothA2dpCapabilities
parcelable BluetoothA2dpCapabilities {
BluetoothA2dpCodec[] supportedCodecs;
boolean supportsNonOffloaded;
boolean supportsOffloaded;
}
2. Spatial Audio HAL 支持
aidl
// IModule 新增空间音频接口
SpatializerCapabilities getSpatializerCapabilities();
void setSpatializerHeadTrackingMode(HeadTrackingMode mode);
3. Ultra-Low Latency (ULL) 路径优化
aidl
// 新增 MMAP 零拷贝模式标志
const int AUDIO_OUTPUT_FLAG_MMAP_NOIRQ = 0x2000;
四、HAL 调用流程:音频数据的传递之旅
下图展示了从 AudioFlinger 到硬件的完整调用链路:

4.1 openOutput:建立输出通道
当 AudioFlinger 需要打开一个新的播放通道时,会调用 HAL 的 openOutputStream():
cpp
// frameworks/av/services/audioflinger/AudioFlinger.cpp
// AudioFlinger::openOutput() 关键流程(简化)
status_t AudioFlinger::openOutput(
const media::OpenOutputRequest& request,
media::OpenOutputResponse* response)
{
// 1. 找到对应的 HAL 模块
sp<DeviceHalInterface> device = mAudioHWDevs[hwDevId]->hwDevice();
// 2. 设置音频配置
AudioConfig config;
config.sample_rate = halConfig.sampleRate;
config.channel_mask = halConfig.channelMask;
config.format = halConfig.format;
// 3. 调用 HAL 打开输出流(关键调用)
sp<StreamOutHalInterface> outStream;
status_t status = device->openOutputStream(
*output,
devices,
flags,
&config,
address.c_str(),
&outStream);
// 4. 创建 PlaybackThread,绑定 outStream
sp<PlaybackThread> thread = new MixerThread(this, outStream, *output, devices);
mPlaybackThreads.add(*output, thread);
return NO_ERROR;
}
在 AIDL HAL 的 vendor 实现中,openOutputStream() 会:
cpp
// vendor/qcom/audio/aidl/AudioDevice.cpp(高通示例,简化)
ndk::ScopedAStatus AudioDevice::openOutputStream(
const OpenOutputStreamArguments& args,
OpenOutputStreamReturn* ret) {
// 1. 解析音频参数
struct pcm_config config;
config.rate = args.portConfig.sampleRate.value;
config.channels = audioChannelCountFromOutMask(args.portConfig.channelMask);
config.format = tinyAlsaFormatFromAidl(args.portConfig.format.value);
config.period_size = args.bufferSizeFrames;
config.period_count = PLAYBACK_PERIOD_COUNT; // 通常为 4
// 2. 打开 ALSA PCM 设备
struct pcm* pcm = pcm_open(SOUND_CARD, PLAYBACK_DEVICE,
PCM_OUT, &config);
if (!pcm_is_ready(pcm)) {
LOG(ERROR) << "pcm_open failed: " << pcm_get_error(pcm);
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
// 3. 创建输出流对象并返回
auto stream = ndk::SharedRefBase::make<StreamOut>(pcm, config);
ret->stream = stream;
return ndk::ScopedAStatus::ok();
}
4.2 混音循环:AudioFlinger 的心跳
MixerThread::threadLoop() 是 AudioFlinger 的核心循环,以固定的时间间隔(通常 5~20ms)运行:
cpp
// frameworks/av/services/audioflinger/Threads.cpp (简化)
bool AudioFlinger::MixerThread::threadLoop() {
while (!exitPending()) {
// 1. 等待新数据或超时
waitForNextBuffer();
// 2. 混音:将所有活跃 Track 的 PCM 数据混合
mAudioMixer->process(pts);
// 3. 对混音结果做音效处理
processEffectChains_l(effectBuffer, frameCount);
// 4. 写入 HAL(关键调用)
ssize_t framesWritten = mOutput->write(
mMixBuffer,
mNormalFrameCount * mFrameSize);
// 5. 更新时间戳
updateTimestamp();
}
return false;
}
mOutput->write() 经由 AIDL Binder 调用到 vendor 进程的 StreamOut::write():
cpp
// vendor HAL: StreamOut::write() 实现
ndk::ScopedAStatus StreamOut::write(
const std::vector<uint8_t>& buffer,
int64_t* framesWritten) {
// 直接调用 TinyALSA 写入
int ret = pcm_write(mPcm,
buffer.data(),
buffer.size());
if (ret != 0) {
LOG(ERROR) << "pcm_write error: " << pcm_get_error(mPcm);
*framesWritten = 0;
return ndk::ScopedAStatus::fromExceptionCode(EX_IO);
}
*framesWritten = buffer.size() / mFrameSize;
return ndk::ScopedAStatus::ok();
}
4.3 时序分析:每一毫秒都很重要
对于音乐播放(44100Hz,stereo,16bit),典型的 timing 如下:
less
AudioTrack.write() [App 进程]
│ (Binder: ~0.1ms)
▼
AudioFlinger SharedBuffer 写入
│ (等待混音周期)
▼
MixerThread::threadLoop() [每 ~5ms 触发一次]
│ ├── 混音处理: ~0.5ms
│ ├── 音效处理: ~0.2ms
│ └── stream->write(): ~0.1ms (Binder 到 HAL)
│
▼
StreamOut::write() [vendor HAL 进程]
│
▼
pcm_write() → /dev/snd/pcmC0D0p [TinyALSA]
│ ALSA ring buffer 还有 3~4 个周期的缓冲
│
▼
DMA 引擎 [内核]
│ 自动搬运数据到 Audio Codec
│
▼
I2S 总线 → Audio Codec → 扬声器
典型延迟(低延迟模式):
- AudioTrack → AudioFlinger: ~20ms(默认双缓冲)
- AudioFlinger → HAL write: ~5ms(一个混音周期)
- HAL → 扬声器(DMA 缓冲): ~10ms
- 总端到端延迟:
3550ms(普通音乐播放)
对于游戏/专业音频,Android 提供了 MMAP 零拷贝模式,可将延迟降低到 ~10ms。
五、TinyALSA:内核音频接口
TinyALSA 是 Android 对标准 ALSA(Advanced Linux Sound Architecture)的轻量级封装,位于 external/tinyalsa/。
5.1 核心数据结构
c
// external/tinyalsa/include/tinyalsa/asoundlib.h
struct pcm_config {
unsigned int channels; // 声道数(1=mono, 2=stereo)
unsigned int rate; // 采样率(Hz,如 44100, 48000)
unsigned int period_size; // 每个 period 的帧数(如 960 帧 @ 48kHz = 20ms)
unsigned int period_count; // period 数量(ring buffer 大小 = period_size × period_count)
enum pcm_format format; // PCM_FORMAT_S16_LE 等
unsigned int start_threshold; // 触发 DMA 启动的阈值
unsigned int stop_threshold; // 触发停止的阈值
unsigned int silence_threshold;
int silence_size;
};
5.2 关键 API
c
// 打开 PCM 设备
// card: 声卡编号(通常为 0)
// device: 设备编号(通常为 0,即 pcmC0D0)
// flags: PCM_OUT(播放)或 PCM_IN(录音)
struct pcm *pcm_open(unsigned int card, unsigned int device,
unsigned int flags, const struct pcm_config *config);
// 检查是否就绪
int pcm_is_ready(const struct pcm *pcm);
// 写入 PCM 数据(播放)
// 阻塞调用,直到数据被写入 ring buffer
int pcm_write(struct pcm *pcm, const void *data, unsigned int count);
// 读取 PCM 数据(录音)
int pcm_read(struct pcm *pcm, void *data, unsigned int count);
// 获取 ring buffer 中已有数据量
unsigned int pcm_avail_update(struct pcm *pcm);
// 关闭 PCM 设备
int pcm_close(struct pcm *pcm);
5.3 ALSA Ring Buffer 原理
TinyALSA 与内核 ALSA 之间通过 mmap 的共享内存实现零拷贝:
arduino
AudioFlinger TinyALSA 内核 ALSA driver
│ │ │
│ pcm_write(buf, bytes) │ │
│ ─────────────────────────► │ │
│ │ mmap 写入 ring buffer │
│ │ ───────────────────────► │
│ │ │ DMA 搬运
│ │ │ ──────────────►
│ │ │ Audio Codec
│ │ │ ↓
│ │ │ I2S 总线
│ │ │ ↓
│ │ │ 扬声器
pcm_write() 的实际实现是通过 ioctl(SNDRV_PCM_IOCTL_WRITEI_FRAMES) 通知内核有新数据可以搬运,然后 DMA 引擎会自动(中断驱动)将 ring buffer 中的数据搬运到音频编解码器的寄存器/FIFO。
5.4 /dev/snd 节点
bash
# 查看声卡和设备节点
$ ls /dev/snd/
controlC0 # 控制设备(mixer controls)
pcmC0D0c # PCM 录音设备(Card 0, Device 0, Capture)
pcmC0D0p # PCM 播放设备(Card 0, Device 0, Playback)
pcmC0D1p # Deep Buffer 播放设备(Card 0, Device 1)
timer # ALSA timer 设备
# 查看声卡信息
$ cat /proc/asound/cards
0 [msmnile ]: msm-tavita-snd - msm-tavita-snd
msm-tavita-snd
# 查看 PCM 设备列表
$ cat /proc/asound/pcm
00-00: MultiMedia1 (*) : playback 1 : capture 1
00-01: MultiMedia2 (*) : playback 1
00-06: MultiMedia7 (*) : playback 1 : capture 1
六、Audio HAL 实现要点
下图展示了 Audio HAL 完整的架构层次:

6.1 vendor HAL 实现结构
一个典型的 vendor Audio HAL 实现(以类 QCOM 架构为例):
bash
vendor/qcom/opensource/audio-hal/
├── primary-hal/
│ ├── hal/
│ │ ├── AudioDevice.cpp # IModule 实现
│ │ ├── AudioStream.cpp # IStreamOut / IStreamIn 实现
│ │ └── AudioEffects.cpp # Effects HAL
│ ├── audio_extn/
│ │ ├── AudioExtn.cpp # 扩展功能
│ │ └── ...
│ └── configs/
│ ├── audio_policy_configuration.xml # 音频路由配置
│ └── mixer_paths.xml # ALSA mixer 路径配置
└── service/
└── main.cpp # HAL 进程入口
6.2 mixer_paths.xml:ALSA mixer 控制
mixer_paths.xml 是 vendor HAL 的核心配置文件,定义了不同音频路径(路由)对应的 ALSA mixer control 设置:
xml
<!-- vendor/qcom/audio/configs/msmnile/mixer_paths.xml 示例 -->
<mixer>
<!-- 定义不同的音频路径 -->
<path name="speaker">
<ctl name="RX_CDC_DMA_RX_0 Audio Mixer MultiMedia1" value="1"/>
<ctl name="RX INT0_1 MIX1 INP0" value="RX0"/>
<ctl name="RDAC_CLK_CTL" value="1"/>
<ctl name="WSA_SPK_PA_GAIN" value="6 dB"/>
</path>
<path name="headphones">
<ctl name="RX_CDC_DMA_RX_1 Audio Mixer MultiMedia1" value="1"/>
<ctl name="RX INT1_1 MIX1 INP0" value="RX1"/>
<ctl name="RX INT2_1 MIX1 INP0" value="RX2"/>
</path>
<path name="speaker-and-headphones">
<path name="speaker"/>
<path name="headphones"/>
</path>
<!-- 麦克风路径 -->
<path name="handset-mic">
<ctl name="TX_CDC_DMA_TX_3 Channels" value="One"/>
<ctl name="TX_DEC0 MUX" value="SWR_MIC"/>
<ctl name="ADC MUX0" value="DMIC"/>
<ctl name="DMIC MUX0" value="DMIC0"/>
</path>
</mixer>
vendor HAL 在路由切换时调用 mixer_ctl_set_value() 应用这些配置:
c
// 路由切换示例(简化)
void AudioDevice::setOutputDevices(audio_devices_t devices) {
struct mixer *mixer = mixer_open(SOUND_CARD);
if (devices & AUDIO_DEVICE_OUT_SPEAKER) {
applyMixerPath(mixer, "speaker");
} else if (devices & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
applyMixerPath(mixer, "headphones");
}
mixer_close(mixer);
}
6.3 典型 out_write 实现
c
// 完整的 out_write 实现(Legacy HAL 风格,也适用于 AIDL HAL 底层)
static ssize_t out_write(struct audio_stream_out *stream,
const void *buffer, size_t bytes)
{
struct stream_out *out = (struct stream_out *)stream;
struct audio_device *adev = out->dev;
int ret = 0;
pthread_mutex_lock(&out->lock);
// 1. 如果流未打开,先打开 PCM 设备
if (!out->pcm) {
out->pcm = pcm_open(adev->card_id,
out->pcm_device_id,
PCM_OUT,
&out->config);
if (!pcm_is_ready(out->pcm)) {
ALOGE("pcm_open failed: %s", pcm_get_error(out->pcm));
pcm_close(out->pcm);
out->pcm = NULL;
pthread_mutex_unlock(&out->lock);
return -ENODEV;
}
}
// 2. 写入数据
ret = pcm_write(out->pcm, buffer, bytes);
if (ret != 0) {
ALOGE("pcm_write error: %s", pcm_get_error(out->pcm));
// 尝试恢复
pcm_close(out->pcm);
out->pcm = NULL;
}
// 3. 更新写入帧数(用于时间戳计算)
out->written += bytes / audio_stream_out_frame_size(stream);
pthread_mutex_unlock(&out->lock);
return (ret == 0) ? (ssize_t)bytes : ret;
}
6.4 Hardware Effects / DSP Offload
现代 SoC(如高通 Snapdragon、联发科 Dimensity)通常集成了独立的 DSP 协处理器,可以卸载音效处理任务,降低 AP 功耗。
scss
AudioFlinger (AP 侧)
│
│ 写入原始 PCM 数据
▼
Audio HAL (AP 侧)
│
│ offload 到 DSP
▼
DSP 处理 (独立核心)
├── Equalizer(EQ 均衡器)
├── Reverb(混响)
├── DRC(动态范围控制)
└── 噪声消除 / 回声消除 (AEC)
│
▼
Audio Codec → 扬声器/耳机
AIDL HAL 中的 Effects 接口:
aidl
// hardware/interfaces/audio/aidl/android/hardware/audio/effect/IEffect.aidl
@VintfStability
interface IEffect {
void open(in Parameter.Common common,
in @nullable Parameter.Specific specific,
out OpenEffectReturn ret);
void close();
void command(in CommandId commandId);
Status process(MQDescriptor<PCM, SynchronizedReadWrite> inBuffer,
MQDescriptor<PCM, SynchronizedReadWrite> outBuffer);
}
七、调试技巧:当声音出问题时
7.1 dumpsys 诊断
bash
# 查看 AudioFlinger HAL 状态
$ adb shell dumpsys media.audio_flinger
# 输出示例(关键部分):
AudioHwDevice 0x7f8b2c0 primary:
hw_ptr framesRead:1234567890 timeNs:1736581515285000
mHALFrameCount:960
mSamplingRate:48000 mFormat:0x1 mChannelMask:0x3
Audio hardware state: NORMAL
Output stream is at:0x7f8b380
Output stream HAL latency: 22 ms
# 查看 ALSA 状态
$ adb shell cat /proc/asound/card0/pcm0p/sub0/status
state: RUNNING
owner_pid : 456 (audioserver)
trigger_time: 12345.678901234
tstamp : 12345.689012345
delay : 960 (frames)
avail : 7680
avail_max : 8640
7.2 tinyplay / tinycap 工具
bash
# 播放 WAV 文件(直接测试 HAL)
$ adb shell tinyplay /sdcard/test.wav -D 0 -d 0 -r 48000 -c 2
# 录音测试
$ adb shell tinycap /sdcard/capture.wav -D 0 -d 0 -r 48000 -c 2 -b 16
# 查看 ALSA mixer controls
$ adb shell tinymix --card 0
# 设置特定 mixer control
$ adb shell tinymix --card 0 "RX_CDC_DMA_RX_0 Audio Mixer MultiMedia1" 1
tinyplay 绕过整个 Android 音频框架,直接测试 HAL 底层 ,是排查 codec 驱动问题的利器。如果 tinyplay 能播放但 Android 应用不能,问题在 AudioFlinger 及以上层;如果 tinyplay 也失败,问题在 HAL/驱动层。
7.3 logcat 过滤
bash
# 过滤 AudioFlinger 相关日志
$ adb logcat -s AudioFlinger:V AudioHAL:V r_submix:E
# 关键日志关键字
# "openOutput" → HAL openOutputStream 调用
# "pcm_open" → TinyALSA 打开设备
# "pcm_write error" → 写入失败
# "underrun" → 播放缓冲区不足(可能导致爆音)
# "overrun" → 录音缓冲区溢出
7.4 常见问题排查
问题1:播放无声音
排查路径:
bash
检查 mixer_paths.xml 对应路径是否正确
→ tinymix 查看 mixer controls 是否已设置
→ 检查 pcm_open 是否成功
→ 检查 /dev/snd/pcmC0D0p 权限
问题2:爆音/杂音
通常是 underrun(欠载)导致:
bash
$ adb shell cat /proc/asound/card0/pcm0p/sub0/status | grep "avail\|delay"
delay : 240 # 缓冲中的帧数(应大于 period_size)
avail : 7440 # 可写入的帧数
# 如果 delay 持续接近 0,说明 AudioFlinger 写入太慢 → underrun
调优方案:增大 period_count 或减少 period_size,平衡延迟和稳定性:
c
// 低延迟配置(可能有更多 underrun 风险)
config.period_size = 240; // 5ms @ 48kHz
config.period_count = 2; // 2 × 5ms = 10ms 缓冲
// 稳定配置(更大缓冲,延迟更高)
config.period_size = 960; // 20ms @ 48kHz
config.period_count = 4; // 4 × 20ms = 80ms 缓冲
问题3:录音有回声
AEC(Acoustic Echo Cancellation)配置问题:
bash
# 检查是否启用了 AEC 音效
$ adb shell dumpsys media.audio_flinger | grep -i "echo\|aec"
# 检查麦克风 mixer 路径
$ adb shell tinymix | grep -i "mic\|ADC"
7.5 性能分析:Perfetto/Systrace
bash
# 使用 Systrace 捕获音频延迟
$ adb shell atrace --async_start -b 32768 audio hal binder
# 捕获 10 秒
$ sleep 10
$ adb shell atrace --async_stop > audio_trace.html
# 在 Perfetto UI 中分析:
# - MixerThread::threadLoop() 执行时间
# - write() 到 HAL 的 Binder 延迟
# - DMA 触发间隔
八、系列总结:八篇之旅的终点与新起点
至此,我们完成了 Android 15 音频子系统系列的全部 8 篇内容。让我们用一张"全景图"回顾整个旅程:
scss
用户视角 Android 音频栈 硬件视角
─────────────────────────────────────────────────────────────────────
[用户点击播放]
App 调用 MediaPlayer
或 AudioTrack.write() ← 第2篇:AudioTrack 与播放链路
│
▼ Binder IPC
AudioFlinger (audioserver)
├── MixerThread 混音 ← 第1篇:AudioFlinger 架构基础
├── EffectChain 音效 ← 第4篇:音效系统与 AudioEffect
└── 音量控制(Q4.12) ← 第7篇:音量控制系统
│
▼ Binder IPC
AudioPolicyService ← 第5篇:AudioPolicy 路由决策
├── Engine 路由决策
└── 设备选择(喇叭/耳机/BT)
│
▼(同进程通知)
AudioService (system_server)
├── 音量分级管理 ← 第7篇(续)
└── 音频焦点仲裁 ← 第6篇:音频焦点管理
│
▼ AIDL Binder IPC
vendor HAL 进程 ← 第8篇(本篇):Audio HAL
├── IModule / IStreamOut
├── TinyALSA pcm_write()
└── ALSA ring buffer
│
▼ DMA
Audio Codec (硬件)
│
▼ I2S 总线
扬声器/耳机 → 声音✓
[同时,麦克风拾音] ← 第3篇:音频录制系统
AudioRecord → AudioFlinger
八篇文章串起的核心知识点:
| 篇章 | 主题 | 核心知识 |
|---|---|---|
| 第1篇 | AudioFlinger 架构 | Binder IPC、线程模型、SharedBuffer |
| 第2篇 | AudioTrack 播放 | 双缓冲、同步机制、低延迟路径 |
| 第3篇 | 音频录制 | AudioRecord、MMAP 模式、AEC |
| 第4篇 | 音效系统 | AudioEffect、插入/辅助音效、链式处理 |
| 第5篇 | AudioPolicy | 路由决策、策略引擎、XML 配置 |
| 第6篇 | 音频焦点 | 焦点栈、GAIN 类型、自动闪避 |
| 第7篇 | 音量控制 | 三层架构、对数曲线、Q4.12 定点 |
| 第8篇 | Audio HAL | AIDL接口、TinyALSA、DMA链路 |
写给最后的你:音频系统是 Android 中少有的"全栈穿透"系统------从 Java API 到 Native C++,从 Binder IPC 到内核驱动,从软件混音到 DMA 硬件操作,每一层都有其独特的设计哲学和工程取舍。
当你下次听到从 Android 设备中流出的音乐时,不妨想一想:这段旋律走过了多少层代码、穿越了多少道 IPC 边界、经历了多少次格式转换,才最终化为空气中的振动,传入你的耳朵。
这就是 Android 音频系统的魅力------它把复杂的底层细节优雅地隐藏在一个简单的 audioTrack.play() 调用之后,而理解这些细节,正是成为优秀 Android 系统工程师的必经之路。
系列完结:如果这个系列对你有帮助,欢迎关注后续的 Android 视频子系统、Binder IPC 深度解析等系列文章。技术的旅程永无止境,我们下个系列见!