【android bluetooth 协议分析 01】【HCI 层介绍 3】【NUMBER_OF_COMPLETED_PACKETS 事件介绍】

1. 背景

我们在介绍 【android bluetooth 框架分析 02】【Module详解 8】【Controller 模块介绍】 中介绍 Controller 模块初始化流程时,在 Controller::impl::Start 函数中会看到 我们对 NUMBER_OF_COMPLETED_PACKETS 事件做了 专门的监听:

c 复制代码
// system/gd/hci/controller.cc

	/*
	2. 注册 NumberOfCompletedPackets 回调
		1. 注册一个事件处理器,用于接收控制器发送的"已完成的数据包数"通知
		2. 该事件用于流控管理(host 根据完成数量决定是否继续发送数据)。
	*/
    hci_->RegisterEventHandler(
        EventCode::NUMBER_OF_COMPLETED_PACKETS, handler->BindOn(this, &Controller::impl::NumberOfCompletedPackets));

这里就专门介绍一下 NUMBER_OF_COMPLETED_PACKETS 事件在蓝牙协议中的作用。

2. NUMBER_OF_COMPLETED_PACKETS 事件介绍

我们会在 btsnoop 中看到如下 信息:

c 复制代码
908	2025-04-24 15:56:16.700694	controller	host	HCI_EVT	8	Rcvd Number of Completed Packets

Bluetooth HCI Event - Number of Completed Packets
    Event Code: Number of Completed Packets (0x13)
    Parameter Total Length: 5
    Number of Connection Handles: 1
    Connection Handle: 0x0001
    Number of Completed Packets: 1

NUMBER_OF_COMPLETED_PACKETS 是一个 标准 HCI 事件 ,由 Controller(蓝牙芯片)发送给 Host(主机,通常是操作系统或上层协议栈),事件码为:0x13

一个 NUMBER_OF_COMPLETED_PACKETS 事件数据结构中包含:

字段 说明
Num_Handles 有多少个连接要上报完成数量
Connection_Handle[i] 第 i 个连接的句柄
Host_Num_Completed_Packets[i] 第 i 个连接完成的包数量(credits)

这个事件可以一次上报多个连接的完成情况。


1. 作用:

  1. 释放 Host Buffer(流控恢复)

蓝牙数据通过 ACL 数据包传输时,Host 必须跟踪发送出去的每一个包。如果控制器没有 buffer,Host 必须停下来不发。

  • Host 发送一个 ACL 数据包 → credits -1;
  • 当 Controller 成功发出这个包(或者丢弃),它就发 NUMBER_OF_COMPLETED_PACKETS 告诉 Host;
  • Host 收到后,credits +1,就可以继续发送更多的数据。

这种机制是流控的关键。

  1. 节省资源,避免 buffer overflow
  • 控制器中的 buffer 数量是有限的;
  • 如果没有 NUMBER_OF_COMPLETED_PACKETS 通知,Host 会不停地发数据,最终会 冲爆控制器 buffer,导致丢包或者崩溃
  • 所以这个事件起到"释放资源、允许继续发送"的作用。

2. 应用场景举例

场景:手机播放蓝牙音频时

  1. 手机作为 Host,不停地通过 ACL 通道发送 A2DP 音频数据包;
  2. 每发一个包就消耗一个 credit;
  3. 控制器发送完数据后,会定期发 NUMBER_OF_COMPLETED_PACKETS
  4. Host 收到后,就知道可以继续发更多的数据,音频才能持续不断。

可以把它类比为:

  • 快递公司(Controller) 告诉你(Host)哪些包裹已经送达;
  • 你就可以 清空这些包裹在仓库中占的空间(buffer)
  • 如果你之前限流了(怕仓库爆了),现在可以重新发货(继续发数据)。

3. 如果没有这个事件会怎样?

  • Host 无法知道 buffer 是否释放,可能发太多数据 → 崩溃
  • Host 一直等待 → 蓝牙数据传输停滞、卡顿
  • 流控失效 → 丢包、延迟、音频断续

所以它是整个蓝牙传输链路中 不可或缺的一环


