Gstreamer应用开发手册13:管道中添加删除数据

手动在管道中添加/删除数据

许多人希望使用自己的源将数据注入到管道中,也有人希望获取管道的输出并在应用程序中处理它。强烈建议不要使用这些方法,但GStreamer会为它们提供支持。由于没有基类的任何支持,因此你需要彻底了解状态更改和同步。如果它不起作用,则有百万种方法可以使自己陷入困境。最好只编写一个插件并让基类对其进行管理。有关此主题的更多信息,请参见《插件编写者指南》。

有两个元件可用于上述目的:appsrc 和 appsink。我们将讨论如何使用它们从管道中插入(使用appsrc)或获取(使用appsink)数据,以及如何设置协商。

appsrc 和 appsink 都提供2套API。一种API使用标准 GObject(动作)信号和属性。相同的API也适用于标准C API。C API的性能更高,但需要你链接到应用程序库才能使用这些元件。

使用 appsrc 插入数据

让我们看一下 appsrc 如何将应用程序数据插入管道。

appsrc 有一些配置选项可控制其操作方式。你应该注意以下事项:

  • 选择 appsrc 在 push 或 pull 模式下运行,可以使用 stream-type 属性控制它。将 stream-type 设置为 random-access(2) 会激活 pull 模式的,否则设置为 stream(0) 或 seekable(1) 会激活 push 模式。
  • appsrc 将推出的 buffer 的功能。需要使用 caps 属性进行配置,此属性必须设置为固定功能,并将用于协商下游格式。
  • appsrc 是否在实时模式下运行。这是使用 is-live 属性配置的。在实时模式下操作时,设置 min-latency 和 max-latency 属性也很重要。 min-latency 应该将其设置为捕获缓冲区到将其压入 appsrc 缓冲区之间所需的时间。在实时模式下,应在捕获缓冲区的第一个字节时使用管道 running-time 对缓冲区加时间标记,然后再将其提供给 appsrc。你可以让 appsrc 通过 do-timestamp 属性带有时间标记,但是 min-latency 必须将其设置为0,因为 appsrc 时间戳基于它获得给定缓冲区 running-time。
  • appsrc 将推送的SEGMENT事件的格式。这种格式对缓冲区的 running-time 计算方式有影响,因此你必须了解这一点。对于实时源,你可能需要将 format 属性设置为 GST_FORMAT_TIME。对于非实时源,这取决于你正在处理的媒体类型。如果你打算对缓冲区加时间戳,则应使用 GST_FORMAT_TIME 格式,如果不这样做,GST_FORMAT_BYTES 可能会合适。
  • 如果 appsrc 以随机访问模式运行,则使用流中的字节数配置 size 属性很重要。这将使下游元件知道媒体的大小,并在需要时搜索流的结尾。

操作数据到 appsrc 元件中的主要方式是调用 gst_app_src_push_buffer 方法,或者是发送 push-buffer 响应信号,该操作将缓存放进了一个队列,appsrc 将在它的流线程中读取该队列中的数据。值得注意的是数据传输过程不是在执行 push-buffer 操作的线程。

max-bytes 属性控制了在被 appsrc 认为队列满之前有多少数据可以被放进 appsrc 里的队列中。内部队列满时将发出“enough-data”信号,该信号通知应用程序应该停止向 appsrc 中推送数据了。block 属性将是 appsrc 阻塞 push-buffer 方法直到可以推进去数据。当内部队列没有可用的数据,“need-data” 信号将被发送,该信号将通知应用程序应该推送更多的数据到 appsrc 中。

在 “need-data” 和 “enough-data” 之外,当 stream-mode 属性设置为 seekable 或者 random-access 时,appsrc 能够发送 “seek-data” 信号。该信号的参数包含了新的希望在 stream 中设定的位置,且该参数以 format 属性为单位。在接收到 seek-data 信号后,应用程序应该从新的位置开始推送数据。这些信号(need-data,enought-data,和seek-data)允许应用程序以两种不同的方式操作 appsrc。

当应用程序完成推送数据到 appsrc,其应该调用 gst_app_src_end_of_stream 函数,或者发送 end-of-stream 响应信号。在调用该函数后,不应该再由缓存被推送到 appsrc,直到立即定位发生或者是 appsrc 切换到了 REDAY 状态。

这些信号允许应用程序 appsrc 在 push 和 pull 模式下运行,如下所述。

在 push 模式下使用 appsrc

当 appsrc 配置为 push 模式(stream-type 设为 stream 或 seekable)时,应用程序将使用新缓冲区重复调用 push-buffer 方法。可选地,通过停止/启动 push-buffer 回调,appsrc 可以使用 enough-data 和 need-data 信号来控制 push-buffer 中的队列大小。min-percent 属性值定义 appsrc 发出 need-data 信号之前内部队列需要有多空。你可以将此值设置为某个正值,以避免完全耗尽队列。

