【Bluedroid】HFP连接流程源码分析(一)

connect

/packages/modules/Bluetooth/system/btif/src/btif_hf_client.cc
static bt_status_t connect(const RawAddress* bd_addr) {
  log::verbose("HFP Client version is  {}", btif_hf_client_version);
  CHECK_BTHF_CLIENT_INIT(); // 检查HFP客户端是否已初始化
  return btif_queue_connect(UUID_SERVCLASS_HF_HANDSFREE, bd_addr, connect_int);
}

connect函数的核心目标是发起一个连接到指定蓝牙设备,该设备支持蓝牙免提协议(HFP, Hands-Free Profile)的,通过调用 btif_queue_connect 来实际发起连接。btif_queue_connect 函数会将连接请求排队,等待蓝牙堆栈的适当时机来处理。

connect_int

/packages/modules/Bluetooth/system/btif/src/btif_hf_client.cc
/*******************************************************************************
 *
 * Function         connect
 *
 * Description     connect to audio gateway
 *
 * Returns         bt_status_t
 *
 ******************************************************************************/
static bt_status_t connect_int(RawAddress* bd_addr, uint16_t uuid) {
  btif_hf_client_cb_t* cb = btif_hf_client_allocate_cb(); // 分配回调函数结构体
  if (cb == NULL) {
    log::error("could not allocate block!");
    return BT_STATUS_BUSY;
  }

  cb->peer_bda = *bd_addr; // 设置蓝牙设备地址
  //  检查是否已经与指定的蓝牙设备建立了连接
  if (is_connected(cb)) return BT_STATUS_BUSY; 

  cb->state = BTHF_CLIENT_CONNECTION_STATE_CONNECTING; // 表示正在尝试连
  cb->peer_bda = *bd_addr;

  /* Open HF connection to remote device and get the relevant handle.
   * The handle is valid until we have called BTA_HfClientClose or the LL
   * has notified us of channel close due to remote closing, error etc.
   */
  return BTA_HfClientOpen(cb->peer_bda, &cb->handle);
}

构建连接音频网关(Audio Gateway)的基础框架,包含了内存分配、错误处理、状态维护以及连接发起等。

BTA_HfClientOpen

/packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_api.cc
/*******************************************************************************
 *
 * Function         BTA_HfClientOpen
 *
 * Description      Opens up a RF connection to the remote device and
 *                  subsequently set it up for a HF SLC
 *
 * Returns          bt_status_t
 *
 ******************************************************************************/
bt_status_t BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
  log::verbose("");
  tBTA_HF_CLIENT_API_OPEN* p_buf =
      (tBTA_HF_CLIENT_API_OPEN*)osi_malloc(sizeof(tBTA_HF_CLIENT_API_OPEN));

  if (!bta_hf_client_allocate_handle(bd_addr, p_handle)) { // 尝试为新的连接分配一个句柄
    log::error("could not allocate handle");
    return BT_STATUS_FAIL;
  }

  p_buf->hdr.event = BTA_HF_CLIENT_API_OPEN_EVT; // 打开HF客户端连接的事件
  p_buf->hdr.layer_specific = *p_handle;
  p_buf->bd_addr = bd_addr;

  bta_sys_sendmsg(p_buf);
  return BT_STATUS_SUCCESS;
}

打开与远程设备的射频(RF)连接,并为蓝牙免提(Hands-Free,HF)语音链路控制(SLC)进行设置。

bta_sys_sendmsg(BTA_HF_CLIENT_API_OPEN_EVT)

来到了详细的消息发送事件,前面多次有展开过,不再详解。

HF Client使能的时候调用bta_sys_register注册其事件处理函数。

HF Client的事件最终都会调用到bta_hf_client_hdl_event进行处理。

bta_hf_client_hdl_event

/packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_main.cc
/*******************************************************************************
 *
 * Function         bta_hf_client_hdl_event
 *
 * Description      Data HF Client main event handling function.
 *
 *
 * Returns          bool
 *
 ******************************************************************************/
bool bta_hf_client_hdl_event(const BT_HDR_RIGID* p_msg) {
  log::verbose("{} (0x{:x})", bta_hf_client_evt_str(p_msg->event),
               p_msg->event);
  bta_hf_client_sm_execute(p_msg->event, (tBTA_HF_CLIENT_DATA*)p_msg);
  return true;
}

