音频焦点仲裁策略


1.关键类及变量列表

android10 音频焦点仲裁关键类及变量列表

变量 类型 说明
AudioFocusInfo 描述焦点申请者属性
FocusEntry 内部类 对AudioFocusInfo和Context的封装
sInteractionMatrix 二维数组 仲裁焦点申请结果
mFocusHolders 全局变量,HashMap 保存当前焦点持有者
mFocusLosers 全局变量,HashMap 保存暂时失去焦点并等待重新获得焦点的申请
losers 局部变量,ArrayList 保存失去焦点但失去焦点类型尚未确定的申请
blocked 局部变量,ArrayList 保存mFocusLosers中可以被当前申请者抢占的申请
permanentlyLost 局部变量,ArrayList 保存永久失去焦点的申请

2.申请焦点

/packages/services/Car/service/src/com/android/car/audio/CarAudioFocus.java

申请焦点的入口函数:private int evaluateFocusRequestLocked(AudioFocusInfo afi)

//新建两个boolean类型参数用来描述焦点申请者属性:
//permanent:描述申请者是否申请永久获取焦点(即申请类型是否为AUDIOFOCUS_GAIN)
//allowDucking:描述申请者是否申请暂时获取焦点且允许混音(即申请类型是否为AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)

final boolean permanent =(afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN);
final boolean allowDucking=(afi.getGainRequest()==AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);

//Convert from audio attributes "usage" to HAL level "context"
final int requestedContext = mCarAudioService.getContextForUsage(afi.getAttributes().getUsage());

//防止已经拥有或者暂时失去焦点的引用再次申请焦点,新建两个FocusEntry:
//replacedCurrentEntry:当已经拥有焦点的应用再次申请焦点时,对这个变量赋值,如果申请成功则删除//replacedCurrentEntry,用新的申请代替
//replacedBlockedEntry:当暂时失去焦点的应用再次申请焦点时,对这个变量赋值,如果申请成功则删除//replacedBlockedEntry,用新的申请代替
FocusEntry replacedCurrentEntry = null;
FocusEntry replacedBlockedEntry = null;

接下来会遍历mFocusHolders

