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. 作用:
- 释放 Host Buffer(流控恢复)
蓝牙数据通过 ACL 数据包传输时,Host 必须跟踪发送出去的每一个包。如果控制器没有 buffer,Host 必须停下来不发。
- Host 发送一个 ACL 数据包 → credits -1;
- 当 Controller 成功发出这个包(或者丢弃),它就发
NUMBER_OF_COMPLETED_PACKETS
告诉 Host; - Host 收到后,credits +1,就可以继续发送更多的数据。
这种机制是流控的关键。
- 节省资源,避免 buffer overflow
- 控制器中的 buffer 数量是有限的;
- 如果没有
NUMBER_OF_COMPLETED_PACKETS
通知,Host 会不停地发数据,最终会 冲爆控制器 buffer,导致丢包或者崩溃; - 所以这个事件起到"释放资源、允许继续发送"的作用。
2. 应用场景举例
场景:手机播放蓝牙音频时
- 手机作为 Host,不停地通过 ACL 通道发送 A2DP 音频数据包;
- 每发一个包就消耗一个 credit;
- 控制器发送完数据后,会定期发
NUMBER_OF_COMPLETED_PACKETS
; - 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 中总共有 两个回调:
- acl_credits_callback_
- 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);
}