☰
BASS服务深度解析:LE Audio广播音频的发现与接入机制
2026/10/24 6:00:18 网站建设 项目流程

做 LE Audio 广播类产品这几年,有一件事我感受特别深:大家讨论最多的永远是 LC3 编码、BIS 同步这些偏底层的技术,但真正让“广播音频”从一个概念变成可用体验的,反而是协议栈上层那个不起眼的服务——BASS(Broadcast Audio Scan Service,广播音频扫描服务)。没有它,你身边那些没有屏幕、没有操作界面的耳机和助听器,根本不知道附近有一条广播流存在,更别说主动接入进去了。

BASS 的核心作用,简单说就是让“有能力交互的设备”去帮“没有能力交互的设备”完成广播音频的发现、选择、同步和状态管理。手机当遥控器,耳机去执行,这是 LE Audio 广播场景里最常见也最实用的形态。这篇文章基于我在实际项目里的协议梳理与开发经验,把 BASS 的角色关系、GATT 服务结构、状态流转、握手细节和踩坑记录都摊开来讲,适合蓝牙协议栈开发、音频固件开发、产品测试以及刚接触 LE Audio 生态的同行参考。不需要你有很深的蓝牙基础,但至少要知道 GATT 特征、广播包这类基本概念。

1. 定位复盘:BASS 到底解决了广播音频里的什么问题

1.1 广播音频的“发现-接入”链路

传统蓝牙音频是连接式音视频分发,手机跟耳机建立 ACL 连接,再走 A2DP 把音频数据传过去。这套模式稳定,但天然是一对一。LE Audio 引入的广播音频不一样,它把音频打散成一个个等时数据包,用 BIS(Broadcast Isochronous Stream,广播等时流)向周围撒,任何支持 LE Audio 的接收器都可以在广播覆盖范围内独立收流。这就是一对多、多对多的共享音频基础。

但广播音频最大也最隐蔽的问题在于:它没有“握手”。广播源根本不知道谁在听,也不关心接收器加入或离开。这时候会产生一个现实需求:接收器得先知道“附近有没有想要的广播”“广播在哪一个物理信道上发”“数据包间隔和重传参数是什么”,才能去做同步。放在传统蓝牙里,这些信息可以通过连接交互一问一答拿到,但在广播音频里没有这种对话通道。唯一的信息来源是广播包本身以及周期性广播同步信号,接收器得靠扫描去解析这些内容。

问题是,很多接收器形态的尴尬由此暴露。耳机、助听器这类设备,为了把功耗压到极低,往往不会持续做全频段扫描;同时它们没有屏幕,也没有合适的交互入口,用户不可能在上面完成“选择某一路广播并订阅”这种动作。于是协议栈上就需要一个中间层,让手机/平板/手表这些有计算能力和交互界面的设备,代替接收器去完成广播扫描辅助工作。BASS 就是为这件事定义的。

1.2 为什么“广播接收器”自己无法完成这件事

把问题再往下拆一层,你会发现真正的瓶颈不在同步算法,而在“谁来决定并下发同步意图”。接收器如果自己扫描,能拿到广播源地址和同步信息,但拿不到用户的收听意图。它不知道自己该收哪一路广播,也不知道该同步哪几个 BIS 索引。这些决策信息对纯音频播放设备来说,属于没必要常驻的附加功能。让一个只负责解码、播放的声学设备去管理“频道切换”的复杂逻辑,无论从产品定义还是系统资源上都不划算。

于是协议里做了一个很巧妙的分工:接收器仍然保底保留基础扫描能力,能自己发现并接入公开广播;但是对于更复杂的覆盖、切换、多流订阅场景,它可以把状态上抛给一个“广播助手(Broadcast Assistant)”。这个助手角色通常由手机承担,它发现接收器身边的广播流,并通过 BASS 服务把同步指令写进接收器。接收器收到指令后,再自己完成对目标广播源的同步。也就是说,BASS 把“决策”和“执行”彻底分离了。

这种分离带来的直接好处是协议分层足够干净。接收器固件只要实现一个标准服务和配套状态机,不需要为每种交互场景定制逻辑。而手机、平板、甚至带屏幕的遥控器,因为它们天然有网络、显示和用户操作能力,可以统一实现 BASS 客户端,去操控任何支持该服务的接收设备。这样一来,整套体验的上限由“屏幕设备”决定,而不是由那个只带一两个按键的耳机决定。

2. BASS 服务体系拆解:角色、特征与状态机

