You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

27 KiB

面对面翻译 · 独立 BLE 链路协议(草案 v0.1)

状态:待固件确认。本文只定义协议,不含实现。 固件与 App 双方按本文对齐后再各自写代码;有异议的条目在 §14 逐条回复,改动记进 §16 变更记录。

配套阅读:bes_earphone_protocol.md(现有 SPP 协议,本文与它并存,不替代)


0. 为什么要单开一条 BLE

面对面翻译要求手机同时做四件事:收左耳麦、收右耳麦、往左耳放译文、往右耳放译文,四条流内容各不相同。

现有恒玄链路是一条经典蓝牙 SPP(RFCOMM,UUID aeaf5241-5453-7070-7353-445542594c46), 所有指令与音频挤在这一条上,上行靠 mode=0x03 交织、下行靠 AA 56 的 84 字节双槽区分方向。 这套结构服务「通话翻译」够用(两路的语义是固定的:本端 mic / 对端 spk), 但面对面的两路是两个人,需要:

  • 两路上行同时并发且可独立起停(A 说话时 B 插话,不能排队);
  • 两路下行可独立寻址(A 说的话只能进 B 的耳朵);
  • 每路带独立序号,好判丢包、好做打断。

在 SPP 的定长双槽结构上硬塞这些语义,会把现有通话翻译的包格式一起改掉。 所以本功能另起一条 BLE 链路,与 SPP 完全隔离:进面对面时建链,退出时拆链并恢复原状。


1. 设计边界(明确不动什么)

不动 说明
现有 SPP 全部指令与音频格式 AA 51/52/56/57/58/59/5A/5E/5F/65/69/6A、BB D1/D2/D9/DA/E5/E6/E7/64/55/68/60/5B 一律不改
现有 AA 66 / AA 67 面对面 那是旧版面对面(手机麦上行 + AA 69 单声道下行),保留可用,新模式用新命令号,两者互斥但互不修改
通话翻译 / AI 对话 / 录音 / OTA 全部保持现状;面对面 BLE 与它们互斥,不并行
经典蓝牙配对关系 面对面结束后必须回到进入前的状态(见 §11)

App 侧对应载体:face_to_face_ble_service.dart (接口已固化,本文补的是它 TODO(固件) 里缺的那部分)。 业务层 translation_controller.dart 的 FaceToFaceTrigger / activeSpeaker 语义不变。


2. 总体拓扑:两档,协议同一套

固件按自身能力选一档,App 侧接口与业务逻辑完全相同,靠握手里的 TOPO 字段自适应。

档位 A —— 单链路(主耳中转)· 建议先落地

        BLE (1 条)
手机 ────────────────► 主耳 ──耳间私有链路──► 副耳
     控制/双路音频           按 EAR 字段分发
  • 只有主耳做 GATT peripheral,两路音频在同一条 BLE 上用帧头的 EAR 字段区分;
  • 与现有 SPP 的双路交织同构,固件改动量最小,TWS 主从关系不用拆;
  • 代价:副耳多一跳转发(实测应 < 20ms,需固件给实测值);耳间链路带宽要能承载一路上行 + 一路下行。

档位 B —— 双链路(左右耳各自独立)· 目标形态

        BLE (2 条,互相独立)
手机 ═══════════════► 左耳
     ═══════════════► 右耳
  • 左右耳各自广播、各自被 App 连接,各跑一条 GATT;
  • 时空壶 WT2 Edge 走的就是这条路(其耳机不出现在系统蓝牙列表,全靠 App 内分别连 L/R);
  • 延迟最低、两路彻底解耦;代价是要拆 TWS 主从,A2DP/HFP 期间必然中断(见 §14 Q1/Q2)。

协议帧在两档下完全一致。档位 B 时每条链路只应出现自己耳位的 EAR 值, 收到不属于本耳的包直接丢弃并上报 ERR_EAR_MISMATCH。


3. 完整时序

