deepstream开发学习笔记: 追踪越界

news2024/11/19 6:22:42

main.cpp 文件解析

!](https://img-blog.csdnimg.cn/e66b0be269414c70b2a07ae58001d0e8.png)

1. 创建元素前的准备

GStreamer是一个开源的流媒体框架,用于构建音频和视频流应用程序。它提供了一组库和工具,可以通过它们将多个组件(element)组合在一起以构建流媒体应用程序。以下是对几个常见组件的简要解释:

Pipeline:由多个元素组成的流水线。在 GStreamer 中,通过将多个元素连接在一起来构建一个流水线来实现多媒体处理和传输。

Source:产生数据的元素。在流媒体应用中,数据可以来自文件、摄像头、网络等。

Streammux:将多个输入流合并成一个流的元素,例如将多个视频流合并为一个视频流,或将多个音频流合并为一个音频流。

Pgie:深度学习推理的元素,用于执行物体识别或其他任务。PGIE 通常由 NVIDIA 的 DeepStream SDK 提供,可以使用预先训练的模型或者训练自己的模型。

Nvvidconv:将一个视频格式转换为另一个视频格式的元素,例如将 NV12 格式转换为 RGBA 格式。

Nvosd:将数据绘制在视频帧上的元素,例如绘制物体检测的边界框和类别标签。

Nvvidconv_postosd:在 Nvosd 元素之后的视频格式转换器。

Caps:对数据流进行格式化和限制的元素,可以用于定义媒体流的格式,例如图像的大小和颜色空间。

Encoder:将原始数据编码为特定格式的元素,例如将 H.264 数据编码为 H.264 格式或 H.265 格式。

Rtppay:将编码后的数据打包为 RTP 包的元素,用于在网络上传输媒体流。

Sink:接收数据并将其保存到文件、显示器、网络等的元素。

GstBus 是一个监视 GStreamer pipeline 中消息的结构体,包括错误、警告和状态改变等信息。可以使用 gst_pipeline_get_bus() 函数从 pipeline 中获取 GstBus 对象。

bus_watch_id 是监视 bus 消息的标识符,可以使用 gst_bus_add_watch() 函数添加 bus 消息处理器,并返回 bus_watch_id。

GstPad 是一个连接两个 GStreamer 元素之间的数据路径。元素之间可以有多个 pad,通过在 pad 上连接事件传递数据。

osd_sink_pad 是一个 GstPad,它连接 nvosd 元素的 sink pad。在这里,添加了一个探针(probe)用于获取元数据。

GstCaps 用于描述多媒体数据流的媒体类型和格式。可以通过创建一个 GstCaps 对象,然后通过 capsfilter 元素设置 pipeline 中的某些元素的输出格式。

// main函数
int main(int argc, char *argv[])
{
  GMainLoop *loop = NULL;
  // 创建各种元素
  GstElement *pipeline = NULL, *source = NULL, *streammux = NULL, *pgie = NULL, *nvvidconv = NULL,
             *nvosd = NULL, *nvvidconv_postosd = NULL, *caps = NULL, *encoder = NULL, *rtppay = NULL, *sink = NULL;
  g_print("With tracker\n");
  GstBus *bus = NULL;
  guint bus_watch_id = 0;
  GstPad *osd_sink_pad = NULL;
  GstCaps *caps_filter = NULL;

这里-1指的是默认Device不是不用GPU

  guint bitrate = 5000000;       // 比特率
  gchar *codec = "H264";         // 设置编码格式
  guint updsink_port_num = 5400; // 设置端口号
  guint rtsp_port_num = 8554;    // 设置RTSP端口号
  gchar *rtsp_path = "/ds-test"; // 设置RTSP路径

  int current_device = -1;
  cudaGetDevice(&current_device);
  struct cudaDeviceProp prop;
  cudaGetDeviceProperties(&prop, current_device);

gst_init 是 GStreamer 库的初始化函数,它负责初始化 GStreamer 库及其插件等各种资源。如果在使用 GStreamer 库之前没有调用 gst_init,可能会导致一些问题,例如无法加载插件、崩溃等等。因此,在使用 GStreamer 库之前,通常需要调用 gst_init 进行初始化。

loop 则是一个 GMainLoop 对象,是 GStreamer 库的主循环。GStreamer 库使用 GMainLoop 来管理事件和消息处理。GStreamer 应用程序可以创建自己的 GMainLoop,但是为了方便,大多数应用程序使用 GStreamer 库提供的默认循环 g_main_loop_new(),并使用 g_main_loop_run() 来启动循环。

/* Check input arguments */
  if (argc != 2)
  {
    g_printerr("OR: %s <H264 filename>\n", argv[0]);
    return -1;
  }

  /* Standard GStreamer initialization */
  // 初始化GStreamer
  gst_init(&argc, &argv);
  loop = g_main_loop_new(NULL, FALSE);

2. 创建元素

在这里插入图片描述

gst_pipeline_new(“ds-tracker-pipeline”) 创建了一个管道,并且指定了它的名字为 “ds-tracker-pipeline”。

create_source_bin(0, argv[1]) 创建了一个包含一个 filesrc 元素和一个 decodebin 元素的源管道,用于从视频文件中读取帧数据。

gst_element_factory_make(“nvstreammux”, “stream-muxer”) 创建了一个流复用器元素,用于将多个流合并为一个流,并将多帧图像打包成 batch。

gst_element_factory_make(“nvinfer”, “primary-nvinference-engine”) 创建了一个推理引擎元素,用于执行物体检测或分类等推理任务。

gst_element_factory_make(“nvvideoconvert”, “nvvideo-converter”) 和

gst_element_factory_make(“nvdsosd”, “nv-onscreendisplay”) 创建了用于图像处理的两个元素。

gst_element_factory_make(“nvvideoconvert”, “convertor_postosd”) 创建了一个用于将 NV12 格式转换为 RGBA 格式的元素。

gst_element_factory_make(“capsfilter”, “filter”) 创建了一个用于配置格式、分辨率等属性的元素。

gst_element_factory_make 是 GStreamer 中创建元素的一个常用函数,它接收两个参数,第一个是元素工厂的名称,第二个是元素在管道中的名称。通过元素工厂的名称,函数可以在 GStreamer 中找到对应的工厂函数,进而创建出对应的元素。

文件或者摄像头提供的是NV12的格式,也是deepstream的默认格式, 需要转换成RGBA做后续的图像处理

A(alpha)是透明度

// ==================== 创建元素 ====================

  pipeline = gst_pipeline_new("ds-tracker-pipeline"); // 创建管道

  source = create_source_bin(0, argv[1]);                                   // 创建source_bin元素, 用于从文件中读取视频流
  streammux = gst_element_factory_make("nvstreammux", "stream-muxer");      // 创建流复用器, 用于将多个流合并为一个流 , 以及将多帧画面打包batch
  pgie = gst_element_factory_make("nvinfer", "primary-nvinference-engine"); // 创建PGIE元素, 用于执行推理
  // nvtracker = gst_element_factory_make("nvtracker", "tracker");                        // 创建tracker元素, 用于跟踪识别到的物体
  nvvidconv = gst_element_factory_make("nvvideoconvert", "nvvideo-converter");         // 创建nvvidconv元素, 用于将NV12转换为RGBA
  nvosd = gst_element_factory_make("nvdsosd", "nv-onscreendisplay");                   // 创建nvosd元素, 用于在转换后的RGBA缓冲区上绘制
  nvvidconv_postosd = gst_element_factory_make("nvvideoconvert", "convertor_postosd"); // 创建nvvidconv_postosd元素, 用于将NV12转换为RGBA
  caps = gst_element_factory_make("capsfilter", "filter");                             // 创建caps元素, 用于设置视频格式

剩下的元素补充进来

 // 创建编码器
  if (g_strcmp0(codec, "H264") == 0)
  {
    // 创建H264编码器
    encoder = gst_element_factory_make("nvv4l2h264enc", "encoder");
    printf("Creating H264 Encoder\n");
  }
  else if (g_strcmp0(codec, "H265") == 0)
  {
    // 创建H265编码器
    encoder = gst_element_factory_make("nvv4l2h265enc", "encoder");
    printf("Creating H265 Encoder\n");
  }

  //  创建rtppay元素, 用于将编码后的数据打包为RTP包
  if (g_strcmp0(codec, "H264") == 0)
  {
    rtppay = gst_element_factory_make("rtph264pay", "rtppay");
    printf("Creating H264 rtppay\n");
  }
  else if (g_strcmp0(codec, "H265") == 0)
  {
    rtppay = gst_element_factory_make("rtph265pay", "rtppay");
    printf("Creating H265 rtppay\n");
  }
  // 创建udpsink元素, 用于将RTP包发送到网络
  sink = gst_element_factory_make("udpsink", "udpsink");

  if (!source || !pgie || !nvvidconv || !nvosd || !nvvidconv_postosd ||
      !caps || !encoder || !rtppay || !sink)
  {
    g_printerr("One element could not be created. Exiting.\n");
    return -1;
  }

3. 设置元素参数

这里设置设置streammux元素的参数, 输出帧的大小是1920x1080, 每个批次只有一帧被处理, 如果当前批次帧达不到batch size的大小, 等待MUXER_BATCH_TIMEOUT_USEC微秒数, 处理当前批次所有帧

这里设置设置streammux元素的参数, 输出帧的大小是1920x1080, 每个批次只有一帧被处理, 如果当前批次帧达不到batch size的大小, 等待MUXER_BATCH_TIMEOUT_USEC微秒数, 处理当前批次所有帧

/* The muxer output resolution must be set if the input streams will be of
 * different resolution. The muxer will scale all the input frames to this
 * resolution. */
#define MUXER_OUTPUT_WIDTH 1920
#define MUXER_OUTPUT_HEIGHT 1080

/* Muxer batch formation timeout, for e.g. 40 millisec. Should ideally be set
 * based on the fastest source's framerate. */
#define MUXER_BATCH_TIMEOUT_USEC 40000



  // 1.设置streammux元素的参数
  g_object_set(G_OBJECT(streammux), "batch-size", 1, NULL);
  g_object_set(G_OBJECT(streammux), "width", MUXER_OUTPUT_WIDTH, "height",
               MUXER_OUTPUT_HEIGHT,
               "batched-push-timeout", MUXER_BATCH_TIMEOUT_USEC, NULL);

PGIE: 深度学习推理元素, 这里的PGIE_CONFIG_FILE是配置文件的路径,

// 2.设置PGIE元素的参数
  g_object_set(G_OBJECT(pgie), "config-file-path", PGIE_CONFIG_FILE, NULL);

这里是解释版的配置文件, 有几个点需要注意network-mode=2 这里还是建议把量化好的engine给他

cluster-mode 设置为 2 表示不使用聚类算法

先生成libyolo_decode.so

/usr/local/cuda/bin/nvcc -Xcompiler -fPIC -shared -o lib/yolov5_decode.so ./src/yoloForward_nc.cu ./src/yoloPlugins.cpp ./src/nvdsparsebbox_Yolo.cpp -isystem /usr/include/x86_64-linux-gnu/ -L /usr/lib/x86_64-linux-gnu/ -I /opt/nvidia/deepstream/deepstream/sources/includes -lnvinfer

这里是解释版的配置文件, 有几个点需要注意network-mode=2 这里还是建议把量化好的engine给他

cluster-mode 设置为 2 表示不使用聚类算法

先生成libyolo_decode.so

/usr/local/cuda/bin/nvcc -Xcompiler -fPIC -shared -o lib/yolov5_decode.so ./src/yoloForward_nc.cu ./src/yoloPlugins.cpp ./src/nvdsparsebbox_Yolo.cpp -isystem /usr/include/x86_64-linux-gnu/ -L /usr/lib/x86_64-linux-gnu/ -I /opt/nvidia/deepstream/deepstream/sources/includes -lnvinfer
# GPU ID,用于指定使用哪个 GPU 进行推理。
gpu-id=0

# 输入帧的缩放因子。
net-scale-factor=0.0039215697906911373

# 输入帧的颜色格式,0 表示 RGB,1 表示 BGR。
model-color-format=0

# ONNX 模型文件的路径。
onnx-file=yolov5s.onnx

# TensorRT 引擎文件的路径。
model-engine-file=../weights/yolov5.engine

# 类别标签文件的路径。
labelfile-path=../weights/labels.txt

# 输入数据的维度,格式为 C;H;W。
infer-dims=3;640;640

# 推理时每批次处理的图像数。
batch-size=1

# TensorRT 最大的工作空间大小。
workspace-size=1024

# 网络的计算精度,0 表示 FP32,1 表示 FP16,2 表示 INT8。
network-mode=2

# 模型能够检测的类别数。
num-detected-classes=80

# 两次检测之间的时间间隔。
interval=0

# GIE 实例的唯一 ID。
gie-unique-id=1

# 处理模式,0 表示默认模式,1 表示次要模式,2 表示全帧模式。
process-mode=1

# 输入数据的类型,0 表示未知,1 表示图片,2 表示视频。
network-type=0

# 聚类模式,0 表示 DBScan,1 表示 MeanShift,2 表示不聚类。
cluster-mode=2

# 维持输入帧的宽高比。
maintain-aspect-ratio=1

# 解析边界框数据的函数名称。
parse-bbox-func-name=NvDsInferParseYolo

# 自定义解码库文件的路径。
custom-lib-path=../build/libyolo_decode.so

[class-attrs-all]
# NMS 阈值。
nms-iou-threshold=0.45

# 预聚类阈值。
pre-cluster-threshold=0.25

# 每个帧最多保留的物体数量。
topk=300

设置caps元素和比特率

  // 4.设置caps元素的视频格式
  caps_filter = gst_caps_from_string("video/x-raw(memory:NVMM), format=I420");
  g_object_set(G_OBJECT(caps), "caps", caps_filter, NULL);
  // 释放caps_filter
  gst_caps_unref(caps_filter);

  // 5.设置编码器的比特率
  g_object_set(G_OBJECT(encoder), "bitrate", bitrate, NULL);
  // 设置编码器的preset-level
  if (is_aarch64())
  {
    g_object_set(G_OBJECT(encoder), "preset-level", 1, NULL);
    g_object_set(G_OBJECT(encoder), "insert-sps-pps", 1, NULL);
  }

设置udpsink元素的参数
udpsink: 将视频数据发送到 UDP 网络流

bus: 处理媒体流消息的代码片段

 // 6.设置udpsink元素的参数
  g_object_set(G_OBJECT(sink), "host", "224.224.255.255", NULL);
  g_object_set(G_OBJECT(sink), "port", updsink_port_num, NULL);
  g_object_set(G_OBJECT(sink), "async", FALSE, NULL);
  g_object_set(G_OBJECT(sink), "sync", 1, NULL);

  // 添加消息处理器
  bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline));
  bus_watch_id = gst_bus_add_watch(bus, bus_call, loop);
  gst_object_unref(bus);

