From 29ecc252212dfee7cfb48a3729e9f54765ddb4b2 Mon Sep 17 00:00:00 2001 From: GuanYuankai Date: Mon, 27 Oct 2025 02:27:05 +0000 Subject: [PATCH] =?UTF-8?q?webRTC=E7=AE=97=E6=B3=95=E6=97=A0=E5=BB=B6?= =?UTF-8?q?=E8=BF=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/rknn/video_service.cc | 109 ++++++++++++++++++++++++-------------- src/rknn/video_service.h | 34 ++++++------ 2 files changed, 86 insertions(+), 57 deletions(-) diff --git a/src/rknn/video_service.cc b/src/rknn/video_service.cc index 08cf2d3..967a496 100644 --- a/src/rknn/video_service.cc +++ b/src/rknn/video_service.cc @@ -45,7 +45,6 @@ VideoService::~VideoService() { bool VideoService::start() { - // (关键修改) rknnPool 的模板参数已更新 rknn_pool_ = std::make_unique>(model_path_.c_str(), thread_num_); if (rknn_pool_->init() != 0) { printf("rknnPool init fail!\n"); @@ -53,18 +52,15 @@ bool VideoService::start() { } printf("rknnPool init success.\n"); - // 2. 设置RTSP传输协议 setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1); printf("Set RTSP transport protocol to TCP\n"); - // 3. 初始化 VideoCapture capture_.open(input_url_, cv::CAP_FFMPEG); if (!capture_.isOpened()) { printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str()); return false; } - // 4. 获取输入视频的属性 frame_width_ = static_cast(capture_.get(cv::CAP_PROP_FRAME_WIDTH)); frame_height_ = static_cast(capture_.get(cv::CAP_PROP_FRAME_HEIGHT)); frame_fps_ = capture_.get(cv::CAP_PROP_FPS); @@ -73,14 +69,15 @@ bool VideoService::start() { printf("RTSP stream opened successfully! (%dx%d @ %.2f FPS)\n", frame_width_, frame_height_, frame_fps_); - std::string gst_pipeline = - "appsrc ! " - "video/x-raw,format=BGR ! " - "videoconvert ! " - "video/x-raw,format=NV12 ! " - "mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! " // (备注) 你可以根据需要调整 mpph265enc 参数 - "h265parse ! " - "rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp"; + std::string gst_pipeline = + "appsrc ! " + "queue max-size-buffers=2 leaky=downstream ! " + "video/x-raw,format=BGR ! " + "videoconvert ! " + "video/x-raw,format=NV12 ! " + "mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! " + "h265parse ! " + "rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp"; printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str()); @@ -98,8 +95,8 @@ bool VideoService::start() { } printf("VideoWriter opened successfully.\n"); - // 6. 启动处理线程 running_ = true; + reading_thread_ = std::thread(&VideoService::reading_loop, this); processing_thread_ = std::thread(&VideoService::processing_loop, this); printf("Processing thread started.\n"); @@ -111,6 +108,10 @@ void VideoService::stop() { printf("Stopping VideoService...\n"); running_ = false; + if (reading_thread_.joinable()) { + reading_thread_.join(); + } + if (processing_thread_.joinable()) { processing_thread_.join(); } @@ -127,11 +128,8 @@ void VideoService::stop() { } -// (新增) 从 rkYolov5s.cc 移入并修改 -// 这是现在唯一的跟踪器逻辑,在主线程中串行调用 void VideoService::update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size) { - // 首次运行时,根据图像尺寸初始化入侵区域 (设定在画面中央) if (intrusion_zone_.width == 0 || intrusion_zone_.height == 0) { intrusion_zone_ = cv::Rect(frame_size.width / 4, frame_size.height / 4, frame_size.width / 2, frame_size.height / 2); } @@ -147,12 +145,10 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co } } - // 1. 对于已有的跟踪目标,增加其未见帧数 for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { it->second.frames_unseen++; } - // 2. 将当前帧的检测结果与已有的跟踪目标进行匹配 for (const auto& det_box : current_detections) { bool is_matched = false; int best_match_id = -1; @@ -167,12 +163,10 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co } if (best_match_id != -1) { - // 匹配成功 tracked_persons_[best_match_id].box = det_box; tracked_persons_[best_match_id].frames_unseen = 0; is_matched = true; } else { - // 匹配失败,创建新的跟踪目标 TrackedPerson new_person; new_person.id = this->next_track_id_++; // 使用成员变量 new_person.box = det_box; @@ -184,7 +178,6 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co } } - // 3. 处理和更新每个目标的状态 double current_time = get_current_time_seconds(); for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { TrackedPerson& person = it->second; @@ -209,7 +202,6 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co } } - // 4. 移除消失太久的目标 for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ) { // (建议) 增加到50帧 (约2秒) 提高鲁棒性,减少ID切换 if (it->second.frames_unseen > 50) { @@ -220,15 +212,39 @@ void VideoService::update_tracker(detect_result_group_t &detect_result_group, co } } -// (新增) 绘图辅助函数,从 rkYolov5s::infer 移入 +void VideoService::reading_loop() { + cv::Mat frame; + spdlog::info("Reading thread started."); + + while (running_) { + if (!capture_.read(frame)) { + spdlog::warn("Reading loop: Failed to read frame from capture. Stopping service."); + running_ = false; + break; + } + + if (frame.empty()) { + continue; + } + + { + std::lock_guard lock(frame_mutex_); + latest_frame_ = frame; + new_frame_available_ = true; + } + + frame_cv_.notify_one(); + } + + frame_cv_.notify_all(); + spdlog::info("Reading loop finished."); +} + void VideoService::draw_results(cv::Mat& frame) { - // 绘制入侵区域 cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); // 黄色 - // 绘制框体和报警状态 for (auto const& [id, person] : this->tracked_persons_) { - // 根据是否触发报警决定颜色 (BGR: 红色 vs 绿色) cv::Scalar box_color = person.alarm_triggered ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 255, 0); int line_thickness = person.alarm_triggered ? 3 : 2; @@ -243,24 +259,36 @@ void VideoService::draw_results(cv::Mat& frame) } -// (关键修改) 彻底重写处理循环为 "一进一出" 模式 void VideoService::processing_loop() { cv::Mat frame; detect_result_group_t detection_results; while (running_) { auto t_start = std::chrono::high_resolution_clock::now(); - if (!capture_.read(frame)) { - spdlog::warn("VideoService: Failed to read frame from capture. Stopping capture."); - running_ = false; - break; + + { + std::unique_lock lock(frame_mutex_); + + frame_cv_.wait(lock, [&]{ + return new_frame_available_ || !running_; + }); + + if (!running_) { + break; + } + + // 确认是有新帧 + frame = latest_frame_.clone(); + new_frame_available_ = false; } - auto t_read = std::chrono::high_resolution_clock::now(); + + auto t_read = std::chrono::high_resolution_clock::now(); if (frame.empty()) { continue; } + // [保留] 后续的 AI 推理、跟踪、绘图、推流逻辑完全不变 if (rknn_pool_->put(frame) != 0) { spdlog::error("VideoService: Failed to put frame into rknnPool. Stopping."); running_ = false; @@ -283,18 +311,17 @@ void VideoService::processing_loop() { writer_.write(frame); } - // 打印详细耗时 auto t_write = std::chrono::high_resolution_clock::now(); - // (新增) 打印耗时 - double read_ms = std::chrono::duration_cast>(t_read - t_start).count(); - double infer_ms = std::chrono::duration_cast>(t_infer - t_read).count(); - double track_ms = std::chrono::duration_cast>(t_track_draw - t_infer).count(); - double write_ms = std::chrono::duration_cast>(t_write - t_track_draw).count(); - double total_ms = std::chrono::duration_cast>(t_write - t_start).count(); + // [保留] 性能日志 + double read_ms = std::chrono::duration_cast>(t_read - t_start).count(); + double infer_ms = std::chrono::duration_cast>(t_infer - t_read).count(); + double track_ms = std::chrono::duration_cast>(t_track_draw - t_infer).count(); + double write_ms = std::chrono::duration_cast>(t_write - t_track_draw).count(); + double total_ms = std::chrono::duration_cast>(t_write - t_start).count(); - printf("Loop time: Total=%.1fms [Read=%.1f, Infer=%.1f, Track=%.1f, Write=%.1f]\n", - total_ms, read_ms, infer_ms, track_ms, write_ms); + printf("Loop time: Total=%.1fms [Read=%.1f, Infer=%.1f, Track=%.1f, Write=%.1f]\n", + total_ms, read_ms, infer_ms, track_ms, write_ms); } spdlog::info("VideoService: Processing loop finished."); diff --git a/src/rknn/video_service.h b/src/rknn/video_service.h index 154060d..9820389 100644 --- a/src/rknn/video_service.h +++ b/src/rknn/video_service.h @@ -5,25 +5,26 @@ #include #include #include -#include // (新增) 用于跟踪 +#include +#include +#include #include #include -#include "postprocess.h" // (新增) 需要 detect_result_group_t +#include "postprocess.h" // 向前声明 template class rknnPool; class rkYolov5s; -// (新增) 从 rkYolov5s.hpp 移动过来的结构体 struct TrackedPerson { - int id; // 唯一ID - cv::Rect box; // 当前位置 - double entry_time; // 进入入侵区域的时间戳 (秒) - bool is_in_zone; // 是否在区域内 - bool alarm_triggered; // 是否已触发报警 - int frames_unseen; // 消失的帧数 + int id; + cv::Rect box; + double entry_time; + bool is_in_zone; + bool alarm_triggered; + int frames_unseen; }; class VideoService { @@ -40,35 +41,36 @@ public: private: void processing_loop(); + void reading_loop(); // [新增] 读取线程的循环函数 - // (新增) 跟踪和绘图相关的私有方法 void update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size); void draw_results(cv::Mat& frame); // 绘图辅助函数 void trigger_alarm(int person_id, const cv::Rect& box); double get_current_time_seconds(); - // 配置 std::string model_path_; int thread_num_; std::string input_url_; std::string output_rtsp_url_; - // 视频属性 int frame_width_ = 0; int frame_height_ = 0; double frame_fps_ = 0.0; - // 资源 - // (关键修改) rknnPool 的输出类型变为 detect_result_group_t std::unique_ptr> rknn_pool_; cv::VideoCapture capture_; cv::VideoWriter writer_; - // 线程管理 + std::thread processing_thread_; + std::thread reading_thread_; std::atomic running_{false}; - // (新增) 跟踪器状态变量 (从 rkYolov5s 移入) + std::mutex frame_mutex_; + std::condition_variable frame_cv_; + cv::Mat latest_frame_; + bool new_frame_available_{false}; + cv::Rect intrusion_zone_; std::map tracked_persons_; int next_track_id_;