17 KiB
通话翻译 硬件协议与数据结构对接文档
适用产品:Voitrans(Deep Voice)AI 翻译耳机 通信方式:BLE(GATT) 音频编码:Opus(16 kHz / 单声道 / 无文件头裸流) 本文档以 App 端实现(
local_plugins/ble_service)为准,供硬件/固件侧对接。
0. 名词约定
| 名词 | 含义 |
|---|---|
| App / 手机 | 主控端(BLE Central),负责 ASR、翻译、TTS、下行翻译音频编码 |
| 设备 / 耳机 | 从机(BLE Peripheral),采集通话双方音频、播放翻译音频 |
| 本端 / 近端 | 佩戴耳机的用户(mic 采集) |
| 对端 / 远端 | 通话另一方(耳机 DAC / 通话下行声音) |
| 上行 | 设备 → App(原始通话音频) |
| 下行 | App → 设备(翻译后的音频) |
通话翻译整体链路:
上行 opus(双声道) ASR+翻译+TTS 下行 opus(分声道)
[耳机 mic/dac] ───────────────► [App 解码/分离] ─────────────► [App 编码] ───────────────► [耳机 左/右声道播放]
近端 = 左声道 左声道(0)=本端译文
远端 = 右声道 右声道(1)=对端译文
1. BLE GATT 服务与特征
1.1 命令与控制服务(主服务)
| 项 | UUID | 属性 | 用途 |
|---|---|---|---|
| 主服务 | 0000ABC0-0000-1111-2222-123456789ABC |
— | 命令控制 |
| 写特征 | 0000ABC1-0000-1111-2222-123456789ABC |
Write | App 下发指令(0xAA 帧) |
| 通知特征 | 0000ABC2-0000-1111-2222-123456789ABC |
Notify | 设备上报响应/主动上报(0xBB / 0xCC 帧) |
1.2 通话音频服务(通话翻译核心)
| 项 | UUID | 属性 | 用途 |
|---|---|---|---|
| 通话音频服务 | 0000ABC0-0001-1111-2222-123456789ABC |
— | 通话翻译音频 |
| 通话写音频特征 | 0000ABC1-0001-1111-2222-123456789ABC |
Write (No Response) | 下行:App → 设备,翻译后 opus |
| 通话收音频特征 | 0000ABC2-0001-1111-2222-123456789ABC |
Notify | 上行:设备 → App,通话原始 opus |
1.3 普通音频服务(AI 单次对话 / 音乐等,非通话翻译)
| 项 | UUID | 属性 | 用途 |
|---|---|---|---|
| 音频服务 | 0000AE00-0000-1000-8000-00805F9B34FB |
— | 普通音频上行 |
| 接收音频特征 | 0000AE02-0000-1000-8000-00805F9B34FB |
Notify | 上行 opus(AI 对话等) |
1.4 唤醒词 OTA 服务(升级唤醒词资源,非通话翻译)
| 项 | UUID |
|---|---|
| 服务 | 0000ABC0-0002-1111-2222-123456789ABC |
| 写 | 0000ABC1-0002-1111-2222-123456789ABC |
| 通知 | 0000ABC2-0002-1111-2222-123456789ABC |
客户端启用通知使用标准 CCCD 描述符
00002902-0000-1000-8000-00805F9B34FB。 App 端建议协商 MTU(下行单包最大约 165B,MTU 需 ≥ ~180,见 §7)。
1.5 广播识别信息(参考)
厂商自定义广播字段常量:
| 字段 | 值 |
|---|---|
| 厂商 ID(Manufacturer ID) | 0xABEF |
| 客户 ID | 0xA001 |
| 产品 ID | 0xA001 |
| 蓝牙状态-未连接 | 0x00 |
| 蓝牙状态-已连接 | 0x0C |
App 端当前扫描/匹配主要依据设备名,广播厂商字段为约定信息。
2. 命令控制协议(0xABC1 / 0xABC2 通道)
所有控制指令走命令控制服务的写/通知特征,采用统一帧格式。
2.1 帧格式
| 方向 | 帧头 | 结构 |
|---|---|---|
| App → 设备(请求) | 0xAA |
[0xAA] [CMD] [LEN] [DATA(LEN 字节)] [CRC] |
| 设备 → App(响应) | 0xBB |
[0xBB] [CMD] [LEN] [DATA(LEN 字节)] [CRC] |
| 设备 → App(主动上报) | 0xCC |
[0xCC] [CMD/类型] [LEN] [DATA(LEN 字节)] [CRC] |
- 帧头:1 字节,区分帧类型(
0xAA/0xBB/0xCC)。 - CMD:1 字节,指令码,见 §3。
- LEN:1 字节,
DATA的字节数(不含帧头/CMD/LEN/CRC)。无数据时LEN=0x00。 - DATA:
LEN字节负载。 - CRC:1 字节,CRC-8(多项式 0x31),校验范围 = 帧头 + CMD + LEN + DATA(即除 CRC 自身外的全部字节)。
- 整帧总长 =
LEN + 4。
说明:设备响应(0xBB)App 侧会做 CRC 校验;主动上报(0xCC)当前 App 侧未强制校验 CRC,但仍要求带上正确的 CRC 字节并保证
总长 = LEN + 4,否则会被丢弃(长度不匹配直接返回)。
2.2 CRC-8 算法(务必与本实现一致)
多项式 0x31、初值 0x00、MSB 优先、不反转、不异或输出。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++) {
if (crc & 0x80) crc = (uint8_t)((crc << 1) ^ 0x31);
else crc = (uint8_t)(crc << 1);
}
}
return crc;
}
2.3 帧示例(含实算 CRC)
| 含义 | 完整字节 |
|---|---|
| 获取版本号(请求) | AA 01 00 86 |
| 获取电量(请求) | AA 04 00 F1 |
| 打开解码(0xA1,立体声,请求) | AA 05 02 A1 03 A3 |
| 开启通话翻译(0xA2,立体声,请求) | AA 05 02 A2 03 8E |
| 打开通话录音解码(0xA3,立体声,请求) | AA 05 02 A3 03 7A |
| 关闭编解码(请求) | AA 05 02 00 03 A8 |
| 查询语音唤醒状态(请求) | AA 14 00 9F |
| 设置语音唤醒开启(请求) | AA 15 01 E1 55 |
3. 指令码(CMD)总表
| CMD | 名称 | 方向 | 说明 |
|---|---|---|---|
0x01 |
获取版本号 | 请求/响应 | DATA 为版本号字节(丢弃开头连续 0xFF,其余按十进制点分) |
0x02 |
获取产品 ID | 请求/响应 | |
0x03 |
获取颜色 ID | 请求/响应 | DATA[0]=颜色 ID |
0x04 |
获取电量 | 请求/响应/上报 | 见 §6.1 |
0x05 |
编解码控制 | 请求/响应/上报 | 通话翻译核心指令,见 §4、§5 |
0x06 |
音量增大 | 请求 | 无 DATA |
0x07 |
音量减小 | 请求 | 无 DATA |
0x08 |
获取唤醒词版本 | 请求/响应 | 响应含左右耳各 4 字节版本 |
0x09 |
开始 AI 单次对话 | 上报(0xCC) | 设备侧触发,App 打开编码器 |
0x10 |
结束 AI 单次对话 | 上报(0xCC) | 设备侧触发,App 关闭编码器 |
0x11 |
唤醒(语音唤醒信号) | 上报(0xCC) | 设备上报唤醒 |
0x14 |
查询语音唤醒状态 | 请求/响应 | 响应 DATA[0]:0xE1=开 / 0xE0=关 |
0x15 |
设置语音唤醒状态 | 请求/响应 | 请求 DATA[0]:0xE1=开 / 0xE0=关 |
0x16 |
上报:开启通话翻译 | 上报(0xCC) | 设备侧发起开启通话翻译,见 §5.2 |
0x17 |
上报:关闭通话翻译 | 上报(0xCC) | 设备侧发起关闭通话翻译,见 §5.2 |
4. 编解码控制指令 0x05(通话翻译核心)
4.1 请求格式
AA 05 02 [control] [channel] [CRC]
DATA = [control(1B), channel(1B)],LEN=0x02。
control(编解码控制值)
| 值 | 名称 | 含义 |
|---|---|---|
0x00 |
关闭编解码 | 关闭全部编解码(结束通话翻译/录音/播放) |
0xA1 |
打开解码 | 播放远端/音乐声音(上行单声道 opus 40B) |
0xA2 |
开启通话翻译 | mic + dac 声音上行,并接收 App 下行翻译重编码音频(上行双声道 opus 80B) |
0xA3 |
通话录音 | mic + dac 声音上行录制(上行双声道 opus 80B) |
0xB1 |
打开编码 | 打开编码器(备用) |
0xF0 |
左声道通话休眠 | 见 §5.3(一般由设备主动上报) |
0xF1 |
右声道通话休眠 | 见 §5.3 |
0xF2 |
左声道通话恢复 | 见 §5.3 |
0xF3 |
右声道通话恢复 | 见 §5.3 |
channel(声道模式)
| 值 | 含义 |
|---|---|
0x01 |
左声道 |
0x02 |
右声道 |
0x03 |
立体声(通话翻译默认) |
4.2 响应格式
设备回:BB 05 02 [control] [channel] [CRC](原样回显 control 与 channel)。
若执行失败,可回 LEN 与实际不符或 DATA[0]=0x00,App 会按失败处理。
4.3 各 control 值对应的上行音频参数
App 收到对应 control 后,按下表启动 Opus 解码器(必须与固件上行编码参数一致):
| control | 上行声道数 | 采样率 | 帧长(packetSize) | 备注 |
|---|---|---|---|---|
0xA1 打开解码 |
1(单声道) | 16000 | 40 | 远端/音乐 |
0xA2 开启通话翻译 |
2(双声道) | 16000 | 80 | 左=mic(近端),右=dac(远端) |
0xA3 通话录音 |
2(双声道) | 16000 | 80 | 同上 |
5. 通话翻译业务协议
5.1 开启/关闭方式
通话翻译可由两侧任意一方发起:
- App 主动开启:App 下发
0x05 / 0xA2(AA 05 02 A2 03 8E)→ 设备开始上行双声道通话音频,并准备接收下行翻译音频。 - 设备主动发起:设备上报
0x16(开启)/0x17(关闭),App 收到后进入/退出通话翻译流程。
关闭时下发 0x05 / 0x00(AA 05 02 00 03 A8)关闭编解码。
5.2 设备主动上报开启/关闭(0x16 / 0x17)
帧格式(0xCC 主动上报):
CC 16 [LEN] [DATA...] [CRC] // 开启通话翻译
CC 17 [LEN] [DATA...] [CRC] // 关闭通话翻译
- App 侧解析:若
DATA长度 ≥ 4,取DATA[3]作为 status(约定0xFF);否则 status 视为无效。 - 建议固件按约定
LEN=0x01, status=0xFF或与固件协商一致的固定格式发送;关键在于总长 = LEN + 4必须成立。
5.3 声道休眠 / 恢复(流控,0x05 的 0xF0~0xF3)
用于通话过程中,设备侧临时暂停某一声道的下行音频(例如该声道当前无需播放),App 不会丢包,休眠期间该声道下行包转入积压缓存,收到恢复指令后按序补发。
由设备主动上报(0xCC,CMD=0x05,DATA[0]=控制值):
CC 05 [LEN] [F0] ... [CRC] // 左声道休眠:App 暂停下发左声道(channel=0)
CC 05 [LEN] [F1] ... [CRC] // 右声道休眠:App 暂停下发右声道(channel=1)
CC 05 [LEN] [F2] ... [CRC] // 左声道恢复:App 恢复并按序补发左声道积压
CC 05 [LEN] [F3] ... [CRC] // 右声道恢复:App 恢复并按序补发右声道积压
| 上报值 | 含义 | App 行为 | 配对 |
|---|---|---|---|
0xF0 |
左声道通话休眠 | 暂停下发左声道(0),仅发右声道 | 与 0xF2 配对 |
0xF1 |
右声道通话休眠 | 暂停下发右声道(1),仅发左声道 | 与 0xF3 配对 |
0xF2 |
左声道通话恢复 | 恢复左声道下发,补发休眠期间积压包 | 与 0xF0 配对 |
0xF3 |
右声道通话恢复 | 恢复右声道下发,补发休眠期间积压包 | 与 0xF1 配对 |
休眠/恢复为幂等操作;重复上报不叠加。
6. 部分响应数据结构
6.1 电量信息(0x04)
DATA 至少 3 字节:[左耳, 右耳, 充电仓],每字节:
- bit0~bit6:电量百分比(
value & 0x7F) - bit7:是否充电(
value & 0x80 != 0)
主动上报同格式,帧头 0xCC,CMD=0x04。
6.2 唤醒词版本(0x08)
响应 DATA 为 8 字节:[左耳 4B][右耳 4B],每 4 字节点分十进制即版本号。
6.3 语音唤醒(0x14 / 0x15)
0x14响应:DATA[0]=0xE1(开) /0xE0(关)。0x15请求:DATA[0]=0xE1(开) /0xE0(关);响应:DATA[0]=命令状态,DATA[1]=0x01表示设置成功。
7. 音频数据结构(0xABC1-0001 / 0xABC2-0001 通道)
音频不走 0xAA/0xBB/0xCC 帧,直接在通话音频服务的特征上传输原始负载。
7.1 上行:设备 → App(通话收音频特征 0000ABC2-0001-...)
- 纯 Opus 裸流(无 0xAA/CRC 帧头、无文件头、无自定义包头),直接把 opus 码流分片放入 Notify 负载即可。
- 通话翻译(
0xA2/0xA3):双声道(stereo)交织 Opus,采样率 16 kHz,帧长(packetSize)80。- 左声道 = 近端(mic 采集,本端说话人)
- 右声道 = 远端(dac / 通话对端)
- 普通解码(
0xA1):单声道 Opus,16 kHz,帧长 40。 - App 侧把收到的负载按到达顺序送入解码器,因此分片可任意长度,但需保证 opus 帧边界正确、顺序不乱。
7.2 下行:App → 设备(通话写音频特征 0000ABC1-0001-...)
App 将本端/对端译文分别用独立 Opus 编码器编码(16 kHz / 单声道 / 每帧约 40B),左右各累积 N 帧后合并为 1 个下行包发送。
下行包结构:
+---------+---------+---------------------------------------------+
| 序号 | 声道 | N 个 opus 帧顺次拼接 |
| 4 字节 | 1 字节 | 每帧约 40 字节(16kHz/mono/无头) |
| uint32 | 0/1 | |
| 小端 | | |
+---------+---------+---------------------------------------------+
[0..3] [4] [5 .. 5 + N*framelen)
| 字段 | 偏移 | 长度 | 说明 |
|---|---|---|---|
| 序号 seq | 0 | 4 | 小端序 uint32(低字节在前)。每个声道各自独立自增,从 0 开始 |
| 声道 channel | 4 | 1 | 0x00=左声道(本端译文) / 0x01=右声道(对端译文) |
| opus 数据 | 5 | N×帧长 | N 帧 opus 顺次拼接,每帧 16kHz/单声道 |
- N(合包帧数):默认
4,可运行时调整(范围 1~20)。故单包总长 =5 + N × 帧长(帧长约 40B,默认约 165B)。 - 写类型:Write Without Response(不等 ACK,保吞吐)。
- 发送节拍:App 按固定间隔逐包下发(默认约 60ms/包,可调);左右声道公平轮转。
- 不丢包保证:底层写拥塞时 App 会退避重试并按声道队头重发,固件侧只需按
seq归位/去重与顺序播放。
⚠️ 字节序特别说明:下行包序号在实现中为小端序(little-endian)(
buildBundlePacket写入与发送线程解析均为小端)。代码注释里出现的“大端”描述为笔误,请以小端为准。
8. 通话翻译完整时序(示例:App 发起)
App 设备(耳机)
│ (已连接, 已订阅 0xABC2 / 0xABC2-0001 通知, 已协商 MTU)
│
│ ── 0x05/0xA2 开启通话翻译 ──► AA 05 02 A2 03 8E
│ ◄── 0xBB 响应回显 ── BB 05 02 A2 03 ..
│
│ 开始采集通话音频
│ ◄════ 上行双声道 opus(16k/80) ════ (通话收音频特征 0xABC2-0001, 裸流)
│
│ [App: 解码→分离近/远端→ASR→翻译→TTS→独立编码]
│
│ ════ 下行 opus 合包 ════► (通话写音频特征 0xABC1-0001)
│ [seq(4B小端)][ch 0/1][N×opus] 耳机按 ch 播放到对应声道
│
│ ◄── (可选) 0xCC 05 F0/F1 休眠 ── 暂停对应声道下发
│ ◄── (可选) 0xCC 05 F2/F3 恢复 ── 恢复并按序补发
│
│ ── 0x05/0x00 关闭编解码 ──► AA 05 02 00 03 A8
│ 或 ◄── 0xCC 0x17 设备请求关闭 ──
│ 停止采集/播放
9. 关键参数汇总(固件对接必对齐项)
| 项 | 值 |
|---|---|
| Opus 采样率 | 16000 Hz |
| Opus 声道(上行通话翻译) | 2(stereo,左=近端 右=远端) |
| Opus 帧长(上行通话翻译 0xA2/0xA3) | packetSize = 80 |
| Opus 帧长(上行单声道 0xA1) | packetSize = 40 |
| Opus 帧长(下行译文) | 单声道,每帧约 40B |
| Opus 文件头 | 无(裸流) |
| 下行合包序号字节序 | 小端 uint32 |
| 下行声道标识 | 0=左(本端译文) / 1=右(对端译文) |
| 下行默认合包帧数 N | 4(可调 1~20) |
| 下行写类型 | Write Without Response |
| 控制帧 CRC | CRC-8,多项式 0x31,初值 0x00,MSB 优先,校验含帧头 |
| 控制帧总长 | LEN + 4 |
| 响应超时(App 侧) | 1000 ms |
10. 对接注意事项 / 已知实现细节
- CRC 覆盖范围包含帧头(
0xAA/0xBB/0xCC),且CRC前的所有字节参与计算。 - 主动上报(0xCC)CRC:App 侧当前未强制校验,但长度必须满足
总长 = LEN + 4,否则整帧被丢弃。请固件正确填LEN并补 CRC 字节。 - 下行序号字节序为小端(代码注释“大端”为笔误,见 §7.2)。
- 下行合包帧数:默认
4(部分注释写“5/205B”为旧值,请以运行参数为准,可由 App 通过调试接口下发调整)。 - 上行为纯 opus 裸流,无任何自定义包头/序号;分片长度自由,但 opus 帧边界与顺序必须正确。
- 上行通话翻译为双声道交织(channel=2),近端/远端由左右声道区分;固件需保证左=mic、右=dac 的固定映射。
- 通话翻译期间设备可用
0xF0~0xF3做单声道流控,App 保证不丢包、按 seq 顺序补发;固件按声道 seq 播放即可。 - 建议连接后协商较大 MTU(≥ ~180 字节)以容纳单个下行包,减少分片。
本文档依据 App 端 local_plugins/ble_service(Android 实现)整理:BleConst.kt、BleCommandSender.kt、OpusAudioManager.kt、BleService.kt。如与固件实测不一致,请以双方联调确认为准。