diff --git a/lover/.env b/lover/.env index e5db62e..781ad8d 100644 --- a/lover/.env +++ b/lover/.env @@ -1,2 +1,5 @@ DATABASE_URL=mysql+pymysql://root:rootx77@localhost:3306/fastadmin?charset=utf8mb4 -USER_INFO_API=http://127.0.0.1:30100/api/user_basic/get_user_basic \ No newline at end of file +USER_INFO_API=http://127.0.0.1:30100/api/user_basic/get_user_basic + +# 语音通话超时设置(秒)- 增加到120秒以适应ASR+LLM+TTS处理时间 +VOICE_CALL_IDLE_TIMEOUT=120 \ No newline at end of file diff --git a/xuniYou/App端实时录音配置说明.md b/xuniYou/App端实时录音配置说明.md new file mode 100644 index 0000000..d52345a --- /dev/null +++ b/xuniYou/App端实时录音配置说明.md @@ -0,0 +1,100 @@ +# App 端实时录音配置说明 + +## 让 recorderManager.onFrameRecorded 在 App 端工作的关键配置 + +### 必须满足的条件: + +1. **format 必须是 'pcm'** + ```javascript + format: 'pcm' // 不能是 mp3 或 aac + ``` + +2. **必须设置 frameSize** + ```javascript + frameSize: 5 // 单位 KB,建议 1-10 + ``` + +3. **sampleRate 必须是标准值** + ```javascript + sampleRate: 16000 // 只能是 8000/16000/44100 + ``` + +4. **完整配置示例** + ```javascript + recorderManager.start({ + duration: 600000, // 最长录音时间 + sampleRate: 16000, // 采样率 + numberOfChannels: 1, // 单声道 + encodeBitRate: 48000, // 编码比特率 + format: 'pcm', // 关键:必须 pcm + frameSize: 5, // 关键:必须设置 + audioSource: 'auto' // 音频源 + }) + ``` + +## 工作原理 + +### 实时流式传输模式(推荐) +- 按住按钮 → 开始录音 +- 录音持续进行,实时产生音频帧 +- 按住时:发送音频帧到服务器 +- 松开时:停止发送(但录音继续) +- 再次按住:继续发送 + +### 优点 +- 低延迟,实时传输 +- 服务器可以实时处理 +- 用户体验好 + +### 缺点 +- 需要 PCM 格式(文件较大) +- 需要服务器支持流式处理 + +## 备用方案 + +如果 `onFrameRecorded` 仍然不工作,会自动降级到 `onStop` 方案: + +```javascript +recorderManager.onStop((res) => { + // 读取完整录音文件 + // 一次性发送到服务器 +}) +``` + +## 调试技巧 + +1. **查看日志** + - `✅ 录音已开始` - 录音启动成功 + - `🎤 收到音频帧 #1` - 实时帧工作 + - `⏹️ 录音已停止` - 降级到备用方案 + +2. **如果没有音频帧** + - 检查 format 是否为 'pcm' + - 检查 frameSize 是否设置 + - 检查 manifest.json 中 Record 模块是否启用 + +3. **性能优化** + - frameSize 越小,延迟越低,但回调频率越高 + - 建议值:3-5 KB + +## 服务器端要求 + +服务器需要支持: +1. WebSocket 连接 +2. 接收 PCM 格式音频流 +3. 实时语音识别(如果需要) +4. 返回音频响应 + +## 当前状态 + +✅ 录音功能已实现 +✅ 文件发送已成功 +⚠️ 需要测试实时帧是否工作 +⚠️ 需要检查服务器端处理 + +## 下一步 + +1. 重新编译并测试 +2. 查看是否有 `🎤 收到音频帧` 日志 +3. 如果有,说明实时流式传输工作 +4. 如果没有,会自动使用备用方案(文件发送) diff --git a/xuniYou/idle_timeout问题分析.md b/xuniYou/idle_timeout问题分析.md new file mode 100644 index 0000000..ef4f48e --- /dev/null +++ b/xuniYou/idle_timeout问题分析.md @@ -0,0 +1,222 @@ +# "idle timeout" 问题分析 + +## 问题来源 + +错误来自 **FastAPI 服务器**(`192.168.1.141:30101`)的 `lover/routers/voice_call.py` 文件。 + +## 代码分析 + +### 1. idle timeout 的触发位置 + +在 `VoiceCallSession` 类中有两个超时检测: + +#### 1.1 空闲超时(idle timeout) + +```python +async def _idle_watchdog(self): + timeout = settings.VOICE_CALL_IDLE_TIMEOUT or 0 + if timeout <= 0: + return + try: + while True: + await asyncio.sleep(5) + if time.time() - self.last_activity > timeout: + await self.send_signal({"type": "error", "msg": "idle timeout"}) + await self.close() + break + except asyncio.CancelledError: + return +``` + +**触发条件**: +- 每 5 秒检查一次 +- 如果 `time.time() - self.last_activity > timeout` +- 就发送 "idle timeout" 错误并关闭连接 + +#### 1.2 静默超时(silence timeout) + +```python +async def _silence_watchdog(self): + """长时间静默时关闭会话,ASR 常驻不再因短静音 stop。""" + try: + while True: + await asyncio.sleep(1.0) + if time.time() - self.last_voice_activity > 60: + logger.info("Long silence, closing session") + await self.send_signal({"type": "error", "msg": "idle timeout"}) + await self.close() + break + except asyncio.CancelledError: + return +``` + +**触发条件**: +- 每 1 秒检查一次 +- 如果 60 秒内没有语音活动 +- 就发送 "idle timeout" 错误并关闭连接 + +### 2. last_activity 的更新 + +`last_activity` 在以下情况更新: + +```python +def _touch(self): + self.last_activity = time.time() +``` + +调用 `_touch()` 的地方: +1. `feed_audio()` - 接收音频数据时 +2. `send_signal()` - 发送信号时 +3. `_synthesize_stream()` - 发送 TTS 音频时 + +### 3. 问题分析 + +从你的日志看: +``` +✅ 录音文件发送成功 +❌ WebSocket 关闭, code: 1000 +{"type":"error","msg":"idle timeout"} +``` + +**可能的原因**: + +#### 原因1:服务器配置的超时时间太短 + +检查 `lover/config.py` 或 `.env` 文件中的配置: +```python +VOICE_CALL_IDLE_TIMEOUT = ? # 这个值可能太小 +``` + +#### 原因2:服务器处理音频时间过长 + +服务器接收音频后需要: +1. ASR 识别(~1-2秒) +2. LLM 生成回复(~2-5秒) +3. TTS 合成语音(~1-2秒) + +如果 `VOICE_CALL_IDLE_TIMEOUT` 设置为 5 秒,而处理需要 6 秒,就会超时。 + +#### 原因3:客户端发送的是完整文件,不是流式数据 + +当前客户端实现: +- 录音完成后一次性发送整个 MP3 文件 +- 服务器期望的是 PCM 流式数据 + +服务器代码中: +```python +self.recognition = Recognition( + model="paraformer-realtime-v2", + format="pcm", # 期望 PCM 格式 + sample_rate=16000, + ... +) +``` + +但客户端发送的是: +```javascript +format: 'mp3' // 发送的是 MP3 +``` + +**这是主要问题!** + +## 解决方案 + +### 方案1:修改服务器超时配置(临时方案) + +在 `lover/.env` 或 `lover/config.py` 中增加超时时间: + +```python +VOICE_CALL_IDLE_TIMEOUT = 30 # 从默认值增加到 30 秒 +``` + +### 方案2:客户端改用 PCM 格式(推荐) + +修改客户端录音配置: + +```javascript +const recorderOptions = { + duration: 600000, + sampleRate: 16000, + numberOfChannels: 1, + format: 'pcm', // 改为 PCM + audioSource: 'auto' +} +``` + +**但是**:PCM 文件很大,一次性发送会很慢。 + +### 方案3:修改服务器支持 MP3 格式(最佳方案) + +修改 `lover/routers/voice_call.py`: + +```python +async def feed_audio(self, data: bytes): + # 检测音频格式 + if self._is_mp3(data): + # 转换 MP3 到 PCM + pcm_data = self._convert_mp3_to_pcm(data) + data = pcm_data + + # 原有逻辑 + if self.recognition: + self.recognition.send_audio_frame(data) +``` + +### 方案4:使用流式录音(理想方案) + +客户端使用实时音频帧: +```javascript +format: 'pcm', +frameSize: 5, // 启用 onFrameRecorded +``` + +但这需要 App 端支持 `onFrameRecorded`。 + +## 当前最快的解决方案 + +### 步骤1:增加服务器超时时间 + +编辑 `lover/.env` 文件: + +```bash +# 在文件中添加或修改 +VOICE_CALL_IDLE_TIMEOUT=30 +``` + +### 步骤2:重启 FastAPI 服务器 + +```bash +# 在服务器上 +cd /path/to/lover +# 停止旧进程 +pkill -f "uvicorn.*main:app" +# 启动新进程 +uvicorn main:app --host 0.0.0.0 --port 30101 --reload +``` + +### 步骤3:测试 + +重新测试语音通话,看是否还有 "idle timeout" 错误。 + +## 长期优化建议 + +1. **服务器端支持 MP3 输入** + - 添加音频格式检测 + - 自动转换 MP3 到 PCM + +2. **优化处理流程** + - 使用更快的 ASR 模型 + - 优化 LLM 调用 + - 使用流式 TTS + +3. **客户端使用流式录音** + - 实现 PCM 实时传输 + - 降低延迟 + +## 配置文件位置 + +需要检查的文件: +- `lover/.env` - 环境变量配置 +- `lover/config.py` - 配置类定义 + +查找 `VOICE_CALL_IDLE_TIMEOUT` 的当前值。 diff --git a/xuniYou/manifest.json b/xuniYou/manifest.json index c8659be..d55579c 100644 --- a/xuniYou/manifest.json +++ b/xuniYou/manifest.json @@ -73,43 +73,23 @@ "landscape-secondary" ] }, - /* 原生插件配置 - 已注释,因为插件文件不存在 - * 如果需要使用这些插件,请先安装插件,然后取消注释并替换下面的空对象 - * Agora-RTC 插件:https://ext.dcloud.net.cn/plugin?id=3720 - * - * 配置示例(取消注释后使用): - * "nativePlugins" : { - * "Agora-RTC" : { - * "__plugin_info__" : { - * "name" : "Agora音视频插件", - * "description" : "Agora官方维护的音视频插件,并且在GitHub上开源,欢迎大家积极参与问题反馈和代码贡献", - * "platforms" : "Android,iOS", - * "url" : "", - * "android_package_name" : "", - * "ios_bundle_id" : "", - * "isCloud" : false, - * "bought" : -1, - * "pid" : "", - * "parameters" : {} - * } - * }, - * "AudioRecode" : { - * "__plugin_info__" : { - * "name" : "AudioRecode", - * "description" : "录音插件", - * "platforms" : "Android", - * "url" : "", - * "android_package_name" : "", - * "ios_bundle_id" : "", - * "isCloud" : false, - * "bought" : -1, - * "pid" : "", - * "parameters" : {} - * } - * } - * } - */ - "nativePlugins" : {} + /* 原生插件配置 */ + "nativePlugins" : { + "AudioRecode" : { + "__plugin_info__" : { + "name" : "AudioRecode", + "description" : "录音插件", + "platforms" : "Android,iOS", + "url" : "", + "android_package_name" : "", + "ios_bundle_id" : "", + "isCloud" : false, + "bought" : -1, + "pid" : "", + "parameters" : {} + } + } + } }, /* 快应用特有相关 */ "quickapp" : {}, diff --git a/xuniYou/pages/chat/phone.vue b/xuniYou/pages/chat/phone.vue index 95b7598..a15d6b0 100644 --- a/xuniYou/pages/chat/phone.vue +++ b/xuniYou/pages/chat/phone.vue @@ -34,9 +34,10 @@ {{ isTalking ? '松开结束' : '按住说话' }} @@ -47,20 +48,6 @@