Gstreamer应用开发手册12:探测函数

管道控制

接下来的几篇文章将讨论如何在你的应用程序控制管道的几种方法。这些部分有点难度,所以你在阅读本文之前需要一些编程知识。接下来将要讨论的包括

  • 如何如何将数据从应用程序插入到管道中,
  • 如何从管道读取数据,
  • 如何控制管道的速度、长度、起始点,
  • 如何监听管道的数据处理过程。

使用探测

探测可以看做访问Pad的侦听器。从技术上讲,探针不过是可以使用gst_pad_add_probe ()附加到pad上的回调函数 。同样的,你可以使用gst_pad_remove_probe ()删除回调。连接后,探针会通知你pad的任何活动。你可以定义添加探针感兴趣的通知类型。

探测类型:

  • 推入或拉出缓冲区。你要在注册探针时指定GST_PAD_PROBE_TYPE_BUFFER 。因为可以用不同的方式安排pad。也可以通过可选GST_PAD_PROBE_TYPE_PUSH和GST_PAD_PROBE_TYPE_PULL标志来指定你对哪种调度模式感兴趣 。你可以使用此探针检查,修改或删除缓冲区。请参阅数据探针。
  • 缓冲区列表被推送。注册探头时使用GST_PAD_PROBE_TYPE_BUFFER_LIST 。
  • 在pad上发生事件。使用 GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM和 GST_PAD_PROBE_TYPE_EVENT_UPSTREAM标志选择下游事件和上游事件。GST_PAD_PROBE_TYPE_EVENT_BOTH还可以方便地通知双向发生的事件。默认情况下,刷新事件不会引起通知。你需要显式启用 GST_PAD_PROBE_TYPE_EVENT_FLUSH以接收刷新事件的回调。事件始终仅在推送模式下通知。你可以使用这种类型的探针来检查,修改或删除事件。
  • 在pad上进行查询。使用 GST_PAD_PROBE_TYPE_QUERY_DOWNSTREAM和 GST_PAD_PROBE_TYPE_QUERY_UPSTREAM标志选择下游查询和上游查询。便利 GST_PAD_PROBE_TYPE_QUERY_BOTH还可以用于选择两个方向。查询探针将收到两次通知:查询在上游/下游进行查询以及返回查询结果。你可以在执行查询和返回查询结果时使用GST_PAD_PROBE_TYPE_PUSH和GST_PAD_PROBE_TYPE_PULL选择在哪个阶段调用回调。你可以使用查询探针来检查或修改查询,甚至可以在探针回调中回答查询。要回答查询,你可以将结果值放入查询中并从回调中返回GST_PAD_PROBE_DROP。
  • 除了通知你数据流之外,还可以要求探针在回调返回时阻止数据流。这称为阻塞探针,可通过指定GST_PAD_PROBE_TYPE_BLOCK标志来激活 。你可以将此标志与其他标志一起使用,以阻止所选活动上的数据流。如果卸下探针或从回调返回GST_PAD_PROBE_REMOVE,则pad将再次变为畅通无阻 。你可以通过GST_PAD_PROBE_PASS从回调返回仅让当前阻止的项目通过,它将在下一个项目上再次阻止。阻塞探针用于暂时阻塞pad,因为它们未链接,或者你打算取消它们的链接。如果没有阻止数据流,如果将数据推送到未链接的pad上,则管道将进入错误状态。
  • pad上没有任何活动时收到通知。你使用GST_PAD_PROBE_TYPE_IDLE标志安装此探针。你可以指定 GST_PAD_PROBE_TYPE_PUSH和/或GST_PAD_PROBE_TYPE_PULL仅根据pad调度模式进行通知。IDLE探针也是一种阻塞探针,因为只要安装了IDLE探针,它就不会允许任何数据通过pad。

你可以使用空闲探针动态重新链接pad。我们将看到如何使用空闲探针替换管道中的元素。另请参阅动态更改管道。

数据探针

pad上有数据通过时,数据探针会通知你。通过 GST_PAD_PROBE_TYPE_BUFFER和/或GST_PAD_PROBE_TYPE_BUFFER_LIST以 gst_pad_add_probe ()创建这种探头。大多数常见的缓冲区操作元素可以在_chain ()函数中执行,也可以在探针回调中完成。
数据探针在管道的流线程上下文中运行,因此回调应尝试避免阻塞,并且通常避免做一些奇怪的事情。这样做可能会对管道的性能产生负面影响,或者在出现错误的情况下导致死锁或崩溃。更准确地说,通常应该避免在探测回调中调用与GUI相关的功能,也不要尝试更改管道的状态。应用程序可以在管道总线上发布自定义消息,以与主应用程序线程进行通信,并执行诸如停止管道之类的操作。
以下是有关使用数据探针的示例。不确定你要查找的内容的,可以将该程序的输出与gst-launch-1.0 videotestsrc ! xvimagesink输出进行比较:

#include 

