1.前言

1.1 音频焦点官方解读

两个或两个以上的 Android 应用可同时向同一输出流播放音频。系统会将所有音频流混合在一起。虽然这是一项出色的技术,但却会给用户带来很大的困扰。为了避免所有音乐应用同时播放,Android 引入了“音频焦点”的概念。 一次只能有一个应用获得音频焦点。

当您的应用需要输出音频时,它需要请求获得音频焦点,获得焦点后,就可以播放声音了。不过,在您获得音频焦点后,您可能无法将其一直持有到播放完成。其他应用可以请求焦点,从而占有您持有的音频焦点。如果发生这种情况,您的应用应暂停播放或降低音量,以便于用户听到新的音频源。

1.2 案例说明解读

1).如果手机上安装了两个音频播放器,当一个正在播放的时候,打开第二个播放歌曲,有没有发现第一个自动暂停了。

2).如果你在听音频的同时,又去打开了其它视频APP,你会发现音频APP暂停播放了。

3).如果你正在听音频或者看视频时,来电话了,那么音视频便会暂停。挂了电话后音乐又继续播放,视频则需要点击按钮播放,是不是很奇怪

4).当你收到消息,比如微信消息,并且有消息声音的时候,那么听音频的那一瞬间,音频的声音会变小了,然后过会儿又恢复了。是不是很有意思。

别蒙圈,这个就叫做音频捕获和丢弃焦点

1.3为什么要处理音频焦点问题

如果不处理捕获与丢弃音频焦点的话,那么同时开几个音视频播放器,就会出现多个声音;

这是一种不好的体验。如下图:

图1

图2

1.4  Vehicle为什么要混音呢?

Vehicle混音场景使用较频繁,常用的场景如下:

  • 媒体播放(音乐 + 收音 + 视频) + 导航场景
  • 媒体播放(音乐 + 收音 + 视频) + 语音提示

2. APP规范化

2.1 遵守音频焦点准则

行为恰当的音频应用应根据以下一般准则来管理音频焦点:

  • 在即将开始播放之前调用 requestAudioFocus(),并验证调用是否返回 AUDIOFOCUS_REQUEST_GRANTED。如果按照本指南中的说明设计应用,则应在媒体会话的 onPlay() 回调中调用requestAudioFocus()
  • 在其他应用获得音频焦点时,停止或暂停播放,或降低音量。
  • 播放停止后,放弃音频焦点。

运行的 Android 版本不同,音频焦点的处理方式也会不同:

  • 从Android 2.2(API 级别 8)开始,应用通过调用 requestAudioFocus()abandonAudioFocus()来管理音频焦点。应用还必须为这两个调用注册 AudioManager.OnAudioFocusChangeListener,以便接收回调并管理自己的音量。
  • 对于以 Android 5.0(API 级别 21)及更高版本为目标平台的应用,音频应用应使用 AudioAttributes来描述应用正在播放的音频类型。例如,播放语音的应用应指定 CONTENT_TYPE_SPEECH。
  • 面向 Android 8.0(API 级别 26)或更高版本的应用应使用 requestAudioFocus() 方法,该方法会接受 AudioFocusRequest 参数。AudioFocusRequest 包含有关应用的音频上下文和功能的信息。系统使用这些信息来自动管理音频焦点的得到和失去。

2.2 请求音频焦点

获取音频焦点的第一个步骤是先向系统发出申请焦点的消息。注意这只是发出请求,并非直接获取。为了申请到音频聚焦,您必须向系统描述好您的意图。介绍四个常见音频焦点类型:

  • AUDIOFOCUS_GAIN的使用场景:应用需要聚焦音频的时长会根据用户的使用时长改变,属于不确定期限。例如:多媒体播放或者播客等应用。
  • AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK的使用场景:应用只需短暂的音频聚焦,来播放一些提示类语音消息,或录制一段语音。例如:闹铃,导航等应用。
  • AUDIOFOCUS_GAIN_TRANSIENT的使用场景:应用只需短暂的音频聚焦,但包含了不同响应情况,例如:电话、QQ、微信等通话应用。
  • AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE 的使用场景:同样您的应用只是需要短暂的音频聚焦。未知时长,但不允许被其它应用截取音频焦点。例如:录音软件。

在音频焦点成功获取后,该方法会返回AUDIOFOCUS_REQUEST_GRANTED常量,否则,会返回AUDIOFOCUS_REQUEST_FAILED常量。