将事件传递给状态机进行处理。

bta_hf_client_sm_execute

/packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_main.cc
/*******************************************************************************
 *
 * Function         bta_hf_client_sm_execute
 *
 * Description      State machine event handling function for HF Client
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_sm_execute(uint16_t event, tBTA_HF_CLIENT_DATA* p_data) {
  tBTA_HF_CLIENT_CB* client_cb =
      bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); // 查找对应的HF Client控制块
  if (client_cb == NULL) {
    log::error("cb not found for handle {}", p_data->hdr.layer_specific);
    return;
  }

  tBTA_HF_CLIENT_ST_TBL state_table;
  uint8_t action;
  int i;

  uint16_t in_event = event;
  uint8_t in_state = client_cb->state; // 从控制块中获取当前状态

  /* Ignore displaying of AT results when not connected (Ignored in state
   * machine) */
  if (client_cb->state == BTA_HF_CLIENT_OPEN_ST) {
    log::verbose("HF Client evt : State {} ({}), Event 0x{:04x} ({})",
                 client_cb->state, bta_hf_client_state_str(client_cb->state),
                 event, bta_hf_client_evt_str(event));
  }

  event &= 0x00FF; // 将事件值限制在有效范围内
  if (event >= (BTA_HF_CLIENT_MAX_EVT & 0x00FF)) {
    log::error("HF Client evt out of range, ignoring...");
    return;
  }

  /* look up the state table for the current state */
  state_table = bta_hf_client_st_tbl[client_cb->state]; // 查找状态表  

  /* set next state */
  client_cb->state = state_table[event][BTA_HF_CLIENT_NEXT_STATE]; // 设置下一个状态

  /* execute action functions */
  // 执行action函数
  for (i = 0; i < BTA_HF_CLIENT_ACTIONS; i++) {
    action = state_table[event][i];
    if (action != BTA_HF_CLIENT_IGNORE) {
      (*bta_hf_client_action[action])(p_data);
    } else {
      break;
    }
  }

  /* If the state has changed then notify the app of the corresponding change */
  // 根据新状态向应用程序发送相应的回调
  if (in_state != client_cb->state) {
    log::verbose("notifying state change to {} -> {} device {}", in_state,
                 client_cb->state,
                 ADDRESS_TO_LOGGABLE_STR(client_cb->peer_addr));
    tBTA_HF_CLIENT evt;
    memset(&evt, 0, sizeof(evt));
    evt.bd_addr = client_cb->peer_addr;
    if (client_cb->state == BTA_HF_CLIENT_INIT_ST) {
      bta_hf_client_app_callback(BTA_HF_CLIENT_CLOSE_EVT, &evt);
      log::verbose("marking CB handle {} to false", client_cb->handle);
      client_cb->is_allocated = false;
    } else if (client_cb->state == BTA_HF_CLIENT_OPEN_ST) {
      evt.open.handle = client_cb->handle;
      bta_hf_client_app_callback(BTA_HF_CLIENT_OPEN_EVT, &evt);
    }
  }

  log::verbose("device {} state change: [{}] -> [{}] after Event [{}]",
               ADDRESS_TO_LOGGABLE_STR(client_cb->peer_addr),
               bta_hf_client_state_str(in_state),
               bta_hf_client_state_str(client_cb->state),
               bta_hf_client_evt_str(in_event));
}

负责根据当前状态和接收到的事件来决定HF Client的下一步动作。包括查找对应的状态表、设置下一个状态、执行相应的动作函数,并在状态发生变化时通知应用程序。

bta_hf_client_st_init

进入到init状态下的HF Client状态表bta_hf_client_action

找到对应的处理函数bta_hf_client_start_open。

bta_hf_client_start_open

/packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_act.cc
/*******************************************************************************
 *
 * Function         bta_hf_client_start_open
 *
 * Description      This starts an HF Client open.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_start_open(tBTA_HF_CLIENT_DATA* p_data) {
  tBTA_HF_CLIENT_CB* client_cb =
      bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); // 查找对应的HF Client控制块
  if (client_cb == NULL) {
    log::error("wrong handle to control block {}", p_data->hdr.layer_specific);
    return;
  }

  /* store parameters */
  if (p_data) {
    client_cb->peer_addr = p_data->api_open.bd_addr;
  }

  /* Check if RFCOMM has any incoming connection to avoid collision. */
  RawAddress pending_bd_addr = RawAddress::kEmpty;
  if (PORT_IsOpening(&pending_bd_addr)) { // 检查是否有正在打开的RFCOMM连接
    /* Let the incoming connection goes through.                        */
    /* Issue collision for now.                                         */
    /* We will decide what to do when we find incoming connection later.*/
    bta_hf_client_collision_cback(BTA_SYS_CONN_OPEN, BTA_ID_HS, 0,
                                  client_cb->peer_addr);
    return;
  }

  /* set role */
  client_cb->role = BTA_HF_CLIENT_INT; // 明确客户端在此次连接中的角色

  /* do service search */
  bta_hf_client_do_disc(client_cb);
}

蓝牙HF Client打开流程的起始点,包含查找关键控制块、存储参数、规避RFCOMM连接冲突、设置角色以及启动服务搜索等步骤,为后续顺利建立蓝牙免提连接铺好道路。

bta_hf_client_do_disc

/packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_sdp.cc
/*******************************************************************************
 *
 * Function         bta_hf_client_do_disc
 *
 * Description      Do service discovery.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_do_disc(tBTA_HF_CLIENT_CB* client_cb) {
  Uuid uuid_list[1];
  uint16_t num_uuid = 1;
  uint16_t attr_list[4];
  uint8_t num_attr;
  bool db_inited = false;

  /* initiator; get proto list and features */
  // 根据HF Client的角色(发起者或接受者),设置要搜索的UUID(通用唯一标识符)列表和属性列表
  if (client_cb->role == BTA_HF_CLIENT_INT) {
    attr_list[0] = ATTR_ID_SERVICE_CLASS_ID_LIST;
    attr_list[1] = ATTR_ID_PROTOCOL_DESC_LIST;
    attr_list[2] = ATTR_ID_BT_PROFILE_DESC_LIST;
    attr_list[3] = ATTR_ID_SUPPORTED_FEATURES;
    num_attr = 4;
    uuid_list[0] = Uuid::From16Bit(UUID_SERVCLASS_AG_HANDSFREE); // 设置为HF服务的16位UUID
  }
  /* acceptor; get features */
  else {
    attr_list[0] = ATTR_ID_SERVICE_CLASS_ID_LIST;
    attr_list[1] = ATTR_ID_BT_PROFILE_DESC_LIST;
    attr_list[2] = ATTR_ID_SUPPORTED_FEATURES;
    num_attr = 3;
    uuid_list[0] = Uuid::From16Bit(UUID_SERVCLASS_AG_HANDSFREE);
  }

  /* allocate buffer for sdp database */
  client_cb->p_disc_db = (tSDP_DISCOVERY_DB*)osi_malloc(BT_DEFAULT_BUFFER_SIZE);

  /* set up service discovery database; attr happens to be attr_list len */
  db_inited = get_legacy_stack_sdp_api()->service.SDP_InitDiscoveryDb(
      client_cb->p_disc_db, BT_DEFAULT_BUFFER_SIZE, num_uuid, uuid_list,
      num_attr, attr_list); // 初始化SDP数据库

  if (db_inited) {
    /*Service discovery not initiated */
    // 向远程设备发起服务搜索请求
    // 请求包括远程设备的蓝牙地址、SDP数据库指针、回调函数(bta_hf_client_sdp_cback)以及用户数据(client_cb指针的强制转换)
    db_inited =
        get_legacy_stack_sdp_api()->service.SDP_ServiceSearchAttributeRequest2(
            client_cb->peer_addr, client_cb->p_disc_db, bta_hf_client_sdp_cback,
            (void*)client_cb);
  }

  if (!db_inited) { // 数据库初始化失败或服务搜索请求未能发起
    /*free discover db */
    osi_free_and_reset((void**)&client_cb->p_disc_db);
    /* sent failed event */
    tBTA_HF_CLIENT_DATA msg;
    msg.hdr.layer_specific = client_cb->handle;
    bta_hf_client_sm_execute(BTA_HF_CLIENT_DISC_FAIL_EVT, &msg);
  }
}