首先判断如果当前申请者的requestedContext是NOTIFICATION并且mFocusHolders已经存在一个暂时独占焦点的申请者(AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE),则直接返回申请失败
if ((requestedContext == ContextNumber.NOTIFICATION) &&
    (entry.mAfi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) 
{   return AudioManager.AUDIOFOCUS_REQUEST_FAILED;  }


接着判断是否是同一个应用申请的焦点,如果是同一个应用且申请的是同一个Context类型的焦点,则将目前mFocusHolders中的此应用的entry赋值给replacedCurrentEntry,之后如果焦点申请成功则会将这个entry删除并将新的加入,如果申请的是不同的Context类型,则直接返回申请失败
            if (afi.getClientId().equals(entry.mAfi.getClientId())) {
                if (entry.mAudioContext == requestedContext) {
                    // This is a request from a current focus holder.
                    // Abandon the previous request (without sending a LOSS notification to it),
                    // and don't check the interaction matrix for it.
                    Slog.i(TAG, "Replacing accepted request from same client");
                    replacedCurrentEntry = entry;
                    continue;
                } else {
                    // Trivially reject a request for a different USAGE
                    Slog.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
                            + "request focus for " + afi.getAttributes().usageToString() + " on "
                            + "same listener.");
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            }

3焦点仲裁

先来看一下我们的焦点仲裁矩阵:

/packages/services/Car/service/src/com/android/car/audio/FocusInteraction.java

      private static int sInteractionMatrix[][] = {
          // Row selected by playing sound (labels along the right)
          // Column selected by incoming request (labels along the top)
          // Cell value is one of INTERACTION_REJECT, INTERACTION_EXCLUSIVE, INTERACTION_CONCURRENT
          // Invalid, Music, Nav, Voice, Ring, Call, Alarm, Notification, System
          {  0,       0,     0,   0,     0,    0,    0,     0,            0 }, // Invalid
          {  0,       1,     2,   1,     1,    1,    1,     2,            2 }, // Music
          {  0,       2,     2,   1,     2,    1,    2,     2,            2 }, // Nav
          {  0,       2,     0,   2,     1,    1,    0,     0,            0 }, // Voice
          {  0,       0,     2,   2,     2,    2,    0,     0,            2 }, // Ring
          {  0,       0,     2,   0,     2,    2,    2,     2,            0 }, // Context
          {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // Alarm
          {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // Notification
          {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // System
      };

用三个数字表示焦点仲裁的三种结果:

 // Values for the internal interaction matrix we use to make focus decisions
 static final int INTERACTION_REJECT = 0;  // Focus not granted   拒绝焦点申请
 static final int INTERACTION_EXCLUSIVE=1; // Focus granted, others loose focus接受焦点申请
 static final int INTERACTION_CONCURRENT = 2;    // Focus granted, others keep focus   同时接受两个申请。

判断结果如果是INTERACTION_REJECT,则返回申请失败,如果是INTERACTION_EXCLUSIVE,则将当前焦点持有者加入losers,如果是INTERACTION_CONCURRENT,则再判断:

1)焦点申请者是否不接受混音;

2)当前焦点持有者是否不接受混音;

3)焦点持有者是否持有RECEIVE_CAR_AUDIO_DUCKING_EVENTS标志(我也不知道啥意思,一般不会使用),如果有任何一条符合,则将当前焦点持有者加入losers,如果所有都不符合,则会出现同时两个应用同时占有焦点同时播放的情况

        for (FocusEntry entry : mFocusHolders.values()) {
            Slog.d(TAG, "Evaluating focus holder: " + entry.toString());
            
            // If this request is for Notifications and a current focus holder has specified
            // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request.
            // This matches the hardwired behavior in the default audio policy engine which apps
            // might expect (The interaction matrix doesn't have any provision for dealing with
            // override flags like this).
            if ((requestedContext == ContextNumber.NOTIFICATION) &&
                    (entry.mAfi.getGainRequest() ==
                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }

            // We don't allow sharing listeners (client IDs) between two concurrent requests
            // (because the app would have no way to know to which request a later event applied)
            if (afi.getClientId().equals(entry.mAfi.getClientId())) {
                if (entry.mAudioContext == requestedContext) {
                    // This is a request from a current focus holder.
                    // Abandon the previous request (without sending a LOSS notification to it),
                    // and don't check the interaction matrix for it.
                    Slog.i(TAG, "Replacing accepted request from same client");
                    replacedCurrentEntry = entry;
                    continue;
                } else {
                    // Trivially reject a request for a different USAGE
                    Slog.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
                            + "request focus for " + afi.getAttributes().usageToString() + " on "
                            + "same listener.");
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            }

            // Check the interaction matrix for the relationship between this entry and the request
            switch (sInteractionMatrix[entry.mAudioContext][requestedContext]) {
                case INTERACTION_REJECT:
                    Slog.d(TAG, "INTERACTION_REJECT");

                    // This request is rejected, so nothing further to do
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                case INTERACTION_EXCLUSIVE:
                    Slog.d(TAG, "INTERACTION_EXCLUSIVE");
                    // The new request will cause this existing entry to lose focus
                    losers.add(entry);
                    break;
                case INTERACTION_CONCURRENT:
                    // If ducking isn't allowed by the focus requestor, then everybody else
                    // must get a LOSS.
                    // If a focus holder has set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,
                    // they must get a LOSS message even if ducking would otherwise be allowed.
                    // If a focus holder holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS permission,
                    // they must receive all audio focus losses.
                    Slog.d(TAG, "INTERACTION_CONCURRENT");
                    if (!allowDucking
                            || entry.wantsPauseInsteadOfDucking()
                            || entry.receivesDuckEvents()) {
                        losers.add(entry);
                    }
                    break;
                default:
                    Slog.e(TAG, "Bad interaction matrix value - rejecting");
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
        }

接下来会遍历mFocusLosers,这里面保存的是暂时失去焦点的申请者,这些申请者很可能因为当前申请者的申请行为而永久失去焦点。遍历的过程和上面如出一辙,仲裁结果为INTERACTION_EXCLUSIVE或INTERACTION_CONCURRENT中符合情况的entry,会被放入blocked中。代码就不列举了,和上面基本一样。

如果一个焦点申请到这里依然没有被拒绝,那么恭喜,它将获得音频焦点,新建一个FocusEntry用来保存它:

        // Now that we've decided we'll grant focus, construct our new FocusEntry
        FocusEntry newEntry = new FocusEntry(afi, requestedContext);

  

新建一个ArrayList:permanentlyLost,用来保存永久失去焦点的申请:

        // These entries have permanently lost focus as a result of this request, so they
        // should be removed from all blocker lists.
        ArrayList<FocusEntry> permanentlyLost = new ArrayList<>();

   

如果replacedCurrentEntry或replacedBlockedEntry不等于null,则把它们从队列中删除,用新申请的newEntry来替换它

        if (replacedCurrentEntry != null) {
            mFocusHolders.remove(replacedCurrentEntry.getClientId());
            permanentlyLost.add(replacedCurrentEntry);
        }
        if (replacedBlockedEntry != null) {
            mFocusLosers.remove(replacedBlockedEntry.getClientId());
            permanentlyLost.add(replacedBlockedEntry);
        }

  

然后遍历blocked中的entry,如果当前焦点申请者希望永久占有焦点,则此entry永久失去焦点,如果当前焦点申请者只是暂时占有焦点,则判断当前焦点申请者是否不允许混音和此entry的mReceivedLossTransientCanDuck变量是否为真,如果都满足,则这个entry气数未尽,还能继续保存在mFocusLosers中,不过它会被加上一个新的mBlockers,也就是当前焦点申请者,在所有mBlockers都被移除后,这个entry就会获得焦点

          // Now that we're sure we'll accept this request, update any requests which we would
          // block but are already out of focus but waiting to come back
          for (FocusEntry entry : blocked) {
              // If we're out of focus it must be because somebody is blocking us
              assert !entry.mBlockers.isEmpty();
 
              if (permanent) {
                  // This entry has now lost focus forever
                  sendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS);
                  entry.mReceivedLossTransientCanDuck = false;
                  final FocusEntry deadEntry = mFocusLosers.remove(entry.mAfi.getClientId());
                  assert deadEntry != null;
                  permanentlyLost.add(entry);
              } else {
                  if (!allowDucking && entry.mReceivedLossTransientCanDuck) {
                      // This entry was previously allowed to duck, but can no longer do so.
                      Log.i(TAG, "Converting duckable loss to non-duckable for "
                              + entry.getClientId());
                      sendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
                      entry.mReceivedLossTransientCanDuck = false;
                  }
                  // Note that this new request is yet one more reason we can't (yet) have focus
                  entry.mBlockers.add(newEntry);
             }
          }

之后遍历losers中的entry,这里会做两件事

    判断entry失去焦点的类型:
    如果当前焦点申请者希望永久占有焦点,则失去焦点的类型为AUDIOFOCUS_LOSS,如果当前焦点申请者只是暂时占有焦点,且允许混音并持有RECEIVE_CAR_AUDIO_DUCKING_EVENTS标志,则失去焦点的类型为AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK并将的entry的mReceivedLossTransientCanDuck变量设为true,其他情况失去焦点的类型为AUDIOFOCUS_LOSS_TRANSIENT
    判断entry是暂时失去焦点还是永久失去:
    如果当前焦点申请者希望永久占有焦点,则entry永久失去焦点,如果不是,则entry暂时失去焦点

          // Notify and update any requests which are now losing focus as a result of the new request
          for (FocusEntry entry : losers) {
              // If we have focus (but are about to loose it), nobody should be blocking us yet
              assert entry.mBlockers.isEmpty();
 
              int lossType;
              if (permanent) {
                  lossType = AudioManager.AUDIOFOCUS_LOSS;
              } else if (allowDucking && entry.receivesDuckEvents()) {
                  lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
                  entry.mReceivedLossTransientCanDuck = true;
              } else {
                  lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
              }
              sendFocusLoss(entry, lossType);
 
              // The entry no longer holds focus, so take it out of the holders list
              mFocusHolders.remove(entry.mAfi.getClientId());
 
              if (permanent) {
                  permanentlyLost.add(entry);
              } else {
                  // Add ourselves to the list of requests waiting to get focus back and
                  // note why we lost focus so we can tell when it's time to get it back
                  mFocusLosers.put(entry.mAfi.getClientId(), entry);
                  entry.mBlockers.add(newEntry);
              }
          }

接下来会遍历permanentlyLost,对这个容器内的所有entry执行removeFocusEntryAndRestoreUnblockedWaiters(entry)方法,这个方法会遍历mFocusLosers并将传入的这个entry从mFocusLosers中的entry的mBlockers中移除,如果mFocusLosers中有哪一个entry的mBlockers变为空,则说明所有阻碍它获取焦点的障碍已经消失,此时它就应该获取焦点:

        // Now that all new blockers have been added, clear out any other requests that have been
        // permanently lost as a result of this request. Treat them as abandoned - if they're on
        // any blocker lists, remove them. If any focus requests become unblocked as a result,
        // re-grant them. (This can happen when a GAIN_TRANSIENT_MAY_DUCK request replaces a
        // GAIN_TRANSIENT request from the same listener.)
        for (FocusEntry entry : permanentlyLost) {
            Slog.d(TAG, "Cleaning up entry " + entry.getClientId());
            removeFocusEntryAndRestoreUnblockedWaiters(entry);
        }

  

    private void removeFocusEntryAndRestoreUnblockedWaiters(FocusEntry deadEntry) {
        // Remove this entry from the blocking list of any pending requests
        Iterator<FocusEntry> it = mFocusLosers.values().iterator();
        while (it.hasNext()) {
            FocusEntry entry = it.next();

            // Remove the retiring entry from all blocker lists
            entry.mBlockers.remove(deadEntry);

            // Any entry whose blocking list becomes empty should regain focus
            if (entry.mBlockers.isEmpty()) {
                Slog.i(TAG, "Restoring unblocked entry " + entry.getClientId());
                // Pull this entry out of the focus losers list
                it.remove();

                // Add it back into the focus holders list
                mFocusHolders.put(entry.getClientId(), entry);

                dispatchFocusGained(entry.mAfi);
            }
        }
    }

最终!将当前申请的焦点放入mFocusHolders,并返回AUDIOFOCUS_REQUEST_GRANTED

          // Finally, add the request we're granting to the focus holders' list
          mFocusHolders.put(afi.getClientId(), newEntry);
 
          Log.i(TAG, "AUDIOFOCUS_REQUEST_GRANTED");
          return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;

与申请焦点对立的放弃焦点申请:

      public synchronized void onAudioFocusAbandon(AudioFocusInfo afi) {
          Log.i(TAG, "onAudioFocusAbandon " + afi.getClientId());
 
          FocusEntry deadEntry = removeFocusEntry(afi);
 
          if (deadEntry != null) {
              removeFocusEntryAndRestoreUnblockedWaiters(deadEntry);
          }
      }

调用removeFocusEntry,这个方法会在mFocusHolders和mFocusLosers中寻找要放弃的deadEntry,如果没有找到就打印一个警告,如果成功放弃,则会继续执行removeFocusEntryAndRestoreUnblockedWaiters
 

猜你喜欢

转载自blog.csdn.net/wangbuji/article/details/125810909