当前位置: 首页 > news >正文

音频焦点使用及原理

音频焦点使用及原理

本博客代码基于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接口即可!

相关文章:

音频焦点使用及原理

音频焦点使用及原理 本博客代码基于Android 10源码 为什么会有音频焦点这一概念? 在Android音频领域中,应用层所有的App播放音频,最终都是走到音频回播线程PlaybackThread中,如果多个App都走到同一个PlaybackThread中去&#xff0…...

PyQt5桌面应用开发(8):从QInputDialog转进到函数参数传递

本文目录 PyQt5桌面应用系列How old are you, Dialog?QInputDialog minimalistwhy not lambdaand how partial worksSummary PyQt5桌面应用系列 PyQt5桌面应用开发(1):需求分析 PyQt5桌面应用开发(2):事件…...

2.0 Vue框架设计的核心要素

本章主要讲解,一个好的框架在构建的时候,需要考虑到的要素,包含报错信息反馈、警告信息反馈、减少打包体积、良好的输出、特性开关(兼容)等 1、提升用户开发体验 提升用户开发体验主要体现在用户使用框架进行开发时&…...

“智慧赋能 强链塑链”——精细化工行业仓储物流数字化转型探讨

精细化工行业作为衡量国家化学工业水平高低的重要标志,为国民经济提供重要的终端产品支持,相比较大化工产品,精细化工产品需要高度专业技能和工艺,其生产过程需要复杂的化学反应,以及严格的控制条件,产出的…...

用DG备库做的rman备份恢复一个数据库

环境描述: 1.因为主库存储空间不足,于是将备份放在dg备库上做。 2.主库因为磁盘空间问题,数据文件有两个目录。 3.dg备库因为主库两个数据文件目录里面有两个同名数据文件,所有dg备库也有两个数据文件目录。 4.主库与备库与测…...

JAVA中的IO操作有哪些?

在Java编程语言中,输入/输出(IO)操作是很重要的部分,它允许程序从外部系统读取数据,或将数据输出到外部系统。Java提供了一组强大的IO类库,可以让开发人员方便地进行各种IO操作。 Java中的IO操作可以分为两…...

10:00面试,10:04就出来了 ,问的实在是太...

从外包出来,没想到竟然死在了另一家厂子 自从加入这家公司,每天都在加班,钱倒是给的不少,所以我也就忍了。没想到12月一纸通知,所有人都不许加班,薪资直降30%,顿时有吃不起饭的赶脚。 好在有个…...

wangzherongyao PMO

感谢【五一节】大家的相遇,总结下。 2023年05月02日,【第一组】组队开黑 我总结了下这天为什么打的那么好,首先赛季初段位在王者附近,大家心态重视程度也高,不轻敌,也不盲目,运营好兵线一步一步…...

Dart语法上

一、Dart介绍及环境 1.1 Dart介绍: Dart是由谷歌开发的计算机编程语言,它可以被用于web、服务器、移动应用 和物联网等领域的开发。Dart诞生于2011年,号称要取代JavaScript。但是过去的几年中一直不温不火。直到Flutter的出现现在被人们重新重视。 要学…...

SignOff Criteria——POCV(Parametric OCV) introduction

文章目录 1. O v e r v i e w Overview Overview2. P O C V A n a l y s i s POCV\ Analysis POCV Analysis3. P O C V F l o w POCV\ Flow POCV Flow4. P O C V R e p o r t POCV\ Report POCV Report 1. O v e r v i e w Overview Overview P r o c e s s v a r i a t i…...

Android 内存分析(java/native heap内存、虚拟内存、处理器内存 )

1.jvm 堆内存(dalvik 堆内存) 不同手机中app进程的 jvm 堆内存是不同的,因厂商在出厂设备时会自定义设置其峰值。比如,在Android Studio 创建模拟器时,会设置jvm heap 默认384m , 如下图所示: 当app 进程中java 层 new 对象(加起来总和)占用…...

产品思维与工程师思维

目录标题 什么是产品思维用户痛点体验价值 产品思维与工程师思维有什么区别?产品需要什么能力洞察需求的能力逻辑思维能力成本意识 场景化思维和用户体验数据分析和售后服务数据分析服务大多数用户原则 什么是产品思维 产品思维就是考虑产品的方方面面,…...

Android---启动速度优化

App 启动流程 1. 点击桌面 App 图标,Launcher 进程采用 Binder IPC 向 system_server 进程发起 startActivity 请求 ; 2. system_server 进程接收到请求后,向 zygote 进程发送创建进程的请求; 3. zygote 进程 fork 出新的子进程…...

使用 Mercury 直接从 Jupyter 构建 Web 程序