按顺序添加就可以了

// ==================== 将元素添加到管道中 ====================
  gst_bin_add_many(GST_BIN(pipeline),
                   source, streammux, pgie,
                   nvvidconv, nvosd, nvvidconv_postosd, caps, encoder, rtppay, sink, NULL);

4. 将source_bin 添加到streammux元素的sink pad

具体来说,代码中首先定义了 sinkpad 和 srcpad 两个 GstPad 类型的指针变量,它们用于保存获取到的 streammux 元素的 sink pad 和 source_bin 元素的 src pad。然后定义了两个 gchar 类型的数组变量 pad_name_sink 和 pad_name_src,分别>用于保存 streammux 元素的 sink pad 名称和 source_bin 元素的 src pad 名称。最后,调用 gst_element_get_request_pad 和 gst_element_get_static_pad 函数获取对应的 sink pad 和 src pad,并将它们连接起来。

需要注意的是,在调用 gst_element_get_request_pad 和 gst_element_get_static_pad 函数时,需要将 sink pad 或 src pad 的名称作为参数传入,用于标识要获取哪一个 pad。在这里,sink pad 的名称为 “sink_0”,src pad 的名称为 “src”。而
在定义 gchar 类型的数组变量时,需要提前指定数组的长度,以确保足够存储 pad 名称。在这里,数组长度为 16,是根据 pad 名称的最大长度来设定的。

