蓝牙那点事儿

4,400 阅读28分钟

第一章 蓝牙基础知识

一. 蓝牙名称来源

蓝牙(Bluetooth)一词取自于十世纪丹麦国王哈拉尔的名字 Harald Bluetooth。他因统一斯堪的纳维亚半岛而闻名于世。正式将Bluetooth作为无线通讯技术标准的名称,是在1996年,英特尔、爱立信和诺基亚三家行业领导者的标准化会议上,一位来自英特尔的工程师Jim Kardach提议,他希望蓝牙也可以像哈拉尔国王统一半岛一样成为统一的通用传输标准——将所有分散的设备与内容互联互通。

蓝牙的 LOGO 来自后弗萨克文的符文组合,将哈拉尔国王名字的首字母 H 和 B 拼在一起,得到了今天大家熟知的蓝色徽标。

二. 蓝牙技术概念

蓝牙是一种无线技术标准,可实现固定设备、移动设备和楼宇个人域网之间的短距离数据交换(使用2.4—2.485GHz的ISM波段的特高频无线电波)。蓝牙技术最初由电信巨头爱立信公司于1994年创制,当时是作为RS232数据线的替代方案。目前由蓝牙技术联盟管理,其在全球拥有超过25,000家成员公司,它们分布在电信、计算机、网络、和消费电子等多重领域。

三. 蓝牙技术变迁

  • 1.0+ BR 时代

早期的1.0,1.1版本,存在很多问题,并且与多家厂商的产品互不兼容。同时在连接过程没法将蓝牙的设备地址给隐匿,容易造成数据泄露的风险,导致市场使用率不高

1.2版本完善了匿名的方式,新增了屏蔽设备硬件地址的功能,保护用户免受身份嗅探攻击和跟踪,同时增加了AFH(自适应调频)和eSCO(延伸同步连结导向信道技术),很大程度上提高了蓝牙的抗干扰性和语音传输的质量。

  • 2.0+ EDR 版本

蓝牙 2.0 是 1.2 版本的改良版,新增的 EDR 技术通过提高多任务处理和多种蓝牙设备同时运行的能力,使得蓝牙设备的传输率可达 3Mbps。同时 2.0 版本支持双工模式,可以一边进行语音通讯,一边传输文档/高像素图片。并且 EDR 技术通过减少工作负载循环来降低功耗,随着带宽的增加,蓝牙 2.0 增加了连接设备的数量。

蓝牙 2.1 新增了 Sniff Subrating 省电功能,将设备间相互确认的讯号发送时间间隔从旧版的 0.1 秒延长到 0.5 秒左右,从而让蓝牙芯片的工作负载大幅降低。

另外,新增 SSP 简易安全配对功能,改善了蓝牙设备的配对体验,同时提升了使用和安全强度。

其中支持 NFC 的OOB配对模式,只要将两个内置有 NFC 芯片的蓝牙设备相互靠近,配对密码将通过 NFC 进行传输,无需手动输入。

  • 3.0+ HS 版本

蓝牙 3.0 新增了可选技术 High Speed,High Speed 可以使蓝牙调用 802.11 用于实现高速数据传输,传输率高达 24Mbps,是蓝牙 2.0 的 8 倍。

蓝牙 3.0 的核心是 AMP(Generic Alternate MAC/PHY),这是一种全新的交替射频技术,允许蓝牙协议栈针对任一任务动态地选择正确射频。功耗方面,蓝牙 3.0 引入了 EPC 增强电源控制技术,再辅以 802.11,实际空闲功耗明显降低。

  • 4.0+ BLE 版本

蓝牙 4.0 是迄今为止第一个蓝牙综合协议规范,将三种规格集成在一起。还提出了低功耗蓝牙、传统蓝牙和高速蓝牙三种模式:

高速蓝牙主攻数据交换与传输;传统蓝牙则以信息沟通、设备连接为重点;低功耗蓝牙以不需占用太多带宽的设备连接为主,功耗较老版本降低了 90%

BLE 前身是 NOKIA 开发的 Wibree 技术,本是作为一项专为移动设备开发的极低功耗的移动无线通信技术,在被 SIG 接纳并规范化之后重命名为 Bluetooth Low Energy。

蓝牙 4.0 的芯片模式分为 Single mode 与 Dual mode。Single mode 只能与蓝牙 4.0 互相传输无法向下与 3.0/2.1/2.0 版本兼容;Dual mode 可以向下兼容 3.0/2.1/2.0 版本。前者应用于使用纽扣电池的传感器设备,例如对功耗要求较高的心率检测器和温度计;后者应用于传统蓝牙设备,同时兼顾低功耗的需求。此外,蓝牙 4.0 还把蓝牙的传输距离提升到100米以上,并拥有更快的响应速度,最短可在 3 毫秒内完成连接设置并开始传输数据,同时使用 AES-128 CCM 加密算法进行数据包加密和认证,让数据的传递变的更加安全可靠。

蓝牙 4.1 在传输速度和传输范围上变化很小,但在软件方面有着明显的改进,目的是为了让 Bluetooth Smart 技术最终成为IOT发展的核心动力。

支持云同步:蓝牙 4.1 加入了专用的 IPv6 通道,蓝牙 4.1 设备只需要连接到可以联网的设备,就可以通过 IPv6 与云端的数据进行同步,满足物联网的应用需求。

支持中心设备与外围设备角色互换:支持蓝牙 4.1 标准的耳机、手表、键鼠,可以不用通过 PC、平板、手机等数据枢纽,实现自主收发数据。例如智能手表和计步器可以绕过智能手机,直接实现对话。

蓝牙 4.2 的传输速度更加快速,比上代提高了 2.5 倍,因为 Bluetooth Smart 数据包的容量提高,其可容纳的数据量相当于此前的10倍左右。

改善了传输速率和隐私保护程度,蓝牙信号想要连接或者追踪用户设备,必须经过用户许可。用户可以放心使用可穿戴设备而不用担心被跟踪。

  • 5.0+ IOT 时代

蓝牙 5.0 在低功耗模式下具备更快更远的传输能力,传输速率是蓝牙 4.2 的两倍(速度上限为 2Mbps),有效传输距离是蓝牙 4.2 的四倍(理论上可达 300 米),数据包容量是蓝牙 4.2 的八倍。

支持蓝牙Mesh网络:Mesh 网络是一项独立研发的网络技术,它能够将蓝牙设备作为信号中继站,将数据覆盖到非常大的物理区域。传统的蓝牙连接是通过一台设备到另一台设备的配对实现的,建立一对一或一对多的微型网络关系。而 Mesh 网络能够使设备实现多对多的关系。Mesh 网络中每个设备节点都能发送和接收信息,只要有一个设备连上网关,信息就能够在节点之间被中继,从而让消息传输至比无线电波正常传输距离更远的位置。

