下面【】内是一个c++的视频分析服务代码。主要功能如下：目前算法分析服务triton server已经部署好了，并且部署了10种算法模型，可以通过的
TritonInference推理客户端调用triton server部署的算法算法模型（有10类算法：人员检测、车辆检测、船只检测、
土壤裸露、烟火检测、违法捕捞、非法垂钓、违法搭建、垃圾裸露、鸟类检测）。实现路径：读取配置文件RTSP流的地址，然后
通过GStreamer并发发接入多路视频流，然后调用ffmpeng来抽帧，抽帧形成图片数据，发送给triton server，然后将
返回的分析结果传给上层应用。同时实现多路并发、高性能、高可靠、系统调用，高效的图片抽帧、报警录像等等。

目前存如下问题需要修改，你要仔细分析【】内的代码，同时审核代码的逻辑和内存管理这块，需要检查的问题如下：
（1）程序正常运行一段时间后,显示-已杀死，运行日志如"""   """所示。通过这个命令：dmesg | grep -i 'killed process',运行后的输出如`  `所示。初步判断应该是内存没有释放导致。请您仔细分析【】内的代码，看看那些文件，那里的内存没有释放，如何修改？输出修改后的完整代码。
（2）配置文件中camera_config.ini，配置了两个相机，两个相机的RTSP的流都是正常的，VLC播放器可以正常播放，但是VideoCapture类的回调函数onNewSample中，始终只有相机Camera2的抽帧信息；尽管显示 Camera1：Started video capture for camera，但是Camera1的抽帧信息没有。请您仔细分析【】内的代码，看看什么原因？如何修改？输出修改后的完整代码。
"""
(triton) (base) chen@chen-Precision-3660:~/data/algorithm_analysis_service/build$ ./video_analysis_service 
2024-11-15 17:26:01 [INFO] Starting Video Analysis Service...
2024-11-15 17:26:01 [INFO] Configuration loaded successfully
construct :TritonInference===1
constuct FrameProcessor: triton_client_
2024-11-15 17:26:01 [INFO] Started video capture for camera Camera2
2024-11-15 17:26:01 [INFO] Started video capture for camera: Camera2
construct :TritonInference===1
constuct FrameProcessor: triton_client_
2024-11-15 17:26:01 [INFO] Started video capture for camera Camera1
2024-11-15 17:26:01 [INFO] Started video capture for camera: Camera1
VideoCapture::onNewSample==1 Camera2
infer:for===1 peoplecarboat_detector
Detection Results at 2024-11-15 17:26:17:
VideoCapture::onNewSample==1 Camera2
infer:for===1 peoplecarboat_detector
Detection Results at 2024-11-15 17:26:20:
VideoCapture::onNewSample==1 Camera2
infer:for===1 peoplecarboat_detector
Detection Results at 2024-11-15 17:26:23:
VideoCapture::onNewSample==1 Camera2
infer:for===1 peoplecarboat_detector
Detection Results at 2024-11-15 17:26:28:
VideoCapture::onNewSample==1 Camera2
infer:for===1 peoplecarboat_detector
Detection Results at 2024-11-15 15:33:05:
Class: 58, Confidence: 0.889648, Box: [1667, 809, 251, 265]
Class: 58, Confidence: 0.88623, Box: [59, 574, 189, 500]
Class: 58, Confidence: 0.844727, Box: [1396, 562, 99, 273]
Class: 56, Confidence: 0.82666, Box: [1301, 937, 132, 140]
Class: 58, Confidence: 0.764648, Box: [509, 611, 55, 174]
Class: 56, Confidence: 0.741699, Box: [1652, 843, 80, 114]
Class: 72, Confidence: 0.67041, Box: [997, 560, 104, 231]
Class: 56, Confidence: 0.65332, Box: [1286, 864, 119, 141]
Class: 56, Confidence: 0.647949, Box: [918, 1009, 263, 69]
Class: 56, Confidence: 0.605957, Box: [1519, 805, 43, 64]
Class: 56, Confidence: 0.57959, Box: [1461, 874, 117, 200]
Class: 60, Confidence: 0.480469, Box: [1379, 890, 140, 45]
Class: 56, Confidence: 0.394287, Box: [843, 995, 112, 83]
Class: 56, Confidence: 0.388672, Box: [1378, 889, 142, 180]
Class: 60, Confidence: 0.299072, Box: [1580, 814, 103, 35]
Class: 62, Confidence: 0.286133, Box: [0, 364, 98, 192]
VideoCapture::onNewSample==1 Camera2
已杀死
(triton) (base) chen@chen-Precision-3660:~/data/algorithm_analysis_service/build$ 
"""
`
(base) chen@chen-Precision-3660:~/data/algorithm_analysis_service/build$ dmesg | grep -i 'killed process'
[1824595.578379] Out of memory: Killed process 615055 (video_analysis_) total-vm:64944444kB, anon-rss:58677436kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:119000kB oom_score_adj:0
[1829410.467633] Out of memory: Killed process 623657 (video_analysis_) total-vm:63946428kB, anon-rss:58843804kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118172kB oom_score_adj:0
[1904815.637080] Out of memory: Killed process 640949 (video_analysis_) total-vm:63537512kB, anon-rss:58192256kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:117336kB oom_score_adj:0
[1907203.647887] Out of memory: Killed process 644875 (video_analysis_) total-vm:61789548kB, anon-rss:57735516kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:113948kB oom_score_adj:0
[1910489.228120] Out of memory: Killed process 651188 (video_analysis_) total-vm:64109752kB, anon-rss:58739912kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118372kB oom_score_adj:0
[1911741.286888] Out of memory: Killed process 655163 (video_analysis_) total-vm:64292176kB, anon-rss:58738228kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118788kB oom_score_adj:0
[1913459.259790] Out of memory: Killed process 657778 (video_analysis_) total-vm:64363360kB, anon-rss:58725600kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118924kB oom_score_adj:0
(base) chen@chen-Precision-3660:~/data/algorithm_analysis_service/build$ dmesg | grep -i 'out of memory'
[1824595.578379] Out of memory: Killed process 615055 (video_analysis_) total-vm:64944444kB, anon-rss:58677436kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:119000kB oom_score_adj:0
[1829410.467633] Out of memory: Killed process 623657 (video_analysis_) total-vm:63946428kB, anon-rss:58843804kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118172kB oom_score_adj:0
[1904815.637080] Out of memory: Killed process 640949 (video_analysis_) total-vm:63537512kB, anon-rss:58192256kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:117336kB oom_score_adj:0
[1907203.647887] Out of memory: Killed process 644875 (video_analysis_) total-vm:61789548kB, anon-rss:57735516kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:113948kB oom_score_adj:0
[1910489.228120] Out of memory: Killed process 651188 (video_analysis_) total-vm:64109752kB, anon-rss:58739912kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118372kB oom_score_adj:0
[1911741.286888] Out of memory: Killed process 655163 (video_analysis_) total-vm:64292176kB, anon-rss:58738228kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118788kB oom_score_adj:0
[1913459.259790] Out of memory: Killed process 657778 (video_analysis_) total-vm:64363360kB, anon-rss:58725600kB, file-rss:0kB, shmem-rss:4kB, UID:1000 pgtables:118924kB oom_score_adj:0
(base) chen@chen-Precision-3660:~/data/algorithm_analysis_service/build$ ^C
`


