首页 > 系统 > Android > 正文

Android N Telecom对Audio的管理

2019-11-07 23:06:57
字体:
来源:转载
供稿:网友

Android N Telecom对Audio的管理

Android N,对通话时Audio的管理与M相比发生了比较大的变化,主要是引入了状态机。

Android N Telecom对Audio的管理CallAudioManageronCallAddedupdateForegroundCallonCallEnteringStateCall call int stateonCallStateChangedCall call int oldState int newStatemCallAudioRouteStateMachine播放tone音时流程CallAudioRouteStateMachine

CallAudioManager

首先是保留的M上的一个类CallAudioManager,通过名字就能知道是管理Audio的,也就是管理铃声,外放,语音,耳机等。 首先它继承了CallsManagerListenerBase,也就是说会监听CallsManager中Call的变化。

public class CallAudioManager extends CallsManagerListenerBase

然后构造函数,看它都管理什么

public CallAudioManager(CallAudioRouteStateMachine callAudioRouteStateMachine, CallsManager callsManager, CallAudioModeStateMachine callAudioModeStateMachine, InCallTonePlayer.Factory playerFactory, Ringer ringer, RingbackPlayer ringbackPlayer, DtmfLocalTonePlayer dtmfLocalTonePlayer) { mActiveDialingOrConnectingCalls = new LinkedHashSet<>(); mRingingCalls = new LinkedHashSet<>(); mHoldingCalls = new LinkedHashSet<>(); mCalls = new HashSet<>(); mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{ put(CallState.CONNECTING, mActiveDialingOrConnectingCalls); put(CallState.ACTIVE, mActiveDialingOrConnectingCalls); put(CallState.DIALING, mActiveDialingOrConnectingCalls); put(CallState.RINGING, mRingingCalls); put(CallState.ON_HOLD, mHoldingCalls); }}; mCallAudioRouteStateMachine = callAudioRouteStateMachine; mCallAudioModeStateMachine = callAudioModeStateMachine; mCallsManager = callsManager; mPlayerFactory = playerFactory; mRinger = ringer; mRingbackPlayer = ringbackPlayer; mDtmfLocalTonePlayer = dtmfLocalTonePlayer; mPlayerFactory.setCallAudioManager(this); mCallAudioModeStateMachine.setCallAudioManager(this); }

可以看到首先是初始化了很多的变量,包括各种Call的集合,并且通过mCallStateToCalls可以根据Call的状态拿到不同的Call。播放铃声或者其他声音的mPlayerFactory,mRinger,mRingbackPlayer,mDtmfLocalTonePlayer。两个非常重要的状态机mCallAudioRouteStateMachine,mCallAudioModeStateMachine。 因为它继承了CallsManagerListenerBase并且记录非常多的Call,所以可以认为它很多的工作都是与Call的变化相关的,我们以CallsManagerListenerBase中的方法为线索看看它都干了什么

onCallAdded()

这个方法看起来很简单,就这么几行代码,看看具体干了什么?

