2025-10-29 11:12:53 +08:00
|
|
|
// video_service.cc (修改后)
|
2025-10-24 18:28:33 +08:00
|
|
|
#include "video_service.h"
|
2025-11-04 18:38:05 +08:00
|
|
|
#include "opencv2/imgproc/imgproc.hpp"
|
2025-10-24 18:28:33 +08:00
|
|
|
#include "spdlog/spdlog.h"
|
2025-11-04 18:38:05 +08:00
|
|
|
#include <stdio.h>
|
2025-10-24 18:42:38 +08:00
|
|
|
|
2025-10-29 11:12:53 +08:00
|
|
|
VideoService::VideoService(std::unique_ptr<IAnalysisModule> module,
|
2025-11-04 18:38:05 +08:00
|
|
|
std::string input_url, std::string output_rtsp_url,
|
|
|
|
|
nlohmann::json module_config)
|
|
|
|
|
: module_(std::move(module)), input_url_(input_url),
|
2025-10-24 18:28:33 +08:00
|
|
|
output_rtsp_url_(output_rtsp_url),
|
2025-11-04 18:38:05 +08:00
|
|
|
module_config_(std::move(module_config)), running_(false) {
|
|
|
|
|
log_prefix_ = "[VideoService: " + input_url + "]";
|
|
|
|
|
spdlog::info("{} Created. Input: {}, Output: {}", log_prefix_,
|
|
|
|
|
input_url_.c_str(), output_rtsp_url_.c_str());
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VideoService::~VideoService() {
|
2025-11-04 18:38:05 +08:00
|
|
|
if (running_) {
|
|
|
|
|
stop();
|
|
|
|
|
}
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool VideoService::start() {
|
2025-11-04 18:38:05 +08:00
|
|
|
if (!module_ || !module_->init(module_config_)) {
|
|
|
|
|
spdlog::error("{} Failed to initialize analysis module!", log_prefix_);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
spdlog::info("{} Analysis module initialized successfully.", log_prefix_);
|
|
|
|
|
|
|
|
|
|
std::string gst_input_pipeline = "rtspsrc location=" + input_url_ +
|
|
|
|
|
" latency=0 protocols=tcp ! "
|
|
|
|
|
"rtph265depay ! "
|
|
|
|
|
"h265parse ! "
|
|
|
|
|
"mppvideodec format=16 ! "
|
|
|
|
|
"videoconvert ! "
|
|
|
|
|
"video/x-raw,format=BGR ! "
|
|
|
|
|
"appsink";
|
|
|
|
|
|
|
|
|
|
spdlog::info("Try to Open RTSP Stream");
|
|
|
|
|
capture_.open(gst_input_pipeline, cv::CAP_GSTREAMER);
|
|
|
|
|
|
|
|
|
|
if (!capture_.isOpened()) {
|
|
|
|
|
printf("Error: Could not open RTSP stream: %s\n", input_url_.c_str());
|
|
|
|
|
return false;
|
|
|
|
|
} else {
|
|
|
|
|
spdlog::info("RTSP Stream Opened!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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_fps_ = capture_.get(cv::CAP_PROP_FPS);
|
|
|
|
|
if (frame_fps_ <= 0)
|
|
|
|
|
frame_fps_ = 25.0;
|
|
|
|
|
|
|
|
|
|
if (frame_width_ == 0 || frame_height_ == 0) {
|
|
|
|
|
spdlog::error("{} Failed to get valid frame width or height from GStreamer "
|
|
|
|
|
"pipeline (got {}x{}).",
|
|
|
|
|
log_prefix_, frame_width_, frame_height_);
|
|
|
|
|
spdlog::error("{} This usually means the RTSP stream is unavailable or the "
|
|
|
|
|
"GStreamer input pipeline (mppvideodec?) failed.",
|
|
|
|
|
log_prefix_);
|
|
|
|
|
|
|
|
|
|
cv::Mat test_frame;
|
|
|
|
|
if (capture_.read(test_frame) && !test_frame.empty()) {
|
|
|
|
|
frame_width_ = test_frame.cols;
|
|
|
|
|
frame_height_ = test_frame.rows;
|
|
|
|
|
spdlog::info(
|
|
|
|
|
"{} Successfully got frame size by reading first frame: {}x{}",
|
|
|
|
|
log_prefix_, frame_width_, frame_height_);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
|
|
|
|
latest_frame_ = test_frame;
|
|
|
|
|
new_frame_available_ = true;
|
|
|
|
|
}
|
|
|
|
|
frame_cv_.notify_one();
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
spdlog::error(
|
|
|
|
|
"{} Failed to read first frame to determine size. Aborting.",
|
|
|
|
|
log_prefix_);
|
|
|
|
|
capture_.release();
|
|
|
|
|
return false;
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
2025-11-04 18:38:05 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("RTSP stream opened successfully! (%dx%d @ %.2f FPS)\n", frame_width_,
|
|
|
|
|
frame_height_, frame_fps_);
|
|
|
|
|
|
|
|
|
|
std::string gst_pipeline = "appsrc ! "
|
|
|
|
|
"queue max-size-buffers=2 leaky=downstream ! "
|
|
|
|
|
"video/x-raw,format=BGR ! "
|
|
|
|
|
"videoconvert ! "
|
|
|
|
|
"video/x-raw,format=NV12 ! "
|
2025-11-04 19:46:01 +08:00
|
|
|
"mpph264enc gop=25 rc-mode=fixqp qp-init=26 ! "
|
|
|
|
|
"h264parse ! "
|
2025-11-04 18:38:05 +08:00
|
|
|
"rtspclientsink location=" +
|
|
|
|
|
output_rtsp_url_ + " latency=0 protocols=tcp";
|
|
|
|
|
|
|
|
|
|
printf("Using GStreamer output pipeline: %s\n", gst_pipeline.c_str());
|
|
|
|
|
|
|
|
|
|
writer_.open(gst_pipeline, cv::CAP_GSTREAMER, 0, frame_fps_,
|
|
|
|
|
cv::Size(frame_width_, frame_height_), true);
|
|
|
|
|
|
|
|
|
|
if (!writer_.isOpened()) {
|
|
|
|
|
printf("Error: Could not open VideoWriter with GStreamer pipeline.\n");
|
|
|
|
|
capture_.release();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
printf("VideoWriter opened successfully.\n");
|
|
|
|
|
|
|
|
|
|
running_ = true;
|
|
|
|
|
reading_thread_ = std::thread(&VideoService::reading_loop, this);
|
|
|
|
|
processing_thread_ = std::thread(&VideoService::processing_loop, this);
|
|
|
|
|
printf("Processing thread started.\n");
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void VideoService::stop() {
|
|
|
|
|
printf("Stopping VideoService...\n");
|
|
|
|
|
running_ = false;
|
|
|
|
|
|
|
|
|
|
frame_cv_.notify_all();
|
|
|
|
|
|
|
|
|
|
if (reading_thread_.joinable()) {
|
|
|
|
|
reading_thread_.join();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (processing_thread_.joinable()) {
|
|
|
|
|
processing_thread_.join();
|
|
|
|
|
}
|
|
|
|
|
printf("Processing thread joined.\n");
|
|
|
|
|
|
|
|
|
|
if (capture_.isOpened()) {
|
|
|
|
|
capture_.release();
|
|
|
|
|
}
|
|
|
|
|
if (writer_.isOpened()) {
|
|
|
|
|
writer_.release();
|
|
|
|
|
}
|
|
|
|
|
module_->stop();
|
|
|
|
|
module_.reset();
|
|
|
|
|
|
|
|
|
|
printf("VideoService stopped.\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
2025-11-04 18:38:05 +08:00
|
|
|
|
|
|
|
|
if (frame.empty()) {
|
|
|
|
|
continue;
|
2025-10-29 11:12:53 +08:00
|
|
|
}
|
2025-10-24 18:28:33 +08:00
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
{
|
|
|
|
|
std::lock_guard<std::mutex> lock(frame_mutex_);
|
|
|
|
|
latest_frame_ = frame;
|
|
|
|
|
new_frame_available_ = true;
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
frame_cv_.notify_one();
|
|
|
|
|
}
|
2025-10-24 18:28:33 +08:00
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
frame_cv_.notify_all(); // 确保 processing_loop 也会退出
|
|
|
|
|
spdlog::info("Reading loop finished.");
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
void VideoService::processing_loop() {
|
|
|
|
|
cv::Mat frame;
|
2025-10-24 18:28:33 +08:00
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
while (running_) {
|
|
|
|
|
{
|
|
|
|
|
// 1. (不变) 获取帧
|
|
|
|
|
std::unique_lock<std::mutex> lock(frame_mutex_);
|
2025-10-24 18:28:33 +08:00
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
frame_cv_.wait(lock, [&] { return new_frame_available_ || !running_; });
|
2025-10-29 11:12:53 +08:00
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
if (!running_) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
2025-10-27 10:27:05 +08:00
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
frame = latest_frame_.clone();
|
|
|
|
|
new_frame_available_ = false;
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
|
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
if (frame.empty()) {
|
|
|
|
|
continue;
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
2025-11-04 18:38:05 +08:00
|
|
|
if (!module_->process(frame)) {
|
|
|
|
|
// 模块报告处理失败
|
|
|
|
|
spdlog::warn("{} Module failed to process frame. Skipping.", log_prefix_);
|
2025-10-24 18:28:33 +08:00
|
|
|
}
|
2025-11-04 18:38:05 +08:00
|
|
|
if (writer_.isOpened()) {
|
|
|
|
|
writer_.write(frame);
|
2025-10-27 10:27:05 +08:00
|
|
|
}
|
2025-11-04 18:38:05 +08:00
|
|
|
}
|
2025-10-27 10:27:05 +08:00
|
|
|
|
2025-11-04 18:38:05 +08:00
|
|
|
spdlog::info("VideoService: Processing loop finished.");
|
|
|
|
|
}
|