【
1. 项目整体架构
video_analysis_service/
├── CMakeLists.txt
├── config/
│   └── camera_config.ini
├── include/
│   ├── alarm_manager.h
│   ├── config_manager.h
│   ├── frame_processor.h
│   ├── logger.h
│   ├── triton_inference.h
│   └── video_capture.h
├── src/
│   ├── alarm_manager.cpp
│   ├── config_manager.cpp
│   ├── frame_processor.cpp
│   ├── logger.cpp
│   ├── main.cpp
│   ├── triton_inference.cpp
│   └── video_capture.cpp
└── build/
2. 项目整体的源代码如下
2.1 config_manager.h
#pragma once
#include <string>
#include <vector>
#include <map>

struct CameraConfig {
    std::string name;
    std::string rtsp_url;
    std::vector<std::string> algorithms;
    int frame_interval;
    std::map<std::string, std::string> alarm_strategies;
    std::string alarm_storage_path;
};

class ConfigManager {
public:
    static ConfigManager& getInstance();
    bool loadConfig(const std::string& config_path);
    const std::vector<CameraConfig>& getCameraConfigs() const;

private:
    ConfigManager() = default;
    std::vector<CameraConfig> camera_configs_;
};

2.2 config_manager.cpp
#include "config_manager.h"
#include "logger.h"
#include <fstream>
#include <sstream>

ConfigManager& ConfigManager::getInstance() {
    static ConfigManager instance;
    return instance;
}

bool ConfigManager::loadConfig(const std::string& config_path) {
    std::ifstream file(config_path);
    if (!file.is_open()) {
        LOG_ERROR("Failed to open config file: " + config_path);
        return false;
    }

    std::string line;
    CameraConfig current_config;
    
    while (std::getline(file, line)) {
        if (line.empty() || line[0] == '#') continue;
        
        if (line[0] == '[') {
            if (!current_config.name.empty()) {
                camera_configs_.push_back(current_config);
            }
            current_config = CameraConfig();
            current_config.name = line.substr(1, line.size() - 2);
            continue;
        }

        size_t pos = line.find('=');
        if (pos == std::string::npos) continue;

        std::string key = line.substr(0, pos);
        std::string value = line.substr(pos + 1);

        if (key == "rtsp_url") {
            current_config.rtsp_url = value;
        } else if (key == "frame_interval") {
            current_config.frame_interval = std::stoi(value);
        } else if (key == "algorithms") {
            std::stringstream ss(value);
            std::string algorithm;
            while (std::getline(ss, algorithm, ',')) {
                current_config.algorithms.push_back(algorithm);
            }
        } else if (key == "alarm_storage_path") {
            current_config.alarm_storage_path = value;
        } else if (key == "alarm_strategy") {
            std::stringstream ss(value);
            std::string strategy;
            while (std::getline(ss, strategy, ',')) {
                size_t colon_pos = strategy.find(':');
                if (colon_pos != std::string::npos) {
                    std::string alg = strategy.substr(0, colon_pos);
                    std::string strat = strategy.substr(colon_pos + 1);
                    current_config.alarm_strategies[alg] = strat;
                }
            }
        }
    }

    if (!current_config.name.empty()) {
        camera_configs_.push_back(current_config);
    }

    return true;
}

const std::vector<CameraConfig>& ConfigManager::getCameraConfigs() const {
    return camera_configs_;
}

2.3 video_capture.h
#pragma once

#include <string>
#include <memory>
#include <chrono>
#include <gst/gst.h>
#include <gst/video/video.h>  
#include <opencv2/opencv.hpp>
#include "config_manager.h"
#include "frame_processor.h"

class VideoCapture {
public:
    VideoCapture(const CameraConfig& config);
    ~VideoCapture();

    bool start();
    bool stop();

    bool isRunning() const;

    void setFrameProcessor(std::shared_ptr<FrameProcessor> processor);

private:
    static GstFlowReturn onNewSample(GstElement* sink, gpointer data);
        static gboolean onBusMessage(GstBus* bus, GstMessage* msg, gpointer data);

    void reconnect();
    
    
    std::chrono::system_clock::time_point last_frame_time_;
    bool is_running_;    
    GstElement* pipeline_;
    std::shared_ptr<FrameProcessor> frame_processor_;
    CameraConfig config_;