4. 协议栈中如何处理它?

在蓝牙协议栈实现中(如 Android、BlueZ、Zephyr 等):

  • 通常会注册对 NUMBER_OF_COMPLETED_PACKETS 的监听;
  • 在事件到来时调用回调,更新 ACL buffer 状态;
  • 对应逻辑函数如:
c 复制代码
// system/gd/hci/controller.cc

	/*
	2. 注册 NumberOfCompletedPackets 回调
		1. 注册一个事件处理器,用于接收控制器发送的"已完成的数据包数"通知
		2. 该事件用于流控管理(host 根据完成数量决定是否继续发送数据)。
	*/
    hci_->RegisterEventHandler(
        EventCode::NUMBER_OF_COMPLETED_PACKETS, handler->BindOn(this, &Controller::impl::NumberOfCompletedPackets));

当我们 收到 Controller 发给我们的 NUMBER_OF_COMPLETED_PACKETS 事件时,就会回调 Controller::impl::NumberOfCompletedPackets 方法。

3. NumberOfCompletedPackets

这个函数是用来处理 HCI 事件 的,事件的类型是 NUMBER_OF_COMPLETED_PACKETS ------ 即:控制器(Controller)告诉主机(Host)某些 ACL 数据包已经传输完成,可以释放 buffer 了

c 复制代码
  void NumberOfCompletedPackets(EventView event) {
    /*
    1. 检查回调是否存在
	    1. acl_credits_callback_ 是一个回调函数对象,代表着 ACL 管理器在监听 Controller 反馈的传输完成信息。
	    2. 如果这个回调是空的(即没有注册),说明此时没人关心 ACL buffer 的返回情况。
	    3. 打个警告日志,然后直接返回,不再处理事件。
    */
    if (acl_credits_callback_.IsEmpty()) {
      LOG_WARN("Received event when AclManager is not listening");
      return;
    }

    /*
    2. 解析事件数据结构
	    1. 使用静态工厂函数 NumberOfCompletedPacketsView::Create(event) 从原始 EventView 对象中构造出更专门的解析类 NumberOfCompletedPacketsView。
	    2. NumberOfCompletedPacketsView 是对原始 HCI Event 的封装,内部封装了解析逻辑,比如提取连接句柄、完成的数据包数量等
	    3. ASSERT(complete_view.IsValid()); 确保事件结构合法(比如长度、格式等没有问题)。如果不合法,直接 crash(这是调试期行为,正式版一般不会 ASSERT)
    */
    auto complete_view = NumberOfCompletedPacketsView::Create(event);
    ASSERT(complete_view.IsValid());


	/*
	3.遍历完成的连接句柄与 packet 数量
		1. connection_handle_:哪个连接的包完成了。
		2. host_num_of_completed_packets_:有多少个包完成了(主机现在可以回收 buffer)。
		3. 对每个连接调用回调函数
			1. 把 handle 和 credit 数量传给 acl_credits_callback_
			2. 这个回调函数由上层的 AclManager 注册,它的作用是:通知 buffer 管理器或流控管理器:可以释放 credits,继续发数据了。
			3. acl_monitor_credits_callback_ 是另一个可选的回调,可能用于监控、日志记录或调试。
				1. 如果存在,也会调用,传入相同的 handle 和 credits
	*/
    for (auto completed_packets : complete_view.GetCompletedPackets()) {
      uint16_t handle = completed_packets.connection_handle_;
      uint16_t credits = completed_packets.host_num_of_completed_packets_;
      acl_credits_callback_.Invoke(handle, credits);

	  
      if (!acl_monitor_credits_callback_.IsEmpty()) {
        acl_monitor_credits_callback_.Invoke(handle, credits);
      }
    }
  }

这个函数负责处理 HCI 层的 Number Of Completed Packets 事件。该事件是 Host Controller Interface(HCI)标准规定的一种机制,用于告诉 Host 哪些连接上的数据包已成功完成,可以释放对应的 buffer。