将 stream-type 设置为 GST_APP_STREAM_TYPE_SEEKABLE 时,请不要忘记实现 seek-data 回调。

实施各种网络协议或硬件设备时,请使用此模式。

在 pull 模式下使用 appsrc

在 pull 模式下,数据从 need-data 信号处理程序进入 appsrc。你应该准确地推送 need-data 信号中请求的字节数 。仅在流末尾时才允许压入更少的字节。

可以使用此模式访问文件或其他随机访问的源。

appsrc 示例

此示例应用程序将 appsrc 用作带格式上限的源,将黑白视频(每秒切换一次)生成到 Xv-window 输出。我们使用色彩空间转换元件来确保将正确的格式提供给 X-server。我们以可变帧率(0/1)配置视频流,并以每秒播放2帧的方式在输出缓冲区上设置时间戳。

请注意,我们使用 push 模式将新缓冲区推入 appsrc ,尽管 appsrc 在 pull 模式下运行。

#include <gst/gst.h>

static GMainLoop *loop;

static void
cb_need_data (GstElement *appsrc,
          guint       unused_size,
          gpointer    user_data)
{
  static gboolean white = FALSE;
  static GstClockTime timestamp = 0;
  GstBuffer *buffer;
  guint size;
  GstFlowReturn ret;

  size = 385 * 288 * 2;

  buffer = gst_buffer_new_allocate (NULL, size, NULL);

  /* this makes the image black/white */
  gst_buffer_memset (buffer, 0, white ? 0xff : 0x0, size);

  white = !white;

  GST_BUFFER_PTS (buffer) = timestamp;
  GST_BUFFER_DURATION (buffer) = gst_util_uint64_scale_int (1, GST_SECOND, 2);

  timestamp += GST_BUFFER_DURATION (buffer);

  g_signal_emit_by_name (appsrc, "push-buffer", buffer, &ret);
  gst_buffer_unref (buffer);

  if (ret != GST_FLOW_OK) {
    /* something wrong, stop pushing */
    g_main_loop_quit (loop);
  }
}

gint
main (gint   argc,
      gchar *argv[])
{
  GstElement *pipeline, *appsrc, *conv, *videosink;

  /* init GStreamer */
  gst_init (&argc, &argv);
  loop = g_main_loop_new (NULL, FALSE);

  /* setup pipeline */
  pipeline = gst_pipeline_new ("pipeline");
  appsrc = gst_element_factory_make ("appsrc", "source");
  conv = gst_element_factory_make ("videoconvert", "conv");
  videosink = gst_element_factory_make ("xvimagesink", "videosink");

  /* setup */
  g_object_set (G_OBJECT (appsrc), "caps",
        gst_caps_new_simple ("video/x-raw",
                     "format", G_TYPE_STRING, "RGB16",
                     "width", G_TYPE_INT, 384,
                     "height", G_TYPE_INT, 288,
                     "framerate", GST_TYPE_FRACTION, 0, 1,
                     NULL), NULL);
  gst_bin_add_many (GST_BIN (pipeline), appsrc, conv, videosink, NULL);
  gst_element_link_many (appsrc, conv, videosink, NULL);

  /* setup appsrc */
  g_object_set (G_OBJECT (appsrc),
        "stream-type", 0,
        "format", GST_FORMAT_TIME, NULL);
  g_signal_connect (appsrc, "need-data", G_CALLBACK (cb_need_data), NULL);

  /* play */
  gst_element_set_state (pipeline, GST_STATE_PLAYING);
  g_main_loop_run (loop);

  /* clean up */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (GST_OBJECT (pipeline));
  g_main_loop_unref (loop);

  return 0;
  }

用 appsink 获取数据

与 appsrc 相比,appsink 使用起来更容易一些。它也支持基于 pull 和 push 的模式,用于从管道中获取数据。

从 appsink 检索 sample 的方法通常是使用 gst_app_sink_pull_sample() 和 gst_app_sink_pull_preroll() 方法或使用 pull-sample 和 pull-preroll 信号。这些方法在 sink 获得 sample 之前会阻塞,直到 sink 关闭或到达 EOS 为止。

appsink 将在内部使用队列从流线程中收集缓冲区。如果应用程序无法足够快地提取样本,则此队列将随着时间消耗大量内存。max-buffers 属性可用于限制队列大小。drop 属性控制在达到最大队列大小时是流线程阻塞还是丢弃较旧的缓冲区。请注意,阻塞流线程可能会对实时性能产生负面影响,应避免。

