174 lines
3.9 KiB
Markdown
174 lines
3.9 KiB
Markdown
# 问题定位总结
|
||
|
||
## 🎯 核心问题
|
||
|
||
**卡在 ASR(语音识别)环节,导致后续 LLM 和 TTS 都没有被触发。**
|
||
|
||
## 🔍 详细分析
|
||
|
||
### 问题流程
|
||
|
||
```
|
||
客户端 → 一次性发送 260KB PCM 文件
|
||
↓
|
||
服务器 → 接收到大块数据
|
||
↓
|
||
ASR → 无法处理(期望流式小块数据)❌
|
||
↓
|
||
没有识别结果
|
||
↓
|
||
LLM 不被触发 ❌
|
||
↓
|
||
TTS 不被触发 ❌
|
||
↓
|
||
60秒后 idle timeout
|
||
```
|
||
|
||
### 根本原因
|
||
|
||
**架构不匹配**:
|
||
|
||
1. **服务器设计**:
|
||
- 使用 `paraformer-realtime-v2`(实时 ASR)
|
||
- 期望流式接收音频数据
|
||
- 设计用于边说边识别
|
||
|
||
2. **客户端实现**:
|
||
- 录音完成后一次性发送整个文件
|
||
- 不是流式传输
|
||
- 类似"批处理"模式
|
||
|
||
这就像:
|
||
- 服务器是一个"实时翻译员",期望你一句一句说
|
||
- 但客户端把整篇文章一次性扔给他
|
||
- 翻译员不知道怎么处理
|
||
|
||
## 📊 各环节状态
|
||
|
||
| 环节 | 状态 | 说明 |
|
||
|------|------|------|
|
||
| 客户端录音 | ✅ 正常 | PCM 格式,16kHz |
|
||
| 文件读取 | ✅ 正常 | 260KB |
|
||
| WebSocket 发送 | ✅ 正常 | 发送成功 |
|
||
| **ASR 识别** | ❌ **卡住** | 无法处理大块数据 |
|
||
| LLM 生成 | ⏸️ 未触发 | 因为 ASR 没有结果 |
|
||
| TTS 合成 | ⏸️ 未触发 | 因为 LLM 没有结果 |
|
||
| 返回音频 | ⏸️ 未触发 | 因为 TTS 没有结果 |
|
||
|
||
## 🔧 解决方案
|
||
|
||
### 已实现:分片发送
|
||
|
||
修改客户端,将大文件分成小块发送:
|
||
|
||
```javascript
|
||
// 每次发送 8KB
|
||
const chunkSize = 8192
|
||
|
||
// 模拟流式传输
|
||
for (let offset = 0; offset < totalSize; offset += chunkSize) {
|
||
const chunk = audioData.slice(offset, offset + chunkSize)
|
||
socketTask.send({ data: chunk })
|
||
await sleep(50) // 延迟 50ms
|
||
}
|
||
|
||
// 发送结束标记
|
||
socketTask.send({ data: 'end' })
|
||
```
|
||
|
||
### 工作原理
|
||
|
||
```
|
||
客户端 → 发送 8KB 片段 1
|
||
↓
|
||
服务器 → ASR 开始识别
|
||
↓
|
||
客户端 → 发送 8KB 片段 2
|
||
↓
|
||
服务器 → ASR 继续识别
|
||
↓
|
||
... (重复)
|
||
↓
|
||
客户端 → 发送 "end" 标记
|
||
↓
|
||
服务器 → ASR 完成识别
|
||
↓
|
||
服务器 → 触发 LLM 生成
|
||
↓
|
||
服务器 → 触发 TTS 合成
|
||
↓
|
||
服务器 → 返回音频
|
||
```
|
||
|
||
## 📈 预期效果
|
||
|
||
### 修改前
|
||
- ❌ ASR 无法识别
|
||
- ❌ 60 秒后超时
|
||
- ❌ 没有任何响应
|
||
|
||
### 修改后
|
||
- ✅ ASR 正常识别
|
||
- ✅ LLM 生成回复
|
||
- ✅ TTS 合成语音
|
||
- ✅ 返回音频播放
|
||
|
||
## 🧪 测试步骤
|
||
|
||
1. 重新编译项目
|
||
2. 进入语音通话页面
|
||
3. 按住"按住说话"按钮
|
||
4. 说话 2-3 秒
|
||
5. 松开按钮
|
||
6. 观察日志:
|
||
- 应该看到 "开始分片发送"
|
||
- 应该看到 "第 X 片发送成功"
|
||
- 应该看到 "发送结束标记"
|
||
- 应该收到服务器的识别结果
|
||
- 应该收到 LLM 的回复
|
||
- 应该收到 TTS 的音频
|
||
|
||
## 🎓 经验总结
|
||
|
||
### 教训
|
||
|
||
1. **架构匹配很重要**
|
||
- 实时 ASR 需要流式输入
|
||
- 不能用批处理方式
|
||
|
||
2. **日志很重要**
|
||
- 通过日志快速定位问题
|
||
- 每个环节都要有日志
|
||
|
||
3. **理解服务端设计**
|
||
- 要了解服务端期望什么
|
||
- 不能想当然
|
||
|
||
### 最佳实践
|
||
|
||
1. **使用流式传输**
|
||
- 对于实时 ASR,必须流式发送
|
||
- 分片大小:4-8KB
|
||
- 发送间隔:50-100ms
|
||
|
||
2. **添加结束标记**
|
||
- 告诉服务器数据发送完毕
|
||
- 触发最终处理
|
||
|
||
3. **完善错误处理**
|
||
- 每个环节都要有错误处理
|
||
- 超时要有提示
|
||
|
||
## 🔗 相关文档
|
||
|
||
- [Paraformer 实时 ASR 文档](https://help.aliyun.com/zh/dashscope/developer-reference/paraformer-realtime-v2)
|
||
- [WebSocket 流式传输最佳实践](https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket)
|
||
|
||
## ✅ 结论
|
||
|
||
问题已定位并解决:
|
||
- **问题**:ASR 无法处理大块数据
|
||
- **原因**:客户端一次性发送,服务器期望流式接收
|
||
- **解决**:客户端改为分片发送,模拟流式传输
|
||
- **状态**:已实现,待测试
|