2.1 角色边界与实现载体

BASS 本身没有定义“广播源”侧的协议行为,它管的是接收器和助手之间的交互。协议里明确了几类角色,顺着应用场景去理解会非常清楚。

  • 广播源(Broadcast Source):通过 BIG 建立广播等时流,周期性广播同步信号和音频包的设备。一般是无屏的媒体网关、音频路由器、电视伴侣之类的设备。
  • 广播接收器(Broadcast Receiver):实际接收并解码 BIS 音频流的设备,同时也是 BASS 服务端(GATT Server)。典型代表是耳机、助听器、音箱。
  • 广播助手(Broadcast Assistant):具备用户交互能力或网络能力的设备,在同一个 LE Audio 场景里负责为接收器“安排”广播同步任务,它是 BASS 客户端(GATT Client)。典型代表是手机 App、系统设置里的音频切换面板、专用遥控器。

注意,一个物理设备可以同时承担多个角色。手机既可以作为广播源把本地音乐广播给附近的耳机,也可以作为普通接收器直接听广播,可以作为 BASS 客户端去控制手表上的音频流。所以在实际实现里,不能把角色跟设备型号绑定,要按逻辑实例去划分。

在手机充当广播助手的场景,手机和耳机之间的连接通常是一条 LE ACL 连接,BASS 就跑在这条连接上。但音频数据不在这条连接上传,它依然通过耳机和广播源之间的 BIS 收到。所以你会看到一种“控制面归控制面,数据面归数据面”的分流设计,两者互不干扰。这种架构的价值在做低功耗产品时尤其突出,接收器不需要长驻高速连接,只要在需要交互时才跟 BASS 客户端通信。

2.2 GATT 服务结构与三个核心特征

BASS 服务在 GATT 层暴露了三个关键数据入口,分别是广播接收状态、广播接收状态表,以及已修改的广播接收状态向量。这三个特征各司其职,理解了它们,BASS 的使用方式就懂了一大半。

广播接收状态特征,用于告诉 BASS 客户端“接收器目前正在接收哪些广播”。它的数据结构是一组列表项,每个列表项对应一条广播流,包含广播源地址、地址类型、广播同步状态、PA 同步状态、BIS 同步状态、当前正在同步的 BIS 索引列表等信息。这个特征通常还要带 Notification 配置,广播源状态发生切换时主动通知客户端,省去客户端反复轮询的功耗开销。

广播接收状态表特征,是 BASS 客户端下达指令的入口。客户端需要在这个表里添加条目,把希望接收器同步的广播源地址、期望同步的 BIS 索引(或者将广播标识符解析后的元数据)写进去。接收器收到这个写入请求后,会解析条目内容并执行同步。不难理解,这个特征需要支持 Write/Write Long,而且写入的数据内容有严格的格式约束,填错一个状态位都可能让同步中止。

已修改的广播接收状态向量特征,是一个精简的通知通道。接收器用它向客户端报告“状态表里哪些条目在上次读取后发生了变化”。它不携带完整状态数据,只带标志位,客户端收到后如果有需要,再去主动读完整的广播接收状态特征。这种设计在广播源较多、列表项较长的场景里非常省流量,也是做多接收器并行调试时的重要依据。

此外,GATT 层还要配合客户端特征配置描述符(CCCD)和服务器特征配置描述符(SCPD)的配置。很多初做 BASS 的同事会在这一步卡壳:只实现了特征本身,忘掉把通知描述符放开,结果设备端状态无论怎么变化,客户端始终收不到任何事件。这类坑会在第四章专门列出来。

2.3 典型状态机与数据传输路径

BASS 的交互流程不像点对点音频那样有复杂的连接握手,它的状态流转集中围绕“同步意图”的写入与回报。以一个手机助手控制耳机接入广播的完整过程为例,状态机会经过这么几步。

第一步,手机通过通用发现流程找到要控制的耳机,并建立 LE 连接。第二步,手机在连接上发现 BASS 服务,通过读“广播接收状态”特征拿到耳机当前已接收的广播列表;如果列表为空,说明耳机处于空闲状态。第三步,手机通过扫描周边环境,发现了一个公开广播源,并从广播包解析出同步信息。第四步,手机向“广播接收状态表”写入一个新条目,把这个广播源的地址、期望同步的 BIS 索引等参数塞进去。第五步,耳机收到写入请求后,启动与广播源的同步流程,包括同步周期性广播同步信号,再建立 BIG 同步拿到 BIS。第六步,耳机完成同步后,更新自身的“广播接收状态”特征,并通过 Notification 告诉手机“状态变化了”。第七步,手机收到通知后读取“已修改的广播接收状态向量”,发现变化条目的索引,再反查完整状态,确认耳机已经进入了广播音频接收态。

