10 changed files with 1088 additions and 175 deletions
@ -0,0 +1,437 @@ |
|||
# 通话翻译 BLE 协议规范 v2(APP ↔ 固件 对接版) |
|||
|
|||
> **用途**:APP 与耳机固件重新联调通话翻译的接口契约。本文只描述**双方约定的线上格式与时序**。 |
|||
> **代码基准**:分支 `newdev_chengguofeng`,`local_plugins/ble_service/`(Android / iOS 行为一致)。 |
|||
> **兼容性**:**下行音频格式为 v2 新格式,与 v1 不兼容**,APP 与固件必须同时升级。 |
|||
|
|||
--- |
|||
|
|||
## 目录 |
|||
|
|||
1. [v2 变更摘要](#1-v2-变更摘要固件重点看这里) |
|||
2. [链路总览与声道语义](#2-链路总览与声道语义) |
|||
3. [GATT 服务与特征](#3-gatt-服务与特征) |
|||
4. [码率与帧长(贯穿全文的基础)](#4-码率与帧长贯穿全文的基础) |
|||
5. [指令协议](#5-指令协议) |
|||
6. [会话时序](#6-会话时序) |
|||
7. [上行音频(设备 → APP)](#7-上行音频设备--app) |
|||
8. [下行音频(APP → 设备)★ 核心](#8-下行音频app--设备--核心) |
|||
9. [可配置参数](#9-可配置参数app-侧运行时可调) |
|||
10. [异常与边界约定](#10-异常与边界约定) |
|||
11. [联调验收清单](#11-联调验收清单) |
|||
12. [待双方确认](#12-待双方确认) |
|||
13. [附:APP 侧实现位置](#13-附app-侧实现位置备查) |
|||
|
|||
--- |
|||
|
|||
## 1. v2 变更摘要(固件重点看这里) |
|||
|
|||
| 项 | v1(旧) | **v2(新)** | |
|||
|---|---|---| |
|||
| 下行包头 | `[4B 序号(小端)][1B 声道]` 共 5 字节 | **无包头**,整包纯 opus 帧数据 | |
|||
| 下行声道组织 | 一包只装一个声道(左包 / 右包分别发) | **一包同时装左右**,帧级交替 `L R L R …` | |
|||
| 下行包长 | `5 + N×40`,变长 | **恒定 `2 × N × F` 字节** | |
|||
| 下行帧数 | 5 帧/包(单声道) | **2 帧/声道/拍**(默认,可调 1–20) | |
|||
| 下行节拍 | 80 ms,左右两路各发一包、错峰 40 ms | **40 ms**(默认),单路统一发送 | |
|||
| 某声道无数据 | 该声道不发包 | **静音帧补齐**,保证左右严格对位、长度恒定 | |
|||
| 完全无音频时 | 不发 | 可配置**持续发送**全静音包(默认关) | |
|||
| 单帧字节数 | 固定 40 B | **由码率决定的变量 `F`**,上下行各自可配 | |
|||
| F4/F5 反馈调速 | 曾设计过 | **已移除**,固件无需上报解码空余 | |
|||
|
|||
**固件唯一必须重写的是下行解包逻辑**:不要再剥 5 字节包头,改为按单帧字节数 `F` 切分、左右交替取帧。指令协议、上行音频、UUID 均**不变**。 |
|||
|
|||
--- |
|||
|
|||
## 2. 链路总览与声道语义 |
|||
|
|||
``` |
|||
BLE |
|||
┌──────────┐ 上行原声(立体声 opus) ┌──────────┐ |
|||
│ │ ───────── notify ───────▶ │ │ |
|||
│ 耳机 │ │ APP │ → 端到端语音翻译(ASR+翻译+TTS) |
|||
│ 固件 │ ◀──────── write ───────── │ │ |
|||
│ │ 下行译音(左右交替 opus) └──────────┘ |
|||
└──────────┘ |
|||
``` |
|||
|
|||
- **上行**:耳机把「本端麦克风原声 + 对方通话原声」合成**立体声 opus** 上报给 APP。 |
|||
- **下行**:APP 把两路译音编码后写回耳机播放。 |
|||
|
|||
**声道语义(上下行含义不同,务必区分):** |
|||
|
|||
| | 左声道 | 右声道 | |
|||
|---|---|---| |
|||
| **上行**(设备→APP) | 本端麦克风原声 | 对方通话原声 | |
|||
| **下行**(APP→设备) | **本端译音**(说给对方听) | **对端译音**(说给本端听) | |
|||
|
|||
APP 只负责按上表填左右;最终左/右播到哪只耳、如何与通话原声混音,**由固件决定**。 |
|||
|
|||
--- |
|||
|
|||
## 3. GATT 服务与特征 |
|||
|
|||
| 服务 | UUID | 特征 | UUID | 方向 | 属性 | |
|||
|---|---|---|---|---|---| |
|||
| 主服务 | `0000ABC0-0000-1111-2222-123456789ABC` | WRITE | `0000ABC1-0000-…` | APP → 设备 | Write No Response | |
|||
| | | NOTIFY | `0000ABC2-0000-…` | 设备 → APP | Notify | |
|||
| **通话音频服务** | `0000ABC0-0001-1111-2222-123456789ABC` | **CALL_WRITE** | `0000ABC1-0001-…` | **APP → 设备(下行译音)** | **Write No Response** | |
|||
| | | **CALL_RECEIVE** | `0000ABC2-0001-…` | **设备 → APP(上行原声)** | **Notify** | |
|||
|
|||
- CCCD:`00002902-0000-1000-8000-00805f9b34fb` |
|||
- **MTU**:APP 连接后请求 **517**(→ 单次写净荷上限 **514 B**) |
|||
- APP 还会尝试 `CONNECTION_PRIORITY_HIGH` 与 **PHY 2M**(失败自动回退 1M) |
|||
|
|||
--- |
|||
|
|||
## 4. 码率与帧长(贯穿全文的基础) |
|||
|
|||
opus 帧时长**恒为 20 ms**(与码率无关)。CBR 模式下: |
|||
|
|||
``` |
|||
单帧字节数 F = 码率(kbps) × 20 ms ÷ 8 |
|||
``` |
|||
|
|||
| 码率 | **单帧字节 F** | 一次 BLE 写最多几帧(514 ÷ 2F) | |
|||
|---|---|---| |
|||
| 8 kbps | 20 B | 12 | |
|||
| **16 kbps(默认)** | **40 B** | **6** | |
|||
| 24 kbps | 60 B | 4 | |
|||
| 32 kbps | 80 B | 3 | |
|||
|
|||
**上行、下行各有独立码率**,均可在 APP 侧配置: |
|||
|
|||
- `F_down`:下行单帧字节,APP 编码器用,固件解码器用。 |
|||
- `F_up`:上行单帧字节,固件编码器用,APP 解码器用 —— **必须与固件实际编码码率一致**(见 §7)。 |
|||
|
|||
> ⚠️ **固件不要把 40 硬编码。** 请按码率推算 `F`,或按 `F = 包长 ÷ (2 × N)` 推算。这样 APP 侧调码率时固件无需改代码。 |
|||
|
|||
--- |
|||
|
|||
## 5. 指令协议 |
|||
|
|||
### 5.1 帧格式 |
|||
|
|||
``` |
|||
APP 请求 : 0xAA [CMD] [LEN] [DATA…] [CRC8] |
|||
设备响应 : 0xBB [CMD] [LEN] [DATA…] [CRC8] |
|||
设备主动上报: 0xCC [CMD] [LEN] [DATA…] [CRC8] |
|||
``` |
|||
|
|||
- `LEN` = DATA 字节数(不含帧头/CMD/LEN/CRC);整帧长 = `LEN + 4`。 |
|||
- `CRC8` 覆盖**从帧头(含)到 DATA 末尾**,不含 CRC 自身。 |
|||
|
|||
### 5.2 CRC8 算法(勿套用标准库的 "CRC-8/MAXIM") |
|||
|
|||
APP 代码里函数名叫 `calculateCrc8Maxim`,但**实现是非反射版**:多项式 `0x31`、初值 `0x00`、MSB-first、不反射输入输出、无最终异或。 |
|||
标准 CRC-8/MAXIM 是**反射版**(0x8C),**结果不同**。请直接照抄下面的实现: |
|||
|
|||
```c |
|||
uint8_t crc8(const uint8_t *data, int len) { |
|||
uint8_t crc = 0x00; |
|||
for (int i = 0; i < len; i++) { |
|||
crc ^= data[i]; |
|||
for (int j = 0; j < 8; j++) { |
|||
crc = (crc & 0x80) ? (uint8_t)((crc << 1) ^ 0x31) : (uint8_t)(crc << 1); |
|||
} |
|||
} |
|||
return crc; |
|||
} |
|||
``` |
|||
|
|||
### 5.3 通话翻译相关指令 |
|||
|
|||
| CMD | 名称 | 方向 | 说明 | |
|||
|---|---|---|---| |
|||
| **0x05** | CONTROL_CODEC | APP → 设备 | 编解码控制(见下) | |
|||
| **0x16** | CALL_TRANSLATION_ON | 设备 → APP(`0xCC`) | 设备侧(按键等)请求**开启**通话翻译 | |
|||
| **0x17** | CALL_TRANSLATION_OFF | 设备 → APP(`0xCC`) | 设备侧请求**关闭**通话翻译 | |
|||
|
|||
**0x05 的 DATA = `[子命令, 声道模式]`** |
|||
|
|||
| 子命令 | 值 | 含义 | |
|||
|---|---|---| |
|||
| CLOSE | `0x00` | 关闭编解码(结束通话翻译) | |
|||
| **A2DP_PLAY** | **`0xA2`** | **开启通话翻译**:mic + dac 声音上行,并接收翻译后音频 | |
|||
| DECODE_ON | `0xA1` | 仅解码(音乐/通话远端),非通话翻译 | |
|||
| CALL_RECORD_PLAY | `0xA3` | 通话录音,非通话翻译 | |
|||
|
|||
声道模式:`0x01`=左 / `0x02`=右 / **`0x03`=立体声(通话翻译固定用这个)** |
|||
|
|||
**实际收发字节:** |
|||
|
|||
``` |
|||
开启:APP → AA 05 02 A2 03 [CRC] |
|||
设备 → BB 05 02 A2 03 [CRC] ← 期望回显子命令与声道 |
|||
关闭:APP → AA 05 02 00 03 [CRC] |
|||
设备 → BB 05 02 00 03 [CRC] |
|||
``` |
|||
|
|||
### 5.4 ACK 约定 |
|||
|
|||
APP 侧指令**串行**:发出一条后等 `0xBB` 响应才发下一条,**超时 1 秒**自动放行队列。 |
|||
请确保每条 `0xAA` 请求都回一条 `0xBB`,否则会拖慢后续指令。 |
|||
|
|||
--- |
|||
|
|||
## 6. 会话时序 |
|||
|
|||
```mermaid |
|||
sequenceDiagram |
|||
participant D as 耳机固件 |
|||
participant A as APP |
|||
|
|||
Note over A,D: ① 开启(APP 主动 或 设备按键触发) |
|||
D-->>A: 0xCC 16 …(可选:设备侧请求开启) |
|||
A->>A: 启动上行解码器 + 下行编码器 + 发送线程 |
|||
A->>D: AA 05 02 A2 03(CONTROL_CODEC / A2DP_PLAY / 立体声) |
|||
D-->>A: BB 05 02 A2 03 |
|||
|
|||
Note over A,D: ② 会话中(双向音频流并行) |
|||
loop 持续 |
|||
D-->>A: notify CALL_RECEIVE:上行立体声 opus(本端 mic + 对方声) |
|||
A->>A: 拆左右 → 两路端到端翻译 → 两路译音 PCM → opus 编码 |
|||
A->>D: write CALL_WRITE:下行 opus(左右帧级交替,无包头) |
|||
end |
|||
|
|||
Note over A,D: ③ 关闭 |
|||
D-->>A: 0xCC 17 …(可选:设备侧请求关闭) |
|||
A->>D: AA 05 02 00 03(CONTROL_CODEC / CLOSE) |
|||
D-->>A: BB 05 02 00 03 |
|||
A->>A: 停发送线程、停编解码、清空队列 |
|||
``` |
|||
|
|||
> ⚠️ APP 在**发出 `0xA2` 之前**就已启动下行发送线程。若「持续发送」开关是开的,固件可能在收到 `0xA2` 前就收到下行写入 —— **固件应能安全丢弃未进入翻译态时收到的音频包**。 |
|||
|
|||
--- |
|||
|
|||
## 7. 上行音频(设备 → APP) |
|||
|
|||
| 项 | 约定 | |
|||
|---|---| |
|||
| 特征 | `CALL_RECEIVE`(`0000ABC2-0001-…`)notify | |
|||
| 编码 | Opus,CBR,帧时长 20 ms | |
|||
| 采样率 | 16 kHz | |
|||
| 声道 | 立体声(左 = 本端麦克风原声,右 = 对方通话原声) | |
|||
| 包头 | **无** | |
|||
| 单帧字节 | `F_up`(由上行码率决定,默认 16 kbps → 40 B) | |
|||
| APP 解码配置 | `hasHeader=false, channel=2, sampleRate=16000, packetSize = 2 × F_up`(默认 80) | |
|||
| 包长校验 | APP **丢弃长度不是 `F_up` 整数倍**的包(防止错位污染流式解码器) | |
|||
|
|||
**上行码率必须双方一致。** APP 用它推 `F_up` 来做收包校验和解码器切分。固件若改了上行码率而 APP 没同步,**所有上行包都会被判成坏包丢掉**(表现:完全收不到对方声音)。 |
|||
|
|||
> ⚠️ **待固件确认**:APP 按 `packetSize = 2 × F_up` 的双声道流解码,即每 `2 × F_up` 字节为一个立体声单元。请确认固件上行的实际组织方式是 **`[左 F_up][右 F_up]` 交替**(与下行对称)。若不是,APP 侧解码参数需相应调整。 |
|||
|
|||
--- |
|||
|
|||
## 8. 下行音频(APP → 设备)★ 核心 |
|||
|
|||
| 项 | 约定 | |
|||
|---|---| |
|||
| 特征 | `CALL_WRITE`(`0000ABC1-0001-…`)**Write Without Response** | |
|||
| 编码 | Opus,**CBR**,单声道编码器 ×2(左右各一路,互相独立) | |
|||
| 采样率 | 16 kHz | |
|||
| 帧时长 | **恒定 20 ms/帧** | |
|||
| 单帧字节 | **`F`**(由下行码率决定,默认 16 kbps → **40 B**) | |
|||
| 包头 | **无** | |
|||
| 包结构 | 左右**帧级交替** | |
|||
| 一拍字节数 | **`2 × N × F`**(N = 每声道帧数,默认 N=2、F=40 → **160 B**) | |
|||
|
|||
### 8.1 包结构 |
|||
|
|||
通式(N 帧/声道,每帧 F 字节): |
|||
|
|||
``` |
|||
[L0][R0][L1][R1] … [L(N-1)][R(N-1)] 每个 [] 恒为 F 字节 |
|||
``` |
|||
|
|||
默认配置(N=2,F=40B,16 kbps)→ 160 字节: |
|||
|
|||
``` |
|||
偏移: 0 40 80 120 160 |
|||
┌────────┬────────┬────────┬────────┐ |
|||
│ L0 │ R0 │ L1 │ R1 │ |
|||
│ 40B │ 40B │ 40B │ 40B │ |
|||
└────────┴────────┴────────┴────────┘ |
|||
左 右 左 右 |
|||
``` |
|||
|
|||
同样 N=2,但下行码率降到 8 kbps(F=20B)→ 只有 80 字节: |
|||
|
|||
``` |
|||
偏移: 0 20 40 60 80 |
|||
┌────┬────┬────┬────┐ |
|||
│ L0 │ R0 │ L1 │ R1 │ 每帧 20B |
|||
└────┴────┴────┴────┘ |
|||
``` |
|||
|
|||
- 每帧都是**完整、独立可解**的 opus 帧。 |
|||
- 左右两路要送进**两个独立的 opus 解码器**(不要用一个立体声解码器解)。 |
|||
|
|||
### 8.2 固件解包伪代码 |
|||
|
|||
```c |
|||
#define OPUS_FRAME_MS 20 |
|||
|
|||
// 由码率推单帧字节:16→40, 8→20, 24→60, 32→80 |
|||
static inline int frame_bytes_of(int bitrate_kbps) { |
|||
return bitrate_kbps * OPUS_FRAME_MS / 8; |
|||
} |
|||
|
|||
// g_downlink_frame_bytes = frame_bytes_of(约定的下行码率) |
|||
void on_downlink_packet(const uint8_t *packet, int len) { |
|||
const int F = g_downlink_frame_bytes; |
|||
if (F <= 0 || len % (2 * F) != 0) { /* 异常包,丢弃 */ return; } |
|||
|
|||
int n = len / (2 * F); // 本包帧数/声道,动态推算 |
|||
for (int i = 0; i < n; i++) { |
|||
const uint8_t *left = packet + (i * 2) * F; |
|||
const uint8_t *right = packet + (i * 2 + 1) * F; |
|||
opus_decode(dec_left, left, F, pcm_left, FRAME_SAMPLES, 0); |
|||
opus_decode(dec_right, right, F, pcm_right, FRAME_SAMPLES, 0); |
|||
// 播放 / 混音 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> **不要假设 N 固定为 2,也不要假设 F 固定为 40。** N 按 `len / (2×F)` 推,F 按码率推。这样 APP 调参时固件无需改代码。 |
|||
|
|||
### 8.3 一拍可能被拆成多个 BLE 写 |
|||
|
|||
BLE 单次写净荷上限是 `MTU-3`(**514 B**)。当一拍的数据(`2 × N × F`)超过它时,**APP 按「左右一对」(`2 × F`) 对齐拆成多个 BLE 写连续发出**。 |
|||
|
|||
例:N=20、F=40B → 一拍 1600 B → 拆 4 个写(6 + 6 + 6 + 2 帧)。 |
|||
|
|||
**这对固件完全透明,不需要任何特殊处理:** |
|||
|
|||
- 每个分片本身仍是 `2 × F` 的整数倍,左右交替顺序不变; |
|||
- 所以固件无论「每个 BLE 包独立解析」还是「当成连续字节流解析」,结果都正确; |
|||
- §8.2 的解包函数原样适用。 |
|||
|
|||
### 8.4 静音填充(关键约定) |
|||
|
|||
APP 的左右两路译音是**异步产生**的,任何一拍都可能只有一边有数据。此时 APP **不会缩短包长**,而是用一帧**编码好的静音 opus 帧**填满缺的位置。 |
|||
|
|||
这意味着: |
|||
|
|||
- 包长永远恒定、左右永远严格对位,**固件可无条件按固定偏移切分**; |
|||
- 静音帧是**合法的 opus 帧**(由编码器编码零 PCM 得到,**不是全 0 字节**),正常解码即得静音 PCM; |
|||
- 只有一边说话时,另一边持续收到静音帧 —— 这是**正常现象,不是丢包**。 |
|||
|
|||
### 8.5 持续发送开关 |
|||
|
|||
| 状态 | 行为 | |
|||
|---|---| |
|||
| **关**(默认) | 左右都没有译音时,该拍**不发包**,链路空闲 | |
|||
| **开** | 左右都没有译音时,仍按节拍发**全静音包**,保持设备侧音频流不断 | |
|||
|
|||
> 若固件的解码/播放管线**要求音频流不能断**(断流会导致 underrun、PLL 失锁、时基丢失等),告诉我们,把默认值改成「开」。 |
|||
|
|||
### 8.6 带宽 |
|||
|
|||
| 配置 | 一拍字节 / 节拍 | 带宽 | |
|||
|---|---|---| |
|||
| 16 kbps,N=2,40 ms | 160 B / 40 ms | 4000 B/s = 32 kbps(左右各 16k) | |
|||
| 8 kbps,N=2,40 ms | 80 B / 40 ms | 2000 B/s = 16 kbps(左右各 8k) | |
|||
|
|||
--- |
|||
|
|||
## 9. 可配置参数(APP 侧运行时可调) |
|||
|
|||
APP 内置调试页(通话翻译调试)可实时下发,**无需重连**,联调时可现场改: |
|||
|
|||
| 参数 | 默认 | 范围 | 说明 | |
|||
|---|---|---|---| |
|||
| `downlinkBitrateKbps` | **16** | 8 / 16 / 24 / 32 | **下行**码率 → `F = kbps × 2.5` | |
|||
| `uplinkBitrateKbps` | **16** | 8 / 16 / 24 / 32 | **上行**码率,**必须与固件实际编码一致** | |
|||
| `bundleFrameCount` | **2** | **1 – 20** | 每声道每拍帧数 N;一拍 = `2 × N × F` 字节 | |
|||
| `sendIntervalMs` | **40** | **5 – 200**(步长 5) | 发送节拍 | |
|||
| `continuousSend` | **关** | 开 / 关 | 无译音时是否持续发静音包 | |
|||
|
|||
### 9.1 节拍必须与帧数匹配 |
|||
|
|||
每帧恒为 20 ms 音频(与码率无关),所以: |
|||
|
|||
``` |
|||
节拍(ms) == N × 20 → 下发速率与真实语速 1:1(推荐) |
|||
节拍 > N × 20 → 偏慢,译音在 APP 侧积压,延迟越来越大 |
|||
节拍 < N × 20 → 偏快,设备侧解码缓存持续上涨,可能溢出 |
|||
``` |
|||
|
|||
速查(16 kbps / F=40B): |
|||
|
|||
| N(帧/声道) | 一拍字节 | 匹配节拍 | BLE 写次数 | |
|||
|---|---|---|---| |
|||
| 1 | 80 B | 20 ms | 1 | |
|||
| **2(默认)** | **160 B** | **40 ms** | **1** | |
|||
| 4 | 320 B | 80 ms | 1 | |
|||
| 6 | 480 B | 120 ms | 1 | |
|||
| 10 | 800 B | 200 ms | 2 | |
|||
| 20 | 1600 B | 400 ms | 4 | |
|||
|
|||
> ⚠️ **N > 10 配不出 1:1 节拍**:节拍上限是 200 ms,而 N=20 需要 400 ms。此时只能超发(设备侧缓存会涨)。若确实要用大 N,需把节拍上限放开 —— 告诉我们即可。 |
|||
|
|||
### 9.2 改码率会重启编码器 |
|||
|
|||
opus 编码器的 packetSize 在启动时固化。APP 改下行码率时会:**停编码流 → 清空左右发送队列 → 作废旧静音模板 → 用新帧长重启编码流**。 |
|||
避免新旧帧长混进同一包导致整包错位。表现为下行有一次**极短(数十毫秒)停顿**,属正常。停顿之后**包长按新 F 变化**,固件需按新码率同步更新 `F`。 |
|||
|
|||
--- |
|||
|
|||
## 10. 异常与边界约定 |
|||
|
|||
| 场景 | APP 侧行为 | 对固件的影响 / 要求 | |
|||
|---|---|---| |
|||
| BLE 写拥塞(底层缓冲满) | 该分片不发,**尚未发出的**帧退回队头,下一拍续发。**不丢译音** | 已发出的分片自身对齐、顺序正确,字节流不错位;剩余帧下一拍补上 | |
|||
| 单帧长度异常(≠ 当前 F) | 入队时**直接丢弃**该帧 | 保证固件收到的包长永远是 `2 × F` 的整数倍 | |
|||
| 静音帧模板未就绪(编码器刚启动的极短窗口) | 整拍**不发**,帧退回队列 | 会话最开始可能有几十 ms 无下行,属正常 | |
|||
| 运行中改码率 | 重启编码器、清队列、重建静音模板 | 短暂停顿;之后包长按新 `F` 变化 | |
|||
| 意外断连 | 1 s 后主动回连(最多 5 次);期间**译音继续缓存不丢**;回连成功后 **1600 ms** 重下 `0xA2` 并续发 | 固件回连后需能重新接受 `0xA2` 并恢复播放 | |
|||
| 会话结束 | 停发送线程、清空左右队列 | 下行立即停止 | |
|||
| 未进翻译态就收到下行包 | 「持续发送」开启时可能发生(见 §6) | **固件应容忍并丢弃** | |
|||
|
|||
--- |
|||
|
|||
## 11. 联调验收清单 |
|||
|
|||
按顺序逐项验证,每项都能独立定位问题: |
|||
|
|||
- [ ] **0. 码率对齐**:双方确认上、下行码率各是多少,据此各自算出 `F`(16 kbps → 40 B)。**这是一切的前提。** |
|||
- [ ] **1. 指令通路**:APP 发 `AA 05 02 A2 03`,固件回 `BB 05 02 A2 03`;关闭同理。CRC 双方一致。 |
|||
- [ ] **2. 上行通路**:固件上报立体声 opus,APP 能解出左右两路(左=本端 mic,右=对方声),无「长度非 `F_up` 倍数」告警。 |
|||
- [ ] **3. 下行包长**:固件收到的每包长度恒为 `2 × N × F`(默认 160 B),且始终是 `2 × F` 的整数倍。 |
|||
- [ ] **4. 下行对位**:固件按 `F` 切分,偶数块喂左解码器、奇数块喂右解码器,**两路都解码成功**(`opus_decode` 无错误返回)。 |
|||
- [ ] **5. 单边说话**:只有本端说话时,右声道收到静音帧(可解码、内容为静音),**不应报错或丢包**。 |
|||
- [ ] **6. 双向同传**:本端说话 → 对方听到译音;对方说话 → 本端听到译音;左右不串台。 |
|||
- [ ] **7. 节拍稳定**:固件侧收包间隔稳定在 40 ms 附近,解码缓存水位不持续上涨、也不饿死。 |
|||
- [ ] **8. 长时间跑**:连续 10 分钟无缓存溢出、卡顿、左右错位。 |
|||
- [ ] **9. 断连恢复**:手动断连后自动回连,`0xA2` 重下发,音频恢复。 |
|||
- [ ] **10. 持续发送开关**:打开后,无人说话时固件仍持续收到全静音包,播放无杂音。 |
|||
- [ ] **11. 大帧数拆写**(可选):把 N 调到 20,固件收到的是多个 480 B/160 B 的包,解码播放仍正常。 |
|||
- [ ] **12. 切码率**(可选):APP 切到 8 kbps 后包长变 80 B,固件按新 `F=20B` 切分仍正常。 |
|||
|
|||
> 联调时 APP 侧日志会打印每拍的:包序、左右真实帧数、静音补齐数、总字节、拆写次数、队列积压。出问题可直接对时间戳定位是哪一侧。 |
|||
|
|||
--- |
|||
|
|||
## 12. 待双方确认 |
|||
|
|||
1. **上下行码率各定多少?**(§4/§9)APP 默认上下行均 16 kbps(F=40 B),支持 8/16/24/32。**上行码率必须与固件实际编码一致**,否则 APP 会把所有上行包判成坏包。 |
|||
2. **上行结构?**(§7)是否为 `[左 F_up][右 F_up]` 交替?APP 当前按 `packetSize = 2 × F_up` / 双声道解。 |
|||
3. **持续发送要不要默认打开?**(§8.5)固件是否要求下行不断流? |
|||
4. **节拍与帧数怎么定?**(§9)默认 2 帧 / 40 ms(160 B)是否符合固件解码缓存的期望?需要大包低频(如 4 帧/80 ms)或小包高频(1 帧/20 ms)都可以,告知目标值即可。 |
|||
5. **静音帧能否正常解码?**(§8.4)是标准 opus 帧,不是全 0 字节。 |
|||
6. **未进翻译态的下行写入能否安全丢弃?**(§6) |
|||
|
|||
--- |
|||
|
|||
## 13. 附:APP 侧实现位置(备查) |
|||
|
|||
| 内容 | 文件 · 符号 | |
|||
|---|---| |
|||
| 下行组包 / 分片写 / 静音补齐 | `ble_service/android/…/BleService.kt` → `trySendMixedChunk` / `buildInterleavedPacket` / `maxFramesPerWrite` | |
|||
| iOS 同构实现 | `ble_service/ios/…/BleService.swift` → 同名方法 | |
|||
| 码率 ↔ 帧长换算、改码率重启编码器 | `BleService.kt` → `bitrateToFrameBytes` / `applyDownlinkBitrate` / `applyUplinkBitrate` | |
|||
| 上行收包校验 | `BleService.kt` → `onCharacteristicChanged` / `isValidUplinkAudioLen` | |
|||
| 指令协议 / CRC8 | `ble_service/android/…/BleCommandSender.kt` → `createCommandPacket` / `calculateCrc8Maxim` | |
|||
| UUID / 指令常量 | `ble_service/android/…/BleConst.kt` | |
|||
| 调试页(现场调参) | `lib/modules/call_translation_debug/` | |
|||
Loading…
Reference in new issue