在 Bluetooth HCI 层,每个 ACL 数据包发送出去之前,Host 需要确认 Controller 是否还有空余 buffer。

  • Host 维护一个 credits(信用额度)计数器;
  • 每发送一个 ACL 包,credits -1;
  • 一旦收到 Controller 发来的 Number Of Completed Packets 事件,表示有包成功发出,credits +N;
  • 如果 credits 用完了,Host 就不能再发数据了(除非收到回报);
  • 这个机制就叫做 flow control(流控)

所以本函数的实质意义是:恢复 Host 的发送能力

1. 回调介绍

NumberOfCompletedPackets 中总共有 两个回调:

  1. acl_credits_callback_
  2. acl_monitor_credits_callback_

下面来看一下这两个回调 是在核实注册的

1. acl_credits_callback_ 回调

c 复制代码
// system/gd/hci/controller.cc

  void register_completed_acl_packets_callback(CompletedAclPacketsCallback callback) {
    ASSERT(acl_credits_callback_.IsEmpty());
    acl_credits_callback_ = callback;
  }

void Controller::RegisterCompletedAclPacketsCallback(CompletedAclPacketsCallback cb) {
  CallOn(impl_.get(), &impl::register_completed_acl_packets_callback, cb);
}

acl_credits_callback_ 最终是通过调用 RegisterCompletedAclPacketsCallback 还设置的。

RegisterCompletedAclPacketsCallback 是谁调用触发的?

  • 答案是 AclManager
c 复制代码
// system/gd/hci/acl_manager.cc

struct AclManager::impl {
  impl(const AclManager& acl_manager) : acl_manager_(acl_manager) {}

  void Start() {
    hci_layer_ = acl_manager_.GetDependency<HciLayer>();
    handler_ = acl_manager_.GetHandler();
    controller_ = acl_manager_.GetDependency<Controller>();
    round_robin_scheduler_ = new RoundRobinScheduler(handler_, controller_, hci_layer_->GetAclQueueEnd());
    ...
  }
  

在 AclManager::impl::Start 函数中,创建了一个 RoundRobinScheduler 对象。

c 复制代码
// system/gd/hci/acl_manager/round_robin_scheduler.cc

RoundRobinScheduler::RoundRobinScheduler(
    os::Handler* handler, Controller* controller, common::BidiQueueEnd<AclBuilder, AclView>* hci_queue_end)
    : handler_(handler), controller_(controller), hci_queue_end_(hci_queue_end) {

	...

  controller_->RegisterCompletedAclPacketsCallback(handler->BindOn(this, &RoundRobinScheduler::incoming_acl_credits)); // 这里注册的
}

在 RoundRobinScheduler 构造函数中调用 Controller::RegisterCompletedAclPacketsCallback 将 RoundRobinScheduler::incoming_acl_credits 注册给 acl_credits_callback_

当收到 NUMBER_OF_COMPLETED_PACKETS 事件,就会触发对 incoming_acl_credits 函数的调用

1. incoming_acl_credits

用于处理 控制器通过 NUMBER_OF_COMPLETED_PACKETS 事件上报的 ACL 信道 credits(也就是控制器告诉 Host,哪些 ACL 包已完成,可以继续发更多包了)

c 复制代码
// system/gd/hci/acl_manager/round_robin_scheduler.cc

void RoundRobinScheduler::incoming_acl_credits(uint16_t handle, uint16_t credits) {

  // 从 acl_queue_handlers_ 容器中查找对应的连接句柄 handle 的记录。这个容器保存着每个连接的发送状态信息。
  auto acl_queue_handler = acl_queue_handlers_.find(handle);
  if (acl_queue_handler == acl_queue_handlers_.end()) {
    // 如果这个 handle 没有记录,说明是个非法或意外的上报,直接返回,避免空指针操作。
    return;
  }

  /*
  更新发送记录:
	  1. number_of_sent_packets_ 是 Host 记录 "自己发送了多少还未完成的包"
	  2. credits 表示 Controller 告诉 Host 有这么多个包完成了;
	  3. 已发未完成的数量 >= 如果完成的数量,就正常减掉;
	  4. 否则说明 Controller 上报了超额的完成包数,可能是 bug 或同步异常,打印警告并重置为 0
  */
  if (acl_queue_handler->second.number_of_sent_packets_ >= credits) {
    acl_queue_handler->second.number_of_sent_packets_ -= credits;
  } else {
    LOG_WARN("receive more credits than we sent");
    acl_queue_handler->second.number_of_sent_packets_ = 0;
  }

  // 标记变量:记录在增加 credit 前是否为 0,如果是 0,说明之前因为 credit 不足而停发了数据包,后面需要重新启动 round-robin 调度。

  bool credit_was_zero = false;
  if (acl_queue_handler->second.connection_type_ == ConnectionType::CLASSIC) {
    // 是传统 Bluetooth BR/EDR(Classic)连接
    
    if (acl_packet_credits_ == 0) {
      // 如果之前为 0,说明之前已经 卡住 了,要恢复调度
      credit_was_zero = true;
    }
    // 增加 credit,表示可以继续发数据
    
    acl_packet_credits_ += credits;
    if (acl_packet_credits_ > max_acl_packet_credits_) {
      // 上限保护:不要超过最大允许的 credit 数量,避免 credit 超限导致逻辑错误
      acl_packet_credits_ = max_acl_packet_credits_;
      LOG_WARN("acl packet credits overflow due to receive %hx credits", credits);
    }
  } else {
    // 是 Bluetooth Low Energy(LE)连接, 同理,更新 LE ACL credit 数量
    
    if (le_acl_packet_credits_ == 0) {
      credit_was_zero = true;
    }
    le_acl_packet_credits_ += credits;
    if (le_acl_packet_credits_ > le_max_acl_packet_credits_) {
	  // 对 LE 信道也做 credit 溢出保护。
      le_acl_packet_credits_ = le_max_acl_packet_credits_;
      LOG_WARN("le acl packet credits overflow due to receive %hx credits", credits);
    }
  }
  if (credit_was_zero) {
    // 如果之前 credit 为 0(即我们因为没信用值停了发包),现在重新获得了 credit,就要 重启 round-robin 轮询机制,开始发送队列中的数据包。
    start_round_robin();
  }
}

它是 Bluetooth ACL 流控的核心环节之一:

