Bootstrap

【Bluedroid】A2dp Source连接流程源码分析(三)

本篇接https://blog.csdn.net/weixin_37800531/article/details/144847341分析。

本篇主要围绕A2dp Source连接流程中的AVDTP Discover展开。

bta_av_discover_req函数是蓝牙音频/视频分发传输协议中用于发现对端设备支持的媒体流端点的一个关键步骤。通过发送发现请求,设备可以了解对方支持哪些类型的音频或视频流,从而建立相应的连接和传输。

bta_av_discover_req

packages/modules/Bluetooth/system/stack/avdt/avdt_api.cc
/*******************************************************************************
 *
 * Function         AVDT_DiscoverReq
 *
 * Description      This function initiates a connection to the AVDTP service
 *                  on the peer device, if not already present, and discovers
 *                  the stream endpoints on the peer device.  (Please note
 *                  that AVDTP discovery is unrelated to SDP discovery).
 *                  This function can be called at any time regardless of
 *                  whether there is an AVDTP connection to the peer device.
 *
 *                  When discovery is complete, an AVDT_DISCOVER_CFM_EVT
 *                  is sent to the application via its callback function.
 *                  The application must not call AVDT_GetCapReq() or
 *                  AVDT_DiscoverReq() again to the same device until
 *                  discovery is complete.
 *
 *                  The memory addressed by sep_info is allocated by the
 *                  application.  This memory is written to by AVDTP as part
 *                  of the discovery procedure.  This memory must remain
 *                  accessible until the application receives the
 *                  AVDT_DISCOVER_CFM_EVT.
 *
 * Returns          AVDT_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
uint16_t AVDT_DiscoverReq(const RawAddress& bd_addr, uint8_t channel_index, // 标识AVDTP连接的通道索引
                          tAVDT_SEP_INFO* p_sep_info, uint8_t max_seps,
                          tAVDT_CTRL_CBACK* p_cback) {
  AvdtpCcb* p_ccb;
  uint16_t result = AVDT_SUCCESS;
  tAVDT_CCB_EVT evt;

  log::verbose("");

  /* find channel control block for this bd addr; if none, allocate one */
  // 查找或分配通道控制块(CCB)
  p_ccb = avdt_ccb_by_bd(bd_addr);
  if (p_ccb == NULL) {
    // 尝试根据通道索引为该地址分配一个新的通道控制块
    p_ccb = avdt_ccb_alloc_by_channel_index(bd_addr, channel_index);
    if (p_ccb == NULL) {
      /* could not allocate channel control block */
      result = AVDT_NO_RESOURCES;
    }
  }

  if (result == AVDT_SUCCESS) {
    /* make sure no discovery or get capabilities req already in progress */
    // 检查当前通道控制块是否已经有正在进行的发现或获取能力请求
    if (p_ccb->proc_busy) {
      result = AVDT_BUSY;
    }
    /* send event to ccb */    
    else {// 发送发现请求事件
      // 创建一个事件,其中包含流端点信息、最大端点数和回调函数指针
      evt.discover.p_sep_info = p_sep_info;
      evt.discover.num_seps = max_seps;
      evt.discover.p_cback = p_cback;
      avdt_ccb_event(p_ccb, AVDT_CCB_API_DISCOVER_REQ_EVT, &evt); // 将此事件发送到通道控制块进行处理
    }
  }

  if (result != AVDT_SUCCESS) {
    log::error("result={} address={}", result,
               ADDRESS_TO_LOGGABLE_CSTR(bd_addr));
  }
  return result;
}

调用AVDT_DiscoverReq函数向对等设备发送一个AVDTP发现请求,以查询对方支持的媒体流端点(SEP,Streaming Endpoint)。

AVDT_DiscoverReq

packages/modules/Bluetooth/system/stack/avdt/avdt_api.cc
/*******************************************************************************
 *
 * Function         AVDT_DiscoverReq
 *
 * Description      This function initiates a connection to the AVDTP service
 *                  on the peer device, if not already present, and discovers
 *                  the stream endpoints on the peer device.  (Please note
 *                  that AVDTP discovery is unrelated to SDP discovery).
 *                  This function can be called at any time regardless of
 *                  whether there is an AVDTP connection to the peer device.
 *
 *                  When discovery is complete, an AVDT_DISCOVER_CFM_EVT
 *                  is sent to the application via its callback function.
 *                  The application must not call AVDT_GetCapReq() or
 *                  AVDT_DiscoverReq() again to the same device until
 *                  discovery is complete.
 *
 *                  The memory addressed by sep_info is allocated by the
 *                  application.  This memory is written to by AVDTP as part
 *                  of the discovery procedure.  This memory must remain
 *                  accessible until the application receives the
 *                  AVDT_DISCOVER_CFM_EVT.
 *
 * Returns          AVDT_SUCCESS if successful, otherwise error.
 *
 ******************************************************************************/
uint16_t AVDT_DiscoverReq(const RawAddress& bd_addr, uint8_t channel_index, // 标识AVDTP连接的通道索引
                          tAVDT_SEP_INFO* p_sep_info, uint8_t max_seps,
                          tAVDT_CTRL_CBACK* p_cback) {
  AvdtpCcb* p_ccb;
  uint16_t result = AVDT_SUCCESS;
  tAVDT_CCB_EVT evt;

  log::verbose("");

  /* find channel control block for this bd addr; if none, allocate one */
  // 查找或分配通道控制块(CCB)
  p_ccb = avdt_ccb_by_bd(bd_addr);
  if (p_ccb == NULL) {
    // 尝试根据通道索引为该地址分配一个新的通道控制块
    p_ccb = avdt_ccb_alloc_by_channel_index(bd_addr, channel_index);
    if (p_ccb == NULL) {
      /* could not allocate channel control block */
      result = AVDT_NO_RESOURCES;
    }
  }

  if (result == AVDT_SUCCESS) {
    /* make sure no discovery or get capabilities req already in progress */
    // 检查当前通道控制块是否已经有正在进行的发现或获取能力请求
    if (p_ccb->proc_busy) {
      result = AVDT_BUSY;
    }
    /* send event to ccb */    
    else {// 发送发现请求事件
      // 创建一个事件,其中包含流端点信息、最大端点数和回调函数指针
      evt.discover.p_sep_info = p_sep_info;
      evt.discover.num_seps = max_seps;
      evt.discover.p_cback = p_cback;
      avdt_ccb_event(p_ccb, AVDT_CCB_API_DISCOVER_REQ_EVT, &evt); // 将此事件发送到通道控制块进行处理
    }
  }

  if (result != AVDT_SUCCESS) {
    log::error("result={} address={}", result,
               ADDRESS_TO_LOGGABLE_CSTR(bd_addr));
  }
  return result;
}