App                                   耳机(主耳)                副耳
 │                                        │                      │
 │  ①  SPP: AA 6B <ver><cap><topo><token> │                      │
 │───────────────────────────────────────►│                      │
 │                                        │ 切工作模式:停A2DP/HFP│
 │  ②  SPP: BB EB <ok><topo><earmask>...  │ 起 BLE 广播(带 token) │
 │◄───────────────────────────────────────│                      │
 │                                        │                      │
 │  ③  BLE 扫描 → 按 token 过滤 → 连接     │                      │
 │  ④  MTU 交换 / 2M PHY / 订阅通知        │                      │
 │───────────────────────────────────────►│                      │
 │                                        │                      │
 │  ⑤  BLE: AA 01 HELLO <token><role>     │                      │
 │───────────────────────────────────────►│                      │
 │  ⑥  BLE: BB 81 HELLO_ACK <ear><fw>...  │                      │
 │◄───────────────────────────────────────│                      │
 │  ⑦  BLE: AA 02 SESSION_CFG             │                      │
 │───────────────────────────────────────►│                      │
 │  ⑧  BLE: AA 03 START_STREAM            │                      │
 │───────────────────────────────────────►│ 开双耳拾音            │
 │                                        │                      │
 │  ⑨  BLE: BB 90 上行音频(EAR=L, seq++)  │                      │
 │◄═══════════════════════════════════════│◄─────────────────────│
 │  ⑩  云端 ASR→翻译→TTS                   │                      │
 │  ⑪  BLE: AA 91 下行音频(EAR=R, seq++)  │                      │
 │═══════════════════════════════════════►│─────────────────────►│ 播放
 │                                        │                      │
 │  ⑫  BLE: AA 05 KEEPALIVE (1s 周期)     │                      │
 │◄──────────────────────────────────────►│                      │
 │                                        │                      │
 │  ⑬  BLE: AA 04 STOP_STREAM             │                      │
 │───────────────────────────────────────►│                      │
 │  ⑭  BLE 主动断开                        │                      │
 │  ⑮  SPP: AA 6C 退出面对面               │                      │
 │───────────────────────────────────────►│ 停广播/恢复TWS/恢复A2DP│
 │  ⑯  SPP: BB EC <ok>                    │                      │
 │◄───────────────────────────────────────│                      │

为什么入口放在 SPP 而不是直接扫 BLE:耳机平时不广播这个服务(省电、也避免被无关 App 扫到), 只有收到 AA 6B 才开始广播,且广播里带上 App 给的 token。 这样「扫描时连到隔壁桌同型号耳机」这个问题在协议层就不成立(见 §5)。


4. 阶段一 · SPP 侧模式切换

沿用现有 SPP 帧格式:AA CMD [LEN] [payload] / BB CMD LEN [payload]。 命令号选在模式控制段(0x5x/0x6x)内的空位,响应码遵守本机固件 响应 = 请求 | 0x80 的规律。

已占用下行:06 09 0B 0C 0E 0F 10 20 51 52 56 57 58 59 5A 5E 5F 65 66 67 69 6A 已占用上行:02 03 04 07 0A 18 55 5B 60 61 64 68 86 89 8C 8E 90 D1 D2 D6 D9 DA DE DF E5 E6 E7 E9 本协议新增 6B/6C ↔ EB/EC,与上表均无冲突。

⚠️ App 侧的 BesBluetoothService.probeCommands() 目前跳过的是 0x51~0x6A(会切工作模式的段), 落地本协议时必须把跳过区间扩到 0x6C —— 否则扫命令时会把耳机扫进面对面模式, 且扫描进程不会发 AA 6C 退出,只能靠看门狗兜底。

4.1 AA 6B 进入面对面 BLE 模式

AA 6B 07 <PROTO_VER> <CAP> <TOPO_REQ> <TOKEN 4B>
字段 长度 取值
PROTO_VER 1 本文档版本,首版 0x01
CAP 1 App 支持的编码位图:bit0 G.722、bit1 mSBC、bit2 Opus(预留)。首版固定 0x01
TOPO_REQ 1 0x00=由耳机决定(推荐)、0x01=要求单链路、0x02=要求双链路
TOKEN 4 App 生成的随机会话标识,每次进入重新生成,用于广播过滤

