DeepStream5.0系列之动态增减输入源

点击查看系列文章目录

0 背景

从 DS4.0 开始就调试了 deepstream 动态增减源的功能,好长时间没接触,又生疏了。好记性不如烂笔头,趁着今天回顾代码,把过程记录一下

1 先跑起来

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 删除一路,直到删完后,结束整个进程。

2 实现思路

在 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:替换管道元件》

3 改进

经过上述的步骤,可以初步实现一个对视频文件源的动态增减,但是我在输入 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源码》。

 

你可能感兴趣的:(deepstream)