webRTC算法无延迟

This commit is contained in:
GuanYuankai 2025-10-27 02:27:05 +00:00
parent 998991d7e5
commit 29ecc25221
2 changed files with 86 additions and 57 deletions

View File

@ -45,7 +45,6 @@ VideoService::~VideoService() {
bool VideoService::start() { bool VideoService::start() {
// (关键修改) rknnPool 的模板参数已更新
rknn_pool_ = std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>(model_path_.c_str(), thread_num_); rknn_pool_ = std::make_unique<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>>(model_path_.c_str(), thread_num_);
if (rknn_pool_->init() != 0) { if (rknn_pool_->init() != 0) {
printf("rknnPool init fail!\n"); printf("rknnPool init fail!\n");
@ -53,18 +52,15 @@ bool VideoService::start() {
} }
printf("rknnPool init success.\n"); printf("rknnPool init success.\n");
// 2. 设置RTSP传输协议
setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1); setenv("OPENCV_FFMPEG_CAPTURE_OPTIONS", "rtsp_transport;tcp", 1);
printf("Set RTSP transport protocol to TCP\n"); printf("Set RTSP transport protocol to TCP\n");
// 3. 初始化 VideoCapture
capture_.open(input_url_, cv::CAP_FFMPEG); capture_.open(input_url_, cv::CAP_FFMPEG);
if (!capture_.isOpened()) { if (!capture_.isOpened()) {
printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str()); printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str());
return false; return false;
} }
// 4. 获取输入视频的属性
frame_width_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_WIDTH)); frame_width_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_WIDTH));
frame_height_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_HEIGHT)); frame_height_ = static_cast<int>(capture_.get(cv::CAP_PROP_FRAME_HEIGHT));
frame_fps_ = capture_.get(cv::CAP_PROP_FPS); 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_); printf("RTSP stream opened successfully! (%dx%d @ %.2f FPS)\n", frame_width_, frame_height_, frame_fps_);
std::string gst_pipeline = std::string gst_pipeline =
"appsrc ! " "appsrc ! "
"video/x-raw,format=BGR ! " "queue max-size-buffers=2 leaky=downstream ! "
"videoconvert ! " "video/x-raw,format=BGR ! "
"video/x-raw,format=NV12 ! " "videoconvert ! "
"mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! " // (备注) 你可以根据需要调整 mpph265enc 参数 "video/x-raw,format=NV12 ! "
"h265parse ! " "mpph265enc gop=25 rc-mode=fixqp qp-init=26 ! "
"rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp"; "h265parse ! "
"rtspclientsink location=" + output_rtsp_url_ + " latency=0 protocols=tcp";
printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str()); printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str());
@ -98,8 +95,8 @@ bool VideoService::start() {
} }
printf("VideoWriter opened successfully.\n"); printf("VideoWriter opened successfully.\n");
// 6. 启动处理线程
running_ = true; running_ = true;
reading_thread_ = std::thread(&VideoService::reading_loop, this);
processing_thread_ = std::thread(&VideoService::processing_loop, this); processing_thread_ = std::thread(&VideoService::processing_loop, this);
printf("Processing thread started.\n"); printf("Processing thread started.\n");
@ -111,6 +108,10 @@ void VideoService::stop() {
printf("Stopping VideoService...\n"); printf("Stopping VideoService...\n");
running_ = false; running_ = false;
if (reading_thread_.joinable()) {
reading_thread_.join();
}
if (processing_thread_.joinable()) { if (processing_thread_.joinable()) {
processing_thread_.join(); 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) 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) { 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); 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) { for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) {
it->second.frames_unseen++; it->second.frames_unseen++;
} }
// 2. 将当前帧的检测结果与已有的跟踪目标进行匹配
for (const auto& det_box : current_detections) { for (const auto& det_box : current_detections) {
bool is_matched = false; bool is_matched = false;
int best_match_id = -1; 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) { if (best_match_id != -1) {
// 匹配成功
tracked_persons_[best_match_id].box = det_box; tracked_persons_[best_match_id].box = det_box;
tracked_persons_[best_match_id].frames_unseen = 0; tracked_persons_[best_match_id].frames_unseen = 0;
is_matched = true; is_matched = true;
} else { } else {
// 匹配失败,创建新的跟踪目标
TrackedPerson new_person; TrackedPerson new_person;
new_person.id = this->next_track_id_++; // 使用成员变量 new_person.id = this->next_track_id_++; // 使用成员变量
new_person.box = det_box; 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(); double current_time = get_current_time_seconds();
for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) { for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ++it) {
TrackedPerson& person = it->second; 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(); ) { for (auto it = tracked_persons_.begin(); it != tracked_persons_.end(); ) {
// (建议) 增加到50帧 (约2秒) 提高鲁棒性减少ID切换 // (建议) 增加到50帧 (约2秒) 提高鲁棒性减少ID切换
if (it->second.frames_unseen > 50) { 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<std::mutex> 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) void VideoService::draw_results(cv::Mat& frame)
{ {
// 绘制入侵区域
cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); // 黄色 cv::rectangle(frame, this->intrusion_zone_, cv::Scalar(255, 255, 0), 2); // 黄色
// 绘制框体和报警状态
for (auto const& [id, person] : this->tracked_persons_) { 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); 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; int line_thickness = person.alarm_triggered ? 3 : 2;
@ -243,24 +259,36 @@ void VideoService::draw_results(cv::Mat& frame)
} }
// (关键修改) 彻底重写处理循环为 "一进一出" 模式
void VideoService::processing_loop() { void VideoService::processing_loop() {
cv::Mat frame; cv::Mat frame;
detect_result_group_t detection_results; detect_result_group_t detection_results;
while (running_) { while (running_) {
auto t_start = std::chrono::high_resolution_clock::now(); 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; std::unique_lock<std::mutex> lock(frame_mutex_);
break;
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()) { if (frame.empty()) {
continue; continue;
} }
// [保留] 后续的 AI 推理、跟踪、绘图、推流逻辑完全不变
if (rknn_pool_->put(frame) != 0) { if (rknn_pool_->put(frame) != 0) {
spdlog::error("VideoService: Failed to put frame into rknnPool. Stopping."); spdlog::error("VideoService: Failed to put frame into rknnPool. Stopping.");
running_ = false; running_ = false;
@ -283,18 +311,17 @@ void VideoService::processing_loop() {
writer_.write(frame); writer_.write(frame);
} }
// 打印详细耗时
auto t_write = std::chrono::high_resolution_clock::now(); auto t_write = std::chrono::high_resolution_clock::now();
// (新增) 打印耗时 // [保留] 性能日志
double read_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_read - t_start).count(); double read_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_read - t_start).count();
double infer_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_infer - t_read).count(); double infer_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_infer - t_read).count();
double track_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_track_draw - t_infer).count(); double track_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_track_draw - t_infer).count();
double write_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_track_draw).count(); double write_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_track_draw).count();
double total_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_start).count(); double total_ms = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(t_write - t_start).count();
printf("Loop time: Total=%.1fms [Read=%.1f, Infer=%.1f, Track=%.1f, Write=%.1f]\n", printf("Loop time: Total=%.1fms [Read=%.1f, Infer=%.1f, Track=%.1f, Write=%.1f]\n",
total_ms, read_ms, infer_ms, track_ms, write_ms); total_ms, read_ms, infer_ms, track_ms, write_ms);
} }
spdlog::info("VideoService: Processing loop finished."); spdlog::info("VideoService: Processing loop finished.");

