main.cpp 文件解析
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(¤t_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;
}