在蓝牙音频传输场景下,连接到对端设备的AVDTP服务(如果尚未连接),并发现该设备上的流端点。

在收到AVDT_DISCOVER_CFM_EVT事件之前,应用程序不应再次对同一设备调用AVDT_GetCapReq()或AVDT_DiscoverReq()。

avdt_ccb_event(AVDT_CCB_API_DISCOVER_REQ_EVT)

packages/modules/Bluetooth/system/stack/avdt/avdt_ccb.cc
/*******************************************************************************
 *
 * Function         avdt_ccb_event
 *
 * Description      State machine event handling function for ccb
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ccb_event(AvdtpCcb* p_ccb, uint8_t event, tAVDT_CCB_EVT* p_data) {
  tAVDT_CCB_ST_TBL state_table;
  uint8_t action;
  int i;

#if (AVDT_DEBUG == TRUE)
  log::verbose("CCB ccb={} event={} state={} p_ccb={}", avdt_ccb_to_idx(p_ccb),
               avdt_ccb_evt_str[event], avdt_ccb_st_str[p_ccb->state],
               fmt::ptr(p_ccb));
#endif

  /* look up the state table for the current state */
  // 查找状态表:根据当前CCB的状态,查找对应的状态表。状态表定义了每个状态下每个事件的处理逻辑,包括下一个状态和要执行的动作
  state_table = avdt_ccb_st_tbl[p_ccb->state];

  /* set next state */
  // 设置下一个状态:如果当前状态不是状态表中定义的下一个状态,则更新CCB的状态
  if (p_ccb->state != state_table[event][AVDT_CCB_NEXT_STATE]) {
    p_ccb->state = state_table[event][AVDT_CCB_NEXT_STATE];
  }

  /* execute action functions */
  for (i = 0; i < AVDT_CCB_ACTIONS; i++) {
    action = state_table[event][i];
    log::verbose("event={} state={} action={}", avdt_ccb_evt_str[event],
                 avdt_ccb_st_str[p_ccb->state], action);
    if (action != AVDT_CCB_IGNORE) {
      (*avdtp_cb.p_ccb_act[action])(p_ccb, p_data);
    } else {
      break;
    }
  }

负责处理AVDTP连接的状态转换和事件响应。通过状态机和动作函数的组合,能够根据接收到的事件和当前状态来执行相应的操作,从而管理AVDTP连接的生命周期。

avdt_ccb_snd_discover_cmd

/packages/modules/Bluetooth/system/stack/avdt/avdt_api.cc
/*******************************************************************************
 *
 * Function         avdt_ccb_snd_discover_cmd
 *
 * Description      This function is called to send a discover command to the
 *                  peer.  It copies variables needed for the procedure from
 *                  the event to the CCB.  It marks the CCB as busy and then
 *                  sends a discover command.
 *
 *
 * Returns          void.
 *
 ******************************************************************************/
void avdt_ccb_snd_discover_cmd(AvdtpCcb* p_ccb, tAVDT_CCB_EVT* p_data) {
  /* store info in ccb struct */
  // 存储信息到CCB结构体:从事件结构体p_data中复制发现命令所需的信息到CCB结构体p_ccb中
  p_ccb->p_proc_data = p_data->discover.p_sep_info; // 指向SEP(Stream End Point,流端点)信息的指针,这些信息描述了要发现的流端点的特性
  p_ccb->proc_cback = p_data->discover.p_cback; // 回调函数指针,当发现过程完成时,这个回调函数将被调用
  p_ccb->proc_param = p_data->discover.num_seps; // 要发现的SEP的数量

  /* we're busy */
  p_ccb->proc_busy = true; // 标记这个CCB当前正在处理一个发现过程,防止其他过程同时使用这个CCB

  /* build and queue discover req */
  avdt_msg_send_cmd(p_ccb, NULL, AVDT_SIG_DISCOVER, NULL); // 建并发送一个发现请求消息
}

发送一个发现命令给对端设备。

avdt_msg_send_cmd

/packages/modules/Bluetooth/system/stack/avdt/avdt_msg.cc
/*******************************************************************************
 *
 * Function         avdt_msg_send_cmd
 *
 * Description      This function is called to send a command message.  The
 *                  sig_id parameter indicates the message type, p_params
 *                  points to the message parameters, if any.  It gets a buffer
 *                  from the AVDTP command pool, executes the message building
 *                  function for this message type.  It then queues the message
 *                  in the command queue for this CCB.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_msg_send_cmd(AvdtpCcb* p_ccb, void* p_scb, uint8_t sig_id,
                       tAVDT_MSG* p_params) {
  uint8_t* p;
  uint8_t* p_start;
  BT_HDR* p_buf = (BT_HDR*)osi_malloc(AVDT_CMD_BUF_SIZE); // 分配缓冲区

  /* set up buf pointer and offset */
  // 设置缓冲区指针和偏移
  p_buf->offset = AVDT_MSG_OFFSET;
  p_start = p = (uint8_t*)(p_buf + 1) + p_buf->offset;

  /* execute parameter building function to build message */
  // 构建消息:根据sig_id调用相应的消息构建函数(从avdt_msg_bld_cmd数组中获取)
  (*avdt_msg_bld_cmd[sig_id - 1])(&p, p_params); // avdt_msg_bld_none

  /* set len */
  p_buf->len = (uint16_t)(p - p_start); // 设置消息长度

  /* now store scb hdls, if any, in buf */
  if (p_scb != NULL) { // 存储SCB句柄(如果适用)
    p = (uint8_t*)(p_buf + 1);

    /* for start and suspend, p_scb points to array of handles */
    if ((sig_id == AVDT_SIG_START) || (sig_id == AVDT_SIG_SUSPEND)) {
      memcpy(p, (uint8_t*)p_scb, p_buf->len);
    }
    /* for all others, p_scb points to scb as usual */
    else {
      *p = avdt_scb_to_hdl((AvdtpScb*)p_scb);
    }
  }

  /* stash sig, label, and message type in buf */
  // 设置消息头:在缓冲区中设置消息的事件类型(即sig_id)、消息类型(命令)和标签(p_ccb->label)
  p_buf->event = sig_id;
  AVDT_BLD_LAYERSPEC(p_buf->layer_specific, AVDT_MSG_TYPE_CMD, p_ccb->label);

  /* increment label */
  p_ccb->label = (p_ccb->label + 1) % 16; // 递增CCB的标签值,并确保它在0到15的范围内循环

  /* queue message and trigger ccb to send it */
  fixed_queue_enqueue(p_ccb->cmd_q, p_buf); // 将消息添加到CCB的命令队列中
  avdt_ccb_event(p_ccb, AVDT_CCB_SENDMSG_EVT, NULL); // 触发事件(AVDT_CCB_SENDMSG_EVT)
}