4.2 BB EB 响应

BB EB 08 <RESULT> <TOPO> <EAR_MASK> <PROTO_VER> <CODEC> <FRAME_MS> <FW_MINOR>
字段 取值
RESULT 见 §12 错误码,0x00=已进入并开始广播
TOPO 0x01=单链路(仅主耳广播)、0x02=双链路(左右耳各自广播)
EAR_MASK bit0=左耳可用、bit1=右耳可用。单耳在盒时只置一位,App 据此提示用户
CODEC 实际采用的编码,0x01=G.722
FRAME_MS 每包承载的音频时长,20 或 40(建议 40)
FW_MINOR 固件的本协议实现小版本,便于后续差异化兼容

4.3 AA 6C 退出

AA 6C            →   BB EC 01 <RESULT>

⚠️ 必须幂等:不在面对面模式时收到 AA 6C 也要回 BB EC 00,不能报错。 App 在异常恢复路径上会无条件补发一次(见 §11)。


5. 阶段二 · BLE 广播与发现

5.1 广播内容

字段 内容
Flags LE General Discoverable + BR/EDR Not Supported
Local Name <产品名>-F2F,左右耳可以同名(靠 MSD 区分,不靠名字)
Service UUID (16B) 见 §6.1
Manufacturer Specific Data 见下

Manufacturer Specific Data(AD Type 0xFF,共 11 字节)

| CompanyID 2B (LE) | MAGIC 0xF2 0xF2 | VER 1B | EAR 1B | TOKEN 4B | BATT 1B |
字段 说明
MAGIC 固定 F2 F2,快速筛掉无关广播
VER 协议版本,与 AA 6B 的 PROTO_VER 一致
EAR 0x01=左耳、0x02=右耳、0x03=主耳(单链路档,代表双耳)
TOKEN 原样回显 AA 6B 里那 4 字节
BATT 该耳电量 0~100,低 7 位;最高位为充电位(与 BB 0A 同口径)

5.2 App 侧扫描规则(强制)

  1. 只接受 MAGIC 命中的广播;
  2. TOKEN 必须与本次 AA 6B 下发的完全一致,否则一律忽略 —— 这是防止连到旁边同型号耳机的唯一手段,会场/展会场景必然会同时扫到多副;
  3. 档位 B 下需集齐 EAR=0x01 与 0x02 两个广播才算就绪;只集到一个时按 §14 Q9 的降级策略处理;
  4. 扫描超时 15s:超时后 App 发 AA 6C 回滚,并向用户报「耳机未就绪」。

5.3 广播参数与耳机侧超时

  • 建链期快速广播:间隔 30~50ms,持续 30s;
  • 30s 内无人连接,耳机自动退出面对面模式并恢复常态(第一道看门狗,见 §11);
  • 连接建立后立即停止广播(档位 B 下各耳独立停)。

5.4 iOS 注意

iOS 拿不到 BLE 设备的 MAC(CoreBluetooth 只给每台手机各不相同的 peripheral UUID), 所以设备身份判定只能靠 TOKEN + EAR,不能靠地址。这也是 §5.2 第 2 条必须强制的原因。 (同一问题在设备确权上已经踩过,见 CLAUDE.md「设备确权」一节。)


6. 阶段三 · GATT 服务与特征

6.1 UUID(待固件分配,下表为占位)

沿用现有 SPP UUID 的 128-bit 风格,基址建议:AEAF5F32-XXXX-4C4B-A100-000000000000

项 占位 UUID 属性 方向
Service AEAF5F32-0001-4C4B-A100-000000000001 — —
CTRL_TX AEAF5F32-0002-4C4B-A100-000000000001 Write / Write Without Response App → 耳机(控制)
CTRL_RX AEAF5F32-0003-4C4B-A100-000000000001 Notify 耳机 → App(控制/事件)
AUDIO_UP AEAF5F32-0004-4C4B-A100-000000000001 Notify 耳机 → App(音频)
AUDIO_DOWN AEAF5F32-0005-4C4B-A100-000000000001 Write Without Response App → 耳机(音频)

