适用版本:tcpv2.3+
mode:vad:{JWT_TOKEN}##mode:vad##input_audio_format:pcm{
"taskid": "abc12345",
"type": "listen",
"state": "start"
}{
"taskid": "abc12345",
"type": "listen",
"state": "detecting",
"mode": "vad"
}{
"taskid": "abc12345",
"type": "listen",
"state": "stop",
"mode": "vad"
}[##START][MessageType:8][task_id:8字节][seq:0000][JSON消息体][##END]{
"taskid": "abc12345",
"type": "listen",
"state": "start"
}{
"taskid": "abc12345",
"type": "listen",
"state": "stop"
}{
"taskid": "abc12345",
"type": "listen",
"state": "detecting",
"mode": "vad"
}{
"taskid": "abc12345",
"type": "listen",
"state": "stop",
"mode": "vad"
}1. 发送 LISTEN start (taskid: A)
2. 上传音频帧
3. 收到 LISTEN stop (taskid: A)
↓
4. 立即发送 LISTEN start (taskid: B) ← 关键:立即开始
5. 继续上传音频帧(taskid: B)
6. 同时接收并播放 taskid A 的音频回复1. 发送 LISTEN start (taskid: A)
2. 上传音频帧
3. 收到 LISTEN stop (taskid: A)
↓
4. 停止上传音频
5. 接收并播放 taskid A 的音频回复
6. 播放完成
↓
7. 发送 LISTEN start (taskid: B) ← 关键:播放完成后
8. 开始上传音频帧(taskid: B)时间轴 客户端 服务端
│
│ 生成 taskid: A
│ │
├─────────────────┼──── LISTEN start (A) ──────────────>│ 重置 VAD
│ │ │ 清空缓冲区
│ │<──── STATUS: 开始监听 ────────────────│
│ │ │
│ 开始录音 │
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:1) ───────────>│
├─────────────────┼──── AUDIO_FRAME (seq:2) ───────────>│ VAD 检测中
├─────────────────┼──── AUDIO_FRAME (seq:3) ───────────>│ 检测到语音
│ │ │
│ 用户说完,停止说话 │ VAD 触发
│ 继续录音(背景音) │
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:4) ───────────>│
│ │<──── LISTEN stop (A) ─────────────────│ 语音结束
│ │ │
│ 停止录音 │ 模型处理中
│ 等待播放 │ (生成回复)
│ │ │
│ │<──── AUDIO (seq:1) ────────────────────│
│ │<──── AUDIO (seq:2) ────────────────────│
│ 播放中... │
│ │<──── AUDIO (seq:3) ────────────────────│
│ │<──── END_FRAME ────────────────────────│
│ │ │
│ 播放完成 │
│ 生成新 taskid: B │
│ │ │
├─────────────────┼──── LISTEN start (B) ──────────────>│ 重置 VAD
│ │<──── STATUS: 开始监听 ────────────────│
│ │ │
│ 开始新一轮录音 │
▼ ▼ ▼时间轴 客户端 服务端
│
│ 生成 taskid: A
│ │
├─────────────────┼──── LISTEN start (A) ──────────────>│ 重置 VAD
│ │ │ 清空缓冲区
│ │<──── STATUS: 开始监听 ────────────────│
│ │ │
│ 开始录音 │
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:1) ───────────>│
├─────────────────┼──── AUDIO_FRAME (seq:2) ───────────>│ VAD 检测中
├─────────────────┼──── AUDIO_FRAME (seq:3) ───────────>│ 检测到语音
│ │ │
│ 用户说完,停止说话 │ VAD 触发
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:4) ───────────>│
│ │<──── LISTEN stop (A) ─────────────────│ 语音结束
│ │ │
│ 生成新 taskid: B(立即!) │ 模型处理 A
│ │ │ (生成回复)
├─────────────────┼──── LISTEN start (B) ──────────────>│ 重置 VAD
│ │<──── STATUS: 开始监听 ────────────────│
│ │ │
│ 继续录音(taskid: B) │
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:1, tid:B) ────>│
│ │<──── AUDIO (seq:1, tid:A) ─────────────│ 返回 A 的回复
│ │ │
│ 同时: │
│ - 播放 A 的回复 │
│ - 录音并上传 B 的音频 │
│ (AEC 消除回声) │
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:2, tid:B) ────>│
│ │<──── AUDIO (seq:2, tid:A) ─────────────│
├─────────────────┼──── AUDIO_FRAME (seq:3, tid:B) ────>│
│ │<──── AUDIO (seq:3, tid:A) ─────────────│
│ │<──── END_FRAME (tid:A) ────────────────│
│ │ │
│ A 播放完成,B 继续录音 │
▼ ▼ ▼时间轴 客户端 服务端
│
│ [taskid: A 正在处理中] │
│ │ │
│ 录音中(taskid: B) │ 生成 A 的回复
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:5, tid:B) ────>│
├─────────────────┼──── AUDIO_FRAME (seq:6, tid:B) ────>│
│ │<──── AUDIO (seq:1, tid:A) ─────────────│ 返回 A
│ │ │
│ 播放 A + 录音 B │ VAD 检测 B
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:7, tid:B) ────>│
│ │ │ B 触发 VAD!
│ │<──── LISTEN stop (B) ─────────────────│
│ │ │
│ 生成新 taskid: C(立即!) │ 打断 A
│ │ │ 开始处理 B
├─────────────────┼──── LISTEN start (C) ──────────────>│
│ │<──── STATUS: 开始监听 ────────────────│
│ │ │
│ 停止播放 A 的回复 │ A 停止发送
│ 继续录音(taskid: C) │ 模型处理 B
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:1, tid:C) ────>│
│ │ │ 生成 B 的回复
│ │<──── AUDIO (seq:1, tid:B) ─────────────│ 返回 B
│ │ │
│ 播放 B + 录音 C │
▼ ▼ ▼时间轴 客户端 服务端
│
│ [taskid: A 正在处理中] │
│ │ │
│ 录音中(taskid: B) │ 生成 A 的回复
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:5, tid:B) ────>│
├─────────────────┼──── AUDIO_FRAME (seq:6, tid:B) ────>│
│ │<──── AUDIO (seq:1, tid:A) ─────────────│ 返回 A
│ │ │
│ 播放 A + 录音 B │ VAD 检测 B
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:7, tid:B) ────>│ 检测到人声!
│ │<──── LISTEN detecting (B) ─────────────│ 200ms后触发
│ │ │
│ 立即停止播放 A │ 继续接收 B
│ 继续录音(taskid: B) │
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:8, tid:B) ────>│
├─────────────────┼──── AUDIO_FRAME (seq:9, tid:B) ────>│
│ │ │ B 语音结束
│ │<──── LISTEN stop (B) ─────────────────│
│ │ │
│ 生成新 taskid: C │ 开始处理 B
│ │ │
├─────────────────┼──── LISTEN start (C) ──────────────>│
│ │<──── STATUS: 开始监听 ────────────────│
│ │ │
│ 继续录音(taskid: C) │ 模型处理 B
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:1, tid:C) ────>│
│ │ │ 生成 B 的回复
│ │<──── AUDIO (seq:1, tid:B) ─────────────│ 返回 B
│ │ │
│ 播放 B + 录音 C │
▼ ▼ ▼时间轴 客户端 服务端
│
│ 录音中(taskid: A) │
│ │ │
├─────────────────┼──── AUDIO_FRAME (seq:1, tid:A) ────>│
├─────────────────┼──── AUDIO_FRAME (seq:2, tid:A) ────>│
│ │ │
│ 用户点击"取消"按钮 │
│ │ │
├─────────────────┼──── LISTEN stop (A) ───────────────>│ 抛弃所有音频
│ │<──── STATUS: 已停止并清空 ───────────│ 重置 VAD
│ │ │
│ 生成新 taskid: B │
│ │ │
├─────────────────┼──── LISTEN start (B) ──────────────>│ 开始新会话
│ │<──── STATUS: 开始监听 ────────────────│
│ │ │
│ 开始新录音 │
▼ ▼ ▼ ┌─────────────┐
│ 初始化 │
└──────┬──────┘
│
▼
┌─────────────┐
┌────>│ 空闲 │<────┐
│ └──────┬──────┘ │
│ │发送 start │
│ ▼ │
│ ┌─────────────┐ │
│ │ 录音中 │ │
│ └──────┬──────┘ │
│ │收到 stop │
│ ▼ │
│ ┌─────────────┐ │
│ │ 等待播放 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 播放中 │ │
│ └──────┬──────┘ │
│ │播放完成 │
└────────────┘ │
│
用户取消 ───────────────────┘| 状态 | 说明 | 可执行操作 |
|---|---|---|
| 空闲 | 等待用户开始对话 | 发送 LISTEN start,生成新 task_id |
| 录音中 | 持续发送音频帧 | 发送 AUDIO_FRAME;收到 stop 后转到等待播放 |
| 等待播放 | 等待服务端返回音频 | 接收音频帧 |
| 播放中 | 播放音频回复 | 播放音频;完成后回到空闲 |
┌─────────────┐
│ 初始化 │
└──────┬──────┘
│
▼
┌─────────────┐
┌────>│ 空闲 │
│ └──────┬──────┘
│ │发送 start
│ ▼
│ ┌─────────────┐
│ │ 录音中 │<──────┐
│ └──────┬──────┘ │
│ │收到 stop │
│ ▼ │
│ ┌─────────────┐ │
│ │ 录音+播放中 │ │
│ └──────┬──────┘ │
│ │ │
│ ├──────────────┘
│ │收到新 stop(打断)
│ │立即发送新 start
│ │
│ │播放完成且无新对话
└────────────┘
用户取消可在任意状态发生| 状态 | 说明 | 可执行操作 |
|---|---|---|
| 空闲 | 等待用户开始对话 | 发送 LISTEN start,生成新 task_id |
| 录音中 | 持续发送音频帧 | 发送 AUDIO_FRAME;收到 stop 后立即发送新 start |
| 录音+播放中 | 同时播放和录音 | 继续发送音频帧;播放音频;收到新 stop 则打断 |
current_task_idcurrent_task_id 改变时,旧 task_id 的音频会自动停止发送1. 用户正在说话(taskid: B)
2. 服务端正在返回 A 的音频回复
3. B 的 VAD 触发 → 发送 LISTEN stop (B)
4. 服务端更新 current_task_id = B
5. A 的音频检测到 task_id 不匹配 → 停止发送
6. 开始处理 B 的音频,生成新回复current_task_id 比较用户说话 A → 服务端处理 A → 用户立即说话 B → 用户又立即说话 Ccurrent_task_id客户端收到旧的音频包(网络延迟)→ 客户端误以为是新对话AI 的回复只剩最后一个字 → 用户开口打断