当前位置: 代码迷 >> 综合 >> DeepStream Hello World:deepstream_test1_app 源代码深入剖析
  详细解决方案

DeepStream Hello World:deepstream_test1_app 源代码深入剖析

热度:37   发布时间:2023-12-12 15:38:58.0

0. 概述

DeepStream Hello World:deepstream_test1_app 提供了 DeepStream 一个最简单的演示。我在 Jetson Nano 上安装的 DeepStream,这个项目的位置是:/opt/nvidia/deepstream/deepstream-5.1/sources/apps/sample_apps/deepstream-test1

关于这程序的结构,官网有一幅图说得很明白:

在这里插入图片描述

简单地说,deepstream-test1:从文件中获取单一视频流,并在屏幕上显示出来。

1. 主程序结构

int main (int argc, char *argv[]) {
    ... ...g_object_set (G_OBJECT (source), "location", argv[1], NULL);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 = gst_element_factory_make ("nvinfer", "primary-nvinference-engine");g_object_set (G_OBJECT (pgie), "config-file-path", "dstest1_pgie_config.txt", NULL);loop = g_main_loop_new (NULL, FALSE);bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));bus_watch_id = gst_bus_add_watch (bus, bus_call, loop);gst_bin_add_many (GST_BIN (pipeline),source, h264parser, decoder, streammux, pgie,nvvidconv, nvosd, transform, sink, NULL);GstPad *sinkpad, *srcpad;gchar pad_name_sink[16] = "sink_0";gchar pad_name_src[16] = "src";sinkpad = gst_element_get_request_pad (streammux, pad_name_sink);srcpad = gst_element_get_static_pad (decoder, pad_name_src);gst_pad_link (srcpad, sinkpad);gst_element_link_many (source, h264parser, decoder, NULL);gst_element_link_many (streammux, pgie, nvvidconv, nvosd, transform, sink, NULL);osd_sink_pad = gst_element_get_static_pad (nvosd, "sink");gst_pad_add_probe (osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER, osd_sink_pad_buffer_probe, NULL, NULL);gst_element_set_state (pipeline, GST_STATE_PLAYING);g_print ("Running...\n");g_main_loop_run (loop);... ...return 0;
}

我上面的程序翻译成 OOP 伪代码,容易理解很多

	source->location = argv[1]; // 视频文件名streammux->batch-size = 1;streammux->width = MUXER_OUTPUT_WIDTH; 						//1920streammux->height = MUXER_OUTPUT_HEIGHT; 					//1080streammux->batched-push-timeout = MUXER_BATCH_TIMEOUT_USEC; //4000pgie = nvinfer.Create("primary-nvinference-engine");pgie->config-file-path = "dstest1_pgie_config.txt"; // 配置文件(DeepStream 的推理模型配置文件定义)loop = new g_main_loop(); bus = pipeline->get_bus();bus_watch_id = bus->add_watch(bus_call, loop);		// bus_call 是回调函数pipeline->add_many(source, h264parser, decoder, streammux, pgie, nvvidconv, nvosd, transform, sink, NULL);sinkpad = streammux->get_request_pad("sink_0");srcpad = decoder->get_static_pad("src");srcpad->pad_link(sinkpad);source->link_many(h264parser, decoder, NULL);streammux->link_many(pgie, nvvidconv, nvosd, transform, sink, NULL);osd_sink_pad = nvosd->get_static_pad("sink");osd_sink_pad->add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER, osd_sink_pad_buffer_probe, NULL, NULL);pipeline->state = GST_STATE_PLAYING;loop->run();

2. loop 和 pipeline

这个里面有两个关键的对象:looppipeline

  • loop:是类 g_main_loop 的实例。注意前缀是 g_ 而不是 gst_。这说明主消息循环类 g_main_loop 是属于 glib 这个框架的,这个内容我还不太了解,先放一放,以后有时间再仔细研究。
  • pipeline:是媒体流管道。这个在 GStream 的范例中已经多次看到了,它是把一系列元素 Element 连接起来得到的。

loop 的作用是通过一个循环,接受其他线程发来的消息,按照优先级排序后,再统一分派消息。我推测,bus_watch_id = bus->add_watch(bus_call, loop) 这行代码,达成了如下的目标:

  1. bus 可能包含多个线程,它在运行过程中产生的所有消息都会发送给 loop
  2. loop 会把自己从 bus 得到的消息,通过调用 bus_call 得到响应。

有人可能觉得这样做等于脱裤子放屁,直接让 bus 调用 bus_call 岂不更直接。其实当年我学习 Windows 的消息循环时也这样想过。后来读的书多了,才知道这样做有两个好处:

  • 一个从数学的角度看,消息循环把来自多线程的任务在一个队列中排队,再按照一定优先顺序统一分发消息,可以保证代码正确能够用数学方法证明。开发高质量的软件首先要保证程序的正确性,而程序的正确性归根结底是以来数学方法证明的。
    此理论来源于”形式化软件工程“,有兴趣的朋友可以参考《零缺陷程序设计》、《净室软件工程技术》、《从规范出发的程序设计》、《B方法》等经典书籍。
  • 第二个好处是,call_back 都是在 loop 线程里执行的,可以避免很多线程之间限制与冲突。例如 WIndows 的绘图命令只能在 loop 线程里执行,如果没有消息循环机制,有可能在任意一个线程执行 GUI 函数,系统稳定性是无法保证的。

3. 基于 Cuda 加速的插件

pipeline 的构成方法,GStreamer 的示例代码已经介绍过很多,本文不再赘述。下面我们看一看 DeepStream 提供的相关元素。

初学 DeepStream 的朋友可能会困惑, GStreamer 的 gst_element_factory_make 函数是如何知道 DeepStream 中设计的 Element 插件的? 原因是,GStreamer 有一套插件注册机制,只要你注册了新的插件(.so 文件),就可以用 gst_element_factory_make 创建实例。

正是由于有这个机制,我们可以利用插件的字符串名称拼接处媒体流的管道。有两个特别有用的命令:gst-inspect-1.0gst-launch-1.0,分别用于查看已注册的插件和执行一个用字符串定义的管道。

本实例中用到 NVidia 提供的基于 Cuda 设计的元素 Element 插件如下(filesrc 属于 GStreamer,这里就不介绍了),这些插件充分利用了 GPU 的强大的硬件计算能力:

插件 用途
h264parser 解析器负责把 h.264 数据从文件解析出来。(注意这是数据解析,不是解码,不需要 GPU 支持)
nvv4l2decoder 使用 nvdec_h264 的硬件加速器解码 h.264 视频流。
nvstreammux 从一个或多个数据流中,汇合构成批处理流。
nvinfer 基于前面的解码数据,使用 nvinfer 运行推理运算。推理算法的定义由配置文件确定。
nvvideoconvert 根据 nvosd 的要求,使用 convertor 将 NV12 转换为 RGBA。
nvdsosd 创建 OSD 以便基于 RGBA 格式的图像缓存显示图像。
nvegltransform 在 Jetson 平台上,Gst-nveglglessink 在 EGLImage 结构上工作。将传入数据(包装在 NVMM 结构中)转换为 EGLImage 实例需要 Gst-nvegltranform。在 dGPU 平台上,Gst-nveglessink 直接处理包装在 NVMM 结构中的数据。
nveglglessink 最终渲染 OSD 输出。

更多资料参考:DeepStream 官方插件手册

  相关解决方案