这个状态机的最关键一点是,接收器自己不会主动发起“接收决策”,一切同步行为的触发源,都来自状态表里出现的新条目。反向地,如果你想让耳机停止接收某一路广播,也是通过删除或更新状态表对应条目来完成。可以说,BASS 的整个状态机就是“客户端写出意图,服务端执行并回报状态”的闭环。理解了这个闭环,后面所有协议细节都能对号入座。

3. 实操推演:一套广播接收系统从配置到出声的完整过程

3.1 广播源侧参数与 BIG 建立

先看广播源这一侧。假设你有一台音频路由器,想把一段音频通过 LE Audio 广播给周围耳机。设备首先要通过 HCI 指令创建一个 BIG(Broadcast Isochronous Group),并指定 BIG 事件间隔、重传次数、BIS 的映射、编解码配置、同步延迟等参数。这些参数会直接影响到接收端的同步开销和抗干扰能力,实际选择时非常讲究。

比如 BIG 事件间隔,它决定广播源多久发送一组等时数据。间隔越小,音频延迟越低,但接收器要频繁唤醒收包,功耗明显上升。间隔越大,功耗越友好,但接收器要做较长时间的定时同步才能跟上事件边界,稍有漂移就会丢包。重传数也很关键,广播环境里射频冲突不可避免,BIS 默认支持网络级重传(BIG Retransmission)来降低丢包率。设置重传时,别忘了它同样占用射频资源,重传次数加得过高会拉长每个事件持续时间,反而挤占了下一个广播事件的发射窗口。

从广播包的结构看,接收器可以发现一个主广播承载的指向信息,拿到“广播音频”服务的数据,然后根据辅助广播特征去同步周期性广播同步信号。周期性广播同步信号里包含了 BIG 广播的绝对时基映射,接收器据此计算出第一个 BIS 事件在本地时间轴上的位置。实际开发里,我们为了兼容低端芯片,通常会把广播包里的 AdvHandle、SID、SyncInfo 整理成参数模板,方便在 BASS 状态表写入之后,直接交给底层同步服务去复用。否则每次都要临时解析广播包,流程会变得既慢又容易出错。

3.2 广播助手如何帮接收器接入 BIS

现在重点推演 BASS 的实际搬砖过程。场景很简单,某用户戴着一副耳机(BASS 服务端),手里拿着一台已经配对过的手机(BASS 客户端),附近有一台广播源正在循环输出公共广播音频。

手机先与耳机建立 LE 连接。这个连接可以走普通 LE 流程,关键是连接参数不能太激进。因为后续要在这个连接上做状态表和通知的读写,如果连接间隔太长,手机发一条 BASS 指令到耳机要等一个半连接事件才能收到响应,体验会非常迟钝。我们通常建议把连接间隔控制在 30ms 到 50ms 之间,既不过度占用射频,又能保证交互时延可控。

连接建立后,手机做 GATT 发现,找到 BASS 服务的三个特征。接着读“广播接收状态”特征,确认当前耳机没有接收任何广播。然后手机开始扫描附近的广播源。这一步并不是 BASS 协议强制规定的,但在产品逻辑上缺不了,因为手机总得先知道附近有哪些流可选。看到目标广播后,手机解析广播包拿到广播源地址、同步信息,也拿到该广播包含的 BIS 数量和每个 BIS 的配置。之后,手机把目标广播源的信息整理成一个状态表条目,写入耳机的“广播接收状态表”特征。

耳机收到这条写入后,会先解析状态的地址类型和地址字段;如果地址是公开广播能够直接解析的,就尝试与该广播源的周期性广播同步信号做同步。同步成功后,耳机继续按 BIG 信息建立等时同步,把指定的 BIS 拉起来,开始解码音频。最后耳机更新本地“广播接收状态”特征,并通过 Notification 推送给手机,手机端界面从“未接收”变成“正在广播收听中”。

这里有个容易被忽略的次序问题:手机必须在写入状态表前确保自己解析到了正确的同步信息,尤其是 BIS 索引列表。如果 BASS 客户端只把广播源地址写进去,而没写 BIS 索引,接收器不知道自己该拉哪些流,可能只同步了一个广播事件却不送音频给解码器。我们项目里因此走了一段时间的弯路,后来统一要求:所有通过 BASS 接入的广播都必须显式带出 BIS 索引集合,就算只带一个索引,也要写明确。

