# "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` 的当前值。