avdt_ccb_event(AVDT_CCB_SENDMSG_EVT)

packages/modules/Bluetooth/system/stack/avdt/avdt_ccb.cc
/*******************************************************************************
 *
 * Function         avdt_ccb_event
 *
 * Description      State machine event handling function for ccb
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ccb_event(AvdtpCcb* p_ccb, uint8_t event, tAVDT_CCB_EVT* p_data) {
  tAVDT_CCB_ST_TBL state_table;
  uint8_t action;
  int i;

#if (AVDT_DEBUG == TRUE)
  log::verbose("CCB ccb={} event={} state={} p_ccb={}", avdt_ccb_to_idx(p_ccb),
               avdt_ccb_evt_str[event], avdt_ccb_st_str[p_ccb->state],
               fmt::ptr(p_ccb));
#endif

  /* look up the state table for the current state */
  state_table = avdt_ccb_st_tbl[p_ccb->state];

  /* set next state */
  if (p_ccb->state != state_table[event][AVDT_CCB_NEXT_STATE]) {
    p_ccb->state = state_table[event][AVDT_CCB_NEXT_STATE];
  }

  /* execute action functions */
  for (i = 0; i < AVDT_CCB_ACTIONS; i++) {
    action = state_table[event][i];
    log::verbose("event={} state={} action={}", avdt_ccb_evt_str[event],
                 avdt_ccb_st_str[p_ccb->state], action);
    if (action != AVDT_CCB_IGNORE) {
      (*avdtp_cb.p_ccb_act[action])(p_ccb, p_data);
    } else {
      break;
    }
  }
}

avdt_ccb_snd_msg

packages/modules/Bluetooth/system/stack/avdt/avdt_ccb_act.cc
/*******************************************************************************
 *
 * Function         avdt_ccb_snd_msg
 *
 * Description
 *
 *
 * Returns          void.
 *
 ******************************************************************************/
void avdt_ccb_snd_msg(AvdtpCcb* p_ccb, UNUSED_ATTR tAVDT_CCB_EVT* p_data) {
  BT_HDR* p_msg;

  /* if not congested */
  if (!p_ccb->cong) { // 检查拥塞状态
    /* are we sending a fragmented message? continue sending fragment */
    // 发送分片消息
    if (p_ccb->p_curr_msg != NULL) { // 当前正在发送一个分片消息
      // 继续发送该消息的下一个分片。通常发生在消息太大而无法一次性发送时,需要将其分成多个较小的分片
      avdt_msg_send(p_ccb, NULL);
    }
    /* do we have responses to send?  send them */
    // 发送响应消息
    else if (!fixed_queue_is_empty(p_ccb->rsp_q)) {
      // 尝试从队列中取出消息并发送它们,直到队列为空或遇到拥塞为止
      while ((p_msg = (BT_HDR*)fixed_queue_try_dequeue(p_ccb->rsp_q)) != NULL) {
        if (avdt_msg_send(p_ccb, p_msg)) {
          /* break out if congested */
          break;
        }
      }
    }

    /* do we have commands to send?  send next command */
    // 发送命令消息:无论是否发送了响应消息,都会尝试发送下一个待发送的命令消息
    avdt_ccb_snd_cmd(p_ccb, NULL);
  }
}

按照分段消息、响应消息、命令消息的优先级顺序,在没有拥塞的情况下,尝试依次发送各类 AVDTP 消息。

avdt_msg_send

packages/modules/Bluetooth/system/stack/avdt/avdt_msg.cc
/*******************************************************************************
 *
 * Function         avdt_msg_send
 *
 * Description      Send, and if necessary fragment the next message.
 *
 *
 * Returns          Congested state; true if CCB congested, false if not.
 *
 ******************************************************************************/
