deepstream-appsrc-test应用介绍

0. 项目背景

在计算机视觉任务中,对于video输入数据,一般需要通过解析器和解码器对video数据解析,然后输入nvinfer来进行推理。这样是因为video的原始数据通过编码和解码两个操作能极大的减少video文件的大小而不丢失数据信息。原始图像文件【raw data】包含从数码相机扫描器电影胶片扫描仪的图像传感器所处理数据,指的是未经过处理,也未经压缩的文件。

  在该sample中,讲述了如何使用appsrc element将raw video 数据作为输入,在deepstream上进行推理,使用appsink element来访问metadata。

Note:该sample只支持格式为RGBA,NV12和I420的raw video数据作为输入。

sample源码地址:/opt/nvidia/deepstream/deepstream-6.0/sources/apps/sample_apps/deepstream-appsrc-test

1. appsrc

应用程序可以使用appsrc element来向pipeline中输入数据。不同于其他常见的Gstreamer element,appsrc element提供了额外的API 函数接口。

在Gstreamer的应用开发手册中,Manually adding or removing data from/to a pipeline向我们展示了如何使用appsrc和appsink。下面我们对这个例子进行分析。

在Gstreamer的应用开发中,可以使用appsrc将数据注入到pipeline中,appsink来获取pipeline的输出,并在应用程序中对输出进行处理。

下面讨论如何使用它们向pipeline中插入(使用appsrc)或从pipeline中获取(使用appsink)数据,以及如何设置negotiations。

1.1 Inserting data with appsrc

首先,学习如何使用appsrc向pipeline中插入数据。appsrc通过一些配置选项来控制其运行方式。

  • push or pull mode。stream-type 属性可以用来控制这个。一个 random-access stream-type将激活appsrc的pull mode,而其他的stream-type则激活push mode.

  • appsrc插入buffer的类型,需要通过配置caps属性来设置。caps的设置需要是固定的,需要通过caps来向下游element通知buffer的类型。

  • appsrc是直播模式(live mode)还是其他。这个通过is-live属性来配置。当处于live mode下,同样需要设置min-latency和max-latency两个属性。

  • SEGMENT event的format。这个format对如何计算buffer的running-time有影响。在live mode下,最好是设置为GST_FORMAT_TIME,而对于non-live数据,需要根据所处理数据的格式来判断。如果打算为buffer添加时间戳,则使用GST_FORMAT_TIME格式。而如果不这么做,最好设置为GST_FORMAT_BYTES。

  • 如果appsrc处于random-access mode(随机访问模式)下,那么使用stream中的bytes number来配置size属性非常重要。这将通知下游elment 媒体数据的大小,以及在必要时候找到stream的尾部。

向appsrc element输入数据的主要方式是使用gst_app_src_push_buffer函数或者发出push-buffer action 信号。此时,会将buffer放入到队列中,然后appsrc从队列的streaming thread中读取数据。

需要注意的是,数据传输不会在执行push-buffer任务的线程中发生。

max-bytes数据用来控制appsrc中queue数据的大小。

当queue被数据填满将释放enough-data信号,来通知应用程序应该停止向appsrc中推送数据。block属性将导致appsrc阻塞push-buffer方法,知道queue空闲下来。

当queue中的数据用完时,将释放need-data信号,来通知应用程序需要开始向appsrc中推送更多数据。

处理need-data和enough-data信号以外,appsrc可以释放seek-data信号,当stream-mode属性设置为seekable或random-access。这个signal参数将包含想要处理的(stream中)位置信息。当接收到seek-data信号时,应用程序应该从新的位置push buffers 。

当数据全部推送到appsrc中,需要调用gst_app_src_end_of_stream函数来发送EOS

1.1.1 Using appsrc in push mode

当appsrc在push模式(stream-type is stream 或者seekable),应用程序将重复调用push-buffer方法。一般情况下,queue的size可以通过enough-data和need-data信号控制从而关闭或停止push-buffer的调用。

min-percent属性表示appsrc内部queue只剩多少数据时,需要发送need-data信号。

当流类型设置为 GST_APP_STREAM_TYPE_SEEKABLE 时,不要忘记实现seek-data callback。

在实现各种网络协议或硬件设备(http、camera)时使用push模式。

1.1.2 Using appsrc in pull mode

在此模式下,数据通过need-data的处理函数来推送到appsrc中。需要准确推送need-data信号请求的字节数。只有在stream末尾时,才能推送比请求值少的数据量。

使用此模式访问文件或其他随机访问(file)的资源。

