Ai_GirlFriend/xuniYou/NO_VALID_AUDIO_ERROR问题修复.md

251 lines
6.0 KiB
Markdown
Raw Normal View History

2026-02-28 18:41:16 +08:00
# NO_VALID_AUDIO_ERROR 问题修复
## 🎯 问题描述
服务器日志显示:
```
2026-02-28 18:24:53.660 - voice_call - INFO - ASR connection opened
2026-02-28 18:25:16.706 - voice_call - ERROR - ASR error: NO_VALID_AUDIO_ERROR
```
阿里云 ASR 报错:`NO_VALID_AUDIO_ERROR` - 音频数据无效
## 🔍 根本原因
### 问题代码
```javascript
fs.readFile({
filePath: res.tempFilePath,
encoding: 'binary', // ❌ 错误!这会返回字符串,不是 ArrayBuffer
success: (fileRes) => {
this.sendAudioInChunks(fileRes.data) // fileRes.data 是字符串!
}
})
```
### 为什么会出错?
1. **`encoding: 'binary'` 返回的是字符串**
- uni-app 的 `readFile` 指定 encoding 后返回字符串
- 不是 ArrayBuffer
2. **字符串的 `slice()` 方法返回的还是字符串**
- `audioData.slice(offset, end)` 返回字符串片段
- 不是二进制数据
3. **WebSocket 发送字符串时会被当作文本消息**
- 服务器收到的不是二进制音频数据
- 而是文本字符串
- ASR 无法识别,报错 `NO_VALID_AUDIO_ERROR`
## ✅ 修复方案
### 修复后的代码
```javascript
fs.readFile({
filePath: res.tempFilePath,
// ✅ 不指定 encoding返回 ArrayBuffer
success: (fileRes) => {
// 验证数据类型
if (!(fileRes.data instanceof ArrayBuffer)) {
console.error('❌ 数据不是 ArrayBuffer')
return
}
this.sendAudioInChunks(fileRes.data) // fileRes.data 是 ArrayBuffer ✅
}
})
```
### sendAudioInChunks 也增加了验证
```javascript
async sendAudioInChunks(audioData) {
// 确保 audioData 是 ArrayBuffer
if (!(audioData instanceof ArrayBuffer)) {
console.error('❌ audioData 不是 ArrayBuffer')
return
}
const totalSize = audioData.byteLength // 使用 byteLength
// ArrayBuffer.slice() 返回新的 ArrayBuffer
const chunk = audioData.slice(offset, end) // ✅ 正确的二进制切片
// WebSocket 发送 ArrayBuffer
this.socketTask.send({
data: chunk // ✅ 发送二进制数据
})
}
```
## 📊 数据类型对比
### 错误的方式encoding: 'binary'
```javascript
typeof fileRes.data // "string"
fileRes.data instanceof ArrayBuffer // false
fileRes.data.length // 字符串长度(可能不等于字节数)
fileRes.data.slice(0, 10) // 返回字符串片段
```
### 正确的方式(不指定 encoding
```javascript
typeof fileRes.data // "object"
fileRes.data instanceof ArrayBuffer // true
fileRes.data.byteLength // 字节数
fileRes.data.slice(0, 10) // 返回 ArrayBuffer 片段
```
## 🔧 如何测试
### 1. 重新编译客户端
在 HBuilderX 中重新运行项目到手机/模拟器
### 2. 测试步骤
1. 打开 App进入语音通话页面
2. 按住"按住说话"按钮
3. 说话 3-5 秒
4. 松开按钮
5. 观察日志
### 3. 预期日志
#### 客户端日志
```
✅ 文件读取成功
📊 数据类型: object
📊 是否为 ArrayBuffer: true
📊 数据大小: 160000 bytes
📦 开始分片发送(官方推荐参数)
📊 总大小: 160000 bytes
📊 预计录音时长: 5.00 秒
📤 发送第 1 片,大小: 3200 bytes
✅ 第 1 片发送成功
...
```
#### 服务器日志
```
✅ 应该看到:
ASR connection opened
ASR event end=False sentence=...
ASR event end=True sentence=[识别的文字]
Handle sentence: [识别的文字]
❌ 不应该再看到:
ASR error: NO_VALID_AUDIO_ERROR
```
## 📚 技术要点
### uni-app readFile 的 encoding 参数
| encoding 值 | 返回类型 | 用途 |
|------------|---------|------|
| 不指定 | ArrayBuffer | 二进制文件(音频、图片、视频) |
| 'utf8' | String | 文本文件 |
| 'base64' | String | Base64 编码 |
| 'binary' | String | ❌ 不要用于音频!返回字符串 |
### WebSocket send() 方法
```javascript
// 发送文本
websocket.send({ data: "hello" }) // 文本消息
// 发送二进制
websocket.send({ data: arrayBuffer }) // 二进制消息
```
服务器端会根据数据类型自动判断:
- 字符串 → `msg["text"]`
- ArrayBuffer → `msg["bytes"]`
## 🎓 经验总结
### 关键教训
1. **不要对二进制文件使用 encoding 参数**
- 音频、图片、视频等二进制文件
- 不指定 encoding让它返回 ArrayBuffer
2. **验证数据类型**
- 使用 `instanceof ArrayBuffer` 验证
- 使用 `byteLength` 而不是 `length`
3. **理解 WebSocket 的数据类型**
- 字符串和二进制数据的处理方式不同
- 服务器端会根据类型分别处理
### 最佳实践
```javascript
// ✅ 读取二进制文件的正确方式
fs.readFile({
filePath: path,
// 不指定 encoding
success: (res) => {
if (res.data instanceof ArrayBuffer) {
// 处理二进制数据
}
}
})
// ✅ 读取文本文件的正确方式
fs.readFile({
filePath: path,
encoding: 'utf8',
success: (res) => {
if (typeof res.data === 'string') {
// 处理文本数据
}
}
})
```
## 🎉 预期结果
修复后,应该能够:
1. ✅ 正确读取 PCM 音频文件为 ArrayBuffer
2. ✅ 正确切片 ArrayBuffer
3. ✅ 正确发送二进制数据到服务器
4. ✅ 服务器 ASR 正确识别音频
5. ✅ 不再出现 `NO_VALID_AUDIO_ERROR` 错误
6. ✅ 完整的对话流程ASR → LLM → TTS
## 📞 如果还有问题
如果修复后还是出现 `NO_VALID_AUDIO_ERROR`,可能的原因:
1. **音频格式不对**
- 确认录音格式为 PCM
- 确认采样率为 16000Hz
- 确认单声道
2. **音频太短**
- 至少录音 3 秒
- 查看日志中的 "预计录音时长"
3. **音频质量差**
- 在安静环境测试
- 清晰发音
- 避免背景噪音
---
**修复时间**: 2026-02-28
**问题**: NO_VALID_AUDIO_ERROR
**原因**: 使用 `encoding: 'binary'` 导致发送字符串而不是二进制数据
**解决**: 不指定 encoding让 readFile 返回 ArrayBuffer
**状态**: ✅ 已修复,待测试