bool avdt_msg_send(AvdtpCcb* p_ccb, BT_HDR* p_msg) {
  uint16_t curr_msg_len;
  uint8_t pkt_type;
  uint8_t hdr_len;
  AvdtpTransportChannel* p_tbl;
  BT_HDR* p_buf;
  uint8_t* p;
  uint8_t label;
  uint8_t msg;
  uint8_t sig;
  uint8_t nosp = 0; /* number of subsequent packets */

  /* look up transport channel table entry to get peer mtu */
  // 查找与信号通道(AVDT_CHAN_SIG)相关联的传输通道表条目,以获取对端的MTU大小
  p_tbl = avdt_ad_tc_tbl_by_type(AVDT_CHAN_SIG, p_ccb, NULL);

  /* set the current message if there is a message passed in */
  if (p_msg != NULL) {
    // 将其设置为当前正在发送的消息
    p_ccb->p_curr_msg = p_msg;
  }

  /* store copy of curr_msg->len */
  curr_msg_len = p_ccb->p_curr_msg->len;

  /* while not congested and we haven't sent it all */
  // 消息发送循环:进入循环,该循环将持续进行,直到CCB拥塞或当前消息完全发送为止
  while ((!p_ccb->cong) && (p_ccb->p_curr_msg != NULL)) {
    /* check what kind of message we've got here; we are using the offset
    ** to indicate that a message is being fragmented
    */
    
    // 消息类型检查与分片
    /* if message isn't being fragmented and it fits in mtu */
    // 如果消息未被分片且适合MTU大小,则将其作为单包消息发送
    if ((p_ccb->p_curr_msg->offset == AVDT_MSG_OFFSET) &&
        (p_ccb->p_curr_msg->len <= p_tbl->peer_mtu - AVDT_LEN_TYPE_SINGLE)) {
      pkt_type = AVDT_PKT_TYPE_SINGLE;
      hdr_len = AVDT_LEN_TYPE_SINGLE;
      p_buf = p_ccb->p_curr_msg;
    }
    /* if message isn't being fragmented and it doesn't fit in mtu */
    // 如果消息未被分片但不适合MTU大小,则开始分片过程,并发送第一个分片(起始分片)
    else if ((p_ccb->p_curr_msg->offset == AVDT_MSG_OFFSET) &&
             (p_ccb->p_curr_msg->len >
              p_tbl->peer_mtu - AVDT_LEN_TYPE_SINGLE)) {
      pkt_type = AVDT_PKT_TYPE_START;
      hdr_len = AVDT_LEN_TYPE_START;
      nosp = (p_ccb->p_curr_msg->len + AVDT_LEN_TYPE_START - p_tbl->peer_mtu) /
                 (p_tbl->peer_mtu - 1) +
             2;

      /* get a new buffer for fragment we are sending */
      p_buf = (BT_HDR*)osi_malloc(AVDT_CMD_BUF_SIZE);

      /* copy portion of data from current message to new buffer */
      p_buf->offset = L2CAP_MIN_OFFSET + hdr_len;
      p_buf->len = p_tbl->peer_mtu - hdr_len;
      memcpy((uint8_t*)(p_buf + 1) + p_buf->offset,
             (uint8_t*)(p_ccb->p_curr_msg + 1) + p_ccb->p_curr_msg->offset,
             p_buf->len);
    }
    /* if message is being fragmented and remaining bytes don't fit in mtu */
    // 如果消息正在被分片且剩余字节不适合MTU大小,则发送一个继续分片
    else if ((p_ccb->p_curr_msg->offset > AVDT_MSG_OFFSET) &&
             (p_ccb->p_curr_msg->len >
              (p_tbl->peer_mtu - AVDT_LEN_TYPE_CONT))) {
      pkt_type = AVDT_PKT_TYPE_CONT;
      hdr_len = AVDT_LEN_TYPE_CONT;

      /* get a new buffer for fragment we are sending */
      p_buf = (BT_HDR*)osi_malloc(AVDT_CMD_BUF_SIZE);

      /* copy portion of data from current message to new buffer */
      p_buf->offset = L2CAP_MIN_OFFSET + hdr_len;
      p_buf->len = p_tbl->peer_mtu - hdr_len;
      memcpy((uint8_t*)(p_buf + 1) + p_buf->offset,
             (uint8_t*)(p_ccb->p_curr_msg + 1) + p_ccb->p_curr_msg->offset,
             p_buf->len);
    }
    /* if message is being fragmented and remaining bytes do fit in mtu */
    else { // 如果消息正在被分片且剩余字节适合MTU大小或更少,则发送最后一个分片(结束分片)
      pkt_type = AVDT_PKT_TYPE_END;
      hdr_len = AVDT_LEN_TYPE_END;
      p_buf = p_ccb->p_curr_msg;
    }

    /* label, sig id, msg type are in hdr of p_curr_msg */    
    label = AVDT_LAYERSPEC_LABEL(p_ccb->p_curr_msg->layer_specific);
    msg = AVDT_LAYERSPEC_MSG(p_ccb->p_curr_msg->layer_specific);
    sig = (uint8_t)p_ccb->p_curr_msg->event;
    log::verbose("avdt_msg_send label:{}, msg:{}, sig:{}", label, msg, sig);

    /* keep track of how much of msg we've sent */
    // 更新消息状态:根据已发送的字节数,更新当前消息的长度和偏移量。如果整个消息已发送完毕,则将当前消息指针设置为NULL
    curr_msg_len -= p_buf->len;
    if (curr_msg_len == 0) {
      /* entire message sent; mark as finished */
      p_ccb->p_curr_msg = NULL;

      /* start timer here for commands */
      // 设置定时器:对于命令消息,根据消息类型设置相应的定时器(响应定时器或重传定时器)
      if (msg == AVDT_MSG_TYPE_CMD) {
        /* if retransmit timeout set to zero, sig doesn't use retransmit */
        if ((sig == AVDT_SIG_DISCOVER) || (sig == AVDT_SIG_GETCAP) ||
            (sig == AVDT_SIG_SECURITY) || (avdtp_cb.rcb.ret_tout == 0)) {
          alarm_cancel(p_ccb->idle_ccb_timer);
          alarm_cancel(p_ccb->ret_ccb_timer);
          uint64_t interval_ms = avdtp_cb.rcb.sig_tout * 1000;
          alarm_set_on_mloop(p_ccb->rsp_ccb_timer, interval_ms,
                             avdt_ccb_rsp_ccb_timer_timeout, p_ccb);
        } else if (sig != AVDT_SIG_DELAY_RPT) {
          alarm_cancel(p_ccb->idle_ccb_timer);
          alarm_cancel(p_ccb->rsp_ccb_timer);
          uint64_t interval_ms = avdtp_cb.rcb.ret_tout * 1000;
          alarm_set_on_mloop(p_ccb->ret_ccb_timer, interval_ms,
                             avdt_ccb_ret_ccb_timer_timeout, p_ccb);
        }
      }
    } else {
      /* message being fragmented and not completely sent */
      p_ccb->p_curr_msg->len -= p_buf->len;
      p_ccb->p_curr_msg->offset += p_buf->len;
    }

    /* set up to build header */
    // 构建消息头:根据消息类型(单包、起始分片、继续分片、结束分片),构建消息头,并可能包括后续分片数量(对于起始分片)和信号ID(对于起始分片和单包消息)
    p_buf->len += hdr_len;
    p_buf->offset -= hdr_len;
    p = (uint8_t*)(p_buf + 1) + p_buf->offset;

    /* build header */
    AVDT_MSG_BLD_HDR(p, label, pkt_type, msg);
    if (pkt_type == AVDT_PKT_TYPE_START) {
      AVDT_MSG_BLD_NOSP(p, nosp);
    }
    if ((pkt_type == AVDT_PKT_TYPE_START) ||
        (pkt_type == AVDT_PKT_TYPE_SINGLE)) {
      AVDT_MSG_BLD_SIG(p, sig);
    }

    /* send msg buffer down */
    avdt_ad_write_req(AVDT_CHAN_SIG, p_ccb, NULL, p_buf); // 将构建好的消息发送到L2CAP层
  }
  return (p_ccb->cong); // 拥塞检查:在循环结束时,返回CCB的拥塞状态
}