// ==================== 将source_bin 添加到streammux元素的sink pad ====================
  GstPad *sinkpad, *srcpad;
  gchar pad_name_sink[16] = "sink_0";
  gchar pad_name_src[16] = "src";

  // 获取streammux元素的sink pad
  sinkpad = gst_element_get_request_pad(streammux, pad_name_sink);
  // 获取source_bin元素的src pad
  srcpad = gst_element_get_static_pad(source, pad_name_src);

  if (gst_pad_link(srcpad, sinkpad) != GST_PAD_LINK_OK)
  {
    g_printerr("Failed to link decoder to stream muxer. Exiting.\n");
    return -1;
  }
  // 释放sinkpad和srcpad
  gst_object_unref(sinkpad);
  gst_object_unref(srcpad);

5. 将元素链接起来

这里调用了探针函数, 探针被设置在nvdsosd上的sink端口, 探针函数可以用于在APP中对推理结果进行处理和展示。在推理完>成后,DeepStream会将视频帧的元数据传递给APP,并通过探针函数的调用机制来处理元数据。因此,APP可以在探针函数中访>问视频帧中的对象元数据、框信息等,完成自己需要的逻辑处理。

在GStreamer中,探针(Probe)是一种机制,可以用来监控流水线(Pipeline)中的数据流并进行处理。它可以用于在特定位置执行自定义处理逻辑,例如提取、修改或丢弃数据包。探针在调试和性能分析方面很有用,可以帮助检测和解决各种问题。

  //  ==================== 将元素链接起来 ====================
  if (!gst_element_link_many(streammux, pgie,
                             nvvidconv, nvosd, nvvidconv_postosd, caps, encoder, rtppay, sink, NULL))
  {
    g_printerr("Elements could not be linked. Exiting.\n");
    return -1;
  }

  // 添加探针,用于获取元数据
  osd_sink_pad = gst_element_get_static_pad(nvosd, "sink"); // 获取nvosd元素的sink pad
  if (!osd_sink_pad)
    g_print("Unable to get sink pad\n");
  else
    // 参数:pad, 探针类型, 探针回调函数, 回调函数的参数, 回调函数的参数释放函数
    gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER, osd_sink_pad_buffer_probe, NULL, NULL); // 添加>探针
  g_timeout_add(5000, perf_print_callback, &g_perf_data);                                              // 添加>定时器,用于打印性能数据
  gst_object_unref(osd_sink_pad);