控制与音频分开成 4 个特征,不合并:音频是每 40ms 一包的高频流, 与控制帧共用一个特征时,控制消息(尤其是「打断」和 STOP_STREAM)会排在音频队列后面, 表现为「点了停止还在播」。分开后两条队列各自独立。

6.2 链路参数要求

项 要求 理由
ATT MTU ≥ 185,建议 247 40ms 聚合包 = 6B 头 + 80B 音频 = 86B;留余量给未来的 Opus/更长聚合
PHY 优先 2M,回退 1M 四路并发时降低空口占用
连接间隔 15~30ms 大于 40ms 会让下行音频出现可闻的断续
Slave latency 0 音频流不允许休眠跳包
数据长度扩展 (DLE) 开 否则 86B 包会被拆成多个 27B 空口包,吞吐骤降

7. 阶段四 · BLE 链路握手

BLE 链路上的操作码空间与 SPP 独立(不同链路,不会混淆)。 帧格式同样是 AA <OP> <LEN> <payload> / BB <OP|0x80> <LEN> <payload>,LEN 为 payload 字节数。

步骤 帧 payload 说明
① HELLO AA 01 <VER 1B><TOKEN 4B><ROLE 1B> ROLE:0x01=App。token 再校验一次,防止连错
② HELLO_ACK BB 81 <RESULT><EAR><FW 3B><CODEC><FRAME_MS><MTU_OK> EAR 必须与广播里一致;MTU_OK=0 时 App 降级到 20ms 单帧包
③ SESSION_CFG AA 02 <UP_ENABLE 1B><VAD_MODE 1B><DOWN_GAIN 1B><TRIGGER 1B> 见下
④ CFG_ACK BB 82 <RESULT>
⑤ START_STREAM AA 03 <EAR_MASK 1B> 指定开哪几路上行;档位 B 下固定为本耳
⑥ START_ACK BB 83 <RESULT> 回 0x00 后耳机开始推 BB 90
⑦ STOP_STREAM AA 04 — 停上行,链路保留(切换语言/暂停时用,不必拆链)
⑧ KEEPALIVE AA 05 <SEQ 1B> App 每 1s 发一次
⑨ KEEPALIVE_ACK BB 85 <SEQ 1B><LOSS 1B> LOSS=耳机侧统计的下行丢包率(%),用于诊断

SESSION_CFG 字段

字段 取值
UP_ENABLE bit0=左耳上行、bit1=右耳上行
VAD_MODE 0x00=常发(不做静音抑制,默认)、0x01=静音抑制(耳机侧 VAD 判静音则不发包)
DOWN_GAIN 下行播放增益 0~100,默认 80
TRIGGER 0x00=免按键(连续拾音)、0x01=按住说话(按键事件驱动,对应 App 的 FaceToFaceTrigger.device)

⚠️ VAD_MODE 默认 0x00 是刻意的:断句策略在 App/云端侧,固件 VAD 一旦判早就会把一句话切碎, 每段各翻一次,效果比多传点静音差得多。同传模式下已经踩过这个坑 (见 CLAUDE.md「面对面 / 同声翻译:延迟构成」第 1 条)。省电需求确认后再开 0x01。


8. 音频上行(耳机 → App)

BB 90 <LEN> <EAR|FLAGS 1B> <SEQ 2B LE> <PAYLOAD…>
字段 说明
EAR 低 4 位:0x1=左耳、0x2=右耳
FLAGS 高 4 位:bit4 VAD_ACTIVE(本包含人声)、bit5 SEG_BEGIN(一段发声起始)、bit6 SEG_END(结束)、bit7 保留
SEQ 每耳独立自增,回绕 0xFFFF。App 据此算丢包,不做重传
PAYLOAD N × 40B G.722 码流。FRAME_MS=40 时 N=2,=20 时 N=1
  • 编码:G.722,16kHz / 16bit / 单声道,一帧 640B PCM(320 样本 = 20ms)→ 40B 码流。 选它是因为现有原生插件的 G722Codec 编解码器两端都已落地、已验证,不引入新依赖。
  • SEG_BEGIN / SEG_END 是耳机给的提示,不是命令:App 可以用它抢跑一次断句, 但最终断句仍以云端 ASR 为准(VAD_MODE=0x00 时耳机照发不误)。
  • 丢包不重传:语音流重传只会加大延迟。App 检测到 seq 跳变时补静音帧维持时基。