    std::mutex reconnect_mutex_;
    int reconnect_attempts_;
    std::vector<int> reconnect_intervals_;

   
};

2.4 video_capture.cpp
#include "video_capture.h"
#include "logger.h"
#include <gst/app/gstappsink.h>
#include <gst/video/video.h>

VideoCapture::VideoCapture(const CameraConfig &config)
    : config_(config), pipeline_(nullptr),
      is_running_(false),
      reconnect_attempts_(0),
      last_frame_time_(std::chrono::system_clock::now())
{
    if (!gst_is_initialized())
    {
        gst_init(NULL, NULL); // Initialize GStreamer if not already initialized
    }

    reconnect_intervals_ = {5, 10, 30, 180}; // 秒
}

VideoCapture::~VideoCapture()
{
    stop();
}

void VideoCapture::setFrameProcessor(std::shared_ptr<FrameProcessor> processor)
{
    frame_processor_ = processor;
}

bool VideoCapture::start()
{
    std::lock_guard<std::mutex> lock(reconnect_mutex_);
    if (is_running_)
        return true;

    GError *error = nullptr;
    std::string pipeline_str =
        "rtspsrc location=" + config_.rtsp_url +
        " protocols=tcp latency=500 ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! appsink name=sink";

    pipeline_ = gst_parse_launch(pipeline_str.c_str(), &error);
    if (error)
    {
        LOG_ERROR("Failed to create pipeline for camera " + config_.name + ": " + error->message);
        g_error_free(error);
        return false;
    }

    // 设置消息总线
    GstBus *bus = gst_element_get_bus(pipeline_);
    gst_bus_add_watch(bus, onBusMessage, this);
    gst_object_unref(bus);

    // 设置AppSink回调
    GstElement *sink = gst_bin_get_by_name(GST_BIN(pipeline_), "sink");
    g_object_set(G_OBJECT(sink), "emit-signals", TRUE, "sync", FALSE, NULL); // Make sure sync is FALSE for better performance
    g_signal_connect(sink, "new-sample", G_CALLBACK(onNewSample), this);
    gst_object_unref(sink);

    // 启动pipeline
    GstStateChangeReturn ret = gst_element_set_state(pipeline_, GST_STATE_PLAYING);
    if (ret == GST_STATE_CHANGE_FAILURE)
    {
        LOG_ERROR("Failed to start pipeline for camera " + config_.name);
        return false;
    }

    is_running_ = true;
    LOG_INFO("Started video capture for camera " + config_.name);
    return true;
}

bool VideoCapture::stop()
{
    std::lock_guard<std::mutex> lock(reconnect_mutex_);
    if (!is_running_)
        return true;

    if (pipeline_)
    {
        gst_element_set_state(pipeline_, GST_STATE_NULL);
        gst_object_unref(pipeline_);
        pipeline_ = nullptr;
    }

    is_running_ = false;
    return true;
}

bool VideoCapture::isRunning() const
{
    return is_running_;
}

GstFlowReturn VideoCapture::onNewSample(GstElement *sink, gpointer data)
{
    VideoCapture *self = static_cast<VideoCapture *>(data);

    // Frame interval check
    auto current_time = std::chrono::system_clock::now();
    auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(current_time - self->last_frame_time_);

    if (elapsed_time.count() < self->config_.frame_interval)
    {
        return GST_FLOW_OK; // Skip frame
    }
    self->last_frame_time_ = current_time;
    std::cout << "VideoCapture::onNewSample==1 " << self->config_.name << std::endl;
    GstSample *sample = gst_app_sink_pull_sample(GST_APP_SINK(sink));
    if (!sample)
    {
        return GST_FLOW_OK;
    }

    GstBuffer *buffer = gst_sample_get_buffer(sample);
    GstMapInfo map;

    if (gst_buffer_map(buffer, &map, GST_MAP_READ))
    {
        GstCaps *caps = gst_sample_get_caps(sample);
        GstStructure *structure = gst_caps_get_structure(caps, 0);
        int width, height;
        gst_structure_get_int(structure, "width", &width);
        gst_structure_get_int(structure, "height", &height);
        const gchar *format = gst_structure_get_string(structure, "format");

        cv::Mat frame;
        if (strcmp(format, "BGR") == 0)
        {
            frame = cv::Mat(height, width, CV_8UC3, (char *)map.data);
        }
        else if (strcmp(format, "RGB") == 0)
        {
            frame = cv::Mat(height, width, CV_8UC3, (char *)map.data);
            cv::cvtColor(frame, frame, cv::COLOR_RGB2BGR);
        }
        else if (strcmp(format, "RGBA") == 0)
        {
            cv::Mat rgba_frame(height, width, CV_8UC4, (char *)map.data);
            cv::cvtColor(rgba_frame, frame, cv::COLOR_RGBA2BGR);
        }
        else if (strcmp(format, "NV12") == 0)
        {
            cv::Mat nv12_frame(height * 3 / 2, width, CV_8UC1, (char *)map.data);
            cv::cvtColor(nv12_frame, frame, cv::COLOR_YUV2BGR_NV12);
        }
        else if (strcmp(format, "I420") == 0)
        {
            cv::Mat i420_frame(height * 3 / 2, width, CV_8UC1, (char *)map.data);
            cv::cvtColor(i420_frame, frame, cv::COLOR_YUV2BGR_I420);
        }
        else
        {
            LOG_ERROR("Unsupported video format: " + std::string(format));
            gst_buffer_unmap(buffer, &map);
            gst_sample_unref(sample);
            return GST_FLOW_OK;
        }
        // Make sure the processor is set
        if (self->frame_processor_)
        {
            self->frame_processor_->processFrame(frame);
        }
        else
        {
            LOG_WARNING("Frame processor not set for camera " + self->config_.name);
        }

        gst_buffer_unmap(buffer, &map);
    }

    gst_sample_unref(sample);
    return GST_FLOW_OK;
}