如果不希望发生阻塞行为,则将该 emit-signals 属性设置为 TRUE 时,appsink 可以在不阻塞的情况下提取 sample 时使发出 new-preroll 和 new-sample 信号。

appsink 可以使用 caps 属性控制所述格式。此属性可以包含不固定的 pad,可以通过获取 sample pad 来获取提取 sample 的格式。

如果 pull-preroll 或 pull-sample 方法返回 NULL, appsink 则处于停止或 EOS 状态。可以使用 eos 属性或 gst_app_sink_is_eos() 方法检查状态。

当达到 EOS 状态时,也可以使用 eos 信号来通知以避免轮询。

考虑在 appsink 中配置以下属性:

  • sync 如果要让接收器基类将属性与缓冲区时钟同步,请在传递样本之前使用该属性。
  • 使用 qos 属性启用服务质量。如果您要处理原始视频帧,并让基类在时钟上同步。让基类向上游发送 QOS 事件也是一个好主意 。
  • caps 属性包含接受的上限。上游元素将尝试转换格式,使其与 appsink 上配置的上限匹配 。您仍然必须检查 GstSample 以获取缓冲区的实际上限。

Appsink 示例

以下是有关如何使用捕获视频流快照的示例 appsink。

#include <gst/gst.h>
#ifdef HAVE_GTK
#include <gtk/gtk.h>
#endif

#include <stdlib.h>

#define CAPS "video/x-raw,format=RGB,width=160,pixel-aspect-ratio=1/1"

int
main (int argc, char *argv[])
{
  GstElement *pipeline, *sink;
  gint width, height;
  GstSample *sample;
  gchar *descr;
  GError *error = NULL;
  gint64 duration, position;
  GstStateChangeReturn ret;
  gboolean res;
  GstMapInfo map;

  gst_init (&argc, &argv);

  if (argc != 2) {
    g_print ("usage: %s <uri>\n Writes snapshot.png in the current directory\n",
        argv[0]);
    exit (-1);
  }

  /* create a new pipeline */
  descr =
      g_strdup_printf ("uridecodebin uri=%s ! videoconvert ! videoscale ! "
      " appsink name=sink caps=\"" CAPS "\"", argv[1]);
  pipeline = gst_parse_launch (descr, &error);

  if (error != NULL) {
    g_print ("could not construct pipeline: %s\n", error->message);
    g_clear_error (&error);
    exit (-1);
  }

  /* get sink */
  sink = gst_bin_get_by_name (GST_BIN (pipeline), "sink");

  /* set to PAUSED to make the first frame arrive in the sink */
  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
  switch (ret) {
    case GST_STATE_CHANGE_FAILURE:
      g_print ("failed to play the file\n");
      exit (-1);
    case GST_STATE_CHANGE_NO_PREROLL:
      /* for live sources, we need to set the pipeline to PLAYING before we can
       * receive a buffer. We don't do that yet */
      g_print ("live sources not supported yet\n");
      exit (-1);
    default:
      break;
  }
  /* This can block for up to 5 seconds. If your machine is really overloaded,
   * it might time out before the pipeline prerolled and we generate an error. A
   * better way is to run a mainloop and catch errors there. */
  ret = gst_element_get_state (pipeline, NULL, NULL, 5 * GST_SECOND);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_print ("failed to play the file\n");
    exit (-1);
  }

  /* get the duration */
  gst_element_query_duration (pipeline, GST_FORMAT_TIME, &duration);

  if (duration != -1)
    /* we have a duration, seek to 5% */
    position = duration * 5 / 100;
  else
    /* no duration, seek to 1 second, this could EOS */
    position = 1 * GST_SECOND;

  /* seek to the a position in the file. Most files have a black first frame so
   * by seeking to somewhere else we have a bigger chance of getting something
   * more interesting. An optimisation would be to detect black images and then
   * seek a little more */
  gst_element_seek_simple (pipeline, GST_FORMAT_TIME,
      GST_SEEK_FLAG_KEY_UNIT | GST_SEEK_FLAG_FLUSH, position);

  /* get the preroll buffer from appsink, this block untils appsink really
   * prerolls */
  g_signal_emit_by_name (sink, "pull-preroll", &sample, NULL);

  /* if we have a buffer now, convert it to a pixbuf. It's possible that we
   * don't have a buffer because we went EOS right away or had an error. */
  if (sample) {
    GstBuffer *buffer;
    GstCaps *caps;
    GstStructure *s;

    /* get the snapshot buffer format now. We set the caps on the appsink so
     * that it can only be an rgb buffer. The only thing we have not specified
     * on the caps is the height, which is dependant on the pixel-aspect-ratio
     * of the source material */
    caps = gst_sample_get_caps (sample);
    if (!caps) {
      g_print ("could not get snapshot format\n");
      exit (-1);
    }
    s = gst_caps_get_structure (caps, 0);

    /* we need to get the final caps on the buffer to get the size */
    res = gst_structure_get_int (s, "width", &width);
    res |= gst_structure_get_int (s, "height", &height);
    if (!res) {
      g_print ("could not get snapshot dimension\n");
      exit (-1);
    }

    /* create pixmap from buffer and save, gstreamer video buffers have a stride
     * that is rounded up to the nearest multiple of 4 */
    buffer = gst_sample_get_buffer (sample);
    /* Mapping a buffer can fail (non-readable) */
    if (gst_buffer_map (buffer, &map, GST_MAP_READ)) {
#ifdef HAVE_GTK
      pixbuf = gdk_pixbuf_new_from_data (map.data,
          GDK_COLORSPACE_RGB, FALSE, 8, width, height,
          GST_ROUND_UP_4 (width * 3), NULL, NULL);

      /* save the pixbuf */
      gdk_pixbuf_save (pixbuf, "snapshot.png", "png", &error, NULL);
#endif
      gst_buffer_unmap (buffer, &map);
    }
    gst_sample_unref (sample);
  } else {
    g_print ("could not make snapshot\n");
  }

  /* cleanup and exit */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (sink);
  gst_object_unref (pipeline);

  exit (0);
}