执行服务发现过程以查找远程蓝牙设备上的Hands-Free (HF) 服务。

SDP_ServiceSearchAttributeRequest2

SDP(Service Discovery Protocol,服务发现协议)是蓝牙技术中用于发现可用服务和设备功能的一种机制。在蓝牙Hands-Free Audio Gateway(HF AG)和Hands-Free Unit(HFU,通常简称为HF)的交互中,SDP服务搜索属性请求是HF AG用于查找HF设备提供的服务和特性的关键步骤。

SDP的代码分析单独分析,这里直接看对应的HCI log:

SDP Service Search Attribute Request (Hands-Free Audio Gateway)

构建请求:客户端把目标服务的 UUID (Universally Unique Identifier,通用唯一识别码),与免提音频网关相关的 UUID,放入请求数据包中 。列举期望查询的属性 ID,常见属性诸如 ATTR_ID_SERVICE_CLASS_ID_LIST(服务类 ID 列表)、ATTR_ID_PROTOCOL_DESC_LIST(协议描述列表)、ATTR_ID_SUPPORTED_FEATURES(支持的特性)等,这些属性能够勾勒出服务的轮廓。

发送与接收:客户端将这个构建好的请求发送给目标免提音频网关设备。网关收到请求后,依据自身配置和支持的服务,在本地 SDP 数据库里检索对应的信息,把匹配的数据整理好,再回应给客户端。

请求过程

  1. 构建请求:HF AG使用SDP API构建服务搜索属性请求,指定目标设备地址、UUID列表和属性列表。

  2. 发送请求:HF AG通过蓝牙L2CAP(Logical Link Control and Adaptation Protocol Layer)通道将请求发送到HF设备。

  3. 处理响应:HF设备收到请求后,会检查其SDP数据库以找到匹配的服务和属性。然后,会构建一个响应消息,包含请求的属性值,并通过L2CAP通道发送回HF AG。

SDP Service Search Attribute Response (Hands-Free Audio Gateway Generic Audio)

  4. 解析响应:HF AG收到响应后,会解析消息以提取所需的服务和属性信息。这些信息通常用于后续的服务连接和配置过程。

通过此请求,HF AG可以了解HF设备支持的功能和特性,从而确保它们能够正确地建立连接并进行通信。例如,HF AG可能会检查HF设备是否支持免提通话、语音识别、电话簿访问等功能。

注意事项

  • 兼容性:不同的HF设备可能支持不同的服务和特性。因此,HF AG需要确保它能够处理来自不同设备的各种响应。

  • 安全性:SDP请求和响应是通过蓝牙无线传输的,因此可能会受到窃听或篡改的风险。为了增强安全性,可以使用蓝牙安全特性(如加密和认证)来保护这些通信。

  • 性能:SDP请求可能会引入一定的延迟,特别是在搜索大量服务或属性时。因此,在设计系统时需要考虑这一点,并尽可能优化搜索过程以减少延迟。

SDP服务搜索属性请求在蓝牙Hands-Free Audio Gateway和Hands-Free Unit之间的交互中扮演着重要角色。它允许HF AG发现HF设备上的服务和特性,从而确保它们能够正确地建立连接并进行通信。

bta_hf_client_sdp_cback

SDP的结果会回调给bta_hf_client_sdp_cback。

/*******************************************************************************
 *
 * Function         bta_hf_client_sdp_cback
 *
 * Description      SDP callback function.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
static void bta_hf_client_sdp_cback(UNUSED_ATTR const RawAddress& bd_addr,
                                    tSDP_STATUS status, const void* data) {
  uint16_t event;
  tBTA_HF_CLIENT_DISC_RESULT* p_buf = (tBTA_HF_CLIENT_DISC_RESULT*)osi_malloc(
      sizeof(tBTA_HF_CLIENT_DISC_RESULT));

  log::verbose("bta_hf_client_sdp_cback status:0x{:x}", status);
  tBTA_HF_CLIENT_CB* client_cb = (tBTA_HF_CLIENT_CB*)data;

  /* set event according to int/acp */
  // 根据客户端的角色(主动发起方或接受方),设置要发送的事件类型
  if (client_cb->role == BTA_HF_CLIENT_ACP)
    event = BTA_HF_CLIENT_DISC_ACP_RES_EVT;
  else
    event = BTA_HF_CLIENT_DISC_INT_RES_EVT;

  // 填充事件消息
  p_buf->hdr.event = event;
  p_buf->hdr.layer_specific = client_cb->handle;
  p_buf->status = status;

  bta_sys_sendmsg(p_buf);
}

