PBP协议解析:LE Audio公共广播的发现机制与公告级别优化
1. 先搞清楚一件事广播音频流凭什么被发现1.1 音频通道与发现通道是两条路如果你抓过 LE Audio 广播场景的空中包应该会发现一个很有意思的现象真正承载声音的 BISBroadcast Isochronous Stream广播等时流和用来让别人找到你的周期广播Periodic AdvertisingPA是两条完全独立的链路。BIS 干的是运输音频的活LC3 编码后的数据按固定的等时间隔ISO Interval从源端发出来而 PA 干的是宣告存在的活它周期性地告诉周围设备这里有一个广播它的参数是什么。接收端要做的第一步不是直接听音频而是先在广播信道里扫描到 PA 事件。PA 事件里除了带一段公告数据还携带 BIGInfo。BIGInfo 才是真正的同步钥匙里面包含 BIS 索引、PHY、信道映射、加密标志和时序偏移。拿生活中的场景类比BIS 是已经铺好的高速公路PA 就是路牌和地图接收端必须先看懂路牌才知道要去哪条车道、在哪个时隙接车。这里有个容易混淆的点LE Audio 广播用的是广播扩展Advertising Extensions机制而不是老的经典广播。设备先在主广播信道37/38/39上收到 ADV_EXT_IND再去辅助信道接收 AUX_SYNC_IND随后才能进入周期广播的锁定状态。很多新接触 LE Audio 的工程师会把主信道上的普通广播当成完整公告结果抓包时看到一堆零散广播包却始终找不到 BIGInfo就是这个链路没理清。1.2 没有统一公告协议时的四种乱象在 PBP 出现之前一个广播源如果想让普适的接收端发现自己基本只能靠各家私有方案工程上会碰到四类很现实的问题。第一是发现时延不可控。广播源可以自己选 PA 间隔有的栈默认 400ms有的为了省电放到 1s 以上而接收端扫描占空比也是各自为政。两者叠加后最坏情况是从用户进入覆盖区到接收端锁上广播要好几秒。在公共广播场景下这个时间意味着用户看着手机屏幕干等体验非常糟糕。第二是功耗双向浪费。想让别人快点发现自己广播源就得把 PA 间隔压短射频发射占空比直线上升接收端为了快一点发现别人就得拉长扫描窗口甚至连续扫描。双方都没有一个共同的速度档位可以协商只能互相过度配置。第三是公告内容不统一。有的把频道名塞在厂商自定义数据结构里有的把语言信息放在服务数据后段接收端应用要解析就得写一堆针对性的分支代码而且一旦广播源换了协议栈版本就解析失败。最终的后果是一个通用的广播接收 App 无法做到列出附近所有公共广播频道这种最基本的功能。第四是高密度场景没有过滤依据。场馆里同时有几十路广播时接收端必须把所有 PA 都解析一遍再把结果全部抛给 UI。没有稳定的短标识比如 16bit 的广播 ID做分组和过滤用户看到的就是一大串以 MAC 地址结尾的条目既难认也难选。PBPPublic Broadcast Profile就是冲着这四类问题来的。它把公告内容长什么样公告频率有多快接收端扫描行为应该满足什么下限这些散落的问题统一成一份可对照、可验收的协议要求。2. PBP 到底管什么协议边界与角色模型2.1 PBP 只标准化公告与发现这一小段做协议实现的人最怕一句话带过的 Profile因为搞不清边界就会把不该自己管的事也揽进来。PBP 的全称是 Public Broadcast Profile它不定义编解码器LC3 是独立编解码规范它不定义音频数据的组包方式那是 ISOAL 和 BAP 的范围它更不涉及单播连接里的音频流建立流程那是 ASCS/PACS 的职责。PBP 真正关心的就两件事第一公共广播源应该用什么格式、多高的频率对外发公告第二支持 PBP 的接收端应当以什么样的扫描行为去发现这些公告从而得到一个有上限的发现时延。你可以把它理解为给广播音频的广告牌统一了尺寸、内容和亮度等级但不负责广告牌后面那家店卖什么商品。这个边界设计是有意为之。LE Audio 协议族采用分层思路BAP 解决流能不能通PBP 解决公共流能不能被高效找到两者解耦后厂商可以只实现 BAP 的私有广播也可以两者都实现。实际开发中判断一个需求该不该放在 PBP 里做就看它是否影响发现效率这四个字。2.2 公共广播 vs 私有广播PBP 管哪一半PBP 名字里的 Public 不是随便加的。LE Audio 广播分两类一类是不加密的公共广播任何实现了对应接收能力的设备都可以直接同步收听典型场景就是场馆助听、车站广播这类开放音频另一类是加密的私有广播需要广播码Broadcast Code才能解开 BIS 负载典型场景是会议室投屏音频、需要访问控制的付费内容。PBP 明确只管前者。公共广播的最大特点是发现即得接收端不需要配对、不需要扫码、不需要任何带外信息交换只要扫到公告就能同步、出声。这既是 PBP 的产品价值也是它的协议约束来源——因为目标受众是任意设备公告格式和级别就必须标准化不能指望每个厂商都提前约定。私有广播的发现和密钥分享走的是另一套机制广播码通常通过 BASS 的 Set Broadcast Code 写入或者通过其他带外通道传递PBP 只是负责把这是私有广播、没有广播码就别试了这类信息在公告里表达清楚避免接收端盲目尝试同步浪费时间。2.3 Source / Sink / Controller 三种角色PBP 定义了三种角色实际设备通常身兼数职。Public Broadcast Source广播源负责配置 BIS、启动 PA、把 BAA 公告放进周期广播数据里并对外承诺某个公告级别。Public Broadcast Sink接收端负责扫描 PA、解析公告、按需同步 BIS、解码 LC3最后把声音交给上层播放。Public Broadcast Controller控制器则是一个管理角色典型形态是手机它自己不一定听音频但它通过 BASS 服务去控制旁边的耳机或助听设备告诉对方去扫这个广播源、去同步那个频道同时读回接收状态。把 Controller 单独拎出来是因为发现决策和音频渲染在真实产品里经常不在同一个设备上。耳机功耗预算小、没有屏幕不适合承担繁重的扫描和 UI 逻辑手机算力强、有界面适合做发现决策。这种角色拆分直接决定了 BASS 在 PBP 里的地位后面拆时序的时候会再回到这一点。3. 公告级别Announcement Level——发现效率的主旋钮3.1 PBAL 字段与三档语义PBP 最核心的机制是公告级别Announcement Level。广播源在公告数据里放一个单字节字段表示自己当前采用哪一档公告强度。这个字段对应的是 Public Broadcast Announcement Level我印象里特性 UUID 是 0x2BDB具体编码可以在蓝牙标准组织维护的官方 UUID 对照表里查到。级别通常分三档低Low、中Medium、高High。语义上低级别意味着可以被发现但对发现速度不做强承诺高级别意味着接收端应当在极短时间内就能锁上我。不同协议栈对枚举值的具体编码习惯不太一样有的用 0x00/0x01/0x02 表示低/中/高有的用 0x01/0x02/0x03还保留 0x00 表示未指定。拿到 SDK 后第一件事就是把 PBAL 的枚举映射确认清楚否则后面写配置工具时会对着数值发呆。为了说清楚三档的量级差别我整理了一张参考表。需要特别说明规范对每一档的精确上限要求以官方 PBP 文档为准下表数值是我在工程实践中看到的主流实现常用参考值用来理解数量级和做验收预判是够用的。公告级别典型语义PA 间隔参考上限适用场景Level 1 低可发现即可时延不敏感约 1s常驻场馆、电池供电的长时间广播Level 2 中几秒内有结果即可约 250ms大多数公共广播的默认档Level 3 高尽快锁定越快越好约 100ms 或更短临时广播、助听场景、需要快速重连的场景3.2 级别为什么能换算成最坏发现时延理解公告级别的关键在于意识到它本质上是一个最坏情况契约。接收端不知道广播源确切的 PA 间隔是多少它只需要知道对方承诺的级别所对应的间隔上限就能规划自己的扫描策略。举个简单的例子如果广播源承诺 Level 3PA 间隔上限约 100ms那么接收端只要保证自己在一个 PA 间隔内至少有一次有效的扫描窗口能与 PA 事件重叠最坏情况下最多等一个多周期就能收到公告。反过来如果广播源只有 Level 1PA 间隔可能到 1s接收端还按 100ms 的预算去设计扫描占空比就会白白浪费功耗如果接收端扫描太稀疏又可能连 1s 一次的公告都错过。所以 PBP 的优化发现效率并不是简单地把 PA 间隔压到最小而是给双方一个可计算、可对齐的档位。这跟网络里的 QoS 类似与其约定一个实际上做不到的固定值不如约定一个上限和一个等级语义让两端在功耗与速度之间找到适合自己的平衡点。另外要记住一个单位细节周期广播间隔的单位是 1.25ms所以 100ms 的 PA 间隔在协议字段里写作 80。很多同学第一次对着协议栈参数调 Level 3 时把 80 当成了 80ms结果发现广播源实际公告间隔比预期慢得多。3.3 不同场景的档位选择建议级别选择本质上是射频预算换发现速度。如果广播源是插电设备比如场馆里的信息屏、会议室的共享音频盒子功耗压力不大直接用 Level 3 换最好的用户体验即可。如果广播源是电池设备比如临时导游机、便携翻译器就要算一下射频开销PA 间隔从 250ms 缩到 100ms发射占空比翻了不止一倍续航会肉眼可见地下降这时候 Level 2 是更稳妥的默认档。还有一种工程上很实用的做法是分级启动设备进入待广播状态时先用 Level 3 高频公告一段时间让附近的接收端快速发现并完成同步等确认至少有一个接收端锁上之后再自动降到 Level 2 甚至 Level 1 维持存在。很多产品把这种模式包装成配对模式或发现模式PBP 协议上完全支持因为它要求的是上限而不是固定值源设备可以在同一级别约束内动态调整实际间隔。我个人的建议是把公告级别做成运行时可配置项不要写死在固件里。上线前验收测试时用 Level 3日常运行用 Level 2既能保证认证测试一次过又能兼顾无线卫生。4. BAA 公告数据结构与 Broadcast ID 的过滤价值4.1 BAA 的 AD 结构与字段说明PBP 定义的公告叫 Broadcast Audio AnnouncementBAA它放在周期广播事件的 AD Structure 里。从协议实现角度看BAA 对应的服务叫 Broadcast Audio Announcement ServiceBAAS服务 UUID 是 0x185D。有意思的是这个服务在多数实现里是虚拟服务主要存在于广播数据中不一定在 GATT 数据库里开一个真正可连接的 Server如果广播源本身是可连接的规范也允许通过 GATT 暴露相同服务让管理端主动读取公告级别。BAA 的数据结构非常紧凑主要包含三部分字段长度说明Service UUIDBAAS2 字节固定 0x185DPublic Broadcast ID2 字节广播源分配的短标识用于分组与过滤PBAL公告级别1 字节当前公告档位用 16 位 UUID 的 Service Data 类型AD Type 0x16表达一个完整的服务数据段看起来是这样06 16 5D 18 12 34 02其中 0x06 是长度0x16 是 Service Data16 位 UUID类型0x5D18 是 BAAS 的 UUID小端0x1234 是 Public Broadcast ID0x02 是 PBAL 公告级别。实际抓包里这段数据前面通常还会跟着 Flags、完整设备名等常规 AD 结构但核心就是这 7 个字节。从抓包工具里看Wireshark 或蓝牙分析仪一般会把这段解析成Service Data - 0x185D并尝试把后面的字段按 PBP 语义解出来。如果解析结果里看不到 Broadcast ID 或 PBAL而只有一串 Raw Data多半是广播源的协议栈没有按标准放字段或者字节序处理错了。4.2 Public Broadcast ID16bit 怎么用才不浪费Public Broadcast ID 设计成 16bit 是有讲究的。它不适合当作全局唯一标识因为空间太小同一场馆里很容易撞 ID它的价值在于分组和过滤。举个例子一个商场里可能有 5 个不同楼层的广播源它们同属一个运营方约定都用同一个 Broadcast ID 0x1234接收端解析到公告后就可以按 ID 把这些频道归为一组UI 上显示成商场广播而不是五条孤立的 MAC 地址。又比如一个博物馆里同时有中英文两路导览两路可以用两个不同 ID 区分用户选择语言时接收端只需要过滤对应 ID不用把另外几路的 BIS 全部同步一遍。工程上两个容易忽略的点第一Broadcast ID 一定要跟源设备地址联合使用。协议上真正唯一的标识是源 BD_ADDR Broadcast ID组合单纯按 ID 过滤会撞车。两个互不相关的广播源碰巧选了同一个 ID 时接收端靠 ID 过滤会把错误频道推给用户。我处理过的方案是 App 端用 ID 分组展示、用地址做最终判定两套信息都保留。第二场馆运营方最好做一次 ID 规划把同一区域的广播源分到固定 ID 段里避免现场广播源各自随机选 ID 导致一片混乱。4.3 BASS 协同让接收端之外的人做发现决策公告级别的效率优化如果没有 Controller 角色配合发挥空间会打折扣因为很多接收端耳机、助听器根本没有 UI发现了广播也不知道该不该连。PBP 在这个场景下依赖 Broadcast Audio Scan ServiceBASS服务 UUID 0x185F。接收端实现 BASS 后手机作为 GATT Client 连接它通过 Broadcast Audio Scan Control Point 写入添加广播源指令把目标源的地址和 Broadcast ID 告诉耳机耳机随后按指令去扫描和同步并把结果通过 Broadcast Receive State 特性回传给手机。对公共广播来说整个过程不需要广播码Controller 只需要知道哪个源、哪个 ID。这比耳机自己盲扫然后在两个耳塞间同步状态要省电得多也让发现这个动作从音频渲染链路里彻底解耦出来。5. 从 PA 到 BIS一次完整的 PBP 发现时序5.1 源端启动广播的四步动作站在广播源视角一次规范的 PBP 广播启动要依次做四件事先定音频参数再建 BIG再配 PA最后把公告挂上去。这里的顺序不是随便排的PA 必须先于 BIG 启动因为接收端需要先通过 PA 获知 BIG 的存在BIG 才能被找到。第一步确定 LC3 编码参数。公共广播常用的配置是 48kHz 采样率、10ms 帧长单声道码率约 64~128kbps可以根据音质目标和射频余量调整。第二步创建广播等时组BIG每个频道对应一个 BIS。ISO 间隔一般取与帧长一致的 10ms这样编码器每出一个帧BIS 事件正好发一次时延和缓冲都最好算。第三步配置周期广播。PA 间隔按公告级别设置比如 Level 3 用 100msPHY 用 1M 还是 Coded 根据覆盖距离来定Coded PHY 能传更远但每次发射时间更长。第四步把 BAA 服务数据填进周期广播的 AD 段然后启动 PA再启动 BIGBIGInfo 就会跟着 PA 事件周期性发出来。很多商业化协议栈把这几步封装成一个Start Broadcast接口参数一填就完事。但从调试角度我强烈建议把 PA 启动和 BIG 启动做成两个可观察的独立阶段至少在日志里能看到各自的开始时间不然后面排查抓包只有 BIG 没有 PA这类问题时根本不知道是哪个环节挂了。5.2 接收端逐步锁定广播的过程接收端的发现过程可以用一条状态链概括初始扫描 - PA 同步 - 公告解析 - BIGInfo 获取 - BIS 同步 - 出声。初始扫描阶段接收端在主广播信道扫描扫描窗口建议覆盖到至少一个完整 PA 周期。这个阶段是功耗大头也是级别发挥作用的地方如果接收端知道目标源承诺 Level 3可以把扫描窗口设置得比 Level 1 场景更激进。扫到 ADV_EXT_IND 后接收端转向辅助信道捕获 AUX_SYNC_IND这时才算真正进入PA 同步状态。PA 同步后接收端在 PA 事件里解析 BAA拿到 Broadcast ID 和 PBAL在应用层决定这个频道要不要继续。这一步是关键的分流点不感兴趣的公告在这里就被丢弃不会触发后续任何 BIS 同步动作功耗和 CPU 开销都省在这里。决定要继续后接收端等待下一个 PA 事件里的 BIGInfo解析出 BIS 索引、PHY、信道映射、加密标志然后在下一个 BIS 事件时把射频切到对应信道接收等时数据包通过 ISOAL 重组还原成完整的 LC3 帧送入解码器再经过一段去抖缓冲后出声。整个过程的端到端耗时可以粗算初始扫描检测约一个扫描周期PA 同步约一两个 PA 间隔BIGInfo 和第一个 BIS 事件最多再等两个周期最后还有接收端的渲染缓冲。按 Level 3 配置PA 间隔 100ms、扫描窗口 60ms 左右从接收端上电到耳朵里听到声音实测通常在 300~800ms 这个量级。如果超过这个量级很多优先怀疑扫描占空比设置和去抖缓冲。5.3 一组可用来验收的示例参数协议开发最怕的就是没有一组可复现的基准参数。我这里给出一组我在测试环境里常用的示例值可以直接拿来当验收基线参数示例值说明LC3 帧长10ms48kHz 采样下的标准帧长ISO 间隔10ms协议字段按 1.25ms 单位编码写 8PA 间隔Level 3100ms协议字段写 80PA 间隔Level 2250ms协议字段写 200接收端扫描窗口60ms需覆盖一个 PA 周期PA 同步超时2~10s连续收不到 PA 事件就判定失步去抖缓冲20~50ms吸收射频抖动和突发干扰最后再强调一次发现效率不等于音频时延。接收端锁上广播之后音频时延主要由 ISO 间隔和去抖缓冲决定和 PA 间隔没有关系。也就是说把公告级别从 Level 2 调到 Level 3只会让发现变快不会让已有连接的声音变快别拿它去优化音频延迟。6. 落地测试中的五个典型坑与对策6.1 公告级别的工程权衡快不是免费的第一类坑是把公告级别当成一个可以无限调大的参数。Level 3 确实发现快但代价是射频发射占空比上升。在一个多源密集的环境里十几个 Level 3 广播源同时高频发公告2.4GHz 频段的信道占用会明显恶化甚至影响同区域 Wi-Fi。我的对策是区分发现期和稳定期。发现期用 Level 3接收端锁上后广播源降级到 Level 2 甚至 Level 1。这个切换逻辑最好放在广播源端因为它掌握全局状态接收端不需要知道源什么时候降级它按之前的级别规划扫描即可。6.2 高密度环境下的过滤与信道卫生第二类坑是忽略多源场景的过滤效率。PBP 标准给了 Broadcast ID但如果接收端把每个 ID 都当成要同步的频道资源仍然会浪费。正确的做法是在 BAA 解析完成后、BIS 同步之前由应用层决定过滤名单固件只负责把过滤命中率高的广播保留下来。大场馆部署上我也建议运营方把广播源的发射功率做一次统一约束。公告覆盖范围应该与音频可收听范围一致而不是盲目开满功率。最典型的故障是PA 用低功率发射、BIS 用高功率发射接收端站在中间地带听得到音频却永远发现不了广播这在抓包里表现得很诡异。6.3 用协议分析仪验证的检查点第三类坑是看起来在广播实际没按 PBP 广播。用蓝牙协议分析仪抓包时我建议至少核对五个检查点BAA 服务数据是否正确出现在 AUX_SYNC_IND 里Broadcast ID 和 PBAL 字段是否按预期编码PA 间隔是否稳定且不超过对应级别上限BIGInfo 是否随 PA 周期性出现BIS 索引和 PHY 是否匹配加密标志是否为未加密。公共广播最隐蔽的问题是加密位不一致广播源在 BIGInfo 里标了加密但在 BAA 里没有同步说明接收端发现后尝试同步失败表现就是 UI 上能看到频道但永远连不上。这类问题不抓包很难定位因为连接阶段的失败日志在各家栈里五花八门。6.4 协议栈实现差异与固件版本的坑第四类坑是不同协议栈对 PBP 的支持深度参差不齐。有些芯片宣称支持 LE Audio 广播但固件只实现了 BAP 层面的 BIS 收发BAA 公告结构要么没有、要么字段顺序不符合标准。碰到这种情况不能靠猜必须用协议分析仪抓包验证以抓包结果为准而不是以 SDK 文档的支持 PBP字样为准。第五类坑是 BASS 指令兼容性。Controller 通过 BASCP 添加广播源时不同实现支持的指令参数格式有差异比如源地址携带方式、Broadcast ID 的字节序。我的经验是先拿一个已认证的参考接收端做互操作测试确认 Controller 端的指令封装没问题后再接入自家接收端否则两边同时排查会非常耗时。7. PBP 与相邻 Profile 的协作边界7.1 与 BAPPBP 是上层公告层BAPBasic Audio Profile定义了广播音频的角色、数据通路和同步基础PBP 则在这些基础之上增加了一套面向公共发现的公告与行为约束。你可以把 BAP 理解成管道规范PBP 是管道上贴的标签规范。实现上PBP 源一定实现了 BAP 的广播角色但反过来不成立——只实现 BAP 的广播源可以通过带外方式比如扫码让接收端获得同步参数只是没法被通用设备自动发现。7.2 与 BASS被管理接收端的必经之路PBP 没有重新发明一套远程发现机制而是明确复用 BASS。接收端如果要支持 Controller 管理就必须实现 BASS 服务Controller 则作为 GATT Client 使用它。这两个 Profile 的分工是BASS 管指令通道PBP 管公告语义。手机上看到附近公共广播列表的体验是 PBP 的公告让列表内容变得可解析、可分类是 BASS 让耳机真正按列表去执行同步。7.3 PBP 明确不负责的事最后把不归 PBP 管的清单列一下方便你排查问题时少走弯路。私有广播的密钥交换与广播码管理归 BAP 私密广播和带外机制管。单播场景的音频流建立与编解码协商归 ASCS/PACS 管。左右耳同步成一套立体声设备归 CSIP 管。助听器特有的精细化调参归 HAS 管。音量同步和媒体控制链路则在单播场景里归对应的媒体控制 Profile 管。把这些边界记住遇到PBP 怎么还管不了这个的疑问时先翻一下问题到底出在哪一层再去对应规范里找答案比在 PBP 文档里反复找不存在的内容高效得多。最后再说一个实测里很有用的习惯把接收端从扫描开始到解码器出第一帧的时间戳打出来写成一个自动化打点脚本每轮改完参数跑一遍。我有几次调 PBP 参数靠抓包看一切正常就是用户感知入耳慢最后都是靠这个端到端时间戳脚本定位到扫描占空比和去抖缓冲的问题。验收时用 Level 3、日常运行用 Level 2、上线前用多源并发场景压一遍这套组合拳打下来广播音频的发现效率问题基本就不会再找上门了。