Ai_GirlFriend/xuniYou/最终修复说明.md

260 lines
6.1 KiB
Markdown
Raw Normal View History

2026-03-02 18:57:11 +08:00
# NO_VALID_AUDIO_ERROR 最终修复说明
## 🎯 问题根源
经过详细分析日志,发现问题的根本原因是:
1.`ptt_on` 信号已发送并被服务器接收(日志显示 `ptt_enabled`
2. ✅ 录音已启动
3.**但是 `onStop` 回调没有被触发**
4. ❌ 导致录音停止后,没有读取和发送音频文件
5. ❌ 服务器等待音频数据超时,报错 `NO_VALID_AUDIO_ERROR`
## 🔧 最终修复方案
### 修复内容
将录音监听器的设置从 `startRecording` 方法中移出,改为在初始化时设置一次。
**原因:**
- 每次调用 `startRecording` 都会重新设置监听器
- 这可能导致监听器被覆盖或 `this` 上下文丢失
- 导致 `onStop` 回调无法正常触发
### 代码变更
#### 1. 在 `onLoad` 中调用 `setupRecorderListeners()`
```javascript
onLoad() {
// 初始化 recorderManager
recorderManager = uni.getRecorderManager()
// 设置录音监听器(只设置一次)
this.setupRecorderListeners()
this.getCallDuration()
this.initAudio()
}
```
#### 2. 新增 `setupRecorderListeners()` 方法
```javascript
setupRecorderListeners() {
// 监听录音开始
recorderManager.onStart(() => {
console.log('✅ 录音已开始')
})
// 监听录音错误
recorderManager.onError((err) => {
console.error('❌ 录音错误:', err)
this.isRecording = false
})
// 监听录音停止 - 读取文件并发送
recorderManager.onStop((res) => {
console.log('⏹️ 录音已停止')
// ... 读取文件并发送的逻辑
})
// 监听音频帧 - 实时发送(如果支持)
recorderManager.onFrameRecorded((res) => {
// ... 实时发送音频帧的逻辑
})
}
```
#### 3. 简化 `startRecording()` 方法
```javascript
async startRecording() {
if (this.isRecording) return
this.isRecording = true
// 直接启动录音,不再设置监听器
const recorderOptions = {
duration: 600000,
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 48000,
format: 'pcm',
frameSize: 5, // 启用实时音频帧
audioSource: 'auto'
}
recorderManager.start(recorderOptions)
}
```
## 📊 预期效果
修复后的完整流程:
```
1. 用户按住"按住说话"
2. 发送 ptt_on 信号 ✅
3. 服务器响应 ptt_enabled ✅
4. 启动录音 ✅
5. 实时发送音频帧(如果支持)
用户松开按钮
6. 停止录音,触发 onStop 回调 ← 修复重点
7. 读取录音文件ArrayBuffer
8. 分片发送音频数据
9. 发送 ptt_off 信号
10. 服务器 ASR 识别 → LLM 生成 → TTS 合成
```
## 🧪 测试步骤
### 1. 重新编译
在 HBuilderX 中:
1. 停止当前运行
2. 清理缓存(菜单 -> 运行 -> 清理缓存)
3. 重新运行到手机/模拟器
### 2. 测试并观察日志
按住说话 3-5 秒,观察日志:
**预期日志(成功):**
```
✅ 开始说话, isTalking 设置为: true
📤 发送 ptt_on 信号
✅ ptt_on 信号发送成功
📋 收到服务器消息: {"type":"info","msg":"ptt_enabled"} ← 服务器确认
录音未启动,开始启动录音
=== startRecording 被调用 ===
🎙️ 启动 recorderManager
✅ 录音已开始
[用户松开按钮]
=== stopTalking 被调用 ===
🛑 停止录音并准备发送...
⏹️ 录音已停止 ← 关键!这个日志必须出现
📁 文件路径: /xxx/recorder/xxx.pcm
✅ 文件读取成功
📊 是否为 ArrayBuffer: true
📊 文件大小: 160000 bytes
📦 开始分片发送
📤 发送第 1 片,大小: 3200 bytes
✅ 第 1 片发送成功
...
✅ 所有音频片段发送完成
✅ ptt_off 信号发送成功
```
### 3. 关键检查点
- ✅ 必须看到 "⏹️ 录音已停止" 日志
- ✅ 必须看到 "✅ 文件读取成功" 日志
- ✅ 必须看到 "📤 发送第 X 片" 日志
- ✅ 服务器不再报 `NO_VALID_AUDIO_ERROR`
## 🐛 如果还有问题
### 问题 1还是没有 "⏹️ 录音已停止" 日志
**可能原因:**
- 录音时间太短(< 500ms
- 录音权限未授予
- 设备不支持 PCM 格式
**解决方案:**
1. 确保录音至少 2-3 秒
2. 检查 App 权限设置,确保麦克风权限已授予
3. 尝试修改录音格式为 `aac`(但需要服务器支持)
### 问题 2有 "⏹️ 录音已停止" 但没有文件路径
**可能原因:**
- 录音失败,没有生成文件
- 存储权限问题
**解决方案:**
1. 检查日志中的录音错误信息
2. 确保 App 有存储权限
3. 检查设备存储空间是否充足
### 问题 3文件读取失败
**可能原因:**
- 文件路径无效
- 文件系统权限问题
**解决方案:**
1. 检查 `res.tempFilePath` 的值
2. 尝试使用绝对路径
3. 检查文件是否真实存在
## 📝 技术要点总结
### 1. 监听器设置的最佳实践
**错误做法:**
```javascript
startRecording() {
// 每次都设置监听器
recorderManager.onStop(() => { ... })
recorderManager.start()
}
```
**正确做法:**
```javascript
onLoad() {
// 初始化时设置一次
this.setupRecorderListeners()
}
startRecording() {
// 只启动录音
recorderManager.start()
}
```
### 2. this 上下文问题
在回调函数中使用箭头函数确保 `this` 指向组件实例:
```javascript
recorderManager.onStop((res) => {
// 箭头函数this 指向组件
this.socketTask.send(...)
})
```
### 3. 双重保障机制
- **主方案:** `onFrameRecorded` 实时发送音频帧(低延迟)
- **备用方案:** `onStop` 发送完整文件(兼容性好)
大多数设备会使用备用方案,因为 `onFrameRecorded` 支持有限。
## ✅ 修复完成
所有代码修改已完成,请重新编译测试!
---
**修复时间:** 2026-03-02
**问题:** NO_VALID_AUDIO_ERROR
**根本原因:** onStop 回调未触发,监听器设置位置不当
**解决方案:** 将监听器设置移到初始化阶段,只设置一次
**状态:** ✅ 已修复,待测试