强制格式

有时您需要设置特定的格式。你可以使用 capsfilter 元件来执行此操作 。

例如,如果需要特定的视频大小和颜色格式,或者音频位大小和多个通道,则你可以使用 filtered  caps 来强制指定管道上的特定 GstCaps 对象。通过在两个元件之间放置一个 capsfilter,并在其 caps 属性中指定所需 GstCaps 的值,可以在链接上设置过滤后的上限。capsfilter 将只允许这些协商过的 capability。

在播放管道中更改格式

还可以在 PLAYING 时动态更改管道中的 caps。只需更改 capsfilter 上的 caps 属性即可完成此操作。该 capsfilter 会给上游发送一个 RECONFIGURE 事件,这将使上游元件尝试重新协商新的格式和分配器。仅当上游元件在其 src pad 上未使用固定 caps 时,此方法才有效。

以下是在 PLAYING 状态下如何更改管道 caps 的示例:

#include <stdlib.h>

#include <gst/gst.h>

#define MAX_ROUND 100

int
main (int argc, char **argv)
{
  GstElement *pipe, *filter;
  GstCaps *caps;
  gint width, height;
  gint xdir, ydir;
  gint round;
  GstMessage *message;

  gst_init (&argc, &argv);

  pipe = gst_parse_launch_full ("videotestsrc ! capsfilter name=filter ! "
             "ximagesink", NULL, GST_PARSE_FLAG_NONE, NULL);
  g_assert (pipe != NULL);

  filter = gst_bin_get_by_name (GST_BIN (pipe), "filter");
  g_assert (filter);

  width = 320;
  height = 240;
  xdir = ydir = -10;

  for (round = 0; round < MAX_ROUND; round++) {
    gchar *capsstr;
    g_print ("resize to %dx%d (%d/%d)   \r", width, height, round, MAX_ROUND);

    /* we prefer our fixed width and height but allow other dimensions to pass
     * as well */
    capsstr = g_strdup_printf ("video/x-raw, width=(int)%d, height=(int)%d",
        width, height);

    caps = gst_caps_from_string (capsstr);
    g_free (capsstr);
    g_object_set (filter, "caps", caps, NULL);
    gst_caps_unref (caps);

    if (round == 0)
      gst_element_set_state (pipe, GST_STATE_PLAYING);

    width += xdir;
    if (width >= 320)
      xdir = -10;
    else if (width < 200)
      xdir = 10;

    height += ydir;
    if (height >= 240)
      ydir = -10;
    else if (height < 150)
      ydir = 10;

    message =
        gst_bus_poll (GST_ELEMENT_BUS (pipe), GST_MESSAGE_ERROR,
        50 * GST_MSECOND);
    if (message) {
      g_print ("got error           \n");

      gst_message_unref (message);
    }
  }
  g_print ("done                    \n");

  gst_object_unref (filter);
  gst_element_set_state (pipe, GST_STATE_NULL);
  gst_object_unref (pipe);

  return 0;
}

请注意我们如何在较小的超时时间内使用 gst_bus_poll() 以获取消息并引入短暂睡眠。

可以为 capsfilter 设置多个 caps,并以 ;分隔。capsfilter 将尝试重新协商列表中的第一种可能的格式。

  • 2
    点赞
  • 0
    评论
  • 7
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页