1. 内容整体设计与思路拆解
1.1 这次项目要解决的问题是什么
做蓝牙开发这么久,我一直觉得“一对一连接”这件事限制了蓝牙的想象力。无论耳机、音箱还是助听器,蓝牙音频走的基本都是经典蓝牙A2DP,或者现在的LE Audio点对点连接。两个设备之间必须先握手、配对、建立连接,然后才能传声音。这个链路稳定是稳定,但在某些场景里真的不够用——比如一群人想同时听同一段广播,比如观众席里想听同传翻译,比如机场里想听某个登机口的最新通知,靠点对点连接完全没戏。
Auracast就是补上这个短板的方案。它是LE Audio里的广播音频技术,核心是把音频流以广播的形式发出去,周围任何支持Auracast的设备都可以“订阅”收听,不需要配对,不需要握手,来多少设备就能服务多少设备。BT2106C就是一颗支持这套协议栈的蓝牙SoC模组,我们要做的这块板子,就是让音频信号通过模组转换成Auracast广播,再用手机或耳机验证实际收听效果。
这个项目适合谁看?一个是正在做音频类硬件、想把广播音频加进来的嵌入式工程师,另一个是想评估Auracast能不能落地到自己产品里的产品经理或技术负责人。我会把从模块选型、硬件设计、协议栈配置到实测踩坑的完整过程都过一遍,很多细节是单看datasheet和SDK文档发现不了的。
1.2 为什么选BT2106C而不是自研方案
先说结论:这个项目的时间窗口很紧张,自研方案的第一版流片风险完全不可控,所以直接选了模组方案。BT2106C这块模组的核心优势,首先是协议栈完整度,Auracast必须依赖LE Audio的同步广播链路,也就是BIS(Broadcast Isochronous Stream),模组SDK里已经把同步广播的收发链路封装好了,省掉了从零啃协议栈的周期;其次是射频性能,PCBA上的天线匹配和生产一致性已经调好,比自己做板子再花两周调天线要省心得多。
模块党还有个隐藏好处在于认证。产品要做BQB认证和各类无线认证,用模组可以直接继承模组厂做过的射频认证报告,自己整板只需要关注产品层面的EMC和音频指标。这是很多团队第一次做蓝牙音频品类时容易忽略的成本项,别小看这份报告,它能省下好几万认证费和两三周整改时间。
模块本身主要关注这几个维度就够了:内核主频和Flash/RAM大小决定功能扩展空间,是否内置音频Codec或者需要外挂,以及SDK是否支持后续OTA升级。我这边因为要同时兼顾低功耗和后期算法迭代,所以选了性能稍高一点的版本,Flash留余量很重要,后期想加个EQ、降噪算法都方便,别把存储卡太死。
2. 核心细节解析与实操要点
2.1 硬件层容易被低估的三个地方
第一是晶振精度。Auracast广播依赖接收设备在指定时间窗口内同步接收数据包,如果发射端晶振漂移太厉害,接收端同步就会断续甚至丢包。BLE音频相关模块一般要求32.768kHz的RTC晶振和32MHz的主晶振精度尽量做到±20ppm以内,这个不能贪便宜,普通晶振贴上去,实测距离一拉开就掉同步,很难排查。
第二是射频周边。Wi-Fi/BLE共存的天线布局要提前想清楚,BT2106C这类模组一般引出RF pin,外接天线或者直接使用板载PCB天线。如果产品里还有4G模组或者WLAN模块,注意分时控发以及天线隔离度,否则互扰情况下Auracast广播会周期性丢失,表现就是“滋滋啦啦”。
第三是音频输入链路。我这块板子的输入源是Line In和板载驻极体麦克风,两颗都进了一颗外置Audio Codec(I2S接口),再送给BT2106C编码广播。这个链路里最容易踩坑的是模拟地分割和Codec供电纹波,Codec的模拟Vref如果贴着DCDC的开关噪声,SNR会被吃掉好几个dB,听感上就是底噪明显。第一次打板我把Codec供电直接挂在系统3.3V上,底噪达到了难以接受的水平,后来换成独立LDO再加磁珠隔离,才把底噪压下去。
硬件上还有一个好习惯:预留Type-C接口的USB调试串口。Auracast开发过程中需要反复看协议栈日志,没串口等于瞎调。我把UART和SWD都引到了Type-C座,一根线供电、烧录、看日志全搞定,开发效率提升非常明显。
2.2 协议栈配置里最容易踩的坑
Auracast的协议栈分层可以简单理解为一个“发广播”的外层广播加一个“传音频”的内层同步流。设备首先要周期性广播一组“音频广播的元数据”,内容包括广播名、语言、音频参数等,接收端(比如手机)扫描到之后,才会去同步对应的BIS流。
关键配置在于三个地方。
第一个是广播与同步广播的数据包间隔。这个影响的是端到端延迟与抗干扰能力。间隔越大延迟越高,但同时功耗越低、抗同频干扰能力也相对好。我们最后采用广播间隔100ms、BIS同步间隔10ms的配置,延迟体感大约在100~200ms之间,这个规格我认为是目前功耗、延迟、稳定性比较平衡的一组值。
第二个是LC3编码器参数。Auracast广播音频统一用LC3编码,我们用的是48kHz采样、单声道、80ms帧间隔、96kbps码率。这个码率在语音广播和音乐广播里都够用,单声道也合理——耳机端本来就是双耳接收同一路广播,没必要发双声道浪费带宽。如果想提升听感可以上128kbps,但距离和抗干扰余量会小一点。
第三个是广播元数据的规范命名。你不起一个规范的广播名和分类,手机上的Auracast扫描界面很难把频道识别成“公开广播”还是“私人广播”。演示给别人看的时候,一个乱码的广播名会显得很不专业。我建议把广播名设置成产品名加语言代码,比如“Meeting Room CN”,方便后续对接Auracast App等工具的过滤。
2.3 用到的测试工具与整体环境
调试阶段强烈建议准备三样东西:一台支持LE Audio的Android手机(Android 13以上)、一台iPhone(iOS 17以上),以及一台支持Auracast接收的TWS耳机。Android这边我用的nRF Connect做底层广播观测,再加Auracast官方的Scratch app做订阅收听;iPhone的Experience Auracast app也可以,两者功能差异不大,但Android能看到更多底层参数,所以我主力用安卓调试。
有人会问没有Auracast耳机怎么办,其实不完全依赖耳机也能验证链路。先用能接收的耳机确认“广播音频依然可用”,再用手机App确认“扫描并订阅Auracast广播”,这两个能力基本能覆盖链路的主要节点。耳机端如果提示“广播暂时不可用”,多半是接收端兼容性问题,跟发射端链路不一定直接相关,要独立排查。
3. 实操过程与核心环节实现
3.1 开发环境搭建与工程结构
BT2106C的SDK是厂商基于Zephyr RTOS定制的,编译工具链用west + GNU Arm Embedded Toolchain。这个组合对于用惯了STM32裸机开发的人会有点陡峭,但别慌,本质上就是“配置设备树 + 写应用层回调”。
我这边工程结构大致是这样:board目录放板级设备树,改I2S、GPIO、电源引脚;audio目录放Codec驱动适配层;broadcast目录放Auracast广播参数配置和应用回调。编译一次大约两分钟,迭代效率完全能接受。
烧录方式两种:一种是用J-Link通过SWD直接烧,另一种是生成量产固件后用串口OTA。OTA一定要在开发早期就接入,否则每次改参数都得开壳接线,会让人崩溃。
3.2 广播配置核心代码思路
下面是广播链路初始化的关键配置思路,我注释了每个参数在实测中的表现影响,大家可以根据自己场景改数值。代码只是示意简化版,完整实现还是要看SDK里的broadcast_demo。
/* 周期性广播参数:决定接收端能否稳定发现广播 */ static const struct bt_le_adv_param adv_param = { .id = BT_ID_DEFAULT, .options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_EXT_ADV, .interval_min = 100, /* 广播间隔,实测100ms比较稳 */ .interval_max = 100, }; /* BIS同步流参数:决定音频数据包发送节奏 */ static struct bt_audio_broadcast_create_param broadcast_param = { .subgroup_count = 1, .subgroup = &subgroup_param, .stream_param = &stream_param, /* framing = BT_AUDIO_FRAMING_LE, LC3配置见stream_param */ }; /* LC3编码参数:48kHz/单声道/80ms帧/96kbps */ static struct bt_audio_codec_cap codec_cap = { .id = BT_AUDIO_CODEC_LC3, .data = lc3_preset_data, };这里有三个细节:
- 广播间隔别贪小,曾经为了“被扫描到更快”改成30ms,结果功耗飙升,接收端在嘈杂环境里反而更容易丢同步;
BT_LE_ADV_OPT_CONNECTABLE这一步不是必须的,但如果之后想用手机App做参数配置或固件升级,保留连接广播能给后续开发留口子;- 音频帧间隔80ms和LC3编码器内部的帧长度是配套的,改动要同时看两端,只改一边会直接起不了流。
3.3 广播启动与音频采集联动
音频采集侧我单独跑了一个线程,从Codec的I2S RX读PCM数据,经过重采样和增益处理后塞进环形缓冲区,广播任务再从缓冲区取数据交LC3编码并送入协议栈。这里最核心的联动是缓冲区的容量设计和回调节奏,不能让音频任务写溢出,也不能让广播任务长时间读空。
我调试时用了一个笨但有效的办法:先在缓冲区写一个固定的正弦波序列,不接真实音源,广播后用耳机听播放出来的音调正不正常。如果音调对了,说明整条链路时序正常;如果音调变调或出现“嚓嚓”声,说明采样率或帧长度设置有偏差。这个方法强烈推荐,它能在没有音频分析仪器的情况下快速区分是“数字链路问题”还是“模拟前端问题”。
音频数据从PCM到LC3是固定码率的,所以网络带宽很稳定。下面这段是实际运行时开启广播音频流的关键调用顺序,缺少任何一步都会导致“广播发出来了但耳机收不到声音”。
/* 1. 创建广播音频流并注册回调 */ bt_audio_broadcast_create(&create_param, &bcast); /* 2. 启动周期性广播,接收端才能发现广播 */ bt_le_adv_start(&adv_param, adv_data, adv_data_len, NULL, 0); /* 3. 更新广播元数据(广播名、语言等) */ bt_audio_broadcast_update_base(bcast, &base_param); /* 4. 提交音频数据到编码器 */ while (1) { pcm_read(&buf); /* 从I2S取得PCM数据 */ lc3_encode(&buf, out_packet); bt_audio_broadcast_send(bcast, out_packet, size); }出错最多的就是第3步:有人把所有步骤都写了但没更新广播Base信息,结果手机App能看到一个空广播,没有音频参数,订阅按钮是灰的。排查这类问题看SDK日志里的base update status,基本一查一个准。注解里写的需求我尽量给了直接可用的代码,但不同SDK版本API名会有差异,还是以你的SDK版本文档为准。
3.4 性能实测数据
测试场地在一块相对空旷的办公区,手机和模块之间没有金属隔断。实测数据如下:
| 测试项 | 测试条件 | 结果 |
|---|---|---|
| 广播发现距离 | 空旷区域,手机扫描 | 约25米内稳定可见 |
| 音频播放距离 | 空旷区域,Auracast耳机接收 | 18米内无断续 |
| 穿越一堵隔墙 | 5米距离,隔混凝土墙 | 可听但偶有卡顿 |
| 延迟体感 | 播放节拍器信号 | 约120ms,感知明显但可接受 |
| 连续播放稳定性 | 1小时持续播放 | 无掉线、无同步丢失 |
空旷区域能跑到18米其实已经超出预期,但穿墙衰减依然明显,这是蓝牙2.4G频段的物理特性决定的。如果产品定位是公共广播,发射端功率可以再往上调,但注意当地法规对蓝牙发射功率的上限要求,不要盲目加大。中等距离下的稳定性优先级高于绝对距离,我的建议是先保证20米内极稳定,再去挑战30米。
4. 常见问题与排查技巧实录
4.1 手机完全搜不到Auracast广播
这个问题出现频率最高。先别怀疑模块,从接收端开始排查:手机是否支持LE Audio,Android手机对Auracast的支持受厂商定制影响很大,系统更新之后也有可能被砍功能;手机系统设置里是否开启了“扫描附近设备”权限;如果你用的是国行手机,部分品牌默认没有开启蓝牙广播扫描的功能,需要在开发者选项里单独打开。
排除了手机之后,用nRF Connect看模块有没有发出周期性广播。如果能看到周期性广播但看不到Audio Broadcast就说明广播扩展包有问题,重点检查bt_audio_broadcast_update_base。如果周期性广播都没有,回到配置来看adv是否启动成功,这个一般日志里能看出来。
4.2 能搜索到广播但耳机没声音
能搜到广播说明元数据链路是通的,耳机没声音大概率是BIS流同步或音频格式不匹配。最典型的例子是LC3采样率和帧间隔不匹配。耳机端如果只支持48kHz/10ms帧,你SDK里配了48kHz/80ms帧,常规情况下它也会尝试解码,但耳机的软件实现可能对长帧支持并不完整。
另外检查是不是把双声道数据发到了单声道接收的链路。有些TWS耳机在Auracast模式下默认按单声道解析,你发了立体声LC3,它会解出来但声音很奇怪。我的建议是广播一律用单声道,兼容性比立体声好太多。如果之后必须做立体声,一定要用支持对应参数的耳机测试。
4.3 播放一会儿后周期性卡顿
这种“间歇性卡顿”我排查了好几天,最终定位到两个原因。
第一个是周围Wi-Fi频段干扰。2.4G Wi-Fi的20MHz带宽信道和蓝牙广播信道的频率存在交叠可能,尤其办公区路由器密集。解决办法是通过产品配置软件把广播信道调整到相对干净的信道上,或者开启模组的共存机制,让Wi-Fi和蓝牙分时调度。硬件上如果天线位置冲突,干扰会比软件更严重,需要检查天线布局。
第二个是缓冲区水位管理问题。我的音频采集线程和广播发送线程速率如果有微小偏差,长时间运行后缓冲区要么溢出要么读空,听起来就是每隔几十秒卡一下。解决方法是把环形缓冲区加大到能容纳400ms以上的音频数据,并做动态水位补偿:水位低于20%时发送端把帧间隔适当拉长,水位高于80%时加快读取。这个策略调好之后,连续播放1小时再没出现卡顿。业余条件下可以用长时播放录音来定位卡顿的周期,再结合日志确认是不是水位触发。
4.4 手机App显示订阅成功但延迟很大
延迟取决于整条链路:采集端缓冲、LC3编码器算法延迟、BIS广播间隔、接收端解码与耳机缓冲。模块这边的可调项主要是BIS同步间隔。如果应用场景是口译广播,延迟超过150ms就建议改成5ms或更密的广播间隔,但功耗会上升,抗干扰余量也会下降。取舍逻辑很简单:延迟敏感场景优先保延迟,稳定优先场景保间隔。
接收端耳机的缓冲策略你控制不了,有的耳机为了抗干扰故意做了较大的缓冲,这会造成一部分延迟。所以做延迟对比测试时,用同一副耳机测不同模块参数才有横向可比性,千万别拿一副耳机延迟去对标另一副耳机的数据,产品实测时对这个差异要保持清醒。
4.5 想要产品化还需要做什么
开发板跑通只是第一步。真正要落地成产品,还要做这些事情:第一,整合低功耗策略,Auracast广播如果一直满功率发送,功耗会比较可观,需要考虑触发式广播、多状态电源管理;第二,把配置工具从调试串口升级成BLE连接配置,用户通过App改广播名、改语言、调整音量增益都走无线通道;第三,申请BQB认证时产品清单里明确用到的模块型号和固件版本,模组认证能简化流程但不能完全替代产品认证;第四,量产测试不能只测射频指标,建议产线上增加“广播能被手机搜索到”的功能测试项,避免个别模组贴歪天线或焊接不良导致出厂就哑火。
我自己的体会是,这个项目最大的收获不在Auracast本身,而在于它让我把Zephyr的协议栈、设备树、音频数据通路完整贯通了一遍。Auracast目前还处于早期应用阶段,接收端生态正在快速补齐,手机上原生的广播扫描已经是标配,后续这个方向的应用会越来越多。如果你手头正好有BT2106C或者其他支持LE Audio的模组,强烈建议先按我上面的思路跑通一版广播,然后加上自己场景里的音频源和交互逻辑,你会很快看到这项技术在产品中的真正价值。