6. 解析探针会掉函数 osd_sink_pad_buffer_probe

在这里插入图片描述

传入三个指针,分别指向爬到pad, info, 用户数据。

buf 是视频帧的数据缓存,info->data 是一个 void 指针,需要转换成 GstBuffer 类型。

num_rects 是当前视频帧中的物体数量。

obj_meta 是一个指向 NvDsObjectMeta 结构体的指针,该结构体包含了检测物体的信息,例如类别、位置、置信度等。

vehicle_count 和 person_count 分别是车辆和人的数量,用于统计多边形区域内的车辆和人数。

l_frame 和 l_obj 分别是 NvDsMetaList 结构体的指针,表示帧元数据列表和物体元数据列表。

display_meta 是一个指向 NvDsDisplayMeta 结构体的指针,该结构体包含了在视频帧上绘制多边形区域、文本等的信息。

用gst_buffer_get_nvds_batch_meta拿到一批元数据

static GstPadProbeReturn osd_sink_pad_buffer_probe(GstPad *pad, GstPadProbeInfo *info,
                                                   gpointer u_data)
{
    GstBuffer *buf = (GstBuffer *)info->data;
    guint num_rects = 0;
    NvDsObjectMeta *obj_meta = NULL;
    guint vehicle_count = 0;
    guint person_count = 0;
    NvDsMetaList *l_frame = NULL;
    NvDsMetaList *l_obj = NULL;
    NvDsDisplayMeta *display_meta = NULL;

    NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf); // 获取批处理元数据