  • 由 Controller 发出 NUMBER_OF_COMPLETED_PACKETS
  • Host 调用此函数更新状态;
  • 恢复调度发送挂起的数据包。

它确保了 Host 和 Controller 之间对 buffer 使用的精确同步,是高效传输和系统稳定运行的关键一环

2. acl_monitor_credits_callback_

c 复制代码
// system/gd/hci/controller.cc

  void register_completed_monitor_acl_packets_callback(CompletedAclPacketsCallback callback) {
    ASSERT(acl_monitor_credits_callback_.IsEmpty());
    acl_monitor_credits_callback_ = callback;
  }
void Controller::RegisterCompletedMonitorAclPacketsCallback(CompletedAclPacketsCallback cb) {
  CallOn(impl_.get(), &impl::register_completed_monitor_acl_packets_callback, cb);
}
相关推荐
用户20187928316716 小时前
ANR之RenderThread不可中断睡眠state=D
android
煤球王子16 小时前
简单学:Android14中的Bluetooth—PBAP下载
android
小趴菜822717 小时前
安卓接入Max广告源
android
齊家治國平天下17 小时前
Android 14 系统 ANR (Application Not Responding) 深度分析与解决指南
android·anr
ZHANG13HAO17 小时前
Android 13.0 Framework 实现应用通知使用权默认开启的技术指南
android
【ql君】qlexcel17 小时前
Android 安卓RIL介绍
android·安卓·ril
写点啥呢17 小时前
android12解决非CarProperty接口深色模式设置后开机无法保持
android·车机·aosp·深色模式·座舱
IT酷盖17 小时前
Android解决隐藏依赖冲突
android·前端·vue.js
努力学习的小廉18 小时前
初识MYSQL —— 数据库基础
android·数据库·mysql
风起云涌~19 小时前
【Android】浅谈androidx.startup.InitializationProvider
android