当前位置: 首页 > news >正文

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

1. 背景

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

// 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 中看到如下 信息:


908	2025-04-24 15:56:16.700694	controller	host	HCI_EVT	8	Rcvd Number of Completed PacketsBluetooth HCI Event - Number of Completed PacketsEvent Code: Number of Completed Packets (0x13)Parameter Total Length: 5Number of Connection Handles: 1Connection Handle: 0x0001Number 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 状态;
  • 对应逻辑函数如:
// 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 了

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_ 回调

// system/gd/hci/controller.ccvoid 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

// system/gd/hci/acl_manager.ccstruct 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 对象。

// system/gd/hci/acl_manager/round_robin_scheduler.ccRoundRobinScheduler::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 包已完成,可以继续发更多包了)

// system/gd/hci/acl_manager/round_robin_scheduler.ccvoid 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_

// system/gd/hci/controller.ccvoid 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);
}

相关文章:

  • Flink CEP是什么?
  • 系统架构设计(十四):解释器风格
  • 开始学习做游戏,就现在
  • 写spark程序数据计算( 数据库的计算,求和,汇总之类的)连接mysql数据库,写入计算结果
  • 【数据结构】2-3-2 单链表的插入删除
  • JSON Schema 高效校验 JSON 数据格式
  • 翻到了一段2005年写的关于需求的文字
  • ⭐️白嫖的阿里云认证⭐️ 第二弹【课时1:提示词(Prompt)技巧】for 「大模型Clouder认证:利用大模型提升内容生产能力」
  • 软件工具:批量图片区域识别+重命名文件的方法,发票识别和区域选择方法参考,基于阿里云实现
  • HarmonyOS 与 OpenHarmony:同根而不同途
  • Kubernetes控制平面组件:Kubelet详解(六):pod sandbox(pause)容器
  • Kubernetes控制平面组件:Kubelet详解(五):切换docker运行时为containerd
  • 【提高+/省选−】洛谷P1495 —— 【模板】中国剩余定理(CRT)/ 曹冲养猪
  • 游戏引擎学习第291天:跳跃的怪物与占据的树木
  • Linux搜索
  • 【ubuntu24.04】pycharm 死机结束进程
  • 正则表达式 - 语法
  • Trae IDE和VSCode Trae插件初探
  • 第6章 实战案例:基于 STEVAL-IDB011V1 板级 CI/CD 全流程
  • PyTorch音频处理技术及应用研究:从特征提取到相似度分析
  • 国家主席习近平任免驻外大使
  • 河南发布高温橙警:郑州、洛阳等地最高气温将达40℃以上
  • 竞彩湃|足总杯决赛或有冷门,德甲欧冠资格之争谁笑到最后
  • 马上评|科学红毯,让科学家成为“最亮的星”
  • 【社论】城市更新,始终以人为核心
  • 夜读丨母亲为燕子打开家门