Compare commits
4 Commits
90a7b5acc0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ff04d10d5 | |||
| 0528b8cbcd | |||
| b69fbb1855 | |||
| fb4c5e2af4 |
786
README.md
786
README.md
@ -28,69 +28,257 @@
|
||||
## 项目结构
|
||||
```
|
||||
.
|
||||
├── pipeline/ # 核心pipeline实现
|
||||
│ ├── common/ # 通用组件
|
||||
│ │ ├── config_parser.* # 配置解析
|
||||
│ │ ├── pipeline.* # Pipeline核心实现
|
||||
│ │ └── yaml_config_parser.*# YAML配置解析
|
||||
│ ├── configs/ # 配置文件
|
||||
│ │ └── pipeline.yaml # Pipeline配置示例
|
||||
│ ├── inference/ # 推理相关实现
|
||||
│ │ ├── cuda_helper.* # CUDA工具函数
|
||||
│ │ ├── preprocess.* # 预处理实现
|
||||
│ │ └── trt_inference.* # TensorRT推理
|
||||
│ ├── input/ # 输入模块
|
||||
│ │ ├── frame_queue.* # 帧队列
|
||||
│ │ ├── input_manager.* # 输入管理
|
||||
│ │ ├── rtsp_reader.* # RTSP读取
|
||||
│ │ └── video_reader.* # 视频读取
|
||||
│ ├── output/ # 输出模块
|
||||
│ │ ├── rtsp_writer.* # RTSP推流
|
||||
│ │ └── video_writer.* # 视频写入
|
||||
│ ├── render/ # 渲染模块
|
||||
│ │ ├── frame_drawer.* # 帧绘制
|
||||
│ │ └── renderer.* # 渲染器
|
||||
│ ├── utils/ # 工具类
|
||||
│ │ ├── cuda_helper.* # CUDA辅助
|
||||
│ │ ├── logger.* # 日志
|
||||
│ │ └── timer.* # 计时器
|
||||
│ ├── types.hpp # 类型定义
|
||||
│ └── CMakeLists.txt # 构建配置
|
||||
├── tests/ # 测试代码
|
||||
│ ├── test_config_parser.cpp
|
||||
│ ├── test_cuda_helper.cpp
|
||||
│ ├── test_frame_drawer.cpp
|
||||
│ ├── test_frame_queue.cpp
|
||||
│ ├── test_input_manager.cpp
|
||||
│ ├── test_pipeline.cpp
|
||||
│ ├── test_preprocess.cpp
|
||||
│ ├── test_renderer.cpp
|
||||
│ ├── test_rtsp_reader.cpp
|
||||
│ ├── test_trt_inference.cpp
|
||||
│ ├── test_video_reader.cpp
|
||||
│ ├── test_yaml_config.cpp
|
||||
│ └── CMakeLists.txt
|
||||
├── examples/ # 示例代码
|
||||
│ ├── CMakeLists.txt # 示例构建配置
|
||||
│ ├── rtsp_demo.cpp # RTSP示例
|
||||
│ └── video_demo.cpp # 视频处理示例
|
||||
├── docs/ # 文档
|
||||
│ ├── api_reference.md # API参考
|
||||
│ ├── architecture.md # 架构设计
|
||||
│ ├── build_guide.md # 构建指南
|
||||
│ └── performance_tuning.md # 性能调优
|
||||
├── models/ # 模型文件
|
||||
│ ├── yolov8n.onnx # ONNX模型
|
||||
│ └── yolov8n.engine # TensorRT引擎
|
||||
├── common/ # 通用组件
|
||||
├── CMakeLists.txt # 主构建配置
|
||||
├── README.md # 项目说明文档
|
||||
├── build.sh # 构建脚本
|
||||
├── common/ # 通用组件
|
||||
│ └── logger.hpp # 日志组件
|
||||
├── ref/ # 参考实现
|
||||
├── build/ # 构建目录
|
||||
├── main.cpp # 主程序入口
|
||||
├── CMakeLists.txt # 主构建配置
|
||||
└── build.sh # 构建脚本
|
||||
├── config.yaml # 主配置文件
|
||||
├── docs/ # 文档
|
||||
│ ├── api_reference.md
|
||||
│ ├── architecture.md
|
||||
│ ├── build_guide.md
|
||||
│ └── performance_tuning.md
|
||||
├── examples/ # 示例代码
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── rtsp_demo.cpp
|
||||
│ └── video_demo.cpp
|
||||
├── logs/ # 日志目录
|
||||
├── main.cpp # 主程序入口
|
||||
├── models/ # 模型文件
|
||||
│ ├── model.engine
|
||||
│ ├── yolov8n.engine
|
||||
│ └── yolov8n.onnx
|
||||
├── output/ # 输出目录
|
||||
│ └── videos/ # 视频输出目录
|
||||
├── pipeline/ # 核心pipeline实现
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── common/ # 通用组件
|
||||
│ │ ├── config_parser.*
|
||||
│ │ ├── logger.hpp
|
||||
│ │ ├── pipeline.*
|
||||
│ │ └── yaml_config_parser.*
|
||||
│ ├── configs/ # 配置文件
|
||||
│ │ └── pipeline.yaml
|
||||
│ ├── inference/ # 推理相关实现
|
||||
│ │ ├── cuda_helper.*
|
||||
│ │ ├── preprocess.*
|
||||
│ │ ├── trt_inference.*
|
||||
│ │ └── types.hpp
|
||||
│ ├── input/ # 输入模块
|
||||
│ │ ├── frame_queue.*
|
||||
│ │ ├── input_manager.*
|
||||
│ │ ├── rtsp_reader.*
|
||||
│ │ └── video_reader.*
|
||||
│ ├── output/ # 输出模块
|
||||
│ │ ├── output_manager.*
|
||||
│ │ ├── rtsp_writer.*
|
||||
│ │ ├── video_writer.*
|
||||
│ │ └── writer_interface.hpp
|
||||
│ ├── render/ # 渲染模块
|
||||
│ │ ├── frame_drawer.*
|
||||
│ │ └── renderer.*
|
||||
│ ├── types.hpp # 类型定义
|
||||
│ └── utils/ # 工具类
|
||||
│ ├── cuda_helper.*
|
||||
│ ├── logger.*
|
||||
│ └── timer.*
|
||||
├── ref/ # 参考实现
|
||||
│ └── tensorrt_engine.cpp
|
||||
├── test_data/ # 测试数据
|
||||
│ └── videos/
|
||||
│ └── raw.mp4
|
||||
└── tests/ # 测试代码
|
||||
├── CMakeLists.txt
|
||||
├── test_base.hpp
|
||||
├── test_config_parser.cpp
|
||||
├── test_cuda_helper.cpp
|
||||
├── test_frame_drawer.cpp
|
||||
├── test_frame_queue.cpp
|
||||
├── test_input_manager.cpp
|
||||
├── test_output_manager.cpp
|
||||
├── test_pipeline.cpp
|
||||
├── test_preprocess.cpp
|
||||
├── test_renderer.cpp
|
||||
├── test_rtsp_reader.cpp
|
||||
├── test_rtsp_writer.cpp
|
||||
├── test_trt_inference.cpp
|
||||
├── test_video_reader.cpp
|
||||
├── test_video_writer.cpp
|
||||
└── test_yaml_config.cpp
|
||||
```
|
||||
|
||||
## 配置文件说明
|
||||
|
||||
项目使用 YAML 格式的配置文件来管理所有设置。主配置文件位于:
|
||||
```
|
||||
/pipeline/configs/pipeline.yaml
|
||||
```
|
||||
|
||||
### 配置文件结构说明
|
||||
|
||||
1. 输入源配置 (input.sources)
|
||||
- `type`: 输入类型 (video/rtsp)
|
||||
- `name`: 输入源名称,用于标识和映射
|
||||
- `url`: 输入源地址
|
||||
- `buffer_size`: 缓冲区大小
|
||||
- `output_targets`: 输出目标列表,指定该输入源对应的输出目标名称
|
||||
- 可以指定一个或多个输出目标
|
||||
- 如果不指定,默认使用所有可用的输出目标
|
||||
- 示例:`output_targets: ["output1", "output2"]`
|
||||
|
||||
2. 输出目标配置 (output.targets)
|
||||
- `type`: 输出类型 (video/rtsp)
|
||||
- `name`: 输出目标名称,用于与输入源映射
|
||||
- `path`: 输出路径
|
||||
- `fps`: 输出帧率
|
||||
- `codec`: 编码器类型
|
||||
- `bitrate`: 码率设置
|
||||
|
||||
3. 输入输出映射关系
|
||||
- 每个输入源可以指定一个或多个输出目标
|
||||
- 通过 `output_targets` 字段指定目标名称列表
|
||||
- 如果未指定 `output_targets`,默认使用所有输出目标
|
||||
- 映射规则:
|
||||
- 一对一映射:一个输入源映射到一个输出目标
|
||||
- 一对多映射:一个输入源可以同时映射到多个输出目标
|
||||
- 多对一映射:多个输入源可以映射到同一个输出目标
|
||||
- 默认映射:未指定映射时使用所有可用输出目标
|
||||
|
||||
### 配置示例
|
||||
|
||||
```yaml
|
||||
# Pipeline配置文件
|
||||
|
||||
input:
|
||||
sources:
|
||||
- type: rtsp
|
||||
name: "camera1"
|
||||
url: "rtsp://10.0.0.17:8554/camera1"
|
||||
buffer_size: 30
|
||||
output_targets: ["output1", "stream1"] # 一个输入映射到多个输出
|
||||
|
||||
- type: video
|
||||
name: "video1"
|
||||
url: "/data/test.mp4"
|
||||
buffer_size: 30
|
||||
output_targets: ["output2"] # 一个输入映射到一个输出
|
||||
|
||||
- type: rtsp
|
||||
name: "camera2"
|
||||
url: "rtsp://10.0.0.17:8554/camera2"
|
||||
buffer_size: 30
|
||||
# 未指定 output_targets,将使用所有可用的输出目标
|
||||
|
||||
output:
|
||||
targets:
|
||||
- type: video
|
||||
name: "output1"
|
||||
path: "/output/camera1.mp4"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
bitrate: 4000000
|
||||
|
||||
- type: rtsp
|
||||
name: "stream1"
|
||||
path: "rtsp://localhost:8554/live/camera1"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
bitrate: 4000000
|
||||
|
||||
- type: video
|
||||
name: "output2"
|
||||
path: "/output/video1.mp4"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
bitrate: 4000000
|
||||
|
||||
# 日志配置
|
||||
log:
|
||||
level: "info" # debug/info/warn/error
|
||||
save_path: "logs/" # 日志保存路径
|
||||
|
||||
```
|
||||
|
||||
### 输入输出映射功能说明
|
||||
|
||||
#### 功能特点
|
||||
|
||||
1. 灵活的映射关系
|
||||
- 支持一对一、一对多、多对一的映射关系
|
||||
- 可以动态配置每个输入源的输出目标
|
||||
- 提供默认映射机制,简化配置
|
||||
|
||||
2. 资源优化
|
||||
- 根据映射关系优化资源分配
|
||||
- 避免不必要的输出处理
|
||||
- 支持输出目标的复用
|
||||
|
||||
3. 错误处理
|
||||
- 配置验证确保映射关系有效
|
||||
- 运行时错误检测和恢复
|
||||
- 详细的错误日志和状态报告
|
||||
|
||||
#### 使用方法
|
||||
|
||||
1. 配置文件设置
|
||||
```yaml
|
||||
input:
|
||||
sources:
|
||||
- name: "camera1"
|
||||
output_targets: ["output1", "stream1"] # 指定多个输出目标
|
||||
- name: "video1"
|
||||
output_targets: ["output2"] # 指定单个输出目标
|
||||
- name: "camera2" # 使用默认映射
|
||||
```
|
||||
|
||||
2. 运行时管理
|
||||
- 可以通过API动态更新映射关系
|
||||
- 支持添加或移除输出目标
|
||||
- 提供映射状态查询接口
|
||||
|
||||
3. 性能考虑
|
||||
- 合理配置映射关系避免资源浪费
|
||||
- 注意输出目标数量对性能的影响
|
||||
- 可以通过监控接口观察资源使用情况
|
||||
|
||||
#### 最佳实践
|
||||
|
||||
1. 映射配置
|
||||
- 根据实际需求配置映射关系
|
||||
- 避免不必要的多重映射
|
||||
- 使用有意义的名称便于管理
|
||||
|
||||
2. 资源管理
|
||||
- 及时清理不再使用的输出目标
|
||||
- 监控资源使用情况
|
||||
- 在配置大量映射时注意性能影响
|
||||
|
||||
3. 错误处理
|
||||
- 实现错误回调处理机制
|
||||
- 定期检查映射状态
|
||||
- 保存详细的错误日志
|
||||
|
||||
### 配置文件使用说明
|
||||
|
||||
1. 测试程序
|
||||
- 测试程序固定使用 `/pipeline/configs/pipeline.yaml`
|
||||
- 配置文件路径在 `tests/test_base.hpp` 中定义
|
||||
- 运行测试时无需指定配置文件
|
||||
|
||||
2. 主程序
|
||||
- 主程序通过命令行参数指定配置文件
|
||||
- 运行格式:`./build/trt_pipeline_demo <config_file>`
|
||||
- 推荐使用默认配置文件:
|
||||
```bash
|
||||
./build/trt_pipeline_demo pipeline/configs/pipeline.yaml
|
||||
```
|
||||
|
||||
3. 配置文件修改
|
||||
- 建议复制 `pipeline.yaml` 进行修改,而不是直接修改原文件
|
||||
- 修改时需要特别注意路径配置
|
||||
- 保持配置文件结构不变,只修改相应的值
|
||||
|
||||
## 环境依赖
|
||||
|
||||
- CUDA = 12.1
|
||||
@ -99,93 +287,6 @@
|
||||
- FFmpeg (RTSP支持)
|
||||
|
||||
## 配置示例
|
||||
|
||||
```yaml
|
||||
# Pipeline配置文件
|
||||
|
||||
input:
|
||||
sources:
|
||||
- type: rtsp
|
||||
name: "camera1"
|
||||
url: "rtsp://10.0.0.17:8554/camera_test/2" # 实际的RTSP地址
|
||||
buffer_size: 30
|
||||
max_batch_size: 4
|
||||
|
||||
inference:
|
||||
model:
|
||||
onnx_path: "/app/models/yolov8n.onnx" # ONNX模型路径
|
||||
engine_path: "/app/models/yolov8n.engine" # TensorRT引擎路径
|
||||
input_shape: [3, 640, 640] # YOLOv8n的输入尺寸
|
||||
precision: "FP16" # FP32/FP16/INT8
|
||||
version: "yolov8" # 可选:YOLO版本信息
|
||||
labels: ["person", "car", "truck"] # 可选:模型标签列表,按class_id顺序配置
|
||||
threshold:
|
||||
conf: 0.5
|
||||
nms: 0.45
|
||||
gpu_id: 0
|
||||
|
||||
render:
|
||||
enable: true # 控制是否启用渲染功能
|
||||
window:
|
||||
name: "Detection Results"
|
||||
width: 1280
|
||||
height: 720
|
||||
fullscreen: false
|
||||
|
||||
# 默认渲染样式
|
||||
default_style:
|
||||
box_color: [0, 255, 0] # BGR格式,默认绿色
|
||||
text_color: [255, 255, 255] # BGR格式,默认白色
|
||||
transparency: 0.0 # 0.0-1.0,0表示不透明
|
||||
box_thickness: 2
|
||||
font_scale: 0.5
|
||||
font_thickness: 1
|
||||
|
||||
# 每个类别的自定义样式
|
||||
class_styles:
|
||||
person:
|
||||
box_color: [255, 0, 0] # BGR格式,红色
|
||||
text_color: [255, 255, 255]
|
||||
transparency: 0.2
|
||||
box_thickness: 2
|
||||
font_scale: 0.5
|
||||
font_thickness: 1
|
||||
car:
|
||||
box_color: [0, 255, 0] # BGR格式,绿色
|
||||
text_color: [255, 255, 255]
|
||||
transparency: 0.2
|
||||
box_thickness: 2
|
||||
font_scale: 0.5
|
||||
font_thickness: 1
|
||||
truck:
|
||||
box_color: [0, 0, 255] # BGR格式,蓝色
|
||||
text_color: [255, 255, 255]
|
||||
transparency: 0.2
|
||||
box_thickness: 2
|
||||
font_scale: 0.5
|
||||
font_thickness: 1
|
||||
|
||||
# 性能指标显示设置
|
||||
metrics:
|
||||
show_fps: true
|
||||
show_inference_time: true
|
||||
show_gpu_usage: true
|
||||
update_interval_ms: 1000
|
||||
|
||||
output:
|
||||
targets:
|
||||
- type: video
|
||||
name: "output1"
|
||||
path: "/output/result.mp4" # 输出MP4文件路径
|
||||
fps: 30
|
||||
codec: "h264" # 视频编码器
|
||||
bitrate: 4000000 # 4Mbps
|
||||
|
||||
# 日志配置
|
||||
log:
|
||||
level: "info" # debug/info/warn/error
|
||||
save_path: "logs/" # 日志保存路径
|
||||
|
||||
```
|
||||
|
||||
## 架构设计
|
||||
@ -202,394 +303,3 @@ log:
|
||||
```
|
||||
[输入源] -> [预处理] -> [推理] -> [后处理] -> [渲染] -> [输出]
|
||||
```
|
||||
|
||||
### 性能优化 🚀
|
||||
|
||||
1. CUDA优化
|
||||
- 使用CUDA Streams实现异步处理
|
||||
- 内存优化:pinned memory和zero-copy memory
|
||||
- Kernel融合减少内存访问
|
||||
- 单元测试完成 (test_cuda_helper.cpp)
|
||||
|
||||
2. Pipeline优化
|
||||
- 多线程并行处理
|
||||
- 内存池复用
|
||||
- 帧队列优化
|
||||
- 单元测试完成 (test_pipeline.cpp)
|
||||
|
||||
3. 推理优化
|
||||
- TensorRT INT8量化
|
||||
- 动态Batch支持
|
||||
- 模型剪枝
|
||||
- 单元测试完成 (test_trt_inference.cpp)
|
||||
|
||||
### 已知问题 ⚠️
|
||||
|
||||
1. Pipeline错误处理
|
||||
- 配置文件验证完善
|
||||
- 运行时状态检查
|
||||
- 资源清理机制
|
||||
- 单元测试完善中
|
||||
|
||||
2. 内存管理
|
||||
- CUDA内存泄漏检测
|
||||
- 资源自动释放
|
||||
- 异常安全保证
|
||||
|
||||
3. 性能监控
|
||||
- 关键指标采集
|
||||
- 性能瓶颈分析
|
||||
- 监控数据可视化
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 确保显卡驱动和CUDA版本匹配
|
||||
- 配置文件使用绝对路径
|
||||
- 多路输入注意内存使用
|
||||
```
|
||||
|
||||
## 当前开发状态
|
||||
|
||||
### 已完成模块 ✅
|
||||
|
||||
1. 文档系统
|
||||
- 架构设计文档 (3.7KB)
|
||||
- 构建指南 (2.3KB)
|
||||
- API参考 (3.7KB)
|
||||
- 性能调优指南 (3.3KB)
|
||||
|
||||
2. 配置系统
|
||||
- YAML配置解析器
|
||||
- 配置验证机制
|
||||
- 单元测试完成 (test_yaml_config.cpp, test_config_parser.cpp)
|
||||
|
||||
3. 推理模块
|
||||
- TensorRT引擎 (20KB实现)
|
||||
- 预处理和后处理 (11.4KB实现)
|
||||
- CUDA优化
|
||||
- 单元测试完成 (test_trt_inference.cpp, test_preprocess.cpp)
|
||||
|
||||
4. 输入模块
|
||||
- 视频读取器 (10KB实现)
|
||||
- RTSP流读取 (7KB实现)
|
||||
- 帧队列管理 (3.1KB实现)
|
||||
- 单元测试完成 (test_video_reader.cpp, test_rtsp_reader.cpp, test_frame_queue.cpp)
|
||||
- 错误处理和恢复机制完善
|
||||
|
||||
5. 渲染模块
|
||||
- 实时渲染器 (9.9KB实现)
|
||||
- 帧绘制 (1.6KB实现)
|
||||
- 单元测试完成 (test_renderer.cpp, test_frame_drawer.cpp)
|
||||
|
||||
6. 输出模块
|
||||
- VideoWriter: 视频文件写入
|
||||
- RtspWriter: RTSP流推送
|
||||
- OutputManager: 输出管理
|
||||
- 支持多路输出目标管理
|
||||
- 支持输入源到输出目标的映射
|
||||
- 支持视频文件和RTSP流输出
|
||||
- 提供目标状态监控和资源管理
|
||||
- 线程安全的接口设计
|
||||
|
||||
### OutputManager 使用指南
|
||||
|
||||
OutputManager提供了统一的输出管理接口,支持多路输出和输入源映射。
|
||||
|
||||
#### 主要功能
|
||||
|
||||
1. 多路输出管理
|
||||
- 支持同时管理多个输出目标
|
||||
- 支持视频文件和RTSP流输出
|
||||
- 动态添加和移除输出目标
|
||||
|
||||
2. 输入源映射
|
||||
- 支持将输入源映射到特定的输出目标
|
||||
- 灵活的映射关系配置
|
||||
- 动态更新映射关系
|
||||
|
||||
3. 资源管理
|
||||
- 自动管理输出资源
|
||||
- 提供状态监控接口
|
||||
- 支持优雅的资源清理
|
||||
|
||||
#### 配置示例
|
||||
|
||||
```yaml
|
||||
output:
|
||||
targets:
|
||||
- type: video
|
||||
name: "camera1_output"
|
||||
path: "/output/camera1.mp4"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
bitrate: 4000000
|
||||
|
||||
- type: rtsp
|
||||
name: "camera1_stream"
|
||||
path: "rtsp://localhost:8554/live/camera1"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
bitrate: 4000000
|
||||
|
||||
# 输入源到输出目标的映射
|
||||
mappings:
|
||||
camera1:
|
||||
- camera1_output
|
||||
- camera1_stream
|
||||
camera2:
|
||||
- camera2_output
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```cpp
|
||||
// 创建OutputManager实例
|
||||
OutputManager output_manager;
|
||||
|
||||
// 添加视频输出目标
|
||||
OutputTargetConfig video_config;
|
||||
video_config.type = "video";
|
||||
video_config.name = "camera1_output";
|
||||
video_config.path = "/output/camera1.mp4";
|
||||
output_manager.addTarget(video_config);
|
||||
|
||||
// 添加RTSP输出目标
|
||||
OutputTargetConfig rtsp_config;
|
||||
rtsp_config.type = "rtsp";
|
||||
rtsp_config.name = "camera1_stream";
|
||||
rtsp_config.path = "rtsp://localhost:8554/live/camera1";
|
||||
output_manager.addTarget(rtsp_config);
|
||||
|
||||
// 配置输入源映射
|
||||
std::vector<std::string> targets = {"camera1_output", "camera1_stream"};
|
||||
output_manager.addSourceTargetMapping("camera1", targets);
|
||||
|
||||
// 写入帧
|
||||
cv::Mat frame = ...;
|
||||
output_manager.writeFrames("camera1", frame);
|
||||
|
||||
// 获取目标状态
|
||||
std::string error_msg;
|
||||
bool status = output_manager.getTargetStatus("camera1_output", error_msg);
|
||||
|
||||
// 清理资源
|
||||
output_manager.cleanup();
|
||||
```
|
||||
|
||||
#### 注意事项
|
||||
|
||||
1. 线程安全
|
||||
- 所有公共接口都是线程安全的
|
||||
- 支持多线程并发写入
|
||||
|
||||
2. 资源管理
|
||||
- 及时调用cleanup()释放资源
|
||||
- 使用RAII管理资源生命周期
|
||||
|
||||
3. 错误处理
|
||||
- 检查接口返回值
|
||||
- 通过getTargetStatus获取详细错误信息
|
||||
|
||||
4. 性能优化
|
||||
- 适当配置编码参数
|
||||
- 注意输出目标数量对性能的影响
|
||||
|
||||
### 进行中模块 🔄
|
||||
|
||||
1. Pipeline核心组件
|
||||
- Pipeline实现 (pipeline.hpp/cpp)
|
||||
- 集成测试 (test_pipeline.cpp)
|
||||
- 性能优化和监控
|
||||
|
||||
### 待开发模块 ❌
|
||||
|
||||
1. 示例代码
|
||||
- 视频处理示例 (video_demo.cpp)
|
||||
- RTSP流处理示例 (rtsp_demo.cpp)
|
||||
|
||||
2. 输出模块
|
||||
- 视频写入器 (video_writer.hpp/cpp)
|
||||
- RTSP推流 (rtsp_writer.hpp/cpp)
|
||||
|
||||
## 开发计划
|
||||
|
||||
### 第一阶段:核心功能完善(预计2周)
|
||||
|
||||
1. Week 1: Pipeline核心组件
|
||||
- 实现pipeline.hpp/cpp
|
||||
- 完成Pipeline单元测试
|
||||
- 进行基础集成测试
|
||||
|
||||
2. Week 2: 输出模块开发
|
||||
- 实现视频写入器
|
||||
- 实现RTSP推流功能
|
||||
- 完成输出模块单元测试
|
||||
|
||||
### 第二阶段:示例与文档(预计1周)
|
||||
|
||||
1. 示例代码开发
|
||||
- 完成视频处理示例
|
||||
- 完成RTSP流处理示例
|
||||
- 编写示例使用文档
|
||||
|
||||
2. 文档更新
|
||||
- 更新API文档
|
||||
- 补充性能优化指南
|
||||
- 完善构建和部署文档
|
||||
|
||||
### 第三阶段:性能优化(预计1周)
|
||||
|
||||
1. 性能测试与优化
|
||||
- 进行完整的性能测试
|
||||
- 优化内存使用
|
||||
- 优化CPU/GPU负载
|
||||
- 优化Pipeline并行性能
|
||||
|
||||
2. 稳定性测试
|
||||
- 长时间运行测试
|
||||
- 压力测试
|
||||
- 内存泄漏检测
|
||||
|
||||
## 已知问题
|
||||
|
||||
1. Pipeline核心组件
|
||||
- Pipeline实现未完成
|
||||
- 集成测试未开始
|
||||
- 性能优化待进行
|
||||
|
||||
2. 输出模块
|
||||
- 视频写入器未实现
|
||||
- RTSP推流功能未实现
|
||||
- 性能测试未开始
|
||||
|
||||
3. 示例代码
|
||||
- 示例代码未实现
|
||||
- 使用文档待完善
|
||||
|
||||
### 构建和测试 🔨
|
||||
|
||||
1. 构建系统
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
2. 单元测试
|
||||
```bash
|
||||
# 运行所有测试
|
||||
make test
|
||||
|
||||
# 运行单个模块测试
|
||||
make input_manager_test && ./tests/input_manager_test
|
||||
make yaml_config_test && ./tests/yaml_config_test
|
||||
make config_parser_test && ./tests/config_parser_test
|
||||
make frame_drawer_test && ./tests/frame_drawer_test
|
||||
make renderer_test && ./tests/renderer_test
|
||||
make trt_inference_test && ./tests/trt_inference_test
|
||||
make preprocess_test && ./tests/preprocess_test
|
||||
make cuda_helper_test && ./tests/cuda_helper_test
|
||||
make frame_queue_test && ./tests/frame_queue_test
|
||||
make pipeline_test && ./tests/pipeline_test
|
||||
```
|
||||
|
||||
3. 测试覆盖率
|
||||
- 输入模块: 95%
|
||||
- 配置系统: 98%
|
||||
- 推理模块: 92%
|
||||
- 渲染模块: 90%
|
||||
- Pipeline: 85%
|
||||
- 总体覆盖率: 92%
|
||||
|
||||
4. 性能测试
|
||||
- 单卡吞吐量: 60FPS
|
||||
- GPU利用率: 85%
|
||||
- CPU利用率: 40%
|
||||
- 内存使用: 2GB
|
||||
- CUDA内存: 1GB
|
||||
```
|
||||
|
||||
# 视频处理管线
|
||||
|
||||
## 开发进度
|
||||
|
||||
### 输入模块
|
||||
- [x] RTSP流读取器
|
||||
- [x] 视频文件读取器
|
||||
- [x] 输入管理器
|
||||
|
||||
### 推理模块
|
||||
- [x] TensorRT引擎封装
|
||||
- [x] CUDA辅助函数
|
||||
- [x] 目标检测接口
|
||||
|
||||
### 渲染模块
|
||||
- [x] 基础渲染器
|
||||
- [x] 帧绘制器
|
||||
- [x] 性能指标显示
|
||||
|
||||
### 输出模块
|
||||
- [x] 视频写入器
|
||||
- 支持多种编码格式(H264/MP4V/MJPG/XVID)
|
||||
- 可配置帧率和码率
|
||||
- 自动创建输出目录
|
||||
- 支持MP4文件输出
|
||||
- [x] RTSP写入器
|
||||
- 支持H264/H265编码
|
||||
- 可配置帧率和码率
|
||||
- 延迟初始化机制
|
||||
- 支持RTSP推流
|
||||
- [x] 输出管理器
|
||||
- 统一管理视频和RTSP输出
|
||||
- 支持同时输出多个目标
|
||||
- 支持动态添加/移除目标
|
||||
- 线程安全的帧写入
|
||||
- 完整的错误处理
|
||||
|
||||
### 配置模块
|
||||
- [x] YAML配置解析器
|
||||
- [x] 参数验证器
|
||||
|
||||
### 管线模块
|
||||
- [x] 管线调度器
|
||||
- [x] 资源管理器
|
||||
- [x] 性能监控器
|
||||
|
||||
### 测试覆盖
|
||||
- [x] 单元测试
|
||||
- 输入模块测试
|
||||
- 推理模块测试
|
||||
- 渲染模块测试
|
||||
- 输出模块测试
|
||||
- 配置模块测试
|
||||
- [ ] 集成测试
|
||||
- 基本集成测试完成
|
||||
- RTSP服务器相关测试待进行
|
||||
|
||||
## 构建和测试
|
||||
|
||||
### 依赖项
|
||||
- OpenCV 4.x
|
||||
- TensorRT 8.x
|
||||
- CUDA 11.x
|
||||
- GTest
|
||||
- FFmpeg
|
||||
- yaml-cpp
|
||||
|
||||
### 构建命令
|
||||
```bash
|
||||
mkdir build && cd build
|
||||
cmake ..
|
||||
make -j
|
||||
```
|
||||
|
||||
### 测试命令
|
||||
```bash
|
||||
cd build
|
||||
ctest --output-on-failure
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
1. RTSP相关测试需要实际的RTSP服务器
|
||||
2. 部分测试可能需要GPU环境
|
||||
3. 确保所有依赖库正确安装
|
||||
67
config.yaml
67
config.yaml
@ -1,67 +0,0 @@
|
||||
input:
|
||||
sources:
|
||||
- type: video
|
||||
name: test_video
|
||||
url: /path/to/test.mp4
|
||||
buffer_size: 30
|
||||
max_batch_size: 4
|
||||
|
||||
inference:
|
||||
model:
|
||||
onnx_path: /path/to/model.onnx
|
||||
engine_path: /path/to/model.engine
|
||||
input_shape: [3, 640, 640]
|
||||
precision: FP16
|
||||
threshold:
|
||||
conf: 0.5
|
||||
nms: 0.45
|
||||
gpu_id: 0
|
||||
|
||||
render:
|
||||
window:
|
||||
name: "Detection Results"
|
||||
width: 1280
|
||||
height: 720
|
||||
fullscreen: false
|
||||
|
||||
default_style:
|
||||
box_color: [0, 255, 0]
|
||||
text_color: [255, 255, 255]
|
||||
transparency: 0.0
|
||||
box_thickness: 2
|
||||
font_scale: 0.5
|
||||
font_thickness: 1
|
||||
|
||||
class_styles:
|
||||
person:
|
||||
box_color: [255, 0, 0]
|
||||
text_color: [255, 255, 255]
|
||||
transparency: 0.2
|
||||
box_thickness: 2
|
||||
font_scale: 0.5
|
||||
font_thickness: 1
|
||||
|
||||
metrics:
|
||||
show_fps: true
|
||||
show_inference_time: true
|
||||
show_gpu_usage: true
|
||||
update_interval_ms: 1000
|
||||
|
||||
output:
|
||||
targets:
|
||||
- type: video
|
||||
name: output_video
|
||||
path: /tmp/output.mp4
|
||||
fps: 30
|
||||
codec: h264
|
||||
bitrate: 4000000
|
||||
- type: rtsp
|
||||
name: output_rtsp
|
||||
path: rtsp://localhost:8554/live
|
||||
fps: 30
|
||||
codec: h264
|
||||
bitrate: 4000000
|
||||
|
||||
log:
|
||||
level: info
|
||||
save_path: /tmp/logs
|
||||
@ -138,9 +138,21 @@ if (reader.connect("rtsp://example/stream")) {
|
||||
## 1. 输入模块 API
|
||||
|
||||
### 1.1 InputManager
|
||||
|
||||
输入管理器,负责管理多路输入源和输出目标映射。
|
||||
|
||||
#### 类定义
|
||||
|
||||
```cpp
|
||||
class InputManager {
|
||||
public:
|
||||
InputManager();
|
||||
~InputManager();
|
||||
|
||||
// 禁用拷贝
|
||||
InputManager(const InputManager&) = delete;
|
||||
InputManager& operator=(const InputManager&) = delete;
|
||||
|
||||
// 添加输入源
|
||||
bool addSource(const std::string& name, const InputConfig& config);
|
||||
|
||||
@ -149,9 +161,154 @@ public:
|
||||
|
||||
// 获取输入源状态
|
||||
SourceStatus getSourceStatus(const std::string& name);
|
||||
|
||||
// 输出目标映射相关接口
|
||||
bool setOutputTargets(const std::string& source_name,
|
||||
const std::vector<std::string>& target_names);
|
||||
bool getOutputTargets(const std::string& source_name,
|
||||
std::vector<std::string>& target_names) const;
|
||||
bool hasOutputTargets(const std::string& source_name) const;
|
||||
bool clearOutputTargets(const std::string& source_name);
|
||||
std::vector<std::string> getAllSourceNames() const;
|
||||
};
|
||||
```
|
||||
|
||||
#### 配置结构
|
||||
|
||||
```cpp
|
||||
struct InputConfig {
|
||||
std::string type; // "video" 或 "rtsp"
|
||||
std::string url; // 输入源地址
|
||||
int buffer_size; // 缓冲区大小
|
||||
std::vector<std::string> output_targets; // 输出目标列表
|
||||
};
|
||||
```
|
||||
|
||||
#### 输出目标映射接口说明
|
||||
|
||||
1. `setOutputTargets`
|
||||
- 功能:设置输入源的输出目标列表
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- `target_names`: 输出目标名称列表
|
||||
- 返回:操作是否成功
|
||||
- 说明:
|
||||
- 会替换现有的输出目标列表
|
||||
- 如果输入源不存在,返回 false
|
||||
- 空列表表示清除所有输出目标
|
||||
|
||||
2. `getOutputTargets`
|
||||
- 功能:获取输入源的输出目标列表
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- `target_names`: 输出参数,存储目标名称列表
|
||||
- 返回:操作是否成功
|
||||
- 说明:
|
||||
- 如果输入源不存在,返回 false
|
||||
- 如果没有输出目标,vector 为空
|
||||
|
||||
3. `hasOutputTargets`
|
||||
- 功能:检查输入源是否有输出目标
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- 返回:是否有输出目标
|
||||
- 说明:
|
||||
- 如果输入源不存在,返回 false
|
||||
- 如果输出目标列表为空,返回 false
|
||||
|
||||
4. `clearOutputTargets`
|
||||
- 功能:清除输入源的所有输出目标
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- 返回:操作是否成功
|
||||
- 说明:
|
||||
- 如果输入源不存在,返回 false
|
||||
- 清除后可以重新设置输出目标
|
||||
|
||||
5. `getAllSourceNames`
|
||||
- 功能:获取所有输入源的名称
|
||||
- 返回:输入源名称列表
|
||||
- 说明:
|
||||
- 返回当前所有已添加的输入源名称
|
||||
- 列表顺序不保证
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```cpp
|
||||
// 创建输入管理器
|
||||
InputManager input_manager;
|
||||
|
||||
// 添加输入源
|
||||
InputConfig config;
|
||||
config.type = "rtsp";
|
||||
config.url = "rtsp://example/stream";
|
||||
config.buffer_size = 30;
|
||||
config.output_targets = {"output1", "stream1"}; // 初始输出目标
|
||||
input_manager.addSource("camera1", config);
|
||||
|
||||
// 修改输出目标
|
||||
std::vector<std::string> new_targets = {"output1"};
|
||||
input_manager.setOutputTargets("camera1", new_targets);
|
||||
|
||||
// 获取输出目标
|
||||
std::vector<std::string> targets;
|
||||
if (input_manager.getOutputTargets("camera1", targets)) {
|
||||
for (const auto& target : targets) {
|
||||
// 处理每个输出目标...
|
||||
}
|
||||
}
|
||||
|
||||
// 检查输出目标状态
|
||||
if (input_manager.hasOutputTargets("camera1")) {
|
||||
// 处理有输出目标的情况...
|
||||
} else {
|
||||
// 处理没有输出目标的情况...
|
||||
}
|
||||
|
||||
// 清除输出目标
|
||||
input_manager.clearOutputTargets("camera1");
|
||||
|
||||
// 获取所有输入源
|
||||
auto sources = input_manager.getAllSourceNames();
|
||||
for (const auto& source : sources) {
|
||||
// 处理每个输入源...
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误处理
|
||||
|
||||
1. 输入源错误
|
||||
- 源不存在:相关操作返回 false
|
||||
- 源已存在:addSource 返回 false
|
||||
- 源状态异常:通过 getSourceStatus 获取详细信息
|
||||
|
||||
2. 输出目标错误
|
||||
- 目标不存在:setOutputTargets 返回 false
|
||||
- 目标列表为空:视为有效操作
|
||||
- 重复目标:自动去重
|
||||
|
||||
3. 线程安全
|
||||
- 所有公共接口都是线程安全的
|
||||
- 支持多线程并发访问
|
||||
- 使用读写锁保护数据
|
||||
|
||||
#### 性能考虑
|
||||
|
||||
1. 数据结构
|
||||
- 使用哈希表存储映射关系
|
||||
- 优化字符串比较和存储
|
||||
- 最小化内存占用
|
||||
|
||||
2. 并发访问
|
||||
- 读写锁分离
|
||||
- 细粒度锁定
|
||||
- 避免长时间持锁
|
||||
|
||||
3. 资源管理
|
||||
- 智能指针管理资源
|
||||
- 及时释放不用的资源
|
||||
- 缓存常用数据
|
||||
|
||||
### 1.2 RtspReader
|
||||
```cpp
|
||||
class RtspReader {
|
||||
@ -198,7 +355,7 @@ public:
|
||||
|
||||
输出管理器,负责管理多路输出目标和输入源映射。
|
||||
|
||||
### 类定义
|
||||
#### 类定义
|
||||
|
||||
```cpp
|
||||
class OutputManager {
|
||||
@ -232,28 +389,179 @@ public:
|
||||
// 清理所有资源
|
||||
void cleanup();
|
||||
|
||||
// 添加输入源到输出目标的映射
|
||||
// 输入输出映射相关接口
|
||||
bool addSourceTargetMapping(const std::string& source_name,
|
||||
const std::vector<std::string>& target_names);
|
||||
|
||||
// 移除输入源的映射
|
||||
bool removeSourceMapping(const std::string& source_name);
|
||||
bool getSourceTargets(const std::string& source_name,
|
||||
std::vector<std::string>& target_names) const;
|
||||
bool updateSourceTargets(const std::string& source_name,
|
||||
const std::vector<std::string>& target_names);
|
||||
bool hasSourceMapping(const std::string& source_name) const;
|
||||
bool isTargetMapped(const std::string& target_name) const;
|
||||
std::vector<std::string> getMappedSources(const std::string& target_name) const;
|
||||
};
|
||||
```
|
||||
|
||||
### 配置结构
|
||||
#### 配置结构
|
||||
|
||||
```cpp
|
||||
struct OutputTargetConfig {
|
||||
std::string type; // "video" 或 "rtsp"
|
||||
std::string name; // 目标名称
|
||||
std::string path; // 输出路径
|
||||
int fps{30}; // 帧率
|
||||
std::string codec; // 编码器
|
||||
int bitrate{4000000}; // 比特率
|
||||
int fps; // 输出帧率
|
||||
std::string codec; // 编码器类型
|
||||
int bitrate; // 码率
|
||||
};
|
||||
```
|
||||
|
||||
#### 输入输出映射接口说明
|
||||
|
||||
1. `addSourceTargetMapping`
|
||||
- 功能:添加输入源到输出目标的映射
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- `target_names`: 输出目标名称列表
|
||||
- 返回:操作是否成功
|
||||
- 说明:
|
||||
- 如果输入源已存在映射,会更新为新的映射关系
|
||||
- 所有目标必须已经通过 `addTarget` 添加
|
||||
- 支持一对多映射
|
||||
|
||||
2. `removeSourceMapping`
|
||||
- 功能:移除输入源的所有映射关系
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- 返回:操作是否成功
|
||||
- 说明:
|
||||
- 不会影响输出目标的存在
|
||||
- 如果输入源不存在映射,返回 false
|
||||
|
||||
3. `getSourceTargets`
|
||||
- 功能:获取输入源映射的所有输出目标
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- `target_names`: 输出参数,存储目标名称列表
|
||||
- 返回:操作是否成功
|
||||
- 说明:
|
||||
- 如果输入源不存在映射,返回 false
|
||||
- 如果输入源存在但没有映射目标,vector 为空
|
||||
|
||||
4. `updateSourceTargets`
|
||||
- 功能:更新输入源的输出目标映射
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- `target_names`: 新的输出目标名称列表
|
||||
- 返回:操作是否成功
|
||||
- 说明:
|
||||
- 会完全替换现有的映射关系
|
||||
- 所有目标必须已经存在
|
||||
- 如果输入源不存在,会创建新的映射
|
||||
|
||||
5. `hasSourceMapping`
|
||||
- 功能:检查输入源是否有映射关系
|
||||
- 参数:
|
||||
- `source_name`: 输入源名称
|
||||
- 返回:是否存在映射
|
||||
|
||||
6. `isTargetMapped`
|
||||
- 功能:检查输出目标是否被映射
|
||||
- 参数:
|
||||
- `target_name`: 输出目标名称
|
||||
- 返回:是否被映射
|
||||
|
||||
7. `getMappedSources`
|
||||
- 功能:获取映射到指定输出目标的所有输入源
|
||||
- 参数:
|
||||
- `target_name`: 输出目标名称
|
||||
- 返回:输入源名称列表
|
||||
- 说明:
|
||||
- 如果目标不存在,返回空列表
|
||||
- 支持查询多对一映射关系
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```cpp
|
||||
// 创建输出管理器
|
||||
OutputManager output_manager;
|
||||
|
||||
// 添加输出目标
|
||||
OutputTargetConfig video_config;
|
||||
video_config.type = "video";
|
||||
video_config.name = "output1";
|
||||
video_config.path = "/output/result1.mp4";
|
||||
output_manager.addTarget(video_config);
|
||||
|
||||
OutputTargetConfig rtsp_config;
|
||||
rtsp_config.type = "rtsp";
|
||||
rtsp_config.name = "stream1";
|
||||
rtsp_config.path = "rtsp://localhost:8554/live/stream1";
|
||||
output_manager.addTarget(rtsp_config);
|
||||
|
||||
// 配置输入源映射
|
||||
std::vector<std::string> targets = {"output1", "stream1"};
|
||||
output_manager.addSourceTargetMapping("camera1", targets);
|
||||
|
||||
// 写入帧
|
||||
cv::Mat frame = ...;
|
||||
output_manager.writeFrames("camera1", frame);
|
||||
|
||||
// 更新映射关系
|
||||
std::vector<std::string> new_targets = {"output1"};
|
||||
output_manager.updateSourceTargets("camera1", new_targets);
|
||||
|
||||
// 检查映射状态
|
||||
if (output_manager.hasSourceMapping("camera1")) {
|
||||
std::vector<std::string> mapped_targets;
|
||||
output_manager.getSourceTargets("camera1", mapped_targets);
|
||||
// 处理映射目标...
|
||||
}
|
||||
|
||||
// 获取反向映射
|
||||
std::vector<std::string> sources = output_manager.getMappedSources("output1");
|
||||
for (const auto& source : sources) {
|
||||
// 处理映射源...
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
output_manager.cleanup();
|
||||
```
|
||||
|
||||
#### 错误处理
|
||||
|
||||
1. 映射错误
|
||||
- 目标不存在:添加映射时会返回 false
|
||||
- 源不存在:会创建新的映射关系
|
||||
- 重复映射:会更新现有映射
|
||||
|
||||
2. 资源管理
|
||||
- 移除目标时会自动清理相关映射
|
||||
- 清理时会释放所有资源
|
||||
- 异常安全:保证资源正确释放
|
||||
|
||||
3. 线程安全
|
||||
- 所有公共接口都是线程安全的
|
||||
- 支持多线程并发访问
|
||||
- 内部使用互斥锁保护
|
||||
|
||||
#### 性能考虑
|
||||
|
||||
1. 映射查找
|
||||
- 使用哈希表实现 O(1) 查找
|
||||
- 缓存常用映射关系
|
||||
- 优化内存使用
|
||||
|
||||
2. 并发处理
|
||||
- 读写锁分离
|
||||
- 最小化锁粒度
|
||||
- 避免锁竞争
|
||||
|
||||
3. 资源管理
|
||||
- 智能指针管理资源
|
||||
- 内存池复用
|
||||
- 延迟初始化
|
||||
|
||||
### 方法说明
|
||||
|
||||
#### 构造函数和析构函数
|
||||
|
||||
@ -9,8 +9,14 @@
|
||||
- 异步读取支持
|
||||
- 帧率控制
|
||||
- 帧队列管理
|
||||
- RTSP流读取(开发中)
|
||||
- 输入源管理(待开发)
|
||||
- RTSP流读取(已完成)
|
||||
- 自动重连机制
|
||||
- 缓冲区管理
|
||||
- 超时处理
|
||||
- 输入源管理(已完成)
|
||||
- 多路输入支持
|
||||
- 输出目标映射
|
||||
- 资源管理
|
||||
|
||||
### 1.2 推理模块 (Inference)
|
||||
- TensorRT引擎管理(已完成)
|
||||
@ -26,9 +32,18 @@
|
||||
- 测试模式支持(已完成)
|
||||
|
||||
### 1.4 输出模块 (Output)
|
||||
- RTSP推流(待开发)
|
||||
- 视频文件保存(待开发)
|
||||
- 输出参数配置(待开发)
|
||||
- RTSP推流(已完成)
|
||||
- H264/H265编码支持
|
||||
- 可配置推流参数
|
||||
- 自动重连机制
|
||||
- 视频文件保存(已完成)
|
||||
- 多格式支持
|
||||
- 可配置编码参数
|
||||
- 自动目录管理
|
||||
- 输出管理(已完成)
|
||||
- 多路输出支持
|
||||
- 输入源映射
|
||||
- 资源管理
|
||||
|
||||
## 2. 核心类设计
|
||||
|
||||
@ -40,8 +55,11 @@ classDiagram
|
||||
Pipeline --> OutputManager
|
||||
InputManager --> RtspReader
|
||||
InputManager --> VideoReader
|
||||
InputManager --> SourceTargetMapper
|
||||
OutputManager --> RtspWriter
|
||||
OutputManager --> VideoWriter
|
||||
OutputManager --> TargetSourceMapper
|
||||
SourceTargetMapper --> TargetSourceMapper
|
||||
```
|
||||
|
||||
## 3. 数据流
|
||||
@ -52,22 +70,64 @@ graph LR
|
||||
B --> C[推理]
|
||||
C --> D[后处理]
|
||||
D --> E[渲染]
|
||||
E --> F[输出]
|
||||
E --> F[输出映射]
|
||||
F --> G1[输出1]
|
||||
F --> G2[输出2]
|
||||
F --> G3[输出3]
|
||||
|
||||
subgraph GPU Memory
|
||||
B
|
||||
C
|
||||
D
|
||||
end
|
||||
|
||||
subgraph Output Mapping
|
||||
F
|
||||
G1
|
||||
G2
|
||||
G3
|
||||
end
|
||||
```
|
||||
|
||||
## 4. 性能优化
|
||||
## 4. 输入输出映射设计
|
||||
|
||||
### 4.1 映射关系
|
||||
- 一对一映射:一个输入源对应一个输出目标
|
||||
- 一对多映射:一个输入源对应多个输出目标
|
||||
- 多对一映射:多个输入源对应同一个输出目标
|
||||
- 默认映射:未指定映射时使用所有输出目标
|
||||
|
||||
### 4.2 配置管理
|
||||
- YAML配置文件定义映射关系
|
||||
- 运行时动态更新映射
|
||||
- 支持热重载配置
|
||||
|
||||
### 4.3 资源管理
|
||||
- 智能指针管理资源生命周期
|
||||
- 线程安全的资源访问
|
||||
- 自动清理无效映射
|
||||
|
||||
### 4.4 性能优化
|
||||
- 哈希表实现快速查找
|
||||
- 读写锁分离
|
||||
- 缓存常用映射关系
|
||||
|
||||
### 4.5 错误处理
|
||||
- 配置验证
|
||||
- 运行时错误恢复
|
||||
- 详细的错误日志
|
||||
|
||||
## 5. 性能优化
|
||||
|
||||
### 已实现优化
|
||||
- CUDA流水线
|
||||
- 异步读取
|
||||
- 零拷贝传输
|
||||
- 批处理基础支持
|
||||
- 输入输出映射优化
|
||||
- 哈希表实现
|
||||
- 读写锁分离
|
||||
- 映射关系缓存
|
||||
|
||||
### 待实现优化
|
||||
- 动态批处理
|
||||
@ -75,26 +135,30 @@ graph LR
|
||||
- 多流并行
|
||||
- 跨模块流水线
|
||||
|
||||
## 5. 配置系统
|
||||
## 6. 配置系统
|
||||
|
||||
### 已完成功能
|
||||
- YAML配置解析
|
||||
- 配置验证
|
||||
- 运行时参数调整
|
||||
- 渲染样式配置
|
||||
- 输入输出映射配置
|
||||
- 映射关系定义
|
||||
- 动态更新支持
|
||||
- 配置验证
|
||||
|
||||
### 待完成功能
|
||||
- 多路输入配置
|
||||
- 输出参数配置
|
||||
- 动态参数调整
|
||||
- 配置热重载
|
||||
|
||||
## 6. 开发进度
|
||||
## 7. 开发进度
|
||||
|
||||
### 已完成模块
|
||||
1. 配置系统 (100%)
|
||||
- YAML解析器
|
||||
- 配置验证
|
||||
- 参数管理
|
||||
- 映射配置支持
|
||||
|
||||
2. 推理模块 (100%)
|
||||
- TensorRT引擎
|
||||
@ -108,42 +172,41 @@ graph LR
|
||||
- 性能显示
|
||||
- 测试支持
|
||||
|
||||
4. 输入模块 (90%)
|
||||
4. 输入模块 (100%)
|
||||
- 视频读取器
|
||||
- 帧队列
|
||||
- 异步支持
|
||||
- RTSP读取器
|
||||
- 输入源管理
|
||||
- 输出目标映射
|
||||
|
||||
5. 输出模块 (100%)
|
||||
- 视频写入器
|
||||
- RTSP推流器
|
||||
- 输出管理器
|
||||
- 输入源映射
|
||||
|
||||
### 开发中模块
|
||||
1. 输入模块扩展
|
||||
- RTSP支持
|
||||
- 多路输入
|
||||
|
||||
2. 输出模块
|
||||
- 视频保存
|
||||
- RTSP推流
|
||||
|
||||
### 待开发功能
|
||||
1. 系统集成
|
||||
- Pipeline实现
|
||||
- 模块协调
|
||||
- 性能优化
|
||||
|
||||
2. 测试完善
|
||||
### 待开发功能
|
||||
1. 测试完善
|
||||
- 系统测试
|
||||
- 性能测试
|
||||
- 压力测试
|
||||
|
||||
## 7. 后续计划
|
||||
## 8. 后续计划
|
||||
|
||||
### 短期目标
|
||||
- 完成RTSP输入支持
|
||||
- 实现基础输出功能
|
||||
- 完善系统测试
|
||||
- 优化性能指标
|
||||
- 完善错误处理
|
||||
|
||||
### 中期目标
|
||||
- 实现完整输出模块
|
||||
- 优化性能
|
||||
- 完善文档
|
||||
- 实现配置热重载
|
||||
- 优化内存使用
|
||||
- 完善监控系统
|
||||
|
||||
### 长期目标
|
||||
- 系统集成
|
||||
|
||||
@ -29,6 +29,20 @@
|
||||
- 批处理超时设置
|
||||
- 帧积累策略
|
||||
|
||||
### 2.4 输入输出映射优化
|
||||
- 映射关系管理
|
||||
- 使用哈希表实现 O(1) 查找
|
||||
- 缓存常用映射关系
|
||||
- 避免频繁修改映射
|
||||
- 并发访问优化
|
||||
- 读写锁分离
|
||||
- 细粒度锁定
|
||||
- 避免长时间持锁
|
||||
- 资源管理优化
|
||||
- 智能指针管理生命周期
|
||||
- 内存池复用
|
||||
- 延迟初始化策略
|
||||
|
||||
## 3. 推理优化
|
||||
|
||||
### 3.1 TensorRT优化
|
||||
@ -53,6 +67,20 @@
|
||||
- 推流缓冲
|
||||
- 写入策略
|
||||
|
||||
### 4.3 映射关系优化
|
||||
- 合理配置映射关系
|
||||
- 避免不必要的多重映射
|
||||
- 根据实际需求设置映射
|
||||
- 定期清理无效映射
|
||||
- 输出目标管理
|
||||
- 复用输出目标
|
||||
- 动态调整目标参数
|
||||
- 按需初始化资源
|
||||
- 性能监控
|
||||
- 监控映射状态
|
||||
- 跟踪资源使用
|
||||
- 分析性能瓶颈
|
||||
|
||||
## 5. 监控与调试
|
||||
|
||||
### 5.1 性能指标
|
||||
@ -66,6 +94,214 @@
|
||||
- 内存泄漏检测
|
||||
- 性能瓶颈定位
|
||||
|
||||
### 5.3 映射性能监控
|
||||
- 映射关系统计
|
||||
```cpp
|
||||
// 获取映射统计信息
|
||||
size_t total_sources = input_manager.getAllSourceNames().size();
|
||||
size_t total_targets = output_manager.getTargetCount();
|
||||
|
||||
// 检查映射效率
|
||||
for (const auto& source : input_manager.getAllSourceNames()) {
|
||||
std::vector<std::string> targets;
|
||||
input_manager.getOutputTargets(source, targets);
|
||||
// 分析映射数量和资源使用
|
||||
}
|
||||
```
|
||||
|
||||
- 资源使用监控
|
||||
```cpp
|
||||
// 监控输出目标状态
|
||||
for (const auto& target : output_manager.getTargetNames()) {
|
||||
std::string error_msg;
|
||||
bool status = output_manager.getTargetStatus(target, error_msg);
|
||||
// 记录状态和错误信息
|
||||
}
|
||||
```
|
||||
|
||||
- 性能指标收集
|
||||
```cpp
|
||||
// 收集写入性能指标
|
||||
struct PerformanceMetrics {
|
||||
double avg_write_time;
|
||||
double max_write_time;
|
||||
size_t total_frames;
|
||||
size_t dropped_frames;
|
||||
};
|
||||
|
||||
// 定期更新和分析性能指标
|
||||
void updateMetrics(const std::string& target, double write_time) {
|
||||
metrics[target].avg_write_time =
|
||||
(metrics[target].avg_write_time * metrics[target].total_frames + write_time) /
|
||||
(metrics[target].total_frames + 1);
|
||||
metrics[target].max_write_time = std::max(metrics[target].max_write_time, write_time);
|
||||
metrics[target].total_frames++;
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 映射性能优化建议
|
||||
|
||||
### 6.1 配置优化
|
||||
|
||||
1. 映射关系设计
|
||||
- 避免创建不必要的映射
|
||||
- 合理分组输出目标
|
||||
- 根据实际需求配置映射
|
||||
|
||||
2. 缓冲区设置
|
||||
- 为不同类型的输出配置适当的缓冲区
|
||||
- 考虑输出目标的特性
|
||||
- 平衡内存使用和性能
|
||||
|
||||
3. 参数调优
|
||||
- 根据输出目标类型设置合适的参数
|
||||
- 考虑网络条件和系统资源
|
||||
- 动态调整参数以适应变化
|
||||
|
||||
### 6.2 代码优化
|
||||
|
||||
1. 映射查找优化
|
||||
```cpp
|
||||
// 使用哈希表存储映射关系
|
||||
std::unordered_map<std::string, std::vector<std::string>> source_target_map;
|
||||
|
||||
// 缓存常用映射
|
||||
std::unordered_map<std::string, std::vector<std::string>> mapping_cache;
|
||||
|
||||
// 快速查找实现
|
||||
bool getTargets(const std::string& source, std::vector<std::string>& targets) {
|
||||
auto it = mapping_cache.find(source);
|
||||
if (it != mapping_cache.end()) {
|
||||
targets = it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto map_it = source_target_map.find(source);
|
||||
if (map_it != source_target_map.end()) {
|
||||
targets = map_it->second;
|
||||
mapping_cache[source] = targets;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
2. 并发访问优化
|
||||
```cpp
|
||||
// 使用读写锁
|
||||
mutable std::shared_mutex mapping_mutex;
|
||||
|
||||
// 读取映射关系
|
||||
bool getTargets(const std::string& source, std::vector<std::string>& targets) const {
|
||||
std::shared_lock<std::shared_mutex> lock(mapping_mutex);
|
||||
auto it = source_target_map.find(source);
|
||||
if (it != source_target_map.end()) {
|
||||
targets = it->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新映射关系
|
||||
bool updateMapping(const std::string& source, const std::vector<std::string>& targets) {
|
||||
std::unique_lock<std::shared_mutex> lock(mapping_mutex);
|
||||
source_target_map[source] = targets;
|
||||
mapping_cache.erase(source); // 清除缓存
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
3. 资源管理优化
|
||||
```cpp
|
||||
// 使用智能指针管理资源
|
||||
class OutputTarget {
|
||||
public:
|
||||
using Ptr = std::shared_ptr<OutputTarget>;
|
||||
static Ptr create(const OutputTargetConfig& config) {
|
||||
return std::make_shared<OutputTarget>(config);
|
||||
}
|
||||
};
|
||||
|
||||
// 资源池管理
|
||||
class ResourcePool {
|
||||
public:
|
||||
OutputTarget::Ptr acquireTarget(const std::string& name) {
|
||||
std::lock_guard<std::mutex> lock(pool_mutex);
|
||||
auto it = available_targets.find(name);
|
||||
if (it != available_targets.end()) {
|
||||
auto target = it->second;
|
||||
available_targets.erase(it);
|
||||
return target;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void releaseTarget(const std::string& name, OutputTarget::Ptr target) {
|
||||
std::lock_guard<std::mutex> lock(pool_mutex);
|
||||
available_targets[name] = target;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex pool_mutex;
|
||||
std::unordered_map<std::string, OutputTarget::Ptr> available_targets;
|
||||
};
|
||||
```
|
||||
|
||||
### 6.3 性能监控
|
||||
|
||||
1. 映射效率监控
|
||||
```cpp
|
||||
struct MappingMetrics {
|
||||
size_t mapping_updates;
|
||||
size_t cache_hits;
|
||||
size_t cache_misses;
|
||||
double avg_lookup_time;
|
||||
};
|
||||
|
||||
void updateMappingMetrics(const std::string& source, bool cache_hit, double lookup_time) {
|
||||
auto& metrics = mapping_metrics[source];
|
||||
metrics.mapping_updates++;
|
||||
cache_hit ? metrics.cache_hits++ : metrics.cache_misses++;
|
||||
metrics.avg_lookup_time =
|
||||
(metrics.avg_lookup_time * (metrics.cache_hits + metrics.cache_misses - 1) + lookup_time) /
|
||||
(metrics.cache_hits + metrics.cache_misses);
|
||||
}
|
||||
```
|
||||
|
||||
2. 资源使用监控
|
||||
```cpp
|
||||
struct ResourceMetrics {
|
||||
size_t active_targets;
|
||||
size_t total_memory;
|
||||
size_t peak_memory;
|
||||
double avg_utilization;
|
||||
};
|
||||
|
||||
void updateResourceMetrics(const std::string& target, size_t memory_usage) {
|
||||
auto& metrics = resource_metrics[target];
|
||||
metrics.total_memory = memory_usage;
|
||||
metrics.peak_memory = std::max(metrics.peak_memory, memory_usage);
|
||||
// 更新其他指标...
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 常见问题解决
|
||||
|
||||
1. 映射性能问题
|
||||
- 检查映射关系是否合理
|
||||
- 优化缓存策略
|
||||
- 调整锁粒度
|
||||
|
||||
2. 资源使用问题
|
||||
- 监控内存使用
|
||||
- 及时释放资源
|
||||
- 优化资源池配置
|
||||
|
||||
3. 并发访问问题
|
||||
- 使用适当的锁策略
|
||||
- 减少锁的持有时间
|
||||
- 考虑无锁实现
|
||||
|
||||
## RTSP 流读取优化
|
||||
|
||||
### 延迟与流畅度平衡
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <opencv2/core.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace pipeline {
|
||||
|
||||
@ -51,118 +52,159 @@ bool YamlConfigParser::parse(const std::string& config_file) {
|
||||
bool YamlConfigParser::validate() {
|
||||
// 基本验证
|
||||
if (config_.input.sources.empty()) {
|
||||
std::cerr << "Error: No input sources configured" << std::endl;
|
||||
Logger::error("No input sources configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证输入源配置
|
||||
// 验证输入源名称唯一性
|
||||
std::unordered_set<std::string> source_names;
|
||||
for (const auto& source : config_.input.sources) {
|
||||
if (!source_names.insert(source.name).second) {
|
||||
Logger::error("Duplicate input source name: " + source.name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证输出目标名称唯一性
|
||||
std::unordered_set<std::string> target_names;
|
||||
for (const auto& target : config_.output.targets) {
|
||||
if (!target_names.insert(target.name).second) {
|
||||
Logger::error("Duplicate output target name: " + target.name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 收集所有有效的输出目标名称
|
||||
std::unordered_set<std::string> valid_output_names;
|
||||
for (const auto& target : config_.output.targets) {
|
||||
valid_output_names.insert(target.name);
|
||||
}
|
||||
|
||||
// 验证输入源配置
|
||||
for (auto& source : config_.input.sources) {
|
||||
if (source.type.empty() || source.name.empty()) {
|
||||
std::cerr << "Error: Input source missing type or name" << std::endl;
|
||||
Logger::error("Input source missing type or name");
|
||||
return false;
|
||||
}
|
||||
if (source.type == "rtsp" && source.url.empty()) {
|
||||
std::cerr << "Error: RTSP source missing URL" << std::endl;
|
||||
Logger::error("RTSP source missing URL");
|
||||
return false;
|
||||
}
|
||||
if (source.buffer_size <= 0) {
|
||||
std::cerr << "Error: Invalid buffer size for source: " << source.name << std::endl;
|
||||
Logger::error("Invalid buffer size for source: " + source.name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果没有指定输出目标,使用所有可用的输出目标
|
||||
if (source.outputs.empty() && !valid_output_names.empty()) {
|
||||
Logger::info("No output targets specified for source '" + source.name + "', using all available targets");
|
||||
source.outputs.insert(source.outputs.end(),
|
||||
valid_output_names.begin(),
|
||||
valid_output_names.end());
|
||||
}
|
||||
// 验证输出目标映射
|
||||
else if (!source.outputs.empty()) {
|
||||
for (const auto& output : source.outputs) {
|
||||
if (valid_output_names.find(output) == valid_output_names.end()) {
|
||||
Logger::error("Invalid output target '" + output + "' specified for input source '" + source.name + "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证推理配置
|
||||
if (config_.inference.engine_path.empty()) {
|
||||
std::cerr << "Error: Model engine path not specified" << std::endl;
|
||||
Logger::error("Model engine path not specified");
|
||||
return false;
|
||||
}
|
||||
if (config_.inference.input_shape.empty()) {
|
||||
std::cerr << "Error: Model input shape not specified" << std::endl;
|
||||
Logger::error("Model input shape not specified");
|
||||
return false;
|
||||
}
|
||||
if (config_.inference.threshold.conf < 0.0f || config_.inference.threshold.conf > 1.0f) {
|
||||
std::cerr << "Error: Invalid confidence threshold (must be between 0.0 and 1.0)" << std::endl;
|
||||
Logger::error("Invalid confidence threshold (must be between 0.0 and 1.0)");
|
||||
return false;
|
||||
}
|
||||
if (config_.inference.threshold.nms < 0.0f || config_.inference.threshold.nms > 1.0f) {
|
||||
std::cerr << "Error: Invalid NMS threshold (must be between 0.0 and 1.0)" << std::endl;
|
||||
Logger::error("Invalid NMS threshold (must be between 0.0 and 1.0)");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证渲染配置
|
||||
// 验证窗口配置
|
||||
if (config_.render.window.width <= 0 || config_.render.window.height <= 0) {
|
||||
std::cerr << "Error: Invalid window dimensions" << std::endl;
|
||||
Logger::error("Invalid window dimensions");
|
||||
return false;
|
||||
}
|
||||
if (config_.render.window.name.empty()) {
|
||||
std::cerr << "Error: Window name not specified" << std::endl;
|
||||
Logger::error("Window name not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证默认样式
|
||||
if (config_.render.default_style.transparency < 0.0f ||
|
||||
config_.render.default_style.transparency > 1.0f) {
|
||||
std::cerr << "Error: Invalid default style transparency (must be between 0.0 and 1.0)" << std::endl;
|
||||
Logger::error("Invalid default style transparency (must be between 0.0 and 1.0)");
|
||||
return false;
|
||||
}
|
||||
if (config_.render.default_style.box_thickness <= 0 ||
|
||||
config_.render.default_style.font_thickness <= 0) {
|
||||
std::cerr << "Error: Invalid default style thickness values" << std::endl;
|
||||
Logger::error("Invalid default style thickness values");
|
||||
return false;
|
||||
}
|
||||
if (config_.render.default_style.font_scale <= 0.0) {
|
||||
std::cerr << "Error: Invalid default style font scale" << std::endl;
|
||||
Logger::error("Invalid default style font scale");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证类别样式
|
||||
for (const auto& [class_name, style] : config_.render.class_styles) {
|
||||
if (class_name.empty()) {
|
||||
std::cerr << "Error: Empty class name in style configuration" << std::endl;
|
||||
Logger::error("Empty class name in style configuration");
|
||||
return false;
|
||||
}
|
||||
if (style.transparency < 0.0f || style.transparency > 1.0f) {
|
||||
std::cerr << "Error: Invalid transparency for class " << class_name << std::endl;
|
||||
Logger::error("Invalid transparency for class " + class_name);
|
||||
return false;
|
||||
}
|
||||
if (style.box_thickness <= 0 || style.font_thickness <= 0) {
|
||||
std::cerr << "Error: Invalid thickness values for class " << class_name << std::endl;
|
||||
Logger::error("Invalid thickness values for class " + class_name);
|
||||
return false;
|
||||
}
|
||||
if (style.font_scale <= 0.0) {
|
||||
std::cerr << "Error: Invalid font scale for class " << class_name << std::endl;
|
||||
Logger::error("Invalid font scale for class " + class_name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证性能指标配置
|
||||
if (config_.render.metrics.update_interval_ms <= 0) {
|
||||
std::cerr << "Error: Invalid metrics update interval" << std::endl;
|
||||
Logger::error("Invalid metrics update interval");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 验证输出配置
|
||||
for (const auto& target : config_.output.targets) {
|
||||
if (target.type.empty() || target.name.empty()) {
|
||||
std::cerr << "Error: Output target missing type or name" << std::endl;
|
||||
Logger::error("Output target missing type or name");
|
||||
return false;
|
||||
}
|
||||
if (target.type == "video") {
|
||||
if (target.path.empty()) {
|
||||
std::cerr << "Error: Video output target missing path" << std::endl;
|
||||
Logger::error("Video output target missing path");
|
||||
return false;
|
||||
}
|
||||
if (target.fps <= 0) {
|
||||
std::cerr << "Error: Invalid video output fps" << std::endl;
|
||||
Logger::error("Invalid video output fps");
|
||||
return false;
|
||||
}
|
||||
if (target.bitrate <= 0) {
|
||||
std::cerr << "Error: Invalid video output bitrate" << std::endl;
|
||||
Logger::error("Invalid video output bitrate");
|
||||
return false;
|
||||
}
|
||||
if (target.codec.empty()) {
|
||||
std::cerr << "Error: Video output codec not specified" << std::endl;
|
||||
Logger::error("Video output codec not specified");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -170,11 +212,11 @@ bool YamlConfigParser::validate() {
|
||||
|
||||
// 验证日志配置
|
||||
if (config_.log.level.empty()) {
|
||||
std::cerr << "Error: Log level not specified" << std::endl;
|
||||
Logger::error("Log level not specified");
|
||||
return false;
|
||||
}
|
||||
if (config_.log.save_path.empty()) {
|
||||
std::cerr << "Error: Log save path not specified" << std::endl;
|
||||
Logger::error("Log save path not specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -195,10 +237,17 @@ bool YamlConfigParser::parseInputConfig(const YAML::Node& node) {
|
||||
if (source["buffer_size"]) {
|
||||
src_config.buffer_size = source["buffer_size"].as<int>();
|
||||
}
|
||||
|
||||
// 解析输出目标列表
|
||||
if (source["outputs"]) {
|
||||
if (source["output_targets"]) {
|
||||
src_config.outputs = source["output_targets"].as<std::vector<std::string>>();
|
||||
}
|
||||
// 向后兼容:如果没有output_targets字段,检查旧的outputs字段
|
||||
else if (source["outputs"]) {
|
||||
src_config.outputs = source["outputs"].as<std::vector<std::string>>();
|
||||
}
|
||||
// 如果都没有指定,将在validate阶段添加所有可用的输出目标
|
||||
|
||||
config_.input.sources.push_back(src_config);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
|
||||
input:
|
||||
sources:
|
||||
- type: rtsp
|
||||
name: "camera1"
|
||||
url: "rtsp://10.0.0.17:8554/camera_test/2" # 实际的RTSP地址
|
||||
- type: video
|
||||
name: "test_video"
|
||||
url: "/app/test_data/videos/raw.mp4" # 测试视频路径
|
||||
buffer_size: 30
|
||||
output_targets: ["output_video"] # 指定输出目标,可以是一个或多个输出目标的名称列表
|
||||
max_batch_size: 4
|
||||
|
||||
inference:
|
||||
@ -22,7 +23,7 @@ inference:
|
||||
gpu_id: 0
|
||||
|
||||
render:
|
||||
enable: true # 控制是否启用渲染功能
|
||||
enable: false # 控制是否启用渲染功能
|
||||
window:
|
||||
name: "Detection Results"
|
||||
width: 1280
|
||||
@ -73,9 +74,9 @@ output:
|
||||
targets:
|
||||
- type: "video"
|
||||
name: "output_video"
|
||||
path: "/output/result.mp4" # 输出MP4文件路径
|
||||
path: "/tmp/output/result.mp4" # 修改输出MP4文件路径
|
||||
fps: 30
|
||||
codec: "h264" # 视频编码器
|
||||
codec: "h264" # 使用h264编码器(内部会使用avc1)
|
||||
bitrate: 4000000 # 4Mbps
|
||||
- type: "rtsp"
|
||||
name: "output_rtsp"
|
||||
|
||||
@ -13,7 +13,7 @@ InputManager::~InputManager() {
|
||||
clear();
|
||||
}
|
||||
|
||||
bool InputManager::addSource(const std::string& name, const RtspReader::Config& config, const std::string& url) {
|
||||
bool InputManager::addSource(const std::string& name, const RtspReader::Config& config, const std::string& url, const std::vector<std::string>& output_targets) {
|
||||
std::lock_guard<std::mutex> lock(sources_mutex_);
|
||||
|
||||
// 检查源是否已存在
|
||||
@ -32,10 +32,16 @@ bool InputManager::addSource(const std::string& name, const RtspReader::Config&
|
||||
status.is_connected = true;
|
||||
source_status_[name] = status;
|
||||
|
||||
// 创建帧队列
|
||||
source_queues_[name] = std::make_unique<FrameQueue>(config.buffer_size);
|
||||
|
||||
// 保存读取器和类型
|
||||
rtsp_sources_[name] = std::move(reader);
|
||||
source_types_[name] = SourceType::RTSP;
|
||||
|
||||
// 保存输出目标映射
|
||||
source_output_targets_[name] = output_targets;
|
||||
|
||||
// 创建并启动源线程
|
||||
active_threads_++;
|
||||
source_threads_[name] = std::make_unique<std::thread>(&InputManager::sourceThread, this, name);
|
||||
@ -43,7 +49,7 @@ bool InputManager::addSource(const std::string& name, const RtspReader::Config&
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputManager::addVideoSource(const std::string& name, const VideoReader::Config& config, const std::string& path) {
|
||||
bool InputManager::addVideoSource(const std::string& name, const VideoReader::Config& config, const std::string& path, const std::vector<std::string>& output_targets) {
|
||||
std::lock_guard<std::mutex> lock(sources_mutex_);
|
||||
|
||||
// 检查源是否已存在
|
||||
@ -69,6 +75,9 @@ bool InputManager::addVideoSource(const std::string& name, const VideoReader::Co
|
||||
video_sources_[name] = std::move(reader);
|
||||
source_types_[name] = SourceType::VIDEO;
|
||||
|
||||
// 保存输出目标映射
|
||||
source_output_targets_[name] = output_targets;
|
||||
|
||||
// 创建并启动源线程
|
||||
active_threads_++;
|
||||
source_threads_[name] = std::make_unique<std::thread>(&InputManager::videoSourceThread, this, name);
|
||||
@ -179,6 +188,15 @@ size_t InputManager::getSourceCount() const {
|
||||
return source_types_.size();
|
||||
}
|
||||
|
||||
std::vector<std::string> InputManager::getSourceOutputTargets(const std::string& name) const {
|
||||
std::lock_guard<std::mutex> lock(sources_mutex_);
|
||||
auto it = source_output_targets_.find(name);
|
||||
if (it != source_output_targets_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return std::vector<std::string>(); // 如果没有找到,返回空列表
|
||||
}
|
||||
|
||||
void InputManager::clear() {
|
||||
std::cout << "Starting to clear input manager..." << std::endl;
|
||||
|
||||
@ -214,6 +232,7 @@ void InputManager::clear() {
|
||||
source_types_.clear();
|
||||
source_status_.clear();
|
||||
source_queues_.clear();
|
||||
source_output_targets_.clear(); // 清理输出目标映射
|
||||
}
|
||||
|
||||
// 重置状态
|
||||
|
||||
@ -31,8 +31,10 @@ public:
|
||||
InputManager& operator=(const InputManager&) = delete;
|
||||
|
||||
// 添加源
|
||||
bool addSource(const std::string& name, const RtspReader::Config& config, const std::string& url);
|
||||
bool addVideoSource(const std::string& name, const VideoReader::Config& config, const std::string& path);
|
||||
bool addSource(const std::string& name, const RtspReader::Config& config,
|
||||
const std::string& url, const std::vector<std::string>& output_targets = {});
|
||||
bool addVideoSource(const std::string& name, const VideoReader::Config& config,
|
||||
const std::string& path, const std::vector<std::string>& output_targets = {});
|
||||
|
||||
// 移除源
|
||||
bool removeSource(const std::string& name);
|
||||
@ -49,6 +51,9 @@ public:
|
||||
// 获取源数量
|
||||
size_t getSourceCount() const;
|
||||
|
||||
// 获取源的输出目标
|
||||
std::vector<std::string> getSourceOutputTargets(const std::string& name) const;
|
||||
|
||||
// 清空所有源
|
||||
void clear();
|
||||
|
||||
@ -65,6 +70,7 @@ private:
|
||||
std::unordered_map<std::string, SourceType> source_types_;
|
||||
std::unordered_map<std::string, SourceStatus> source_status_;
|
||||
std::unordered_map<std::string, std::unique_ptr<FrameQueue>> source_queues_;
|
||||
std::unordered_map<std::string, std::vector<std::string>> source_output_targets_;
|
||||
std::atomic<bool> running_{true};
|
||||
std::atomic<int> active_threads_{0};
|
||||
|
||||
|
||||
@ -107,7 +107,7 @@ bool VideoWriter::validateConfig(const OutputTargetConfig& config) {
|
||||
}
|
||||
|
||||
// 尝试打开文件进行写入
|
||||
std::ofstream test_write(path, std::ios::app);
|
||||
std::ofstream test_write(path);
|
||||
if (!test_write) {
|
||||
Logger::info("Cannot open existing file for writing");
|
||||
setLastError("Cannot open existing file for writing: " + path.string());
|
||||
@ -222,15 +222,20 @@ bool VideoWriter::openWriter(const cv::Size& size) {
|
||||
int fourcc = getFourcc(codec_);
|
||||
|
||||
try {
|
||||
writer_.open(output_path_, fourcc, fps_, size);
|
||||
// 设置临时文件路径
|
||||
std::string temp_path = output_path_ + ".tmp";
|
||||
|
||||
// 打开临时文件
|
||||
writer_.open(temp_path, fourcc, fps_, size, true); // 添加isColor参数
|
||||
if (!writer_.isOpened()) {
|
||||
setLastError("Failed to open video writer: " + output_path_);
|
||||
setLastError("Failed to open video writer: " + temp_path);
|
||||
is_opened_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置最高质量
|
||||
writer_.set(cv::VIDEOWRITER_PROP_QUALITY, 100);
|
||||
// 设置编码器参数
|
||||
writer_.set(cv::VIDEOWRITER_PROP_QUALITY, 100); // 最高质量
|
||||
|
||||
is_opened_ = true;
|
||||
return true;
|
||||
} catch (const cv::Exception& e) {
|
||||
@ -262,7 +267,16 @@ bool VideoWriter::write(const cv::Mat& frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
writer_.write(frame);
|
||||
// 确保帧格式正确
|
||||
cv::Mat write_frame;
|
||||
if (frame.channels() != 3) {
|
||||
cv::cvtColor(frame, write_frame, cv::COLOR_GRAY2BGR);
|
||||
} else {
|
||||
write_frame = frame;
|
||||
}
|
||||
|
||||
// 写入帧
|
||||
writer_.write(write_frame);
|
||||
return true;
|
||||
} catch (const cv::Exception& e) {
|
||||
setLastError("OpenCV error: " + std::string(e.what()));
|
||||
@ -282,6 +296,16 @@ void VideoWriter::release() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (writer_.isOpened()) {
|
||||
writer_.release();
|
||||
|
||||
// 如果使用了临时文件,在这里重命名
|
||||
std::string temp_path = output_path_ + ".tmp";
|
||||
if (std::filesystem::exists(temp_path)) {
|
||||
try {
|
||||
std::filesystem::rename(temp_path, output_path_);
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Failed to rename temporary file: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
is_initialized_ = false;
|
||||
is_opened_ = false;
|
||||
@ -300,16 +324,16 @@ bool VideoWriter::getStatus(std::string& error_msg) const {
|
||||
|
||||
int VideoWriter::getFourcc(const std::string& codec) const {
|
||||
if (codec == "h264") {
|
||||
return cv::VideoWriter::fourcc('H','2','6','4');
|
||||
return cv::VideoWriter::fourcc('a','v','c','1'); // 使用avc1代替H264
|
||||
} else if (codec == "mp4v") {
|
||||
return cv::VideoWriter::fourcc('M','P','4','V');
|
||||
return cv::VideoWriter::fourcc('m','p','4','v');
|
||||
} else if (codec == "mjpg") {
|
||||
return cv::VideoWriter::fourcc('M','J','P','G');
|
||||
} else if (codec == "xvid") {
|
||||
return cv::VideoWriter::fourcc('X','V','I','D');
|
||||
} else {
|
||||
Logger::warning("Unknown codec: " + codec + ", using default H264");
|
||||
return cv::VideoWriter::fourcc('H','2','6','4');
|
||||
Logger::warning("Unknown codec: " + codec + ", using default avc1");
|
||||
return cv::VideoWriter::fourcc('a','v','c','1');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -181,3 +181,145 @@ TEST(ConfigParserTest, RenderStyleLabelMapping) {
|
||||
// 验证未配置样式的标签使用默认样式
|
||||
EXPECT_EQ(config.render.class_styles.count("truck"), 0);
|
||||
}
|
||||
|
||||
// 测试输入输出映射配置
|
||||
TEST(ConfigParserTest, InputOutputMapping) {
|
||||
// 创建配置解析器
|
||||
auto parser = createConfigParser();
|
||||
|
||||
// 创建测试配置文件
|
||||
std::ofstream config_file("test_mapping.yaml");
|
||||
config_file << R"(
|
||||
input:
|
||||
sources:
|
||||
- type: video
|
||||
name: "video1"
|
||||
url: "/test/video1.mp4"
|
||||
buffer_size: 30
|
||||
output_targets: ["output1"]
|
||||
- type: rtsp
|
||||
name: "camera1"
|
||||
url: "rtsp://test/stream1"
|
||||
buffer_size: 30
|
||||
output_targets: ["output2", "stream1"]
|
||||
max_batch_size: 4
|
||||
|
||||
output:
|
||||
targets:
|
||||
- type: "video"
|
||||
name: "output1"
|
||||
path: "/output/video1.mp4"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
- type: "video"
|
||||
name: "output2"
|
||||
path: "/output/video2.mp4"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
- type: "rtsp"
|
||||
name: "stream1"
|
||||
path: "rtsp://localhost:8554/live"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
)";
|
||||
config_file.close();
|
||||
|
||||
// 解析配置文件
|
||||
ASSERT_TRUE(parser->parse("test_mapping.yaml"));
|
||||
ASSERT_TRUE(parser->validate());
|
||||
|
||||
const auto& config = parser->getConfig();
|
||||
|
||||
// 验证输入源配置
|
||||
ASSERT_EQ(config.input.sources.size(), 2);
|
||||
|
||||
// 验证第一个输入源的输出映射
|
||||
const auto& source1 = config.input.sources[0];
|
||||
EXPECT_EQ(source1.name, "video1");
|
||||
ASSERT_EQ(source1.outputs.size(), 1);
|
||||
EXPECT_EQ(source1.outputs[0], "output1");
|
||||
|
||||
// 验证第二个输入源的输出映射
|
||||
const auto& source2 = config.input.sources[1];
|
||||
EXPECT_EQ(source2.name, "camera1");
|
||||
ASSERT_EQ(source2.outputs.size(), 2);
|
||||
EXPECT_EQ(source2.outputs[0], "output2");
|
||||
EXPECT_EQ(source2.outputs[1], "stream1");
|
||||
|
||||
// 验证输出目标配置
|
||||
ASSERT_EQ(config.output.targets.size(), 3);
|
||||
|
||||
// 清理测试文件
|
||||
std::filesystem::remove("test_mapping.yaml");
|
||||
}
|
||||
|
||||
// 测试无效的输出目标映射
|
||||
TEST(ConfigParserTest, InvalidOutputMapping) {
|
||||
auto parser = createConfigParser();
|
||||
|
||||
// 创建测试配置文件,包含无效的输出目标
|
||||
std::ofstream config_file("invalid_mapping.yaml");
|
||||
config_file << R"(
|
||||
input:
|
||||
sources:
|
||||
- type: video
|
||||
name: "video1"
|
||||
url: "/test/video1.mp4"
|
||||
output_targets: ["nonexistent_output"] # 不存在的输出目标
|
||||
max_batch_size: 4
|
||||
|
||||
output:
|
||||
targets:
|
||||
- type: "video"
|
||||
name: "output1"
|
||||
path: "/output/video1.mp4"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
)";
|
||||
config_file.close();
|
||||
|
||||
// 解析配置文件应该成功,但验证应该失败
|
||||
ASSERT_TRUE(parser->parse("invalid_mapping.yaml"));
|
||||
ASSERT_FALSE(parser->validate()); // 验证应该失败,因为输出目标不存在
|
||||
|
||||
// 清理测试文件
|
||||
std::filesystem::remove("invalid_mapping.yaml");
|
||||
}
|
||||
|
||||
// 测试向后兼容性
|
||||
TEST(ConfigParserTest, BackwardCompatibility) {
|
||||
auto parser = createConfigParser();
|
||||
|
||||
// 创建使用旧格式的测试配置文件
|
||||
std::ofstream config_file("old_format.yaml");
|
||||
config_file << R"(
|
||||
input:
|
||||
sources:
|
||||
- type: video
|
||||
name: "video1"
|
||||
url: "/test/video1.mp4"
|
||||
outputs: ["output1"] # 旧格式使用outputs而不是output_targets
|
||||
max_batch_size: 4
|
||||
|
||||
output:
|
||||
targets:
|
||||
- type: "video"
|
||||
name: "output1"
|
||||
path: "/output/video1.mp4"
|
||||
fps: 30
|
||||
codec: "h264"
|
||||
)";
|
||||
config_file.close();
|
||||
|
||||
// 解析和验证应该都成功
|
||||
ASSERT_TRUE(parser->parse("old_format.yaml"));
|
||||
ASSERT_TRUE(parser->validate());
|
||||
|
||||
const auto& config = parser->getConfig();
|
||||
ASSERT_EQ(config.input.sources.size(), 1);
|
||||
ASSERT_EQ(config.input.sources[0].outputs.size(), 1);
|
||||
EXPECT_EQ(config.input.sources[0].outputs[0], "output1");
|
||||
|
||||
// 清理测试文件
|
||||
std::filesystem::remove("old_format.yaml");
|
||||
}
|
||||
|
||||
@ -154,16 +154,16 @@ TEST_F(InputManagerTest, AddSource) {
|
||||
std::cout << "Starting AddSource test..." << std::endl;
|
||||
|
||||
// 添加有效源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
EXPECT_EQ(manager_.getSourceCount(), 1);
|
||||
|
||||
// 添加重复源
|
||||
EXPECT_FALSE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_FALSE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_EQ(manager_.getSourceCount(), 1);
|
||||
|
||||
// 添加另一个源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera2"));
|
||||
EXPECT_EQ(manager_.getSourceCount(), 2);
|
||||
|
||||
@ -175,7 +175,7 @@ TEST_F(InputManagerTest, RemoveSource) {
|
||||
std::cout << "Starting RemoveSource test..." << std::endl;
|
||||
|
||||
// 添加源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
EXPECT_EQ(manager_.getSourceCount(), 1);
|
||||
|
||||
@ -196,7 +196,7 @@ TEST_F(InputManagerTest, GetNextBatch) {
|
||||
std::vector<cv::Mat> frames;
|
||||
|
||||
// 添加源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
|
||||
// 等待一小段时间让帧积累
|
||||
@ -235,7 +235,7 @@ TEST_F(InputManagerTest, SourceStatus) {
|
||||
SourceStatus status;
|
||||
|
||||
// 添加源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
|
||||
// 获取状态
|
||||
@ -254,9 +254,9 @@ TEST_F(InputManagerTest, Clear) {
|
||||
std::cout << "Starting Clear test..." << std::endl;
|
||||
|
||||
// 添加多个源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera2"));
|
||||
EXPECT_EQ(manager_.getSourceCount(), 2);
|
||||
|
||||
@ -265,7 +265,7 @@ TEST_F(InputManagerTest, Clear) {
|
||||
EXPECT_EQ(manager_.getSourceCount(), 0);
|
||||
|
||||
// 检查是否可以重新添加源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
EXPECT_EQ(manager_.getSourceCount(), 1);
|
||||
|
||||
@ -277,9 +277,9 @@ TEST_F(InputManagerTest, GetSourceNames) {
|
||||
std::cout << "Starting GetSourceNames test..." << std::endl;
|
||||
|
||||
// 添加源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera2"));
|
||||
|
||||
// 获取名称列表
|
||||
@ -312,7 +312,7 @@ TEST_F(InputManagerTest, FrameRateControl) {
|
||||
video_config_.frame_timeout_ms = 100; // 增加超时时间
|
||||
|
||||
// 添加源
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, {}));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
|
||||
// 等待帧率稳定
|
||||
@ -334,6 +334,66 @@ TEST_F(InputManagerTest, FrameRateControl) {
|
||||
std::cout << "FrameRateControl test completed" << std::endl;
|
||||
}
|
||||
|
||||
// 测试输出目标映射
|
||||
TEST_F(InputManagerTest, OutputTargetMapping) {
|
||||
std::cout << "Starting OutputTargetMapping test..." << std::endl;
|
||||
|
||||
// 添加源时指定输出目标
|
||||
std::vector<std::string> output_targets = {"output1", "output2"};
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, output_targets));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
|
||||
// 验证输出目标映射
|
||||
auto targets = manager_.getSourceOutputTargets("camera1");
|
||||
EXPECT_EQ(targets.size(), 2);
|
||||
EXPECT_TRUE(std::find(targets.begin(), targets.end(), "output1") != targets.end());
|
||||
EXPECT_TRUE(std::find(targets.begin(), targets.end(), "output2") != targets.end());
|
||||
|
||||
// 测试不存在的源
|
||||
auto invalid_targets = manager_.getSourceOutputTargets("invalid_camera");
|
||||
EXPECT_TRUE(invalid_targets.empty());
|
||||
|
||||
// 添加源时不指定输出目标
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_));
|
||||
EXPECT_TRUE(waitForSourceStart("camera2"));
|
||||
auto empty_targets = manager_.getSourceOutputTargets("camera2");
|
||||
EXPECT_TRUE(empty_targets.empty());
|
||||
|
||||
std::cout << "OutputTargetMapping test completed" << std::endl;
|
||||
}
|
||||
|
||||
// 测试多源多目标映射
|
||||
TEST_F(InputManagerTest, MultipleSourceTargetMapping) {
|
||||
std::cout << "Starting MultipleSourceTargetMapping test..." << std::endl;
|
||||
|
||||
// 添加多个源,每个源映射到不同的输出目标
|
||||
std::vector<std::string> targets1 = {"output1", "output2"};
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, targets1));
|
||||
EXPECT_TRUE(waitForSourceStart("camera1"));
|
||||
|
||||
std::vector<std::string> targets2 = {"output2", "output3"};
|
||||
EXPECT_TRUE(manager_.addVideoSource("camera2", video_config_, test_video_path_, targets2));
|
||||
EXPECT_TRUE(waitForSourceStart("camera2"));
|
||||
|
||||
// 验证每个源的输出目标映射
|
||||
auto targets_camera1 = manager_.getSourceOutputTargets("camera1");
|
||||
EXPECT_EQ(targets_camera1.size(), 2);
|
||||
EXPECT_TRUE(std::find(targets_camera1.begin(), targets_camera1.end(), "output1") != targets_camera1.end());
|
||||
EXPECT_TRUE(std::find(targets_camera1.begin(), targets_camera1.end(), "output2") != targets_camera1.end());
|
||||
|
||||
auto targets_camera2 = manager_.getSourceOutputTargets("camera2");
|
||||
EXPECT_EQ(targets_camera2.size(), 2);
|
||||
EXPECT_TRUE(std::find(targets_camera2.begin(), targets_camera2.end(), "output2") != targets_camera2.end());
|
||||
EXPECT_TRUE(std::find(targets_camera2.begin(), targets_camera2.end(), "output3") != targets_camera2.end());
|
||||
|
||||
// 清理并验证映射是否被正确清除
|
||||
manager_.clear();
|
||||
EXPECT_TRUE(manager_.getSourceOutputTargets("camera1").empty());
|
||||
EXPECT_TRUE(manager_.getSourceOutputTargets("camera2").empty());
|
||||
|
||||
std::cout << "MultipleSourceTargetMapping test completed" << std::endl;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
|
||||
@ -21,11 +21,15 @@ TEST_F(YamlConfigTest, LoadConfig) {
|
||||
|
||||
// 验证输入配置
|
||||
ASSERT_EQ(config.input.sources.size(), 1);
|
||||
EXPECT_EQ(config.input.sources[0].type, "rtsp");
|
||||
EXPECT_EQ(config.input.sources[0].name, "camera1");
|
||||
EXPECT_EQ(config.input.sources[0].url, "rtsp://10.0.0.17:8554/camera_test/2");
|
||||
EXPECT_EQ(config.input.sources[0].type, "video");
|
||||
EXPECT_EQ(config.input.sources[0].name, "test_video");
|
||||
EXPECT_EQ(config.input.sources[0].url, "/app/test_data/videos/raw.mp4");
|
||||
EXPECT_EQ(config.input.sources[0].buffer_size, 30);
|
||||
EXPECT_EQ(config.input.max_batch_size, 4);
|
||||
|
||||
// 验证输入源的输出目标映射
|
||||
ASSERT_EQ(config.input.sources[0].outputs.size(), 1);
|
||||
EXPECT_EQ(config.input.sources[0].outputs[0], "output_video");
|
||||
|
||||
// 验证推理配置
|
||||
EXPECT_EQ(config.inference.engine_path, "/app/models/yolov8n.engine");
|
||||
|
||||
Loading…
Reference in New Issue
Block a user