diff --git a/TODO.md b/TODO.md index f077bf2..fe8f57f 100644 --- a/TODO.md +++ b/TODO.md @@ -2,8 +2,6 @@ ## 当前任务 (Doing) -- [ ] 验证 OpenCV 在 RK3588 容器内的调用 (/dev/video0) -- [ ] 接入摄像头设备 rtsp://admin:123456@192.168.1.57:554/stream0 - [ ] 接入 RKNN 模型 ## 待办 (To Do) @@ -17,3 +15,5 @@ - [x] 项目重命名 (EdgeProxy -> RoadCounter) - [x] 解决 Git 权限问题 - [x] 跑通 MQTT 上报基础流程 +- [x] 验证 OpenCV 在 RK3588 容器内的调用 (/dev/video0) +- [x] 接入摄像头设备 rtsp://admin:123456@192.168.1.57:554/stream0 diff --git a/data/test_car.mp4 b/data/test_car.mp4 new file mode 100644 index 0000000..08be912 Binary files /dev/null and b/data/test_car.mp4 differ diff --git a/src/main.cpp b/src/main.cpp index 20fe199..57e8be3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,7 +74,8 @@ void poll_system_metrics(boost::asio::steady_timer& timer, SystemMonitor::System int main(int argc, char* argv[]) { // TODO: [GYK] DEV#1: 将 URL 放入 config.json 中读取 - std::string cam_rtsp_input = "rtsp://admin:123456@192.168.1.57:554/stream0"; + // std::string cam_rtsp_input = "rtsp://admin:123456@192.168.1.57:554/stream0"; + std::string cam_rtsp_input = "../data/test_car.mp4"; // std::string cam_rtsp_input = // "rtsp://admin:hzx12345@192.168.1.10:554/Streaming/Channels/1901"; @@ -112,8 +113,6 @@ int main(int argc, char* argv[]) { AlarmService alarm_service(g_io_context, mqtt_client); - // VideoPipeline video_pipeline; - if (!alarm_service.load_rules(config.getAlarmRulesPath())) { spdlog::error("Failed to load alarm rules. Alarms may be disabled."); } @@ -180,7 +179,7 @@ int main(int argc, char* argv[]) { web_server.start(); spdlog::info("Starting Video Pipeline Service..."); - video_pipeline.Start(cam_rtsp_input, output_stream_url); + video_pipeline.StartTest(cam_rtsp_input, output_stream_url); // video_pipeline.Start(cam_rtsp_input, algorithm_rtsp_output); boost::asio::signal_set signals(g_io_context, SIGINT, SIGTERM); diff --git a/src/videoService/video_pipeline.cpp b/src/videoService/video_pipeline.cpp index 3a93364..cf7c305 100644 --- a/src/videoService/video_pipeline.cpp +++ b/src/videoService/video_pipeline.cpp @@ -14,7 +14,17 @@ void VideoPipeline::Start(const std::string& inputUrl, const std::string& output running_ = true; spdlog::info("Starting VideoPipeline with Input: {}", inputUrl); - processingThread_ = std::thread(&VideoPipeline::processLoop, this, inputUrl, outputUrl); + processingThread_ = std::thread(&VideoPipeline::processLoop, this, inputUrl, outputUrl, false); +} + +void VideoPipeline::StartTest(const std::string& filePath, const std::string& outputUrl) { + if (running_) + return; + running_ = true; + spdlog::info("Starting VideoPipeline (File Test Mode) Input: {}", filePath); + + // true 表示是文件源 + processingThread_ = std::thread(&VideoPipeline::processLoop, this, filePath, outputUrl, true); } void VideoPipeline::Stop() { @@ -42,7 +52,7 @@ std::vector VideoPipeline::mockInference(const cv::Mat& frame) res.y = 200; res.width = 150; res.height = 300; - res.label = "EV CAR"; + res.label = "TEST_CLIP"; res.confidence = 0.95f; results.push_back(res); @@ -61,38 +71,25 @@ void VideoPipeline::drawOverlay(cv::Mat& frame, const std::vector 1000 / 20 = 50ms + const double FRAME_DURATION_MS = 1000.0 / TARGET_FPS; int width = cap.get(cv::CAP_PROP_FRAME_WIDTH); int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT); - spdlog::info("Video Source: {}x{} | Input FPS: {} | Target Output FPS: {}", width, height, - cap.get(cv::CAP_PROP_FPS), TARGET_FPS); - - // === [MOD] 优化后的 GStreamer H.264 推流管道 === - // 1. appsrc: OpenCV 数据源 - // 2. videoconvert: 像素格式转换 - // 3. capsfilter: 强制转换为 NV12 (RK3588 编码器首选格式) 并 锁定 20/1 帧率 - // 4. mpph264enc: Rockchip 硬件 H.264 编码器 - // 5. h264parse: 解析 NALU,对 RTSP 传输至关重要 - // 6. rtspclientsink: 推流到 MediaMTX + spdlog::info("Source: {}x{} | Mode: {}", width, height, + isFileSource ? "FILE LOOP" : "LIVE STREAM"); std::stringstream pipeline; pipeline << "appsrc ! " @@ -101,35 +98,33 @@ void VideoPipeline::processLoop(std::string inputUrl, std::string outputUrl) { << ",framerate=20/1 ! " << "mpph264enc ! " << "h264parse ! " - << "rtspclientsink location=" << outputUrl - << " protocols=tcp"; // [MOD] 使用 TCP 协议推流更稳定 - - spdlog::debug("GStreamer Pipeline: {}", pipeline.str()); + << "rtspclientsink location=" << outputUrl << " protocols=tcp"; cv::VideoWriter writer; - // [MOD] 在 open 时传入 TARGET_FPS (20.0) writer.open(pipeline.str(), cv::CAP_GSTREAMER, 0, TARGET_FPS, cv::Size(width, height), true); if (!writer.isOpened()) { - spdlog::error("Failed to initialize VideoWriter. Check GStreamer plugins."); + spdlog::error("Failed to initialize VideoWriter."); } cv::Mat frame; - // [MOD] 帧率控制辅助 - // 如果摄像头实际输出稍微快于或慢于20帧,OpenCV的阻塞读取会自动对齐 - // 但如果源断流,我们需要处理 - while (running_) { - // [MOD] 记录时间以监测实际处理耗时 - auto start = std::chrono::steady_clock::now(); + // 记录循环开始时间 + auto loop_start = std::chrono::steady_clock::now(); if (!cap.read(frame)) { - spdlog::warn("Frame read failed. Reconnecting..."); - std::this_thread::sleep_for(std::chrono::seconds(1)); - cap.release(); - cap.open(inputUrl); - continue; + if (isFileSource) { + spdlog::info("End of file reached, looping..."); + cap.set(cv::CAP_PROP_POS_FRAMES, 0); + continue; + } else { + spdlog::warn("Frame read failed. Reconnecting..."); + std::this_thread::sleep_for(std::chrono::seconds(1)); + cap.release(); + cap.open(inputUrl); + continue; + } } if (frame.empty()) @@ -141,18 +136,22 @@ void VideoPipeline::processLoop(std::string inputUrl, std::string outputUrl) { // 2. 绘制叠加 drawOverlay(frame, results); - // 3. 硬件编码推流 + // 3. 推流 if (writer.isOpened()) { writer.write(frame); } - // 简单监控处理延迟 - auto end = std::chrono::steady_clock::now(); - std::chrono::duration elapsed = end - start; + if (isFileSource) { + auto loop_end = std::chrono::steady_clock::now(); + std::chrono::duration elapsed = loop_end - loop_start; - // 如果处理太快(例如只是简单的画框,几毫秒就完了), - // 这里的 cap.read 会自动阻塞等待下一帧,所以不需要手动 sleep。 - // 只要输入是 20fps,这个循环就会被输入流“带”着以 20fps 运行。 + double elapsed_ms = elapsed.count(); + double wait_ms = FRAME_DURATION_MS - elapsed_ms; + + if (wait_ms > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds((int)wait_ms)); + } + } } cap.release(); diff --git a/src/videoService/video_pipeline.hpp b/src/videoService/video_pipeline.hpp index 42832d0..f46dc00 100644 --- a/src/videoService/video_pipeline.hpp +++ b/src/videoService/video_pipeline.hpp @@ -25,12 +25,13 @@ public: // 启动视频流处理 void Start(const std::string& inputUrl, const std::string& outputUrl); + void StartTest(const std::string& filePath, const std::string& outputUrl); // 停止处理 void Stop(); private: - void processLoop(std::string inputUrl, std::string outputUrl); + void processLoop(std::string inputUrl, std::string outputUrl, bool isFileSource); // 占位算法函数:模拟推理 std::vector mockInference(const cv::Mat& frame);