Android 蓝牙HFP通话源码分析 ---HFP连接,电话接通,在UI上面切换到蓝牙,SCO链路连接流程(三.1)

Android 蓝牙HFP通话源码分析 ---HFP连接,电话接通,在UI上面切换到蓝牙,SCO链路连接流程(三.1),第1张

Android 蓝牙hfp初始化、rfcomm连接、slc连接、sco连接源代码分析大全,非常详细的从btif-bta-btm - 点击下载

该文章基于Android 11

一、概述

        HFP SLC连接成功后,状态转变为connected,然后开始设置device为active状态,同时获取硬件模块。

二、源码分析

场景:在手机音频正常连接时,接通电话,在UI上面点选蓝牙通话。audio连接成功后后,状态为audio_on,再次获取音频焦点(force_sco_on),获取到输出设备。

/packages/services/Telecomm/src/com/android/server/telecom/InCallAdapter.java

从通话中应用程序接收呼叫命令和更新,并将它们传递给 CallsManager。此时我们要切换路由。

   public void setAudioRoute(int route, String bluetoothAddress) {
383          try {
384              Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerPackageAbbreviation);
385              long token = Binder.clearCallingIdentity();
386              try {
387                  synchronized (mLock) {
388                      mCallsManager.setAudioRoute(route, bluetoothAddress);
389                  }
390              } finally {
391                  Binder.restoreCallingIdentity(token);
392              }
393          } finally {
394              Log.endSession();
395          }
396      }

/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java

Called by the in-call UI to change the audio route
     void setAudioRoute(int route, String bluetoothAddress) {
2803          mCallAudioManager.setAudioRoute(route, bluetoothAddress);
2804      }

/packages/services/Telecomm/src/com/android/server/telecom/CallAudioManager.java