2. pipeline

该sample的整体pipeline结构如下图所示,appsrc 用于接收数据,nvvidconv1转换数据格式,capsfilter用来设置修改后的数据格式,来适配nvstreammux的pad caps,接下来的就是常见用于推理 elements,最后使用nveglglessink来可视化结果,appsink来获取推理后的数据进行分析。下面就一些主要的代码进行分析。

 1. 设置appsrc pad的caps属性,指定推送到appsrc的数据类型,并在element的连接阶段检查后续的element是否支持此数据类型。使用gst_caps_new_simple函数来创建一个新的GstCaps对象。

  /* Configure appsrc */
  g_object_set (data.app_source, "caps",
      gst_caps_new_simple ("video/x-raw",
          "format", G_TYPE_STRING, format,
          "width", G_TYPE_INT, width,
          "height", G_TYPE_INT, height,
          "framerate", GST_TYPE_FRACTION, data.fps, 1, NULL), NULL);
#if !CUSTOM_PTS
  g_object_set (G_OBJECT (data.app_source), "do-timestamp", TRUE, NULL);
#endif

 2. 监听“need-data"和”enough-data"信号,这两个信号在appsrc需要数据和缓冲区满时触发。通过这两个事件可以方便控制何时产生数据与停止推送。

  g_signal_connect (data.app_source, "need-data", G_CALLBACK (start_feed),
      &data);
  g_signal_connect (data.app_source, "enough-data", G_CALLBACK (stop_feed),
      &data);

 start_feed函数,在appsrc内部的数据队列即将缺少数据时调用,通过注册一个idle函数来向appsrc中填充数据。GLib的主循环在"idle状态下会循环调用idle函数,用于向appsrc中填充数据。

同时保存了g_idle_add函数的返回值,表示event source的ID值,通过这个ID值以便后续用于停止数据的写入。

/* This signal callback triggers when appsrc needs data. Here,
 * we add an idle handler to the mainloop to start pushing
 * data into the appsrc */
static void
start_feed (GstElement * source, guint size, AppSrcData * data) // need-data
{
  if (data->sourceid == 0) // source id
  {
    data->sourceid = g_idle_add ((GSourceFunc) read_data, data);
  }
}

当enough-data event释放时,stop_feed函数将被调用。通过g_source_remove函数将先前注册的idle函数从GLib的主循环中移除。

/* This callback triggers when appsrc has enough data and we can stop sending.
 * We remove the idle handler from the mainloop */
static void
stop_feed (GstElement * source, AppSrcData * data)
{
  if (data->sourceid != 0) {
    g_source_remove (data->sourceid);
    data->sourceid = 0;
  }
}

read_data函数的作用读取数据,然后将其填充到appsrc的数据队列中。

gst_buffer_new_allocate函数用来分配指定大小内存的buffer对象;gst_util_uint64_scale(val, num, denom)函数用于计算val * num / denom,此函数会在内部对数据范围进行检测,避免溢出问题。

向appsrc中填充数据有两种方式:

  • 通过释放“push-buffer" event,通知appsrc数据准备就绪,并释放申请的buffer
/* Push the buffer into the appsrc */
g_signal_emit_by_name (data->app_source, "push-buffer", buffer, &ret);
/* Free the buffer now that we are done with it */
gst_buffer_unref (buffer);
  • gst_app_src_push_buffer函数,该函数会接管GstBuffer的所有权,调用者无需释放buffer。

gstret = gst_app_src_push_buffer ((GstAppSrc *) data->app_source, buffer); 

 当所有数据都推送完毕之后,通过调用gst_app_src_end_of_stream函数向pipeline发送EOS event。

/* This method is called by the idle GSource in the mainloop, 
 * to feed one raw video frame into appsrc.
 * The idle handler is added to the mainloop when appsrc requests us
 * to start sending data (need-data signal)
 * and is removed when appsrc has enough data (enough-data signal).
 */