BatchMeta->FrameMeta

这段代码的作用是获取当前 buffer 中的批处理元数据 batch_meta,然后遍历 batch_meta 中的每一帧的元数据 frame_meta。

NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta(buf); // 获取批处理元数据
    // 遍历批处理元数据,得到每一帧的元数据
    for (l_frame = batch_meta->frame_meta_list; l_frame != NULL;
         l_frame = l_frame->next)
    {
        // 获取每一帧的元数据
        NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)(l_frame->data);

恢复多边形框, 跟之前的应用开发一样

if (g_ploygon.size() == 0)
        {
            
            guint width = frame_meta->pipeline_width;
            guint height = frame_meta->pipeline_height;
            // 从配置文件恢复多边形区域
            readPoints("./config/polygon_1.txt", g_ploygon, width, height);
            g_print("read polygon.txt success!, frame height = %d, width = %d \r \n",  height, width);
        }

FrameData->(NvDsObjectMeta *)(l_obj->data), 判断
这段代码从每一帧的元数据中遍历每一个检测到的物体的元数据,然后根据物体的类别(person/vehicle)将物体的 id 添加到不同的 id 列表(g_person_ids/g_vehicle_ids)中,并统计人数和车辆数。在该函数中,g_vehicle_ids 和 g_person_ids 是全局变量,用于保存所有帧中检测到的所有车辆和人的 id,vehicle_count 和 person_count 分别记录当前帧中检测到的车辆和人的数量。

