Compare commits

..

4 Commits

13 changed files with 1260 additions and 705 deletions

786
README.md
View File

@ -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.00表示不透明
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. 确保所有依赖库正确安装

View File

@ -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

View File

@ -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. 资源管理
- 智能指针管理资源
- 内存池复用
- 延迟初始化
### 方法说明
#### 构造函数和析构函数

View File

@ -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输入支持
- 实现基础输出功能
- 完善系统测试
- 优化性能指标
- 完善错误处理
### 中期目标
- 实现完整输出模块
- 优化性能
- 完善文档
- 实现配置热重载
- 优化内存使用
- 完善监控系统
### 长期目标
- 系统集成

View File

@ -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 流读取优化
### 延迟与流畅度平衡

View File

@ -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);
}
}

View File

@ -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"

View File

@ -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(); // 清理输出目标映射
}
// 重置状态

View File

@ -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};

View File

@ -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');
}
}

View File

@ -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");
}

View File

@ -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();

View File

@ -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");