处理SDP查询的结果。根据SDP操作的状态和提供的回调数据,构造一个事件消息,并通过蓝牙系统的消息发送机制发送Event到bta_sys_event中进行处理。 最后还是回到bta_hf_client_sm_execute()方法中。

bta_hf_client_sm_execute

从API_OPEN 状态 BTA_HF_CLIENT_INIT_ST, 之后将状态迁移到 BTA_HF_CLIENT_OPENING_ST。对应的state_stable是 bta_hf_client_st_opening。

根据BTA_HF_CLIENT_DISC_INT_RES_EVT 事件再进行枚举

根据BTA_HF_CLIENT_DISC_INT_RES_EVT 的值。在bta_hf_client_st_opening找到下标为9那一列。

相应的action 值,以及下一个状态。 BTA_HF_CLIENT_DISC_INT_RES 为 13,下一个迁移状态仍然是 BTA_HF_CLIENT_OPENING_ST。

根据上面的action值,在bta_hf_client_action 函数指针数组中找到对应下标的函数。

最后找到bta_hf_client_disc_int_res。

bta_hf_client_disc_int_res

/packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_act.cc
/*******************************************************************************
 *
 * Function         bta_hf_client_disc_int_res
 *
 * Description      This function handles a discovery result when initiator.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_disc_int_res(tBTA_HF_CLIENT_DATA* p_data) {
  uint16_t event = BTA_HF_CLIENT_DISC_FAIL_EVT;

  log::verbose("Status: {}", p_data->disc_result.status);
  tBTA_HF_CLIENT_CB* client_cb =
      bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific); // 查找客户端控制块
  if (client_cb == NULL) {
    log::error("cb not found for handle {}", p_data->hdr.layer_specific);
    return;
  }

  /* if found service */
  if (p_data->disc_result.status == SDP_SUCCESS ||
      p_data->disc_result.status == SDP_DB_FULL) {
    /* get attributes */
    if (bta_hf_client_sdp_find_attr(client_cb)) { // 进一步尝试获取服务属性
      event = BTA_HF_CLIENT_DISC_OK_EVT;
    }
  }

  /* free discovery db */
  bta_hf_client_free_db(p_data);

  /* send ourselves sdp ok/fail event */
  // 将之前确定的事件(成功或失败)以及相关的数据发送给HF客户端的状态机,以便进行后续处理
  bta_hf_client_sm_execute(event, p_data);
}

处理作为服务发现发起者时的发现结果。根据服务发现的结果来决定后续的操作,包括是否成功找到服务、释放发现数据库以及发送相应的事件给状态机。

bta_hf_client_sdp_find_attr
packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_sdp.cc
/*******************************************************************************
 *
 * Function         bta_hf_client_sdp_find_attr
 *
 * Description      Process SDP discovery results to find requested attribute
 *
 *
 * Returns          true if results found, false otherwise.
 *
 ******************************************************************************/
