备份最新边缘设备程序
This commit is contained in:
@@ -0,0 +1,802 @@
|
||||
#include <cuda_runtime_api.h>
|
||||
#include <glib.h>
|
||||
#include <gmodule.h>
|
||||
#include <gst/gst.h>
|
||||
#include <locale.h>
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ds_yml_parse.h"
|
||||
#include "nvds_yml_parser.h"
|
||||
#include "nvdsmeta.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gstnvdsmeta.h"
|
||||
#include "nvds_analytics_meta.h"
|
||||
|
||||
#include "gst-nvmessage.h"
|
||||
#include <map>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "vehicle_event_service.h"
|
||||
#include <yaml-cpp/yaml.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
#include "nvbufsurface.h"
|
||||
#include "nvds_obj_encode.h"
|
||||
// 文本显示最大长度
|
||||
#define MAX_DISPLAY_LEN 256
|
||||
// 主检测器中车辆的类别ID
|
||||
#define PGIE_CLASS_ID_VEHICLE 0
|
||||
// nvstreammux 输出分辨率
|
||||
#define MUXER_OUTPUT_WIDTH 1920
|
||||
#define MUXER_OUTPUT_HEIGHT 1080
|
||||
// 多路视频 batch 形成的超时时间(微秒)
|
||||
#define MUXER_BATCH_TIMEOUT_USEC 4000000
|
||||
// 错误处理宏
|
||||
#define RETURN_ON_PARSER_ERROR(parse_expr) \
|
||||
if (NVDS_YAML_PARSER_SUCCESS != parse_expr) { \
|
||||
g_printerr("Error in parsing configuration file.\n"); \
|
||||
return -1; \
|
||||
}
|
||||
|
||||
/**
|
||||
* 唯一组件ID定义
|
||||
*/
|
||||
#define SECONDARY_TRAFFIC_UID 1
|
||||
#define SECONDARY_VEHICLE_TYPE_UID 2
|
||||
/* NVIDIA Decoder source pad memory feature. This feature signifies that source
|
||||
* pads having this capability will push GstBuffers containing cuda buffers. */
|
||||
#define GST_CAPS_FEATURES_NVMM "memory:NVMM"
|
||||
|
||||
gchar *yaml_file_path = nullptr;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
void cleanTempFolder() {
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::hours(3)); // 等待3h后清理一次
|
||||
|
||||
try {
|
||||
for (auto &entry : fs::directory_iterator("./temp")) {
|
||||
fs::remove_all(entry.path());
|
||||
}
|
||||
std::cout << "Temp folder cleaned.\n";
|
||||
} catch (const fs::filesystem_error &e) {
|
||||
std::cerr << "Error cleaning temp folder: " << e.what() << "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void setYamlFilePath(const gchar *name) {
|
||||
if (yaml_file_path)
|
||||
free(yaml_file_path); // 释放旧路径
|
||||
yaml_file_path = (gchar *)malloc(strlen(name) + 1);
|
||||
strcpy(yaml_file_path, name);
|
||||
}
|
||||
|
||||
struct SourceInfo {
|
||||
std::string main_uri;
|
||||
std::string side_rtsp_uri;
|
||||
};
|
||||
|
||||
bool parse_source_list(const std::string &cfg_file,
|
||||
std::vector<SourceInfo> &out_list) {
|
||||
try {
|
||||
YAML::Node root = YAML::LoadFile(cfg_file);
|
||||
if (!root["source-list"]) {
|
||||
std::cerr << "[ERR] source-list not found in YAML\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
YAML::Node sources = root["source-list"];
|
||||
if (!sources.IsSequence()) {
|
||||
std::cerr << "[ERR] source-list should be a sequence\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
out_list.clear();
|
||||
for (auto node : sources) {
|
||||
if (!node["main"] || !node["side"]) {
|
||||
std::cerr << "[WARN] source missing main or side, skipping\n";
|
||||
continue;
|
||||
}
|
||||
SourceInfo info;
|
||||
info.main_uri = node["main"].as<std::string>();
|
||||
info.side_rtsp_uri = node["side"].as<std::string>();
|
||||
out_list.push_back(info);
|
||||
}
|
||||
} catch (const YAML::Exception &e) {
|
||||
std::cerr << "[ERR] YAML parse error: " << e.what() << "\n";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心算法逻辑
|
||||
*/
|
||||
static GstPadProbeReturn
|
||||
osd_sink_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info, gpointer ctx) {
|
||||
GstBuffer *buf = (GstBuffer *)info->data;
|
||||
NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf);
|
||||
NvDsObjEncCtxHandle enc_ctx = reinterpret_cast<NvDsObjEncCtxHandle>(ctx);
|
||||
|
||||
//=======================取帧流程========================
|
||||
GstMapInfo inmap = GST_MAP_INFO_INIT;
|
||||
if (!gst_buffer_map(buf, &inmap, GST_MAP_READ)) {
|
||||
GST_ERROR("input buffer mapinfo failed");
|
||||
return GST_PAD_PROBE_DROP;
|
||||
}
|
||||
NvBufSurface *ip_surf = (NvBufSurface *)inmap.data;
|
||||
gst_buffer_unmap(buf, &inmap);
|
||||
//=======================取帧流程========================
|
||||
for (NvDsMetaList *l_frame = batch_meta->frame_meta_list; l_frame;
|
||||
l_frame = l_frame->next) {
|
||||
NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)(l_frame->data);
|
||||
guint source_id = frame_meta->source_id;
|
||||
if (!frame_meta)
|
||||
continue;
|
||||
|
||||
// 遍历对象,只处理车辆
|
||||
for (NvDsMetaList *l_obj = frame_meta->obj_meta_list; l_obj;
|
||||
l_obj = l_obj->next) {
|
||||
NvDsObjectMeta *obj_meta = (NvDsObjectMeta *)(l_obj->data);
|
||||
// 只处理车辆
|
||||
if (!obj_meta ||
|
||||
(obj_meta->unique_component_id == SECONDARY_TRAFFIC_UID &&
|
||||
obj_meta->class_id != PGIE_CLASS_ID_VEHICLE))
|
||||
continue;
|
||||
|
||||
guint64 object_id = obj_meta->object_id;
|
||||
Record vehicle;
|
||||
vehicle.object_id = object_id;
|
||||
vehicle.source_id = source_id;
|
||||
|
||||
// 二级分类模型
|
||||
for (NvDsMetaList *l_class = obj_meta->classifier_meta_list; l_class;
|
||||
l_class = l_class->next) {
|
||||
NvDsClassifierMeta *class_meta = (NvDsClassifierMeta *)(l_class->data);
|
||||
if (!class_meta)
|
||||
continue;
|
||||
|
||||
// 车型分类
|
||||
if (class_meta->unique_component_id == SECONDARY_VEHICLE_TYPE_UID) {
|
||||
for (NvDsMetaList *l_label = class_meta->label_info_list; l_label;
|
||||
l_label = l_label->next) {
|
||||
NvDsLabelInfo *label_info = (NvDsLabelInfo *)(l_label->data);
|
||||
if (!label_info || label_info->label_id != 0)
|
||||
continue;
|
||||
if (label_info->result_prob > 0.0f)
|
||||
vehicle.vehicle_type = label_info->result_label;
|
||||
}
|
||||
}
|
||||
update_object_base_info(source_id, vehicle.object_id,
|
||||
vehicle.vehicle_type);
|
||||
}
|
||||
}
|
||||
|
||||
// 跨线检测,只触发车辆
|
||||
for (NvDsMetaList *l_user = frame_meta->frame_user_meta_list; l_user;
|
||||
l_user = l_user->next) {
|
||||
NvDsUserMeta *user_meta = (NvDsUserMeta *)(l_user->data);
|
||||
if (!user_meta ||
|
||||
user_meta->base_meta.meta_type != NVDS_USER_FRAME_META_NVDSANALYTICS)
|
||||
continue;
|
||||
|
||||
NvDsAnalyticsFrameMeta *meta =
|
||||
(NvDsAnalyticsFrameMeta *)user_meta->user_meta_data;
|
||||
// 跨线事件
|
||||
for (const auto &status : meta->objLCCurrCnt) {
|
||||
std::string line_id = status.first;
|
||||
int cross_count = status.second;
|
||||
if (cross_count <= 0)
|
||||
continue;
|
||||
|
||||
// 回到遍历帧对象,寻找对应的 object_id
|
||||
for (NvDsMetaList *l_obj = frame_meta->obj_meta_list; l_obj;
|
||||
l_obj = l_obj->next) {
|
||||
NvDsObjectMeta *obj_meta = (NvDsObjectMeta *)(l_obj->data);
|
||||
if (!obj_meta || obj_meta->class_id != PGIE_CLASS_ID_VEHICLE)
|
||||
continue;
|
||||
|
||||
// 遍历对象中
|
||||
for (NvDsMetaList *l_user_meta = obj_meta->obj_user_meta_list;
|
||||
l_user_meta != NULL; l_user_meta = l_user_meta->next) {
|
||||
NvDsUserMeta *user_meta = (NvDsUserMeta *)(l_user_meta->data);
|
||||
|
||||
if (user_meta->base_meta.meta_type ==
|
||||
NVDS_USER_OBJ_META_NVDSANALYTICS) {
|
||||
if (line_id == "LineA") {
|
||||
trigger_line_a_capture(source_id, obj_meta->object_id);
|
||||
} else if (line_id == "LineB") {
|
||||
// =================截图流程========================start
|
||||
NvDsObjEncUsrArgs objData = {0};
|
||||
objData.saveImg = true;
|
||||
objData.attachUsrMeta = true;
|
||||
objData.scaleImg = FALSE;
|
||||
objData.scaledWidth = 0;
|
||||
objData.scaledHeight = 0;
|
||||
objData.quality = 100;
|
||||
char fileNameImgNoPath[FILE_NAME_SIZE];
|
||||
snprintf(objData.fileNameImg, FILE_NAME_SIZE,
|
||||
"./temp/%d_%d_%ld_%dx%d.jpg", frame_meta->frame_num,
|
||||
source_id, obj_meta->object_id,
|
||||
(int)obj_meta->rect_params.width,
|
||||
(int)obj_meta->rect_params.height);
|
||||
|
||||
nvds_obj_enc_process(enc_ctx, &objData, ip_surf, obj_meta,
|
||||
frame_meta);
|
||||
nvds_obj_enc_finish(enc_ctx);
|
||||
|
||||
// 去掉 "./temp/" 前缀
|
||||
snprintf(fileNameImgNoPath, FILE_NAME_SIZE, "%s",
|
||||
objData.fileNameImg + 7); // "./temp/" 长度为7
|
||||
// 传去掉前缀的文件名
|
||||
trigger_line_b_upload(source_id, obj_meta->object_id,
|
||||
fileNameImgNoPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return GST_PAD_PROBE_OK;
|
||||
}
|
||||
|
||||
static gboolean bus_call(GstBus *bus, GstMessage *msg, gpointer data) {
|
||||
GMainLoop *loop = (GMainLoop *)data;
|
||||
switch (GST_MESSAGE_TYPE(msg)) {
|
||||
case GST_MESSAGE_EOS:
|
||||
g_print("End of stream\n");
|
||||
g_main_loop_quit(loop);
|
||||
break;
|
||||
case GST_MESSAGE_WARNING: {
|
||||
gchar *debug = NULL;
|
||||
GError *error = NULL;
|
||||
gst_message_parse_warning(msg, &error, &debug);
|
||||
g_printerr("WARNING from element %s: %s\n", GST_OBJECT_NAME(msg->src),
|
||||
error->message);
|
||||
g_free(debug);
|
||||
g_printerr("Warning: %s\n", error->message);
|
||||
g_error_free(error);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_ERROR: {
|
||||
gchar *debug = NULL;
|
||||
GError *error = NULL;
|
||||
gst_message_parse_error(msg, &error, &debug);
|
||||
g_printerr("ERROR from element %s: %s\n", GST_OBJECT_NAME(msg->src),
|
||||
error->message);
|
||||
if (debug)
|
||||
g_printerr("Error details: %s\n", debug);
|
||||
g_free(debug);
|
||||
g_error_free(error);
|
||||
g_main_loop_quit(loop);
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_ELEMENT: {
|
||||
if (gst_nvmessage_is_stream_eos(msg)) {
|
||||
guint stream_id = 0;
|
||||
if (gst_nvmessage_parse_stream_eos(msg, &stream_id)) {
|
||||
g_print("Got EOS from stream %d\n", stream_id);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void cb_newpad(GstElement *decodebin, GstPad *decoder_src_pad,
|
||||
gpointer data) {
|
||||
GstCaps *caps = gst_pad_get_current_caps(decoder_src_pad);
|
||||
if (!caps)
|
||||
caps = gst_pad_query_caps(decoder_src_pad, NULL);
|
||||
|
||||
const GstStructure *str = gst_caps_get_structure(caps, 0);
|
||||
const gchar *name = gst_structure_get_name(str);
|
||||
GstCapsFeatures *features = gst_caps_get_features(caps, 0);
|
||||
|
||||
GstElement *source_bin = (GstElement *)data;
|
||||
/* 只接 video */
|
||||
if (!strncmp(name, "video", 5)) {
|
||||
/* 必须是 NVMM(GPU 内存) */
|
||||
if (gst_caps_features_contains(features, GST_CAPS_FEATURES_NVMM)) {
|
||||
GstPad *ghost_pad = gst_element_get_static_pad(source_bin, "src");
|
||||
if (!gst_ghost_pad_set_target(GST_GHOST_PAD(ghost_pad),
|
||||
decoder_src_pad)) {
|
||||
g_printerr("Failed to link decoder src pad to source bin ghost pad\n");
|
||||
}
|
||||
|
||||
gst_object_unref(ghost_pad);
|
||||
}
|
||||
}
|
||||
gst_caps_unref(caps);
|
||||
}
|
||||
|
||||
static inline const char *infer_plugin(NvDsGieType type) {
|
||||
switch (type) {
|
||||
case NVDS_GIE_PLUGIN_INFER:
|
||||
return "nvinfer";
|
||||
case NVDS_GIE_PLUGIN_INFER_SERVER:
|
||||
return "nvinferserver";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void decodebin_child_added(GstChildProxy *child_proxy, GObject *object,
|
||||
gchar *name, gpointer user_data) {
|
||||
g_print("Decodebin child added: %s\n", name);
|
||||
if (g_strrstr(name, "decodebin") == name) {
|
||||
g_signal_connect(G_OBJECT(object), "child-added",
|
||||
G_CALLBACK(decodebin_child_added), user_data);
|
||||
}
|
||||
if (g_strrstr(name, "source") == name) {
|
||||
g_object_set(G_OBJECT(object), "drop-on-latency", true, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static GstElement *create_source_bin(guint index, gchar *uri) {
|
||||
GstElement *bin = NULL, *uri_decode_bin = NULL;
|
||||
gchar name[64];
|
||||
|
||||
g_snprintf(name, sizeof(name), "source-bin-%02d", index);
|
||||
bin = gst_bin_new(name);
|
||||
|
||||
/* 7×24 强烈建议用 nvurisrcbin */
|
||||
uri_decode_bin = gst_element_factory_make("nvurisrcbin", NULL);
|
||||
if (!bin || !uri_decode_bin)
|
||||
return NULL;
|
||||
|
||||
g_object_set(G_OBJECT(uri_decode_bin), "uri", uri, NULL); // 地址
|
||||
|
||||
// "reconnect-interval", 5, // RTSP
|
||||
// 断流重连 "drop-on-latency", TRUE, //
|
||||
// 保实时 NULL);
|
||||
|
||||
g_signal_connect(uri_decode_bin, "pad-added", G_CALLBACK(cb_newpad), bin);
|
||||
|
||||
g_signal_connect(G_OBJECT(uri_decode_bin), "child-added",
|
||||
G_CALLBACK(decodebin_child_added), bin);
|
||||
gst_bin_add(GST_BIN(bin), uri_decode_bin);
|
||||
|
||||
/* 对外只暴露一个 src */
|
||||
|
||||
if (!gst_element_add_pad(bin,
|
||||
gst_ghost_pad_new_no_target("src", GST_PAD_SRC))) {
|
||||
g_printerr("Failed to add ghost pad in source bin\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return bin;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
setYamlFilePath(argv[1]);
|
||||
GMainLoop *loop = NULL;
|
||||
GstElement *pipeline = NULL, *streammux = NULL, *sink = NULL,
|
||||
*nvvidconv = NULL, *nvosd = NULL, *nvvidconv1 = NULL,
|
||||
*outenc = NULL, *capfilt = NULL, *mux = NULL, *nvtile = NULL,
|
||||
*encparse = NULL, *rtppay = NULL;
|
||||
|
||||
GstElement *primary_detector = NULL, *secondary_vt = NULL, *tracker = NULL,
|
||||
*nvdsanalytics = NULL;
|
||||
|
||||
GstElement *queue1 = NULL, *queue2 = NULL, *queue3 = NULL, *queue4 = NULL,
|
||||
*queue5 = NULL, *queue6 = NULL, *queue7 = NULL, *queue8 = NULL,
|
||||
*queue9 = NULL, *queue10 = NULL, *queue11 = NULL;
|
||||
GstBus *bus = NULL;
|
||||
// 用来挂 probe 的 pad,临时 pad 指针,用来 link
|
||||
GstPad *osd_sink_pad = NULL;
|
||||
// 消息总线回调ID、多路视频拼图行数、列数
|
||||
guint bus_watch_id;
|
||||
|
||||
static guint udp_sink_port_num = 5400;
|
||||
int enc_type = ENCODER_TYPE_HW;
|
||||
std::vector<SourceInfo> sources;
|
||||
|
||||
// 推理插件类型
|
||||
NvDsGieType pgie_type = NVDS_GIE_PLUGIN_INFER;
|
||||
NvDsGieType sgie1_type = NVDS_GIE_PLUGIN_INFER;
|
||||
|
||||
if (argc != 2) {
|
||||
g_printerr("Usage: %s <yml file>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// CUDA设备查询
|
||||
int current_device = -1;
|
||||
cudaGetDevice(¤t_device);
|
||||
// 获取CUDA设备属性 例如是否为集成GPU
|
||||
struct cudaDeviceProp prop;
|
||||
cudaGetDeviceProperties(&prop, current_device);
|
||||
|
||||
// 初始化 GStreamer
|
||||
gst_init(&argc, &argv);
|
||||
loop = g_main_loop_new(NULL, FALSE);
|
||||
|
||||
// 创建gstreamer管道
|
||||
pipeline = gst_pipeline_new("pipeline");
|
||||
if (!pipeline) {
|
||||
g_printerr("Pipeline could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
streammux = gst_element_factory_make("nvstreammux", "stream-muxer");
|
||||
if (!streammux) {
|
||||
g_printerr("Streammux could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
// 管道加入nv-stream-mux
|
||||
gst_bin_add(GST_BIN(pipeline), streammux);
|
||||
|
||||
// 解析YAML
|
||||
if (!parse_source_list(yaml_file_path, sources)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RETURN_ON_PARSER_ERROR(
|
||||
nvds_parse_gie_type(&pgie_type, yaml_file_path, "primary-gie"));
|
||||
RETURN_ON_PARSER_ERROR(
|
||||
nvds_parse_gie_type(&sgie1_type, yaml_file_path, "secondary-gie1"));
|
||||
|
||||
for (size_t src_cnt = 0; src_cnt < sources.size(); ++src_cnt) {
|
||||
const auto &src_info = sources[src_cnt]; // 当前主摄像头和侧摄像头地址
|
||||
g_source_side_map[src_cnt] = src_info.side_rtsp_uri;
|
||||
|
||||
GstElement *source_bin =
|
||||
create_source_bin(src_cnt, (gchar *)src_info.main_uri.c_str());
|
||||
if (!source_bin) {
|
||||
g_printerr("Failed to create source bin. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
gst_bin_add(GST_BIN(pipeline), source_bin);
|
||||
gchar pad_name[16];
|
||||
g_snprintf(pad_name, sizeof(pad_name), "sink_%zu", src_cnt);
|
||||
GstPad *sinkpad = gst_element_request_pad_simple(streammux, pad_name);
|
||||
if (!sinkpad)
|
||||
return -1;
|
||||
|
||||
GstPad *srcpad = gst_element_get_static_pad(source_bin, "src");
|
||||
if (!srcpad)
|
||||
return -1;
|
||||
|
||||
if (gst_pad_link(srcpad, sinkpad) != GST_PAD_LINK_OK)
|
||||
return -1;
|
||||
|
||||
gst_object_unref(srcpad);
|
||||
gst_object_unref(sinkpad);
|
||||
}
|
||||
|
||||
// 创建推理器元素
|
||||
primary_detector = gst_element_factory_make(infer_plugin(pgie_type),
|
||||
"primary-infer-engine1");
|
||||
if (!primary_detector) {
|
||||
g_printerr("Primary detector could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
secondary_vt = gst_element_factory_make(infer_plugin(sgie1_type),
|
||||
"secondary-infer-engine1");
|
||||
if (!secondary_vt) {
|
||||
g_printerr("Secondary VT could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
// 创建队列元素
|
||||
queue1 = gst_element_factory_make("queue", "queue1");
|
||||
queue2 = gst_element_factory_make("queue", "queue2");
|
||||
queue3 = gst_element_factory_make("queue", "queue3");
|
||||
queue4 = gst_element_factory_make("queue", "queue4");
|
||||
queue5 = gst_element_factory_make("queue", "queue5");
|
||||
queue6 = gst_element_factory_make("queue", "queue6");
|
||||
queue7 = gst_element_factory_make("queue", "queue7");
|
||||
queue8 = gst_element_factory_make("queue", "queue8");
|
||||
queue9 = gst_element_factory_make("queue", "queue9");
|
||||
queue10 = gst_element_factory_make("queue", "queue10");
|
||||
queue11 = gst_element_factory_make("queue", "queue11");
|
||||
// 添加队列和其他元素到管道
|
||||
gst_bin_add_many(GST_BIN(pipeline), queue1, queue2, queue3, queue4, queue5,
|
||||
queue6, queue7, queue8, queue9, queue10, queue11, NULL);
|
||||
|
||||
// 配置
|
||||
// ===============================================================================start
|
||||
// streammux 配置
|
||||
g_object_set(G_OBJECT(streammux), "width", MUXER_OUTPUT_WIDTH, "height",
|
||||
MUXER_OUTPUT_HEIGHT, "batch-size", sources.size(),
|
||||
"batched-push-timeout", MUXER_BATCH_TIMEOUT_USEC, NULL);
|
||||
// 主推理器:检测车辆/行人/交通标志/自行车
|
||||
nvds_parse_gie(primary_detector, yaml_file_path, "primary-gie");
|
||||
// 第二推理器:车辆类型分类:轿车/SUV/卡车等
|
||||
nvds_parse_gie(secondary_vt, yaml_file_path, "secondary-gie1");
|
||||
// 多目标跟踪
|
||||
tracker = gst_element_factory_make("nvtracker", "nvtracker");
|
||||
// 多目标跟踪
|
||||
nvds_parse_tracker(tracker, yaml_file_path, "tracker");
|
||||
// 跨线检测等高级分析
|
||||
nvdsanalytics = gst_element_factory_make("nvdsanalytics", "nvdsanalytics");
|
||||
ds_parse_nvdsanalytics(nvdsanalytics, yaml_file_path, "analytics");
|
||||
// 配置===============================================================================end
|
||||
|
||||
// 添加各个通用元素
|
||||
gst_bin_add_many(GST_BIN(pipeline), primary_detector, secondary_vt, tracker,
|
||||
nvdsanalytics, NULL);
|
||||
// 链接各个通用元素
|
||||
if (!gst_element_link_many(streammux, queue1, primary_detector, queue2,
|
||||
tracker, queue3, nvdsanalytics, queue4,
|
||||
secondary_vt, queue5, NULL)) {
|
||||
g_printerr("Inferring and tracking elements link failure.\n");
|
||||
return -1;
|
||||
}
|
||||
// 输出方式 默认2:fakesink
|
||||
guint output_type = 2;
|
||||
output_type = ds_parse_group_type(yaml_file_path, "output");
|
||||
if (output_type == 2) {
|
||||
// 假输出,生产环境用
|
||||
if (!gst_element_link_many(queue5, queue11, NULL)) {
|
||||
g_printerr("Inferring and tracking elements link failure.\n");
|
||||
return -1;
|
||||
}
|
||||
sink = gst_element_factory_make("fakesink", "fake-renderer");
|
||||
g_object_set(G_OBJECT(sink), "sync", FALSE, "async", FALSE, NULL);
|
||||
} else {
|
||||
// 输出显示器、文件、RTSP
|
||||
|
||||
// 创建视频转换器,将 NV12 转 RGBA,以供 OSD 使用
|
||||
nvvidconv = gst_element_factory_make("nvvideoconvert", "nvvid-converter");
|
||||
if (!nvvidconv) {
|
||||
g_printerr("NVVideoConvert could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
// 创建 OSD,用于在 RGBA 上绘制文字和框
|
||||
nvosd = gst_element_factory_make("nvdsosd", "nv-onscreendisplay");
|
||||
if (!nvosd) {
|
||||
g_printerr("NvOSD could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
// 拼图
|
||||
nvtile = gst_element_factory_make("nvmultistreamtiler", "nvtiler");
|
||||
if (!nvtile) {
|
||||
g_printerr("nvmultistreamtiler could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
// 计算行列和每路尺寸
|
||||
guint columns = (guint)ceil(sqrt(sources.size()));
|
||||
guint rows = (guint)ceil((float)sources.size() / columns);
|
||||
guint src_width = 1280, src_height = 720;
|
||||
guint single_width = src_width / columns;
|
||||
guint single_height = single_width * 9 / 16;
|
||||
if (single_height * rows > src_height) {
|
||||
// 如果高度超出画布,按高度缩放
|
||||
single_height = src_height / rows;
|
||||
single_width = single_height * 16 / 9;
|
||||
}
|
||||
// 总画布尺寸
|
||||
guint total_width = single_width * columns;
|
||||
guint total_height = single_height * rows;
|
||||
// 设置 tiler
|
||||
g_object_set(G_OBJECT(nvtile), "rows", rows, "columns", columns, "width",
|
||||
total_width, "height", total_height, NULL);
|
||||
// 添加元素
|
||||
gst_bin_add_many(GST_BIN(pipeline), nvvidconv, nvosd, nvtile, NULL);
|
||||
if (!gst_element_link_many(queue5, nvtile, queue6, nvvidconv, queue7, nvosd,
|
||||
NULL)) {
|
||||
g_printerr("Inferring and tracking elements link failure.\n");
|
||||
return -1;
|
||||
}
|
||||
if (output_type == 3) {
|
||||
// 输出显示器
|
||||
if (!gst_element_link_many(nvosd, queue11, NULL)) {
|
||||
g_printerr("Inferring and tracking elements link failure.\n");
|
||||
return -1;
|
||||
}
|
||||
if (prop.integrated)
|
||||
sink = gst_element_factory_make("nv3dsink", "nv3d-sink");
|
||||
else
|
||||
#ifdef __aarch64__
|
||||
sink = gst_element_factory_make("nv3dsink", "nv3d-sink");
|
||||
#else
|
||||
sink = gst_element_factory_make("nveglglessink", "nvvideo-renderer");
|
||||
#endif
|
||||
} else {
|
||||
// 还有1 4
|
||||
|
||||
// 第二个视频转换器,OSD 后 → 编码前
|
||||
nvvidconv1 =
|
||||
gst_element_factory_make("nvvideoconvert", "nvvid-converter1");
|
||||
// 限制视频格式
|
||||
capfilt = gst_element_factory_make("capsfilter", "nvvideo-caps");
|
||||
// 编码类型
|
||||
bool isH264 = !(ds_parse_enc_codec(yaml_file_path, "output"));
|
||||
// 编码方式
|
||||
enc_type = ds_parse_enc_type(yaml_file_path, "output");
|
||||
if (output_type == 1) {
|
||||
// 输出文件
|
||||
// 创建编码器链
|
||||
create_video_encoder(isH264, enc_type, &capfilt, &outenc, &encparse,
|
||||
NULL);
|
||||
if (!capfilt || !outenc || !encparse) {
|
||||
g_printerr("enc element could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 文件输出,需要编码
|
||||
gchar *filepath = NULL;
|
||||
// 封装MP4
|
||||
mux = gst_element_factory_make("qtmux", "mp4-mux");
|
||||
// 添加元素到管道
|
||||
gst_bin_add(GST_BIN(pipeline), mux);
|
||||
// 设置输出文件路径
|
||||
GString *output_file = ds_parse_file_name(yaml_file_path, "output");
|
||||
filepath = g_strconcat(output_file->str, ".mp4", NULL);
|
||||
g_string_free(output_file, TRUE); // ✅ 释放 GString
|
||||
ds_parse_enc_config(outenc, yaml_file_path, "output");
|
||||
// 链接/添加元素到管道
|
||||
gst_bin_add_many(GST_BIN(pipeline), nvvidconv1, capfilt, outenc,
|
||||
encparse, NULL);
|
||||
if (!gst_element_link_many(nvosd, queue8, nvvidconv1, queue9, capfilt,
|
||||
queue10, outenc, encparse, mux, queue11,
|
||||
NULL)) {
|
||||
g_printerr("OSD and sink elements link failure.\n");
|
||||
return -1;
|
||||
}
|
||||
sink = gst_element_factory_make("filesink", "nvvideo-renderer");
|
||||
// 设置 sink 属性
|
||||
g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
|
||||
g_object_set(G_OBJECT(sink), "sync", TRUE, NULL);
|
||||
g_object_set(G_OBJECT(sink), "location", filepath, NULL);
|
||||
} else if (output_type == 4) {
|
||||
// UDP输出
|
||||
// 创建编码器链
|
||||
create_video_encoder(isH264, enc_type, &capfilt, &outenc, &encparse,
|
||||
&rtppay);
|
||||
g_object_set(G_OBJECT(outenc), "preset-level", 3,
|
||||
NULL); // 0:平衡 1:速度 2:质量 3:无损
|
||||
g_object_set(G_OBJECT(outenc), "insert-sps-pps", 1,
|
||||
NULL); // 每个 I 帧插 SPS/PPS
|
||||
g_object_set(G_OBJECT(outenc), "bitrate", 4000000,
|
||||
NULL); // 4 Mbps
|
||||
g_object_set(G_OBJECT(outenc), "iframeinterval", 30,
|
||||
NULL); // I 帧间隔
|
||||
if (!capfilt || !outenc || !encparse || !rtppay) {
|
||||
g_printerr("enc element could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
// 添加元素到管道
|
||||
gst_bin_add_many(GST_BIN(pipeline), nvvidconv1, capfilt, outenc,
|
||||
encparse, rtppay, NULL);
|
||||
// 链接元素
|
||||
if (!gst_element_link_many(nvosd, queue8, nvvidconv1, capfilt, queue9,
|
||||
outenc, encparse, queue10, rtppay, queue11,
|
||||
NULL)) {
|
||||
g_printerr("OSD and sink elements link failure.\n");
|
||||
return -1;
|
||||
}
|
||||
// 输出UDP(RTSP Server读取)
|
||||
sink = gst_element_factory_make("udpsink", "udp-sink");
|
||||
// 设置 sink 属性
|
||||
g_object_set(G_OBJECT(sink), "host", "224.224.255.255", "port",
|
||||
udp_sink_port_num, "async", FALSE, "buffer-size", 2097152,
|
||||
"sync", 1, "qos", 0, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
} // end
|
||||
|
||||
// 添加sink元素到管道
|
||||
if (!sink) {
|
||||
g_printerr("sink element could not be created. Exiting.\n");
|
||||
return -1;
|
||||
}
|
||||
gst_bin_add_many(GST_BIN(pipeline), sink, NULL);
|
||||
if (!gst_element_link_many(queue11, sink, NULL)) {
|
||||
g_printerr("OSD and sink elements link failure.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 获取 pipeline 的消息总线
|
||||
bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
|
||||
// 注册回调
|
||||
bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
|
||||
// 释放引用
|
||||
gst_object_unref(bus);
|
||||
// 拿 OSD 的 sink pad
|
||||
osd_sink_pad = gst_element_get_static_pad(queue5, "sink");
|
||||
NvDsObjEncCtxHandle obj_ctx_handle =
|
||||
nvds_obj_enc_create_context(/*GPU id:*/ 0);
|
||||
if (!obj_ctx_handle) {
|
||||
g_print("Unable to create context\n");
|
||||
return -1;
|
||||
}
|
||||
if (!osd_sink_pad) {
|
||||
g_print("Unable to get sink pad\n");
|
||||
} else {
|
||||
//挂 probe 所有 metadata 在这里被读取
|
||||
gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER,
|
||||
osd_sink_pad_buffer_probe, (gpointer)obj_ctx_handle,
|
||||
NULL);
|
||||
}
|
||||
// 释放之前获得的对象引用 不释放会内存泄漏
|
||||
gst_object_unref(osd_sink_pad);
|
||||
// 开启RTSP服务
|
||||
if (output_type == 4) {
|
||||
// ================= RTSP Server =================
|
||||
guint rtsp_port_num = 8554;
|
||||
guint64 udp_buffer_size = 2 * 1024 * 1024; // 2 MB
|
||||
GstRTSPMountPoints *mounts;
|
||||
GstRTSPMediaFactory *factory;
|
||||
char udpsrc_pipeline[512];
|
||||
|
||||
char port_num_Str[64] = {0};
|
||||
const char *codec =
|
||||
ds_parse_enc_codec(yaml_file_path, "output") ? "H265" : "H264";
|
||||
sprintf(udpsrc_pipeline,
|
||||
"( udpsrc name=pay0 port=%d buffer-size=%lu "
|
||||
"caps=\"application/x-rtp, media=video, "
|
||||
"clock-rate=90000, encoding-name=%s, payload=96 \" )",
|
||||
udp_sink_port_num, udp_buffer_size, codec);
|
||||
|
||||
sprintf(port_num_Str, "%d", rtsp_port_num);
|
||||
GstRTSPServer *server = gst_rtsp_server_new();
|
||||
g_object_set(server, "service", port_num_Str, NULL);
|
||||
mounts = gst_rtsp_server_get_mount_points(server);
|
||||
factory = gst_rtsp_media_factory_new();
|
||||
gst_rtsp_media_factory_set_shared(factory, TRUE);
|
||||
gst_rtsp_media_factory_set_launch(factory, udpsrc_pipeline);
|
||||
gst_rtsp_mount_points_add_factory(mounts, "/ds-test", factory);
|
||||
g_object_unref(mounts);
|
||||
gst_rtsp_server_attach(server, NULL);
|
||||
g_print("\n *** DeepStream: Launched RTSP Streaming at "
|
||||
"rtsp://localhost:%d/ds-test ***\n\n",
|
||||
rtsp_port_num);
|
||||
// ================= RTSP Server End =================
|
||||
}
|
||||
// 初始化 RTSP 管理器
|
||||
rtsp_mgr_side.configure(g_source_side_map);
|
||||
// 启动上传线程
|
||||
WorkerManager manager;
|
||||
manager.start();
|
||||
// 启动清理线程
|
||||
std::thread cleaner(cleanTempFolder);
|
||||
cleaner.detach(); // 分离线程,程序退出时不阻塞
|
||||
|
||||
// 启动管道
|
||||
g_print("Now playing: %s\n", yaml_file_path);
|
||||
gst_element_set_state(pipeline, GST_STATE_PLAYING);
|
||||
|
||||
// 主循环
|
||||
g_print("Running...\n");
|
||||
g_main_loop_run(loop);
|
||||
/* Destroy context for Object Encoding */
|
||||
nvds_obj_enc_destroy_context(obj_ctx_handle);
|
||||
|
||||
// 退出,清理
|
||||
g_print("Returned, stopping playback\n");
|
||||
gst_element_set_state(pipeline, GST_STATE_NULL);
|
||||
|
||||
g_print("Deleting pipeline\n");
|
||||
// 释放管道和消息总线回调
|
||||
gst_object_unref(GST_OBJECT(pipeline));
|
||||
g_source_remove(bus_watch_id);
|
||||
g_main_loop_unref(loop);
|
||||
|
||||
// 停止摄像头取流服务
|
||||
rtsp_mgr_side.shutdown();
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
#include "rtsp_grabber.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#define LOG_ERR(msg) std::cerr << "[ERR] " << msg << std::endl
|
||||
#define LOG_INF(msg) std::cerr << "[INF] " << msg << std::endl
|
||||
|
||||
struct RtspFrameGrabberImpl {
|
||||
AVFormatContext *fmt_ctx = nullptr;
|
||||
AVCodecContext *dec_ctx = nullptr;
|
||||
int video_stream_index = -1;
|
||||
AVFrame *decoded_frame = nullptr;
|
||||
AVFrame *sws_frame = nullptr;
|
||||
SwsContext *sws_ctx = nullptr;
|
||||
AVCodecContext *jpeg_enc_ctx = nullptr;
|
||||
AVPacket *enc_pkt = nullptr;
|
||||
AVFrame *latest_frame_copy = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
AVPixelFormat src_pix_fmt = AV_PIX_FMT_NONE;
|
||||
std::atomic<bool> stop_flag{false};
|
||||
std::atomic<bool> started{false};
|
||||
std::atomic<bool> ready{false};
|
||||
std::thread th;
|
||||
int reconnect_delay_ms = 1000;
|
||||
};
|
||||
|
||||
static void free_impl(RtspFrameGrabberImpl *impl) {
|
||||
if (!impl)
|
||||
return;
|
||||
if (impl->th.joinable()) {
|
||||
impl->stop_flag = true;
|
||||
impl->th.join();
|
||||
}
|
||||
if (impl->enc_pkt)
|
||||
av_packet_free(&impl->enc_pkt);
|
||||
if (impl->jpeg_enc_ctx)
|
||||
avcodec_free_context(&impl->jpeg_enc_ctx);
|
||||
if (impl->latest_frame_copy)
|
||||
av_frame_free(&impl->latest_frame_copy);
|
||||
if (impl->sws_frame)
|
||||
av_frame_free(&impl->sws_frame);
|
||||
if (impl->decoded_frame)
|
||||
av_frame_free(&impl->decoded_frame);
|
||||
if (impl->sws_ctx)
|
||||
sws_freeContext(impl->sws_ctx);
|
||||
if (impl->dec_ctx)
|
||||
avcodec_free_context(&impl->dec_ctx);
|
||||
if (impl->fmt_ctx)
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
delete impl;
|
||||
}
|
||||
|
||||
static AVFrame *avframe_deep_copy(const AVFrame *src) {
|
||||
if (!src)
|
||||
return nullptr;
|
||||
AVFrame *dst = av_frame_alloc();
|
||||
dst->format = src->format;
|
||||
dst->width = src->width;
|
||||
dst->height = src->height;
|
||||
av_frame_get_buffer(dst, 0);
|
||||
av_frame_copy(dst, src);
|
||||
av_frame_copy_props(dst, src);
|
||||
return dst;
|
||||
}
|
||||
static bool init_jpeg_encoder(RtspFrameGrabberImpl *impl, int width, int height,
|
||||
AVPixelFormat pix_fmt) {
|
||||
if (!impl || impl->jpeg_enc_ctx)
|
||||
return true;
|
||||
|
||||
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
|
||||
if (!codec)
|
||||
return false;
|
||||
|
||||
AVCodecContext *c = avcodec_alloc_context3(codec);
|
||||
|
||||
c->pix_fmt = pix_fmt;
|
||||
c->width = width;
|
||||
c->height = height;
|
||||
c->time_base = AVRational{1, 25};
|
||||
|
||||
// ⭐ 最高质量
|
||||
c->qmin = 1;
|
||||
c->qmax = 2;
|
||||
|
||||
c->compression_level = 0;
|
||||
|
||||
if (avcodec_open2(c, codec, nullptr) < 0) {
|
||||
avcodec_free_context(&c);
|
||||
return false;
|
||||
}
|
||||
|
||||
impl->jpeg_enc_ctx = c;
|
||||
impl->enc_pkt = av_packet_alloc();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_frame_to_jpeg(RtspFrameGrabberImpl *impl, AVFrame *frame,
|
||||
const std::string &filename) {
|
||||
if (!impl || !impl->jpeg_enc_ctx || !frame)
|
||||
return false;
|
||||
AVCodecContext *enc = impl->jpeg_enc_ctx;
|
||||
AVPacket *pkt = impl->enc_pkt;
|
||||
av_packet_unref(pkt);
|
||||
if (avcodec_send_frame(enc, frame) < 0)
|
||||
return false;
|
||||
if (avcodec_receive_packet(enc, pkt) < 0)
|
||||
return false;
|
||||
FILE *f = fopen(filename.c_str(), "wb");
|
||||
if (!f)
|
||||
return false;
|
||||
fwrite(pkt->data, 1, pkt->size, f);
|
||||
fclose(f);
|
||||
av_packet_unref(pkt);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------- RtspFrameGrabber Methods --------
|
||||
RtspFrameGrabber::RtspFrameGrabber(const std::string &rtsp_url)
|
||||
: rtsp_url_(rtsp_url) {
|
||||
avformat_network_init();
|
||||
opaque_impl_ = new RtspFrameGrabberImpl();
|
||||
}
|
||||
|
||||
RtspFrameGrabber::~RtspFrameGrabber() {
|
||||
stop();
|
||||
free_impl(reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_));
|
||||
opaque_impl_ = nullptr;
|
||||
}
|
||||
|
||||
bool RtspFrameGrabber::start() {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl || impl->started)
|
||||
return false;
|
||||
impl->stop_flag = false;
|
||||
impl->th = std::thread([this, impl]() { this->decode_loop(); });
|
||||
impl->started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RtspFrameGrabber::stop() {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl)
|
||||
return;
|
||||
impl->stop_flag = true;
|
||||
if (impl->th.joinable())
|
||||
impl->th.join();
|
||||
impl->started = false;
|
||||
impl->ready = false;
|
||||
}
|
||||
|
||||
bool RtspFrameGrabber::is_ready() const {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
return impl && impl->ready;
|
||||
}
|
||||
|
||||
bool RtspFrameGrabber::capture_jpeg(const std::string &filename) {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl)
|
||||
return false;
|
||||
std::lock_guard<std::mutex> lk(frame_mutex_);
|
||||
if (!impl->latest_frame_copy)
|
||||
return false;
|
||||
int w = impl->latest_frame_copy->width;
|
||||
int h = impl->latest_frame_copy->height;
|
||||
AVPixelFormat pf = (AVPixelFormat)impl->latest_frame_copy->format;
|
||||
if (!init_jpeg_encoder(impl, w, h, pf))
|
||||
return false;
|
||||
return encode_frame_to_jpeg(impl, impl->latest_frame_copy, filename);
|
||||
}
|
||||
|
||||
void RtspFrameGrabber::decode_loop() {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl)
|
||||
return;
|
||||
|
||||
AVDictionary *opts = nullptr;
|
||||
|
||||
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
|
||||
av_dict_set(&opts, "stimeout", "2000000", 0);
|
||||
|
||||
av_dict_set(&opts, "fflags", "nobuffer", 0);
|
||||
av_dict_set(&opts, "flags", "low_delay", 0);
|
||||
av_dict_set(&opts, "max_delay", "0", 0);
|
||||
av_dict_set(&opts, "buffer_size", "102400", 0);
|
||||
|
||||
AVPixelFormat dst_pix_fmt = AV_PIX_FMT_YUVJ420P;
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 57
|
||||
dst_pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
#endif
|
||||
|
||||
while (!impl->stop_flag) {
|
||||
AVFormatContext *fmt_ctx = nullptr;
|
||||
int ret = avformat_open_input(&fmt_ctx, rtsp_url_.c_str(), nullptr, &opts);
|
||||
if (ret != 0) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(impl->reconnect_delay_ms));
|
||||
continue;
|
||||
}
|
||||
impl->fmt_ctx = fmt_ctx;
|
||||
impl->reconnect_delay_ms = 1000;
|
||||
|
||||
if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
|
||||
int video_index = -1;
|
||||
for (unsigned i = 0; i < fmt_ctx->nb_streams; ++i) {
|
||||
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
video_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (video_index < 0) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
impl->video_stream_index = video_index;
|
||||
|
||||
AVCodecParameters *codecpar = fmt_ctx->streams[video_index]->codecpar;
|
||||
const AVCodec *dec = avcodec_find_decoder(codecpar->codec_id);
|
||||
if (!dec) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
|
||||
AVCodecContext *dec_ctx = avcodec_alloc_context3(dec);
|
||||
avcodec_parameters_to_context(dec_ctx, codecpar);
|
||||
if (avcodec_open2(dec_ctx, dec, nullptr) < 0) {
|
||||
avcodec_free_context(&dec_ctx);
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
impl->dec_ctx = dec_ctx;
|
||||
|
||||
impl->decoded_frame = av_frame_alloc();
|
||||
impl->width = dec_ctx->width;
|
||||
impl->height = dec_ctx->height;
|
||||
impl->src_pix_fmt = dec_ctx->pix_fmt;
|
||||
impl->sws_frame = av_frame_alloc();
|
||||
impl->sws_frame->format = dst_pix_fmt;
|
||||
impl->sws_frame->width = impl->width;
|
||||
impl->sws_frame->height = impl->height;
|
||||
av_frame_get_buffer(impl->sws_frame, 32);
|
||||
impl->sws_ctx = sws_getContext(impl->width, impl->height, impl->src_pix_fmt,
|
||||
impl->width, impl->height,
|
||||
(AVPixelFormat)impl->sws_frame->format,
|
||||
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
||||
init_jpeg_encoder(impl, impl->width, impl->height,
|
||||
(AVPixelFormat)impl->sws_frame->format);
|
||||
impl->ready = true;
|
||||
// LOG_INF("connected and decoding: " << rtsp_url_);
|
||||
|
||||
AVPacket *pkt = av_packet_alloc();
|
||||
while (!impl->stop_flag) {
|
||||
int r = av_read_frame(fmt_ctx, pkt);
|
||||
if (r < 0) {
|
||||
av_packet_unref(pkt);
|
||||
impl->ready = false;
|
||||
break;
|
||||
}
|
||||
if (pkt->stream_index != impl->video_stream_index) {
|
||||
av_packet_unref(pkt);
|
||||
continue;
|
||||
}
|
||||
if (avcodec_send_packet(dec_ctx, pkt) < 0) {
|
||||
av_packet_unref(pkt);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (!impl->stop_flag) {
|
||||
r = avcodec_receive_frame(dec_ctx, impl->decoded_frame);
|
||||
if (r == AVERROR(EAGAIN) || r == AVERROR_EOF)
|
||||
break;
|
||||
sws_scale(impl->sws_ctx, impl->decoded_frame->data,
|
||||
impl->decoded_frame->linesize, 0, impl->height,
|
||||
impl->sws_frame->data, impl->sws_frame->linesize);
|
||||
std::lock_guard<std::mutex> lk(frame_mutex_);
|
||||
if (impl->latest_frame_copy)
|
||||
av_frame_free(&impl->latest_frame_copy);
|
||||
impl->latest_frame_copy = avframe_deep_copy(impl->sws_frame);
|
||||
}
|
||||
av_packet_unref(pkt);
|
||||
}
|
||||
av_packet_free(&pkt);
|
||||
impl->ready = false;
|
||||
if (impl->sws_ctx) {
|
||||
sws_freeContext(impl->sws_ctx);
|
||||
impl->sws_ctx = nullptr;
|
||||
}
|
||||
if (impl->sws_frame) {
|
||||
av_frame_free(&impl->sws_frame);
|
||||
impl->sws_frame = nullptr;
|
||||
}
|
||||
if (impl->decoded_frame) {
|
||||
av_frame_free(&impl->decoded_frame);
|
||||
impl->decoded_frame = nullptr;
|
||||
}
|
||||
if (impl->dec_ctx) {
|
||||
avcodec_free_context(&impl->dec_ctx);
|
||||
impl->dec_ctx = nullptr;
|
||||
}
|
||||
if (impl->fmt_ctx) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
impl->fmt_ctx = nullptr;
|
||||
}
|
||||
|
||||
if (!impl->stop_flag)
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(impl->reconnect_delay_ms));
|
||||
}
|
||||
impl->ready = false;
|
||||
LOG_INF("decode thread exit for: " << rtsp_url_);
|
||||
}
|
||||
RtspManager::~RtspManager() { shutdown(); }
|
||||
|
||||
void RtspManager::configure(
|
||||
const std::unordered_map<int, std::string> &side_sources) {
|
||||
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
|
||||
// 删除不再需要的grabber
|
||||
for (auto it = side_grabbers_.begin(); it != side_grabbers_.end();) {
|
||||
auto f = side_sources.find(it->first);
|
||||
|
||||
if (f == side_sources.end() || f->second != it->second->get_rtsp_url()) {
|
||||
try {
|
||||
it->second->stop();
|
||||
} catch (...) {
|
||||
// 忽略异常,避免影响主程序
|
||||
}
|
||||
it = side_grabbers_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// 新增grabber
|
||||
for (const auto &kv : side_sources) {
|
||||
|
||||
int sid = kv.first;
|
||||
const std::string &url = kv.second;
|
||||
|
||||
if (side_grabbers_.find(sid) != side_grabbers_.end())
|
||||
continue;
|
||||
|
||||
try {
|
||||
|
||||
auto g = std::make_unique<RtspFrameGrabber>(url);
|
||||
|
||||
if (!g) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// start失败直接跳过
|
||||
if (!g->start()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
side_grabbers_.emplace(sid, std::move(g));
|
||||
|
||||
} catch (...) {
|
||||
// 捕获所有异常,避免configure影响系统稳定
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RtspManager::shutdown() {
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
|
||||
for (auto &kv : side_grabbers_) {
|
||||
kv.second->stop();
|
||||
}
|
||||
side_grabbers_.clear();
|
||||
}
|
||||
std::string RtspManager::capture_side_frame(int source_id) {
|
||||
if (!is_ready(source_id))
|
||||
return "";
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
|
||||
auto it = side_grabbers_.find(source_id);
|
||||
|
||||
if (it == side_grabbers_.end())
|
||||
return "";
|
||||
|
||||
std::filesystem::create_directories("./temp");
|
||||
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::time_t t = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
oss << "side_" << source_id << "_" << t << ".jpg";
|
||||
|
||||
std::string name = oss.str();
|
||||
std::string path = "./temp/" + name;
|
||||
|
||||
if (!it->second->capture_jpeg(path))
|
||||
return "";
|
||||
|
||||
return name;
|
||||
}
|
||||
bool RtspManager::is_ready(int source_id) const {
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
|
||||
auto it = side_grabbers_.find(source_id);
|
||||
|
||||
return it != side_grabbers_.end() && it->second->is_ready();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
class RtspFrameGrabber {
|
||||
public:
|
||||
explicit RtspFrameGrabber(const std::string &rtsp_url);
|
||||
~RtspFrameGrabber();
|
||||
|
||||
bool start();
|
||||
|
||||
void stop();
|
||||
|
||||
bool capture_jpeg(const std::string &filename);
|
||||
|
||||
bool is_ready() const;
|
||||
|
||||
const std::string &get_rtsp_url() const { return rtsp_url_; }
|
||||
|
||||
private:
|
||||
void decode_loop();
|
||||
|
||||
private:
|
||||
std::string rtsp_url_;
|
||||
std::thread worker_;
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> ready_{false};
|
||||
mutable std::mutex frame_mutex_;
|
||||
void *opaque_impl_ = nullptr;
|
||||
};
|
||||
|
||||
class RtspManager {
|
||||
public:
|
||||
RtspManager() = default;
|
||||
~RtspManager();
|
||||
|
||||
void configure(const std::unordered_map<int, std::string> &side_sources);
|
||||
|
||||
void shutdown();
|
||||
|
||||
std::string capture_side_frame(int source_id);
|
||||
|
||||
bool is_ready(int source_id) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<int, std::unique_ptr<RtspFrameGrabber>> side_grabbers_;
|
||||
|
||||
mutable std::mutex mgr_mutex_;
|
||||
};
|
||||
@@ -0,0 +1,375 @@
|
||||
#include "rtsp_grabber.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#define LOG_ERR(msg) std::cerr << "[ERR] " << msg << std::endl
|
||||
#define LOG_INF(msg) std::cerr << "[INF] " << msg << std::endl
|
||||
|
||||
struct RtspFrameGrabberImpl {
|
||||
AVFormatContext *fmt_ctx = nullptr;
|
||||
AVCodecContext *dec_ctx = nullptr;
|
||||
int video_stream_index = -1;
|
||||
AVFrame *decoded_frame = nullptr;
|
||||
AVFrame *sws_frame = nullptr;
|
||||
SwsContext *sws_ctx = nullptr;
|
||||
AVCodecContext *jpeg_enc_ctx = nullptr;
|
||||
AVPacket *enc_pkt = nullptr;
|
||||
AVFrame *latest_frame_copy = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
AVPixelFormat src_pix_fmt = AV_PIX_FMT_NONE;
|
||||
std::atomic<bool> stop_flag{false};
|
||||
std::atomic<bool> started{false};
|
||||
std::atomic<bool> ready{false};
|
||||
std::thread th;
|
||||
int reconnect_delay_ms = 1000;
|
||||
};
|
||||
|
||||
static void free_impl(RtspFrameGrabberImpl *impl) {
|
||||
if (!impl)
|
||||
return;
|
||||
if (impl->th.joinable()) {
|
||||
impl->stop_flag = true;
|
||||
impl->th.join();
|
||||
}
|
||||
if (impl->enc_pkt)
|
||||
av_packet_free(&impl->enc_pkt);
|
||||
if (impl->jpeg_enc_ctx)
|
||||
avcodec_free_context(&impl->jpeg_enc_ctx);
|
||||
if (impl->latest_frame_copy)
|
||||
av_frame_free(&impl->latest_frame_copy);
|
||||
if (impl->sws_frame)
|
||||
av_frame_free(&impl->sws_frame);
|
||||
if (impl->decoded_frame)
|
||||
av_frame_free(&impl->decoded_frame);
|
||||
if (impl->sws_ctx)
|
||||
sws_freeContext(impl->sws_ctx);
|
||||
if (impl->dec_ctx)
|
||||
avcodec_free_context(&impl->dec_ctx);
|
||||
if (impl->fmt_ctx)
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
delete impl;
|
||||
}
|
||||
|
||||
static AVFrame *avframe_deep_copy(const AVFrame *src) {
|
||||
if (!src)
|
||||
return nullptr;
|
||||
AVFrame *dst = av_frame_alloc();
|
||||
dst->format = src->format;
|
||||
dst->width = src->width;
|
||||
dst->height = src->height;
|
||||
av_frame_get_buffer(dst, 0);
|
||||
av_frame_copy(dst, src);
|
||||
av_frame_copy_props(dst, src);
|
||||
return dst;
|
||||
}
|
||||
|
||||
static bool init_jpeg_encoder(RtspFrameGrabberImpl *impl, int width, int height,
|
||||
AVPixelFormat pix_fmt) {
|
||||
if (!impl || impl->jpeg_enc_ctx)
|
||||
return true;
|
||||
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
|
||||
if (!codec)
|
||||
return false;
|
||||
AVCodecContext *c = avcodec_alloc_context3(codec);
|
||||
c->pix_fmt = pix_fmt;
|
||||
c->width = width;
|
||||
c->height = height;
|
||||
c->time_base = AVRational{1, 25};
|
||||
av_opt_set_int(c->priv_data, "quality", 1, 0);
|
||||
if (avcodec_open2(c, codec, nullptr) < 0) {
|
||||
avcodec_free_context(&c);
|
||||
return false;
|
||||
}
|
||||
impl->jpeg_enc_ctx = c;
|
||||
impl->enc_pkt = av_packet_alloc();
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool encode_frame_to_jpeg(RtspFrameGrabberImpl *impl, AVFrame *frame,
|
||||
const std::string &filename) {
|
||||
if (!impl || !impl->jpeg_enc_ctx || !frame)
|
||||
return false;
|
||||
AVCodecContext *enc = impl->jpeg_enc_ctx;
|
||||
AVPacket *pkt = impl->enc_pkt;
|
||||
av_packet_unref(pkt);
|
||||
if (avcodec_send_frame(enc, frame) < 0)
|
||||
return false;
|
||||
if (avcodec_receive_packet(enc, pkt) < 0)
|
||||
return false;
|
||||
FILE *f = fopen(filename.c_str(), "wb");
|
||||
if (!f)
|
||||
return false;
|
||||
fwrite(pkt->data, 1, pkt->size, f);
|
||||
fclose(f);
|
||||
av_packet_unref(pkt);
|
||||
return true;
|
||||
}
|
||||
|
||||
// -------- RtspFrameGrabber Methods --------
|
||||
RtspFrameGrabber::RtspFrameGrabber(const std::string &rtsp_url)
|
||||
: rtsp_url_(rtsp_url) {
|
||||
avformat_network_init();
|
||||
opaque_impl_ = new RtspFrameGrabberImpl();
|
||||
}
|
||||
|
||||
RtspFrameGrabber::~RtspFrameGrabber() {
|
||||
stop();
|
||||
free_impl(reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_));
|
||||
opaque_impl_ = nullptr;
|
||||
}
|
||||
|
||||
bool RtspFrameGrabber::start() {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl || impl->started)
|
||||
return false;
|
||||
impl->stop_flag = false;
|
||||
impl->th = std::thread([this, impl]() { this->decode_loop(); });
|
||||
impl->started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RtspFrameGrabber::stop() {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl)
|
||||
return;
|
||||
impl->stop_flag = true;
|
||||
if (impl->th.joinable())
|
||||
impl->th.join();
|
||||
impl->started = false;
|
||||
impl->ready = false;
|
||||
}
|
||||
|
||||
bool RtspFrameGrabber::is_ready() const {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
return impl && impl->ready;
|
||||
}
|
||||
|
||||
bool RtspFrameGrabber::capture_jpeg(const std::string &filename) {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl)
|
||||
return false;
|
||||
std::lock_guard<std::mutex> lk(frame_mutex_);
|
||||
if (!impl->latest_frame_copy)
|
||||
return false;
|
||||
int w = impl->latest_frame_copy->width;
|
||||
int h = impl->latest_frame_copy->height;
|
||||
AVPixelFormat pf = (AVPixelFormat)impl->latest_frame_copy->format;
|
||||
if (!init_jpeg_encoder(impl, w, h, pf))
|
||||
return false;
|
||||
return encode_frame_to_jpeg(impl, impl->latest_frame_copy, filename);
|
||||
}
|
||||
|
||||
void RtspFrameGrabber::decode_loop() {
|
||||
RtspFrameGrabberImpl *impl =
|
||||
reinterpret_cast<RtspFrameGrabberImpl *>(opaque_impl_);
|
||||
if (!impl)
|
||||
return;
|
||||
|
||||
AVDictionary *opts = nullptr;
|
||||
av_dict_set(&opts, "rtsp_transport", "tcp", 0);
|
||||
av_dict_set(&opts, "stimeout", "2000000", 0);
|
||||
|
||||
AVPixelFormat dst_pix_fmt = AV_PIX_FMT_YUVJ420P;
|
||||
#if LIBAVUTIL_VERSION_MAJOR >= 57
|
||||
dst_pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
#endif
|
||||
|
||||
while (!impl->stop_flag) {
|
||||
AVFormatContext *fmt_ctx = nullptr;
|
||||
int ret = avformat_open_input(&fmt_ctx, rtsp_url_.c_str(), nullptr, &opts);
|
||||
if (ret != 0) {
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(impl->reconnect_delay_ms));
|
||||
continue;
|
||||
}
|
||||
impl->fmt_ctx = fmt_ctx;
|
||||
impl->reconnect_delay_ms = 1000;
|
||||
|
||||
if (avformat_find_stream_info(fmt_ctx, nullptr) < 0) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
|
||||
int video_index = -1;
|
||||
for (unsigned i = 0; i < fmt_ctx->nb_streams; ++i) {
|
||||
if (fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||
video_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (video_index < 0) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
impl->video_stream_index = video_index;
|
||||
|
||||
AVCodecParameters *codecpar = fmt_ctx->streams[video_index]->codecpar;
|
||||
const AVCodec *dec = avcodec_find_decoder(codecpar->codec_id);
|
||||
if (!dec) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
|
||||
AVCodecContext *dec_ctx = avcodec_alloc_context3(dec);
|
||||
avcodec_parameters_to_context(dec_ctx, codecpar);
|
||||
if (avcodec_open2(dec_ctx, dec, nullptr) < 0) {
|
||||
avcodec_free_context(&dec_ctx);
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
continue;
|
||||
}
|
||||
impl->dec_ctx = dec_ctx;
|
||||
|
||||
impl->decoded_frame = av_frame_alloc();
|
||||
impl->width = dec_ctx->width;
|
||||
impl->height = dec_ctx->height;
|
||||
impl->src_pix_fmt = dec_ctx->pix_fmt;
|
||||
impl->sws_frame = av_frame_alloc();
|
||||
impl->sws_frame->format = dst_pix_fmt;
|
||||
impl->sws_frame->width = impl->width;
|
||||
impl->sws_frame->height = impl->height;
|
||||
av_frame_get_buffer(impl->sws_frame, 32);
|
||||
impl->sws_ctx = sws_getContext(impl->width, impl->height, impl->src_pix_fmt,
|
||||
impl->width, impl->height,
|
||||
(AVPixelFormat)impl->sws_frame->format,
|
||||
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
||||
init_jpeg_encoder(impl, impl->width, impl->height,
|
||||
(AVPixelFormat)impl->sws_frame->format);
|
||||
impl->ready = true;
|
||||
// LOG_INF("connected and decoding: " << rtsp_url_);
|
||||
|
||||
AVPacket *pkt = av_packet_alloc();
|
||||
while (!impl->stop_flag) {
|
||||
int r = av_read_frame(fmt_ctx, pkt);
|
||||
if (r < 0) {
|
||||
av_packet_unref(pkt);
|
||||
impl->ready = false;
|
||||
break;
|
||||
}
|
||||
if (pkt->stream_index != impl->video_stream_index) {
|
||||
av_packet_unref(pkt);
|
||||
continue;
|
||||
}
|
||||
if (avcodec_send_packet(dec_ctx, pkt) < 0) {
|
||||
av_packet_unref(pkt);
|
||||
continue;
|
||||
}
|
||||
|
||||
while (!impl->stop_flag) {
|
||||
r = avcodec_receive_frame(dec_ctx, impl->decoded_frame);
|
||||
if (r == AVERROR(EAGAIN) || r == AVERROR_EOF)
|
||||
break;
|
||||
sws_scale(impl->sws_ctx, impl->decoded_frame->data,
|
||||
impl->decoded_frame->linesize, 0, impl->height,
|
||||
impl->sws_frame->data, impl->sws_frame->linesize);
|
||||
std::lock_guard<std::mutex> lk(frame_mutex_);
|
||||
if (impl->latest_frame_copy)
|
||||
av_frame_free(&impl->latest_frame_copy);
|
||||
impl->latest_frame_copy = avframe_deep_copy(impl->sws_frame);
|
||||
}
|
||||
av_packet_unref(pkt);
|
||||
}
|
||||
av_packet_free(&pkt);
|
||||
impl->ready = false;
|
||||
if (impl->sws_ctx) {
|
||||
sws_freeContext(impl->sws_ctx);
|
||||
impl->sws_ctx = nullptr;
|
||||
}
|
||||
if (impl->sws_frame) {
|
||||
av_frame_free(&impl->sws_frame);
|
||||
impl->sws_frame = nullptr;
|
||||
}
|
||||
if (impl->decoded_frame) {
|
||||
av_frame_free(&impl->decoded_frame);
|
||||
impl->decoded_frame = nullptr;
|
||||
}
|
||||
if (impl->dec_ctx) {
|
||||
avcodec_free_context(&impl->dec_ctx);
|
||||
impl->dec_ctx = nullptr;
|
||||
}
|
||||
if (impl->fmt_ctx) {
|
||||
avformat_close_input(&impl->fmt_ctx);
|
||||
impl->fmt_ctx = nullptr;
|
||||
}
|
||||
|
||||
if (!impl->stop_flag)
|
||||
std::this_thread::sleep_for(
|
||||
std::chrono::milliseconds(impl->reconnect_delay_ms));
|
||||
}
|
||||
impl->ready = false;
|
||||
LOG_INF("decode thread exit for: " << rtsp_url_);
|
||||
}
|
||||
|
||||
// -------- RtspManager Methods --------
|
||||
RtspManager::~RtspManager() { shutdown(); }
|
||||
|
||||
void RtspManager::configure(
|
||||
const std::unordered_map<int, std::string> &source_map) {
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
for (auto it = grabbers_.begin(); it != grabbers_.end();) {
|
||||
int sid = it->first;
|
||||
auto found = source_map.find(sid);
|
||||
if (found == source_map.end() ||
|
||||
found->second != it->second->get_rtsp_url()) {
|
||||
it->second->stop();
|
||||
it = grabbers_.erase(it);
|
||||
} else
|
||||
++it;
|
||||
}
|
||||
for (const auto &kv : source_map) {
|
||||
int sid = kv.first;
|
||||
const std::string &url = kv.second;
|
||||
if (grabbers_.find(sid) == grabbers_.end()) {
|
||||
auto g = std::make_unique<RtspFrameGrabber>(url);
|
||||
g->start();
|
||||
grabbers_.emplace(sid, std::move(g));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RtspManager::shutdown() {
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
for (auto &kv : grabbers_)
|
||||
kv.second->stop();
|
||||
grabbers_.clear();
|
||||
}
|
||||
|
||||
std::string RtspManager::capture_frame_by_source(int source_id) {
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
auto it = grabbers_.find(source_id);
|
||||
if (it == grabbers_.end())
|
||||
return "";
|
||||
std::filesystem::create_directories("./temp");
|
||||
auto now = std::chrono::system_clock::now();
|
||||
std::time_t t = std::chrono::system_clock::to_time_t(now);
|
||||
std::ostringstream oss;
|
||||
oss << "source_" << source_id << "_" << t << ".jpg";
|
||||
std::string filename_only = oss.str();
|
||||
std::string full_path = "./temp/" + filename_only;
|
||||
|
||||
if (!it->second->capture_jpeg(full_path))
|
||||
return "";
|
||||
|
||||
return filename_only; // 只返回文件名
|
||||
}
|
||||
|
||||
bool RtspManager::is_ready(int source_id) const {
|
||||
std::lock_guard<std::mutex> lk(mgr_mutex_);
|
||||
auto it = grabbers_.find(source_id);
|
||||
return it != grabbers_.end() && it->second->is_ready();
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
class RtspFrameGrabber {
|
||||
public:
|
||||
explicit RtspFrameGrabber(const std::string &rtsp_url);
|
||||
~RtspFrameGrabber();
|
||||
|
||||
bool start();
|
||||
void stop();
|
||||
bool capture_jpeg(const std::string &filename);
|
||||
bool is_ready() const;
|
||||
|
||||
// 新增 getter,让外部访问 rtsp_url
|
||||
const std::string &get_rtsp_url() const { return rtsp_url_; }
|
||||
|
||||
private:
|
||||
void decode_loop();
|
||||
|
||||
private:
|
||||
std::string rtsp_url_;
|
||||
std::thread worker_;
|
||||
std::atomic<bool> running_{false};
|
||||
std::atomic<bool> ready_{false};
|
||||
mutable std::mutex frame_mutex_;
|
||||
void *opaque_impl_ = nullptr;
|
||||
};
|
||||
|
||||
class RtspManager {
|
||||
public:
|
||||
RtspManager() = default;
|
||||
~RtspManager();
|
||||
|
||||
void configure(const std::unordered_map<int, std::string> &source_map);
|
||||
void shutdown();
|
||||
std::string capture_frame_by_source(int source_id);
|
||||
bool is_ready(int source_id) const;
|
||||
|
||||
private:
|
||||
std::unordered_map<int, std::unique_ptr<RtspFrameGrabber>> grabbers_;
|
||||
mutable std::mutex mgr_mutex_;
|
||||
};
|
||||
@@ -0,0 +1,363 @@
|
||||
#include <curl/curl.h>
|
||||
#include <gst/app/gstappsink.h>
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "my_utils.h"
|
||||
#include "vehicle_event_service.h"
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <miniocpp/client.h>
|
||||
|
||||
#include "ds_yml_parse.h"
|
||||
|
||||
#include <SimpleAmqpClient/SimpleAmqpClient.h>
|
||||
// ======================== 配置区 ========================
|
||||
|
||||
// 全局记录存储
|
||||
RecordStore g_store;
|
||||
// 全局请求队列
|
||||
RecordQueue g_queue;
|
||||
// 正面摄像头 RTSP 地址映射表
|
||||
std::unordered_map<int, std::string> g_source_front_map;
|
||||
// 侧面摄像头 RTSP 地址映射表
|
||||
std::unordered_map<int, std::string> g_source_side_map;
|
||||
RtspManager rtsp_mgr_side;
|
||||
static std::mutex g_vehicle_mutex;
|
||||
|
||||
std::atomic<bool> stop_flag(false);
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
bool delete_file(const std::string &file_path) {
|
||||
try {
|
||||
if (std::filesystem::exists(file_path)) {
|
||||
return std::filesystem::remove(file_path); // 成功返回 true
|
||||
} else {
|
||||
std::cout << "file does not exist: " << file_path << std::endl;
|
||||
return false;
|
||||
}
|
||||
} catch (const std::filesystem::filesystem_error &e) {
|
||||
std::cout << "failed to delete file: " << e.what() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新车辆基础信息(跨线前,可多次更新)
|
||||
*/
|
||||
void update_object_base_info(guint source_id, guint64 object_id,
|
||||
const std::string &vehicle_type) {
|
||||
std::lock_guard<std::mutex> lk(g_vehicle_mutex);
|
||||
TrackKey key{source_id, object_id};
|
||||
Record record;
|
||||
if (g_store.get(key, record)) {
|
||||
// 已经过 Line B 上传的不再更新车牌/车型
|
||||
if (record.crossed_line_b) {
|
||||
return;
|
||||
}
|
||||
// vehicle_type 一般比车牌稳定,可直接覆盖或也做类似策略
|
||||
if (!vehicle_type.empty()) {
|
||||
record.vehicle_type = vehicle_type;
|
||||
}
|
||||
} else {
|
||||
// 新对象
|
||||
record.source_id = source_id;
|
||||
record.object_id = object_id;
|
||||
record.vehicle_type = vehicle_type;
|
||||
record.insert_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
g_store.upsert(key, record);
|
||||
}
|
||||
/**
|
||||
* 触发车辆跨线事件(拍照线 Line A)
|
||||
*/
|
||||
void trigger_line_a_capture(guint source_id, guint64 object_id) {
|
||||
std::lock_guard<std::mutex> lk(g_vehicle_mutex);
|
||||
TrackKey key{source_id, object_id};
|
||||
Record record;
|
||||
if (!g_store.get(key, record)) {
|
||||
// 没有基础信息,不允许触发
|
||||
return;
|
||||
}
|
||||
if (record.crossed_line_a) {
|
||||
// 已经在 Line A 拍照过,不再重复
|
||||
return;
|
||||
}
|
||||
// 状态迁移:未拍照 -> 已拍照
|
||||
record.crossed_line_a = true;
|
||||
// 拍照,但不上传
|
||||
record.vehicle_image_side = rtsp_mgr_side.capture_frame_by_source(source_id);
|
||||
g_store.upsert(key, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发车辆跨线事件(上传线 Line B)
|
||||
*/
|
||||
void trigger_line_b_upload(guint source_id, guint64 object_id,
|
||||
const std::string &file_name) {
|
||||
std::lock_guard<std::mutex> lk(g_vehicle_mutex);
|
||||
TrackKey key{source_id, object_id};
|
||||
Record record;
|
||||
if (!g_store.get(key, record)) {
|
||||
// 没有基础信息,不允许触发
|
||||
return;
|
||||
}
|
||||
if (!record.crossed_line_a) {
|
||||
// 没有经过 Line A,不允许上传
|
||||
g_store.remove(key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (record.crossed_line_b) {
|
||||
// 已经在 Line B 上传过,保证只触发一次
|
||||
g_store.remove(key);
|
||||
return;
|
||||
}
|
||||
record.vehicle_image_front = file_name;
|
||||
// 状态迁移:未上传 -> 已上传
|
||||
record.crossed_line_b = true;
|
||||
g_store.upsert(key, record);
|
||||
|
||||
int vehicle_type = ds_parse_group_type(yaml_file_path, "vehicle_type");
|
||||
bool match = false;
|
||||
switch (vehicle_type) {
|
||||
case 0:
|
||||
match = true;
|
||||
break;
|
||||
case 1:
|
||||
match = (record.vehicle_type == "coupe");
|
||||
break;
|
||||
case 2:
|
||||
match = (record.vehicle_type == "largevehicle");
|
||||
break;
|
||||
case 3:
|
||||
match = (record.vehicle_type == "sedan");
|
||||
break;
|
||||
case 4:
|
||||
match = (record.vehicle_type == "suv");
|
||||
break;
|
||||
case 5:
|
||||
match = (record.vehicle_type == "truck");
|
||||
break;
|
||||
case 6:
|
||||
match = (record.vehicle_type == "van");
|
||||
break;
|
||||
case 7:
|
||||
match = (record.vehicle_type == "largevehicle" ||
|
||||
record.vehicle_type == "truck");
|
||||
break;
|
||||
default:
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (match) {
|
||||
g_queue.push(record);
|
||||
} else {
|
||||
// 删除不符合条件的记录与图片,避免占用空间
|
||||
g_store.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
// =======================================================
|
||||
|
||||
/**
|
||||
* MinIO 上传
|
||||
*/
|
||||
bool upload_minio(const std::string &object_name,
|
||||
const std::string &file_path) {
|
||||
|
||||
// Create S3 base URL.
|
||||
minio::s3::BaseUrl base_url;
|
||||
base_url.host = "ai.ronsunny.cn";
|
||||
base_url.port = 9000;
|
||||
base_url.https = true;
|
||||
base_url.region = "China.Chengdu";
|
||||
// Create credential provider.
|
||||
minio::creds::StaticProvider provider("minioadmin", "minioadmin");
|
||||
// Create S3 client.
|
||||
minio::s3::Client client(base_url, &provider);
|
||||
std::string bucket_name = "sentinel";
|
||||
// Check 'asiatrip' bucket exist or not.
|
||||
bool exist;
|
||||
{
|
||||
minio::s3::BucketExistsArgs args;
|
||||
args.bucket = bucket_name;
|
||||
minio::s3::BucketExistsResponse resp = client.BucketExists(args);
|
||||
if (!resp) {
|
||||
std::cout << "unable to do bucket existence check:" << resp.Error()
|
||||
<< std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
exist = resp.exist;
|
||||
}
|
||||
if (!exist) {
|
||||
minio::s3::MakeBucketArgs args;
|
||||
args.bucket = bucket_name;
|
||||
minio::s3::MakeBucketResponse resp = client.MakeBucket(args);
|
||||
if (!resp) {
|
||||
std::cout << "unable to create bucket; " << resp.Error() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
minio::s3::UploadObjectArgs args;
|
||||
args.bucket = bucket_name;
|
||||
args.object = object_name;
|
||||
args.filename = file_path;
|
||||
minio::s3::UploadObjectResponse resp = client.UploadObject(args);
|
||||
if (!resp) {
|
||||
std::cout << "unable to upload object; " << resp.Error() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
// void front_worker_thread() {
|
||||
// std::string device_id = GetDeviceID();
|
||||
|
||||
// AmqpClient::Channel::OpenOpts opts;
|
||||
// opts.host = "ai.ronsunny.cn";
|
||||
// opts.port = 5672;
|
||||
// opts.auth =
|
||||
// AmqpClient::Channel::OpenOpts::BasicAuth("jetson_sentinel", "123456");
|
||||
// opts.vhost = "sentinel";
|
||||
|
||||
// auto channel = AmqpClient::Channel::Open(opts);
|
||||
// std::string queue = "sentinel.front_pic." + device_id;
|
||||
|
||||
// channel->DeclareQueue(queue, false, true, false, false);
|
||||
|
||||
// std::string consumer_tag =
|
||||
// channel->BasicConsume(queue, "", true, true, false);
|
||||
|
||||
// while (!stop_flag) {
|
||||
// AmqpClient::Envelope::ptr_t envelope;
|
||||
|
||||
// bool received = channel->BasicConsumeMessage(consumer_tag, envelope);
|
||||
|
||||
// if (received) {
|
||||
// printf("Received message: %s\n", envelope->Message()->Body().c_str());
|
||||
// std::string recordId = envelope->Message()->Body();
|
||||
// // 1. 上传正面照到OSS
|
||||
// Record record;
|
||||
// if (!g_store.get_by_id(recordId, record)) {
|
||||
// std::cerr << "No record found for id=" << recordId << std::endl;
|
||||
// continue;
|
||||
// }
|
||||
// if (record.vehicle_image_front.empty()) {
|
||||
// std::cerr << "No front image for record id=" << recordId <<
|
||||
// std::endl; continue;
|
||||
// }
|
||||
// auto file_path =
|
||||
// "./temp/" +
|
||||
// std::filesystem::path(record.vehicle_image_front).filename().string();
|
||||
// auto upload_result = upload_minio(
|
||||
// "vehicle_image_front/" + record.vehicle_image_side, file_path);
|
||||
// if (EXIT_FAILURE == upload_result) {
|
||||
// std::cerr << "Failed to upload image for record id=" <<
|
||||
// record.object_id
|
||||
// << std::endl;
|
||||
// continue;
|
||||
// }
|
||||
// // 2. HTTP 请求服务器
|
||||
// AnalyticsClient client("https://ai.ronsunny.cn:8090/api/public/"
|
||||
// "sentinel-record-analytics");
|
||||
// std::string err;
|
||||
// nlohmann::json j;
|
||||
// j["Id"] = recordId;
|
||||
// j["DeviceId"] = device_id;
|
||||
// j["vehicleImageFront"] = record.vehicle_image_front;
|
||||
// j["vehicleImageSide"] = record.vehicle_image_side;
|
||||
// if (!client.call(j, err)) {
|
||||
// std::cerr << "Analytics request failed: " << err << "\n";
|
||||
// }
|
||||
// TrackKey key{record.source_id, record.object_id};
|
||||
// g_store.remove(key);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
void side_worker_thread() {
|
||||
while (!stop_flag) {
|
||||
Record record = g_queue.pop();
|
||||
if (record.vehicle_image_side.empty() ||
|
||||
record.vehicle_image_front.empty()) {
|
||||
std::cerr << "No side image for record id=" << record.object_id
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
auto file_path =
|
||||
"./temp/" +
|
||||
std::filesystem::path(record.vehicle_image_side).filename().string();
|
||||
if (!std::filesystem::exists(file_path) ||
|
||||
std::filesystem::is_directory(file_path)) {
|
||||
|
||||
std::cerr << "invalid file: " << file_path << std::endl;
|
||||
continue;
|
||||
}
|
||||
auto upload_result = upload_minio(
|
||||
"vehicle_image_side/" + record.vehicle_image_side, file_path);
|
||||
if (EXIT_FAILURE == upload_result) {
|
||||
std::cerr << "Failed to upload image for record id=" << record.object_id
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
file_path =
|
||||
"./temp/" +
|
||||
std::filesystem::path(record.vehicle_image_front).filename().string();
|
||||
if (!std::filesystem::exists(file_path) ||
|
||||
std::filesystem::is_directory(file_path)) {
|
||||
|
||||
std::cerr << "invalid file: " << file_path << std::endl;
|
||||
continue;
|
||||
}
|
||||
upload_result = upload_minio(
|
||||
"vehicle_image_front/" + record.vehicle_image_front, file_path);
|
||||
if (EXIT_FAILURE == upload_result) {
|
||||
std::cerr << "Failed to upload image for record id=" << record.object_id
|
||||
<< std::endl;
|
||||
continue;
|
||||
}
|
||||
// 2. HTTP 请求服务器
|
||||
AnalyticsClient client("https://ai.ronsunny.cn:8090/api/public/"
|
||||
"sentinel-record-analytics");
|
||||
std::string err;
|
||||
|
||||
std::string id = generateUUID();
|
||||
record.id = id;
|
||||
nlohmann::json j;
|
||||
j["Id"] = id;
|
||||
j["DeviceId"] = GetDeviceID();
|
||||
j["VehicleType"] = record.vehicle_type;
|
||||
j["vehicleImageFront"] = record.vehicle_image_front;
|
||||
j["vehicleImageSide"] = record.vehicle_image_side;
|
||||
if (!client.call(j, err)) {
|
||||
std::cerr << "Analytics request failed: " << err << "\n";
|
||||
}
|
||||
// 删除本地文件和记录
|
||||
TrackKey key{record.source_id, record.object_id};
|
||||
g_store.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
#include "rtsp_grabber.h"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* 车辆信息记录-结构体
|
||||
*/
|
||||
struct Record {
|
||||
std::string id; // 记录ID(唯一,用于寻找对应记录)
|
||||
guint source_id = 0; // 资源id
|
||||
guint64 object_id = 0; // 当前source下的车辆id
|
||||
std::string vehicle_type = ""; // 车类型
|
||||
std::string vehicle_image_front = ""; // 车身正面图片地址
|
||||
std::string vehicle_image_side = ""; // 车身侧面图片地址
|
||||
bool crossed_line_a = false; // 是否跨线A
|
||||
bool crossed_line_b = false; // 是否跨线B
|
||||
bool crossed = false; // 是否已经跨线
|
||||
std::chrono::steady_clock::time_point
|
||||
insert_time; // 记录插入时间,用于过期清理
|
||||
};
|
||||
/**
|
||||
* 车辆信息记录-键
|
||||
*/
|
||||
struct TrackKey {
|
||||
guint source_id;
|
||||
guint64 object_id;
|
||||
|
||||
bool operator==(const TrackKey &other) const {
|
||||
return source_id == other.source_id && object_id == other.object_id;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* 车辆信息记录-存储
|
||||
*/
|
||||
class RecordStore {
|
||||
public:
|
||||
explicit RecordStore(size_t capacity = 50, int expire_seconds = 60 * 10)
|
||||
: capacity_(capacity),
|
||||
expire_duration_(std::chrono::seconds(expire_seconds)) {}
|
||||
|
||||
void upsert(const TrackKey &key, const Record &record) {
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
clean_expired();
|
||||
|
||||
auto it = map_.find(key);
|
||||
if (it != map_.end()) {
|
||||
// 已存在,更新并移动到最新
|
||||
it->second.first = record;
|
||||
lru_.splice(lru_.begin(), lru_, it->second.second);
|
||||
it->second.second = lru_.begin();
|
||||
} else {
|
||||
// 新增
|
||||
if (map_.size() >= capacity_) {
|
||||
evict_one();
|
||||
}
|
||||
lru_.push_front(key);
|
||||
map_[key] = {record, lru_.begin()};
|
||||
}
|
||||
}
|
||||
|
||||
bool get(const TrackKey &key, Record &out) {
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
auto it = map_.find(key);
|
||||
if (it == map_.end())
|
||||
return false;
|
||||
out = it->second.first;
|
||||
return true;
|
||||
}
|
||||
bool get_by_id(const std::string &id, Record &out_record) {
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
for (const auto &pair : map_) {
|
||||
const Record &rec = pair.second.first;
|
||||
if (rec.id == id) {
|
||||
out_record = rec;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // 没找到
|
||||
}
|
||||
bool remove(const TrackKey &key) {
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
auto it = map_.find(key);
|
||||
if (it == map_.end())
|
||||
return false;
|
||||
|
||||
// 删除图片文件
|
||||
const Record &record = it->second.first;
|
||||
if (!record.vehicle_image_front.empty()) {
|
||||
std::remove(record.vehicle_image_front.c_str());
|
||||
}
|
||||
if (!record.vehicle_image_side.empty()) {
|
||||
std::remove(record.vehicle_image_side.c_str());
|
||||
}
|
||||
|
||||
// 移除 map 和 lru
|
||||
lru_.erase(it->second.second);
|
||||
map_.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
void evict_one() {
|
||||
auto last = lru_.back();
|
||||
map_.erase(last);
|
||||
lru_.pop_back();
|
||||
}
|
||||
|
||||
void clean_expired() {
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
for (auto it = lru_.begin(); it != lru_.end();) {
|
||||
auto &record = map_[*it].first;
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
now - record.insert_time);
|
||||
if (elapsed >= expire_duration_) {
|
||||
// 删除对应文件
|
||||
if (!record.vehicle_image_front.empty()) {
|
||||
std::remove(record.vehicle_image_front.c_str());
|
||||
}
|
||||
if (!record.vehicle_image_side.empty()) {
|
||||
std::remove(record.vehicle_image_side.c_str());
|
||||
}
|
||||
// 移除 map 和 lru
|
||||
map_.erase(*it);
|
||||
it = lru_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TrackKeyHash {
|
||||
std::size_t operator()(const TrackKey &k) const {
|
||||
// 64位安全
|
||||
return std::hash<guint>()(k.source_id) ^
|
||||
std::hash<guint64>()(k.object_id);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
size_t capacity_;
|
||||
std::list<TrackKey> lru_;
|
||||
std::chrono::seconds expire_duration_;
|
||||
std::unordered_map<TrackKey, std::pair<Record, std::list<TrackKey>::iterator>,
|
||||
TrackKeyHash>
|
||||
map_;
|
||||
std::mutex mtx_;
|
||||
};
|
||||
/**
|
||||
* 车辆信息记录-队列
|
||||
*/
|
||||
class RecordQueue {
|
||||
public:
|
||||
void push(const Record &record) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mtx_);
|
||||
queue_.push(record);
|
||||
}
|
||||
cv_.notify_one();
|
||||
}
|
||||
|
||||
Record pop() {
|
||||
std::unique_lock<std::mutex> lock(mtx_);
|
||||
cv_.wait(lock, [&] { return !queue_.empty(); });
|
||||
Record r = queue_.front();
|
||||
queue_.pop();
|
||||
return r;
|
||||
}
|
||||
|
||||
private:
|
||||
std::queue<Record> queue_;
|
||||
std::mutex mtx_;
|
||||
std::condition_variable cv_;
|
||||
};
|
||||
#include <curl/curl.h>
|
||||
#include <iostream>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string>
|
||||
|
||||
class AnalyticsClient {
|
||||
public:
|
||||
AnalyticsClient(const std::string &url) : url_(url) {
|
||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||
curl_ = curl_easy_init();
|
||||
if (!curl_)
|
||||
throw std::runtime_error("Failed to init curl");
|
||||
|
||||
// 设置全局 headers
|
||||
headers_ = curl_slist_append(headers_, "Content-Type: application/json");
|
||||
headers_ =
|
||||
curl_slist_append(headers_, "apikey: NzusyzcLIUoZ22tflHN2sOjHrry3W7zJ");
|
||||
curl_easy_setopt(curl_, CURLOPT_HTTPHEADER, headers_);
|
||||
curl_easy_setopt(curl_, CURLOPT_TIMEOUT_MS, 3000L);
|
||||
}
|
||||
|
||||
~AnalyticsClient() {
|
||||
if (curl_)
|
||||
curl_easy_cleanup(curl_);
|
||||
if (headers_)
|
||||
curl_slist_free_all(headers_);
|
||||
curl_global_cleanup();
|
||||
}
|
||||
|
||||
// 返回 true/false,并可通过 err_msg 获取错误
|
||||
bool call(nlohmann::json j, std::string &err_msg, bool is_side = true) {
|
||||
std::string json_str = j.dump();
|
||||
curl_easy_setopt(curl_, CURLOPT_URL, url_.c_str());
|
||||
curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, json_str.c_str());
|
||||
CURLcode res = curl_easy_perform(curl_);
|
||||
if (res != CURLE_OK) {
|
||||
err_msg = curl_easy_strerror(res);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string url_;
|
||||
CURL *curl_{nullptr};
|
||||
struct curl_slist *headers_{nullptr};
|
||||
};
|
||||
|
||||
// 函数声明
|
||||
std::string capture_frame_from_rtsp(const std::string &rtsp_url,
|
||||
unsigned int source_id,
|
||||
unsigned int object_id);
|
||||
|
||||
void update_object_base_info(guint source_id, guint64 object_id,
|
||||
const std::string &vehicle_type);
|
||||
void trigger_line_a_capture(guint source_id, guint64 object_id);
|
||||
void trigger_line_b_upload(guint source_id, guint64 object_id,
|
||||
const std::string &file_name);
|
||||
|
||||
void worker_start();
|
||||
|
||||
// 全局变量声明(不要定义)
|
||||
extern RecordStore g_store;
|
||||
extern RecordQueue g_queue;
|
||||
extern std::unordered_map<int, std::string> g_source_side_map;
|
||||
extern std::unordered_map<int, std::string> g_source_front_map;
|
||||
extern RtspManager rtsp_mgr_front;
|
||||
extern RtspManager rtsp_mgr_side;
|
||||
|
||||
void side_worker_thread();
|
||||
// void front_worker_thread();
|
||||
|
||||
extern std::atomic<bool> stop_flag;
|
||||
|
||||
class WorkerManager {
|
||||
public:
|
||||
void start() {
|
||||
// threads.emplace_back(front_worker_thread);
|
||||
threads.emplace_back(side_worker_thread);
|
||||
}
|
||||
|
||||
void stop() {
|
||||
stop_flag = true;
|
||||
for (auto &t : threads) {
|
||||
if (t.joinable())
|
||||
t.join();
|
||||
}
|
||||
threads.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::thread> threads;
|
||||
};
|
||||
@@ -0,0 +1,67 @@
|
||||
皖
|
||||
沪
|
||||
津
|
||||
渝
|
||||
冀
|
||||
晋
|
||||
蒙
|
||||
辽
|
||||
吉
|
||||
黑
|
||||
苏
|
||||
浙
|
||||
京
|
||||
闽
|
||||
赣
|
||||
鲁
|
||||
豫
|
||||
鄂
|
||||
湘
|
||||
粤
|
||||
桂
|
||||
琼
|
||||
川
|
||||
贵
|
||||
云
|
||||
藏
|
||||
陕
|
||||
甘
|
||||
青
|
||||
宁
|
||||
新
|
||||
警
|
||||
学
|
||||
A
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
F
|
||||
G
|
||||
H
|
||||
J
|
||||
K
|
||||
L
|
||||
M
|
||||
N
|
||||
P
|
||||
Q
|
||||
R
|
||||
S
|
||||
T
|
||||
U
|
||||
V
|
||||
W
|
||||
X
|
||||
Y
|
||||
Z
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
9
|
||||
@@ -0,0 +1,806 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <yaml-cpp/yaml.h>
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <cuda_runtime_api.h>
|
||||
#include "ds_yml_parse.h"
|
||||
#include "nvds_yml_parser.h"
|
||||
|
||||
static gchar *get_absolute_file_path(gchar *cfg_file_path, const gchar *file_path) {
|
||||
gchar abs_cfg_path[PATH_MAX + 1];
|
||||
gchar *abs_file_path;
|
||||
gchar *delim;
|
||||
|
||||
if (file_path && file_path[0] == '/') {
|
||||
return (gchar *)file_path;
|
||||
}
|
||||
|
||||
if (!realpath(cfg_file_path, abs_cfg_path)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Return absolute path of config file if file_path is NULL. */
|
||||
if (!file_path) {
|
||||
abs_file_path = g_strdup(abs_cfg_path);
|
||||
return abs_file_path;
|
||||
}
|
||||
|
||||
delim = g_strrstr(abs_cfg_path, "/");
|
||||
*(delim + 1) = '\0';
|
||||
|
||||
abs_file_path = g_strconcat(abs_cfg_path, file_path, NULL);
|
||||
return abs_file_path;
|
||||
}
|
||||
|
||||
NvDsYamlParserStatus
|
||||
ds_parse_rtsp_output(GstElement * sink,
|
||||
GstRTSPServer *server, GstRTSPMediaFactory *factory,
|
||||
gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
|
||||
int total_docs = docs.size();
|
||||
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
|
||||
YAML::const_iterator itr = docs[i].begin();
|
||||
std::string group_name = itr->first.as<std::string>();
|
||||
docs_indx_umap[group_name] = i;
|
||||
docs_indx_vec.push_back(i);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!sink || !server || !factory)
|
||||
{
|
||||
std::cerr << "[ERROR] Pass element does not exist!" << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
} else {
|
||||
GstElementFactory *factory = GST_ELEMENT_GET_CLASS(sink)->elementfactory;
|
||||
if (g_strcmp0(GST_OBJECT_NAME(factory), "udpsink")) {
|
||||
std::cerr << "[ERROR] Passed element is not udpsink" << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int docs_indx_vec_size = docs_indx_vec.size();
|
||||
|
||||
int docs_indx_umap_size = docs_indx_umap.size();
|
||||
|
||||
if (docs_indx_umap_size != docs_indx_vec_size) {
|
||||
std::cerr << "[ERROR] Duplicate group names in the config file : " << group << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
|
||||
for (int i = 0; i< docs_indx_vec_size; i++)
|
||||
{
|
||||
int indx = docs_indx_vec [i];
|
||||
for(YAML::const_iterator itr = docs[indx][group].begin(); itr != docs[indx][group].end(); ++itr)
|
||||
{
|
||||
paramKey = itr->first.as<std::string>();
|
||||
|
||||
if(paramKey == "udpport") {
|
||||
char udpsrc_pipeline[512];
|
||||
g_object_set (G_OBJECT (sink), "host", "224.224.255.255", "port",
|
||||
itr->second.as<guint>(), "async", FALSE, "sync", 0, NULL);
|
||||
sprintf (udpsrc_pipeline,
|
||||
"( udpsrc name=pay0 port=%s buffer-size=65536 caps=\"application/x-rtp, media=video, "
|
||||
"clock-rate=90000, encoding-name=H264, payload=96 \" )",
|
||||
itr->second.as<std::string>().c_str());
|
||||
gst_rtsp_media_factory_set_launch (factory, udpsrc_pipeline);
|
||||
}
|
||||
else if(paramKey == "rtspport") {
|
||||
g_object_set (G_OBJECT(server), "service", itr->second.as<std::string>().c_str(), NULL);
|
||||
g_print("Please reach RTSP with rtsp://ip:%s/ds-out-avc\n", itr->second.as<std::string>().c_str());
|
||||
}
|
||||
else {
|
||||
std::cerr << "!! [WARNING] Unknown param found : " << paramKey << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NVDS_YAML_PARSER_SUCCESS;
|
||||
}
|
||||
|
||||
NvDsYamlParserStatus
|
||||
ds_parse_enc_config(GstElement *encoder,
|
||||
gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
|
||||
int total_docs = docs.size();
|
||||
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
|
||||
YAML::const_iterator itr = docs[i].begin();
|
||||
std::string group_name = itr->first.as<std::string>();
|
||||
docs_indx_umap[group_name] = i;
|
||||
docs_indx_vec.push_back(i);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!encoder)
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
else {
|
||||
GstElementFactory *factory = GST_ELEMENT_GET_CLASS(encoder)->elementfactory;
|
||||
if (g_strcmp0(GST_OBJECT_NAME(factory), "nvv4l2h264enc")
|
||||
&& g_strcmp0(GST_OBJECT_NAME(factory), "nvv4l2h265enc")) {
|
||||
std::cerr << "[ERROR] Passed element is not encoder" << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int docs_indx_vec_size = docs_indx_vec.size();
|
||||
|
||||
int docs_indx_umap_size = docs_indx_umap.size();
|
||||
|
||||
if (docs_indx_umap_size != docs_indx_vec_size) {
|
||||
std::cerr << "[ERROR] Duplicate group names in the config file : " << group << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
|
||||
for (int i = 0; i< docs_indx_vec_size; i++)
|
||||
{
|
||||
int indx = docs_indx_vec [i];
|
||||
for(YAML::const_iterator itr = docs[indx][group].begin(); itr != docs[indx][group].end(); ++itr)
|
||||
{
|
||||
paramKey = itr->first.as<std::string>();
|
||||
|
||||
if(paramKey == "bitrate") {
|
||||
g_object_set(G_OBJECT(encoder), "bitrate",
|
||||
itr->second.as<guint>(), NULL);
|
||||
}
|
||||
else if(paramKey == "iframeinterval") {
|
||||
g_object_set(G_OBJECT(encoder), "iframeinterval",
|
||||
itr->second.as<guint>(), NULL);
|
||||
}
|
||||
else {
|
||||
std::cerr << "!! [WARNING] Unknown param found : " << paramKey << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NVDS_YAML_PARSER_SUCCESS;
|
||||
}
|
||||
|
||||
NvDsYamlParserStatus
|
||||
ds_parse_videotemplate_config(GstElement *vtemplate,
|
||||
gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
|
||||
int total_docs = docs.size();
|
||||
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
|
||||
YAML::const_iterator itr = docs[i].begin();
|
||||
std::string group_name = itr->first.as<std::string>();
|
||||
docs_indx_umap[group_name] = i;
|
||||
docs_indx_vec.push_back(i);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!vtemplate)
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
else {
|
||||
GstElementFactory *factory = GST_ELEMENT_GET_CLASS(vtemplate)->elementfactory;
|
||||
if (g_strcmp0(GST_OBJECT_NAME(factory), "nvdsvideotemplate")) {
|
||||
std::cerr << "[ERROR] Passed element is not nvdsvideotemplate" << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
int docs_indx_vec_size = docs_indx_vec.size();
|
||||
|
||||
int docs_indx_umap_size = docs_indx_umap.size();
|
||||
|
||||
if (docs_indx_umap_size != docs_indx_vec_size) {
|
||||
std::cerr << "[ERROR] Duplicate group names in the config file : " << group << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
|
||||
for (int i = 0; i< docs_indx_vec_size; i++)
|
||||
{
|
||||
int indx = docs_indx_vec [i];
|
||||
for(YAML::const_iterator itr = docs[indx][group].begin(); itr != docs[indx][group].end(); ++itr)
|
||||
{
|
||||
paramKey = itr->first.as<std::string>();
|
||||
|
||||
if(paramKey == "customlib-name") {
|
||||
g_object_set(G_OBJECT(vtemplate), "customlib-name",
|
||||
itr->second.as<std::string>().c_str(), NULL);
|
||||
}
|
||||
else if(paramKey == "customlib-props") {
|
||||
g_object_set(G_OBJECT(vtemplate), "customlib-props",
|
||||
itr->second.as<std::string>().c_str(), NULL);
|
||||
}
|
||||
else {
|
||||
std::cerr << "!! [WARNING] Unknown param found : " << paramKey << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NVDS_YAML_PARSER_SUCCESS;
|
||||
}
|
||||
|
||||
NvDsYamlParserStatus
|
||||
ds_parse_ocdr_videotemplate_config(GstElement *vtemplate,
|
||||
gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
if (!vtemplate)
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
else {
|
||||
GstElementFactory *factory = GST_ELEMENT_GET_CLASS(vtemplate)->elementfactory;
|
||||
if (g_strcmp0(GST_OBJECT_NAME(factory), "nvdsvideotemplate")) {
|
||||
std::cerr << "[ERROR] Passed element is not nvdsvideotemplate" << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
YAML::Node node = YAML::LoadFile(cfg_file_path);
|
||||
|
||||
YAML::Node docs = node[group];
|
||||
|
||||
if(docs["customlib-name"]) {
|
||||
std::string libPath = docs["customlib-name"].as<std::string>();
|
||||
g_object_set(G_OBJECT(vtemplate), "customlib-name",
|
||||
libPath.c_str(), NULL);
|
||||
}
|
||||
|
||||
if(docs["customlib-props"]) {
|
||||
auto listNode = docs["customlib-props"];
|
||||
for(uint32_t i = 0; i < listNode.size(); i++) {
|
||||
std::string tmpProb = listNode[i].as<std::string>();
|
||||
g_object_set(G_OBJECT(vtemplate), "customlib-props",
|
||||
tmpProb.c_str(), NULL);
|
||||
}
|
||||
}
|
||||
|
||||
return NVDS_YAML_PARSER_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
guint
|
||||
ds_parse_group_type(gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
|
||||
int total_docs = docs.size();
|
||||
guint val = 0;
|
||||
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
|
||||
if (docs[i][group]["type"]) {
|
||||
val= docs[i][group]["type"].as<guint>();
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
NvDsYamlParserStatus ds_parse_nvdsanalytics(GstElement *element, gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
NvDsYamlParserStatus ret = NVDS_YAML_PARSER_SUCCESS;
|
||||
GstElementFactory *factory = GST_ELEMENT_GET_CLASS(element)->elementfactory;
|
||||
if (g_strcmp0(GST_OBJECT_NAME(factory), "nvdsanalytics")) {
|
||||
std::cerr << "[ERROR] Passed element is not nvdsanalytics" << std::endl;
|
||||
ret = NVDS_YAML_PARSER_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string paramKey = "";
|
||||
auto docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
int total_docs = docs.size();
|
||||
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (docs[i][group].Type() != YAML::NodeType::Null) {
|
||||
if (docs[i][group]["enable"]) {
|
||||
gboolean val= docs[i][group]["enable"].as<gboolean>();
|
||||
if(val == FALSE) {
|
||||
g_print("!! [WARNING] \"analytics\" group not enabled.\n");
|
||||
g_object_set(G_OBJECT(element), "enable", val, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
YAML::const_iterator itr = docs[i].begin();
|
||||
std::string group_name = itr->first.as<std::string>();
|
||||
docs_indx_umap[group_name] = i;
|
||||
docs_indx_vec.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
int docs_indx_vec_size = docs_indx_vec.size();
|
||||
int docs_indx_umap_size = docs_indx_umap.size();
|
||||
if (docs_indx_umap_size != docs_indx_vec_size) {
|
||||
std::cerr << "[ERROR] Duplicate group names in the config file : " << group << std::endl;
|
||||
ret = NVDS_YAML_PARSER_ERROR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (int i = 0; i< docs_indx_vec_size; i++)
|
||||
{
|
||||
int indx = docs_indx_vec [i];
|
||||
for(YAML::const_iterator itr = docs[indx][group].begin(); itr != docs[indx][group].end(); ++itr)
|
||||
{
|
||||
paramKey = itr->first.as<std::string>();
|
||||
|
||||
if (paramKey == "enable" && docs[indx][group]["enable"].as<gboolean>() == TRUE) {
|
||||
continue;
|
||||
} else if(paramKey == "config-file") {
|
||||
std::string temp = itr->second.as<std::string>();
|
||||
if (temp.empty()) {
|
||||
g_printerr ("Error: Could not parse config-file-path in %s.\n", group);
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
char *config_file_path = get_absolute_file_path(cfg_file_path, temp.c_str());
|
||||
if (!config_file_path) {
|
||||
g_printerr ("Error: Could not get absolute path for config-file-path in %s.\n", group);
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
g_print("Setting config-file for nvdsanalytics: %s\n", config_file_path);
|
||||
g_object_set(G_OBJECT(element), "config-file", config_file_path, NULL);
|
||||
g_free(config_file_path);
|
||||
} else {
|
||||
std::cerr << "!! [WARNING] Unknown param found for nvdsanalytics: " << paramKey << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char* dgpus_unsupport_hw_enc[] = {
|
||||
"NVIDIA A100",
|
||||
"NVIDIA A30",
|
||||
"NVIDIA H100", // NVIDIA H100 SXM, NVIDIA H100 PCIe, NVIDIA H100 NVL
|
||||
"NVIDIA T500",
|
||||
"GeForce MX570 A",
|
||||
"DGX A100"
|
||||
};
|
||||
|
||||
static bool
|
||||
is_enc_hw_support() {
|
||||
int current_device = -1;
|
||||
cudaGetDevice(¤t_device);
|
||||
struct cudaDeviceProp prop;
|
||||
cudaGetDeviceProperties(&prop, current_device);
|
||||
bool enc_hw_support = TRUE;
|
||||
if (prop.integrated) {
|
||||
char device_name[50];
|
||||
FILE* ptr = fopen("/proc/device-tree/model", "r");
|
||||
|
||||
if (ptr) {
|
||||
while (fgets(device_name, 50, ptr) != NULL) {
|
||||
if (strstr(device_name,"Orin") && (strstr(device_name,"Nano")))
|
||||
enc_hw_support = FALSE;
|
||||
}
|
||||
}
|
||||
fclose(ptr);
|
||||
} else {
|
||||
for (uint32_t i = 0; i < sizeof(dgpus_unsupport_hw_enc)/sizeof(dgpus_unsupport_hw_enc[0]); i++) {
|
||||
if (!strncasecmp(prop.name, dgpus_unsupport_hw_enc[i], strlen(dgpus_unsupport_hw_enc[i]))) {
|
||||
enc_hw_support = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return enc_hw_support;
|
||||
}
|
||||
|
||||
guint
|
||||
ds_parse_enc_type(gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
|
||||
int total_docs = docs.size();
|
||||
guint val = 0;
|
||||
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
if (docs[i][group]["enc-type"]) {
|
||||
val = docs[i][group]["enc-type"].as<guint>();
|
||||
// If hardware encoding is configured but the hardware does not support it,
|
||||
// fallback to software encoding
|
||||
if (val == 0 && !is_enc_hw_support()) {
|
||||
g_print("** WARN: hardware encoding is not supported, fallback to software encoding \n");
|
||||
val = 1;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
guint
|
||||
ds_parse_enc_codec(gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
|
||||
int total_docs = docs.size();
|
||||
guint val = 0;
|
||||
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
|
||||
if (docs[i][group]["codec"]) {
|
||||
val= docs[i][group]["codec"].as<guint>();
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
GString *
|
||||
ds_parse_file_name(gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
|
||||
int total_docs = docs.size();
|
||||
GString *str = NULL;
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
if (docs[i][group]["filename"]) {
|
||||
std::string temp = docs[i][group]["filename"].as<std::string>();
|
||||
str = g_string_new(temp.c_str());
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GString *
|
||||
ds_parse_config_yml_filepath(gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
|
||||
int total_docs = docs.size();
|
||||
GString *str = NULL;
|
||||
|
||||
g_print("total %d item\n",total_docs);
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
g_print("group %s found %d\n", group, !(docs[i][group]["config-file-path"]));
|
||||
if (docs[i][group]["config-file-path"]) {
|
||||
std::string temp = docs[i][group]["config-file-path"].as<std::string>();
|
||||
str = g_string_new(temp.c_str());
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* this is only for video encoding. */
|
||||
void
|
||||
create_video_encoder(bool isH264, int enc_type, GstElement** conv_capfilter,
|
||||
GstElement** outenc, GstElement** encparse, GstElement** rtppay)
|
||||
{
|
||||
GstCaps *caps = NULL;
|
||||
GstCapsFeatures *feature = NULL;
|
||||
|
||||
g_print("in create_video_encoder, isH264:%d, enc_type:%d\n", isH264, enc_type);
|
||||
caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING,
|
||||
"I420", NULL);
|
||||
if(enc_type == ENCODER_TYPE_HW) {
|
||||
feature = gst_caps_features_new ("memory:NVMM", NULL);
|
||||
gst_caps_set_features (caps, 0, feature);
|
||||
}
|
||||
g_object_set (G_OBJECT (*conv_capfilter), "caps", caps, NULL);
|
||||
|
||||
if(isH264) {
|
||||
if(enc_type == ENCODER_TYPE_HW) {
|
||||
*outenc = gst_element_factory_make ("nvv4l2h264enc" ,"nvvideo-h264enc");
|
||||
} else {
|
||||
*outenc = gst_element_factory_make ("x264enc" ,"x264enc");
|
||||
}
|
||||
*encparse = gst_element_factory_make ("h264parse", "encparse");
|
||||
if(rtppay)
|
||||
*rtppay = gst_element_factory_make ("rtph264pay", "rtppay");
|
||||
} else {
|
||||
if(enc_type == ENCODER_TYPE_HW) {
|
||||
*outenc = gst_element_factory_make ("nvv4l2h265enc" ,"nvvideo-h265enc");
|
||||
} else {
|
||||
*outenc = gst_element_factory_make ("x265enc" ,"x265enc");
|
||||
}
|
||||
*encparse = gst_element_factory_make ("h265parse", "encparse");
|
||||
if(rtppay)
|
||||
*rtppay = gst_element_factory_make ("rtph265pay", "rtppay");
|
||||
}
|
||||
}
|
||||
|
||||
guint
|
||||
ds_parse_group_enable(gchar *cfg_file_path, const char* group)
|
||||
{
|
||||
std::string paramKey = "";
|
||||
std::vector<YAML::Node> docs = YAML::LoadAllFromFile(cfg_file_path);
|
||||
std::vector<int> docs_indx_vec;
|
||||
std::unordered_map<std::string, int> docs_indx_umap;
|
||||
int total_docs = docs.size();
|
||||
guint val = 0;
|
||||
for (int i =0; i < total_docs;i++)
|
||||
{
|
||||
if (!docs[i][group].IsNull()) {
|
||||
if (docs[i][group]["enable"]) {
|
||||
val= docs[i][group]["enable"].as<guint>();
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Function to get the absolute path of a file.*/
|
||||
gboolean
|
||||
get_absolute_file_path_yaml (
|
||||
const gchar * cfg_file_path, const gchar * file_path,
|
||||
char *abs_path_str)
|
||||
{
|
||||
gchar abs_cfg_path[PATH_MAX + 1];
|
||||
gchar abs_real_file_path[PATH_MAX + 1];
|
||||
gchar *abs_file_path;
|
||||
gchar *delim;
|
||||
|
||||
/* Absolute path. No need to resolve further. */
|
||||
if (file_path[0] == '/') {
|
||||
/* Check if the file exists, return error if not. */
|
||||
if (!realpath (file_path, abs_real_file_path)) {
|
||||
/* Ignore error if file does not exist and use the unresolved path. */
|
||||
if (errno != ENOENT)
|
||||
return FALSE;
|
||||
}
|
||||
g_strlcpy (abs_path_str, abs_real_file_path, _PATH_MAX);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Get the absolute path of the config file. */
|
||||
if (!realpath (cfg_file_path, abs_cfg_path)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Remove the file name from the absolute path to get the directory of the
|
||||
* config file. */
|
||||
delim = g_strrstr (abs_cfg_path, "/");
|
||||
*(delim + 1) = '\0';
|
||||
|
||||
/* Get the absolute file path from the config file's directory path and
|
||||
* relative file path. */
|
||||
abs_file_path = g_strconcat (abs_cfg_path, file_path, nullptr);
|
||||
|
||||
/* Resolve the path.*/
|
||||
if (realpath (abs_file_path, abs_real_file_path) == nullptr) {
|
||||
/* Ignore error if file does not exist and use the unresolved path. */
|
||||
if (errno == ENOENT)
|
||||
g_strlcpy (abs_real_file_path, abs_file_path, _PATH_MAX);
|
||||
else
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_free (abs_file_path);
|
||||
|
||||
g_strlcpy (abs_path_str, abs_real_file_path, _PATH_MAX);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
NvDsYamlParserStatus
|
||||
nvds_parse_postprocess (GstElement *element, gchar* app_cfg_file_path, const char* group)
|
||||
{
|
||||
NvDsYamlParserStatus ret = NVDS_YAML_PARSER_SUCCESS;
|
||||
GstElementFactory *factory = GST_ELEMENT_GET_CLASS(element)->elementfactory;
|
||||
if (g_strcmp0(GST_OBJECT_NAME(factory), "nvdspostprocess")) {
|
||||
std::cerr << "[ERROR] Passed element is not nvdspostprocess" << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
|
||||
if (!app_cfg_file_path) {
|
||||
printf("Config file not provided.\n");
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
|
||||
YAML::Node configyml = YAML::LoadFile(app_cfg_file_path);
|
||||
for(YAML::const_iterator itr = configyml[group].begin();
|
||||
itr != configyml[group].end(); ++itr)
|
||||
{
|
||||
std::string paramKey = itr->first.as<std::string>();
|
||||
if (paramKey == "config-file-path") {
|
||||
std::string temp = itr->second.as<std::string>();
|
||||
char* str = (char*) malloc(sizeof(char) * 1024);
|
||||
std::strncpy (str, temp.c_str(), 1024);
|
||||
char *path = (char*) malloc(sizeof(char) * 1024);
|
||||
if (!get_absolute_file_path_yaml (app_cfg_file_path, str,
|
||||
path)) {
|
||||
ret = NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
g_object_set(G_OBJECT(element), "postprocesslib-config-file",
|
||||
path, NULL);
|
||||
printf("postprocesslib-config-file:%s\n", path);
|
||||
g_free (str);
|
||||
g_free(path);
|
||||
} else if (paramKey == "lib-name") {
|
||||
std::string temp = itr->second.as<std::string>();
|
||||
char* str = (char*) malloc(sizeof(char) * 1024);
|
||||
std::strncpy (str, temp.c_str(), 1024);
|
||||
char *path = (char*) malloc(sizeof(char) * 1024);
|
||||
if (!get_absolute_file_path_yaml (app_cfg_file_path, str,
|
||||
path)) {
|
||||
ret = NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
g_object_set(G_OBJECT(element), "postprocesslib-name",
|
||||
path, NULL);
|
||||
printf("postprocesslib-name:%s\n", path);
|
||||
g_free (str);
|
||||
g_free(path);
|
||||
} else {
|
||||
printf("[WARNING] Unknown param found in postprocess: %s\n", paramKey.c_str());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
NvDsYamlParserStatus
|
||||
nvds_parse_preprocess (GstElement *element, gchar* app_cfg_file_path, const char* group)
|
||||
{
|
||||
NvDsYamlParserStatus ret = NVDS_YAML_PARSER_SUCCESS;
|
||||
GstElementFactory *factory = GST_ELEMENT_GET_CLASS(element)->elementfactory;
|
||||
if (g_strcmp0(GST_OBJECT_NAME(factory), "nvdspreprocess")) {
|
||||
std::cerr << "[ERROR] Passed element is not nvdspreprocess" << std::endl;
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
|
||||
if (!app_cfg_file_path) {
|
||||
printf("Config file not provided.\n");
|
||||
return NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
|
||||
YAML::Node configyml = YAML::LoadFile(app_cfg_file_path);
|
||||
for(YAML::const_iterator itr = configyml[group].begin();
|
||||
itr != configyml[group].end(); ++itr)
|
||||
{
|
||||
std::string paramKey = itr->first.as<std::string>();
|
||||
if (paramKey == "config-file-path") {
|
||||
std::string temp = itr->second.as<std::string>();
|
||||
char* str = (char*) malloc(sizeof(char) * 1024);
|
||||
std::strncpy (str, temp.c_str(), 1024);
|
||||
char *config_file_path = (char*) malloc(sizeof(char) * 1024);
|
||||
if (!get_absolute_file_path_yaml (app_cfg_file_path, str,
|
||||
config_file_path)) {
|
||||
ret = NVDS_YAML_PARSER_ERROR;
|
||||
}
|
||||
g_object_set(G_OBJECT(element), "config-file",
|
||||
config_file_path, NULL);
|
||||
printf("config_file_path:%s\n", config_file_path);
|
||||
g_free (str);
|
||||
g_free(config_file_path);
|
||||
} else {
|
||||
printf("[WARNING] Unknown param found in preprocess: %s\n", paramKey.c_str());
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
parse_streammux_width_height_yaml (gint *width, gint *height, gchar *cfg_file_path)
|
||||
{
|
||||
YAML::Node configyml = YAML::LoadFile(cfg_file_path);
|
||||
|
||||
for(YAML::const_iterator itr = configyml["streammux"].begin();
|
||||
itr != configyml["streammux"].end(); ++itr) {
|
||||
std::string paramKey = itr->first.as<std::string>();
|
||||
if (paramKey == "width") {
|
||||
*width = itr->second.as<gint>();
|
||||
} else if(paramKey == "height"){
|
||||
*height = itr->second.as<gint>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
parse_sink_type_yaml (gint *type, gchar *cfg_file_path)
|
||||
{
|
||||
YAML::Node configyml = YAML::LoadFile(cfg_file_path);
|
||||
|
||||
for(YAML::const_iterator itr = configyml["sink"].begin();
|
||||
itr != configyml["sink"].end(); ++itr) {
|
||||
std::string paramKey = itr->first.as<std::string>();
|
||||
if (paramKey == "sink-type") {
|
||||
*type = itr->second.as<gint>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
parse_sink_enc_type_yaml (gint *enc_type, gchar *cfg_file_path)
|
||||
{
|
||||
YAML::Node configyml = YAML::LoadFile(cfg_file_path);
|
||||
|
||||
for(YAML::const_iterator itr = configyml["sink"].begin();
|
||||
itr != configyml["sink"].end(); ++itr) {
|
||||
std::string paramKey = itr->first.as<std::string>();
|
||||
if (paramKey == "enc-type") {
|
||||
int value = itr->second.as<gint>();
|
||||
if(value == 0 || value == 1){
|
||||
*enc_type = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef _DS_YAML_PARSER_H_
|
||||
#define _DS_YAML_PARSER_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "nvds_yml_parser.h"
|
||||
#include <gst/gst.h>
|
||||
#include <gst/rtsp-server/rtsp-server.h>
|
||||
#define _PATH_MAX 1024
|
||||
|
||||
typedef enum { ENCODER_TYPE_HW, ENCODER_TYPE_SW } EncHwSwType;
|
||||
|
||||
NvDsYamlParserStatus ds_parse_rtsp_output(GstElement *sink,
|
||||
GstRTSPServer *server,
|
||||
GstRTSPMediaFactory *factory,
|
||||
gchar *cfg_file_path,
|
||||
const char *group);
|
||||
|
||||
NvDsYamlParserStatus ds_parse_enc_config(GstElement *encoder,
|
||||
gchar *cfg_file_path,
|
||||
const char *group);
|
||||
|
||||
guint ds_parse_group_type(gchar *cfg_file_path, const char *group);
|
||||
|
||||
guint ds_parse_enc_type(gchar *cfg_file_path, const char *group);
|
||||
|
||||
guint ds_parse_enc_codec(gchar *cfg_file_path, const char *group);
|
||||
|
||||
GString *ds_parse_file_name(gchar *cfg_file_path, const char *group);
|
||||
|
||||
GString *ds_parse_config_yml_filepath(gchar *cfg_file_path, const char *group);
|
||||
|
||||
NvDsYamlParserStatus ds_parse_videotemplate_config(GstElement *vtemplate,
|
||||
gchar *cfg_file_path,
|
||||
const char *group);
|
||||
|
||||
NvDsYamlParserStatus ds_parse_ocdr_videotemplate_config(GstElement *vtemplate,
|
||||
gchar *cfg_file_path,
|
||||
const char *group);
|
||||
|
||||
NvDsYamlParserStatus ds_parse_nvdsanalytics(GstElement *element,
|
||||
gchar *cfg_file_path,
|
||||
const char *group);
|
||||
|
||||
void create_video_encoder(bool isH264, int enc_type,
|
||||
GstElement **conv_capfilter, GstElement **outenc,
|
||||
GstElement **encparse, GstElement **rtppay);
|
||||
|
||||
/** Function to get the absolute path of a file.*/
|
||||
gboolean get_absolute_file_path_yaml(const gchar *cfg_file_path,
|
||||
const gchar *file_path,
|
||||
char *abs_path_str);
|
||||
|
||||
/** Parse preprocess configurations. */
|
||||
NvDsYamlParserStatus nvds_parse_preprocess(GstElement *element,
|
||||
gchar *app_cfg_file_path,
|
||||
const char *group);
|
||||
|
||||
/** Parse postprocess configurations. */
|
||||
NvDsYamlParserStatus nvds_parse_postprocess(GstElement *element,
|
||||
gchar *app_cfg_file_path,
|
||||
const char *group);
|
||||
|
||||
/** Parse width and height of nvstreammux. */
|
||||
void parse_streammux_width_height_yaml(gint *width, gint *height,
|
||||
gchar *cfg_file_path);
|
||||
|
||||
/** Parse type of sink. */
|
||||
void parse_sink_type_yaml(gint *type, gchar *cfg_file_path);
|
||||
|
||||
/** Parse enc type of sink. */
|
||||
void parse_sink_enc_type_yaml(gint *enc_type, gchar *cfg_file_path);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,83 @@
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <ifaddrs.h>
|
||||
#include <iostream>
|
||||
#include <net/if.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <uuid/uuid.h>
|
||||
// 获取第一个非零 MAC
|
||||
std::string getFirstMac() {
|
||||
int fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (fd < 0)
|
||||
return "";
|
||||
|
||||
struct ifreq ifr;
|
||||
struct ifconf ifc;
|
||||
char buf[1024];
|
||||
ifc.ifc_len = sizeof(buf);
|
||||
ifc.ifc_buf = buf;
|
||||
if (ioctl(fd, SIOCGIFCONF, &ifc) == -1) {
|
||||
close(fd);
|
||||
return "";
|
||||
}
|
||||
|
||||
struct ifreq *it = ifc.ifc_req;
|
||||
const struct ifreq *const end = it + (ifc.ifc_len / sizeof(struct ifreq));
|
||||
|
||||
for (; it != end; ++it) {
|
||||
strcpy(ifr.ifr_name, it->ifr_name);
|
||||
if (ioctl(fd, SIOCGIFHWADDR, &ifr) == 0) {
|
||||
unsigned char *mac =
|
||||
reinterpret_cast<unsigned char *>(ifr.ifr_hwaddr.sa_data);
|
||||
if (!(mac[0] == 0 && mac[1] == 0 && mac[2] == 0 && mac[3] == 0 &&
|
||||
mac[4] == 0 && mac[5] == 0)) {
|
||||
char mac_str[18];
|
||||
snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
close(fd);
|
||||
return std::string(mac_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string GetDeviceID() {
|
||||
// 优先读取环境变量 DEVICE_ID
|
||||
const char *env = std::getenv("DEVICE_ID");
|
||||
if (env && *env)
|
||||
return std::string(env);
|
||||
|
||||
// 尝试读取 /etc/machine-id
|
||||
std::ifstream f("/etc/machine-id");
|
||||
if (f) {
|
||||
std::string id;
|
||||
std::getline(f, id);
|
||||
id.erase(0, id.find_first_not_of(" \t\n\r"));
|
||||
id.erase(id.find_last_not_of(" \t\n\r") + 1);
|
||||
if (!id.empty())
|
||||
return id;
|
||||
}
|
||||
|
||||
// fallback: hostname + first non零 MAC
|
||||
char hostname[256];
|
||||
if (gethostname(hostname, sizeof(hostname)) != 0) {
|
||||
strcpy(hostname, "unknown");
|
||||
}
|
||||
|
||||
std::string mac = getFirstMac();
|
||||
return std::string(hostname) + "|" + mac;
|
||||
}
|
||||
|
||||
std::string generateUUID() {
|
||||
uuid_t id;
|
||||
uuid_generate(id);
|
||||
char str[37]; // UUID 36 个字符 + 终止符
|
||||
uuid_unparse_lower(id, str);
|
||||
return std::string(str);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
// my_utils.h
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
extern gchar *yaml_file_path;
|
||||
std::string getFirstMac();
|
||||
std::string GetDeviceID();
|
||||
std::string generateUUID();
|
||||
Reference in New Issue
Block a user