From 8ff04d10d5db2f8a1b7806a1cb55e2da72b10519 Mon Sep 17 00:00:00 2001 From: sladro Date: Tue, 24 Dec 2024 17:33:34 +0800 Subject: [PATCH] Remove deprecated config.yaml file and update README to reflect new configuration structure. Enhance input/output mapping functionality in InputManager and add validation for unique source and target names in YamlConfigParser. Update tests to cover new output target mapping features and ensure backward compatibility with previous configuration formats. --- README.md | 786 ++++++++----------------- config.yaml | 67 --- docs/api_reference.md | 324 +++++++++- docs/architecture.md | 123 +++- docs/performance_tuning.md | 236 ++++++++ pipeline/common/yaml_config_parser.cpp | 103 +++- pipeline/configs/pipeline.yaml | 13 +- pipeline/input/input_manager.cpp | 23 +- pipeline/input/input_manager.hpp | 10 +- pipeline/output/video_writer.cpp | 44 +- tests/test_config_parser.cpp | 142 +++++ tests/test_input_manager.cpp | 84 ++- tests/test_yaml_config.cpp | 10 +- 13 files changed, 1260 insertions(+), 705 deletions(-) delete mode 100644 config.yaml diff --git a/README.md b/README.md index cec1dd2..1a14481 100644 --- a/README.md +++ b/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 ` + - 推荐使用默认配置文件: + ```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 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. 确保所有依赖库正确安装 \ No newline at end of file diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 9ab86e8..0000000 --- a/config.yaml +++ /dev/null @@ -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 \ No newline at end of file diff --git a/docs/api_reference.md b/docs/api_reference.md index 08e4fc0..db8737e 100644 --- a/docs/api_reference.md +++ b/docs/api_reference.md @@ -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& target_names); + bool getOutputTargets(const std::string& source_name, + std::vector& target_names) const; + bool hasOutputTargets(const std::string& source_name) const; + bool clearOutputTargets(const std::string& source_name); + std::vector getAllSourceNames() const; }; ``` +#### 配置结构 + +```cpp +struct InputConfig { + std::string type; // "video" 或 "rtsp" + std::string url; // 输入源地址 + int buffer_size; // 缓冲区大小 + std::vector 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 new_targets = {"output1"}; +input_manager.setOutputTargets("camera1", new_targets); + +// 获取输出目标 +std::vector 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& target_names); - - // 移除输入源的映射 bool removeSourceMapping(const std::string& source_name); + bool getSourceTargets(const std::string& source_name, + std::vector& target_names) const; + bool updateSourceTargets(const std::string& source_name, + const std::vector& target_names); + bool hasSourceMapping(const std::string& source_name) const; + bool isTargetMapped(const std::string& target_name) const; + std::vector 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 targets = {"output1", "stream1"}; +output_manager.addSourceTargetMapping("camera1", targets); + +// 写入帧 +cv::Mat frame = ...; +output_manager.writeFrames("camera1", frame); + +// 更新映射关系 +std::vector new_targets = {"output1"}; +output_manager.updateSourceTargets("camera1", new_targets); + +// 检查映射状态 +if (output_manager.hasSourceMapping("camera1")) { + std::vector mapped_targets; + output_manager.getSourceTargets("camera1", mapped_targets); + // 处理映射目标... +} + +// 获取反向映射 +std::vector sources = output_manager.getMappedSources("output1"); +for (const auto& source : sources) { + // 处理映射源... +} + +// 清理资源 +output_manager.cleanup(); +``` + +#### 错误处理 + +1. 映射错误 + - 目标不存在:添加映射时会返回 false + - 源不存在:会创建新的映射关系 + - 重复映射:会更新现有映射 + +2. 资源管理 + - 移除目标时会自动清理相关映射 + - 清理时会释放所有资源 + - 异常安全:保证资源正确释放 + +3. 线程安全 + - 所有公共接口都是线程安全的 + - 支持多线程并发访问 + - 内部使用互斥锁保护 + +#### 性能考虑 + +1. 映射查找 + - 使用哈希表实现 O(1) 查找 + - 缓存常用映射关系 + - 优化内存使用 + +2. 并发处理 + - 读写锁分离 + - 最小化锁粒度 + - 避免锁竞争 + +3. 资源管理 + - 智能指针管理资源 + - 内存池复用 + - 延迟初始化 + ### 方法说明 #### 构造函数和析构函数 diff --git a/docs/architecture.md b/docs/architecture.md index af103fb..468cb40 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -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输入支持 -- 实现基础输出功能 - 完善系统测试 +- 优化性能指标 +- 完善错误处理 ### 中期目标 -- 实现完整输出模块 -- 优化性能 -- 完善文档 +- 实现配置热重载 +- 优化内存使用 +- 完善监控系统 ### 长期目标 - 系统集成 diff --git a/docs/performance_tuning.md b/docs/performance_tuning.md index 70542d1..4647d37 100644 --- a/docs/performance_tuning.md +++ b/docs/performance_tuning.md @@ -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 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> source_target_map; + + // 缓存常用映射 + std::unordered_map> mapping_cache; + + // 快速查找实现 + bool getTargets(const std::string& source, std::vector& 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& targets) const { + std::shared_lock 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& targets) { + std::unique_lock lock(mapping_mutex); + source_target_map[source] = targets; + mapping_cache.erase(source); // 清除缓存 + return true; + } + ``` + +3. 资源管理优化 + ```cpp + // 使用智能指针管理资源 + class OutputTarget { + public: + using Ptr = std::shared_ptr; + static Ptr create(const OutputTargetConfig& config) { + return std::make_shared(config); + } + }; + + // 资源池管理 + class ResourcePool { + public: + OutputTarget::Ptr acquireTarget(const std::string& name) { + std::lock_guard 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 lock(pool_mutex); + available_targets[name] = target; + } + + private: + std::mutex pool_mutex; + std::unordered_map 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 流读取优化 ### 延迟与流畅度平衡 diff --git a/pipeline/common/yaml_config_parser.cpp b/pipeline/common/yaml_config_parser.cpp index 00526fc..f5f5f98 100644 --- a/pipeline/common/yaml_config_parser.cpp +++ b/pipeline/common/yaml_config_parser.cpp @@ -3,6 +3,7 @@ #include #include #include +#include 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 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 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 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(); } + // 解析输出目标列表 - if (source["outputs"]) { + if (source["output_targets"]) { + src_config.outputs = source["output_targets"].as>(); + } + // 向后兼容:如果没有output_targets字段,检查旧的outputs字段 + else if (source["outputs"]) { src_config.outputs = source["outputs"].as>(); } + // 如果都没有指定,将在validate阶段添加所有可用的输出目标 + config_.input.sources.push_back(src_config); } } diff --git a/pipeline/configs/pipeline.yaml b/pipeline/configs/pipeline.yaml index e966492..92c0fcc 100644 --- a/pipeline/configs/pipeline.yaml +++ b/pipeline/configs/pipeline.yaml @@ -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" diff --git a/pipeline/input/input_manager.cpp b/pipeline/input/input_manager.cpp index a31b742..403ab73 100644 --- a/pipeline/input/input_manager.cpp +++ b/pipeline/input/input_manager.cpp @@ -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& output_targets) { std::lock_guard 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(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(&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& output_targets) { std::lock_guard 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(&InputManager::videoSourceThread, this, name); @@ -179,6 +188,15 @@ size_t InputManager::getSourceCount() const { return source_types_.size(); } +std::vector InputManager::getSourceOutputTargets(const std::string& name) const { + std::lock_guard lock(sources_mutex_); + auto it = source_output_targets_.find(name); + if (it != source_output_targets_.end()) { + return it->second; + } + return std::vector(); // 如果没有找到,返回空列表 +} + 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(); // 清理输出目标映射 } // 重置状态 diff --git a/pipeline/input/input_manager.hpp b/pipeline/input/input_manager.hpp index 28c4652..046bdf9 100644 --- a/pipeline/input/input_manager.hpp +++ b/pipeline/input/input_manager.hpp @@ -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& output_targets = {}); + bool addVideoSource(const std::string& name, const VideoReader::Config& config, + const std::string& path, const std::vector& output_targets = {}); // 移除源 bool removeSource(const std::string& name); @@ -49,6 +51,9 @@ public: // 获取源数量 size_t getSourceCount() const; + // 获取源的输出目标 + std::vector getSourceOutputTargets(const std::string& name) const; + // 清空所有源 void clear(); @@ -65,6 +70,7 @@ private: std::unordered_map source_types_; std::unordered_map source_status_; std::unordered_map> source_queues_; + std::unordered_map> source_output_targets_; std::atomic running_{true}; std::atomic active_threads_{0}; diff --git a/pipeline/output/video_writer.cpp b/pipeline/output/video_writer.cpp index 99cdca6..9e1a0bb 100644 --- a/pipeline/output/video_writer.cpp +++ b/pipeline/output/video_writer.cpp @@ -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 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'); } } diff --git a/tests/test_config_parser.cpp b/tests/test_config_parser.cpp index 0e2474a..74c0afd 100644 --- a/tests/test_config_parser.cpp +++ b/tests/test_config_parser.cpp @@ -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"); +} diff --git a/tests/test_input_manager.cpp b/tests/test_input_manager.cpp index 9dbb16e..1670e45 100644 --- a/tests/test_input_manager.cpp +++ b/tests/test_input_manager.cpp @@ -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 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 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 targets1 = {"output1", "output2"}; + EXPECT_TRUE(manager_.addVideoSource("camera1", video_config_, test_video_path_, targets1)); + EXPECT_TRUE(waitForSourceStart("camera1")); + + std::vector 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(); diff --git a/tests/test_yaml_config.cpp b/tests/test_yaml_config.cpp index 28f0f3f..c850bbe 100644 --- a/tests/test_yaml_config.cpp +++ b/tests/test_yaml_config.cpp @@ -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");