@Override public void onCallAdded(Call call) { if (shouldIgnoreCallForAudio(call)) { return; // Don't do audio handling for calls in a conference, or external calls. } addCall(call); }

首先是shouldIgnoreCallForAudio(Call call),看名字就知道是否要忽略这个Call,它的代码也是非常简单:忽略conference或者external的Call

PRivate boolean shouldIgnoreCallForAudio(Call call) { return call.getParentCall() != null || call.isExternalCall(); }

然后就是这个addCall(Call call)

private void addCall(Call call) { // 略 if (mCallStateToCalls.get(call.getState()) != null) { mCallStateToCalls.get(call.getState()).add(call); } updateForegroundCall(); mCalls.add(call); onCallEnteringState(call, call.getState()); }

忽略一些不重要的判重条件和log,有用就这么几行。首先想特定的状态的Call的集合加入这个Call,然后更新前台的Call,向mCalls里加入这个Call,根据Call的状态进入Audio的状态

updateForegroundCall()

private void updateForegroundCall() { Call oldForegroundCall = mForegroundCall; if (mActiveDialingOrConnectingCalls.size() > 0) { // Give preference for connecting calls over active/dialing for foreground-ness. Call possibleConnectingCall = null; for (Call call : mActiveDialingOrConnectingCalls) { if (call.getState() == CallState.CONNECTING) { possibleConnectingCall = call; } } if (possibleConnectingCall != null) { mForegroundCall = possibleConnectingCall; } else { mForegroundCall = getCallFromList(mActiveDialingOrConnectingCalls); } } else if (mRingingCalls.size() > 0) { mForegroundCall = getCallFromList(mRingingCalls); } else if (mHoldingCalls.size() > 0) { mForegroundCall = getCallFromList(mHoldingCalls); } else { mForegroundCall = null; } if (mForegroundCall != oldForegroundCall) { mCallAudioRouteStateMachine.sendMessageWithsessionInfo( CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE); mDtmfLocalTonePlayer.onForegroundCallChanged(oldForegroundCall, mForegroundCall); maybePlayHoldTone(); } }

就是根据Call的状态为优先级,去获得前台的Call,CONNECTING>ACTIVE>RINGING>HOLD,否则为空。当前台Call发生变化,去更新一些Audio的状态。

onCallEnteringState(Call call, int state)

private void onCallEnteringState(Call call, int state) { switch (state) { case CallState.ACTIVE: case CallState.CONNECTING: onCallEnteringActiveDialingOrConnecting(); break; case CallState.RINGING: onCallEnteringRinging(); break; case CallState.ON_HOLD: onCallEnteringHold(); break; case CallState.DIALING: onCallEnteringActiveDialingOrConnecting(); playRingbackForCall(call); break; } }

根据Call的状态设置不同的Audio状态,它们的代码都类似:就是向CallAudioModeStateMachine发送一个消息,更新Audio的状态

private void onCallEnteringActiveDialingOrConnecting() { if (mActiveDialingOrConnectingCalls.size() == 1) { mCallAudioModeStateMachine.sendMessageWithArgs( CallAudioModeStateMachine.NEW_ACTIVE_OR_DIALING_CALL, makeArgsForModeStateMachine()); } }

onCallStateChanged(Call call, int oldState, int newState)

public void onCallStateChanged(Call call, int oldState, int newState) { // 略 for (int i = 0; i < mCallStateToCalls.size(); i++) { mCallStateToCalls.valueAt(i).remove(call); } if (mCallStateToCalls.get(newState) != null) { mCallStateToCalls.get(newState).add(call); } updateForegroundCall(); if (newState == CallState.DISCONNECTED) { playToneForDisconnectedCall(call); } onCallLeavingState(call, oldState); onCallEnteringState(call, newState); }

可以看到代码也是非常简单,从原先的某个状态Call的集合删除这个Call,再将这个Call更新到现在状态的集合,更新前台Call,判断是否播放结束音,最后是onCallLeavingState和onCallEnteringState,离开一个状态,进入新的状态,其过程还是一样,向mCallAudioModeStateMachine发送一个消息。

其他的几个方法也是类似,最终的结果无外乎是向mCallAudioModeStateMachine发送消息和更新前台的Call,而更新前台的Call会向mCallAudioRouteStateMachine发送一个消息,所以归根到底就是向这两个状态机发送消息。所以根源上,就是这两个状态机mCallAudioModeStateMachine,mCallAudioRouteStateMachine。

mCallAudioRouteStateMachine

谈到状态机那么有那些状态呢? UnfocusedState,RingingFocusState,SimCallFocusState,VoipCallFocusState,OtherFocusState。 这几种状态应该都很好理解,没有电话,响铃时,Sim电话,Voip电话,和OtherFocus。最奇怪的就是OtherFocusState 根据注释

/** * This class is used for calls on hold and end-of-call tones. */

这个状态是为了Call在hold时和播放Call结束的时候的tones,因为M在这上面处理的不好,在Call Remove的时候应该将audio abandon,就会切换route,但是又要播放tone音,又要focus audio,就会有可能tone音放不完或者不是预期的route。 所以研究一下在电话挂断时播放tone音的流程。

播放tone音时流程

首先Call Leave Active,进入DISCONNECTED状态,向CallAudioModeStateMachine发送NO_MORE_ACTIVE_OR_DIALING_CALLS消息

private void onCallLeavingActiveDialingOrConnecting() { if (mActiveDialingOrConnectingCalls.size() == 0) { mCallAudioModeStateMachine.sendMessageWithArgs( CallAudioModeStateMachine.NO_MORE_ACTIVE_OR_DIALING_CALLS, makeArgsForModeStateMachine()); } }

现在CallAudioModeStateMachine在SimCallFocusState状态

case NO_MORE_ACTIVE_OR_DIALING_CALLS: // Switch to either ringing, holding, or inactive transitionTo(destinationStateAfterNoMoreActiveCalls(args)); return HANDLED; private BaseState destinationStateAfterNoMoreActiveCalls(MessageArgs args) { if (args.hasHoldingCalls) { return mOtherFocusState; } else if (args.hasRingingCalls) { return mRingingFocusState; } else if (args.isTonePlaying) { return mOtherFocusState; } else { return mUnfocusedState; } }

因为此时args.isTonePlaying为true,就会进入mOtherFocusState。

@Override public void enter() { Log.i(LOG_TAG, "Audio focus entering TONE/HOLDING state"); mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_VOICE_CALL, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT); mAudioManager.setMode(mMostRecentMode); mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.HAS_FOCUS); }

所以可以认为它保持了上一个状态即SimCallFocusState。它没有变更focus状态和route状态,那么tone音也就不会突然终止,也不会突然切换到其他route上。 那么什么时候结束呢?

public void setIsTonePlaying(boolean isTonePlaying) { mIsTonePlaying = isTonePlaying; mCallAudioModeStateMachine.sendMessageWithArgs( isTonePlaying ? CallAudioModeStateMachine.TONE_STARTED_PLAYING : CallAudioModeStateMachine.TONE_STOPPED_PLAYING, makeArgsForModeStateMachine()); } case TONE_STOPPED_PLAYING: transitionTo(destinationStateAfterNoMoreActiveCalls(args));

就是在tone音放完时,这时会切到mUnfocusedState状态

CallAudioRouteStateMachine

这个是管理route的状态机,看一下它的构造函数

public CallAudioRouteStateMachine( Context context, CallsManager callsManager, BluetoothManager bluetoothManager, WiredHeadsetManager wiredHeadsetManager, StatusBarNotifier statusBarNotifier, CallAudioManager.AudioServiceFactory audioServiceFactory, boolean doesDeviceSupportEarpieceRoute) { super(NAME); addState(mActiveEarpieceRoute); addState(mActiveHeadsetRoute); addState(mActiveBluetoothRoute); addState(mActiveSpeakerRoute); addState(mQuiescentEarpieceRoute); addState(mQuiescentHeadsetRoute); addState(mQuiescentBluetoothRoute); addState(mQuiescentSpeakerRoute); mContext = context; mCallsManager = callsManager; mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); mBluetoothManager = bluetoothManager; mWiredHeadsetManager = wiredHeadsetManager; mStatusBarNotifier = statusBarNotifier; mAudioServiceFactory = audioServiceFactory; mDoesDeviceSupportEarpieceRoute = doesDeviceSupportEarpieceRoute; mStateNameToRouteCode = new HashMap<>(8); mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE); mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH); mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET); mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER); mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE); mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH); mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET); mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER); mRouteCodeToQuiescentState = new HashMap<>(4); mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute); mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute); mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute); mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute); }