static gboolean
read_data (AppSrcData * data)
{
  GstBuffer *buffer;
  GstFlowReturn gstret;

  size_t ret = 0;
  GstMapInfo map;
  buffer = gst_buffer_new_allocate (NULL, data->frame_size, NULL);    // allocate memory

  gst_buffer_map (buffer, &map, GST_MAP_WRITE);     // buffer map
  ret = fread (map.data, 1, data->frame_size, data->file);    // read data
  map.size = ret;

  gst_buffer_unmap (buffer, &map);
  if (ret > 0) 
  {
#if CUSTOM_PTS
    GST_BUFFER_PTS (buffer) =gst_util_uint64_scale (data->appsrc_frame_num, GST_SECOND, data->fps);
#endif
    gstret = gst_app_src_push_buffer ((GstAppSrc *) data->app_source, buffer);  // push buffer
    if (gstret != GST_FLOW_OK) 
    {
      g_print ("gst_app_src_push_buffer returned %d \n", gstret);
      return FALSE;
    }
  } 
  else if (ret == 0) // eos
  {
    gstret = gst_app_src_end_of_stream ((GstAppSrc *) data->app_source);  // release eos signal
    if (gstret != GST_FLOW_OK) 
    {
      g_print("gst_app_src_end_of_stream returned %d. EoS not queued successfully.\n",gstret);
      return FALSE;
    }
  } 
  else 
  {
    g_print ("\n failed to read from file\n");
    return FALSE;
  }
  data->appsrc_frame_num++;

  return TRUE;
}

在deep stream中,streammux插件接收的数据类型应该是NVMM,因此实现需要nvvidconvert element转换数据格式,capsfilter element规定了转换数据格式的类型应该是什么样的。

  caps = gst_caps_new_simple ("video/x-raw", "format", G_TYPE_STRING,
      vidconv_format, NULL);
  feature = gst_caps_features_new ("memory:NVMM", NULL);
  gst_caps_set_features (caps, 0, feature);
  g_object_set (G_OBJECT (caps_filter), "caps", caps, NULL);

对于appsink element,通过监听"new-sample" event,用于appsink接收数据时的处理。 

  /* Configure appsink to extract data from DeepStream pipeline */
  g_object_set (appsink, "emit-signals", TRUE, "async", FALSE, NULL);
  /* Callback to access buffer and object info. */
  g_signal_connect (appsink, "new-sample", G_CALLBACK (new_sample), NULL);

当appsink element接收到数据时会调用new_sample函数, 通过gst_app_sink_pull_sample函数来提取sample。当得到GstSample之后,可以通过gst_sample_get_buffer函数得到所包含的GstBuffer。使用完毕之后同样需要通过gst_sample_unref函数来对sample进行释放。

/* new_sample is an appsink callback that will extract metadata received
 * tee sink pad and update params for drawing rectangle,
 *object information etc. */
static GstFlowReturn
new_sample (GstElement * sink, gpointer * data)
{
  GstSample *sample;
  GstBuffer *buf = NULL;
  guint num_rects = 0;
  NvDsObjectMeta *obj_meta = NULL;
  guint vehicle_count = 0;
  guint person_count = 0;
  NvDsMetaList *l_frame = NULL;
  NvDsMetaList *l_obj = NULL;
  unsigned long int pts = 0;

  sample = gst_app_sink_pull_sample (GST_APP_SINK (sink));
  if (gst_app_sink_is_eos (GST_APP_SINK (sink))) {
    g_print ("EOS received in Appsink********\n");
  }

  if (sample) {
    /* Obtain GstBuffer from sample and then extract metadata from it. */
    buf = gst_sample_get_buffer (sample);
    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);
      pts = frame_meta->buf_pts;
      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) {
          vehicle_count++;
          num_rects++;
        }
        if (obj_meta->class_id == PGIE_CLASS_ID_PERSON) {
          person_count++;
          num_rects++;
        }
      }
    }

    g_print ("Frame Number = %d Number of objects = %d "
        "Vehicle Count = %d Person Count = %d PTS = %d" GST_TIME_FORMAT "\n",
        frame_number, num_rects, vehicle_count, person_count,
        GST_TIME_ARGS (pts));
    frame_number++;
    gst_sample_unref (sample);
    return GST_FLOW_OK;
  }
  return GST_FLOW_ERROR;
}

3. 有用的链接

GStreamer基础教程09 - Appsrc及Appsink - John.Leng - 博客园 (cnblogs.com)

About streammux and appsrc - Intelligent Video Analytics / DeepStream SDK - NVIDIA Developer Forums

How to use appsrc to feed the deepstream pipeline - Intelligent Video Analytics / DeepStream SDK - NVIDIA Developer Forums

Benchmark real FPS with appsrc - Intelligent Video Analytics / DeepStream SDK - NVIDIA Developer Forums

gstreamer appsrc "gst_app_src_push_buffer" successfully but no video show in autovideosink - Jetson & Embedded Systems / Jetson TX2 - NVIDIA Developer Forums

c++ - Push OpenCV mat inside a DeepStream pipeline - Stack Overflow

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值