学习Android Audio 焦点记录

06-01 1763阅读

Android Audio 焦点管理详解

1.概述

  • 音频焦点:Android 系统中用于管理多个应用同时播放音频的机制。
  • 核心目标:确保用户在不同应用之间切换时,音频播放行为合理且有序。

    2.音频焦点的基本概念

    2.1 什么是音频焦点?

    • 定义:音频焦点是系统为应用分配的一种资源,用于控制音频播放的优先级。
    • 作用:避免多个应用同时播放音频,导致声音混乱。

      2.2音频焦点的类型

      焦点类型使用场景
      AUDIOFOCUS_NONE无焦点状态,通常用于初始化或未请求焦点时。
      AUDIOFOCUS_GAIN长时间占用音频焦点,如音乐播放器、视频播放器等。
      AUDIOFOCUS_GAIN_TRANSIENT短时获取焦点,失去焦点时暂停播放,如语音提示、通知音等。
      AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK短时获取焦点,失去焦点时降低音量,如导航提示音等。
      AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE短时获取焦点,失去焦点时完全停止播放,如电话铃声、紧急提示音等。

      3.获取与释放音频焦点

      3.1获取音频焦点

      • 方法:使用 AudioManager.requestAudioFocus。
      • 示例代码:
        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
        int result = audioManager.requestAudioFocus(
            focusChangeListener,
            AudioManager.STREAM_MUSIC,
            AudioManager.AUDIOFOCUS_GAIN
        );
        if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
            // 成功获取焦点
        }
        

        3.2释放音频焦点

        • 方法:使用 AudioManager.abandonAudioFocus。
        • 示例代码:
            audioManager.abandonAudioFocus(focusChangeListener);
          

          4.处理音频焦点变化

          4.1焦点变化事件

          焦点类型使用场景
          AUDIOFOCUS_LOSS永久失去焦点,停止播放并释放资源。
          AUDIOFOCUS_LOSS_TRANSIENT暂时失去焦点,暂停播放。
          AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK暂时失去焦点,降低音量。

          4.2实现 OnAudioFocusChangeListener

          • 示例代码:
              AudioManager.OnAudioFocusChangeListener focusChangeListener = new AudioManager.OnAudioFocusChangeListener() {
                @Override
                public void onAudioFocusChange(int focusChange) {
                    switch (focusChange) {
                        case AudioManager.AUDIOFOCUS_GAIN:
                            // 恢复播放
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS:
                            // 停止播放
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
                            // 暂停播放
                            break;
                        case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            // 降低音量
                            break;
                    }
                }
            };
            

            5.代码分析

            5.1requestAudioFocus分析

            在AudioManager中做了如下工作:

            • 检查传入的 AudioManager.OnAudioFocusChangeListener 是否为空。
            • 检查音频流类型(如 AudioManager.STREAM_MUSIC)是否有效。
            • 检查焦点请求类型(如 AUDIOFOCUS_GAIN)是否合法。
            • 将传参打包成AudioFocusRequest对象,并且生成FocusRequestInfo存储到mAudioFocusIdListenerMap中便于后续管理和触发焦点变化事件。
            • 通过binder调用AudioService.java接口requestAudioFocus

              在AudioService.java中做了如下工作:

            • 初始化MediaMetrics,这说明这个方法有日志记录的功能,帮助开发者跟踪焦点请求的情况。
            • 完成权限和参数校验
                  protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
                          IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
                          int flags, int sdk, boolean forceDuck, int testUid) {
                      // we need a valid binder callback for clients
                      if (!cb.pingBinder()) {
                          Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
                          return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                      }
                      synchronized(mAudioFocusLock) {
                          if (mFocusStack.size() > MAX_STACK_SIZE) {
                              Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
                              return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                          }
                          boolean enteringRingOrCall = !mRingOrCallActive
                                  & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
                          if (enteringRingOrCall) { mRingOrCallActive = true; }
                          final AudioFocusInfo afiForExtPolicy;
                          if (mFocusPolicy != null) {
                              // construct AudioFocusInfo as it will be communicated to audio focus policy
                              afiForExtPolicy = new AudioFocusInfo(aa, uid,
                                      clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
                                      flags, sdk);
                          } else {
                              afiForExtPolicy = null;
                          }
                          // handle delayed focus
                          boolean focusGrantDelayed = false;
                          if (!canReassignAudioFocus()) {
                              if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                                  return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                              } else {
                                  // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
                                  // granted right now, so the requester will be inserted in the focus stack
                                  // to receive focus later
                                  focusGrantDelayed = true;
                              }
                          }
                          // external focus policy?
                          if (mFocusPolicy != null) {
                              if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
                                  // stop handling focus request here as it is handled by external audio
                                  // focus policy (return code will be handled in AudioManager)
                                  return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
                              } else {
                                  // an error occured, client already dead, bail early
                                  return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                              }
                          }
                          AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
                          try {
                              cb.linkToDeath(afdh, 0);
                          } catch (RemoteException e) {
                              // client has already died!
                              Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
                              return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                          }
                          if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                              // if focus is already owned by this client and the reason for acquiring the focus
                              // hasn't changed, don't do anything
                              final FocusRequester fr = mFocusStack.peek();
                              if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                                  // unlink death handler so it can be gc'ed.
                                  // linkToDeath() creates a JNI global reference preventing collection.
                                  cb.unlinkToDeath(afdh, 0);
                                  notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
                                          AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                                  return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                              }
                              // the reason for the audio focus request has changed: remove the current top of
                              // stack and respond as if we had a new focus owner
                              if (!focusGrantDelayed) {
                                  mFocusStack.pop();
                                  // the entry that was "popped" is the same that was "peeked" above
                                  fr.release();
                              }
                          }
                          // focus requester might already be somewhere below in the stack, remove it
                          removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
                          final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                                  clientId, afdh, callingPackageName, uid, this, sdk);
                          if (mMultiAudioFocusEnabled
                                  && (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
                              if (enteringRingOrCall) {
                                  if (!mMultiAudioFocusList.isEmpty()) {
                                      for (FocusRequester multifr : mMultiAudioFocusList) {
                                          multifr.handleFocusLossFromGain(focusChangeHint, nfr, forceDuck);
                                      }
                                  }
                              } else {
                                  boolean needAdd = true;
                                  if (!mMultiAudioFocusList.isEmpty()) {
                                      for (FocusRequester multifr : mMultiAudioFocusList) {
                                          if (multifr.getClientUid() == Binder.getCallingUid()) {
                                              needAdd = false;
                                              break;
                                          }
                                      }
                                  }
                                  if (needAdd) {
                                      mMultiAudioFocusList.add(nfr);
                                  }
                                  nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                                  notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                                          AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                                  return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                              }
                          }
                          if (focusGrantDelayed) {
                              //找到最后一个被锁定的焦点所有者(exclusive focus owner)的位置,然后将nfr插入到该位置之下
                              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
                              propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
                              // push focus requester at the top of the audio focus stack
                              mFocusStack.push(nfr);
                              nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                          }
                          notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                                  AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                          if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
                              runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
                          }
                      }//synchronized(mAudioFocusLock)
                      return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                  }
              

              在MediaFocusControl.java中做了如下工作:

              • 检查Binder的存活状态来确保客户端有效,如果客户端已经死亡,则直接返回失败
              • 电话或铃声场景的特殊情况,设置相关标志位 enteringRingOrCall
              • 构建AudioFocusInfo
              • 当存在外部焦点策略(mFocusPolicy)时,方法将焦点请求转发给外部策略处理,并根据返回结果决定是否继续内部处理 比如CarAudio
              • 注册死亡通知,确保在客户端死亡时能正确释放焦点,避免资源泄漏。
              • 如果当前堆栈顶部的焦点请求者与当前客户端相同且参数未变,则通知外部焦点策略然后直接返回已授予状态,避免重复处理,
              • 移除旧的焦点请求从堆栈中
              • 生成FocusRequester对象
              • 根据是否启用多音频焦点(mMultiAudioFocusEnabled)来处理不同的场景
              • 对于延迟授予焦点的情况(AUDIOFOCUS_FLAG_DELAY_OK),方法将新请求插入堆栈中的适当位置,等待后续处理。否则,立即传播焦点变化,并更新焦点堆栈。
              • 最后,方法处理进入电话或铃声场景时的音频检查,确保系统音频状态正确更新,并返回焦点请求的结果。

                5.2.propagateFocusLossFromGain_syncAf分析

                这个函数的作用就是给注册的焦点发送焦点loss的消息。

                直接来看FocusRequester.java中的代码

                    void handleFocusLossFromGain(int focusChange, FocusRequester requester, boolean forceDuck) {
                        if (mAudioFocusDispatcher != null) {
                            mAudioFocusDispatcher.dispatchAudioFocusChange(focusChange, requester);
                        } else {
                            dispatchAudioFocusChange(focusChange, requester);
                        }
                    }
                

                学习Android Audio 焦点记录

                • 1、2、3分别是A、B和C申请焦点,分别在栈中的位置以及其丢失焦点类型

                  3步骤C申请AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK时,会根据MediaFocusControl的ENFORCE_DUCKING 来决定谁负责降音,true由framework进行降音,false将会以AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK类型返回给B,由B自己负责降音

                • 4步骤是再次申请焦点时,

                  1)如果申请者A再次申请,已经存在栈中但又不在栈顶,则先移除栈中的A,在从新push到栈顶

                  2)如果申请者C再次申请,已经存在且在栈顶,并且申请的焦点类型与之前相同就直接返回给C获得焦点;反之申请焦点类型不同,则会移除栈顶C,然后在重新push到栈顶

                • 4步骤释放abandon焦点;何时释放焦点呢?

                  1)焦点使用者播放完音频资源后主动释放焦点

                  2)焦点使用者收到了LOSS_GAIN永久丢失焦点类型,主动释放焦点

                  释放规则如上图,自行查看

                  5.3frameworkHandleFocusLoss

                  frameworkHandleFocusLoss来负责framework的降音逻辑,如果返回值是true那么代表framework已经处理过了不需要通知app,如果是false那么就是需要通知app处理

                  注意

                  焦点切换发生在同一个应用内时,framework不会处理,而是直接返回false,由应用自己处理

                  框架处理焦点丢失的主要场景包括:

                  • 降低音量:当焦点丢失类型为 AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK 且满足特定条件时,由框架强制处理 ducking。
                  • 淡出播放:当焦点丢失类型为 AUDIOFOCUS_LOSS 且满足特定条件时,由框架处理淡出逻辑。

                    其他情况:通常由应用自己处理焦点丢失事件。

                    5.4 dispatchAudioFocusChange

                    在AudioManager.java中dispatchAudioFocusChange会从mAudioFocusIdListenerMap找到指定的app,然后把焦点改变的消息通知给app。app在自身做相应的处理。

                    6.总结

                    音频焦点框架主要就是对各个APP的焦点进行注册、通知、释放。焦点释放可能会在framework完成那么app也就收不到相应的焦点通知了。

                    参考链接

                    https://blog.csdn.net/jackzhouyu/article/details/130527253?ops_request_misc=%257B%2522request%255Fid%2522%253A%25226a043c2db23192475d075cb14bd2cd11%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=6a043c2db23192475d075cb14bd2cd11&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2blogfirst_rank_ecpm_v1~rank_v31_ecpm-1-130527253-null-null.nonecase&utm_term=%E7%84%A6%E7%82%B9&spm=1018.2226.3001.4450

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码