首页 > 其他分享 >音频焦点使用及原理

音频焦点使用及原理

时间:2023-05-18 11:46:49浏览次数:37  
标签:AudioManager 焦点 音频 final AUDIOFOCUS 原理 App

音频焦点使用及原理

本博客代码基于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 Context
        sdk = 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 policy
            status = 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 policy
            return 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() {
        @Override
        public 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);
                }
            }
        }

        @Override
        public 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 100
        if (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 stack
            if (!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接口即可!

标签:AudioManager,焦点,音频,final,AUDIOFOCUS,原理,App
From: https://www.cnblogs.com/jackzhous/p/17410964.html

相关文章

  • < Python全景系列-4 > 史上最全文件类型读写库大盘点!什么?还包括音频、视频?
    欢迎来到我们的系列博客《Python全景系列》!在这个系列中,我们将带领你从Python的基础知识开始,一步步深入到高级话题,帮助你掌握这门强大而灵活的编程语言!本文系列第四篇,介绍史上最全PYTHON文件类型读写库大盘点!包含常用和不常用的大量文件格式!文本、音频、视频应有尽有!废话不多说!......
  • 计算机组成原理-第四章 指令系统
    指令系统概述指令指令:特指要计算机执行某种操作的命令。本章所讨论的指令特指机器指令。指令系统一台计算机中所有机器指令的集合,称为这台计算机的指令系统(指令集)。指令系统的性能要求1.完备性用汇编语言编写各种程序时,指令系统直接提供的指令足够使用,而不必用软件来实......
  • 同态密码学原理及算法-笔记
    阅读《同态密码学原理及算法-钟焰涛》的笔记基本概念同态加密部分同态加密......
  • LSTM原理以及基于PyTorch的LSTM实现MNIST手写数字
    循环神经网络让神经网络有了记忆,对于序列话的数据,循环神经网络能达到更好的效果.我们将图片数据看成一个时间上的连续数据,每一行的像素点都是这个时刻的输入,读完整张图片就是从上而下的读完了每行的像素点.然后我们就可以拿出RNN在最后一步的分析值判断图片是哪一类了下......
  • 41、说一下 HashSet 的实现原理?
    HashSet实际上是一个HashMap实例,数据存储结构都是数组+链表。HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value都是一个统一的对象PRESENT。privatestaticfinalObjectPRESENT=newObject();HashSet中add方法调用的是底层HashMap中的put方法,put......
  • Paxos算法原理与推导
    Paxos算法在分布式领域具有非常重要的地位。但是Paxos算法有两个比较明显的缺点:1.难以理解2.工程实现更难。网上有很多讲解Paxos算法的文章,但是质量参差不齐。看了很多关于Paxos的资料后发现,学习Paxos最好的资料是论文《PaxosMadeSimple》,其次是中、英文版维基百科对Paxos的介......
  • 无感带载启动,高频注入和DQ位置估算完整代码,有原理
    无感带载启动,高频注入和DQ位置估算完整代码,有原理图。全开源代码,不是库。可用于压缩机,水泵,风扇,空调,洗衣机等场合。可编译。ID:213900636564495410......
  • 无刷电机FOC控制量产方案,原理图,PCB,源代码,元器件BOM。
    无刷电机FOC控制量产方案,原理图,PCB,源代码,元器件BOM。可用于电动三轮,电动车等代步工具上,代码基于C语言,功能丰富,具有刹车功能、助力功能、欠压检测、巡航功能、防盗、自学习、故障显示等功能,可移植到家用电子,工业控制等领域。YID:618674459643030......
  • 三相光伏并网逆变器设计,原理图,PCB,以及源代码。
    三相光伏并网逆变器设计,原理图,PCB,以及源代码。主要包括以下板卡:1)主控DSP板,负责逆变器的逆变及保护控制。原理图为pdf.pcb为AD文件。2)接口板,负责信号采集、处理,以及信号等的连接。3)电源板:为整个系统提供24V以及±15V。4)功率板:实现驱动及功率逆变。5)总控板:MPPT控制、RS485modbus......
  • 15kw充电桩模块设计,源代码,原理图,pcb 1. 某达15kw充电桩模块
    15kw充电桩模块设计,源代码,原理图,pcb1.某达15kw充电桩模块,提供AD设计的电路图和pcb,源代码,并包括三相PFC程序参数变量的计算书。2.某默生15kw充电桩模块,PFC+DCDC双DSP控制,原理图(主板原理图为AD设计,其他为pdf格式),以及附有上位机软件,can通讯协议,产品规格书,无pcb源文件。YID:1315676......