动动发财的小手,点个赞吧! 有效的沟通在所有数据驱动的项目中都至关重要。数据专业人员通常需要将他们的发现和见解传达给利益相关者,包括业务领导、技术团队和其他数据科学家。 虽然传达数据见解的传统方法(如 PowerPoint 演示文…...

Python基础(二)

目录 一、类型转换 1、为什么需要数据类型转换 2、数据类型转化的函数 3、str()函数类型转换使用 4、int()函数类型转换使用 4.1int()不能将str类型数据转换成int 4.2int()将bool类型转换成int 4.3int()将float转换成int 5、Float()函数类型转换使用 5.1Float()函数不…...

第41讲:Python循环语句中的break-else语法结构

文章目录 1.在循环正常结束后执行动作的思路2.通过控制布尔值变量的方式在循环正常结束后执行某些操作2.1.while循环语句2.2.for-in循环语句3.通过else从句来执行某些操作1.在循环正常结束后执行动作的思路 在执行while循环语句或者for循环语句时,如果循环是正常结束的,非执…...

双系统-真机安装ubuntu

服务器系统最好选择legacy启动mbr硬盘,数据盘可以使用gpt格式,超过2t的只能用gpt。 华为2288v3用uefi找不到启动硬盘,或者是找到硬盘后无法引导,迁移系统得到有efi引导文件的硬盘也不行,选择用legacy吧。 ubuntu默认uefi启动,若使用legacy,则需要easybcd处理一下引导。 …...

Android实现向facebook回复消息代码

以下是一个示例代码,它基于Facebook SDK版本5.0,具体实现如下: 1. 集成Facebook SDK库 下载Facebook SDK并将其加入到Android Gradle构建文件中,像这样: groovy dependencies { implementation com.facebook.an…...

IDEA小技巧-Git的回滚强推代码找回

标题IDEA小技巧-Git的回滚&&强推&&代码找回 本地未Commit 新增文件 delete 变更文件 rollback 第一种方式 第二种方式 切换默认变更列表 Commit未push undo commit 仅适用于最后一次的提交进行回滚 drop commit 回滚 revert commit revert commi…...

即时通讯为什么不采用UDP的连接方式呢

即时通讯为什么不采用UDP的连接方式呢 博主今天从网络上找了几个比较关注的热点的内容进行讲解 1.首先介绍一下UDP连接的缺点 不可靠:UDP是一种无连接的传输协议,它不提供数据包的可靠传输保证。这意味着当使用UDP进行通信时,数据包可能会丢…...

二叉树(纲领篇)

文档阅读 文档阅读 二叉树解题的思维模式分两类: 1、是否可以通过遍历一遍二叉树得到答案?如果可以,用一个 traverse 函数配合外部变量来实现,这叫「遍历」的思维模式。 2、是否可以定义一个递归函数,通过子问题&a…...

day41—选择题

文章目录 1.某主机的IP 地址为 180.80.77.55,子网掩码为 255.255.252.0。若该主机向其所在子网发送广播分组,则目的地址可以是(D)2.ARP 协议的功能是(A)3.以太网的MAC 协议提供的是(A&#xff0…...

Vue3 watch 监听对象数组中对象的特定属性

在 Vue 3 中,可以使用 watch 函数来监听对象数组中对象的特定属性。可以通过在回调函数中遍历数组来检查对象的特定属性是否发生变化,并在变化发生时执行相应的操作。 一、监听对象的特定属性 例如,假设有一个名为 items 的对象数组&#x…...

请求策略库alova小记

官方文档地址:https://alova.js.org/zh-CN/get-started/overview 定义 alova是一个简单编码即可实现特定场景的高效请求的请求策略工具。 场景痛点 现在一般的请求场景,一般分为两个部分: 请求部分。一般用axios等库触发http请求&#xf…...

[C++]string的使用

目录 string的使用:: 1.string类介绍 2.string常用接口说明 string相关习题训练:: 1.仅仅反转字母 2.找字符串中第一个只出现一次的字符 3.字符串里面最后一个单词的长度 4.验证一个字符串是否是回文 5.字符串相加 6.翻转字符串…...

Kali Linux 操作系统安装详细步骤——基于 VMware 虚拟机

1. Kali 操作系统简介 Kali Linux 是一个基于 Debian 的 Linux 发行版,旨在进行高级渗透测试和安全审计。Kali Linux 包含数百种工具,适用于各种信息安全任务,如渗透测试,安全研究,计算机取证和逆向工程。Kali Linux 由…...

R语言APSIM模型应用及批量模拟实践技术

查看原文>>>基于R语言APSIM模型高级应用及批量模拟实践技术 目录 专题一、APSIM模型应用与R语言数据清洗 专题二、APSIM气象文件准备与R语言融合应用 专题三、APSIM模型的物候发育和光合生产模块 专题四、APSIM物质分配与产量模拟 专题五、APSIM土壤水平衡模块 …...

破解马赛克有多「容易」?

刷短视频时,估计大家都看过下面这类视频,各家营销号争相曝光「一分钟解码苹果笔刷背后内容」的秘密。换汤不换药,自媒体们戏称其为「破解马赛克」,殊不知让多少不明真相的用户建立起了错误的认知,也让苹果笔刷第 10086…...

【.NET基础加强第八课--委托】

.NET基础加强第八课--委托 委托(Delegate)委托操作顺序实例多播委托—委托链实例实例委托传值 委托(Delegate) 委托(Delegate) 是存有对某个方法的引用的一种引用类型变量 委托操作顺序 1,定义一个委托类…...

jetcache:阿里这款多级缓存框架一定要掌握

0. 引言 之前我们讲解了本地缓存ehcache组件,在实际应用中,并不是单一的使用本地缓存或者redis,更多是组合使用来满足不同的业务场景,于是如何优雅的组合本地缓存和远程缓存就成了我们要研究的问题,而这一点&#xff…...