它比CallAudioModeStateMachine会复杂一些,它有mBluetoothManager和mWiredHeadsetManager它实际管理这audio route,真正决定声音从哪个route发出 可以看到有8中状态,也可以说是4种:听筒,耳机,蓝牙,外放。每一种又分为两个状态Quiescent和Active Quiescent和Active有什么区别呢?从enter方法就能看到:

// ActiveEarpieceRoute @Override public void enter() { super.enter(); setSpeakerphoneOn(false); setBluetoothOn(false); CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE, mAvailableRoutes); setSystemAudioState(newState); updateInternalCallAudioState(); } // QuiescentEarpieceRoute @Override public void enter() { super.enter(); mHasUserExplicitlyLeftBluetooth = false; updateInternalCallAudioState(); }

可以看到在休眠的时候少做了一些事情,没有setSystemAudioState(newState); 只是updateInternalCallAudioState();

/** * Updates the CallAudioState object from current internal state. The result is used for * external communication only. */ private void updateInternalCallAudioState() { IState currentState = getCurrentState(); if (currentState == null) { Log.e(this, new IllegalStateException(), "Current state should never be null" + " when updateInternalCallAudioState is called."); mCurrentCallAudioState = new CallAudioState( mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes); return; } int currentRoute = mStateNameToRouteCode.get(currentState.getName()); mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes); }

非常明显,如果在休眠状态,我们只更新了mCurrentCallAudioState,然后就什么都没干,而在Active状态,我们才会真正设置AudioRoute,并setSystemAdioState,不执行setSystemAudioState会怎样呢?

private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) { Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState, newCallAudioState); Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE, CallAudioState.audioRouteToString(newCallAudioState.getRoute())); if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) { mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState); updateAudioForForegroundCall(newCallAudioState); mLastKnownCallAudioState = newCallAudioState; } }

也就是说不会通知CallsManger说audio state发生了变化。也就是说当audio route在Quiescent间切换时不会通知外界。但是我们通过CallsManager的getCurrentAudioState方法又能得到当前的Audio State。也就是说在休眠状态下,不主动告知Audio的变化。


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表