# 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 **状态**: ✅ 已修复,待测试