9. 音频下行(App → 耳机)

AA 91 <LEN> <EAR|FLAGS 1B> <SEQ 2B LE> <PAYLOAD…>
字段 说明
EAR 低 4 位:这包该播给哪只耳。档位 A 由主耳按此转发;档位 B 下与本链路耳位不符则丢弃并回 ERR_EAR_MISMATCH
FLAGS bit4 PLAY_BEGIN(一段译文开始,耳机可据此压低本地音/给提示音)、bit5 PLAY_END(结束,可恢复)、bit6 FLUSH(打断:丢弃本耳未播完的缓冲,立即播本包)
SEQ 每耳独立自增
PAYLOAD 同上行
  • 打断(FLUSH)是面对面必需的:对方还在听上一句译文时新一句已经翻好, 没有 flush 就会越积越多、译文与对话彻底脱节。App 在每段译文首包置 PLAY_BEGIN, 需要抢播时同时置 FLUSH。
  • 不需要静音填充。这点与 SPP 的 AA 56 84 字节双槽不同:那是定长双槽包,空槽必须填静音帧; BLE 是包交换,没数据就不发包,耳机侧靠 SEQ 与到达时间维持时基即可。
  • 节流:App 按 FRAME_MS 的实时速率发送。耳机可用 BB 86 <interval_ms> 请求改节奏 (语义对齐现有 SPP 的 0xD6/0xE9 调速),合法值 16 / 20 / 25 / 40。
  • 缓冲:耳机侧建议 60~120ms 抖动缓冲;溢出时丢最旧的并上报 EVT_BUF_OVERFLOW,不要阻塞。

10. 事件上报

BB 87 <LEN> <EVENT 1B> <PARAM…>
EVENT 含义 PARAM App 侧对应
0x01 按键 <EAR><KEY_CODE><ACTION>,ACTION:0x01按下 / 0x02抬起 / 0x03单击 / 0x04双击 按住说话:左耳按下→activeSpeaker=1、右耳按下→=2、抬起→=0。语义已在 translation_controller 落地,固件只需如实上报
0x02 佩戴状态 <EAR><STATE>,STATE:0x00摘下 / 0x01戴上 / 0x02入盒 摘下/入盒 → App 暂停该路,UI 提示
0x03 电量 <EAR><LEVEL>,最高位为充电位 与 BB 0A 同口径
0x04 链路统计 <EAR><UP_LOSS%><DOWN_LOSS%> 每 10s 一次,仅用于诊断日志
0x05 异常 <CODE>,见 §12 缓冲溢出、编码失败等
0x06 耳机请求退出 <REASON> 用户在耳机上操作或电量过低;App 收到后走正常退出流程

11. 退出与恢复(重点)

功能结束后必须完整回到进入前的状态。这一节的每一条都是验收项。

11.1 三层看门狗

层 条件 动作 责任方
1 开始广播后 30s 无 BLE 连接 停广播、退出面对面模式、恢复常态、SPP 主动上报 BB EC 00 固件
2 BLE 已连接但 5s 未收到 AA 05 KEEPALIVE 断 BLE、退出面对面模式、恢复常态 固件
3 BLE 意外断开 / App 侧异常 立即补发 SPP AA 6C;SPP 也不可用时,下次 SPP 连上后先发一次 AA 6C 清状态 App

第 2 层是本协议里最重要的一条。App 被系统杀掉、崩溃、用户强退时, 没有任何机会发退出指令;缺了它耳机会永远卡在面对面模式,用户表现为 「耳机连着但放不了音乐、也接不了电话」,且看不出原因。 (同类教训见 CLAUDE.md 里 BES OTA 的 90s 看门狗一节。)