3.3 公共广播与私有广播的差异处理

广播音频有两种形态,一种是完全公开的,比如商场广播或校园广播,接收器通过扫描就可以直接接入,不需要任何凭证。另一种是私有广播,广播源会对音频流做加密,接收器必须拿到广播码或密钥才能解密并同步。BASS 在处理这两种广播时,侧重点完全不同。

公开广播场景,BASS 客户端的职责基本就是“发现、选项、写入、回报”,整个过程没有秘密信息交换,安全性压力很小。手机只要能解析广播包里的同步参数,就能指挥任何一台支持 BASS 的耳机接入。这种场景对建立 BIG 时的重传参数、信源同步稳定性要求高,但协议逻辑最简单。

私有广播场景,难点转移到密钥传输。广播码不能直接放在普通广播包里被任何人读取,必须通过已经建立的安全连接,由一个可信的 BASS 客户端下发给接收器。BASS 状态表条目里要携带广播码信息,接收器拿着这个码去做解密同步。这个过程要求手机和耳机之间的 LE 连接加密且经过配对认证,否则广播码存在被中间人截获的风险。不少产品把“允许手机控制耳机接入私有广播”功能做成安全敏感操作,接入前强制弹窗让用户确认,道理就在这。

公共广播和私有广播还存在一个混合形态:同一路 BIS 流,部分流公开,部分流加密。例如某个混合广播,把伴奏作为公开 BIS,把主唱人声作为私有 BIS,听者可以选择只收伴奏或者同时订阅两个流。BASS 状态表要为每一条 BIS 分别标记是否需要解密信息,这也意味着接收器内部要对同一个 BIG 事件里的不同 BIS 做差异化处理。这种场景在实现芯片驱动时最容易出问题,建议先在 PC 上做原型验证,熟悉了混合订阅的状态流转之后再移到嵌入式平台。

4. 开发排雷:BASS 实践经验、常见问题与测量手段

4.1 实现踩坑实录

做 BASS 实现,我踩过最深刻的一个坑是“特征写了但没彻底实现属性”。GATT 层上,广播接收状态表如果要支持客户端写入,特征属性里必须包含 Write 或 Write Long。部分协议栈封装库里,默认只开放了 Read 和 Notify,导致手机端明明看到了这个特征,一写入就返回“请求不支持”。排查这类问题最简单的方法是,先抓取 GATT 层的 ATT Write 响应的错误码,如果返回 0x06(Attribute Not Found)或者 0x80(Application Error),先别怀疑手机端代码,回头检查服务端固件的特征配置矩阵。

第二个坑出现在长特征写入上。BASS 状态表在管理多路广播、多 BIS 的情况下,单条 Attribute Value 可能超过默认 MTU 大小。GATT 客户端需要执行 Prepare Write 和 Execute Write 的“长写”流程,服务端则必须支持偏移写入,并按顺序缓存分段数据。很多实现把 MTU 协商到了 247 或更高,就以为不会触发长写,但实际中广播源数量一多,单条目照样会撑爆一个 ATT 包。正确姿势是同时处理“短写”和“长写”两条路径,在协议栈回调里对长写做重新组装,并在组装完成后触发状态表更新逻辑。

第三个坑和通知有关。已修改的广播接收状态向量,以及广播接收状态本身的 Notification,都需要客户端先在 CCCD 里把通知开关打开。如果手机端没有写 0x0100 到描述符,无论服务端怎么变化,手机都收不到任何事件。这个坑在联调阶段特别阴险,因为特征读出来是正常的,命令写下去也是成功的,但界面始终不刷新。排查方法很简单,找一台逻辑分析仪抓 LE 连接上的 ATT Handle Value Notification,如果抓不到包,就先看 CCCD 状态。

第四个坑集中在并发同步能力。有些低端耳机的蓝牙芯片同时只能维护一个 BIG 同步,如果状态表里被写入了两个或更多广播流条目,固件要么直接拒绝后续条目,要么在切换时出现数秒静默。产品设计上一定要在实现前明确“最大同步流数”,并在 BASS 状态表写入前做校验,接收器对自己的能力边界要有硬性回合,而不是默默失败。