change the audio route,我们切换到蓝牙。

    void setAudioRoute(int route, String bluetoothAddress) {
402          Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
403          switch (route) {
404              case CallAudioState.ROUTE_BLUETOOTH:
405                  mCallAudioRouteStateMachine.sendMessageWithSessionInfo(
406                          CallAudioRouteStateMachine.USER_SWITCH_BLUETOOTH, 0, bluetoothAddress);
407                  return;
....

/packages/services/Telecomm/src/com/android/server/telecom/CallAudioRouteStateMachine.java

在状态机中切换

class ActiveHeadsetRoute extends HeadsetRoute {应该在这个状态中
。。。。。
                 case USER_SWITCH_BLUETOOTH:
594                      if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
595                          if (mAudioFocusType == ACTIVE_FOCUS
596                                  || mBluetoothRouteManager.isInbandRingingEnabled()) {
597                              String address = (msg.obj instanceof SomeArgs) ?
598                                      (String) ((SomeArgs) msg.obj).arg2 : null;
599                              // Omit transition to ActiveBluetoothRoute until actual connection.
600                              setBluetoothOn(address);
601                          } else {
602                              transitionTo(mRingingBluetoothRoute);
603                          }
604                      } else {
605                          Log.w(this, "Ignoring switch to bluetooth command. Not available.");
606                      }
607                      return HANDLED;
   private void setBluetoothOn(String address) {
1609          if (mBluetoothRouteManager.isBluetoothAvailable()) {首先判断蓝牙是否可用
1610              BluetoothDevice connectedDevice =
1611                      mBluetoothRouteManager.getBluetoothAudioConnectedDevice();获取已经连接的蓝牙设备
1612              if (address == null && connectedDevice != null) {
1613                  // null means connect to any device, so if we're already connected to some device,
1614                  // that means we can just tell ourselves that it's connected.
1615                  // Do still try to connect audio though, so that BluetoothRouteManager knows that
1616                  // there's an active call.
1617                  Log.i(this, "Bluetooth audio already on.");
1618                  sendInternalMessage(BT_AUDIO_CONNECTED);
1619                  mBluetoothRouteManager.connectBluetoothAudio(connectedDevice.getAddress());
1620                  return;
1621              }
1622              if (connectedDevice == null || !Objects.equals(address, connectedDevice.getAddress())) {
1623                  Log.i(this, "connecting bluetooth audio: %s", address);
1624                  mBluetoothRouteManager.connectBluetoothAudio(address);
1625              }
1626          }
1627      }

/packages/services/Telecomm/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java

尝试连接到蓝牙音频。 如果第一次连接尝试同步失败,则安排稍后重试。

    public void connectBluetoothAudio(String address) {
535          SomeArgs args = SomeArgs.obtain();
536          args.arg1 = Log.createSubsession();
537          args.arg2 = address;
538          sendMessage(CONNECT_HFP, args);发送内部消息
539      }

发送内部消息

 private final class AudioOffState extends State {此时audio应该处于off状态
                     case CONNECT_HFP:
159                          String actualAddress = connectBtAudio((String) args.arg2);
160  
161                          if (actualAddress != null) {
162                              transitionTo(getConnectingStateForAddress(actualAddress,
163                                      "AudioOff/CONNECT_HFP"));
164                          } else {
165                              Log.w(LOG_TAG, "Tried to connect to %s but failed to connect to" +
166                                      " any HFP device.", (String) args.arg2);
167                          }
168                          break;
   private String connectBtAudio(String address) {
614          return connectBtAudio(address, 0);
615      }

尝试连接计数开始为0,向指定的BT地址发起连接

mDeviceManager.connectAudio返回true。
如果是之前默认蓝牙mDeviceManager.connectAudio返回false。
packages/services/Telecomm/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java

    private String connectBtAudio(String address, int retryCount) {
        ......
        if (!mDeviceManager.connectAudio(actualAddress)) {
            boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
            Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
                    shouldRetry ? "retry" : "not retry");
            if (shouldRetry) {
                SomeArgs args = SomeArgs.obtain();
                args.arg1 = Log.createSubsession();
                args.arg2 = actualAddress;
                args.argi1 = retryCount + 1;
                sendMessageDelayed(RETRY_HFP_CONNECTION, args,
                        mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                                mContext.getContentResolver()));
            }
            return null;
        }

        return actualAddress;
    }

packages/services/Telecomm/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java

    public boolean connectAudio(String address) {
        if (mHearingAidDevicesByAddress.containsKey(address)) {
            ......
        } else if (mHfpDevicesByAddress.containsKey(address)) {
            BluetoothDevice device = mHfpDevicesByAddress.get(address);
            if (mBluetoothHeadsetService == null) {
                Log.w(this, "Attempting to turn on audio when the headset service is null");
                return false;
            }
            boolean success = mBluetoothHeadsetService.setActiveDevice(device);
            if (!success) {
                Log.w(this, "Couldn't set active device to %s", address);
                return false;
            }
            if (!mBluetoothHeadsetService.isAudioOn()) {
                return mBluetoothHeadsetService.connectAudio();
            }
            return true;
        } else {
            Log.w(this, "Attempting to turn on audio for a disconnected device");
            return false;
        }
    }

packages/services/Telecomm/src/com/android/server/telecom/BluetoothHeadsetProxy.java

    public boolean connectAudio() {
        return mBluetoothHeadset.connectAudio();
    }

从这里就调用到了hfp 服务中去了:

frameworks/base/core/java/android/bluetooth/BluetoothHeadset.java

    public boolean connectAudio() {
        final IBluetoothHeadset service = mService;
        if (service != null && isEnabled()) {
            try {
                return service.connectAudio();
            } catch (RemoteException e) {
                Log.e(TAG, e.toString());
            }
        } else {
            Log.w(TAG, "Proxy not attached to service");
            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
        }
        return false;
    }

packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java

    private static class BluetoothHeadsetBinder extends IBluetoothHeadset.Stub{
        @Override
        public boolean connectAudio() {
            HeadsetService service = getService();
            if (service == null) {
                return false;
            }
            return service.connectAudio();
        }
    }
    
    boolean connectAudio() {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
        synchronized (mStateMachines) {
            BluetoothDevice device = mActiveDevice;
            if (device == null) {
                Log.w(TAG, "connectAudio: no active device, " + Utils.getUidPidString());
                return false;
            }
            return connectAudio(device);
        }
    }

如果之前默认蓝牙通话,isScoAcceptable返回false。
如果是点选蓝牙会向状态机发送CONNECT_AUDIO。

    boolean connectAudio(BluetoothDevice device) {
        enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
        Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
        synchronized (mStateMachines) {
            if (!isScoAcceptable(device)) {
                Log.w(TAG, "connectAudio, rejected SCO request to " + device);
                return false;
            }
            final HeadsetStateMachine stateMachine = mStateMachines.get(device);
            ......
            stateMachine.sendMessage(HeadsetStateMachine.CONNECT_AUDIO, device);
        }
        return true;
    }

当前状态机为Connected,接前篇hfp连接。
调用jni connectAudio,最后状态机转到mAudioConnecting
packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
 

    class Connected extends ConnectedBase {
        public boolean processMessage(Message message) {
            switch (message.what) {
                case CONNECT_AUDIO:
                    stateLogD("CONNECT_AUDIO, device=" + mDevice);
                    ......
                    if (!mNativeInterface.connectAudio(mDevice)) {
                        stateLogE("Failed to connect SCO audio for " + mDevice);
                        // No state change involved, fire broadcast immediately
                        broadcastAudioState(mDevice, BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
                                BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
                        break;
                    }
                    transitionTo(mAudioConnecting);
                    break;
                }
            }
        }
    }

packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java

static jboolean connectAudioNative(JNIEnv* env, jobject object,
                                   jbyteArray address) {
  ......
  bt_status_t status = sBluetoothHfpInterface->ConnectAudio((RawAddress*)addr);
  if (status != BT_STATUS_SUCCESS) {
    ALOGE("Failed HF audio connection, status: %d", status);
  }
  env->ReleaseByteArrayElements(address, addr, 0);
  return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

在协议栈中直接调用到了connectAudio()->BTA_AgAudioOpen()->bta_ag_sco_open()来打开sco链路了。具体协议栈连接流程请查看文首链接。

在协议栈中就发起来SCO的连接,暂且不看了:

三、音频连接状态

packages/apps/Bluetooth/jni/com_android_bluetooth_hfp.cpp
当音频连接状态改变会回调com_android_bluetooth_hfp.cpp中AudioStateCallback函数。

  void AudioStateCallback(bluetooth::headset::bthf_audio_state_t state,
                          RawAddress* bd_addr) override {
    ALOGI("%s, %d for %s", __func__, state, bd_addr->ToString().c_str());

    std::shared_lock lock(callbacks_mutex);
    CallbackEnv sCallbackEnv(__func__);
    if (!sCallbackEnv.valid() || !mCallbacksObj) return;

    ScopedLocalRef addr(sCallbackEnv.get(), marshall_bda(bd_addr));
    if (!addr.get()) return;

    sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAudioStateChanged,
                                 (jint)state, addr.get());
  }

packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java

    private void onAudioStateChanged(int state, byte[] address) {
        HeadsetStackEvent event =
                new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, state,
                        getDevice(address));
        sendMessageToService(event);
    }

    private void sendMessageToService(HeadsetStackEvent event) {
        HeadsetService service = HeadsetService.getHeadsetService();
        if (service != null) {
            service.messageFromNative(event);
        } else {
            // Service must call cleanup() when quiting and native stack shouldn't send any event
            // after cleanup() -> cleanupNative() is called.
            Log.wtfStack(TAG, "FATAL: Stack sent event while service is not available: " + event);
        }
    }