// 遍历每一帧的元数据,得到每一个检测到的物体的元数据
        for (l_obj = frame_meta->obj_meta_list; l_obj != NULL;
             l_obj = l_obj->next)
        {
            // 获取每一个检测到的物体的元数据
            obj_meta = (NvDsObjectMeta *)(l_obj->data);
            // 如果是车辆
            if (obj_meta->class_id == PGIE_CLASS_ID_VEHICLE)
            {
                // 如果object id不在g_vehicle_ids中,添加进去
                if (std::find(g_vehicle_ids.begin(), g_vehicle_ids.end(), obj_meta->object_id) == g_vehicle_ids.end())
                {
                    g_vehicle_ids.push_back(obj_meta->object_id);
                }
                vehicle_count++;
                num_rects++;
            }
            
            // 如果是人
            if (obj_meta->class_id == PGIE_CLASS_ID_PERSON)
            {
                // 如果object id不在g_person_ids中,添加进去
                if (std::find(g_person_ids.begin(), g_person_ids.end(), obj_meta->object_id) == g_person_ids.end())
                {
                    g_person_ids.push_back(obj_meta->object_id);
                }
                person_count++;
                num_rects++;
            }

拿到中心点, 对在方框内的目标进行颜色跟背景图的变更,这里结束对当前Frame的obj的遍历,

// 获取检测框的中心点
            Point p = {
                obj_meta->rect_params.left + obj_meta->rect_params.width / 2,
                obj_meta->rect_params.top + obj_meta->rect_params.height / 2};
            //  如果中心点在多边形内
            if (isInside(g_ploygon, p))
            {
                // 更改检测框的颜色为红色
                obj_meta->rect_params.border_color.red = 1.0;
                obj_meta->rect_params.border_color.green = 0.0;
                obj_meta->rect_params.border_color.blue = 0.0;
                // 设置检测框的背景颜色为红色, 透明度为0.2
                obj_meta->rect_params.has_bg_color = 1;
                obj_meta->rect_params.bg_color.red = 1.0;
                obj_meta->rect_params.bg_color.green = 0.0;
                obj_meta->rect_params.bg_color.blue = 0.0;
                obj_meta->rect_params.bg_color.alpha = 0.2;
            }
            else
            {
                // 更改检测框的颜色为绿色
                obj_meta->rect_params.border_color.red = 0.0;
                obj_meta->rect_params.border_color.green = 1.0;
                obj_meta->rect_params.border_color.blue = 0.0;
            }
        }

绘制多边形, 统计有多少条边, 然后再搞到两个点的坐标, 然后再设置颜色, 最后,将绘制多边形的结果保存在 display_meta 中。

// 绘制多边形
        guint line_num = g_ploygon.size();
        display_meta->num_lines = line_num;

        for (guint i = 0; i < line_num; i++)
        {
            NvOSD_LineParams *line_params = &display_meta->line_params[i];
            line_params->x1 = g_ploygon[i % line_num].x;
            line_params->y1 = g_ploygon[i].y;
            line_params->x2 = g_ploygon[(i + 1) % line_num].x;
            line_params->y2 = g_ploygon[(i + 1) % line_num].y;

            line_params->line_width = 2;
            line_params->line_color.red = 1.0;
            line_params->line_color.green = 0.0;
            line_params->line_color.blue = 1.0;
            line_params->line_color.alpha = 1.0;
        }
