# 语音通话完整流程图 ## 📊 整体架构 ``` ┌─────────────┐ WebSocket ┌─────────────┐ │ │ ◄─────────────────────────► │ │ │ 客户端 │ │ 服务器端 │ │ (uni-app) │ │ (FastAPI) │ │ │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────────┐ │ 录音管理器 │ │ ASR (阿里云) │ │ recorderMgr │ │ Paraformer │ └─────────────┘ └─────────────────┘ │ ▼ ┌─────────────────┐ │ LLM (通义千问) │ │ Qwen-flash │ └─────────────────┘ │ ▼ ┌─────────────────┐ │ TTS (阿里云) │ │ CosyVoice-v2 │ └─────────────────┘ ``` ## 🔄 详细流程 ### 阶段1:连接建立 ``` 客户端 服务器 │ │ ├─ 1. 打开语音通话页面 │ │ │ ├─ 2. 建立 WebSocket 连接 ──────────────►│ │ ws://192.168.1.141:30101/voice/call │ │ │ │ ├─ 3. 验证用户身份 │ │ │ ├─ 4. 创建会话 (VoiceCallSession) │ │ │ ├─ 5. 启动 ASR (paraformer-realtime-v2) │ │ │ ├─ 6. 启动后台任务 │ │ - LLM 处理循环 │ │ - TTS 处理循环 │ │ - 空闲检测 │ │ - 静默检测 │ │ │ ◄──────────────────────────────────── ├─ 7. 发送 ready 信号 │ {"type":"ready"} │ │ │ ├─ 8. 显示"已连接" │ │ │ ``` ### 阶段2:用户说话(优化后的流程) ``` 客户端 服务器 │ │ ├─ 1. 用户按住"按住说话"按钮 │ │ isTalking = true │ │ │ ├─ 2. 启动录音 │ │ format: 'pcm' │ │ sampleRate: 16000 │ │ numberOfChannels: 1 │ │ │ ├─ 3. 用户说话 3-5 秒 │ │ "你好,今天天气怎么样?" │ │ │ ├─ 4. 用户松开按钮 │ │ isTalking = false │ │ │ ├─ 5. 停止录音 │ │ recorderManager.stop() │ │ │ ├─ 6. 读取录音文件 │ │ tempFilePath → ArrayBuffer │ │ 大小: 160000 bytes (5秒) │ │ │ ├─ 7. 开始分片发送 ─────────────────────►│ │ 【官方推荐参数】 │ │ 每片: 3200 bytes │ │ 间隔: 100ms │ │ │ ├─ 片段 1 (3200 bytes) ─────────────────►├─ 接收片段 1 │ ├─ feed_audio(data) │ ├─ recognition.send_audio_frame(data) │ │ ├─ 延迟 100ms │ │ │ ├─ 片段 2 (3200 bytes) ─────────────────►├─ 接收片段 2 │ ├─ feed_audio(data) │ ├─ recognition.send_audio_frame(data) │ │ ├─ 延迟 100ms │ │ │ ├─ ... │ │ │ ├─ 片段 50 (3200 bytes) ────────────────►├─ 接收片段 50 │ ├─ feed_audio(data) │ ├─ recognition.send_audio_frame(data) │ │ ├─ 延迟 100ms │ │ │ ├─ 发送 "end" 标记 ─────────────────────►├─ 接收 "end" │ ├─ finalize_asr() │ ├─ recognition.stop() │ │ │ ├─ ASR 完成识别 │ │ 识别结果: "你好,今天天气怎么样?" │ │ ``` ### 阶段3:ASR 识别 ``` 服务器端流程 │ ├─ 1. ASR 接收音频片段 │ - 片段 1: 3200 bytes │ - 片段 2: 3200 bytes │ - ... │ - 片段 50: 3200 bytes │ ├─ 2. 实时识别(边收边识别) │ - 部分结果: "你好" │ - 部分结果: "你好,今天" │ - 部分结果: "你好,今天天气" │ ├─ 3. 收到 "end" 标记 │ - 调用 recognition.stop() │ ├─ 4. 返回最终结果 │ - is_sentence_end: true │ - text: "你好,今天天气怎么样?" │ ├─ 5. 触发回调 │ - WSRecognitionCallback.on_event() │ - session.handle_sentence(text) │ ├─ 6. 放入 LLM 队列 │ - asr_to_llm.put(text) │ ``` ### 阶段4:LLM 生成回复 ``` 服务器端流程 客户端 │ │ ├─ 1. 从队列获取识别文本 │ │ text = "你好,今天天气怎么样?" │ │ │ ├─ 2. 构建对话历史 │ │ history = [ │ │ {role: "system", content: "..."},│ │ {role: "user", content: text} │ │ ] │ │ │ ├─ 3. 调用 LLM (Qwen-flash) │ │ stream = chat_completion_stream() │ │ │ ├─ 4. 流式接收 LLM 输出 │ │ chunk 1: "你" │ │ chunk 2: "好" │ │ chunk 3: "呀" │ │ chunk 4: "," ◄─ 遇到标点 │ │ │ ├─ 5. 发送文本给客户端 ─────────────────►├─ 收到 reply_text │ {"type":"reply_text", │ 显示文字 │ "text":"你好呀,今天..."} │ │ │ ├─ 6. 放入 TTS 队列 │ │ llm_to_tts.put("你好呀,") │ │ │ ├─ 7. 继续接收 LLM 输出 │ │ chunk 5: "今" │ │ chunk 6: "天" │ │ ... │ │ │ ├─ 8. LLM 完成 │ │ llm_to_tts.put(END_OF_TTS) │ │ │ ``` ### 阶段5:TTS 合成语音 ``` 服务器端流程 客户端 │ │ ├─ 1. 从队列获取文本片段 │ │ text = "你好呀," │ │ │ ├─ 2. 清理文本 │ │ - 去除 Markdown 符号 │ │ - 去除动作描述 │ │ - 去除波浪线 │ │ │ ├─ 3. 调用 TTS (CosyVoice-v2) │ │ model: "cosyvoice-v2" │ │ voice: "longxiaochun_v2" │ │ format: "mp3" │ │ │ ├─ 4. 合成音频 │ │ audio_bytes = synthesize(text) │ │ │ ├─ 5. 发送音频数据 ─────────────────────►├─ 收到音频流 │ websocket.send_bytes(audio_bytes) │ audioData.push(data) │ │ ├─ 6. 继续处理下一个片段 │ │ text = "今天天气不错~" │ │ │ ├─ 7. 合成并发送 ───────────────────────►├─ 收到音频流 │ │ audioData.push(data) │ │ ├─ 8. 所有片段完成 │ │ │ ├─ 9. 发送结束信号 ─────────────────────►├─ 收到 reply_end │ {"type":"reply_end"} │ │ │ │ ├─ 合并音频数据 │ │ mergeAudioData() │ │ │ ├─ 播放音频 │ │ audioContext.play() │ │ │ ├─ 用户听到 AI 的声音 │ │ "你好呀,今天天气不错~" │ │ ``` ## ⏱️ 时间线分析 ### 优化前(会超时) ``` 时间轴: 0s ─ 用户按住按钮 1s ─ 用户说话 2s ─ 用户松开按钮 2s ─ 一次性发送 260KB ❌ 3s ─ 服务器收到大块数据 3s ─ ASR 无法处理 ❌ ... 62s ─ 空闲超时 ❌ 62s ─ 连接关闭 ``` ### 优化后(正常工作) ``` 时间轴: 0s ─ 用户按住按钮 5s ─ 用户松开按钮(说了 5 秒) 5s ─ 开始分片发送 5.1s ─ 发送片段 1 (3200 bytes) 5.2s ─ 发送片段 2 (3200 bytes) ... 10s ─ 发送片段 50 (3200 bytes) 10s ─ 发送 "end" 标记 10s ─ ASR 完成识别 ✅ 11s ─ LLM 开始生成 13s ─ LLM 完成,TTS 开始 15s ─ TTS 完成,发送音频 16s ─ 客户端播放音频 ✅ 20s ─ 播放完成 ✅ 总耗时: 20 秒 超时时间: 120 秒 状态: 正常 ✅ ``` ## 🔑 关键优化点 ### 1. 分片大小 ``` 优化前: 8192 bytes (8KB) 优化后: 3200 bytes (3.2KB) ✅ 原因: 匹配官方推荐,符合 100ms 音频时长 ``` ### 2. 发送间隔 ``` 优化前: 50ms 优化后: 100ms ✅ 原因: 匹配官方推荐,模拟实时音频流 ``` ### 3. 超时时间 ``` 优化前: 60 秒 优化后: 120 秒 ✅ 原因: 给 ASR + LLM + TTS 留出足够处理时间 ``` ### 4. 结束标记 ``` 优化前: 无 优化后: 发送 "end" 标记 ✅ 原因: 告诉服务器音频发送完毕,触发最终识别 ``` ## 📈 性能指标 ### 延迟分析 ``` ┌─────────────────┬──────────┬─────────┐ │ 环节 │ 耗时 │ 占比 │ ├─────────────────┼──────────┼─────────┤ │ 用户说话 │ 5s │ 25% │ │ 分片发送 │ 5s │ 25% │ │ ASR 识别 │ 1s │ 5% │ │ LLM 生成 │ 3s │ 15% │ │ TTS 合成 │ 2s │ 10% │ │ 音频传输 │ 1s │ 5% │ │ 音频播放 │ 3s │ 15% │ ├─────────────────┼──────────┼─────────┤ │ 总计 │ 20s │ 100% │ └─────────────────┴──────────┴─────────┘ ``` ### 网络流量 ``` 上行(客户端 → 服务器): - 音频数据: 160KB (5秒录音) - 控制消息: < 1KB - 总计: ~160KB 下行(服务器 → 客户端): - 音频数据: ~50KB (MP3 格式) - 控制消息: < 1KB - 总计: ~50KB 总流量: ~210KB / 次对话 ``` ## 🎯 成功标准 一次成功的语音通话应该满足: 1. ✅ 录音时长 ≥ 3 秒 2. ✅ 分片发送完成(50 片左右) 3. ✅ ASR 识别成功(收到识别文本) 4. ✅ LLM 生成成功(收到回复文本) 5. ✅ TTS 合成成功(收到音频数据) 6. ✅ 音频播放成功(听到声音) 7. ✅ 总耗时 < 30 秒 8. ✅ 无 "idle timeout" 错误 ## 🚀 下一步优化方向 ### 短期优化 1. 实现真正的实时流式录音(使用 `onFrameRecorded`) 2. 优化 LLM 响应速度(使用更快的模型) 3. 实现打断功能(用户可以打断 AI) ### 长期优化 1. 多轮对话优化(更好的上下文管理) 2. 情感识别(根据语气调整回复) 3. 个性化语音(用户自定义音色) 4. 降噪处理(提高识别准确率)