static GstPadProbeReturn
cb_have_data (GstPad          *pad,
              GstPadProbeInfo *info,
              gpointer         user_data)
{
  gint x, y;
  GstMapInfo map;
  guint16 *ptr, t;
  GstBuffer *buffer;

  buffer = GST_PAD_PROBE_INFO_BUFFER (info);

  buffer = gst_buffer_make_writable (buffer);

  /* Making a buffer writable can fail (for example if it
   * cannot be copied and is used more than once)
   */
  if (buffer == NULL)
    return GST_PAD_PROBE_OK;

  /* Mapping a buffer can fail (non-writable) */
  if (gst_buffer_map (buffer, &map, GST_MAP_WRITE)) {
    ptr = (guint16 *) map.data;
    /* invert data */
    for (y = 0; y < 288; y++) {
      for (x = 0; x < 384 / 2; x++) {
        t = ptr[384 - 1 - x];
        ptr[384 - 1 - x] = ptr[x];
        ptr[x] = t;
      }
      ptr += 384;
    }
    gst_buffer_unmap (buffer, &map);
  }

  GST_PAD_PROBE_INFO_DATA (info) = buffer;

  return GST_PAD_PROBE_OK;
}

gint
main (gint   argc,
      gchar *argv[])
{
  GMainLoop *loop;
  GstElement *pipeline, *src, *sink, *filter, *csp;
  GstCaps *filtercaps;
  GstPad *pad;

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

  /* build */
  pipeline = gst_pipeline_new ("my-pipeline");
  src = gst_element_factory_make ("videotestsrc", "src");
  if (src == NULL)
    g_error ("Could not create 'videotestsrc' element");

  filter = gst_element_factory_make ("capsfilter", "filter");
  g_assert (filter != NULL); /* should always exist */

  csp = gst_element_factory_make ("videoconvert", "csp");
  if (csp == NULL)
    g_error ("Could not create 'videoconvert' element");

  sink = gst_element_factory_make ("xvimagesink", "sink");
  if (sink == NULL) {
    sink = gst_element_factory_make ("ximagesink", "sink");
    if (sink == NULL)
      g_error ("Could not create neither 'xvimagesink' nor 'ximagesink' element");
  }

  gst_bin_add_many (GST_BIN (pipeline), src, filter, csp, sink, NULL);
  gst_element_link_many (src, filter, csp, sink, NULL);
  filtercaps = 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, 25, 1,
               NULL);
  g_object_set (G_OBJECT (filter), "caps", filtercaps, NULL);
  gst_caps_unref (filtercaps);

  pad = gst_element_get_static_pad (src, "src");
  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER,
      (GstPadProbeCallback) cb_have_data, NULL, NULL);
  gst_object_unref (pad);

  /* run */
  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  /* wait until it's up and running or failed */
  if (gst_element_get_state (pipeline, NULL, NULL, -1) == GST_STATE_CHANGE_FAILURE) {
    g_error ("Failed to go into PLAYING state");
  }

  g_print ("Running ...\n");
  g_main_loop_run (loop);

  /* exit */
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);

  return 0;
}

严格来说,仅当缓冲区可写时,才允许填充探针回调修改缓冲区内容。是否是这种情况在很大程度上取决于管道和涉及的元件。通常是这种情况,但有时并非如此,如果不是这样,则对数据或元数据的意外修改可能会引入难以调试和跟踪的错误。你可以使用gst_buffer_is_writable ()来检查缓冲区是否可写。由于你可以传回与传入的缓冲区不同的缓冲区,因此最好使用gst_buffer_make_writable ()将该缓冲区写入回调函数中。

pad式探头最适合查看数据通过管道时的情况。如果需要修改数据,则应该编写自己的GStreamer元件,例如GstAudioFilter,GstVideoFilter或 GstBaseTransform使之相当容易。

如果你只想在缓冲区通过管道时对其进行检查,则甚至无需设置pad探针。你也可以只将标识元件插入管道中并连接到其“切换”信号。identity元件还提供了一些有用的调试工具,例如 dump和last-message属性。通过将'-v'开关传递到gst-launch并将silent身份的属性设置为FALSE来启用。

播放媒体文件的一部分

在此示例中,我们将向你展示如何播放媒体文件的一部分。目标是只播放2到5秒,然后退出。

第一步,我们将uridecodebin元件设置为PAUSED状态,并确保我们阻止所有创建的pad。当所有的pad都被阻塞时,我们在所有pad上都有数据,我们说它们uridecodebin已经预卷。

在预卷管道中,我们可以询问媒体的持续时间,也可以执行搜索。我们在管道上执行搜索操作以选择2到5秒部分。

配置完所需的部分后,我们可以链接接收器元件,解除对pad的阻塞并将管道设置为PLAYING状态。你会看到接收器在转到EOS之前已准确显示了所请求的区域。

这是代码:

#include 

static GMainLoop *loop;
static volatile gint counter;
static GstBus *bus;
static gboolean prerolled = FALSE;
static GstPad *sinkpad;

static void
dec_counter (GstElement * pipeline)
{
  if (prerolled)
    return;

  if (g_atomic_int_dec_and_test (&counter)) {
    /* all probes blocked and no-more-pads signaled, post
     * message on the bus. */
    prerolled = TRUE;

    gst_bus_post (bus, gst_message_new_application (
          GST_OBJECT_CAST (pipeline),
          gst_structure_new_empty ("ExPrerolled")));
  }
}

