> 适用平台:Android Automotive OS (AAOS)
> 适用版本:Android 14 / 15 / 16
> 注:Android 17 源码尚未公开,暂不涉及
一、AudioTrack 在车载音频架构中的位置
1.1 整体架构
车载 Android 的音频路径比普通 Android 复杂得多,新增了 CarAudioService 作为中间层:
┌─────────────────────────────────────────────────────────────┐
│ 应用层 (App) │
│ AudioTrack.write() / AAudio │
└────────────────────────────┬────────────────────────────────┘
│ JNI
┌────────────────────────────▼────────────────────────────────┐
│ Native AudioTrack (C++) │
│ frameworks/av/media/libmedia/ │
└────────────────────────────┬────────────────────────────────┘
│ Binder IPC
┌────────────────────────────▼────────────────────────────────┐
│ CarAudioService │
│ (CarAudioManager / AudioZone / AudioFocus) │
│ frameworks/av/services/car/ │
└────────────────────────────┬────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────┐
│ AudioFlinger │
│ (PlaybackThread / MixerThread) │
└────────────────────────────┬────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────┐
│ CarAudioHAL │
│ (车载功放 / DSP / 多区域路由管理) │
└────────────────────────────┬────────────────────────────────┘
│
车载硬件(扬声器 / 功放 / DSP)
关键区别:
- 普通 Android:App → AudioFlinger → HAL → 硬件
- 车载 Android:App → CarAudioService → AudioFlinger → CarAudioHAL → 硬件
1.2 共享内存机制(与普通 Android 相同)
AudioTrack 与 AudioFlinger 之间通过 mmap 共享内存传输数据,不拷贝:
// audio_track_cblk_t — 核心控制结构
struct audio_track_cblk_t {
volatile uint32_t server; // AudioFlinger 已消费到的帧位置
volatile uint32_t user; // App 已写入的帧位置
uint32_t bufferEnd; // 缓冲区末尾帧索引
void* buffers; // 共享内存数据区
};
App 写数据时更新 user,AudioFlinger 消费时更新 server。user - server 即为缓冲区中尚未被消费的数据量。
二、车载多区域音频(Audio Zone)
2.1 为什么需要分区
车是一个多人共处的空间,不同座位的乘客需要独立的音频控制:
├── 驾驶座区域(Driver Zone, Zone 0)
│ ├── 扬声器:通常位于前门 + 仪表台中央
│ ├── 典型用途:导航播报、仪表盘提示音
│ └── 特点:安全相关优先级最高
│
├── 副驾区域(Front Passenger Zone, Zone 1)
│ ├── 扬声器:前门
│ └── 典型用途:副驾娱乐
│
├── 后排左侧 / 右侧(rear left / right)
│ └── 各有独立音量控制
│
└── 中央通道(Center Channel)
└── 导航语音助手、ADAS 警示音专属
2.2 声音类型与优先级
车机上同时存在多个音频流竞争播放,AudioFlinger 的 Mixer 按以下优先级混合:
| 优先级 | 声音类型 | 说明 |
|——–|———|——|
| **最高** | ADAS 警示音(碰撞预警、车道偏离) | 必须立即抢占,FLAG_AUDIBILITY_ENFORCED |
| **高** | 导航播报 | 次高优先级 |
| **高** | 免提电话(下行) | 通话时压制媒体 |
| **中** | 语音助手 / 唤醒词 | 需要持续监听 |
| **低** | 媒体音乐 | 可被高优先级声音打断 |
// 正确的车载 AudioTrack 创建方式
AudioAttributes attrs = new AudioAttributes.Builder()
.setUsage(AudioUsage.Voice) // 语音类用途
.setSource(AudioSource.VoiceCall) // 来源是通话
.setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) // 安全警示音必加
.build();
AudioTrack track = new AudioTrack.Builder()
.setAudioAttributes(attrs)
.setAudioZoneId(0) // 绑定到驾驶区
.setBufferSizeInBytes(8192)
.build();
三、缓冲区管理与 Underrun / Overrun
3.1 什么是 Underrun(欠载)
Underrun 发生在 AudioFlinger 消费数据的速度大于 App 生产数据的速度时:
时间轴 ──────────────────────────────────────────►
时刻A: [##############........] 75% 满,正常播放
时刻B: [--------------------] 0% 空,AudioFlinger 无数据 → 静音
↑
这里产生静音
恢复瞬间 → Pop 音
根本原因:
1. App 层 write() 太慢(主线程被 GC、CPU 抢占等)
2. 缓冲区设置太小
3. AudioFlinger 的 PlaybackThread 被高优先级线程抢占
4. HAL 层缓冲过大,系统整体”生产-消费”节奏被打乱
3.2 什么是 Overrun(过载)
Overrun 与 underrun 相反,App 写入速度过快,write() 长时间阻塞在缓冲区满的状态:
adb logcat | grep "AudioTrack"
# 看到 "write() blocked for > 50ms" 这类 log,说明正在 Overrun
3.3 Pop 音(爆破音)的来源
| 来源 | 触发场景 | 根因 |
|——|———|——|
| **Underrun 恢复** | 缓冲区清空后第一次恢复播放 | 共享内存未初始化 / DAC 没有 fade-in |
| **HAL 第一帧** | 每次播放线程启动时 | HAL 层驱动没有在第一帧做清零 |
| **打断-恢复** | 导航播报打断 → 恢复媒体 | 恢复瞬间缓冲区状态与停止前不一致 |
| **启停(Start-Stop)** | 发动机启停系统熄火/重启时 | DAC 电源序列不对,I2S 重新初始化 |
| **采样率切换** | 从 44.1kHz 切换到 48kHz | 重采样器初始阶段输出突变 |
Android 16 对启停场景做了修复,要求 HAL 实现必须做 fade-in/fade-out,避免电源波动导致的 Pop。
3.4 缓冲区大小参考
以 48kHz 双声道 16bit PCM 为例:
| bufferSizeInBytes | 帧数 | 缓冲时长 | 适用场景 |
|——————|——|———|———|
| 2048 | 512 帧 | ~10ms | 超低延迟,但 underrun 风险高 |
| 4096 | 1024 帧 | ~21ms | 低延迟场景 |
| 8192 | 2048 帧 | ~42ms | 平衡之选,适合大多数场景 |
| 16384 | 4096 帧 | ~85ms | 高稳定性,延迟不敏感场景 |
四、车载音频延迟分析
4.1 端到端延迟构成
总延迟 = T_app + T_buffer + T_mixer + T_hal + T_dac
| 阶段 | 说明 | 典型延迟 |
|——|——|———|
| **T_app** | App 的 write() 调用返回时间 | 1-5ms |
| **T_buffer** | 数据在 AudioTrack 缓冲区和 Mixer 缓冲区的排队时间 | 变量,10-100ms |
| **T_mixer** | PlaybackThread 混音/重采样 | 5-20ms |
| T_hal | CarAudioHAL 内部缓冲 | 5-30ms(取决于厂商实现)|
| **T_dac** | I2S/PCM 到扬声器 | 1-5ms |
T_buffer 是最大的不确定因素,也是车载音频延迟的主要瓶颈。
4.2 车载场景的延迟要求
| 场景 | 可接受延迟 | 说明 |
|——|———–|——|
| **普通音乐播放** | 50-150ms | 体验不受影响 |
| **导航播报** | < 100ms | 再高会有明显滞后感 |
| **卡拉OK / 实时监听** | < 30ms | 人耳能感知 20ms 以上的滞后 |
| **ADAS 警示音** | < 50ms | 安全相关,但不需要极低延迟 |
4.3 降低延迟的方法
#### 方法一:使用低延迟标志
AudioAttributes attrs = new AudioAttributes.Builder()
.setUsage(AudioUsage.Game) // 或 Greeting / Sonification
.setFlags(AudioAttributes.FLAG_LOW_LATENCY)
.build();
FLAG_LOW_LATENCY 会让 CarAudioService 请求 AudioFlinger 使用更小的缓冲区。但实际效果取决于 HAL 厂商实现,很多车载 HAL 内部缓冲区无法进一步缩小。
#### 方法二:绕过 Mixer(直连 HAL,Android 15+)
Android 15 引入了直连 HAL 路径,绕过 MixerThread,减少调度开销:
// 请求独占输出路径
track.setPreferredAudioClientUid(Process.SYSTEM_UID);
#### 方法三:使用 AAudio
AAudio 是比 AudioTrack 更轻量的 API,设计目标就是低延迟:
AAudioStreamBuilder builder;
AAudioStream *stream;
AAudioStreamBuilder_setPerformanceMode(builder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_openStream(builder, &stream);
#### 方法四:硬件选型
| 方案 | 延迟 | 说明 |
|——|——|——|
| 蓝牙 A2DP | 100-200ms | **不适合卡拉OK / 实时监听** |
| 3.5mm 模拟输出 | 中等 | 内置 Codec 通常有额外缓冲 |
| USB 声卡 | 低 | 推荐用于卡拉OK等实时场景 |
| 车载 DSP 直解 | 极低 | 代价是架构复杂,需厂商配合 |
五、车载特有的问题场景
5.1 导航播报打断媒体 → Pop 音
场景:车主正在听音乐,导航说”前方500米右转”——导航播报抢占焦点后,媒体恢复播放时出现 Pop 音。
时序分析:
T1: 导航 AudioTrack 抢占焦点 → 媒体 track 被强制 pause()
T2: 导航播报结束 → 媒体 track 恢复 play()
T3: 缓冲区残留 T1 时刻的旧数据 + HAL 可能已重新初始化
↓
Pop 音出现
调试方法:
adb shell dumpsys media.audio_flinger | grep -A 20 "Media"
# 看 underrun 计数是否在导航打断后飙升
adb logcat | grep -E "CarAudioService|AudioFocus|Navigation"
5.2 免提通话与媒体混音问题
场景:通话时音乐应该暂停或降混(ducking),但某些 HAL 实现不正确,导致:
- 对方听到你车里的音乐声
- 通话结束后媒体恢复时出现 Pop
根因:Audio Focus 机制和 HAL 的路由策略没有正确联动。
5.3 启停(Start-Stop)造成的 Pop
场景:等红灯时发动机启停系统熄火,然后重新启动——音频系统随之重启,DAC 的 I2S 总线重新初始化,产生 Pop。
修复情况:
- Android 16 已改进了 Suspend/Resume 的保护机制
- 要求 HAL 实现必须做 fade-in/fade-out
- 如果仍有问题,大概率是 HAL 驱动层没有按规范实现
5.4 多区域音量控制失效
场景:司机调大导航音量,后排乘客的音乐音量也跟着变,或者反之。
根因:App 的 AudioTrack 没有正确绑定到对应的 AudioZone,导致音量被错误地路由到了全局控制链路上。
正确做法:
// 每个区域的 App 应该绑定到对应 Zone
audioZone = CarAudioManager.getAudioZone(CarAudioManager.ZONE_ID_DRIVER);
track = audioZone.createTrack(audioAttributes, config, bufferSize, ...);
六、调试方法
6.1 确认问题边界
第一步:确认是系统问题还是 App 问题
↓
用系统自带应用测试(音乐播放器 / 导航)
系统自带也有问题 → 系统层问题(HAL / CarAudioService)
只有我的 App 有问题 → App 层问题
6.2 CarAudioService 和 AudioFlinger Dump
# 查看车载音频服务全状态(最重要)
adb shell dumpsys car_audio_service
# 查看 AudioFlinger(PlaybackThread / underrun 计数)
adb shell dumpsys media.audio_flinger
# 输出关键字段解读:
# Underruns: N → N > 0 = 发生过 underrun
# Buffer size: X → 缓冲区总大小
# Latency: Y ms → 端到端延迟
# Sample rate: Z → 当前采样率
6.3 Logcat 过滤
# 过滤 AudioTrack 相关
adb logcat | grep -E "AudioTrack|AudioFlinger|PlaybackThread"
# 过滤 underrun / buffer 相关
adb logcat | grep -i "underrun|buffer|pop"
# 过滤 CarAudioService(车载专用)
adb logcat | grep -E "CarAudioService|AudioZone|AudioFocus|CarAudio"
# 过滤 HAL 层(确认问题在哪一层)
adb logcat | grep -E "audio_hw|StreamOut|output_stream_write"
6.4 代码层加 dump
#### 在 App 的 write() 前后加时间戳:
long t0 = System.nanoTime();
int written = track.write(buffer, buffer.length);
long t1 = System.nanoTime();
Log.d("AudioDebug", "write " + written + " bytes, cost=" + (t1-t0)/1_000_000 + "ms");
// 如果 write() 返回耗时 > 20ms,说明 App 层生产速度是瓶颈
#### 获取缓冲区消费进度(Android 14+):
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
AudioTrack.Tracker tracker = track.getTracker();
// 通过 tracker 监控缓冲区填满程度
}
6.5 HAL 层 PCM dump(定位问题在哪一层)
需要 root 权限,在 HAL 层加 dump(临时修改):
// 修改 hardware/interfaces/audio/core/all-versions/default/StreamOut.cpp
// 在 write() 函数中:
FILE* fp = fopen("/data/vendor/audio_hal_dump.pcm", "ab");
fwrite(buffer, 1, size, fp);
fclose(fp);
判断逻辑:
- HAL dump 数据正确 + App dump 数据正确 → 问题在驱动 / 硬件
- HAL dump 全是 0 或错误 → 问题在 AudioFlinger 或更上层
- App dump 本身错误 → App 代码的问题
6.6 systrace 分析
python systrace.py -a com.example.yourapp \
-o trace.html \
sched freq idle am wm audio binder_driver
# 在 chrome://tracing 打开 trace 文件
# 关注:
# - AudioTrack write() 行是否有长时间空白(underrun)
# - PlaybackThread 是否频繁被高优先级线程抢占
# - 主线程是否在 write() 上长时间阻塞(CPU 竞争)
6.7 完整调试清单
① 用系统 App 测试,确认是系统还是 App 问题
② adb shell dumpsys car_audio_service
→ 确认 Zone 配置是否正确
→ 确认 Audio Focus 持有者
③ adb shell dumpsys media.audio_flinger
→ 找 Underruns 计数 > 0 的 Track
→ 找 Latency 过大的 Track
④ adb logcat 过滤
→ 搜 underrun / pop / AudioTrack 相关 tag
⑤ 确认 bufferSizeInBytes
→ < 4096 容易 underrun,建议 ≥ 4096
⑥ 确认采样率匹配
→ track.getSampleRate() vs 音频源采样率
→ 不一致时看 log 有无重采样警告
⑦ 抓 HAL dump
→ 对比 Framework 层和 HAL 层的数据是否一致
⑧ systrace
→ 分析线程调度是否抢占了 AudioFlinger
⑨ 测试启停场景
→ 发动机启停后音频是否正常恢复,有无 Pop
⑩ 多 Zone 测试
→ 确认音量控制是否在正确的 Zone 生效
七、总结
| 维度 | 说明 |
|------|------|
| **架构** | 车载比普通 Android 多 CarAudioService 中间层,负责多 Zone 和 Audio Focus |
| **延迟** | 正常场景 50-150ms 可接受;卡拉OK < 30ms 需要 AAudio + FLAG_LOW_LATENCY + USB 声卡 |
| **Pop 音根因** | 最常见的是 underrun 恢复 + HAL 层没有 fade-in,其次是启停场景 HAL 重初始化 |
| **多 Zone** | App 必须通过 `setAudioZoneId()` 绑定到正确 Zone,否则音量控制失效 |
| **调试入口** | `dumpsys car_audio_service` + `dumpsys media.audio_flinger` + HAL 层 dump 配合使用 |
如果遇到具体问题,可以按以下格式描述:
1. 出现的具体场景(播放音乐 / 导航播报 / 免提通话 / 启停后)
2. 具体表现(Pop 音 / 无声 / 延迟大 / 某个 Zone 无声)
3. 所有 App 都有问题还是只有你的 App 有问题
4. Android 版本(14 / 15 / 16)
5. 能否提供 adb shell dumpsys car_audio_service 和 adb shell dumpsys media.audio_flinger 的完整输出