Ai_GirlFriend/xuniYou/idle_timeout问题分析.md
2026-02-28 18:04:34 +08:00

5.0 KiB
Raw Permalink Blame History

"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() 的地方:

  1. feed_audio() - 接收音频数据时
  2. send_signal() - 发送信号时
  3. _synthesize_stream() - 发送 TTS 音频时

3. 问题分析

从你的日志看:

✅ 录音文件发送成功
❌ WebSocket 关闭, code: 1000
{"type":"error","msg":"idle timeout"}

可能的原因

原因1服务器配置的超时时间太短

检查 lover/config.py.env 文件中的配置:

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 流式数据

服务器代码中:

self.recognition = Recognition(
    model="paraformer-realtime-v2",
    format="pcm",  # 期望 PCM 格式
    sample_rate=16000,
    ...
)

但客户端发送的是:

format: 'mp3'  // 发送的是 MP3

这是主要问题!

解决方案

方案1修改服务器超时配置临时方案

lover/.envlover/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" 错误。

长期优化建议

  1. 服务器端支持 MP3 输入

    • 添加音频格式检测
    • 自动转换 MP3 到 PCM
  2. 优化处理流程

    • 使用更快的 ASR 模型
    • 优化 LLM 调用
    • 使用流式 TTS
  3. 客户端使用流式录音

    • 实现 PCM 实时传输
    • 降低延迟

配置文件位置

需要检查的文件:

  • lover/.env - 环境变量配置
  • lover/config.py - 配置类定义

查找 VOICE_CALL_IDLE_TIMEOUT 的当前值。