音频焦点处理逻辑:

  • 在service的oncreate方法中调用初始化方法
  • 在播放音频的时候开始请求捕获音频焦点
  • 在音频销毁的时候开始丢弃音频焦点

1). Android O之前版本,需要用到 AudioFocusRequest,只需实现 AudioManager.OnAudioFocusChangeListener 接口。代码如下:

AudioManager mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
int focusRequest = mAudioManager.requestAudioFocus(
..., // Need to implement listenerAudioManager.STREAM_MUSIC,AudioManager.AUDIOFOCUS_GAIN);
switch (focusRequest) {case AudioManager.AUDIOFOCUS_REQUEST_FAILED://请求焦点失败-->不允许播放case AudioManager.AUDIOFOCUS_REQUEST_GRANTED://请求焦点成功->开始播放
}

2). Android O以及更新的版本上 必须使用 builder 来实例化一个 AudioFocusRequest 类。(在 builder 中必须指明请求的音频焦点类型),接口代码如下:

audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE);playbackAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_GAME).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build();focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).setAudioAttributes(playbackAttributes).setAcceptsDelayedFocusGain(true).setOnAudioFocusChangeListener(afChangeListener, handler).build();mediaPlayer = new MediaPlayer();final Object focusLock = new Object();boolean playbackDelayed = false;boolean playbackNowAuthorized = false;// ...int res = audioManager.requestAudioFocus(focusRequest);synchronized(focusLock) {if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) {//请求焦点失败-->不允许播放} else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {//请求焦点成功->开始播放} else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {//延时焦点}}// ...

延时焦点-场景案例:

假如当用户在通话中打开游戏,他们想玩游戏,不想听到游戏声音。但是当他们通话结束的时候他们想听到游戏声音(通话应用暂时持有音频焦点)。如果您的应用支持延迟音频聚焦,会发生如下情况:

  • 当您的应用申请音频焦点的时候,会被拒绝并锁住,通话应用继续持有音频焦点,您的应用因此不播放音频。因为您的应用是游戏,可以正常继续操作,只是没有声音。
  • 当通话结束,您的应用会被授权延迟音频聚焦。这个授权是来自刚才申请音频聚焦被拒绝后锁住的那个请求,它只是被延迟一段时间后再授权给您。您可以像上文建议应对音频焦点得失的处理方式那样处理,在本例中,此时便可以开始恢复播放。 目前低于 Android O 的版本是不支持延迟音频聚焦这个功能的,所以本用例在其它版本下,应用并不会延迟获得音频焦点。

​​​​​​​2.3 响应音频焦点的状态改变

一旦获得音频聚焦,您的应用要马上做出响应,因为它的状态可能在任何时间发生改变(丢失或重新获取),您可以实现 OnAudioFocusChangeListener 的来响应状态改变。

以下代码展示了 OnAudioFocusChangeListener 接口的实现,它处理了与 Google Assistant 应用协同工作的时候,音频焦点的各种状态的变化。

状态说明:

  • AUDIOFOCUS_GAIN:重新获取音频焦点。
  • AUDIOFOCUS_LOSS:永久性失去音频焦点,则其他应用会播放音频。您的应用应立即暂停播放,清理资源;因为它不会收到 AUDIOFOCUS_GAIN 回调。
  • AUDIOFOCUS_ LOSS_TRANSIENT:暂时失去音频焦点,但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源,
  • AUDIOFOCUS_ LOSS_TRANSIENT _CAN_DUCK:暂时失去音频焦点,应用应该降低音量(如果您不依赖于自动降低音量),允许持续播放音频(以很小的声音),不需要完全停止播放。