负责将消息发送到对端,并在必要时对消息进行分片。

avdt_ad_write_req

packages/modules/Bluetooth/system/stack/avdt/avdt_ad.cc
/*******************************************************************************
 *
 * Function         avdt_ad_write_req
 *
 * Description      This function is called by a CCB or SCB to send data to a
 *                  transport channel.  It looks up the LCID of the channel
 *                  based on the type, CCB, and SCB (if present).  Then it
 *                  passes the data to L2CA_DataWrite().
 *
 *
 * Returns          AVDT_AD_SUCCESS, if data accepted
 *                  AVDT_AD_CONGESTED, if data accepted and the channel is
 *                                     congested
 *                  AVDT_AD_FAILED, if error
 *
 ******************************************************************************/
uint8_t avdt_ad_write_req(uint8_t type, AvdtpCcb* p_ccb, AvdtpScb* p_scb,
                          BT_HDR* p_buf) {
  uint8_t tcid;

  /* get tcid from type, scb */
  // 查找LCID:根据数据类型和可能的SCB来查找对应的传输通道标识符(TCID)
  tcid = avdt_ad_type_to_tcid(type, p_scb);

  return L2CA_DataWrite(avdtp_cb.ad.rt_tbl[avdt_ccb_to_idx(p_ccb)][tcid].lcid,
                        p_buf);
}

把 AVDTP 相关的传输需求转接到了 L2CA_DataWrite 函数。

L2CA_DataWrite

/packages/modules/Bluetooth/system/stack/l2cap/l2c_api.cc
/*******************************************************************************
 *
 * Function         L2CA_DataWrite
 *
 * Description      Higher layers call this function to write data.
 *
 * Returns          L2CAP_DW_SUCCESS, if data accepted, else false
 *                  L2CAP_DW_CONGESTED, if data accepted and the channel is
 *                                      congested
 *                  L2CAP_DW_FAILED, if error
 *
 ******************************************************************************/
uint8_t L2CA_DataWrite(uint16_t cid, BT_HDR* p_data) {
  log::verbose("L2CA_DataWrite()  CID: 0x{:04x}  Len: {}", cid, p_data->len);
  return l2c_data_write(cid, p_data, L2CAP_FLUSHABLE_CH_BASED);
}

L2CAP层提供的API的一部分,允许上层协议将数据写入到指定的L2CAP通道。

l2c_data_write

/packages/modules/Bluetooth/system/stack/l2cap/l2c_main.cc
/*******************************************************************************
 *
 * Function         l2c_data_write
 *
 * Description      API functions call this function to write data.
 *
 * Returns          L2CAP_DW_SUCCESS, if data accepted, else false
 *                  L2CAP_DW_CONGESTED, if data accepted and the channel is
 *                                      congested
 *                  L2CAP_DW_FAILED, if error
 *
 ******************************************************************************/
uint8_t l2c_data_write(uint16_t cid, BT_HDR* p_data, uint16_t flags) {
  /* Find the channel control block. We don't know the link it is on. */
  // 查找通道控制块, 尝试根据提供的CID找到对应的通道控制块(CCB)
  tL2C_CCB* p_ccb = l2cu_find_ccb_by_cid(NULL, cid);
  if (!p_ccb) {
    log::warn("L2CAP - no CCB for L2CA_DataWrite, CID: {}", cid);
    osi_free(p_data);
    return (L2CAP_DW_FAILED);
  }

  /* Sending message bigger than mtu size of peer is a violation of protocol */
  uint16_t mtu;
  // 检查MTU大小:检查要发送的数据长度是否超过了对端设备的MTU大小
  if (p_ccb->p_lcb->transport == BT_TRANSPORT_LE)
    mtu = p_ccb->peer_conn_cfg.mtu;
  else
    mtu = p_ccb->peer_cfg.mtu;

  if (p_data->len > mtu) {
    log::warn(
        "L2CAP - CID: 0x{:04x}  cannot send message bigger than peer's mtu "
        "size: len={} mtu={}",
        cid, p_data->len, mtu);
    osi_free(p_data);
    return (L2CAP_DW_FAILED);
  }

  /* channel based, packet based flushable or non-flushable */
  p_data->layer_specific = flags;

  /* If already congested, do not accept any more packets */
  // 检查拥塞状态 
  if (p_ccb->cong_sent) { // 通道已经处于拥塞状态
    log::error(
        "L2CAP - CID: 0x{:04x} cannot send, already congested  "
        "xmit_hold_q.count: {}  buff_quota: {}",
        p_ccb->local_cid, fixed_queue_length(p_ccb->xmit_hold_q),
        p_ccb->buff_quota);

    osi_free(p_data);
    return (L2CAP_DW_FAILED);
  }

  // 执行L2CAP状态机
  l2c_csm_execute(p_ccb, L2CEVT_L2CA_DATA_WRITE, p_data);

  // 检查通道是否仍然处于拥塞状态
  if (p_ccb->cong_sent) return (L2CAP_DW_CONGESTED);

  return (L2CAP_DW_SUCCESS);
}

处理来自上层(如L2CAP API函数L2CA_DataWrite)的数据写入请求。验证请求的有效性,确保数据符合协议规范,并最终将数据传递给L2CAP状态机进行处理。

l2c_csm_execute(CST_OPEN)

    case CST_OPEN:
      l2c_csm_open(p_ccb, event, p_data);
      break;

l2c_csm_open(L2CEVT_L2CA_DATA_WRITE)

/packages/modules/Bluetooth/system/stack/l2cap/l2c_csm.cc
/*******************************************************************************
 *
 * Function         l2c_csm_open
 *
 * Description      This function handles events when the channel is in
 *                  OPEN state.
 *
 * Returns          void
 *
 ******************************************************************************/
