Sniff 是经典蓝牙三个节能模式里用得最多的一个。TWS 耳机待机时能不能省电、A2DP 播放时会不会断音,都和 Sniff 参数直接相关。这篇拆解 Sniff 的三个核心参数,以及 TWS 场景下怎么调才平衡。
TL;DR
- Sniff 模式让从设备周期性"醒来"监听,其余时间休眠。三个参数:Interval(醒的周期)、Attempt(醒多久)、Timeout(额外等多久)。
- 参数调优的核心矛盾:Interval 越大越省电,但响应延迟越高;Attempt 越大越稳,但功耗越高。
- TWS 待机场景 Interval 可以拉到 500ms-1s,A2DP 播放场景要压到 40ms 左右,HFP 通话不能用 Sniff。
- Sniff Subrating 允许在不退出 Sniff 的情况下动态调整 Interval,实现"待机省电、有数据快速响应"。
目录
- 为什么要用 Sniff 模式
- Sniff 的工作原理
- 三个核心参数详解
- Sniff Subrating 机制
- HCI 命令与 LMP 交互
- TWS 场景参数调优
- 抓包分析 Sniff
- 常见问题排查
1. 为什么要用 Sniff 模式
经典蓝牙的 ACL 链路在 Active 状态下,从设备每个时隙都要监听主设备是否发数据——即使没有数据,RF 和基带也一直开着。这对于电池供电的设备(TWS 耳机、手表、传感器)是巨大的功耗浪费。
蓝牙定义了三个节能模式来降低功耗:
| 模式 | 功耗 | 响应延迟 | 适用场景 |
|---|---|---|---|
| Hold | 中 | 中 | 短时间挂起(如协商加密) |
| Sniff | 低 | 可调 | 长时间连接但数据稀疏(TWS 待机、传感器) |
| Park | 极低 | 高 | 已基本不用(BLE 取代) |
Sniff 是实际项目中最常用的——它保持 ACL 链路不断开,但从设备大部分时间在休眠,只在约定的时隙醒来监听。TWS 耳机待机时能撑好几天,核心就是 Sniff 在工作。
为什么不用 Hold?Hold 是"约定一段时间不通信",到期后自动恢复 Active。它适合临时挂起(比如切换加密模式时),不适合长期待机——因为 Hold 期间完全不能收数据,到期恢复 Active 后又回到全时监听。
为什么不用 Park?Park 几乎完全休眠,只保留 beacon 监听,但响应延迟太大(秒级),而且 Park 模式下设备没有 AM_ADDR,不能直接收发数据,必须先 Unpark。现在 BLE 已经取代了 Park 的大多数场景,经典蓝牙里 Park 基本不用了。
2. Sniff 的工作原理
Sniff 模式下,主从双方约定一个Sniff Interval(嗅探间隔)。每隔这个间隔,从设备"醒来"一段窗口(Sniff Attempt),监听主设备是否发数据。如果在这段窗口里收到数据,从设备保持唤醒继续收发;如果没收到,从设备重新休眠,等下一个 Interval。
时间轴 → 主设备: ──[发数据]──────────────────────────[发数据]── 从设备: ▏醒▕─────────睡─────────▏醒▕─────────睡─────────▏醒▕ │←Attempt→│←Interval-Attempt→│←Attempt→│←Interval-Attempt→│ Active 模式对比: 从设备: ▏醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕醒▕ ← 全时监听关键点:主设备也必须在 Sniff 窗口才能给从设备发数据。Sniff 是双方约定的——主设备不能在从设备休眠时发数据,否则从设备收不到。这意味着 Sniff 参数影响的不只是从设备的功耗,还有链路的传输能力。
3. 三个核心参数详解
3.1 Sniff Interval(嗅探间隔)
从设备两次"醒来"之间的时间间隔。
- 取值范围:0x0004 - 0x4000,单位 0.625ms(即 2.5ms - 10.24s)
- 对功耗的影响:越大越省电(休眠时间越长)
- 对延迟的影响:越大延迟越高(主设备要等到下一个窗口才能发数据)
- 对吞吐的影响:越大有效带宽越低(传输机会少)
典型值:
| 场景 | Interval | 理由 |
|---|---|---|
| TWS 待机 | 500ms - 1s | 无数据传输,最大化省电 |
| A2DP 播放 | 40ms - 60ms | 匹配音频帧周期(SBC 约 20ms/帧),保证及时传输 |
| HID 键盘 | 100ms - 200ms | 按键响应延迟 < 200ms 用户可接受 |
| 传感器上报 | 1s - 4s | 数据稀疏,延迟可接受 |
3.2 Sniff Attempt(嗅探尝试窗口)
从设备每次醒来后,持续监听的时长。
- 取值范围:0x0001 - 0x7FFF,单位 0.625ms
- 对功耗的影响:越大越耗电(醒着的时间长)
- 对稳定性的影响:越大越稳(给链路更多时间重传/同步)
Attempt 不能为 0——至少要醒一个时隙才能判断主设备有没有发数据。Attempt 通常设置成能覆盖若干个跳频时隙(每个时隙 625μs),确保即使遇到跳频干扰也能收到。
典型值:
| 场景 | Attempt | 理由 |
|---|---|---|
| TWS 待机 | 4 - 8 时隙(2.5ms - 5ms) | 只需确认有无数据,短窗口够 |
| A2DP 播放 | 16 - 32 时隙(10ms - 20ms) | 要收完整个 SBC 帧包 |
| 弱信号环境 | 加大到 32 - 64 时隙 | 给重传留余量 |
3.3 Sniff Timeout(嗅探超时)
从设备在 Attempt 窗口内收到数据后,额外延长监听的时间。
- 取值范围:0x0000 - 0x7FFF,单位 0.625ms
- 作用:如果 Attempt 窗口里收到了数据,说明有通信需求,从设备继续醒着 Timeout 时长,看主设备是否还有后续数据
- Timeout=0:Attempt 结束立即休眠,不管有没有收到数据
这个参数解决的是"突发数据"场景——主设备可能连续发多个包,如果每个包都要等下一个 Interval,延迟会累积。Timeout 让从设备在收到数据后"多醒一会儿",避免频繁休眠-唤醒。
典型值:
| 场景 | Timeout | 理由 |
|---|---|---|
| TWS 待机 | 0 或 4 时隙 | 无突发数据,不需额外等待 |
| A2DP 播放 | 16 - 32 时隙 | 连续媒体包,需要延长接收窗口 |
| 大数据传输 | 64+ 时阶 | 临时退出 Sniff 更好(见下一节) |
3.4 三个参数的关系
│←───────────────── Sniff Interval ─────────────────→│ │ │ │ │← Attempt →│← Timeout →│ │ │ │ (仅收到数据时) │ │ ▏醒▕─────────睡────────────────────────────────────── │ │ │ 下一个 Interval 开始 │ │← Attempt →│← Timeout →│ │ ▏醒▕─────────睡──────────────────────────────────────三个参数的工程意义:
- Interval决定省电程度(宏观)
- Attempt决定基础可用性(微观)
- Timeout决定突发处理能力(弹性)
调参的核心逻辑:先定 Interval(决定省电目标),再定 Attempt(保证基础通信),最后用 Timeout 优化突发场景。
4. Sniff Subrating 机制
4.1 问题:频繁进出 Sniff 的开销
TWS 耳机在"待机 - 来电 - 通话 - 挂断 - 待机"这种场景里,状态频繁切换。每次切换如果都要 Exit Sniff → Active → 重新 Enter Sniff,开销很大:
- LMP_unsniff + LMP_sniff 两次握手
- 状态机切换的时序约束
- 重新协商参数的延迟
4.2 Subrating 的解法
Sniff Subrating(opcode 0x54)允许在不退出 Sniff的情况下,动态调整有效 Sniff Interval。原理是引入一个"基础 Interval"和"子速率"概念:
- 基础 Interval 保持较短(如 40ms),保证低延迟响应能力
- 实际运行时通过 Subrating 把有效 Interval 拉长到 N 倍(如 N=16,有效 Interval=640ms)
- 一旦有数据要传,立刻回到基础 Interval,无需重新协商
无 Subrating: Interval=640ms ▏醒▕──────────────────────────────────────睡────── ↑ 来数据了,但要等 640ms 才能响应 有 Subrating (base=40ms, subrate=16): 基础周期: ▏醒▕────▏醒▕────▏醒▕──── ... 共 16 次 ↑ 第 1 次就收到数据,立即保持 40ms 的活跃 Interval (无需 LMP 重新协商) 无数据时: 有效周期: ▏醒▕────────────────────────────睡────── (640ms) ↑ 每 16 个基础周期才真正醒一次4.3 Subrating 参数
HCI_Sniff_Subrating 命令(Opcode 0x0C11)的参数:
| 参数 | 含义 |
|---|---|
| Max_Latency | 最大允许的有效 Interval(×0.625ms),即子速率后的最大间隔 |
| Min_Remote_Timeout | 对端的最小 Sniff Timeout |
| Min_Local_Timeout | 本端的最小 Sniff Timeout |
Max_Latency 是核心参数——它告诉控制器"有效 Interval 可以拉到多大",但基础 Interval 保持不变。控制器根据链路活动自动决定实际 Interval:有数据时用基础 Interval,无数据时逐步拉长到 Max_Latency。
4.4 适用场景
Subrating 非常适合"间歇性活动"的设备:
- TWS 耳机:待机时拉长 Interval 省电,有来电/通知时立刻回到短 Interval 响应
- HID 设备:无输入时长 Interval,按键时短 Interval 低延迟
- 健康传感器:周期性上报数据,上报期间短 Interval,其余时间长 Interval
5. HCI 命令与 LMP 交互
5.1 进入 Sniff 的 HCI 命令
Host 通过 HCI_Sniff_Mode 命令(Opcode 0x0803)让 Controller 发起 Sniff:
// HCI_Sniff_Mode 命令参数structhci_sniff_mode_cmd{uint16_thandle;// ACL 连接句柄uint16_tsniff_interval;// 嗅探间隔 (×0.625ms)uint16_tsniff_attempt;// 嗅探尝试 (×0.625ms)uint16_tsniff_timeout;// 嗅探超时 (×0.625ms)};// 示例:进入 Sniff,Interval=100ms, Attempt=5ms, Timeout=0structhci_sniff_mode_cmdcmd={.handle=acl_handle,.sniff_interval=160,// 100ms / 0.625ms = 160.sniff_attempt=8,// 5ms / 0.625ms ≈ 8.sniff_timeout=0,// 不额外等待};hci_send_cmd(HCI_SNIFF_MODE,&cmd,sizeof(cmd));5.2 底层 LMP 交互
Controller 收到 HCI 命令后,发起 LMP 交互:
注意:LMP_sniff 是 Master 发起的。如果 Slave 想进入 Sniff,它发 HCI 命令给自己的 Controller,Controller 会通过 LMP 通知 Master,由 Master 决定是否发起 Sniff 协商。
5.3 退出 Sniff
退出 Sniff 用 HCI_Exit_Sniff_Mode(Opcode 0x0804),底层走 LMP_unsniff:
5.4 Sniff Subrating 的 HCI 命令
// HCI_Sniff_Subrating 命令structhci_sniff_subrating_cmd{uint16_thandle;// ACL 连接句柄uint16_tmax_latency;// 最大有效 Interval (×0.625ms)uint16_tmin_remote_timeout;// 对端最小 Timeout (×0.625ms)uint16_tmin_local_timeout;// 本端最小 Timeout (×0.625ms)};这个命令可以在 Sniff 模式下随时发,不触发状态切换。
6. TWS 场景参数调优
6.1 TWS 耳机的四种工作状态
| 状态 | 链路活动 | 推荐参数 |
|---|---|---|
| 待机(充电盒外) | 偶尔有通知 | Interval=500ms-1s, Attempt=8, Timeout=0 |
| A2DP 播放 | 持续媒体流 | Interval=40-60ms, Attempt=16, Timeout=16 |
| HFP 通话 | SCO/eSCO 占用 | 不能用 Sniff(见下文) |
| 来电响铃 | 突发信令 | 用 Subrating,base=40ms, max=200ms |
6.2 为什么 HFP 通话不能用 Sniff
HFP 通话走 SCO 或 eSCO 链路,SCO/eSCO 是同步链路,时隙是预留的。ACL 链路在通话期间仍然存在,但实际数据量很少(只有 HFP 控制信令)。
理论上 ACL 可以进 Sniff,但实际实现中:
- SCO/eSCO 已经占用了大量时隙,剩余的 ACL 时隙很少
- Sniff 协商的 Interval 可能和 SCO 时隙冲突
- 大多数芯片厂商在 SCO 激活时不允许 ACL 进 Sniff
所以 TWS 通话期间,ACL 保持 Active,靠 SCO 的预留时隙机制省电。通话结束后再切回 Sniff。
6.3 A2DP 播放时的 Sniff 参数
A2DP 播放时,媒体流通过 ACL 传输,Sniff 参数直接影响音频稳定性:
Interval 的选择:
SBC 帧周期约 20ms(48kHz × 16 blocks)。一个 RTP 包通常含 2-6 个 SBC 帧,即每 40-120ms 发一个 RTP 包。Interval 应该小于 RTP 包间隔,否则可能错过传输窗口。
推荐 Interval = 40-60ms,覆盖 1-2 个 RTP 包周期。
Attempt 的选择:
Attempt 要足够收完一个 RTP 包。RTP 包 + L2CAP 头约 700-1000 字节,EDR 2Mbps 下传输约 4ms。考虑重传,Attempt 设 16 时隙(10ms)比较稳妥。
Timeout 的选择:
A2DP 是连续流,一个 RTP 包后面可能紧跟着另一个。Timeout 设 16 时隙(10ms),让从设备在收完一个包后多醒一会儿,收下一个。
A2DP Sniff 参数推荐: Interval = 40ms (64 × 0.625ms) Attempt = 10ms (16 × 0.625ms) Timeout = 10ms (16 × 0.625ms) 时间线: │←── Interval 40ms ──→│←── Interval 40ms ──→│ │←Attempt+Timeout=20ms→│ ▏醒▕────收RTP────────────▏醒▕────收RTP──────────── ↑ 连续收完媒体包后休眠6.4 待机时的 Sniff 参数
待机时没有持续数据,只需要偶尔收控制信令(如 AVRCP 按键):
待机 Sniff 参数推荐: Interval = 800ms (1280 × 0.625ms) Attempt = 2.5ms (4 × 0.625ms) Timeout = 0 (不额外等待) 时间线: │←────────── Interval 800ms ──────────→│ │←2.5ms→│ ▏醒▕─────────────睡───────────────────── ↑ 确认无数据,立即休眠待机时 Interval 可以拉得很大,但要考虑两个约束:
- 链路监督超时(Supervision Timeout):Interval 必须 < Supervision Timeout,否则链路会被判超时断开。通常 Supervision Timeout 设 5-10s,Interval 最大不超过它的一半。
- 应用响应延迟:如果用户按按键,响应延迟 ≈ Interval。Interval=800ms 意味着按键响应最多延迟 800ms——这对 AVRCP 不可接受。
解决响应延迟的方法:用 Sniff Subrating。基础 Interval=40ms(低延迟响应),Max_Latency=800ms(待机时拉长)。有按键时立刻回到 40ms,无数据时逐步拉到 800ms。
6.5 主从切换时的 Sniff 处理
TWS 耳机主从切换时,Sniff 参数需要重新协商:
- 主从切换前,先 Exit Sniff 回到 Active
- 执行角色切换(LMP_switch_req)
- 切换完成后,新主设备重新发起 Sniff 协商
如果直接在 Sniff 状态下做角色切换,可能因为 Sniff 窗口不对齐导致切换失败。稳妥的做法是先 Exit Sniff。
7. 抓包分析 Sniff
7.1 HCI 层抓包
用 btmon 或 hcidump 抓 HCI 层,能看到 Host 和 Controller 的交互:
# btmon 抓包sudobtmon-wsniff.pcap&# 触发 Sniff 进入/退出# ...# 停止sudopkillbtmonWireshark 过滤 Sniff 相关事件:
# Sniff 模式切换事件 bt HCI_EVT_Mode_Change # Sniff 命令 btcmd.opcode == 0x0803 # HCI_Sniff_Mode btcmd.opcode == 0x0804 # HCI_Exit_Sniff_Mode btcmd.opcode == 0x0C11 # HCI_Sniff_SubratingHCI 层抓包看到的是"Host 下命令 → Controller 确认 → Mode_Change 事件",看不到具体的 LMP 交互。
7.2 空口抓包看 LMP
要看 Sniff 的 LMP 交互,需要空口抓包仪(Ellisys / Frontline)。关键 LMP PDU:
| Opcode | 命令 | 抓包看什么 |
|---|---|---|
| 0x08 | LMP_sniff | 参数协商:D_Sniff, T_Sniff, D_Sniff_Attempt, T_Sniff_Attempt |
| 0x09 | LMP_unsniff | 退出 Sniff |
| 0x54 | LMP_sniff_subrating | 子速率协商 |
| 0x00 | LMP_accepted | 接受 Sniff 请求 |
空口抓包还能看到 Sniff 模式下的实际时隙占用——主设备只在 Sniff 窗口发数据,其余时隙静默。这是验证参数是否生效的最直接方法。
7.3 验证 Sniff 是否生效
抓包看三个信号:
- HCI_Mode_Change 事件:Mode 字段从 0x00(Active)变成 0x02(Sniff),说明进入 Sniff 成功
- ACL 数据包的时间分布:Sniff 模式下,ACL 包应该集中在 Sniff 窗口附近,非窗口期应该没有包
- 电流测试:用功耗分析仪测电流,Sniff 模式下应该看到周期性的电流脉冲(Attempt 窗口),其余时间电流极低
8. 常见问题排查
8.1 进入 Sniff 失败
现象:发 HCI_Sniff_Mode 后收到HCI_Command_Status (Status=ERROR)或长时间无 Mode_Change 事件。
排查:
- 检查参数合法性:Interval ≥ 0x0004,Attempt ≥ 0x0001
- 检查连接状态:链路必须是 Active 才能进 Sniff
- 检查是否有 SCO 激活:SCO 激活时通常不允许进 Sniff
- 检查对端是否支持 Sniff:查 LMP features(Page 0 bit 5)
8.2 Sniff 模式下音频卡顿
现象:A2DP 播放时进入 Sniff 后,音频周期性卡顿。
原因:Interval 太大或 Attempt 太小,导致 RTP 包传输不及时。
调参方向:
- 缩小 Interval(如从 100ms 降到 40ms)
- 加大 Attempt(如从 8 时隙加到 16 时隙)
- 加大 Timeout(如从 0 加到 16 时隙)
8.3 Sniff 模式下链路断开
现象:Sniff 模式下链路突然断开,日志显示 Supervision Timeout。
原因:Sniff Interval > Supervision Timeout,或者信号质量差导致 Sniff 窗口里收不到数据,累计超时。
调参方向:
- 检查 Interval 是否 < Supervision Timeout / 2
- 弱信号环境下加大 Attempt,增加接收成功率
- 适当缩短 Interval,增加链路存活机会
8.4 待机功耗没降
现象:进了 Sniff,但整机功耗没明显下降。
排查:
- 确认 Sniff 真的生效(看 Mode_Change 事件)
- 测控制器电流:如果控制器电流没降,可能是 RF 没真正休眠(检查硬件设计)
- 检查其他耗电大户:传感器、LED、充电电路等可能抵消 Sniff 的省电效果
- Interval 是否足够大:Interval=100ms 省电效果不明显,至少 500ms 以上
8.5 Sniff Subrating 不生效
现象:发了 HCI_Sniff_Subrating,但有效 Interval 没变化。
排查:
- 确认双方都支持 Sniff Subrating(LMP features Page 0 bit 15)
- 检查 Max_Latency 是否 > 当前 Interval(否则没有拉长空间)
- 检查链路状态:必须在 Sniff 模式下才能用 Subrating
- 查控制器实现:某些低端芯片的 Subrating 实现有 bug,需要厂商确认