// ==================== 创建rtsp服务器, 用于将视频流发布到网络 ====================
    GstRTSPServer *server;
    GstRTSPMountPoints *mounts;
    GstRTSPMediaFactory *factory;

    server = gst_rtsp_server_new();
    g_object_set(G_OBJECT(server), "service", g_strdup_printf("%d", rtsp_port_num), NULL);
    gst_rtsp_server_attach(server, NULL);
    mounts = gst_rtsp_server_get_mount_points(server);

    factory = gst_rtsp_media_factory_new();
    gst_rtsp_media_factory_set_launch(factory, g_strdup_printf("( udpsrc name=pay0 port=%d buffer-size=524288 caps=\"application/x-rtp, media=video, clock-rate=90000, encoding-name=(string)%s, payload=96 \" )", updsink_port_num, codec));
    gst_rtsp_media_factory_set_shared(factory, TRUE);
    gst_rtsp_mount_points_add_factory(mounts, rtsp_path, factory);

    g_object_unref(mounts);

    printf("\n *** DeepStream: Launched RTSP Streaming at rtsp://localhost:%d%s ***\n\n", rtsp_port_num, rtsp_path);

    // ==================== 启动管道 ====================
    /* Set the pipeline to "playing" state */
    g_print("Using file: %s\n", argv[1]);
    gst_element_set_state(pipeline, GST_STATE_PLAYING);

    /* Iterate */
    g_print("Running...\n");
    g_main_loop_run(loop);

    /* Out of the main loop, clean up nicely */
    g_print("Returned, stopping playback\n");
    gst_element_set_state(pipeline, GST_STATE_NULL); // 设置管道状态为NULL
    g_print("Deleting pipeline\n");
    gst_object_unref(GST_OBJECT(pipeline));
    g_source_remove(bus_watch_id);
    g_main_loop_unref(loop);
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/444827.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

仅需三步,快速打造指标数据应用

Kyligence Zen 一站式指标平台&#xff0c;致力于通过低代码的使用体验&#xff0c;帮助企业简洁高效地开发指标数据应用&#xff0c;将数据价值转化为业务洞察。 下面我们以零售交易和绩效管理场景为例&#xff0c;一起来看下如何通过简单三步&#xff0c;快速打造指标数据应用…

初始Sentinel

目录 雪崩问题及解决方案 服务保护技术对比 Sentinel介绍和安装 微服务整合Sentinel 雪崩问题及解决方案 微服务调用链路中的某个服务故障&#xff0c;引起整个链路中的所有微服务都不可用&#xff0c;这就是雪崩。 解决雪崩问题的常见方式有四种&#xff1a; 超时处理&…

ubuntu(20.04)-shell脚本(3)-sed-mysqldump

1.sed cmd&#xff1a; sed 选项 ‘指令’ 文件 sed指令保存到文件中&#xff1a;sed 选项 -f 包含sed指令的文件 文件 sed的常用选项&#xff1a; -r&#xff1a;使用扩展正则表达式 -e&#xff1a;它告诉sed将下一个参数解释为一个sed指令&#xff0c;只有当命令行…

day14 信号机制(下)

目录 信号集、信号的阻塞 信号集、信号的阻塞 有时候不希望在接收到信号时就立即停止当前执行&#xff0c;去处理信号&#xff0c;同时也不希望忽略该信号&#xff0c;而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。 信号的阻塞概念&#xff1a; 信号…

234:vue+openlayers 加载本地shp数据,在map上显示图形

第234个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+openlayers中利用shapefile读取本地的shp数据,并在地图上显示图形。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果 文章目录 示例效果安装引用配置方式示例源代码(共143行)相关API参考:专栏…

自然语言处理 —— 03 统计语言模型

一、背景 统计语言模型的理论基础是信源-信道模型,这两个模型都基于马尔可夫假设,即当前的事件只依赖于前面的一些事件。在统计语言模型中,当前的单词只依赖于前面的一些单词,而在信源-信道模型中,接收端只能看到传输过程中的有限的信息,而不知道整个传输过程。信源-信…

ASEMI代理ADI亚德诺AD8638ARJZ-REEL7车规级芯片

编辑-Z AD8638ARJZ-REEL7芯片参数&#xff1a; 型号&#xff1a;AD8638ARJZ-REEL7 偏移电压&#xff1a;3μV 输入偏置电流&#xff1a;1.5 pA 输入失调电流&#xff1a;7 pA 输入电压范围&#xff1a;−0.1~ 3V 共模抑制比&#xff1a;133 dB 输入电阻&#xff1a;22.…

Nuxt3中使用swiper

参考&#xff1a;nuxt3&#xff1a;swiper实现轮播效果_nuxt 使用swiper_snowli的博客-CSDN博客再引入swiper时&#xff0c;尝试了npm 包&#xff1a; swiper、vue-awesome-swiper等&#xff0c;尝试在nuxt3里增加plugin的方式引入&#xff0c;都没有成功&#xff0c;个人感觉应…