static void l2c_csm_open(tL2C_CCB* p_ccb, tL2CEVT event, void* p_data) {
  uint16_t local_cid = p_ccb->local_cid;
  tL2CAP_CFG_INFO* p_cfg;
  tL2C_CHNL_STATE tempstate;
  uint8_t tempcfgdone;
  uint8_t cfg_result = L2CAP_PEER_CFG_DISCONNECT;
  uint16_t credit = 0;
  tL2CAP_LE_CFG_INFO* p_le_cfg = (tL2CAP_LE_CFG_INFO*)p_data;

  log::verbose("LCID: 0x{:04x}  st: OPEN  evt: {}", p_ccb->local_cid,
               l2c_csm_get_event_name(event));

  switch (event) {
    ...
    
    case L2CEVT_L2CA_DATA_WRITE: /* Upper layer data to send */
      if (p_data) {
        uint16_t package_len = ((BT_HDR*)p_data)->len; // 提取数据包的长度
        l2c_enqueue_peer_data(p_ccb, (BT_HDR*)p_data); // 将数据加入到对端设备的发送队列中
        l2c_link_check_send_pkts(p_ccb->p_lcb, 0, NULL);
        power_telemetry::GetInstance().LogTxBytes(
            p_ccb->p_rcb->psm, p_ccb->local_cid, p_ccb->remote_id,
            p_ccb->p_lcb->remote_bd_addr, package_len);
      }
      break;
    ...
      
    default:
      log::error("Handling unexpected event:{}", l2c_csm_get_event_name(event));
  }
  log::verbose("Exit chnl_state={} [{}], event={} [{}]",
               channel_state_text(p_ccb->chnl_state), p_ccb->chnl_state,
               l2c_csm_get_event_name(event), event);
}

L2CAP状态机(CSM)的一部分,专门用于处理通道处于OPEN状态时的事件。OPEN状态意味着通道已经建立,可以进行数据传输。

l2c_link_check_send_pkts

具体过程前文有分析。直接看snoop log:

AVDTP Discover Command

AVDTP中的Discover Command(发现命令)用于查询远端蓝牙设备可以提供的SEP(Stream End Point,流端点)的重要步骤。

AVDTP Discover Command的主要作用是发现远端蓝牙设备中的流端点(SEP)。每个SEP可以提供特定的服务,通过Discover Command,设备可以了解到远端设备支持哪些类型的音视频流,从而为后续的音视频传输做好准备。

AVDTP Discover Command的流程:

  • 建立Signalling Channel

    • AVDTP协议首先需要使用L2CAP(Logical Link Control and Adaptation Protocol Layer,逻辑链路控制和适配协议层)来建立Signalling Channel(信令通道)。
    • 在建立Signalling Channel的过程中,会生成Source CID(Channel Identifier,通道标识符)和Destination CID,以便后续区分使用。
  • 发送Discover Command

    • 一旦Signalling Channel建立完成,设备就可以通过该通道发送Discover Command来查询远端设备的SEP。
    • Discover Command会包含一些必要的参数,以便远端设备能够正确理解和响应。

  • 接收并处理响应

    • 远端设备在收到Discover Command后,会检查自身的SEP,并返回所有可用的SEP信息给发起查询的设备。
    • 发起查询的设备在收到响应后,会解析这些信息,并了解到远端设备支持哪些类型的音视频流。
AVDTP Discover Accept

AVDTP Discover Command是蓝牙音视频传输协议中的重要组成部分。通过该命令,设备可以了解到远端设备的音视频流支持情况,从而为后续的音视频传输选择合适的参数和配置。对于实现高质量的蓝牙音视频传输至关重要。

除了Discover Command外,AVDTP协议还包括其他多个重要命令,如Get Capabilities Command(获取能力命令)、Set Configuration Command(设置配置命令)、Open Stream Command(打开流命令)等。后面章节具体展开分析。

这些命令共同构成了AVDTP协议的核心功能,实现了蓝牙设备之间的音视频流的传输和控制。 

l2c_rcv_acl_data

ACL数据接收的流程前文有问题,这里直接看对应的事件状态机处理部分。

l2c_csm_execute(L2CEVT_L2CAP_DATA)

    case CST_OPEN:
      l2c_csm_open(p_ccb, event, p_data);
      break;

l2c_csm_open(L2CEVT_L2CAP_DATA)

    case L2CEVT_L2CAP_DATA: /* Peer data packet rcvd    */
      if (p_data && (p_ccb->p_rcb)) {
        uint16_t package_len = ((BT_HDR*)p_data)->len; // 数据包头部中提取数据包的长度
        if (p_ccb->p_rcb->api.pL2CA_DataInd_Cb) { // 检查是否注册了回调函数 
          p_ccb->metrics.rx(static_cast<BT_HDR*>(p_data)->len);
          // 调用回调函数
          (*p_ccb->p_rcb->api.pL2CA_DataInd_Cb)(p_ccb->local_cid,
                                                (BT_HDR*)p_data);
        }

        power_telemetry::GetInstance().LogRxBytes(
            p_ccb->p_rcb->psm, p_ccb->local_cid, p_ccb->remote_id,
            p_ccb->p_lcb->remote_bd_addr, package_len);
      }
      break;

L2CAP状态机处理接收到的L2CAP数据事件。负责检查数据包和远程通道的有效性,调用注册的回调函数(如果存在),对于确保数据的正确处理和上层应用的通知至关重要。

avdt_l2c_data_ind_cback

/*******************************************************************************
 *
 * Function         avdt_l2c_data_ind_cback
 *
 * Description      This is the L2CAP data indication callback function.
 *
 *
 * Returns          void
 *
 ******************************************************************************/
void avdt_l2c_data_ind_cback(uint16_t lcid, BT_HDR* p_buf) {
  AvdtpTransportChannel* p_tbl; // 包含有关AVDTP传输通道的信息

  /* look up info for this channel */
  p_tbl = avdt_ad_tc_tbl_by_lcid(lcid);
  if (p_tbl != NULL) {
    avdt_ad_tc_data_ind(p_tbl, p_buf); // 处理接收到的数据
  } else /* prevent buffer leak */
    osi_free(p_buf);
}

avdt_l2c_data_ind_cback 是一个回调函数,用于处理通过L2CAP接收到的数据。

avdt_ad_tc_data_ind