bool bta_hf_client_sdp_find_attr(tBTA_HF_CLIENT_CB* client_cb) {
  tSDP_DISC_REC* p_rec = NULL;
  tSDP_DISC_ATTR* p_attr;
  tSDP_PROTOCOL_ELEM pe;
  bool result = false;

  client_cb->peer_version = HFP_VERSION_1_1; /* Default version */

  /* loop through all records we found */
  while (true) { // 通过循环遍历所有发现的SDP记录
    /* get next record; if none found, we're done */
    p_rec = get_legacy_stack_sdp_api()->db.SDP_FindServiceInDb(
        client_cb->p_disc_db, UUID_SERVCLASS_AG_HANDSFREE, p_rec);
    if (p_rec == NULL) {
      break;
    }

    /* get scn from proto desc list if initiator */
    if (client_cb->role == BTA_HF_CLIENT_INT) {
      // 获取SCN(Service Channel Number,服务通道号)
      if (get_legacy_stack_sdp_api()->record.SDP_FindProtocolListElemInRec(
              p_rec, UUID_PROTOCOL_RFCOMM, &pe)) {
        client_cb->peer_scn = (uint8_t)pe.params[0];
      } else {
        continue;
      }
    }

    /* get profile version (if failure, version parameter is not updated) */
    get_legacy_stack_sdp_api()->record.SDP_FindProfileVersionInRec(
        p_rec, UUID_SERVCLASS_HF_HANDSFREE, &client_cb->peer_version); // 从记录中找到HFP的版本

    /* get features */
    // 获取特性
    p_attr = get_legacy_stack_sdp_api()->record.SDP_FindAttributeInRec(
        p_rec, ATTR_ID_SUPPORTED_FEATURES);
    if (p_attr != NULL &&
        SDP_DISC_ATTR_TYPE(p_attr->attr_len_type) == UINT_DESC_TYPE &&
        SDP_DISC_ATTR_LEN(p_attr->attr_len_type) >= 2) {
      /* Found attribute. Get value. */
      /* There might be race condition between SDP and BRSF.  */
      /* Do not update if we already received BRSF.           */
      if (client_cb->peer_features == 0) {
        client_cb->peer_features = p_attr->attr_value.v.u16;

        /* SDP and BRSF WBS bit are different, correct it if set */
        if (client_cb->peer_features & 0x0020) {
          client_cb->peer_features &= ~0x0020;
          client_cb->peer_features |= BTA_HF_CLIENT_PEER_CODEC;
        }

        /* get network for ability to reject calls */
        p_attr = get_legacy_stack_sdp_api()->record.SDP_FindAttributeInRec(
            p_rec, ATTR_ID_NETWORK);
        if (p_attr != NULL &&
            SDP_DISC_ATTR_TYPE(p_attr->attr_len_type) == UINT_DESC_TYPE &&
            SDP_DISC_ATTR_LEN(p_attr->attr_len_type) >= 2) {
          if (p_attr->attr_value.v.u16 == 0x01) {
            client_cb->peer_features |= BTA_HF_CLIENT_PEER_REJECT;
          }
        }
      }
    }

    /* found what we needed */
    result = true;
    break;
  }

  log::verbose("peer_version=0x{:x} peer_features=0x{:x}",
               client_cb->peer_version, client_cb->peer_features);

  return result;
}

在 SDP 发现结果中深挖目标服务的关键信息,如版本、特性、协议元素等,为后续蓝牙免提连接流程的精准推进提供支撑。

bta_hf_client_sm_execute(BTA_HF_CLIENT_DISC_OK_EVT)

目前的状态是BTA_HF_CLIENT_OPENING_ST。对应的state_stable是 bta_hf_client_st_opening。

根据BTA_HF_CLIENT_DISC_OK_EVT事件再进行枚举,找相应的action 值,以及下一个状态。

找相应的action 值,以及下一个状态。下一个迁移状态仍然是 BTA_HF_CLIENT_OPENING_ST。

根据上面的action值,在bta_hf_client_action 函数指针数组中找到对应下标的函数。

最后找到bta_hf_client_rfc_do_open。

bta_hf_client_rfc_do_open

packages/modules/Bluetooth/system/bta/hf_client/bta_hf_client_rfc.cc
/*******************************************************************************
 *
 * Function         bta_hf_client_rfc_do_open
 *
 * Description      Open an RFCOMM connection to the peer device.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void bta_hf_client_rfc_do_open(tBTA_HF_CLIENT_DATA* p_data) {
  tBTA_HF_CLIENT_CB* client_cb =
      bta_hf_client_find_cb_by_handle(p_data->hdr.layer_specific);
  if (client_cb == NULL) {
    log::error("cb not found for handle {}", p_data->hdr.layer_specific);
    return;
  }

  // 创建RFCOMM连接
  if (RFCOMM_CreateConnectionWithSecurity(
          UUID_SERVCLASS_HF_HANDSFREE, client_cb->peer_scn, false,
          BTA_HF_CLIENT_MTU, client_cb->peer_addr, &(client_cb->conn_handle),
          bta_hf_client_mgmt_cback,
          BTA_SEC_AUTHENTICATE | BTA_SEC_ENCRYPT) == PORT_SUCCESS) {
    bta_hf_client_setup_port(client_cb->conn_handle); // 设置端口
    log::verbose("bta_hf_client_rfc_do_open : conn_handle = {}",
                 client_cb->conn_handle);
  }
  /* RFCOMM create connection failed; send ourselves RFCOMM close event */
  else {
    bta_hf_client_sm_execute(BTA_HF_CLIENT_RFC_CLOSE_EVT, p_data);
  }
}

