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

223 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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