gboolean VideoCapture::onBusMessage(GstBus *bus, GstMessage *msg, gpointer data)
{
    VideoCapture *self = static_cast<VideoCapture *>(data);

    switch (GST_MESSAGE_TYPE(msg))
    {
    case GST_MESSAGE_EOS:
        LOG_WARNING("Stream reached EOS for camera " + self->config_.name);
        std::cout << "VideoCapture::onBusMessage==disconnect" << std::endl;
        self->reconnect();
        break;
    case GST_MESSAGE_ERROR:
    {
        GError *err;
        gchar *debug_info;
        gst_message_parse_error(msg, &err, &debug_info);
        LOG_ERROR("GStreamer error for camera " + self->config_.name + ": " + err->message);
        std::cout << "VideoCapture::onBusMessage:error" << std::endl;
        self->reconnect();
        g_clear_error(&err);
        g_free(debug_info);
        self->reconnect();
        break;
    }
    default:
        break;
    }
    return TRUE;
}

void VideoCapture::reconnect()
{
    std::lock_guard<std::mutex> lock(reconnect_mutex_);
    if (is_running_)
    {
        gst_element_set_state(pipeline_, GST_STATE_NULL);
        gst_object_unref(pipeline_);
        pipeline_ = nullptr;
        is_running_ = false;
    }

    int wait_time = reconnect_attempts_ < reconnect_intervals_.size() ? reconnect_intervals_[reconnect_attempts_] : reconnect_intervals_.back();
    LOG_INFO("Attempting to reconnect camera " + config_.name + " in " + std::to_string(wait_time) + " seconds");
    std::this_thread::sleep_for(std::chrono::seconds(wait_time));

    reconnect_attempts_++;
    start();
}



2.5 alarm_manager.h
#pragma once
#include <string>
#include <vector>
#include <memory>
#include "config_manager.h"
#include "triton_inference.h"

class AlarmManager {
public:
    AlarmManager(const std::string& alarm_log_path);
    void handleAlarm(const CameraConfig& config, 
                     const std::vector<TritonInference::Detection>& detections);

private:
    bool checkAlarmCondition(const CameraConfig& config,
                             const std::vector<TritonInference::Detection>& detections);
    void startRecording(const CameraConfig& config, 
                        const std::string& video_path);
    void recordAlarm(const CameraConfig& config,
                     const std::string& image_path,
                     const std::string& video_path);
    std::string createMonthDirectory();
    
    std::string alarm_log_path_;
};

2.6 alarm_manager.cpp
#include "alarm_manager.h"
#include "logger.h"
#include <fstream>
#include <filesystem>
#include <chrono>
#include <thread>
#include <gst/gst.h>

namespace fs = std::filesystem;

AlarmManager::AlarmManager(const std::string& alarm_log_path)
    : alarm_log_path_(alarm_log_path) {
    fs::create_directories(alarm_log_path);
}

void AlarmManager::handleAlarm(const CameraConfig& config,
                               const std::vector<TritonInference::Detection>& detections) {

    // 检查报警条件
    bool should_alarm = checkAlarmCondition(config, detections);
    if (!should_alarm) return;

    // 生成报警时间戳
    auto now = std::chrono::system_clock::now();
    auto timestamp = std::chrono::system_clock::to_time_t(now);

    // 创建月份目录
    std::string month_dir = createMonthDirectory();

    // 保存报警图片
    std::string image_path = month_dir + "/" + config.name + "_" +
                            std::to_string(timestamp) + ".jpg";
    // 保存图片的逻辑需要在FrameProcessor中实现，这里假设已经保存

    // 开始录制报警视频
    std::string video_path = month_dir + "/" + config.name + "_" +
                            std::to_string(timestamp) + ".mp4";
    startRecording(config, video_path);

    // 记录报警信息
    recordAlarm(config, image_path, video_path);
}

bool AlarmManager::checkAlarmCondition(
    const CameraConfig& config,
    const std::vector<TritonInference::Detection>& detections) {
    
    // 根据配置文件中的报警策略检查是否需要报警
    for (const auto& det : detections) {
        auto it = config.alarm_strategies.find(det.model_name);
        if (it != config.alarm_strategies.end()) {
            // 根据策略进行判断，这里简单示例
            if (det.model_name == "peoplecarboat_detector" && det.class_id == 0) {
                int person_count = std::count_if(detections.begin(), detections.end(),
                    [&](const auto& d) { return d.model_name == "peoplecarboat_detector" && d.class_id == 0; });
                if (person_count >= 1) return true;
            } else if (det.model_name == "peoplecarboat_detector"  && det.class_id ==13) {
                return true; // 只要检测到船只就报警
            }
            // 其他策略...
        }
    }

    return false;
}

void AlarmManager::startRecording(const CameraConfig& config, 
                                  const std::string& video_path) {
    // 使用更完善的 GStreamer 管道配置
    std::string pipeline_str = 
        "rtspsrc location=" + config.rtsp_url + " protocols=tcp latency=500 ! "
        "rtph264depay ! h264parse ! "
        "mp4mux faststart=true fragment-duration=1000 ! "  // 添加 faststart 和 fragment-duration 配置
        "filesink location=" + video_path + " sync=false";

    GError* error = nullptr;
    GstElement* pipeline = gst_parse_launch(pipeline_str.c_str(), &error);
    
    if (error) {
        LOG_ERROR("Failed to create recording pipeline: " + std::string(error->message));
        g_error_free(error);
        return;
    }

    // 设置录制时长为10秒
    gst_element_set_state(pipeline, GST_STATE_PLAYING);
    
    // 等待pipeline完全启动
    GstState state;
    GstStateChangeReturn ret = gst_element_get_state(pipeline, &state, nullptr, GST_CLOCK_TIME_NONE);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        LOG_ERROR("Failed to start recording pipeline");
        gst_object_unref(pipeline);
        return;
    }

    std::this_thread::sleep_for(std::chrono::seconds(10));
    
    // 优雅地停止pipeline
    gst_element_send_event(pipeline, gst_event_new_eos());
    gst_element_set_state(pipeline, GST_STATE_NULL);
    
    gst_object_unref(pipeline);
}

