Ai_GirlFriend/xuniYou/最终修复说明.md
2026-03-02 18:57:11 +08:00

6.1 KiB
Raw Permalink Blame History

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()

onLoad() {
    // 初始化 recorderManager
    recorderManager = uni.getRecorderManager()
    
    // 设置录音监听器(只设置一次)
    this.setupRecorderListeners()
    
    this.getCallDuration()
    this.initAudio()
}

2. 新增 setupRecorderListeners() 方法

setupRecorderListeners() {
    // 监听录音开始
    recorderManager.onStart(() => {
        console.log('✅ 录音已开始')
    })
    
    // 监听录音错误
    recorderManager.onError((err) => {
        console.error('❌ 录音错误:', err)
        this.isRecording = false
    })
    
    // 监听录音停止 - 读取文件并发送
    recorderManager.onStop((res) => {
        console.log('⏹️ 录音已停止')
        // ... 读取文件并发送的逻辑
    })
    
    // 监听音频帧 - 实时发送(如果支持)
    recorderManager.onFrameRecorded((res) => {
        // ... 实时发送音频帧的逻辑
    })
}

3. 简化 startRecording() 方法

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. 监听器设置的最佳实践

错误做法:

startRecording() {
    // 每次都设置监听器
    recorderManager.onStop(() => { ... })
    recorderManager.start()
}

正确做法:

onLoad() {
    // 初始化时设置一次
    this.setupRecorderListeners()
}

startRecording() {
    // 只启动录音
    recorderManager.start()
}

2. this 上下文问题

在回调函数中使用箭头函数确保 this 指向组件实例:

recorderManager.onStop((res) => {
    // 箭头函数this 指向组件
    this.socketTask.send(...)
})

3. 双重保障机制

  • 主方案: onFrameRecorded 实时发送音频帧(低延迟)
  • 备用方案: onStop 发送完整文件(兼容性好)

大多数设备会使用备用方案,因为 onFrameRecorded 支持有限。

修复完成

所有代码修改已完成,请重新编译测试!


修复时间: 2026-03-02
问题: NO_VALID_AUDIO_ERROR
根本原因: onStop 回调未触发,监听器设置位置不当
解决方案: 将监听器设置移到初始化阶段,只设置一次
状态: 已修复,待测试