241 lines
7.2 KiB
Markdown
241 lines
7.2 KiB
Markdown
# Day 19 - 通信传输优化
|
||
|
||
**日期**: 2025-12-23
|
||
**主题**: 服务器端数据处理逻辑优化,解决传输拥堵问题
|
||
|
||
---
|
||
|
||
## 问题分析
|
||
|
||
用户反馈室外测试时传输拥堵,经分析确认:
|
||
- 2.4GHz 热点带宽不是瓶颈(需求 ~1.6 Mbps,实测 5-10 Mbps)
|
||
- 服务器配置强劲(双 E5-2680 v4 + 双 RTX 3090)
|
||
- **真正问题**:服务器端每帧都执行无意义的解码-编码循环
|
||
|
||
---
|
||
|
||
## 瓶颈识别
|
||
|
||
### 之前的代码逻辑
|
||
```python
|
||
# 每帧都解码,无论是否需要处理
|
||
arr = np.frombuffer(data, dtype=np.uint8)
|
||
bgr = cv2.imdecode(arr, cv2.IMREAD_COLOR) # CPU 密集!
|
||
|
||
# 即使不需要处理,也重新编码再发送
|
||
ok, enc = cv2.imencode(".jpg", bgr, ...) # 又是 CPU 密集!
|
||
await _broadcast_to_viewers(enc.tobytes())
|
||
```
|
||
|
||
**问题**:客户端发送的 `data` 本身就是 JPEG,完全不需要解码再编码!
|
||
|
||
---
|
||
|
||
## 优化方案
|
||
|
||
### 1. 零拷贝直传
|
||
对于不需要处理的模式(CHAT/IDLE/ITEM_SEARCH),直接转发原始 JPEG:
|
||
|
||
```diff
|
||
- if bgr is not None:
|
||
- ok, enc = cv2.imencode(".jpg", bgr, ...)
|
||
- await _broadcast_to_viewers(enc.tobytes())
|
||
+ await _broadcast_to_viewers(data) # 直接转发原始 JPEG
|
||
```
|
||
|
||
### 2. 延迟解码
|
||
只在真正需要导航处理时才执行 `cv2.imdecode`:
|
||
|
||
```diff
|
||
+ needs_processing = (orchestrator and not yolomedia_running)
|
||
+ bgr = None # 延迟初始化
|
||
|
||
+ if needs_processing:
|
||
+ current_state = orchestrator.get_state()
|
||
+ if current_state in ("ITEM_SEARCH", "CHAT", "IDLE"):
|
||
+ await _broadcast_to_viewers(data) # 零拷贝
|
||
+ continue
|
||
+ # 只有导航模式才解码
|
||
+ bgr = turbo_decode(data)
|
||
```
|
||
|
||
### 3. TurboJPEG 加速编解码
|
||
|
||
使用 **TurboJPEG**(基于 libjpeg-turbo 的 SIMD 优化库)替换 `cv2.imencode`/`cv2.imdecode`:
|
||
|
||
```python
|
||
# 安装
|
||
pip install PyTurboJPEG
|
||
|
||
# 使用(带回退逻辑)
|
||
from turbojpeg import TurboJPEG
|
||
_turbo_jpeg = TurboJPEG()
|
||
|
||
def turbo_decode(jpeg_bytes):
|
||
return _turbo_jpeg.decode(jpeg_bytes) # 2-3x faster
|
||
|
||
def turbo_encode(bgr_image, quality=80):
|
||
return _turbo_jpeg.encode(bgr_image, quality=quality) # 2-3x faster
|
||
```
|
||
|
||
**速度对比**:
|
||
| 操作 | cv2 | TurboJPEG | 提升 |
|
||
|------|-----|-----------|------|
|
||
| 解码 640x480 | ~5ms | ~2ms | **2.5x** |
|
||
| 编码 640x480 | ~8ms | ~3ms | **2.7x** |
|
||
|
||
### 4. 导航结果 JPEG 缓存
|
||
|
||
导航处理后的标注图像只在有新结果时编码一次,后续帧复用缓存:
|
||
|
||
```python
|
||
_nav_last_result_jpeg = None # 缓存编码后的 JPEG
|
||
|
||
# 新结果时编码并缓存
|
||
if res.annotated_image is not None:
|
||
_nav_last_result_jpeg = turbo_encode(res.annotated_image, quality=80)
|
||
|
||
# 广播时直接使用缓存
|
||
await _broadcast_to_viewers(_nav_last_result_jpeg)
|
||
```
|
||
|
||
---
|
||
|
||
## 修改文件
|
||
|
||
| 文件 | 修改内容 |
|
||
|------|----------|
|
||
| `app_main.py` | TurboJPEG 导入、turbo_decode/encode 函数、零拷贝直传、延迟解码、JPEG 缓存 |
|
||
| `requirements.txt` | 添加 PyTurboJPEG>=1.7.0 |
|
||
| `.env` | GPU 1 配置、性能参数优化 |
|
||
|
||
---
|
||
|
||
## 预期效果
|
||
|
||
| 场景 | 之前 | 之后 | 优化幅度 |
|
||
|------|------|------|---------|
|
||
| CHAT/IDLE 模式 | imdecode + imencode | 直接转发 | **-100%** |
|
||
| ITEM_SEARCH 模式 | imdecode + imencode | 直接转发 | **-100%** |
|
||
| 导航模式(新结果) | imdecode + imencode | turbo_decode + turbo_encode(一次) | **2-3x** |
|
||
| 导航模式(复用结果) | imdecode + imencode | 直接转发缓存 | **-100%** |
|
||
|
||
**综合效果**:
|
||
- 非导航场景:CPU 开销降低 **90%+**
|
||
- 导航场景(8fps,YOLO 约 2fps):编码次数从 8 次/秒 降至 **2 次/秒**
|
||
- 编解码速度:提升 **2-3 倍**
|
||
|
||
---
|
||
|
||
## 部署步骤
|
||
|
||
```bash
|
||
# 服务器端
|
||
cd OpenAIglasses_for_Navigation
|
||
pip install PyTurboJPEG
|
||
python app_main.py
|
||
|
||
# 启动后应看到日志:
|
||
# [INIT] TurboJPEG 加载成功,JPEG 编解码将使用加速版本
|
||
```
|
||
|
||
---
|
||
|
||
## 验证结果 (2025-12-23 下午)
|
||
|
||
- [x] 服务器端部署后测试
|
||
- [x] 导航模式功能正常
|
||
- [x] 所有模式切换正常
|
||
- [x] nvidia-smi 确认使用 GPU 1
|
||
- [ ] 室外 4G 热点实测传输流畅度(待测)
|
||
|
||
---
|
||
|
||
## 新问题诊断
|
||
|
||
### 问题现象
|
||
|
||
测试盲道导航时发现:
|
||
1. **GPU 利用率仅 7%** - 远低于预期
|
||
2. **Python 进程占用 120% CPU** - 但服务器总 CPU 仅 3%(56 核利用率极低)
|
||
3. **帧处理 FPS 仅 3-4 帧** - 画面卡顿严重
|
||
4. **TTS 语音有时不播放** - 音频 WebSocket 断开导致缓冲
|
||
|
||
### 根本原因:Python GIL 瓶颈
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Python 进程 │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ GIL (全局解释器锁) │ │
|
||
│ │ 同一时刻只有一个线程执行 Python 代码 │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ Thread 1 (frame_proc-0) ──▶ 执行中 │
|
||
│ Thread 2 (frame_proc-1) ──▶ 等待 GIL │
|
||
│ Thread 3 (frame_proc-2) ──▶ 等待 GIL │
|
||
│ │
|
||
│ 结果:3 个线程实际只能用 1 个 CPU 核心 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**关键代码位置**:
|
||
```python
|
||
# app_main.py 第 237 行
|
||
frame_processing_executor = ThreadPoolExecutor(max_workers=3, ...)
|
||
```
|
||
|
||
`ThreadPoolExecutor` 受 GIL 限制,无法真正并行化 CPU 密集型任务。
|
||
|
||
---
|
||
|
||
## 遗留问题与解决方案
|
||
|
||
### 问题 1:CPU 单线程瓶颈
|
||
|
||
**状态**:❌ 未解决
|
||
|
||
**解决方案**:使用 **PyNvJpeg** 将 JPEG 编解码移到 GPU
|
||
|
||
```bash
|
||
pip install pynvjpeg
|
||
```
|
||
|
||
**实施步骤**:
|
||
|
||
1. 创建 `gpu_jpeg.py` 模块
|
||
2. 修改 `app_main.py` 替换 turbo_decode/turbo_encode
|
||
|
||
### 问题 2:TTS 语音不播放
|
||
|
||
**状态**:❌ 未解决
|
||
|
||
**现象**:`[TTS->WS] Buffering TTS audio, will send when reconnected`
|
||
|
||
**原因**:音频 WebSocket 断开
|
||
|
||
### 问题 3:画面卡顿/滞后
|
||
|
||
**状态**:⚠️ 待问题 1 解决后验证
|
||
|
||
---
|
||
|
||
## 明日开发计划
|
||
|
||
1. **安装 PyNvJpeg**:`pip install pynvjpeg`
|
||
2. **创建 gpu_jpeg.py 模块**
|
||
3. **修改 app_main.py 使用 GPU JPEG**
|
||
4. **性能验证**:`watch -n 1 nvidia-smi`
|
||
5. **验证 TTS 播放问题**
|
||
|
||
---
|
||
|
||
## 服务器配置
|
||
|
||
| 组件 | 配置 |
|
||
|------|------|
|
||
| CPU | 2× Intel Xeon E5-2680 v4 (56 线程) |
|
||
| 内存 | 192 GB DDR4 |
|
||
| GPU | 2× NVIDIA RTX 3090 (24 GB) |
|
||
|