5.0 KiB
5.0 KiB
"idle timeout" 问题分析
问题来源
错误来自 FastAPI 服务器(192.168.1.141:30101)的 lover/routers/voice_call.py 文件。
代码分析
1. idle timeout 的触发位置
在 VoiceCallSession 类中有两个超时检测:
1.1 空闲超时(idle timeout)
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)
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 在以下情况更新:
def _touch(self):
self.last_activity = time.time()
调用 _touch() 的地方:
feed_audio()- 接收音频数据时send_signal()- 发送信号时_synthesize_stream()- 发送 TTS 音频时
3. 问题分析
从你的日志看:
✅ 录音文件发送成功
❌ WebSocket 关闭, code: 1000
{"type":"error","msg":"idle timeout"}
可能的原因:
原因1:服务器配置的超时时间太短
检查 lover/config.py 或 .env 文件中的配置:
VOICE_CALL_IDLE_TIMEOUT = ? # 这个值可能太小
原因2:服务器处理音频时间过长
服务器接收音频后需要:
- ASR 识别(~1-2秒)
- LLM 生成回复(~2-5秒)
- TTS 合成语音(~1-2秒)
如果 VOICE_CALL_IDLE_TIMEOUT 设置为 5 秒,而处理需要 6 秒,就会超时。
原因3:客户端发送的是完整文件,不是流式数据
当前客户端实现:
- 录音完成后一次性发送整个 MP3 文件
- 服务器期望的是 PCM 流式数据
服务器代码中:
self.recognition = Recognition(
model="paraformer-realtime-v2",
format="pcm", # 期望 PCM 格式
sample_rate=16000,
...
)
但客户端发送的是:
format: 'mp3' // 发送的是 MP3
这是主要问题!
解决方案
方案1:修改服务器超时配置(临时方案)
在 lover/.env 或 lover/config.py 中增加超时时间:
VOICE_CALL_IDLE_TIMEOUT = 30 # 从默认值增加到 30 秒
方案2:客户端改用 PCM 格式(推荐)
修改客户端录音配置:
const recorderOptions = {
duration: 600000,
sampleRate: 16000,
numberOfChannels: 1,
format: 'pcm', // 改为 PCM
audioSource: 'auto'
}
但是:PCM 文件很大,一次性发送会很慢。
方案3:修改服务器支持 MP3 格式(最佳方案)
修改 lover/routers/voice_call.py:
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:使用流式录音(理想方案)
客户端使用实时音频帧:
format: 'pcm',
frameSize: 5, // 启用 onFrameRecorded
但这需要 App 端支持 onFrameRecorded。
当前最快的解决方案
步骤1:增加服务器超时时间
编辑 lover/.env 文件:
# 在文件中添加或修改
VOICE_CALL_IDLE_TIMEOUT=30
步骤2:重启 FastAPI 服务器
# 在服务器上
cd /path/to/lover
# 停止旧进程
pkill -f "uvicorn.*main:app"
# 启动新进程
uvicorn main:app --host 0.0.0.0 --port 30101 --reload
步骤3:测试
重新测试语音通话,看是否还有 "idle timeout" 错误。
长期优化建议
-
服务器端支持 MP3 输入
- 添加音频格式检测
- 自动转换 MP3 到 PCM
-
优化处理流程
- 使用更快的 ASR 模型
- 优化 LLM 调用
- 使用流式 TTS
-
客户端使用流式录音
- 实现 PCM 实时传输
- 降低延迟
配置文件位置
需要检查的文件:
lover/.env- 环境变量配置lover/config.py- 配置类定义
查找 VOICE_CALL_IDLE_TIMEOUT 的当前值。