void AlarmManager::recordAlarm(const CameraConfig& config,
                               const std::string& image_path,
                               const std::string& video_path) {
    std::ofstream log_file(alarm_log_path_ + "/alarm_log.txt", 
                          std::ios::app);
    
    if (log_file.is_open()) {
        auto now = std::chrono::system_clock::now();
        auto timestamp = std::chrono::system_clock::to_time_t(now);
        
        log_file << "时间: " << std::ctime(&timestamp)
                << "摄像头: " << config.name << "\n"
                << "报警图片: " << image_path << "\n"
                << "报警视频: " << video_path << "\n"
                << "-------------------\n";
        
        log_file.close();
    }
}

std::string AlarmManager::createMonthDirectory() {
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    std::tm* ltm = std::localtime(&time);
    
    std::string month_dir = alarm_log_path_ + "/" + 
                           std::to_string(1900 + ltm->tm_year) + "_" +
                           std::to_string(1 + ltm->tm_mon);
    
    fs::create_directories(month_dir);
    return month_dir;
}

2.7 triton_inference.h
#pragma once
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <filesystem>
#include "http_client.h"

namespace tc = triton::client;
class TritonInference {
public:
    TritonInference(const std::string& url, const std::vector<std::string>& model_names);

    struct Detection {
        cv::Rect box;
        float confidence;
        int class_id;
        std::string model_name;
    };

    std::vector<Detection> infer(const cv::Mat& image);

private:
    std::unique_ptr<tc::InferenceServerHttpClient> triton_client_;
    std::vector<std::string> model_names_;
    std::string input_name_;
    std::string output_name_;
    int input_h_;
    int input_w_;
    std::vector<std::string> class_names_;

    std::tuple<cv::Mat, float, cv::Point2f> preprocess(const cv::Mat& image);
    std::vector<float> prepare_input(const cv::Mat& image);
    std::vector<Detection> postprocess(const float* output_data, size_t output_size, float scale_factor, const cv::Point2f& offset, const cv::Size& original_size, const std::string& model_name);
};
2.8 triton_inference.cpp
#include "triton_inference.h"
#include "logger.h"

TritonInference::TritonInference(const std::string &url, const std::vector<std::string> &model_names)
    : model_names_(model_names)
{
    std::cout << "construct :TritonInference===1" << std::endl;
    // 创建HTTP客户端
    tc::Error err = tc::InferenceServerHttpClient::Create(
        &triton_client_, url, false);
    if (!err.IsOk())
    {
        throw std::runtime_error("Failed to create client: " + err.Message());
    }

    input_name_ = "images";
    output_name_ = "output0";
    input_h_ = 640;
    input_w_ = 640;

    // 初始化类别名称
    class_names_ = {"person", "bicycle", "car", "motorcycle", "airplane", "bus", "train", "truck", "boat",
                    "traffic light", "fire hydrant", "stop sign", "parking meter", "bench", "bird", "cat",
                    "dog", "horse", "sheep", "cow", "elephant", "bear", "zebra", "giraffe", "backpack",
                    "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball",
                    "kite", "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket",
                    "bottle", "wine glass", "cup", "fork", "knife", "spoon", "bowl", "banana", "apple",
                    "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza", "donut", "cake", "chair",
                    "couch", "potted plant", "bed", "dining table", "toilet", "tv", "laptop", "mouse",
                    "remote", "keyboard", "cell phone", "microwave", "oven", "toaster", "sink", "refrigerator",
                    "book", "clock", "vase", "scissors", "teddy bear", "hair drier", "toothbrush"}; // 根据实际类别设置
}

std::vector<TritonInference::Detection> TritonInference::infer(const cv::Mat &image)
{
    if (image.empty())
    {
        throw std::runtime_error("Invalid image");
    }

    // 预处理
    cv::Mat preprocessed;
    float scale_factor;
    cv::Point2f offset;
    std::tie(preprocessed, scale_factor, offset) = preprocess(image);

    // 准备输入数据
    std::vector<float> input_data = prepare_input(preprocessed);

    // 创建输入tensor
    std::vector<int64_t> input_shape = {1, 3, input_h_, input_w_};

    std::vector<TritonInference::Detection> all_detections;

    for (const auto &model_name : model_names_)
    {
        std::cout << "infer:for===1 " << model_name <<std::endl;
        tc::InferInput *input;
        tc::Error err = tc::InferInput::Create(&input, input_name_, input_shape, "FP32");
        if (!err.IsOk())
        {
            LOG_ERROR("Failed to create input: " + err.Message());
            continue;
        }
        std::shared_ptr<tc::InferInput> input_ptr(input);

        // 设置输入数据
        err = input_ptr->AppendRaw(
            reinterpret_cast<uint8_t *>(input_data.data()),
            input_data.size() * sizeof(float));
        if (!err.IsOk())
        {
            LOG_ERROR("Failed to set input data: " + err.Message());
            continue;
        }

        // 创建输出
        tc::InferRequestedOutput *output;
        err = tc::InferRequestedOutput::Create(&output, output_name_);
        if (!err.IsOk())
        {
            LOG_ERROR("Failed to create output: " + err.Message());
            continue;
        }
        std::shared_ptr<tc::InferRequestedOutput> output_ptr(output);

        // 设置推理选项
        tc::InferOptions options(model_name);
        options.model_version_ = "1";

        // 执行推理
        std::vector<tc::InferInput *> inputs = {input_ptr.get()};
        std::vector<const tc::InferRequestedOutput *> outputs = {output_ptr.get()};
        tc::InferResult *result;

        err = triton_client_->Infer(&result, options, inputs, outputs);
        if (!err.IsOk())
        {
            LOG_ERROR("Failed to run inference: " + err.Message());
            continue;
        }
        std::shared_ptr<tc::InferResult> result_ptr(result);

        // 获取输出数据
        const uint8_t *output_data;
        size_t output_byte_size;
        err = result_ptr->RawData(output_name_, &output_data, &output_byte_size);
        if (!err.IsOk())
        {
            LOG_ERROR("Failed to get output data: " + err.Message());
            continue;
        }

        // 后处理
        std::vector<Detection> detections = postprocess(
            reinterpret_cast<const float *>(output_data),
            output_byte_size / sizeof(float),
            scale_factor,
            offset,
            image.size(),
            model_name);

        all_detections.insert(all_detections.end(), detections.begin(), detections.end());
    }

    return all_detections;
}

