音频焦点使用及原理

news/2024/10/31 3:24:21/

音频焦点使用及原理

本博客代码基于Android 10源码



为什么会有音频焦点这一概念?

在Android音频领域中,应用层所有的App播放音频,最终都是走到音频回播线程PlaybackThread中,如果多个App都走到同一个PlaybackThread中去,就会出现混音情况,Android本身对混音也有很好的支持,但是也会造成某些重要音频资源播放时,用户听不太清晰,这个时候就引入音频焦点这一概念!

所谓的音频焦点,可以理解为一个播放权限的东西,App获得了音频焦点,你就可以播放你的音频内容,当你失去了音频焦点,你就得暂停、停止或降低你播放的音频;在Android 10上验证了,以上这些工作就是App自己要遵守、完成的工作,App自己监听音频焦点状态,得到不同的音频焦点状态,执行对应的操作。



Android音频焦点基本用法

在Android 10版本上音频焦点申请,主要分为三个步骤:

  1. 组装音频焦点申请请求
AudioAttributes.Builder attributes = new AudioAttributes.Builder();
attributes.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setUsage(AudioAttributes.USAGE_MEDIA);//App应用申请的音频焦点类型
AudioFocusRequest.Builder request = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN);
request.setAudioAttributes(attributes.build()).setOnAudioFocusChangeListener{public void onAudioFocusChange(int i) {//音频焦点状态回调}}

以上new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)这句,表明App申请焦点类型,有以下:

焦点类型焦点解释
AUDIOFOCUS_GAIN长时间占用音频焦点,如音频播放这种,失去焦点停止播放
AUDIOFOCUS_GAIN_TRANSIENT短时获取焦点,失去焦点暂停播放,比如语音、电话
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK短时获取焦点,失去焦点降低音量,如导航
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE短暂占有焦点,但希望失去焦点者不要有声音播放,比如电话
  1. 音频焦点申请
/** @gainFlag* AUDIOFOCUS_REQUEST_DELAYED = 2;* AUDIOFOCUS_REQUEST_FAILED = 0;* AUDIOFOCUS_REQUEST_GRANTED = 1;*/
int gainFlag = mAudioManager.requestAudioFocus(request.build());

如上请求后获取的返回值gainFlag取值1就是请求通过,可以播放音频了;0就是被拒绝了不允许播放,2就是延迟获取申请的结果

  1. 音频状态回调
    在第一个步骤中,设置的setOnAudioFocusChangeListener焦点回调,音频焦点就是通过这个回调返回的,如下代码:
public void onAudioFocusChange(int i) {Log.i(TAG, "onAudioFocusChange " + i);switch (i){//永久的失去音频焦点case AudioManager.AUDIOFOCUS_LOSS:Log.d(TAG, "AUDIOFOCUS_LOSS");mMediaPlayer.stop();mAudioManager.abandonAudioFocusRequest(request.build());break;//短暂失去焦点,并可能会恢复case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT");mMediaPlayer.pause();break;//短暂性丢失焦点并作降音处理case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:Log.d(TAG, "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK");mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 3, 0);break;//当其他应用申请焦点之后又释放焦点会触发此回调case AudioManager.AUDIOFOCUS_GAIN:Log.d(TAG, "AUDIOFOCUS_GAIN");if(isMediaPrepared) {mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 10, 0);mMediaPlayer.start();}break;}
}

具体如上代码,返回值如上,并有注释!



音频焦点原理framework分析

这里主要分析上节的2和3步骤,申请焦点与焦点回调两个过程,在framework中是如何运作的?

申请音频焦点

流程图如下:
在这里插入图片描述

如上图,请求过程相对简单,依次经历AudioManager、AudioService和MediaFocusControl三个类中,主要的工作是在红圈1处AudioManager和红圈3处MediaFocusControl中执行的;

AudioManager中做的事情

public int requestAudioFocus(@NonNull AudioFocusRequest afr, @Nullable AudioPolicy ap) {.....registerAudioFocusRequest(afr);final IAudioService service = getService();final int status;int sdk;try {sdk = getContext().getApplicationInfo().targetSdkVersion;} catch (NullPointerException e) { // some tests don't have a Contextsdk = Build.VERSION.SDK_INT;}final String clientId = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());final BlockingFocusResultReceiver focusReceiver;synchronized (mFocusRequestsLock) {try {// TODO status contains result and generation counter for ext policystatus = service.requestAudioFocus(afr.getAudioAttributes(),afr.getFocusGain(), mICallBack,mAudioFocusDispatcher,clientId,getContext().getOpPackageName() /* package name */, afr.getFlags(),ap != null ? ap.cb() : null,sdk);} catch (RemoteException e) { throw e.rethrowFromSystemServer();}if (status != AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY) {// default path with no external focus policyreturn status;}.....}
}

主要完成的三件事:

  1. 将此次的请求AudioFocusRequest注册进去,调用registerAudioFocusRequest,其内部就是将请求push到一个map结构中去
  2. getIdForAudioFocusListener从第一个步骤中map的对应key,也就是clientId
  3. 调用audioService的requestAudioFocus方法,并将重要参数如clientId和mAudioFocusDispatcher传递过去
    上述第一步的register方法如下:
public void registerAudioFocusRequest(@NonNull AudioFocusRequest afr) {final Handler h = afr.getOnAudioFocusChangeListenerHandler();final FocusRequestInfo fri = new FocusRequestInfo(afr, (h == null) ? null :new ServiceEventHandlerDelegate(h).getHandler());final String key = getIdForAudioFocusListener(afr.getOnAudioFocusChangeListener());//focus回调集合mAudioFocusIdListenerMap.put(key, fri);
}

mAudioFocusIdListenerMap也就是一个map集合
上述第三步的mAudioFocusDispatcher是啥?

 private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {@Overridepublic void dispatchAudioFocusChange(int focusChange, String id) {final FocusRequestInfo fri = findFocusRequestInfo(id);if (fri != null)  {final OnAudioFocusChangeListener listener =fri.mRequest.getOnAudioFocusChangeListener();if (listener != null) {final Handler h = (fri.mHandler == null) ?mServiceEventHandlerDelegate.getHandler() : fri.mHandler;final Message m = h.obtainMessage(MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/,id/*obj*/);h.sendMessage(m);}}}@Overridepublic void dispatchFocusResultFromExtPolicy(int requestResult, String clientId) {.......}};

实质就是一个aidl的远端回调接口,因为它要和AudioService测进行binder通信,那肯定得用aidl接口

MediaFocusControl中做的事情

protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,int flags, int sdk, boolean forceDuck) {synchronized(mAudioFocusLock) {//MAX_STACK_SIZE 100if (mFocusStack.size() > MAX_STACK_SIZE) {return AudioManager.AUDIOFOCUS_REQUEST_FAILED;}//为此次焦点申请在service端创建对应的实体类final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk);if (focusGrantDelayed) {// focusGrantDelayed being true implies we can't reassign focus right now// which implies the focus stack is not empty.final int requestResult = pushBelowLockedFocusOwners(nfr);if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);}return requestResult;} else {// propagate the focus change through the stackif (!mFocusStack.empty()) {propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);}// 加入到栈中mFocusStack.push(nfr);nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);}}......
}

主要完成的工作:

  1. 检测栈成员mFocusStack是否已经装满,装满就返回请求失败;
  2. 为此次客户端的焦点请求创建对应的实体类FocusRequester,其中它的参数clientId和cb,就是客户端App的信息和远端aidl回调
  3. 最后,在将FocusRequester压栈;
    这个mFocusStack很重要,是一个栈结构,先进后出,在栈顶的FocusRequester就会获得音频焦点

最后,经过上述的流程后,客户端AudioManager与服务端AudioService就建立好了返回的引用链路,也就是音频焦点回调链路,如下图:

在这里插入图片描述

音频焦点回调

虽然流程图是焦点回调,但还是包含了音频焦点申请部分,为什么呢?

因为实际上就是通过这种流程触发的,假如我们第一个音乐App申请焦点成功后,在播放音乐music,此时它的FocusRequestor位于FocusStack栈顶,此时若有电话接入,电话App应用会申请音频焦点,电话App会位于FocusStack的栈顶,而music的app在电话App的下面,就会触发对于音乐App失去焦点的回调,当然还有其他焦点触发变化的情况,此处解释就是上述流程图的红圈1处!

红圈2处的handleFocusXXX,所有的音频焦点获得gain、失去loss等,替换XXX字符串的方面名,然后通过aidl的回调接口IAudioFocusDispatcher回调到应用端App,应用端收到后根据焦点状态情况,对音频进行播放、暂停、降低音量等操作。

IAudioFocusDispatcher的回调方法dispatchAudioFocusChange去看看前面章节AudioMananger的aidl接口即可!


http://www.ppmy.cn/news/62961.html

相关文章

为文本检测和识别在OCR应用中的突破

OCR场景文本识别:文字检测文字识别 随着数字化时代的到来,文字识别技术得到了广泛的应用。OCR(Optical Character Recognition,光学字符识别)是一种技术,通过图像处理和计算机视觉来识别印刷或手写文字。在…

[JAVA EE ]创建Servlet——继承HttpServlet类笔记3

Response 一、响应行 组成:协议/版本 响应状态码 状态码描述响应状态码:服务器告诉客户端浏览器本次请求响应的一个状态,都是三位数 1xx:服务器接收客户端消息,但是没有接收完成,等待一段时间后&#xff…

《Vue.js 设计与实现》—— 02 框架设计核心要素

框架设计并非仅仅实现功能那么简单,里面有很多学问。例如: 框架应该给用户提供哪些构建产物?产物的模块格式如何? 当用户没有以预期的方式使用框架时,是否应该打印合适的警告信息从而提供更好的开发体验,让…

惠普暗影精灵5 super 873-068rcn如何重装系统

惠普暗影精灵5 super 873-068rcn是一款家用游戏台式电脑,有时候你可能用久会遇到系统出现故障、中毒、卡顿等问题,或者你想要更换一个新的操作系统,这时候你就需要重装系统。重装系统可以让你的电脑恢复到出厂状态,清除所有的个人…

AI仿写软件-仿写文章生成器

AI仿写软件:高效出色的营销利器 作为互联网时代的营销人员,我们不仅需要品牌意识,还必须深谙营销技巧。万恶的时限压力使得我们不得不在有限的时间内输出更多的文本内容,以便吸引更多的关注。那么,如何解决这个问题呢…

Sass使用

前言: 这份记录,主要是记录学习sass的学习记录,用于记录一些本人认为可能以后会用到的比较常用的一些知识点,更详细的请看sass官网 功能1-嵌套规则 Sass 允许将一套 CSS 样式嵌套进另一套样式中,内层的样式将它外层的…

Python的HTTP库及示例

13.3 HTTP库 HTTP(Hyper Text Transfer Protocol)是一个客户端和服务器端请求和应答的标准。客户端是终端用户,服务器端是网站。客户端发起一个到服务器上指定端口的HTTP请求,服务器向客户端发回一个状态行和响应的消息。 可以…

华为OD机试 - 第一个错误的版本(Java)

一、题目描述 你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。 假设你有 n 个版本 [1, 2, …, n]&#xff…