回调信息会通过messageFromNative发送STACK_EVENT消息转给状态机。
packages/apps/Bluetooth/src/com/android/bluetooth/hfp/HeadsetService.java

    void messageFromNative(HeadsetStackEvent stackEvent) {
        Objects.requireNonNull(stackEvent.device,
                "Device should never be null, event: " + stackEvent);
        synchronized (mStateMachines) {
            HeadsetStateMachine stateMachine = mStateMachines.get(stackEvent.device);
            ......
            stateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT, stackEvent);
        }
    }

此时状态机为AudioConnecting。AudioConnecting中的processMessage没有STACK_EVENT项,
运行super.processMessage。

    class AudioConnecting extends ConnectedBase {
        public boolean processMessage(Message message) {
            switch (message.what) {
                case CONNECT:
                case DISCONNECT:
                case CONNECT_AUDIO:
                case DISCONNECT_AUDIO:
                    ......
                    break;
                case CONNECT_TIMEOUT: {
                	......
                    break;
                }
                default:
                    return super.processMessage(message);
            }
            return HANDLED;
        }
        ......
    }
   private abstract class ConnectedBase extends HeadsetStateBase {
        public boolean processMessage(Message message) {
            switch (message.what) {
                case STACK_EVENT:
                    HeadsetStackEvent event = (HeadsetStackEvent) message.obj;
                    stateLogD("STACK_EVENT: " + event);
                    switch (event.type) {
                        case HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED:
                            processAudioEvent(event.valueInt);
                            break;
                    }
            }
        }

        public abstract void processAudioEvent(int state);
    }