蓝牙 5.1 利用测向功能检测蓝牙信号方向,进而提升室内定位服务。借助蓝牙测向功能,开发者能够将可探测设备方向及实现厘米级定位精度的产品推向市场。

AOA(Angle of Arrival): 可用于室内的实时定位、物品追踪和地标信息。

AOD(Angle of Departure): 可用于室内定位系统,手机就是使用这种方法,通过低功耗接收器来接收信号,配合周围的定位Beacon来实现的。

蓝牙 5.2 引入了新一代蓝牙音频技术,打破了经典蓝牙音频的市场垄断地位,开创了蓝牙无线音频新市场。LE Audio不仅在音频质量上面做出了提升,更是加入了低功耗特性,使得蓝牙音频能够在更多场景中应用。

LC3 编辑解码器打造更好更高音质和更低功耗: LE Audio 将采用全新的低复杂性通信编解码器(Low Complexity Communication Codec, LC3),以提供实现更高的音质和更低的功耗,虽然LC3编码比不上APTX,APTX-HD音频编码,但是相较于经典蓝牙音频编码SBC,LC3编码效率和质量都有了较大的提高,这使得LE Audio比经典蓝牙音频技术拥有更大的技术优势,经典蓝牙音频的垄断地位将被LE Audio所打破。

多重串流音频:LE Audio的多重串流音频(Multi-Stream Audio)可助力打造性能更卓越的入耳式耳机。多重串流音频功能将实现在智能手机等单一音频源设备(source device)、单个或多个音频接收设备(sink device)间,同步进行多重且独立的音频串流传输。

四. 蓝牙协议架构

蓝牙协议架构,从上到下,分别为应用配置层,应用协议层,逻辑链路适配层,主机控制接口层,链路管理协议层,基带协议层,链路控制协议层,射频协议层

应用配置层: A2DP, AVRCP, HFP,GATT分别代表蓝牙的一个配置文件,配置文件是对潜在应用场景的定义,并指定Bluetooth启用设备与其他Bluetooth设备通信时使用的一般行为。每个配置文件都定义了蓝牙使用的一个场景,如通过蓝牙播放音乐,就需要通过A2dp Profile, 如通过蓝牙拨打电话,就需要通过Hfp Profile。下面给出了日常被使用的Profile列表:

应用协议层: 上层配置文件的基础协议,具体的细化了每个使用场景功能的数据,控制处理。如AVDTP,则规定了两个蓝牙设备是如何建立音视频连接的,包括如何发现对端支持的音视频能力,协商codec等。

逻辑链路适配层: 蓝牙的核心协议,支持高层协议多路复用、数据分段和重组,并且支持传送服务质量信息,通过信道识别符(CID)引用每条 L2CAP 信道的端点。

主机控制接口层: 提供了访问 bluetooth host/control 的统一接口,通俗来讲,就是定义了由蓝牙协议栈来控制蓝牙芯片来做相应的动作(比如inquiry,connect,disconnect)的特定格式,并且由蓝牙芯片回应蓝牙协议栈状态以及事件消息。

链路管理层: 链路管理器协议(LMP)控制和协调两个设备之间Bluetooth连接运行的所有方面。包括设置和控制逻辑传输,逻辑链路及物理链路控制,用于通过ACL逻辑传输连接的两个设备的链路管理器(LM)之间的通信。

基带协议层: 蓝牙发送数据时,基带将来自高层的数据进行信道编码,向下发给射频进行发送,接收数据时,将解调恢复空中数据并上传给基带,基带进行信道编码传送给上层。

链路控制协议层: 链路控制协议层负责物理通道,逻辑传输和逻辑链路的数据,控制等蓝牙packet的加解密。

射频协议层: 射频是指介于声音频率与红外线频率之间的电磁波频率,蓝牙射频协议规定了蓝牙射频频段,调制方式,调频频率,发射功率,接收灵敏度等参数。

第二章 Android蓝牙知识

一. Android 蓝牙架构

Android平台上,蓝牙的架构如上图所示

  1. Android Applications: 基于蓝牙APIs开发的各种蓝牙功能的应用,这里列出了五个主要功能的app
  • Setting App: 控制蓝牙开关状态和系统操作的应用
  • Media App: 通过蓝牙设备播放音乐,并能控制远端的播放,暂停,上一曲,下一曲等逻辑
  • Bluetooth Share App: 通过蓝牙的分享功能,传递文件到对端手机
  • Phone App: 通过蓝牙使用电话功能,并可以和对端设备同步联系人等
  • Ble App: 低功耗蓝牙app,可以和ble的设备进行数据的同步控制等,如手环,手表的步数同步
  1. Bluetooth Services: 蓝牙进程的上层核心处理Service,实现对app逻辑调用的封装,向下传递给Bluetooth HAL,同时接收HAL的消息,数据回调,返回给上层app
  1. Bluetooth HAL: bluedroid的核心代码,实现了蓝牙底层多种protocol,并对数据进行分段和重组,通过主机控制接口传给蓝牙驱动
  1. UART Driver: 手机上常用的蓝牙物理传输通路,是一种通用串行数据总线,用于异步通信,该总线双向通信,可以实现全双工传输和接收
  1. Bluetooth Chipset: 蓝牙芯片,内部的链路管理器,基带管理器以及射频系统负责最终信道编码,packet的加解密以及无线电的传输与接收。

二. 关键类和接口

BluetoothAdapter: 本地蓝牙适配器,是所有蓝牙交互的入口点。借助该类,可以发现其他蓝牙设备、查询已绑定(已配对)设备的列表、使用已知的 MAC 地址实例化BluetoothDevice,以及通过创建 BluetoothServerSocket侦听来自其他设备的通信。

BluetoothDevice: 表示远程蓝牙设备。借助该类,可以通过 BluetoothSocket 请求与某个远程设备建立连接,或查询有关该设备的信息,例如设备的名称、地址、类和绑定状态等。

BluetoothSocket: 表示蓝牙套接字接口(类似于 TCP Socket),允许应用使用 InputStream 和 OutputStream 与其他蓝牙设备交换数据。

BluetoothClass: 描述蓝牙设备的一般特征和功能。这是一组只读属性,用于定义设备的类和服务,提供了大部分关于蓝牙类型的有用信息。

BluetoothHeadset: 耳机支持的蓝牙接口类,同时提供了对于耳机和免提控制的功能,通过binder调用

和HeadsetService进行通信。

BluetoothA2dp: 定义如何使用蓝牙高级音频传输配置文件 (A2DP),通过蓝牙连接将高质量音频从一个设备流式传输至另一个设备,通过binder调用和A2dpService进行通信。