11.2 固件的恢复清单

收到 AA 6C、或任一看门狗触发时,逐项恢复:

  1. 停止 BLE 广播,断开本协议的 GATT 连接;
  2. 恢复 TWS 主从与耳间同步(档位 B 拆过主从时);
  3. 恢复 A2DP / HFP —— 音乐能播、来电能接;
  4. 恢复按键功能映射到用户原设置(面对面期间若临时改过键位);
  5. 恢复 ANC / 通透模式到进入前的档位;
  6. 恢复麦克风通路与增益;
  7. SPP 回 BB EC 00(看门狗触发时为主动上报)。

11.3 App 的恢复动作

  • 退出 BLE 模式后不重连 SPP:SPP 链路全程未断,BesBluetoothService 状态不受影响;
  • 恢复完成前不允许再次进入面对面(防止半状态叠加);
  • UI 上「结束」按钮必须先返回、后台跑收尾,不要 await 整条退出链 (录音页踩过这个坑,见 CLAUDE.md「录音页退不出去」)。

11.4 互斥

面对面 BLE 模式与下列功能同一时刻只能有一个,冲突时 AA 6B 回 ERR_BUSY: 通话翻译(AA 51)、AI 对话(0x64)、主麦录音(AA 59)、媒体录音(AA 5E)、OTA、旧版面对面(AA 66)。


12. 错误码(RESULT / CODE 共用一张表)

值 名称 含义 App 侧表现
0x00 OK 成功 —
0x01 ERR_UNSUPPORTED 固件不支持本协议或版本过低 提示「耳机固件需升级」,引导 OTA
0x02 ERR_BUSY 正在通话/AI/录音/OTA 提示「请先结束当前功能」
0x03 ERR_EAR_UNAVAILABLE 单耳或双耳不可用(在盒/未佩戴) 提示「请取出并佩戴双耳」
0x04 ERR_TOKEN_MISMATCH token 校验失败 静默重试一次,仍失败则回滚
0x05 ERR_BLE_RESOURCE BLE 资源不足(连接数/内存) 提示重试
0x06 ERR_CODEC 编码不支持或初始化失败 记日志,回滚
0x07 ERR_TIMEOUT 阶段超时 回滚并提示
0x08 ERR_EAR_MISMATCH 下行包耳位与链路不符 记日志(属 App 侧 bug)
0x09 EVT_BUF_OVERFLOW 下行缓冲溢出 记日志,App 降速
0x0A ERR_INTERNAL 固件内部错误 回滚并提示

13. 带宽与延迟预算

带宽(G.722 @16kHz = 16 kbps/路)

流 路数 小计
上行(左耳 + 右耳) 2 32 kbps
下行(左耳 + 右耳) 2 32 kbps
控制 + keepalive — < 2 kbps
合计 ≈ 66 kbps

40ms 聚合时每包 86 字节,四条流合计每 40ms 4 包 ≈ 8.6 KB/s。 BLE 1M PHY 在 30ms 连接间隔下的实测可用吞吐通常在 100~300 kbps,余量充足; 开 2M PHY + DLE 后更宽松。档位 B 下两条链路各承担一半,压力更小。

对照:若直接传 16k/16bit 裸 PCM,单路即 256 kbps,四路 1 Mbps —— BLE 上不可行。 所以下行/上行都必须是编码后码流,不能传裸 PCM。

延迟预算(单向,从 A 开口到 B 听到)

段 预估 备注
耳机采集 + 编码 20~40ms 一个聚合包
BLE 上行 30~60ms 含连接间隔与重传
档位 A 的耳间转发 +10~20ms 仅档位 A、且仅下行侧
云端 ASR→翻译→TTS 800~2500ms 大头。端到端模型可压到 ~800ms,三段串行则 2s+
BLE 下行 30~60ms
解码 + 抖动缓冲 + 播放 60~120ms
合计 ≈ 1.0~2.8s

