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
这个里面有两个关键的对象:loop
和 pipeline
。
loop
:是类g_main_loop
的实例。注意前缀是g_
而不是gst_
。这说明主消息循环类g_main_loop
是属于 glib 这个框架的,这个内容我还不太了解,先放一放,以后有时间再仔细研究。pipeline
:是媒体流管道。这个在 GStream 的范例中已经多次看到了,它是把一系列元素 Element 连接起来得到的。
loop 的作用是通过一个循环,接受其他线程发来的消息,按照优先级排序后,再统一分派消息。我推测,bus_watch_id = bus->add_watch(bus_call, loop)
这行代码,达成了如下的目标:
bus
可能包含多个线程,它在运行过程中产生的所有消息都会发送给loop
。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.0
和 gst-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 官方插件手册