BluetoothPbap: 通讯录支持的蓝牙接口类,提供了对于蓝牙通讯录的功能支持,通过binder调用和PbapService进行通信。

BluetoothHidHost: 蓝牙输入设备控制支持的接口类,提供了对于远端input device的支持,通过binder调用和HidHostService进行通信。

BluetoothHearingAid: 蓝牙助听器设备控制支持的接口类,提供了对于远端助听器的音量调节支持,通过binder调用和HearingAidService进行通信。

BluetoothGatt: 蓝牙ble设备center端控制支持的接口类,提供了对于peripheral设备的连接,控制支持,通过binder调用和GattService进行通信。

BluetoothGattServer: 蓝牙ble设备peripheral端支持的接口类,提供了对于peripheral设备的功能模拟,如Service和Characteristic的创建,通过binder调用和GattService进行通信。

三. Android 蓝牙的工作流程

  1. 蓝牙扫描的流程

蓝牙扫描流程时序图如上

  1. 从系统设置的蓝牙选项中,调用startDiscovery发起扫描
frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java
public boolean startDiscovery() {
    // ...
    try {
        mServiceLock.readLock().lock();
        if (mService != null) {
            return mService.startDiscovery(getOpPackageName());
  1. 通过aidl调用到蓝牙进程的核心处理类AdapterService,然后通过JNI调用native方法 startDiscoveryNative
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterService.java
synchronized (mDiscoveringPackages) {
    mDiscoveringPackages.add(new DiscoveringPackage(callingPackage, permission));
}
return startDiscoveryNative();
  1. 在JNI类中,调用了下面的方法,sBluetoothInterface->start_discovery,这里我们看下sBluetoothInterface是如何被赋值
packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
static jboolean startDiscoveryNative(JNIEnv* env, jobject obj) {
  // ...
  int ret = sBluetoothInterface->start_discovery();
  return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
if (hal_util_load_bt_library((bt_interface_t const**)&sBluetoothInterface)) {
    ALOGE("No Bluetooth Library found");
  }
}
int hal_util_load_bt_library(const bt_interface_t** interface) {
  // bluetoothInterface
  const char* sym = BLUETOOTH_INTERFACE_STRING;
  bt_interface_t* itf = nullptr;
  // The library name is not set by default, so the preset library name is used.
  char path[PROPERTY_VALUE_MAX] = "";
  // #define PROPERTY_BT_LIBRARY_NAME "ro.bluetooth.library_name" 
 //#define DEFAULT_BT_LIBRARY_NAME "libbluetooth.so"
  property_get(PROPERTY_BT_LIBRARY_NAME, path, DEFAULT_BT_LIBRARY_NAME);
  //打开对应的library
  void* handle = dlopen(path, RTLD_NOW);
  if (!handle) {
    const char* err_str = dlerror();
    LOG(ERROR) << __func__ << ": failed to load Bluetooth library, error="
               << (err_str ? err_str : "error unknown");
    goto error;
  }
  // Get the address of the bt_interface_t.
  itf = (bt_interface_t*)dlsym(handle, sym);
  if (!itf) {
    LOG(ERROR) << __func__ << ": failed to load symbol from Bluetooth library "
               << sym;
    goto error;
  }
  // Success.
  LOG(INFO) << __func__ << " loaded HAL: btinterface=" << itf
            << ", handle=" << handle;
  *interface = itf;
  return 0;
error:
  *interface = NULL;
  if (handle) dlclose(handle);
  return -EINVAL;
}

jni lib被load的时候,在classInitNative中会调用hal_util_load_bt_library,这里的参数就是上面的sBluetoothInterface

在hal_util_load_bt_library中,首选会去查找 “ro.bluetooth.library_name”的prop是否有library,如果有,则通过dlopen打开对应的library,否则使用默认的library,这里以darwin为例,使用的是高通的蓝牙协议栈库

darwin:/ # getprop | grep "ro.bluetooth.library_name"
[ro.bluetooth.library_name]: [libbluetooth_qti.so]

然后通过dlsym返回一个bluetoothInterface的地址,并赋值给interface

所以上面的sBluetoothInterface->start_discovery最终调用到蓝牙协议栈的逻辑中

  1. 进入到协议栈代码中,可以看到bt_interface_t的结构体在下面被初始化,继续往下走,调用btif_dm_start_discovery
vendor/qcom/opensource/commonsys/system/bt/btif/src/bluetooth.cc
EXPORT_SYMBOL bt_interface_t bluetoothInterface = {
    sizeof(bluetoothInterface),
    // ...
    start_discovery,
    cancel_discovery,
    create_bond,
    create_bond_out_of_band,
    remove_bond,
    // ...
};
static int start_discovery(void) {
  /* sanity check */
  if (interface_ready() == false) return BT_STATUS_NOT_READY;
  return btif_dm_start_discovery();
}
  1. 在bitf_dm_start_discovery中,对扫描的一些参数进行了预设,如duration,默认为10,所以蓝牙的默认扫描周期为1.28 x 10, 也就是我们常说的12.8s,然后向下继续调用BTA_DMSearch
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_dm.cc
// #define BTIF_DM_DEFAULT_INQ_MAX_DURATION 10
inq_params.duration = BTIF_DM_DEFAULT_INQ_MAX_DURATION;
// #define BTIF_DM_DEFAULT_INQ_MAX_RESULTS 0
inq_params.max_resps = BTIF_DM_DEFAULT_INQ_MAX_RESULTS;
inq_params.report_dup = true;
// ...
/* find nearby devices */
BTA_DmSearch(&inq_params, services, bte_search_devices_evt);
  1. 这里新建了一个search msg,将对应的event, 扫描参数以及callback进行赋值,然后通过bta_sys_send_msg (GKI msg)发送了出去
vendor/qcom/opensource/commonsys/system/bt/bta/dm/bta_dm_api.cc
void BTA_DmSearch(tBTA_DM_INQ* p_dm_inq, tBTA_SERVICE_MASK services,
                  tBTA_DM_SEARCH_CBACK* p_cback) {
  p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;
  memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));
  p_msg->services = services;
  p_msg->p_cback = p_cback;
  p_msg->rs_res = BTA_DM_RS_NONE;
  bta_sys_sendmsg(p_msg);
}
  1. 在bta_dm_search_start中,将传过来的service和callback进行保存,然后将bta_dm_inq_results_cb和bta_dm_inq_cmpl_cb这两个callback函数作为参数,继续调用BTM_StartInquiry
vendor/qcom/opensource/commonsys/system/bt/bta/dm/bta_dm_act.cc
void bta_dm_search_start(tBTA_DM_MSG* p_data) {
  // ...
  BTM_ClearInqDb(NULL);
  /* save search params */
  bta_dm_search_cb.p_search_cback = p_data->search.p_cback;
  bta_dm_search_cb.services = p_data->search.services;
  osi_free_and_reset((void**)&bta_dm_search_cb.p_srvc_uuid);
  if ((bta_dm_search_cb.num_uuid = p_data->search.num_uuid) != 0 &&
      p_data->search.p_uuid != NULL) {
    bta_dm_search_cb.p_srvc_uuid = (Uuid*)osi_malloc(len);
    *bta_dm_search_cb.p_srvc_uuid = *p_data->search.p_uuid;
  }
  result.status = BTM_StartInquiry((tBTM_INQ_PARMS*)&p_data->search.inq_params,
                                   bta_dm_inq_results_cb,
                                   (tBTM_CMPL_CB*)bta_dm_inq_cmpl_cb);
  }
}
  1. 在BTM_StartInquiry中,会对传递过来的callback进行保存,因为是双模蓝牙芯片,支持ble,所以这里会先调用 ble扫描的方法 btm_ble_start_inquiry
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_inq.cc
tBTM_STATUS BTM_StartInquiry(tBTM_INQ_PARMS* p_inqparms,
                             tBTM_INQ_RESULTS_CB* p_results_cb,
                             tBTM_CMPL_CB* p_cmpl_cb) {
if (!controller_get_interface()->supports_ble()) {
  p_inq->inqparms.mode &= ~BTM_BLE_INQUIRY_MASK;
  status = BTM_ILLEGAL_VALUE;
}
/* BLE for now does not support filter condition for inquiry */
else {
  status = btm_ble_start_inquiry(
      (uint8_t)(p_inqparms->mode & BTM_BLE_INQUIRY_MASK),
      p_inqparms->duration);
  }
}
  1. 这里,会通过hci将ble的扫描参数发送给controller,然后调用btm_ble_start_scan
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_ble_gap.cc
if (!BTM_BLE_IS_SCAN_ACTIVE(p_ble_cb->scan_activity)) {
    btm_send_hci_set_scan_params(
        scan_phy, BTM_BLE_SCAN_MODE_ACTI, scan_interval,
        scan_window, btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type, SP_ADV_ALL);
    // ...
    status = btm_ble_start_scan();
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_ble_gap.cc
tBTM_STATUS btm_ble_start_scan(void) {
  tBTM_BLE_INQ_CB* p_inq = &btm_cb.ble_ctr_cb.inq_var;
  /* start scan, disable duplicate filtering */
  btm_send_hci_scan_enable(BTM_BLE_SCAN_ENABLE, p_inq->scan_duplicate_filter);
}
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_ble_gap.cc
void btm_send_hci_scan_enable(uint8_t enable, uint8_t filter_duplicates) {
  if (controller_get_interface()->supports_ble_extended_advertising()) {
    btsnd_hcic_ble_set_extended_scan_enable(enable, filter_duplicates, 0x0000,
                                            0x0000);
  } else {
    btsnd_hcic_ble_set_scan_enable(enable, filter_duplicates);
  }
}

通过以上两个内部调用,将ble scan的参数置为enable,调用hci layer发送command给bluetooth controller开始执行,ble扫描开始启动

  1. 但是我们从设置页面点击扫描之后,不仅可以发现ble设备,也能看到经典蓝牙设备,这说明了BR/EDR的扫描也被执行了,看一下这个是在哪里执行的,回到btm_inq.cc的BTM_StartInquiry函数中,这里调用了btm_set_inq_event_filter,对当前的inquiry event filter进行了设置,然后通过hci command发送给bluetooth controller执行
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_inq.cc
/* Before beginning the inquiry the current filter must be cleared, so
 * initiate the command */
status = btm_set_inq_event_filter(p_inqparms->filter_cond_type,
                                  &p_inqparms->filter_cond);
  1. 当controller执行了event filter之后,通过hci event上报给协议栈,这里在收到HCI_SET_EVENT_FILTER event 后执行 btm_event_filter_complete的回调
vendor/qcom/opensource/commonsys/system/bt/stack/btu/btu_hcif.cc
static void btu_hcif_hdl_command_complete(uint16_t opcode, uint8_t* p,
                                          uint16_t evt_len,
                                          void* p_cplt_cback) {
  switch (opcode) {
    case HCI_SET_EVENT_FILTER:
      btm_event_filter_complete(p);
  1. 回头btm_inq.cc中,当收到event complete回调之后,会正式执行btm_initiate_inquiry操作
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_inq.cc
else /* Initiate the Inquiry or Periodic Inquiry */
{
  p_inq->state = BTM_INQ_ACTIVE_STATE;
  p_inq->inqfilt_active = false;
  btm_initiate_inquiry(p_inq);
}
  1. 发送hci command给bluetooth controller执行BR/EDR扫描
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_inq.cc
btsnd_hcic_inquiry(*lap, p_inqparms->duration, 0);
  1. 蓝牙的扫描是一个异步的过程,当bluetooth controller开始扫描之后,协议栈会收到BTA_DM_BUSY_LEVEL_EVT的event,这里会判断当前level_flag,然后将当前的蓝牙扫描状态通过discovery_state_changed_cb上报给 upper layer
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_dm.cc
case BTA_DM_BUSY_LEVEL_EVT: {
    } else if (p_data->busy_level.level_flags == BTM_BL_INQUIRY_CANCELLED) {
      HAL_CBACK(bt_hal_cbacks, discovery_state_changed_cb,
                BT_DISCOVERY_STOPPED);
  1. 上层收到对应的callback之后,通过广播的形式将当前的扫描状态发送给各个接收者
packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void discovery_state_changed_callback(bt_discovery_state_t state) {
    // ...
    sCallbackEnv->CallVoidMethod(
      sJniCallbacksObj, method_discoveryStateChangeCallback, (jint)state);
}
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/AdapterProperties.java
void discoveryStateChangeCallback(int state) {
    infoLog("Callback:discoveryStateChangeCallback with state:" + state);
    synchronized (mObject) {
        Intent intent;
        if ((state == AbstractionLayer.BT_DISCOVERY_STOPPED) && mDiscovering) {
        // ...
        } else if (state == AbstractionLayer.BT_DISCOVERY_STARTED) {
            // ...
            intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
            mService.sendBroadcast(intent, AdapterService.BLUETOOTH_PERM);
        }
    }
}
  1. 蓝牙的扫描状态也是一样的过程,当bluetooth controller扫描到设备之后,会将对应的设备信息上报
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_dm.cc
/* Callback to notify upper layer of device */
HAL_CBACK(bt_hal_cbacks, device_found_cb, num_properties, properties);
  1. 上层收到扫描到的蓝牙设备信息callback之后,通过广播的形式发送给各个接收者
packages/apps/Bluetooth/jni/com_android_bluetooth_btservice_AdapterService.cpp
static void device_found_callback(int num_properties,
                                  bt_property_t* properties) {
 // ...
  sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_deviceFoundCallback,
                               addr.get());
}
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/RemoteDevices.java
void deviceFoundCallback(byte[] address) {
   // ...
    Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    intent.putExtra(BluetoothDevice.EXTRA_CLASS,
            new BluetoothClass(deviceProp.mBluetoothClass));
    intent.putExtra(BluetoothDevice.EXTRA_RSSI, deviceProp.mRssi);
    intent.putExtra(BluetoothDevice.EXTRA_NAME, deviceProp.mName);
}
  1. ble设备首次连接流程

ble设备时序图如上, 从时序图中可以看出,ble设备的首次连接其实分两个步骤:

  • ble app 的register
  • ble 设备的连接

下面介绍下android代码的调用流程

  1. 从ble扫描得到的BluetoothDevice中选择想要连接的设备,调用connectGatt,这里会new一个BluetoothGatt的对象,然后调用gatt.connect
frameworks/base/core/java/android/bluetooth/BluetoothDevice.java
public BluetoothGatt connectGatt(Context context, boolean autoConnect,
        BluetoothGattCallback callback, int transport,
        boolean opportunistic, int phy, Handler handler) {
    // ...
    try {
        IBluetoothGatt iGatt = managerService.getBluetoothGatt();
        // ...
        BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, opportunistic, phy);
        gatt.connect(autoConnect, callback, handler);
        return gatt;
}
  1. 在BluetoothGatt的connect方法中,会针对每个注册的app生成一个随机的uuid,然后通过aidl调用registerClient进入到蓝牙进程的GattService中
frameworks/base/core/java/android/bluetooth/BluetoothGatt.java
private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
    // ...
    UUID uuid = UUID.randomUUID();
    try {
        mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
    }
  1. GattService中,会用map将uuid和传递过来的callback进行保存, 后续协议栈上来的callback会根据map做分发,然后继续向下调用native方法 gattClientRegsiterAppNative
packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
void registerClient(UUID uuid, IBluetoothGattCallback callback) {
    // ...
    mClientMap.add(uuid, null, callback, null, this);
    gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits());
}
  1. jni方法中,将高位的uuid和低位的uuid重新合并,调用到蓝牙协议栈
packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
static void gattClientRegisterAppNative(JNIEnv* env, jobject object,
                                        jlong app_uuid_lsb,
                                        jlong app_uuid_msb) {
  // ...
  Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
  sGattIf->client->register_client(uuid);
}
  1. 在btif_gattc_regsiter_app中,这里在jni的thread中新建一个task去处理app的register
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_gatt_client.cc
bt_status_t btif_gattc_register_app(const Uuid& uuid) {
   return do_in_jni_thread(Bind(
      [](const Uuid& uuid) {
        BTA_GATTC_AppRegister(
            bta_gattc_cback,
            base::Bind(
                [](const Uuid& uuid, uint8_t client_id, uint8_t status) {
                  do_in_jni_thread(Bind(
                      [](const Uuid& uuid, uint8_t client_id, uint8_t status) {
                        HAL_CBACK(bt_gatt_callbacks, client->register_client_cb,
                                  status, client_id, uuid);
                      },
                    }
  1. 在bta的thread中,新建一个task,调用bta_gattc_regsiter
vendor/qcom/opensource/commonsys/system/bt/bta/gatt/bta_gattc_api.cc
void BTA_GATTC_AppRegister(tBTA_GATTC_CBACK* p_client_cb,
                           BtaAppRegisterCallback cb) {
  // ...
  do_in_bta_thread(FROM_HERE, base::Bind(&bta_gattc_register, Uuid::GetRandom(),
                                         p_client_cb, std::move(cb)));
}
  1. 在bta_gattc_register方法中,for循环遍历最多支持的32个client application,如果当前不在使用过程中,则进行GATT_Register的调用,我们先继续深入到GATT_Register方法中看看做了什么
vendor/qcom/opensource/commonsys/system/bt/bta/gatt/bta_gattc_act.cc
/** Register a GATT client application with BTA */
void bta_gattc_register(const Uuid& app_uuid, tBTA_GATTC_CBACK* p_cback,
                        BtaAppRegisterCallback cb) {
  // ... 
  for (uint8_t i = 0; i < BTA_GATTC_CL_MAX; i++) {
    if (!bta_gattc_cb.cl_rcb[i].in_use) {
      if ((bta_gattc_cb.cl_rcb[i].client_if =
               GATT_Register(app_uuid, &bta_gattc_cl_cback)) == 0) {
       // ....
}
  1. 这里我们可以看到,会从最多32个app client的register中遍历寻找还没有被使用的,对当前的client进行注册,返回对应的gatt_if
vendor/qcom/opensource/commonsys/system/bt/stack/gatt/gatt_api.cc
tGATT_IF GATT_Register(const Uuid& app_uuid128, tGATT_CBACK* p_cb_info) {
  // ...
  for (i_gatt_if = 0, p_reg = gatt_cb.cl_rcb; i_gatt_if < GATT_MAX_APPS;
       i_gatt_if++, p_reg++) {
    if (!p_reg->in_use) {
      memset(p_reg, 0, sizeof(tGATT_REG));
      i_gatt_if++; /* one based number */
      p_reg->app_uuid128 = app_uuid128;
      gatt_if = p_reg->gatt_if = (tGATT_IF)i_gatt_if;
      return gatt_if;
    }
  }
  1. 回到刚刚的bta_gattc_register中,这里因为返回了可用的gatt_if,所以走到else的逻辑里面,会在bta thread中新建一个task处理bta_gattc_start_if,然后调用GATT_StartIf,结束之后,如果cb != null,将会进行register callback的回调
vendor/qcom/opensource/commonsys/system/bt/bta/gatt/bta_gattc_act.cc
/** Register a GATT client application with BTA */
void bta_gattc_register(const Uuid& app_uuid, tBTA_GATTC_CBACK* p_cback,
                        BtaAppRegisterCallback cb) {
   // ...
    if (!bta_gattc_cb.cl_rcb[i].in_use) {
      // ...
      } else {
        bta_gattc_cb.cl_rcb[i].in_use = true;
        bta_gattc_cb.cl_rcb[i].p_cback = p_cback;
        bta_gattc_cb.cl_rcb[i].app_uuid = app_uuid;
        /* BTA use the same client interface as BTE GATT statck */
        client_if = bta_gattc_cb.cl_rcb[i].client_if;
        do_in_bta_thread(FROM_HERE,
                          base::Bind(&bta_gattc_start_if, client_if));
        status = GATT_SUCCESS;
      }
    }
  }
}
if (!cb.is_null()) cb.Run(client_if, status);
  1. 上一步中,cb.run会回调bt_status_btif_gattc_regsiter_app中的task,然后通过 register_client_cb 向jni层传递register app的结果和分配的client id
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_gatt_client.cc
bt_status_t btif_gattc_register_app(const Uuid& uuid) {
    HAL_CBACK(bt_gatt_callbacks, client->register_client_cb,
            status, client_id, uuid);
}
  1. Jni层通过 onClientRegistered继续上报
packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
void btgattc_register_app_cb(int status, int clientIf, const Uuid& app_uuid) {
  // ...
  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
                               clientIf, UUID_PARAMS(app_uuid));
}
  1. GattService收到jni上报的回调之后,通过上传的uuid,从clientmap里去寻找对应的app,如果找到了,则继续上报给framework
packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb)
        throws RemoteException {
    UUID uuid = new UUID(uuidMsb, uuidLsb);
    ClientMap.App app = mClientMap.getByUuid(uuid);
    if (app != null) {
        // ...     
        app.callback.onClientRegistered(status, clientIf);
    }
}
  1. BluetoothGatt收到回到之后,会调用clientConnect正式发起ble的连接
frameworks/base/core/java/android/bluetooth/BluetoothGatt.java
public void onClientRegistered(int status, int clientIf) {
    // ...
    mClientIf = clientIf;
    try {
        mService.clientConnect(mClientIf, mDevice.getAddress(),
                !mAutoConnect, mTransport, mOpportunistic,
                mPhy); // autoConnect is inverse of "isDirect"
    } catch (RemoteException e) {
        Log.e(TAG, "", e);
    }
}
  1. GattService中,调用jni方法 gattClientConnectNative
packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
void clientConnect(int clientIf, String address, boolean isDirect, int transport,
        boolean opportunistic, int phy) {
    // ...
    gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic, phy);
}
  1. client->connect继续往协议栈调用
packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
static void gattClientConnectNative(JNIEnv* env, jobject object, jint clientif,
                                    jstring address, jboolean isDirect,
                                    jint transport, jboolean opportunistic,
                                    jint initiating_phys) {
  // ...
  sGattIf->client->connect(clientif, str2addr(env, address), isDirect,
                           transport, opportunistic, initiating_phys);
}
  1. 在bitf_gattc_open_impl中,调用bta的方法 BTA_GATTC_Open
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_gatt_client.cc
void btif_gattc_open_impl(int client_if, RawAddress address, bool is_direct,
                          int transport_p, bool opportunistic,
                          int initiating_phys) {
  // ...
  BTA_GATTC_Open(client_if, address, is_direct, transport, opportunistic,
                 initiating_phys);
}
  1. 这里,封装了BTA_GATTC_API_OPEN_EVT的消息,然后通过bta_sys_sendmsg发送给bta_gattc_act.cc
vendor/qcom/opensource/commonsys/system/bt/bta/gatt/bta_gattc_api.cc
void BTA_GATTC_Open(tGATT_IF client_if, const RawAddress& remote_bda,
                    bool is_direct, tGATT_TRANSPORT transport,
                    bool opportunistic, uint8_t initiating_phys) {
  p_buf->hdr.event = BTA_GATTC_API_OPEN_EVT;
  p_buf->client_if = client_if;
  p_buf->is_direct = is_direct;
  // ...
  bta_sys_sendmsg(p_buf);
}
  1. 通过GATT_Connect open一个ble connection
vendor/qcom/opensource/commonsys/system/bt/bta/gatt/bta_gattc_act.cc
void bta_gattc_open(tBTA_GATTC_CLCB* p_clcb, tBTA_GATTC_DATA* p_data) {
  /* open/hold a connection */
  if (!GATT_Connect(p_clcb->p_rcb->client_if, p_data->api_conn.remote_bda, true,
                    p_data->api_conn.transport, p_data->api_conn.opportunistic,
                    p_data->api_conn.initiating_phys)) {
}
  1. 这里会根据is_direct的值来做判断,如果为true,则调用gatt_act_connect发起直接连接,如果为false(远距离断开自动重连的场景),则会判断当前的连接的外围设备是否是一个已知的,如果不是,则直接返回,否则进行后台的被动连接,我们这里场景是主动连接,所以is_direct为true,走gatt_act_connect的调用
vendor/qcom/opensource/commonsys/system/bt/stack/gatt/gatt_api.cc
bool GATT_Connect(tGATT_IF gatt_if, const RawAddress& bd_addr, bool is_direct,
                  tBT_TRANSPORT transport, bool opportunistic,
                  uint8_t initiating_phys) {
  if (is_direct) {
    ret = gatt_act_connect(p_reg, bd_addr, transport, initiating_phys);
  } else {
    if (!BTM_BackgroundConnectAddressKnown(bd_addr)) {
      //  RPA can rotate, causing address to "expire" in the background
      //  connection list. RPA is allowed for direct connect, as such request
      //  times out after 30 seconds
      LOG(INFO) << "Can't add RPA to background connection.";
      ret = true;
    } else {
      ret = connection_manager::background_connect_add(gatt_if, bd_addr);
    }
  }
}
  1. 通过connection_manager的direct_connect_add方法将需要连接的对端设备address加入到直接连接列表
vendor/qcom/opensource/commonsys/system/bt/stack/gatt/gatt_main.cc
bool gatt_connect(const RawAddress& rem_bda, tGATT_TCB* p_tcb,
                  tBT_TRANSPORT transport, uint8_t initiating_phys,
                  tGATT_IF gatt_if) {
  p_tcb->att_lcid = L2CAP_ATT_CID;
  return connection_manager::direct_connect_add(gatt_if, rem_bda);
}
  1. 这里会判断对端的mac地址是否在白名单列表里,如果不在,则加入到白名单
vendor/qcom/opensource/commonsys/system/bt/stack/gatt/connection_manager.cc
bool direct_connect_add(uint8_t app_id, const RawAddress& address) {
  // ...
  if (!in_white_list) {
    if (!BTM_WhiteListAdd(address)) {
  // ...
}
  1. 在加入到白名单列表的操作中,首先会判断当期的白名单列表是否已经满了,如果没满,则将address加入到controller的白名单列表,然后开始自动连接, 调用btm_send_hci_create_connection发送hci command到controller执行
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_ble_bgconn.cc
bool BTM_WhiteListAdd(const RawAddress& address) {
  if (background_connections_count() ==
      controller_get_interface()->get_ble_white_list_size()) {
    BTM_TRACE_ERROR("%s Whitelist full, unable to add device", __func__);
    return false;
  }
  btm_add_dev_to_controller(true, address);
  btm_ble_resume_bg_conn();
  return true;
}
vendor/qcom/opensource/commonsys/system/bt/stack/btm/btm_ble_bgconn.cc
bool btm_ble_start_auto_conn() {
  // ...
  btm_send_hci_create_connection(
      scan_int,                       /* uint16_t scan_int      */
      scan_win,                       /* uint16_t scan_win      */
      0x01,                           /* uint8_t white_list     */
      peer_addr_type,                 /* uint8_t addr_type_peer */
      RawAddress::kEmpty,             /* BD_ADDR bda_peer     */
      own_addr_type,                  /* uint8_t addr_type_own */
      BTM_BLE_CONN_INT_MIN_DEF,       /* uint16_t conn_int_min  */
      BTM_BLE_CONN_INT_MAX_DEF,       /* uint16_t conn_int_max  */
      BTM_BLE_CONN_SLAVE_LATENCY_DEF, /* uint16_t conn_latency  */
      BTM_BLE_CONN_TIMEOUT_DEF,       /* uint16_t conn_timeout  */
      0,                              /* uint16_t min_len       */
      0,                              /* uint16_t max_len       */
      phy);
}
  1. 当ACL链路建立上之后,ble连接成功,controller会反馈给协议栈, 这里会收到上报的BTA_GATTC_OPEN_EVT,然后通过open_cb上报给jni
vendor/qcom/opensource/commonsys/system/bt/btif/src/btif_gatt_client.cc
case BTA_GATTC_OPEN_EVT: {
  HAL_CBACK(bt_gatt_callbacks, client->open_cb, p_data->open.conn_id,
            p_data->open.status, p_data->open.client_if,
            p_data->open.remote_bda);
}
  1. jni收到回调找到对应的java层方法onConnected继续上报
packages/apps/Bluetooth/jni/com_android_bluetooth_gatt.cpp
void btgattc_open_cb(int conn_id, int status, int clientIf,
                     const RawAddress& bda) {
  // ...
  sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onConnected, clientIf,
                               conn_id, status, address.get());
}
  1. 如果ble连接成功,会把对端的address和对应的client id添加到clientMap中,然后再从之前注册的clientmap中取出对应的app,通过onClientConnectionState通知给app ble连接成功的状态
packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java
void onConnected(int clientIf, int connId, int status, String address) throws RemoteException {
    // ... 
    if (status == 0) {
        mClientMap.addConnection(clientIf, connId, address);
    }
    ClientMap.App app = mClientMap.getById(clientIf);
    if (app != null) {
        app.callback.onClientConnectionState(status, clientIf,
                (status == BluetoothGatt.GATT_SUCCESS), address);
    }
}

第三章 Android R 蓝牙新增功能

随着google发布了新一代的Android R操作系统,下面一起来看下蓝牙模块新增了哪些功能

一. PBAP喜爱分组同步的功能

这个Favorite分组,其实在Pbap1.2.1当中就已经定义,但是在之前的Android版本中,一直没有实现。如果之前手机和车载设备进行Pbap的同步,只能从手机端拉取未接通话记录,来电记录,去电记录,合并通话记录(包含来电,去电,未接通话记录)以及联系人通讯录。

// missed call history
private static final String MCH = "mch";
// incoming call history
private static final String ICH = "ich";
// outgoing call history
private static final String OCH = "och";
// combined call history
private static final String CCH = "cch";
// phone book
private static final String PB = "pb";

在Android R上,它终于来了。有了这个同步联系人分组之后,用户只需要将常用联系人加入到favorite分组,等下次车载同步时,只同步favorite中保存的联系人即可。

// favorites
private static final String FAV = "fav";
private static final String FAV_PATH = "/telecom/fav";

二. 蓝牙keyStore机制

Android keyStore system,可以让开发者在容器中存储加密密钥,从而提高从设备中提取密钥的难度。在密钥进入密钥库后,可以将它们用于加密操作,而密钥材料仍不可导出。此外,它提供了密钥使用的时间和方式限制措施,例如要求进行用户身份验证才能使用密钥,或者限制为只能在某些加密模式中使用。

Android R之前的版本上,蓝牙的已连接设备配置文件bt_config.conf是以明文的方式保存在/data/misc/bluedroid/目录下,如果遇到外部攻击导致被bt_config.conf窃取,根据link key id,就可以通过嗅探软件来监听和对端蓝牙的所有通信,包括电话音乐都一些隐私的数据,这非常的危险。

在Android R上,google引入了KeyStore system,来对bt_config.conf中的key进行加解密,不再以明文的方式直接呈现,大大的提高了安全性。

packages/apps/Bluetooth/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java
   
    private @Nullable String encrypt(String data) {
        BluetoothKeystoreProto.EncryptedData protobuf;
        byte[] outputBytes;
        String outputBase64 = null;
        try {
            if (data == null) {
                errorLog("encrypt: data is null");
                return outputBase64;
            }
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            SecretKey secretKeyReference = getOrCreateSecretKey();
            if (secretKeyReference != null) {
                cipher.init(Cipher.ENCRYPT_MODE, secretKeyReference);
                protobuf = BluetoothKeystoreProto.EncryptedData.newBuilder()
                    .setEncryptedData(ByteString.copyFrom(cipher.doFinal(data.getBytes())))
                    .setInitVector(ByteString.copyFrom(cipher.getIV())).build();
                outputBytes = protobuf.toByteArray();
                if (outputBytes == null) {
                    errorLog("encrypt: Failed to serialize EncryptedData protobuf.");
                    return outputBase64;
                }
                outputBase64 = mEncoder.encodeToString(outputBytes);
    }
packages/apps/Bluetooth/src/com/android/bluetooth/btservice/bluetoothKeystore/BluetoothKeystoreService.java
private @Nullable String decrypt(String encryptedDataBase64) {
        BluetoothKeystoreProto.EncryptedData protobuf;
        byte[] encryptedDataBytes;
        byte[] decryptedDataBytes;
        String output = null;
        try {
            if (encryptedDataBase64 == null) {
                errorLog("decrypt: encryptedDataBase64 is null");
                return output;
            }
            encryptedDataBytes = mDecoder.decode(encryptedDataBase64);
            protobuf = BluetoothKeystoreProto.EncryptedData.parser().parseFrom(encryptedDataBytes);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            GCMParameterSpec spec =
                    new GCMParameterSpec(GCM_TAG_LENGTH, protobuf.getInitVector().toByteArray());
            SecretKey secretKeyReference = getOrCreateSecretKey();
            if (secretKeyReference != null) {
                cipher.init(Cipher.DECRYPT_MODE, secretKeyReference, spec);
                decryptedDataBytes = cipher.doFinal(protobuf.getEncryptedData().toByteArray());
                output = new String(decryptedDataBytes);
    }

三. 蓝牙加入apex升级

为了解决Android系统碎片化严重的问题, google在Android 10 中引入的一种容器格式 Android Pony EXpress (APEX),用于在较低级别系统模块的安装流程中使用。此格式可帮助更新不适用于标准 Android 应用模型的系统组件。

Android R上,将蓝牙模块放到了apex的升级包里面,这有助于蓝牙模块的需求更新可以在更快的时间给到相关app开发者进行相关的需求适配和改动。

packages/apps/Bluetooth/Android.bp
 
required: ["libbluetooth"],
    apex_available: [        "//apex_available:platform",        "com.android.bluetooth.updatable",    ],

四. Avrcp Controller Cover Art & Play Mode(Automotive)

Cover Art是歌曲的专辑封面,在早期的android automotive版本中,avrcp并没有实现cover art的同步,导致当手机和车载连接听音乐时,车载拿不到对端播放歌曲的专辑封面。

在最新的Android R上, google实现了cover art的传递,让更多的歌曲信息可以通过蓝牙实时同步。

主要实现类:

packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtManager.java

packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtProvider.java

packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpCoverArtStorage.java

packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpBipClient.java

播放模式是音乐播放器播放歌曲的方式,一般分为单曲循环,随机播放和顺序播放三种模式。早先的android automotive版本不支持单曲循环和随机模式的设置。在Android R上,google实现这两种模式的avrcp控制,让用户可以从车载端直接切换手机音乐的播放模式。

packages/apps/Bluetooth/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java
static final int MSG_AVRCP_SET_SHUFFLE = 303;
static final int MSG_AVRCP_SET_REPEAT = 304;
private void setRepeat(int repeatMode) {
    mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
        new byte[]{PlayerApplicationSettings.REPEAT_STATUS}, new byte[]{
            PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
                PlayerApplicationSettings.REPEAT_STATUS, repeatMode)});
}
private void setShuffle(int shuffleMode) {
    mService.setPlayerApplicationSettingValuesNative(mDeviceAddress, (byte) 1,
        new byte[]{PlayerApplicationSettings.SHUFFLE_STATUS}, new byte[]{
            PlayerApplicationSettings.mapAvrcpPlayerSettingstoBTattribVal(
                PlayerApplicationSettings.SHUFFLE_STATUS, shuffleMode)});
}

第四章 蓝牙兼容性问题分析

一. A品牌汽车在和手机连接的情况下,从音乐app切换到抖音,抖音内容正常播放,耳机无声音

问题分析

  • 首先确认音频流是否正确的下发,从下面的avdtp signaling的最后log可以看出,avdtp已经正常的start,audio stream下发ok

  • 确认avrcp的状态是否ok,从avdtp stream start的时间节点往下看车载的avrcp状态,avdtp stream start之后,手机的play status状态转换为playing,这里ok,但是9s之后,play status被设置成了paused,在手机端没有任何暂停播放动作的前提下,这个状态是不对的

  • 结合adb log进一步确认,发现当music为active时,mediaController上报了错误的playState 2,导致车载收到avrcp paused指令,忽略了后续的接收到的avdtp stream,从而导致车载无声音
// final static byte PLAYSTATUS_STOPPED = 0;
// final static byte PLAYSTATUS_PLAYING = 1;
// final static byte PLAYSTATUS_PAUSED = 2;
31656 31770 V Avrcp_ext: newPlayStatus:2mReportedPlayStatus:-1
31770 V Avrcp_ext: updatePlaybackState, state: PlaybackState {state=2, position=8406, buffered position=0, speed=1.0, updated=10990642, actions=695, custom actions=[], active item       id=-1, error=null} device: null

问题解决

通过mac地址过滤的方式将该代表该车型的高三位mac地址加入到playstatus更新的黑名单,针对该车型,当请求playstatus时,需要同时判断A2dp的playstate,和musicActive的状态,保证playstatus更新的正确性。

private static final String playerStateUpdateBlackListedAddr[] = {
     "BC:30:7E", //bc-30-7e-5e-f6-27, Name: Porsche BT 0310; bc-30-7e-8c-22-cb, Name: Audi MMI 1193
     "2C:DC:AD", //2C-DC-AD-BB-2F-25, Name: PORSCHE
     "00:1E:43", //00-1e-43-14-f0-68, Name: Audi MMI 4365
     "9C:DF:03", //9C:DF:03:D3:C0:17, Name: Benz S600L
     "00:0A:08",  //00:0A:08:51:1E:E7, Name: BMW530
// ...

二. B品牌耳机和手机连接之后,播放音乐无声

问题分析

  • 首先确认下音频流下发是否ok,查看avdtp stream的状态,发现只有open,并没有start,证明audio实际上没有开始下发音频,所以猜测audio select device或者start output stream可能出问题了

  • 查询相关的日志,发现如下异常log:
858 31680 E audio_hw_primary: out_set_parameters: A2DP profile is not ready,ignoring routing request 
858 22492 D audio_hw_primary: start_output_stream: enter: stream(0xe9286800)usecase(0: deep-buffer-playback) devices(0x80) is_haptic_usecase(0), regdump_on(0)
858 22492 E audio_hw_primary: start_output_stream: A2DP profile is not ready, return error

audio在start_output_stream时,认为A2dp Profile一直没有ready,导致切路由失败

8709  8893 I AudioManager: In startbluetoothSco(), calling application: com.tencent.mm
1453  3033 I AS.BtHelper: In connectBluetoothScoAudioHelper(), calling startScoUsingVirtualVoiceCallEx()                                                                              
1453  3033 D BluetoothHeadset: startScoUsingVirtualVoiceCall()
13084 13201 D HeadsetService: startScoUsingVirtualVoiceCall, Calling : 8709
13084 13201 I HeadsetService: startScoUsingVirtualVoiceCall: uid/pid=1000/1453
  • 顺着audio切路由失败往前继续查询相关的log,发现在a2dp连接上之后,微信主动去调用startScoUsingVirtualVoiceCall,这是一个异常的调用。正常的逻辑,此函数只会在微信拨打语音电话时被触发去创建sco链路,然后等语音通话结束,微信会去调用stopScoUsingVirtualVoiceCall释放sco链路。

问题解决

通过上述分析,结论是某个版本的微信在监听到Hfp Connected之后,错误的去请求了sco链路,导致了后续的音乐播放因为audio通道被占用无法切换而无声,反馈给微信去更新相关的代码解决。