与对端设备之间建立RFCOMM(Radio FrequencyCOMMunication)连接。用于音频通信,如蓝牙耳机或其他蓝牙音频设备。

RFCOMM_CreateConnectionWithSecurity接下一篇分析。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/952784.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Shader->LinearGradient线性渐变着色器详解

XML文件 <com.example.myapplication.MyViewxmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_gravity"center"android:layout_height"400dp"/>自定义View代码 c…

【芯片封测学习专栏 -- 单 Die 与 多Die(Chiplet)介绍】

请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | ARM GCC | CSH】 文章目录 Overview单个Die&#xff08;Monolithic Die&#xff09;多个Die&#xff08;Chiplet Architecture or Heterogeneous SoC&#xff09;如何判断一个SoC是…

acwing_5722_十滴水

acwing_5722_十滴水 下面这篇大佬的题解属实是把指针用明白了&#xff0c;可以好好理解一下&#xff1a; 原题解连接&#xff1a;AcWing 5722. 一个简单模拟实现 - AcWing map/unordered_map的用法:见收藏夹 #include<iostream> #include<unordered_map> #incl…

零基础学AI大模型要多久?真的能学会吗?

很多人都对学习AI大模型抱有疑问&#xff0c;特别是那些完全没有编程基础的朋友。其实&#xff0c;从零开始学习AI大模型是可以做到的&#xff0c;关键在于你的学习方法和投入的时间。下面我们来详细聊一聊这个问题。 学习时间 自学&#xff1a; 如果你选择自学&#xff0c;…

攻防靶场(34):隐蔽的计划任务提权 Funbox1

目录 1. 侦查 1.1 收集目标网络信息&#xff1a;IP地址 1.2 主动扫描&#xff1a;扫描IP地址段 1.3 搜索目标网站 2. 初始访问 2.1 有效账户&#xff1a;默认账户 2.2 利用面向公众的应用 2.3 有效账户&#xff1a;默认账户 3. 权限提升 3.1 计划任务/作业&#xff1a;Cron 靶场…

Java高频面试之SE-11

hello啊&#xff0c;各位观众姥爷们&#xff01;&#xff01;&#xff01;本牛马baby今天又来了&#xff01;哈哈哈哈哈嗝&#x1f436; Java中是引用传递还是值传递&#xff1f; 在 Java 中&#xff0c;方法参数传递是通过 值传递 的方式实现的&#xff0c;但这可能会引起一…

2.两数相加--力扣

给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都不会以 0 …

与Linux的设备树文件(dts)的基础知识【编写和操作】

编写相关 01-双引号中的内容表示字符串,<>中的内容表示数值 示例如下&#xff1a; / {swh_led0 {compatible "swh_leddrv";pin <0x00030001>;};.......};compatible的具体内容为字符串swh_leddrv&#xff0c;而pin的值为数值0x00030001。 操作相关…

STM32第6章、WWDG

一、简介 WWDG&#xff1a;全称Window watchdog&#xff0c;即窗口看门狗&#xff0c;本质上是一个能产生系统复位信号和提前唤醒中断的计数器。 特性&#xff1a; 是一个递减计数器。 看门狗被激活后&#xff0c; 当递减计数器值从 0x40减到0x3F时会产生复位&#xff08;即T6位…

国产fpga nvme ip高速存储方案设计

国产高速存储方案主要是使用nvme ip实现高速存储方案&#xff0c;nvme ip采用纯verilog语言实现&#xff0c;用户拿到nvme ip使用起来也很简单。 先看看效果如 zu7eg板子&#xff0c;这个芯片支持pcie3.0 x4. zynq 7045板子只支持pcie 2.0 x4 速度测试&#xff0c;测试nvme …