蓝牙链路只占其中 100~250ms。优化重心在云端那一段,不要在协议上过度抠 10ms。


14. 待固件确认清单

请逐条回复「支持 / 不支持 / 需改为」:

# 问题 影响
Q1 能否做档位 B(双链路)?拆开 TWS 主从后,两只耳机还能各自独立跑 GATT 并保持同步吗? 决定最终形态;不能则先做档位 A
Q2 面对面模式下 A2DP / HFP 是否必须中断?若中断,来电时如何处理(自动退出面对面?) 决定 §11 恢复清单与用户提示文案
Q3 §6.1 的 GATT UUID 由谁分配?固件方是否已有预留的 128-bit 基址? 阻塞两端编码
Q4 支持 MTU 247 / 2M PHY / DLE 吗?最大支持的 MTU 是多少? 决定聚合帧长(40ms vs 20ms)
Q5 G.722 能否直接用在 BLE 通道上(现有实现在 SPP 上)?编解码器是否可复用? 不能则要换 Opus,两端都要加依赖
Q6 是否有入耳检测 / 骨传导 / VPU 可用来判定「是佩戴者本人在说话」?能否随上行包给出该标志? 面对面串音抑制的关键;没有的话只能靠云端,效果差一档
Q7 面对面模式下哪些按键可用、键位码如何编排?长按/抬起事件能否上报? 决定「按住说话」能否做
Q8 广播里的 TOKEN 回显能实现吗?广播内容能否在运行时动态改? 不能则无法防止连错耳机,需另想办法
Q9 单耳在盒 / 只戴一只时的降级行为?是拒绝进入,还是以单耳模式进入? 决定 EAR_MASK 语义与 App 提示
Q10 §11.1 的三层看门狗超时值(30s / 5s)是否合适?固件侧能否实现第 1、2 层? 直接决定「App 崩了耳机会不会卡死」
Q11 与 OTA / AI 对话 / 通话的互斥由固件拦(回 ERR_BUSY)还是 App 自律? 建议固件拦,App 也拦,双保险
Q12 档位 B 下两只耳机的上行时间戳是否需要对齐?耳间有共同时钟吗? 影响能否做跨耳的回声/串音抑制
Q13 单包最大长度、单位时间最大包数有无限制? 影响聚合策略
Q14 耳机侧下行抖动缓冲多大?溢出策略是丢旧还是丢新? 影响打断(FLUSH)的实际效果

15. 联调验收用例

# 场景 期望
T01 正常进入 → 双耳广播 → 连接 → 握手 → 上下行通 全流程 < 5s,双耳都能收发
T02 左耳说话 只有右耳听到译文;左耳自己听不到
T03 双方同时说话 两路各自独立上行,互不排队,两路译文分别到达对侧
T04 打断:A 的第二句在第一句译文播完前到达 置 FLUSH 后立即切到新句,不排队堆积
T05 扫描期旁边有另一副同型号耳机(也在面对面模式) 绝不连错:token 不匹配的广播被忽略
T06 正常退出 音乐可播、来电可接、按键功能恢复、ANC 恢复
T07 App 进程被杀 ≤ 5s 后耳机自行退出并恢复常态(看门狗 2)
T08 进入模式后一直不连(App 卡住) ≤ 30s 后耳机自行退出(看门狗 1)
T09 面对面期间来电 按 Q2 的结论执行,且结束后状态一致
T10 面对面期间摘下一只耳 / 放回盒 上报 EVT 0x02,App 暂停该路并提示,戴回后恢复
T11 走出距离边缘导致丢包 无崩溃、无杂音,seq 补洞,恢复后自愈
T12 连续进出 20 次 无资源泄漏,第 20 次与第 1 次耗时相当
T13 重复发 AA 6C 三次 三次都回 BB EC 00(幂等)
T14 面对面期间尝试进 AI / 通话翻译 回 ERR_BUSY,两边状态都不被破坏

16. 变更记录

版本 日期 变更
v0.1 2026-09-05 首版草案,待固件确认 §14