音频通道
WebSocket 与 MQTT+UDP 音频帧格式、心跳。
WebSocket 音频
直接通过 WebSocket 二进制帧收发 Opus 音频。上行用户语音 16kHz,下行 TTS 24kHz,帧长均 60ms。
MQTT + UDP 音频(加密)
握手响应的 udp 字段下发 AES-128 密钥与 nonce。每个 UDP 包 = 16 字节包头 + AES-CTR 加密的 Opus 负载:
16 字节包头(同时作为 AES-CTR nonce):
[0] type = 0x01 (audio) | 0x02 (heartbeat)
[1] flags = 0x00
[2:4] payload_len (big-endian uint16)
[4:8] ssrc (会话标识,取自 hello.udp.nonce 字节 [4:8])
[8:12] timestamp (设备自定义,如 unix 秒 / 毫秒)
[12:16] sequence (设备自定义序号)
后接:
type=0x01 → AES-128-CTR(Opus 负载)
type=0x02 → 可选不透明负载(原样回显,可不加密)建立 UDP 通道后,设备先发一个空包做 NAT 穿透,让服务端获知设备的 UDP 地址。之后即可双向收发音频。空包(仅 16 字节、非心跳 type)只用于地址学习,不会回包。
UDP 心跳(平台通用能力)
MQTT KeepAlive 只能证明控制通道仍在;海外 NAT / 防火墙常会在闲置数十秒后回收 UDP 映射,导致「MQTT 文字正常、说话无语音」。平台提供 UDP 心跳 echo:设备发送 type=0x02 包,Gateway 校验 ssrc 属于当前会话后,将完整 UDP 数据报原样返回,并刷新设备地址。发送频率、超时判定、断开重连由设备端自行实现。
| 字段 | 要求 | 说明 |
|---|---|---|
type | 固定 0x02 | 与音频 0x01 区分;不会进入 ASR |
ssrc | 必须匹配会话 | 取自 hello 下发的 udp.nonce 字节 [4:8] |
timestamp / sequence | 设备自定义 | 可用于算 RTT、去重;平台不解释 |
payload | 可选 | 0~若干字节,原样回显;可不加密 |
设备 → Gateway(Ping)
[0]=0x02 | flags | payload_len | ssrc | timestamp | sequence | [optional payload]
Gateway → 设备(Pong,字节级原样回显)
同一数据报完整返回
推荐(设备侧策略,非平台强制):
· 会话存活期间每 15~20s 发一次(有真实音频上行时可跳过当次)
· 连续 2~3 次无 Pong(如单次等待 2~3s)→ CloseAudioChannel + 重新 hello
· 收到 Pong 只更新「UDP 健康」时间戳,勿与 MQTT 超时混用下行 TTS 音频仍为 type=0x01。设备收包时若收到 type=0x02,应走心跳回调,不要当 Opus 解码。