/* called when a source pad of uridecodebin is blocked */
static GstPadProbeReturn
cb_blocked (GstPad          *pad,
            GstPadProbeInfo *info,
            gpointer         user_data)
{
  GstElement *pipeline = GST_ELEMENT (user_data);

  if (prerolled)
    return GST_PAD_PROBE_REMOVE;

  dec_counter (pipeline);

  return GST_PAD_PROBE_OK;
}

/* called when uridecodebin has a new pad */
static void
cb_pad_added (GstElement *element,
              GstPad     *pad,
              gpointer    user_data)
{
  GstElement *pipeline = GST_ELEMENT (user_data);

  if (prerolled)
    return;

  g_atomic_int_inc (&counter);

  gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
      (GstPadProbeCallback) cb_blocked, pipeline, NULL);

  /* try to link to the video pad */
  gst_pad_link (pad, sinkpad);
}

/* called when uridecodebin has created all pads */
static void
cb_no_more_pads (GstElement *element,
                 gpointer    user_data)
{
  GstElement *pipeline = GST_ELEMENT (user_data);

  if (prerolled)
    return;

  dec_counter (pipeline);
}

/* called when a new message is posted on the bus */
static void
cb_message (GstBus     *bus,
            GstMessage *message,
            gpointer    user_data)
{
  GstElement *pipeline = GST_ELEMENT (user_data);

  switch (GST_MESSAGE_TYPE (message)) {
    case GST_MESSAGE_ERROR:
      g_print ("we received an error!\n");
      g_main_loop_quit (loop);
      break;
    case GST_MESSAGE_EOS:
      g_print ("we reached EOS\n");
      g_main_loop_quit (loop);
      break;
    case GST_MESSAGE_APPLICATION:
    {
      if (gst_message_has_name (message, "ExPrerolled")) {
        /* it's our message */
        g_print ("we are all prerolled, do seek\n");
        gst_element_seek (pipeline,
            1.0, GST_FORMAT_TIME,
            GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
            GST_SEEK_TYPE_SET, 2 * GST_SECOND,
            GST_SEEK_TYPE_SET, 5 * GST_SECOND);

        gst_element_set_state (pipeline, GST_STATE_PLAYING);
      }
      break;
    }
    default:
      break;
  }
}

gint
main (gint   argc,
      gchar *argv[])
{
  GstElement *pipeline, *src, *csp, *vs, *sink;

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

  if (argc < 2) {
    g_print ("usage: %s ", argv[0]);
    return -1;
  }

  /* build */
  pipeline = gst_pipeline_new ("my-pipeline");

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  gst_bus_add_signal_watch (bus);
  g_signal_connect (bus, "message", (GCallback) cb_message,
      pipeline);

  src = gst_element_factory_make ("uridecodebin", "src");
  if (src == NULL)
    g_error ("Could not create 'uridecodebin' element");

  g_object_set (src, "uri", argv[1], NULL);

  csp = gst_element_factory_make ("videoconvert", "csp");
  if (csp == NULL)
    g_error ("Could not create 'videoconvert' element");

  vs = gst_element_factory_make ("videoscale", "vs");
  if (csp == NULL)
    g_error ("Could not create 'videoscale' element");

  sink = gst_element_factory_make ("autovideosink", "sink");
  if (sink == NULL)
    g_error ("Could not create 'autovideosink' element");

  gst_bin_add_many (GST_BIN (pipeline), src, csp, vs, sink, NULL);

  /* can't link src yet, it has no pads */
  gst_element_link_many (csp, vs, sink, NULL);

  sinkpad = gst_element_get_static_pad (csp, "sink");

  /* for each pad block that is installed, we will increment
   * the counter. for each pad block that is signaled, we
   * decrement the counter. When the counter is 0 we post
   * an app message to tell the app that all pads are
   * blocked. Start with 1 that is decremented when no-more-pads
   * is signaled to make sure that we only post the message
   * after no-more-pads */
  g_atomic_int_set (&counter, 1);

  g_signal_connect (src, "pad-added",
      (GCallback) cb_pad_added, pipeline);
  g_signal_connect (src, "no-more-pads",
      (GCallback) cb_no_more_pads, pipeline);

  gst_element_set_state (pipeline, GST_STATE_PAUSED);

  g_main_loop_run (loop);

  gst_element_set_state (pipeline, GST_STATE_NULL);

  gst_object_unref (sinkpad);
  gst_object_unref (bus);
  gst_object_unref (pipeline);
  g_main_loop_unref (loop);

  return 0;
}

请注意,我们使用自定义应用程序消息来向主线程发出 uridecodebin已预卷的信号。然后,主线程将向请求的区域发出刷新搜索。刷新将暂时解除pad的阻塞,并在新数据再次到达时重新阻塞它们。我们检测到第二个块以删除探针。然后我们将管道设置为PLAYING,它应该播放选定的2到5秒部分;应用程序等待EOS消息并退出。

你可能感兴趣的:(gstreamer开发手册)