packages/modules/Bluetooth/system/stack/avdt/avdt_ad.cc
/*******************************************************************************
 *
 * Function         avdt_ad_tc_data_ind
 *
 * Description      This function is called by the L2CAP interface layer when
 *                  incoming data is received from L2CAP.  It looks up the CCB
 *                  or SCB for the channel and routes the data accordingly.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_ad_tc_data_ind(AvdtpTransportChannel* p_tbl, BT_HDR* p_buf) {
  AvdtpCcb* p_ccb;
  AvdtpScb* p_scb;

  /* store type (media, recovery, reporting) */
  p_buf->layer_specific = avdt_ad_tcid_to_type(p_tbl->tcid); // 设置数据类型

  /* if signaling channel, handle control message */
  if (p_tbl->tcid == 0) { // 处理信令通道数据
    p_ccb = avdt_ccb_by_idx(p_tbl->ccb_idx);
    avdt_msg_ind(p_ccb, p_buf);
    return;
  }
  /* if media or other channel, send event to scb */
  p_scb = avdtp_cb.ad.LookupAvdtpScb(*p_tbl); // 处理通过媒体通道或其他类型的通道发送的数据
  if (p_scb == nullptr) {
    log::error("Cannot find AvdtScb entry: ccb_idx:{} tcid:{}", p_tbl->ccb_idx,
               p_tbl->tcid);
    osi_free(p_buf);
    log::error("buffer freed");
    return;
  }
  // 将接收到的数据作为事件传递给SCB进行处理
  avdt_scb_event(p_scb, AVDT_SCB_TC_DATA_EVT, (tAVDT_SCB_EVT*)&p_buf);
}

根据接收到的数据的通道类型(信令通道或媒体通道),将数据路由到相应的控制块(CCB或SCB)进行处理。

avdt_msg_ind(AVDT_MSG_TYPE_RSP)

packages/modules/Bluetooth/system/stack/avdt/avdt_msg.cc
/*******************************************************************************
 *
 * Function         avdt_msg_ind
 *
 * Description      This function is called by the adaption layer when an
 *                  incoming message is received on the signaling channel.
 *                  It parses the message and sends an event to the appropriate
 *                  SCB or CCB for the message.
 *
 *
 * Returns          Nothing.
 *
 ******************************************************************************/
void avdt_msg_ind(AvdtpCcb* p_ccb, BT_HDR* p_buf) {
  AvdtpScb* p_scb;
  uint8_t* p;
  bool ok = true;
  bool handle_rsp = false;
  bool gen_rej = false;
  uint8_t label;
  uint8_t pkt_type;
  uint8_t msg_type;
  uint8_t sig = 0;
  tAVDT_MSG msg{};
  AvdtpSepConfig cfg{};
  uint8_t err;
  uint8_t evt = 0;
  uint8_t scb_hdl;

  /* reassemble message; if no message available (we received a fragment) return
   */
  // 消息重组
  p_buf = avdt_msg_asmbl(p_ccb, p_buf);
  if (p_buf == NULL) {
    return;
  }

  p = (uint8_t*)(p_buf + 1) + p_buf->offset; // 调整指针指向消息内容起始位置

  /* parse the message header */
  // 从缓冲区中提取并解析消息头,获取标签(label)、包类型(pkt_type)和消息类型(msg_type)
  AVDT_MSG_PRS_HDR(p, label, pkt_type, msg_type);

  log::verbose("msg_type={}, sig={}", msg_type, sig);
  /* set up label and ccb_idx in message hdr */
  msg.hdr.label = label;
  msg.hdr.ccb_idx = avdt_ccb_to_idx(p_ccb);

  /* verify msg type */
  if (msg_type == AVDT_MSG_TYPE_GRJ) {
    log::warn("Dropping msg msg_type={}", msg_type);
    ok = false;
  }
  /* check for general reject */
  else if ((msg_type == AVDT_MSG_TYPE_REJ) &&
           (p_buf->len == AVDT_LEN_GEN_REJ)) {
    gen_rej = true;
    if (p_ccb->p_curr_cmd != NULL) {
      msg.hdr.sig_id = sig = (uint8_t)p_ccb->p_curr_cmd->event;
      evt = avdt_msg_rej_2_evt[sig - 1];
      msg.hdr.err_code = AVDT_ERR_NSC;
      msg.hdr.err_param = 0;
    }
  } else /* not a general reject */
  {
    /* get and verify signal */
    AVDT_MSG_PRS_SIG(p, sig);
    msg.hdr.sig_id = sig;
    if ((sig == 0) || (sig > AVDT_SIG_MAX)) { // 如果信号无效
      log::warn("Dropping msg sig={} msg_type:{}", sig, msg_type);
      ok = false;

      /* send a general reject */
      if (msg_type == AVDT_MSG_TYPE_CMD) {
        avdt_msg_send_grej(p_ccb, sig, &msg);
      }
    }
  }
  
  // 消息内容解析
  if (ok && !gen_rej) {
    /* skip over header (msg length already verified during reassembly) */
    p_buf->len -= AVDT_LEN_TYPE_SINGLE;

    /* set up to parse message */
    if ((msg_type == AVDT_MSG_TYPE_RSP) && (sig == AVDT_SIG_DISCOVER)) {
      /* parse discover rsp message to struct supplied by app */
      msg.discover_rsp.p_sep_info = (tAVDT_SEP_INFO*)p_ccb->p_proc_data;
      msg.discover_rsp.num_seps = p_ccb->proc_param;
    } else if ((msg_type == AVDT_MSG_TYPE_RSP) &&
               ((sig == AVDT_SIG_GETCAP) || (sig == AVDT_SIG_GET_ALLCAP))) {
      /* parse discover rsp message to struct supplied by app */
      msg.svccap.p_cfg = (AvdtpSepConfig*)p_ccb->p_proc_data;
    } else if ((msg_type == AVDT_MSG_TYPE_RSP) && (sig == AVDT_SIG_GETCONFIG)) {
      /* parse get config rsp message to struct allocated locally */
      msg.svccap.p_cfg = &cfg;
    } else if ((msg_type == AVDT_MSG_TYPE_CMD) && (sig == AVDT_SIG_SETCONFIG)) {
      /* parse config cmd message to struct allocated locally */
      msg.config_cmd.p_cfg = &cfg;
    } else if ((msg_type == AVDT_MSG_TYPE_CMD) && (sig == AVDT_SIG_RECONFIG)) {
      /* parse reconfig cmd message to struct allocated locally */
      msg.reconfig_cmd.p_cfg = &cfg;
    }

    /* parse message; while we're at it map message sig to event */
    if (msg_type == AVDT_MSG_TYPE_CMD) {
      msg.hdr.err_code = err =
          (*avdt_msg_prs_cmd[sig - 1])(&msg, p, p_buf->len);
      evt = avdt_msg_cmd_2_evt[sig - 1];
    } else if (msg_type == AVDT_MSG_TYPE_RSP) {
      msg.hdr.err_code = err =
          (*avdt_msg_prs_rsp[sig - 1])(&msg, p, p_buf->len);
      evt = avdt_msg_rsp_2_evt[sig - 1];
    } else /* msg_type == AVDT_MSG_TYPE_REJ */
    {
      err = avdt_msg_prs_rej(&msg, p, p_buf->len, sig);
      evt = avdt_msg_rej_2_evt[sig - 1];
    }

    /* if parsing failed */
    if (err != 0) {
      log::warn("Parsing failed sig={} err=0x{:x}", sig, err);

      /* if its a rsp or rej, drop it; if its a cmd, send a rej;
      ** note special case for abort; never send abort reject
      */
      ok = false;
      if ((msg_type == AVDT_MSG_TYPE_CMD) && (sig != AVDT_SIG_ABORT)) {
        avdt_msg_send_rej(p_ccb, sig, &msg);
      }
    }
  }

  /* if its a rsp or rej, check sent cmd to see if we're waiting for
  ** the rsp or rej.  If we didn't send a cmd for it, drop it.  If
  ** it does match a cmd, stop timer for the cmd.
  */
  // 响应与拒绝的处理
  if (ok) {
    if ((msg_type == AVDT_MSG_TYPE_RSP) || (msg_type == AVDT_MSG_TYPE_REJ)) { 
      // 检查它们是否与当前CCB中的命令匹配
      if ((p_ccb->p_curr_cmd != NULL) && (p_ccb->p_curr_cmd->event == sig) &&
          (AVDT_LAYERSPEC_LABEL(p_ccb->p_curr_cmd->layer_specific) == label)) {
        /* stop timer */
        alarm_cancel(p_ccb->idle_ccb_timer);
        alarm_cancel(p_ccb->ret_ccb_timer);
        alarm_cancel(p_ccb->rsp_ccb_timer);

        /* clear retransmission count */
        p_ccb->ret_count = 0;

        /* later in this function handle ccb event */
        handle_rsp = true;
      } else {
        ok = false;
        log::warn("Cmd not found for rsp sig={} label={}", sig, label);
      }
    }
  }

  // 事件分发:根据解析得到的信号和事件类型,将事件分发到相应的CCB或SCB(流控制块)进行处理
  if (ok) {
    /* if it's a ccb event send to ccb */
    if (evt & AVDT_CCB_MKR) {
      tAVDT_CCB_EVT avdt_ccb_evt;
      avdt_ccb_evt.msg = msg;
      avdt_ccb_event(p_ccb, (uint8_t)(evt & ~AVDT_CCB_MKR), &avdt_ccb_evt);
    }
    /* if it's a scb event */
    else {
      /* Scb events always have a single seid.  For cmd, get seid from
      ** message.  For rej and rsp, get seid from p_curr_cmd.
      */
      if (msg_type == AVDT_MSG_TYPE_CMD) {
        scb_hdl = msg.single.seid;
      } else {
        scb_hdl = *((uint8_t*)(p_ccb->p_curr_cmd + 1));
      }

      /* Map seid to the scb and send it the event.  For cmd, seid has
      ** already been verified by parsing function.
      */
      if (evt) {
        p_scb = avdt_scb_by_hdl(scb_hdl);
        if (p_scb != NULL) {
          tAVDT_SCB_EVT avdt_scb_evt;
          avdt_scb_evt.msg = msg;
          avdt_scb_event(p_scb, evt, &avdt_scb_evt);
        }
      }
    }
  }

  /* free message buffer */
  osi_free(p_buf);

  /* if its a rsp or rej, send event to ccb to free associated
  ** cmd msg buffer and handle cmd queue
  */
  if (handle_rsp) {
    avdt_ccb_event(p_ccb, AVDT_CCB_RCVRSP_EVT, NULL);
  }
}