《框架程序设计》复习题解析-1

目录 单选题 1.以下关于Maven说法错误的是&#xff1f;&#xff08; &#xff09;。 2.在项目的开发过程中&#xff0c;MyBatis承担的责任是( ) 3.当项目引用依赖的范围设置为以下&#xff08; &#xff09;选项时&#xff0c;表示依赖在编译时是必需的&#xff0c;但在运…

STM32F103的ADC通道映射

ADC通道映射 STM32F103带3个ADC控制器&#xff0c;一共支持23个通道&#xff0c;包括21个外部和2个内部信号源。ADC1控制器最多有18个通道&#xff0c;包括16个外部和2个内部信号源。 ADC1和ADC2的16个外部通道相同&#xff0c;且ADC1和ADC2共用一个系统中断向量&#xff0c;A…

Vue2+OpenLayers使用Overlay实现点击获取当前经纬度信息(提供Gitee源码)

目录 一、案例截图 二、安装OpenLayers库 三、代码实现 关键参数&#xff1a; 实现思路&#xff1a; 核心代码&#xff1a; 完整代码&#xff1a; 四、Gitee源码 一、案例截图 二、安装OpenLayers库 npm install ol 三、代码实现 覆盖物&#xff08;Overlay&#xf…

[Transformer] The Structure of GPT, Generative Pretrained Transformer

The Structure of Generative Pretrained Transformer Reference: The Transformer architecture of GPT models How GPT Models Work

【芯片封测学习专栏 -- Substrate | RDL Interposer | Si Interposer | 嵌入式硅桥(EMIB)详细介绍】

请阅读【嵌入式开发学习必备专栏 Cache | MMU | AMBA BUS | CoreSight | Trace32 | CoreLink | ARM GCC | CSH】 文章目录 OverviewSubstrate&#xff08;衬底或基板&#xff09;Substrate 定义Substrate 特点与作用Substrate 实例 RDL Interposer&#xff08;重布线层中介层&a…

基于单片机的无线气象仪系统设计(论文+源码)

1系统方案设计 如图2.1所示为无线气象仪系统设计框架。系统设计采用STM32单片机作为主控制器&#xff0c;结合DHT11温湿度传感器、光敏传感器、BMP180气压传感器、PR-3000-FS-N01风速传感器实现气象环境的温度、湿度、光照、气压、风速等环境数据的检测&#xff0c;并通过OLED1…

【JavaWeb】JavaWeb入门之Tomcat详解

目录 1.Java Web前奏 1.1.C/S结构 1.2.B/S结构 1.3.静态网页和动态网页 1.4.常见的网页 1.5.Web服务器 2.HTTP协议 2.1.HTTP协议概念 2.2.无状态协议 2.3.HTTP1.0和HTTP1.1 2.4.请求协议和响应协议 2.5.请求协议 2.5.1.GET请求 2.5.2.POST请求 2.6.响应协议 1.J…

【SpringBoot】@Value 没有注入预期的值

问题复现 在装配对象成员属性时&#xff0c;我们常常会使用 Autowired 来装配。但是&#xff0c;有时候我们也使用 Value 进行装配。不过这两种注解使用风格不同&#xff0c;使用 Autowired 一般都不会设置属性值&#xff0c;而 Value 必须指定一个字符串值&#xff0c;因为其…

nginx反向代理和负载均衡的区别

1、反向代理&#xff0c;不需要服务器池&#xff0c;直接代理某台服务器 location / {proxy_pass http://192.168.18.201;proxy_set_header Host $host;proxy_set_header X-Forwarded-For $remote_addr; }proxy_set_header Host $host; …

uniApp通过xgplayer(西瓜播放器)接入视频实时监控

&#x1f680; 个人简介&#xff1a;某大型国企资深软件开发工程师&#xff0c;信息系统项目管理师、CSDN优质创作者、阿里云专家博主&#xff0c;华为云云享专家&#xff0c;分享前端后端相关技术与工作常见问题~ &#x1f49f; 作 者&#xff1a;码喽的自我修养&#x1f9…