std::tuple<cv::Mat, float, cv::Point2f> TritonInference::preprocess(const cv::Mat &image)
{
    float ratio = std::min(
        static_cast<float>(input_w_) / image.cols,
        static_cast<float>(input_h_) / image.rows);

    int new_width = static_cast<int>(image.cols * ratio);
    int new_height = static_cast<int>(image.rows * ratio);

    cv::Mat resized;
    cv::resize(image, resized, cv::Size(new_width, new_height));

    cv::Mat canvas = cv::Mat::zeros(input_h_, input_w_, CV_8UC3);
    cv::Point2f offset(
        (input_w_ - new_width) / 2.0f,
        (input_h_ - new_height) / 2.0f);

    resized.copyTo(canvas(cv::Rect(
        offset.x, offset.y, new_width, new_height)));

    return {canvas, ratio, offset};
}

std::vector<float> TritonInference::prepare_input(const cv::Mat &image)
{
    cv::Mat blob;
    cv::dnn::blobFromImage(image, blob, 1.0 / 255.0,
                           cv::Size(input_w_, input_h_),
                           cv::Scalar(0, 0, 0), true, false, CV_32F);

    std::vector<float> input_data(blob.ptr<float>(),
                                  blob.ptr<float>() + blob.total());
    return input_data;
}

// ... 前面的代码保持不变 ...

std::vector<TritonInference::Detection> TritonInference::postprocess(
    const float *output_data,
    size_t output_size,
    float scale_factor,
    const cv::Point2f &offset,
    const cv::Size &original_size,
    const std::string &model_name) // 添加缺失的 model_name 参数
{
    const int num_classes = 80;
    const int num_anchors = 8400;
    std::vector<Detection> detections;

    // 重新组织输出数据为 [num_anchors, num_classes + 4] 格式
    std::vector<std::vector<float>> output_reshaped(num_anchors,
                                                    std::vector<float>(num_classes + 4));
    for (int i = 0; i < num_anchors; ++i)
    {
        for (int j = 0; j < num_classes + 4; ++j)
        {
            output_reshaped[i][j] = output_data[j * num_anchors + i];
        }
    }

    // 处理每个候选框
    for (int i = 0; i < num_anchors; ++i)
    {
        const auto &anchor_data = output_reshaped[i];

        // 获取类别得分
        float max_conf = 0;
        int class_id = -1;
        for (int j = 4; j < num_classes + 4; ++j)
        {
            if (anchor_data[j] > max_conf)
            {
                max_conf = anchor_data[j];
                class_id = j - 4;
            }
        }

        // 应用置信度阈值
        if (max_conf > 0.25f && class_id >= 0)
        {
            // 解析边界框坐标
            float cx = anchor_data[0];
            float cy = anchor_data[1];
            float w = anchor_data[2];
            float h = anchor_data[3];

            // 转换为左上角和右下角坐标
            float x1 = (cx - w / 2 - offset.x) / scale_factor;
            float y1 = (cy - h / 2 - offset.y) / scale_factor;
            float x2 = (cx + w / 2 - offset.x) / scale_factor;
            float y2 = (cy + h / 2 - offset.y) / scale_factor;

            // 确保坐标在图像范围内
            x1 = std::max(0.0f, std::min(x1, static_cast<float>(original_size.width)));
            y1 = std::max(0.0f, std::min(y1, static_cast<float>(original_size.height)));
            x2 = std::max(0.0f, std::min(x2, static_cast<float>(original_size.width)));
            y2 = std::max(0.0f, std::min(y2, static_cast<float>(original_size.height)));

            Detection det;
            det.box = cv::Rect(x1, y1, x2 - x1, y2 - y1);
            det.confidence = max_conf;
            det.class_id = class_id;
            det.model_name = model_name; // 设置模型名称
            detections.push_back(det);
        }
    }

    // 执行NMS
    std::vector<cv::Rect> boxes;
    std::vector<float> scores;
    std::vector<int> class_ids;
    for (const auto &det : detections)
    {
        boxes.push_back(det.box);
        scores.push_back(det.confidence);
        class_ids.push_back(det.class_id);
    }

    std::vector<int> indices;
    cv::dnn::NMSBoxes(boxes, scores, 0.25f, 0.45f, indices);

    // 保留NMS后的检测框
    std::vector<Detection> final_detections;
    for (int idx : indices)
    {
        final_detections.push_back(detections[idx]);
    }

    return final_detections;
}



2.9 frame_processor.h
#pragma once
#include <string>
#include <memory>
#include <nlohmann/json.hpp>
#include "triton_inference.h"
#include "config_manager.h"
#include "alarm_manager.h"
#include <opencv2/opencv.hpp>