processAudioEvent由子类实现。
连接成功,状态机变为mAudioOn

    class AudioConnecting extends ConnectedBase {
        ......
        public void processAudioEvent(int state) {
            switch (state) {
                case HeadsetHalConstants.AUDIO_STATE_CONNECTED:
                    if (!mSystemInterface.getHeadsetPhoneState().getIsCsCall()) {
                        stateLogI("Sco connected for call other than CS, check network type");
                        sendVoipConnectivityNetworktype(true);
                    } else {
                        stateLogI("Sco connected for CS call, do not check network type");
                    }
                    stateLogI("processAudioEvent: audio connected");
                    transitionTo(mAudioOn);
                    break;
            }
        }
    }
    class AudioOn extends ConnectedBase {
        public void enter() {
            super.enter();
            ......
            setAudioParameters();

            broadcastStateTransitions();
        }

通过broadcastAudioState发送广播ACTION_AUDIO_STATE_CHANGED

    private abstract class HeadsetStateBase extends State {
        void broadcastStateTransitions() {
            ......
            if (getAudioStateInt() != mPrevState.getAudioStateInt() || (
                    mPrevState instanceof AudioDisconnecting && this instanceof AudioOn)) {
                stateLogD("audio state changed: " + mDevice + ": " + mPrevState + " -> " + this);
                broadcastAudioState(mDevice, mPrevState.getAudioStateInt(), getAudioStateInt());
            }
            if (getConnectionStateInt() != mPrevState.getConnectionStateInt()) {
                stateLogD(
                        "connection state changed: " + mDevice + ": " + mPrevState + " -> " + this);
                broadcastConnectionState(mDevice, mPrevState.getConnectionStateInt(),
                        getConnectionStateInt());
            }
        }

        void broadcastAudioState(BluetoothDevice device, int fromState, int toState) {
            stateLogD("broadcastAudioState: " + device + ": " + fromState + "->" + toState);
            ......
            mHeadsetService.onAudioStateChangedFromStateMachine(device, fromState, toState);
            Intent intent = new Intent(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
            intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState);
            intent.putExtra(BluetoothProfile.EXTRA_STATE, toState);
            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
            mHeadsetService.sendBroadcastAsUser(intent, UserHandle.ALL,
                    HeadsetService.BLUETOOTH_PERM);
        }

下一期我们将当SCO链路连接完成后,如何去切换设备输出。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1295205.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-10
下一篇 2022-06-10

发表评论

登录后才能评论

评论列表(0条)

保存