第五个坑和时间同步有关。BASS 客户端写入同步参数后,接收器立刻去同步周期性广播同步信号。但广播源的事件时钟和接收器本地时钟本来就是异步的,如果接收器底层没有做好绝对参考时基换算,就会出现看似同步成功但听不到声音的问题。调试这种场景时,最直观的办法是打印 HCI 层同步事件里的“同步偏移量”以及每次同步建立的时间戳,对比接收器侧维护的“广播事件本地计数”有没有跳变。一旦发现大量跳变,基本可以断定是底层时钟漂移补偿没有接上。

4.2 常见问题速查表

聊了这么多,把我在联调里遇到的典型问题整理成了一张速查表,按“现象-可能原因-处置方式”的格式列出来,方便你遇到同类问题时直接对照。

现象可能原因处置方式
手机发现不了 BASS 服务固件没有在广播/连接后公开服务;GATT 数据库未包含 BASS检查服务 UUID 注册;确认特征属性有 Read/Write/Notify
写入广播接收状态表返回失败特征不支持 Write Long;MTU 协商值过小;状态表条目格式错位抓取 ATT 错误码;适配长写流程;按规范字段逐位校验
状态表写入成功,但没有音频BIS 索引集合没写全;同步参数解析错误;广播源加密但未传广播码核对 BIS 索引;确认广播码字段;打印 HCI 同步事件状态码
耳机已经出声,手机端界面未更新广播接收状态特征的通知开关没打开;服务端状态变化后没有发送 Notification确认 CCCD 写 0x0100;检查服务端是否调用状态通知接口
多广播源切换时延迟大接收器需要先断开当前同步,再建立新同步提前释放旧 BIG 同步;优化状态表写入顺序
私有广播始终无法解密广播码下发时机不对;连接未加密就尝试同步确认连接已通过配对和加密;广播码在状态表中按正确字段放置

这张表只能覆盖高频问题,实际项目里还会有芯片底层的奇偶性问题,但大方向都离不开特征属性、写流程、状态通知、同步参数和加密处理这五个维度。排查的时候优先抓 HCI 层事件,比在应用层猜来猜去高效得多。

4.3 可复用的验证手段与调试建议

BASS 的验证不能只靠“听响不响”。推荐一套我一直在用的验证路径,从功能覆盖到异常注入,能有效减少现场回归问题。第一步,准备两台支持 LE Audio 的接收器设备,一台作为纯接收器,一台同时开启 BASS 服务端和广播源能力,让接收器能同时扮演“控制端”与“被控端”,覆盖角色切换。第二步,用手机端抓包工具,观察 GATT 发现、状态表写入、Notification 上报这三段关键行为,确认时序符合协议预期。第三步,做异常输入测试,比如清空状态表后让耳机进入广播接收状态,再随机删除状态表条目,看设备是否还能正常回到空闲态或报出正确错误码。

抓包是必须掌握的技能。我习惯用支持蓝牙 HCI 日志的手机做全量抓取,再把日志导入到抓包分析工具里解析 ATT、L2CAP 层面的交互。分析时重点看三条命令序列:GATT Discover,ATT Write Request/Response,ATT Handle Value Notification。如果这三条序列在日志里都完整出现,且顺序正确,BASS 基本就通了。如果发现在 Write 之后一直没有 Notification,多半卡在服务端状态机的同步流程内部,这时候需要回到固件侧打印同步日志。

最后给个很实用的小建议:在实际调试过程中,不要直接在“广播接收状态”特征里同时塞过多广播流。规范虽然允许列表项比较多,但每增加一个条目,接收器内部就要多维护一条同步上下文,内存和 CPU 开销非线性上升。产品功能上宁可通过“列表覆盖”的方式管理,也就是客户端在写入新广播源前,先删除不再需要的旧条目,再添加新条目。这样做既符合接收器侧能力模型,也能显著降低并发同步导致的时钟漂移概率。

我在实际项目里体会到,BASS 的复杂不在于它的协议定义有多长,而在于它把广播音频的“决策面”和“执行面”切开之后,两端都需要严格对齐状态认知。作为广播助手那一端,你得确保 BASS 客户端写入的参数足够准;作为接收器那一端,你得确保状态变化能及时同步并上报。很多“听不到声音”“切换卡顿”的问题,追到最后往往不是编解码问题,而是状态表维护和 Notification 时序没有做好。把这套机制吃透,再去设计共享音频、助听辅听、公共广播类的产品,你会发现原本看起来最麻烦的“一拖多”音频接入问题,其实早就有了一条清晰可靠的解决路径。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询