View File

@ -5,25 +5,26 @@
#include <thread> #include <thread>
#include <atomic> #include <atomic>
#include <memory> #include <memory>
#include <map> // (新增) 用于跟踪 #include <map>
#include <mutex>
#include <condition_variable>
#include <opencv2/core/core.hpp> #include <opencv2/core/core.hpp>
#include <opencv2/videoio.hpp> #include <opencv2/videoio.hpp>
#include "postprocess.h" // (新增) 需要 detect_result_group_t #include "postprocess.h"
// 向前声明 // 向前声明
template<typename T, typename IN, typename OUT> template<typename T, typename IN, typename OUT>
class rknnPool; class rknnPool;
class rkYolov5s; class rkYolov5s;
// (新增) 从 rkYolov5s.hpp 移动过来的结构体
struct TrackedPerson struct TrackedPerson
{ {
int id; // 唯一ID int id;
cv::Rect box; // 当前位置 cv::Rect box;
double entry_time; // 进入入侵区域的时间戳 (秒) double entry_time;
bool is_in_zone; // 是否在区域内 bool is_in_zone;
bool alarm_triggered; // 是否已触发报警 bool alarm_triggered;
int frames_unseen; // 消失的帧数 int frames_unseen;
}; };
class VideoService { class VideoService {
@ -40,35 +41,36 @@ public:
private: private:
void processing_loop(); void processing_loop();
void reading_loop(); // [新增] 读取线程的循环函数
// (新增) 跟踪和绘图相关的私有方法
void update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size); void update_tracker(detect_result_group_t &detect_result_group, const cv::Size& frame_size);
void draw_results(cv::Mat& frame); // 绘图辅助函数 void draw_results(cv::Mat& frame); // 绘图辅助函数
void trigger_alarm(int person_id, const cv::Rect& box); void trigger_alarm(int person_id, const cv::Rect& box);
double get_current_time_seconds(); double get_current_time_seconds();
// 配置
std::string model_path_; std::string model_path_;
int thread_num_; int thread_num_;
std::string input_url_; std::string input_url_;
std::string output_rtsp_url_; std::string output_rtsp_url_;
// 视频属性
int frame_width_ = 0; int frame_width_ = 0;
int frame_height_ = 0; int frame_height_ = 0;
double frame_fps_ = 0.0; double frame_fps_ = 0.0;
// 资源
// (关键修改) rknnPool 的输出类型变为 detect_result_group_t
std::unique_ptr<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>> rknn_pool_; std::unique_ptr<rknnPool<rkYolov5s, cv::Mat, detect_result_group_t>> rknn_pool_;
cv::VideoCapture capture_; cv::VideoCapture capture_;
cv::VideoWriter writer_; cv::VideoWriter writer_;
// 线程管理
std::thread processing_thread_; std::thread processing_thread_;
std::thread reading_thread_;
std::atomic<bool> running_{false}; std::atomic<bool> 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_; cv::Rect intrusion_zone_;
std::map<int, TrackedPerson> tracked_persons_; std::map<int, TrackedPerson> tracked_persons_;
int next_track_id_; int next_track_id_;