class FrameProcessor {
public:
    FrameProcessor(const std::string& save_path, const CameraConfig& config, std::shared_ptr<AlarmManager> alarm_manager);
    void processFrame(const cv::Mat& frame);
    void sendAlarmToUpperLayer(const nlohmann::json& alarm_json);
    void printDetectionResults();

private:
    void saveFrame(const cv::Mat& frame);
    void cleanOldFrames();

   
    TritonInference triton_client_;  
    std::shared_ptr<AlarmManager> alarm_manager_;  
    CameraConfig config_; 
    std::string save_path_;
};

2.10 logger.h
#pragma once
#include <string>
#include <fstream>
#include <mutex>

enum class LogLevel {
    DEBUG,
    INFO,
    WARNING,
    ERROR
};

class Logger {
public:
    static void init(const std::string& log_file);
    static void log(LogLevel level, const std::string& message);

    static void debug(const std::string& message) {
        log(LogLevel::DEBUG, message);
    }
    
    static void info(const std::string& message) {
        log(LogLevel::INFO, message);
    }
    
    static void warning(const std::string& message) {
        log(LogLevel::WARNING, message);
    }
    
    static void error(const std::string& message) {
        log(LogLevel::ERROR, message);
    }

private:
    static std::ofstream log_file_;
    static std::mutex mutex_;
    static std::string getTimestamp();
    static std::string getLevelString(LogLevel level);
};

#define LOG_DEBUG(msg) Logger::debug(msg)
#define LOG_INFO(msg) Logger::info(msg)
#define LOG_WARNING(msg) Logger::warning(msg)
#define LOG_ERROR(msg) Logger::error(msg)

2.11 logger.cpp
#include "logger.h"
#include <chrono>
#include <iomanip>
#include <iostream>

std::ofstream Logger::log_file_;
std::mutex Logger::mutex_;

void Logger::init(const std::string& log_file) {
    std::lock_guard<std::mutex> lock(mutex_);
    log_file_.open(log_file, std::ios::app);
}

void Logger::log(LogLevel level, const std::string& message) {
    std::lock_guard<std::mutex> lock(mutex_);
    
    std::string log_message = getTimestamp() + " [" + 
                             getLevelString(level) + "] " + 
                             message + "\n";
    
    // 输出到控制台
    std::cout << log_message;
    
    // 写入日志文件
    if (log_file_.is_open()) {
        log_file_ << log_message;
        log_file_.flush();
    }
}

std::string Logger::getTimestamp() {
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    std::stringstream ss;
    ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S");
    return ss.str();
}

std::string Logger::getLevelString(LogLevel level) {
    switch (level) {
        case LogLevel::DEBUG:   return "DEBUG";
        case LogLevel::INFO:    return "INFO";
        case LogLevel::WARNING: return "WARNING";
        case LogLevel::ERROR:   return "ERROR";
        default:               return "UNKNOWN";
    }
}

2.12 frame_processor.cpp
#include "frame_processor.h"
#include "logger.h"
#include <filesystem>
#include <chrono>
#include <ctime>

namespace fs = std::filesystem;

FrameProcessor::FrameProcessor(const std::string &save_path, const CameraConfig &config, std::shared_ptr<AlarmManager> alarm_manager)
    : save_path_(save_path), config_(config), alarm_manager_(alarm_manager), triton_client_("192.168.20.95:9000", config.algorithms)
{
    std::cout << "constuct FrameProcessor: triton_client_" << std::endl;
    fs::create_directories(save_path_);
}

void FrameProcessor::processFrame(const cv::Mat &frame)
{
    // 保存原始帧
    saveFrame(frame);

    // 清理旧文件
    cleanOldFrames();

    // 调用Triton进行推理
    try
    {
        auto detections = triton_client_.infer(frame);
        
        //std::cout << "Detection Results:" << std::endl;
        printDetectionResults();
        for (const auto &det : detections)
        {
            std::cout << "Class: " << det.class_id
                      << ", Confidence: " << det.confidence
                      << ", Box: [" << det.box.x << ", " << det.box.y << ", "
                      << det.box.width << ", " << det.box.height << "]" << std::endl;
        }

        // 处理检测结果
        if (!detections.empty())
        {
            // 生成报警信息
            // nlohmann::json alarm_info = {
            //     {"camera_name", config_.name},
            //     {"timestamp", std::chrono::system_clock::now().time_since_epoch().count()},
            //     {"detections", nlohmann::json::array()}};

            // for (const auto &det : detections)
            // {
            //     alarm_info["detections"].push_back({{"model_name", det.model_name},
            //                                         {"class_id", det.class_id},
            //                                         {"confidence", det.confidence},
            //                                         {"box", {{"x", det.box.x}, {"y", det.box.y}, {"width", det.box.width}, {"height", det.box.height}}}});
            // }

            // 发送报警信息到报警管理器
            alarm_manager_->handleAlarm(config_, detections);

            // // 发送报警信息到上层应用
            // sendAlarmToUpperLayer(alarm_info);
        }
    }
    catch (const std::exception &e)
    {
        LOG_ERROR("Failed to process frame for camera " + config_.name + ": " + e.what());
    }
}

void FrameProcessor::printDetectionResults() {
    // 获取当前时间点
    auto now = std::chrono::system_clock::now();

    // 将时间点转换为时间戳
    std::time_t now_c = std::chrono::system_clock::to_time_t(now);

    // 将时间戳格式化为字符串
    char buffer[80];
    strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", std::localtime(&now_c));

    // 打印检测结果和当前时间
    std::cout << "Detection Results at " << buffer << ":" << std::endl;
}

void FrameProcessor::sendAlarmToUpperLayer(const nlohmann::json &alarm_json)
{
    // 实现发送报警信息的逻辑，比如通过HTTP POST发送到上层Java应用
    std::cout << "Sending alarm: " << alarm_json.dump() << std::endl;
}

