Ai_GirlFriend/xuniYou/idle_timeout问题分析.md

223 lines
5.0 KiB
Markdown
Raw Normal View History

2026-02-28 18:04:34 +08:00
# "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` 的当前值。