通过重组消息、解析消息头、验证消息类型和信号、处理响应与拒绝消息、分发事件以及释放资源等步骤,确保了AVDTP协议栈能够正确接收和处理来自蓝牙音频设备的控制消息

avdt_ccb_event(AVDT_CCB_RCVRSP_EVT)

前文有分析,直接看处理函数。

avdt_ccb_hdl_discover_rsp

packages/modules/Bluetooth/system/stack/avdt/avdt_ccb_act.cc
/*******************************************************************************
 *
 * Function         avdt_ccb_hdl_discover_rsp
 *
 * Description      This function is called when a discover response or
 *                  reject is received from the peer.  It calls the application
 *                  callback function with the results.
 *
 *
 * Returns          void.
 *
 ******************************************************************************/
void avdt_ccb_hdl_discover_rsp(AvdtpCcb* p_ccb, tAVDT_CCB_EVT* p_data) {
  /* we're done with procedure */
  p_ccb->proc_busy = false; // 表示当前的发现过程已经结束

  /* call app callback with results */
  // 调用应用回调,将发现的结果通知给应用层。
  (*p_ccb->proc_cback)(0, p_ccb->peer_addr, AVDT_DISCOVER_CFM_EVT,
                       (tAVDT_CTRL*)(&p_data->msg.discover_rsp),
                       p_ccb->BtaAvScbIndex());
}

处理来自对端设备的发现响应,并将这些响应转换为应用层可以理解的事件。通过这种方式,应用层可以得知对等设备支持哪些服务,进而决定如何建立连接和传输数据。

bta_av_proc_stream_evt(AVDT_DISCOVER_CFM_EVT)

前文有分析过。

bta_av_stream_evt_ok()转化为BTA_AV_STR_DISC_OK_EVT。

void bta_sys_sendmsg(void* p_msg) {
  if (do_in_main_thread(
          FROM_HERE,
          base::BindOnce(&bta_sys_event, static_cast<BT_HDR_RIGID*>(p_msg))) !=
      BT_STATUS_SUCCESS) {
    log::error("do_in_main_thread failed");
  }
}

bta_sys_sendmsg(BTA_AV_STR_DISC_OK_EVT)

        case BTA_AV_STR_DISC_OK_EVT:
          event_handler1 = &bta_av_disc_results;
          break;

bta_av_better_stream_state_machine

        case BTA_AV_STR_DISC_OK_EVT:
          event_handler1 = &bta_av_disc_results;
          break;

bta_av_disc_results接下一篇分析。

LOG关键字

AVDT_DiscoverReq|API_DISCOVER_REQ_EVT|SENDMSG_EVT|avdt_msg_send|L2CA_DataWrite|l2c_data_write|CST_OPEN|l2c_csm_open|MSG_DISCOVER_RSP_EVT

;