void FrameProcessor::saveFrame(const cv::Mat &frame)
{
    auto now = std::chrono::system_clock::now();
    auto timestamp = std::chrono::system_clock::to_time_t(now);

    std::string filename = save_path_ + "/" + config_.name + "_" +
                           std::to_string(timestamp) + ".jpg";

    cv::imwrite(filename, frame);
}

void FrameProcessor::cleanOldFrames()
{
    const int MAX_AGE_MINUTES = 10;
    auto now = fs::file_time_type::clock::now();

    for (const auto &entry : fs::directory_iterator(save_path_))
    {
        auto last_write_time = fs::last_write_time(entry.path());
        auto duration = std::chrono::duration_cast<std::chrono::minutes>(now - last_write_time);

        if (duration.count() > MAX_AGE_MINUTES)
        {
            fs::remove(entry.path());
        }
    }
}

2.13 main.cpp
#include <iostream>
#include <thread>
#include <vector>
#include "config_manager.h"
#include "video_capture.h"
#include "logger.h"
#include "alarm_manager.h"

int main() {
    Logger::init("video_analysis_service.log");
    LOG_INFO("Starting Video Analysis Service...");

    // 初始化 GStreamer
    gst_init(nullptr, nullptr);

    // 加载配置
    if (!ConfigManager::getInstance().loadConfig("../config/camera_config.ini")) {
        LOG_ERROR("Failed to load configuration");
        return -1;
    }
    LOG_INFO("Configuration loaded successfully");

    // 初始化组件
    std::vector<std::unique_ptr<VideoCapture>> video_captures;

    // 创建一个共享的报警管理器实例
    auto alarm_manager = std::make_shared<AlarmManager>("./alarm_logs");

    // 启动视频捕获
    for (const auto& config : ConfigManager::getInstance().getCameraConfigs()) {
        auto capture = std::make_unique<VideoCapture>(config);

        // 创建带有自己TritonInference客户端的FrameProcessor
        auto frame_processor = std::make_shared<FrameProcessor>("./frames", config, alarm_manager);

        // 设置帧处理器
        capture->setFrameProcessor(frame_processor);

        if (!capture->start()) {
            LOG_ERROR("Failed to start video capture for camera: " + config.name);
            continue;
        }
        video_captures.push_back(std::move(capture));
        LOG_INFO("Started video capture for camera: " + config.name);
    }

    // 主循环
    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }

    gst_deinit();
    return 0;
}

2.16.配置文件内容(camera_config.ini)
[Camera1]
name=room1
rtsp_url=rtsp://117.71.99.41:5544/live/34020000001180000002_34020000001320000027?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOjAsIlVzZXJuYW1lIjoiMzQwMjAwMDAwMDExODAwMDAwMDJfMzQwMjAwMDAwMDEzMjAwMDAwMjciLCJHcm91cElEIjowLCJpc3MiOiJ4eEBnb2xhbmcuc3BhY2UiLCJleHAiOjE3Mzc3ODgyMjgsImlhdCI6MTczMTU2NzQyOH0.tC862jJ0F_aISNrzck4hIFPZeqydPAe08WeZPbZSzL4&expired=20250125145708
algorithms=peoplecarboat_detector
frame_interval=5000
alarm_strategy=peoplecarboat_detector:person_count>2,peoplecarboat_detector:vehicle_present=true
alarm_storage_path=/data/alarms/camera1

[Camera2]
name=room2
rtsp_url=rtsp://117.71.99.41:5544/live/34020000001180000002_34020000001320000003?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOjAsIlVzZXJuYW1lIjoiMzQwMjAwMDAwMDExODAwMDAwMDJfMzQwMjAwMDAwMDEzMjAwMDAwMDMiLCJHcm91cElEIjowLCJpc3MiOiJ4eEBnb2xhbmcuc3BhY2UiLCJleHAiOjE3Mzc3ODgyNjIsImlhdCI6MTczMTU2NzQ2Mn0.7GVR-331DomajNmac6ZBrbphAfgClQgH1VHzJptWDI4&expired=20250125145742
algorithms=peoplecarboat_detector
frame_interval=5000
alarm_strategy=peoplecarboat_detector:boat_present=true,peoplecarboat_detector:illegal_fishing=true
alarm_storage_path=/data/alarms/camera2

2.17.makefile文件内容(CMakeLists.txt)
cmake_minimum_required(VERSION 3.10)
project(video_analysis_service)

# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找必要的包
find_package(OpenCV REQUIRED)
find_package(CURL REQUIRED)
find_package(PkgConfig REQUIRED)
find_package(ZLIB REQUIRED)
pkg_check_modules(GST REQUIRED gstreamer-1.0 gstreamer-app-1.0)

# 设置Triton Client的路径
set(TRITON_CLIENT_PATH "/home/chen/data/client-main/build/install/client")

# 添加头文件路径
include_directories(
    ${PROJECT_SOURCE_DIR}/include
    ${TRITON_CLIENT_PATH}/include
    ${OpenCV_INCLUDE_DIRS}
    ${GST_INCLUDE_DIRS}
    ${CURL_INCLUDE_DIRS}
)

# 添加源文件
set(SOURCES
    src/main.cpp
    src/config_manager.cpp
    src/video_capture.cpp
    src/frame_processor.cpp
    src/alarm_manager.cpp
    src/logger.cpp
    src/triton_inference.cpp
)

# 添加可执行文件
add_executable(${PROJECT_NAME} ${SOURCES})

# 链接库
link_directories(${TRITON_CLIENT_PATH}/lib)

# 链接库
target_link_libraries(${PROJECT_NAME}
    PRIVATE
    ${OpenCV_LIBS}
    ${GST_LIBRARIES}
    ${CURL_LIBRARIES}
    httpclient_static
    grpcclient_static
    curl
    ZLIB::ZLIB
    pthread
    stdc++fs
)

# 设置编译选项
target_compile_options(${PROJECT_NAME}
    PRIVATE
    -Wall
    -Wextra
    -O2
)

】