材料科学基础名词解释|第二章 晶体缺陷

第二章 晶体缺陷 1、空位形成能&#xff1a;在某一空位周围的原子&#xff0c;它们在靠近空位一侧失去了正常的原子作用力&#xff0c;平衡位置向空位存在的地方移动&#xff0c;引起空位周围晶格畸变&#xff0c;系统能量增高&#xff0c;这部分增高的能量叫做空位形成能。 …

【CSDN周赛】第46期题解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 本文章收录于专栏 【CSDN周赛】 本篇文章目录 &#x1f30f;一、吃吃吃&#x1f338;题目描述&#x1f338;题解 &#x1f30f;二、n …

论文的总体结构及质量控制

要写出一篇高质量AI领域的论文&#xff0c;首先要搞清楚论文由哪几部分组成&#xff0c;即论文的总体结构。同时&#xff0c;还要了解AI论文的质量评价与质量控制的指标。这样做的目的是为了弄明白AI论文的结构以及什么样的AI论文才是好的论文。 通常一篇AI论文的总体结构主…

React 列表 Keys

列表 & Keys 列表 React 列表可以使用 JavaScript 的 map() 方法来创建。如下&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"UTF-8" /> <title>React demo</title> <script src"https://cdn.static…

5.2、Unix/Linux上的五种IO模型

5.2、Unix/Linux上的五种IO模型 1.阻塞blocking2.非阻塞non-blocking&#xff08;NIO&#xff09;3.IO复用&#xff08;IO_multiplexing&#xff09;4.信号驱动&#xff08;signal-driven&#xff09;5.异步&#xff08;asynchronous&#xff09;①异步函数介绍 1.阻塞blocking…

大文件传输的3个重要替代方案

企业文件同步传输是一个广泛的类别。如何与地理位置相距遥远的合作伙伴进行同步、共享和协作呢&#xff1f;在本文中&#xff0c;我们将讨论可用于企业大文件同步传输的3种解决方案。 IBM Aspera Caption Aspera是一种高度可扩展、用户友好的解决方案&#xff0c;用于传输和同…

AI-TestOps —— 软件测试工程师的一把利剑

写在前面软件测试的前世今生测试工具开始盛行AI-TestOps 云平台● AI-TestOps 功能模块● AI-TestOps 自动化测试流程 写在前面 最近偶然间看到一句话&#xff1a;“软件测试是整个 IT 行业中最差的岗位”。这顿时激起了我对软件测试领域的兴趣&#xff0c;虽然之前未涉及过软…

堆排序及常见面试题

⭐️前言⭐️ 本篇文章记录堆排序以及对应的一些练习。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题可以在评论区留言 &#x1f349;博客中涉及源码及博…

硬“核”数字员工:中国核能行业协会携手实在智能,成功举办核能行业数字化实战培训

导语&#xff1a; 近期&#xff0c;中国核能行业协会携手实在智能&#xff0c;成功举办核能行业数字化实战培训&#xff0c;通过理论学习和参与实践&#xff0c;学员们亲手打造出一个个硬“核”数字员工。 十多张表格快速切换&#xff0c;无数串数字在面前跳动&#xff0c;上岗…

数据中台建设:千万级的瀑布式,和十万级的迭代式,你会选择哪一个?

中台十年&#xff0c;再看已成桑田。 最初&#xff0c;为了解决互联网行业快速发展催生出的海量数据累积和碎片化问题&#xff0c;企业开始尝试将数据整合到一个中央平台&#xff0c;以提高数据的使用效率和管理水平&#xff0c;中台建设雏形初现。巨头领跑之下&#xff0c;从…

刷题笔记【7】| 快速刷完67道剑指offer(Java版)

本文已收录于专栏 &#x1f33b; 《刷题笔记》 文章目录 前言&#x1f3a8; 1、二叉树中和为某一值的路径题目描述思路&#xff08;深度优先搜索&#xff09; &#x1f3a8; 2、复杂链表的复制题目描述思路 &#x1f3a8; 3、二叉搜索树与双向链表题目描述思路 &#x1f3a8; 4…

【蓝桥杯省赛真题20】python二次加密 青少年组蓝桥杯比赛python编程省赛真题解析

目录 python二次加密 一、题目要求 1、编程实现 2、输入输出 二、解题思路