高版本的android系统为第三方应用提供了一些接口,可以让应用发起蓝牙SCO通话,这种通话与真正的蓝牙电话有差异。真正的蓝牙电话音频流是在蓝牙模块与MODEM之间传输。而这种第三方应用的语音通话,是应用本身使用audiorecorder与audiotrack进行录音和放音来实现的,也就是应用(微信)使用audiorecorder从蓝牙耳机/车机取到麦克风声音后,传输到另一台手机的应用(微信),使用audiotrack把另一台手机的应用(微信)的录音播放出来,此时实际是把这个播放出来的音频传输到蓝牙模块后传给蓝牙耳机/车机播放出来。因此对于手机上的蓝牙芯片而言,这种虚拟电话实际和真正的电话是无差异的,对于蓝牙耳机/车机而言,也是无差异的。这种实现方式比起真正的通话应该会感觉到语音有延迟。

第三方APP如微信等IM工具,连接蓝牙后,发起在线语音聊天时,一般会使用蓝牙sco通话。通过一系列接口调用之后,实际会调用到packagesappsBluetoothsrccomandroidbluetoothhfpHeadsetService.java下面的startScoUsingVirtualVoiceCall接口。


第三方蓝牙库 android 第三方蓝牙软件安卓_android app系统的测试


可以看到,这个接口最后虚拟了拨号中,响铃,与接听操作。可以看到这三个操作是拨打电话时就一起调用的,因此对于微信app来说,它是在对方接听之后或者自己接听之后才调用的这个接口,实际上此时通话已经建立了。这是为什么这种通话车机端不会显示来电通知框。因为对于车机而言,是微信在通话建立后才调用startScoUsingVirtualVoiceCall来通知车机现在在打电话。

蓝牙虚拟通话建立后,当车机主动发起挂断时,会下发AT+CHUP指令进行hang up。手机上蓝牙应用解析到这条指令后,会回调hangupCall。可以看到hangupCall判断有虚拟通话在进行,直接执行stopScoUsingVirtualVoiceCall进行挂断操作。注意,这里是直接执行挂断,而不是通知微信APP,让APP挂断后再回来通知蓝牙应用,最终通知车机。也就是当车机发起挂断之后,手机蓝牙应用就告诉车机通话已经挂断了。而微信它最终去不去挂断,车机是控制不了的,从测试结果看,微信的设计是不挂断。但是对于车机而言,电话已经挂断packagesappsBluetoothsrccomandroidbluetoothhfpHeadsetStateMachine.java


第三方蓝牙库 android 第三方蓝牙软件安卓_android 蓝牙 鼠标 app_02


packagesappsBluetoothsrccomandroidbluetoothhfpHeadsetSystemInterface.java


第三方蓝牙库 android 第三方蓝牙软件安卓_android 蓝牙 鼠标 app_03


packagesappsBluetoothsrccomandroidbluetoothhfpHeadsetService.java


第三方蓝牙库 android 第三方蓝牙软件安卓_android app系统的测试_04


最后这个stopScoUsingVirtualVoiceCall会发送AT指令告诉车机电话挂断,并且关闭SCO通道。

实测发现,当在车机侧把音频切换到手机时,车机侧会收到挂断信号。这是因为当车机把切换的指令下发给手机后,手机蓝牙应用收到指令后,判断到有虚拟蓝牙电话进行就直接调用stopScoUsingVirtualVoiceCall把电话挂断了。所以车机此后会认为电话挂断了。实际微信有没有挂断,车机是不知道的,也没有办法知道。从蓝牙协议层面,通话已经挂断。packagesappsBluetoothsrccomandroidbluetoothhfpHeadsetService.java


第三方蓝牙库 android 第三方蓝牙软件安卓_android app系统的测试_05


综合上述分析,android系统下提供给第三方应用的蓝牙通话接口不够细腻,比如如果微信要实现车机来电通知框的话就比较困难,实际有可能能实现,但是需要微信去实现。比如挂断,应该手机蓝牙应用把挂断信号发给微信,微信响应后再来告诉车机。这里比较奇怪的是微信的产品设计成车机挂断后,微信还在手机侧通话,微信本来是可以获取到挂断的通知,它没有挂断,估计有其他考量,车机侧无法实现让其强制挂断。日后新版本android应该会完善这个蓝牙SCO通话功能,让第三方APP更方便更友好使用这些功能。

以上基于android P版本原生协议栈进行分析。