点击查看系列文章目录
从 DS4.0 开始就调试了 deepstream 动态增减源的功能,好长时间没接触,又生疏了。好记性不如烂笔头,趁着今天回顾代码,把过程记录一下
NVIDIA 官方其实已经发布过 runtime_source_add_delete 模块,见 https://github.com/NVIDIA-AI-IOT/deepstream_reference_apps
我们先把他跑起来,然后我再介绍一下这代码的思路,以及我调试过程中遇到的一些问题
在 /opt/nvidia/deepstream/deepstream-5.0/source/app/samples 路径下克隆工程
git clone https://github.com/NVIDIA-AI-IOT/deepstream_reference_apps.git
cd runtime_source_add_delete
make
make 完生成可执行文件,可以使用以下下边的方式测试
$ ./deepstream-test-rt-src-add-del file:///opt/nvidia/deepstream/deepstream-5.0/samples/streams/sample_1080p_h264.mp4
运行起来后的效果是:每隔 10s 新增一路输入,通过 tiler 模式显示,最多增加到 4 路,然后开始每隔 10s 删除一路,直到删完后,结束整个进程。
在 deepstream_test_rt_src_add_del.c 中,首先会创建一条 pipeline,顺序如下
uridecodebin -> streammux -> pgie -> tracker -> sgie1 -> sgie2 -> sgie3 -> tiler -> nvvideoconvert -> nvosd -> sink
然后增加一个定时函数,
g_timeout_add_seconds (10, add_sources, (gpointer) g_source_bin_list);
表示每隔 10s 执行一遍 add_sources 函数,进入该函数
static gboolean
add_sources (gpointer data)
{
gint source_id = g_num_sources;
GstElement *source_bin;
GstStateChangeReturn state_return;
// 生成随机 source_id
do {
source_id = rand () % MAX_NUM_SOURCES;
} while (g_source_enabled[source_id]);
g_source_enabled[source_id] = TRUE;
// 创建 uridecodebin
// uridecodebin 是一种能自动识别 url 类别并对应解码的一种source bin
g_print ("Calling Start %d \n", source_id);
source_bin = create_uridecode_bin (source_id, uri);
if (!source_bin) {
g_printerr ("Failed to create source bin. Exiting.\n");
return -1;
}
// 更新全局变量,保存 source 列表
g_source_bin_list[source_id] = source_bin;
// 将生成的 source_bin 加到 pipeline 中
gst_bin_add (GST_BIN (pipeline), source_bin);
// 设置新加的 source_bin 状态为 playing,并得到状态返回值
state_return =
gst_element_set_state (g_source_bin_list[source_id], GST_STATE_PLAYING);
switch (state_return) {
case GST_STATE_CHANGE_SUCCESS:
g_print ("STATE CHANGE SUCCESS\n\n");
source_id++;
break;
case GST_STATE_CHANGE_FAILURE:
g_print ("STATE CHANGE FAILURE\n\n");
break;
case GST_STATE_CHANGE_ASYNC:
g_print ("STATE CHANGE ASYNC\n\n");
state_return =
gst_element_get_state (g_source_bin_list[source_id], NULL, NULL,
GST_CLOCK_TIME_NONE);
source_id++;
break;
case GST_STATE_CHANGE_NO_PREROLL:
g_print ("STATE CHANGE NO PREROLL\n\n");
break;
default:
break;
}
// 计数
g_num_sources++;
if (g_num_sources == MAX_NUM_SOURCES) {
// 当达到设置的最大输入数量时,开始删除 source
g_timeout_add_seconds (10, delete_sources, (gpointer) g_source_bin_list);
// 返回 FALSE,会终止该定时任务
return FALSE;
}
// 返回 TRUE,会继续下一次定时任务
return TRUE;
}
如代码中说明的,当达到设置的最大输入数量时,结束新增,开始删除,也是通过 g_timeout_add_seconds 定时任务,进入 delete_source 函数来完成
delete_source 函数主要是对列表的一些判断,真正执行删除的是 stop_release_source 函数
static void
stop_release_source (gint source_id)
{
GstStateChangeReturn state_return;
gchar pad_name[16];
GstPad *sinkpad = NULL;
// 设置source bin 为 NULL 状态
state_return =
gst_element_set_state (g_source_bin_list[source_id], GST_STATE_NULL);
switch (state_return) {
case GST_STATE_CHANGE_SUCCESS:
g_print ("STATE CHANGE SUCCESS\n\n");
// 得到 streammux 的 sink pad
g_snprintf (pad_name, 15, "sink_%u", source_id);
sinkpad = gst_element_get_static_pad (streammux, pad_name);
// 给 streammux 的 sink pad 发送 flush stop 信号,停止视频流传输
gst_pad_send_event (sinkpad, gst_event_new_flush_stop (FALSE));
gst_element_release_request_pad (streammux, sinkpad);
g_print ("STATE CHANGE SUCCESS %p\n\n", sinkpad);
// 删除 bin 操作
gst_object_unref (sinkpad);
gst_bin_remove (GST_BIN (pipeline), g_source_bin_list[source_id]);
source_id--;
g_num_sources--;
break;
case GST_STATE_CHANGE_FAILURE:
g_print ("STATE CHANGE FAILURE\n\n");
break;
case GST_STATE_CHANGE_ASYNC:
g_print ("STATE CHANGE ASYNC\n\n");
state_return =
gst_element_get_state (g_source_bin_list[source_id], NULL, NULL,
GST_CLOCK_TIME_NONE);
g_snprintf (pad_name, 15, "sink_%u", source_id);
sinkpad = gst_element_get_static_pad (streammux, pad_name);
gst_pad_send_event (sinkpad, gst_event_new_flush_stop (FALSE));
gst_element_release_request_pad (streammux, sinkpad);
g_print ("STATE CHANGE ASYNC %p\n\n", sinkpad);
gst_object_unref (sinkpad);
gst_bin_remove (GST_BIN (pipeline), g_source_bin_list[source_id]);
source_id--;
g_num_sources--;
break;
case GST_STATE_CHANGE_NO_PREROLL:
g_print ("STATE CHANGE NO PREROLL\n\n");
break;
default:
break;
}
}
原理其实很简单,关于动态替换 element 的过程,我在 gstreamer 专栏中有过介绍,可以参考《Gstreamer应用开发手册14:替换管道元件》
经过上述的步骤,可以初步实现一个对视频文件源的动态增减,但是我在输入 rtsp 执行删除操作的时候会报错如下
Calling Stop 3
STATE CHANGE SUCCESS
STATE CHANGE SUCCESS 0x7e640052c0
ERROR from element source: Unhandled error
Error details: gstrtspsrc.c(6161): gst_rtspsrc_send (): /GstPipeline:dstest-pipeline/GstURIDecodeBin:source-bin-03/GstRTSPSrc:source:
Option not supported (551)
Returned, stopping playback
Deleting pipeline
这个问题我曾经在论坛中提问过,可惜没得到好的解决方案
https://forums.developer.nvidia.com/t/error-happend-when-run-runtime-source-add-delete/115285
在论坛中查了一下类似的问题,大概原因是说 rtsp 输入不支持暂停等操作,如下
https://forums.developer.nvidia.com/t/delete-source-dynamically-error/146830/7
这里提供一个“暴力”的方法,就是注释掉 bus_call 里边的 g_main_loop_quit (loop) 函数,也能运行起来,但依然会输出相关的错误信息。
另外一种方法是修改 rtsp source bin 的源码,因为涉及的内容比较细且通用,我会在另外一边博文中介绍,参考《DeepStream5.0系列之修改rtsp source源码》。