//焦点状态改变
@Override
public void onAudioFocusChange(int focusChange) {int volume;switch (focusChange) {// 重新获得焦点//如果通话结束,恢复播放;获取音量并且恢复音量。这个情景应该经常遇到case AudioManager.AUDIOFOCUS_GAIN:if (!willPlay() && isPausedByFocusLossTransient) {// 通话结束,恢复播放mPlayService.playPause();}//获取音量volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);if (mVolumeWhenFocusLossTransientCanDuck > 0 && volume ==mVolumeWhenFocusLossTransientCanDuck / 2) {// 恢复音量mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,mVolumeWhenFocusLossTransientCanDuck, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);}isPausedByFocusLossTransient = false;mVolumeWhenFocusLossTransientCanDuck = 0;break;// 永久丢失焦点,如被其他播放器抢占,释放焦点// 必须停止所有的audio播放,清理资源case AudioManager.AUDIOFOCUS_LOSS:if (willPlay()) {forceStop();}break;// 短暂丢失焦点,比如来了电话或者微信视频音频聊天等等// 但是很快就会重新获得,在此状态应该暂停所有音频播放,但是不能清除资源case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:if (willPlay()) {forceStop();isPausedByFocusLossTransient = true;}break;// 瞬间丢失焦点,如通知,导航// 暂时失去 audio focus,但是允许持续播放音频(以很小的声音),不需要完全停止播放。case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:// 音量减小为一半volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);if (willPlay() && volume > 0) {mVolumeWhenFocusLossTransientCanDuck = volume;mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,mVolumeWhenFocusLossTransientCanDuck / 2,AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);}break;default:break;}

失去焦点的三种类型:

  • 失去短暂焦点 (AudioManager.AUDIOFOCUS_LOSS_TRANSIENT)
  • 失去永久焦点 (AudioManager.AUDIOFOCUS_LOSS)
  • Ducking (AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK)

场景:

  1. 当永久丢失焦点,比如同时打开播放器,则停止或者暂停播放,否则出现两个声音
  2. 当短暂丢失焦点,比如比如来了电话或者微信视频音频聊天等等,则暂停或者停止播放
  3. 当瞬间丢失焦点,比如手机来了通知/导航。前提是你的通知是震动或者声音时,会短暂地将媒体音量减小一半;当然你也可以减小三分之一。

重新获得焦点场景:

  • 当重新获得焦点的时候,如果通话结束,恢复播放;获取音量并且恢复音量。这个情景应该经常遇到。

​​​​​​​2.4 释放音频焦点

播放完音频,记得使用 AudioManager.abandonAudioFocus(...) 来释放掉音频焦点。在前面的步骤中,我们遇到了一个应用暂停播放应该释放音频焦点的情况,但是这个应用依旧保留了音频焦点。

// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);
  • 如果要释放的client是在栈顶,则释放之后,让下一个栈顶的client获得了音频焦点。
  • 如果要释放的client不是在栈顶,则只是移除这个记录,不需要更改当前音频焦点的占有情况。

​​​​​​​2.5 APP音频焦点规范化后的效果:如下图

3. 混音策略

3.1 系统软件混音

通过上述音频焦点AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK模式实现混音操作,或者在修改系统框架层,处理导航输出,压低媒体声音实现混音效果。

​​​​​​​3.2 硬件混音

参考框架图:

如上硬件框架,软件设计如下:

  • NAV + Media混音时候,FM声道Mute,同时压低Media音量。
  • NAV + FM混音时候,Media声道Mute,同时压低FM音量。

4.  异常情况处理

4.1 导航APP未按音频焦点规范,系统如何处理?//主要涉及系统软件混音

处理方案如下:

  • 软件混音:系统Framework层将第三方导航的声音绑定到STREAM_ALARM通道, 系统媒体声音绑定到STREAM_MUSIC通道,通过设置不同通道的音量来实现混音效果。
  • 如何获取当前使用的APP包名?

方法:ActivityThread.currentPackageName();//获取该Class在哪个package调用

//AudioAttributes 参考修改
Public AudioAttributes build(){...String mCurName = ActivityThread.currentPackageName();if(mCurName  != null  && mCurName .equals(“com.android.nav”)){//重置媒体ContentTypemContentType = AudioManager.STREAM_ALARM;//重置mUsage 类型mUsage = USAGE_ALARM;}...
}
  • 检测到是第三方导航请求音频焦点,那么通知媒体减低音量

​​​​​​​4.2 第三方媒体播放器未按音频焦点规范,系统如何处理?

  • 系统层强制切换焦点,将第三方APP焦点移除堆栈(或者清空堆栈),将新请求的焦 点移到栈顶,重新请求焦点。

​​​​​​​4.3 未经过SOC处理的收音机FM/AM音频焦点及混音如何处理?

  • 通过Native控制FM/AM的源切换
  • 通过硬件混音实现
  • 软件混音,通过Native控制driver压低FM/AM音量

音频焦点涉及相关代码路径:

frameworks/base/media/java/android/media/AudioFocusRequest.java

frameworks/base/media/java/android/media/AudioAttributes.java

frameworks/base/media/java/android/media/AudioManager.java

frameworks/base/media/java/android/media/AudioSystem.java

frameworks/base/media/java/android/media/AudioFocusInfo.java

frameworks/base/services/core/java/com/android/server/audio/AudioService.java

frameworks/base/services/core/java/com/android/server/audio/FocusRequest.java

frameworks/base/services/core/java/com/android/server/audio/MediaFocusControl.java

Android音频焦点及混音策略相关推荐

  1. java sound 混音_iOS音频编程之混音

    title: iOS音频编程之混音 date: 2017-04-19 tags: Audio Unit,AUGraph, Mixer,混音 博客地址 iOS音频编程之混音 需求:多个音频源混合后输出, ...

  2. Android音频焦点申请处理

    为了便于理解,我们以android的8.0以前的版本为例,8.0以后有一定改动,但是基本思路一样. 关于管理音频焦点(8.0以前和更高版本)的官方文档:https://developer.androi ...

  3. android html5播放器,android Html5播放器混音解决方案

    背景 当一个用户正在听音乐而另一个应用需要通知用户一些重要的事情时,用户可能由于音乐声音大而不能听的通知.从Android2.2开始,平台为应用提供了一个协商它们如何使用设备音频输出的途径,这个机制叫 ...

  4. android音频焦点Audio Focus

    为了便于理解,我们以android的8.0以前的版本为例,8.0以后有一定改动,但是基本思路一样. 关于管理音频焦点(8.0以前和更高版本)的官方文档:https://developer.androi ...

  5. Android音频焦点处理

    概要 Android系统允许多个应用同时播放音频,这种特性有利有弊.例如当我们正在听音乐的时候突然点开了一个视频,如果我们发现音乐的声音和视频的的声音混合了在一起,这显然让我们非常不爽.而如果我们在播 ...

  6. Android 音频焦点(Audio Focus)

    原址 CONTENTS 引子 音频焦点 一个简单的示例 注意: 引子 说 Audio Focus 前先说个很简单需求:来电时暂停正在播放的音乐,电话结束时恢复播放. 看到这个需求,第一反应肯定是:监听 ...

  7. Android音频焦点

    因为系统中可能会有多个应用程序会播放音频,所以需要考虑他们之间该如何交互,为了避免多个应用程序同时播放 音乐,Android 系统使用音频焦点来进行统一管理,即只有获得了音频焦点的应用程序才可以播放音 ...

  8. Android音频焦点AudioFocus使用

    Android开发中免不了需要播放视频,音频文件,但是手机上可能有其他的一些软件,在后台播放音频的时候,这个时候另外的软件也需要播放音频,这个时候就会出现俩个音频同时播放的问题,在Android2.2 ...

  9. Android 音频焦点处理

    刚开始的时候,认为在智能机中,每个 APP 都是各自管各自的,媒体播放也是这样子的:然而对比同类产品,发现同类产品可以放到播放自如,体验很好,通过对比研究,根源就在于音频焦点处理上. 一.引言 在功能 ...

最新文章

  1. 2021-2028年中国阻燃装饰行业市场需求与投资规划分析报告
  2. Python入门100题 | 第075题
  3. Oracle 审计文件
  4. 一步步教你搭建SSM整合+前提配置超详细版(IDEA版本)
  5. jsp中get请求与post请求编码统一问题(1.0)
  6. hive 把mysql语句执行_Hive SQL 语句的执行顺序
  7. 页面间传值的新思路--PreviousPage
  8. Redis 不安全临时文件漏洞
  9. 【转】微波射频工程师必读经典参考书
  10. 阿里矢量库(各种图标搜索功能)
  11. wso2_使用WSO2开发
  12. EPLAN电气设计实例入门教程pdf
  13. 「课程」微观经济学-北京大学光华管理学院
  14. CISSP考点拾遗——介质净化
  15. C语言 文件的打开方式
  16. 三层神经网络实现分类器
  17. 黑客在开源网站植入秘密后门、恶意软件通过非常规IP逃避检测|1月25日全球网络安全热点
  18. python爬虫跳过异常处理
  19. 生存类html5小游戏,紧张绝望!刺激爽爆!盘点最好玩的PC生存类游戏(中)
  20. ecmall 连接mysql服务器失败_ecmall ECMall的MySQL数据库调用

热门文章

  1. @CacheEvict
  2. Unity Shader 常规光照模型代码整理
  3. 简单的Java视频播放器
  4. 助记词(Mnemonics)生成种子,以及Public Key, Private key
  5. Linux系统结构以及各类命令的汇总
  6. 阿里云ACP认证 VPC专项练习
  7. TB-RK1808M0最新固件烧录和驱动更新
  8. trove mysql 镜像_OpenStack(Queens)制作 Trove 镜像
  9. 函数周期表丨筛选丨值丨CALCULATE
  10. 用HTML写一个简易的登录界面