13、音频焦点与并发:AudioFocus机制、多应用音频混合、低延时场景下的焦点处理
音频焦点,说白了就是系统里的一套“礼貌规则”。
你想想看,手机同时只能出一个声音。要是导航、音乐、游戏、通话一起抢喇叭,那不乱套了?Android 用 AudioFocus 来管这件事。谁拿到焦点,谁出声;丢了焦点的,要么暂停,要么降低音量。
但在低延时场景下,这套规则有时候会“好心办坏事”。我遇到过好几次,游戏里刚开麦,背景音乐突然被掐断,玩家直接骂街。嗯,今天我们就来把这套机制掰开揉碎,看看怎么在低延时需求下优雅地处理音频并发。
13.1 AudioFocus 机制:谁说了算?
AudioFocus 本质上是一个“令牌”。应用通过 requestAudioFocus() 去申请,系统根据优先级和当前焦点持有者来决定给不给。
焦点类型有几种:
- AudioManager.AUDIOFOCUS_GAIN:长期持有焦点,比如音乐播放器。
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:短暂持有,比如通知音、导航提示。
- AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:短暂持有,但允许其他应用降低音量继续播放(俗称“闪避”)。
焦点丢失的回调也很关键:
- onAudioFocusChange(AUDIOFOCUS_LOSS):永久丢失,通常需要暂停播放。
- onAudioFocusChange(AUDIOFOCUS_LOSS_TRANSIENT):短暂丢失,暂停后等恢复。
- onAudioFocusChange(AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK):短暂丢失,降低音量即可。
13.2 多应用音频混合:谁的声音更大?
Android 从 8.0 开始引入了 音频策略(AudioPolicy),系统可以同时让多个应用发声,但通过混音器(AudioFlinger)来调整每个流的音量、采样率、通道数。
低延时场景下,混音器可能会引入额外的延迟。为什么呢?因为混音需要把多个音频流的数据按时间对齐,然后叠加。如果某个流的采样率不一致,还得做重采样——这玩意儿很耗时间。
我建议你在多应用并发时,尽量统一采样率。比如所有音频流都用 48000Hz,避免重采样带来的延迟抖动。
| 场景 | 推荐采样率 | 延迟影响 |
|---|---|---|
| 音乐 + 导航 | 48000Hz | 低(无需重采样) |
| 游戏 + 语音 | 48000Hz | 低(建议使用 AAudio) |
| 通话 + 媒体 | 16000Hz / 48000Hz | 中(需要混音策略) |
SL_ANDROID_KEY_STREAM_TYPE,导致 AudioFlinger 把它当成了不同的流类型,混音策略出了问题。记住:流类型(STREAM_MUSIC、STREAM_VOICE_CALL 等)一定要明确指定。
13.3 低延时场景下的焦点处理:快进快出
低延时应用(比如实时语音、游戏音效)对焦点的要求很特殊:
- 不能因为焦点丢失而卡顿。
- 不能因为焦点申请而增加延迟。
- 不能因为焦点恢复而出现“啪”的一声(pop noise)。
我一般会这样做:
- 预申请焦点:在音频流启动前 100ms 就申请焦点,而不是等到播放那一刻。这样可以避免焦点申请阻塞音频线程。
- 使用 TRANSIENT_MAY_DUCK:对于游戏音效,尽量用这个类型。这样即使丢了焦点,也只是降低音量,不会暂停。玩家体验会好很多。
- 焦点丢失时静音,但不释放资源:低延时场景下,释放和重新初始化音频设备很耗时。我建议焦点丢失时只做静音处理,保留音频流和缓冲区。等焦点恢复时,直接取消静音,延迟可以控制在 1ms 以内。
// 焦点变化回调
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS:
// 永久丢失,暂停播放
pausePlayback();
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 短暂丢失,静音但不释放
setMute(true);
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 降低音量
setVolume(0.3f);
break;
case AudioManager.AUDIOFOCUS_GAIN:
// 恢复焦点,取消静音
setMute(false);
setVolume(1.0f);
break;
}
}
你可能会问:为什么不直接暂停?因为暂停再恢复,中间涉及音频设备重新打开、缓冲区重新填充,延迟可能高达 50ms 以上。对于实时语音来说,50ms 的延迟已经能被人耳感知到了。
13.4 知识体系:一张图看懂音频焦点与并发
下面这张 SVG 图,我把本章的核心逻辑画了出来。你可以看到焦点申请、混音、低延时处理这三条线是如何交织的。
13.5 实战建议:如何优雅地处理焦点?
最后,我总结几条实战经验:
- 不要在主线程申请焦点:
requestAudioFocus()虽然是异步的,但回调可能发生在主线程。如果你在音频线程里直接调用,可能会造成线程阻塞。我习惯在音频线程初始化时,提前通过 Handler 在主线程申请焦点。 - 使用 AudioFocusRequest.Builder:Android 8.0 之后推荐用这个 API,可以设置延迟播放、暂停时是否闪避等参数。比老版的
requestAudioFocus()更灵活。 - 测试多应用并发:别只在单应用环境下测试。打开音乐播放器、导航、游戏同时运行,看看焦点切换是否流畅。我曾经在测试时发现,某个应用在焦点丢失后没有及时静音,导致两个声音叠加,用户体验极差。
嗯,关于音频焦点与并发,今天就聊到这里。记住:焦点不是枷锁,而是工具。用好了,多应用共存也能很丝滑。
公众号:蓝海资料掘金营,微信deep3321