Gstreamer应用开发手册14:替换管道元件

动态改变管道

在本节中,我们讨论一些用于动态修改管道的技术。我们正在专门讨论在PLAYING状态下且不中断数据流的情况下更改管道。

建立动态管道时,需要考虑一些重要事项:

  • 从管道中删除元件时,请确保未链接的pad上没有数据流,因为这将导致致命的管道错误。断开pad连接之前,请始终阻塞source pad(在推模式下)或sink pad(在拉模式下)。
  • 将元件添加到管道中时,请确保在允许数据流之前,将元件置于正确的状态,通常与父级处于同一状态。新创建元件时,该元件处于 NULL状态,并且在接收数据时将返回错误。
  • 将元件添加到管道时,GStreamer默认情况下会将元件的时钟和基准时间设置为管道的当前值。这意味着该元件将能够构造与管道中其他元件相同的管道运行时间。这意味着接收器将像管道中的其他接收器一样同步缓冲区,并且源将生成运行时间与其他源匹配的缓冲区。
  • 当从上游链取消链接元件时,请始终确保通过向元件接收器发送一个EOS事件并等待EOS元件离开(带有事件探测器)来刷新元件中所有排队的数据。如果不执行刷新,则将丢失由未链接的元件缓冲的数据。这可能会导致简单的帧丢失(一些视频帧,几毫秒的音频等),但是如果您删除多路复用器(在某些情况下为编码器或类似元件),则可能会损坏文件,由于某些相关的元数据(标题,查找/索引表,内部同步标签)可能未正确存储或更新,因此无法正确播放。
  • 实时源将产生与管道的running-time相等的缓冲区running-time。没有活动源的管道会产生running-time从0开始的缓冲区。 同样,在进行刷新查找之后,这些管道会将其重置running-time为0。该running-time可被改变gst_pad_set_offset ()。重要的是要知道running-time管道中的元件,以保持同步。
  • 添加元件可能会更改管道的状态。例如,添加未预卷的接收器会使管道返回到预卷状态。例如,删除未预卷的接收器可能会将管道更改为PAUSED和PLAYING状态。添加实时源会取消预滚动阶段,并使管道处于播放状态。添加任何活动元件也可能会更改管道的延迟。添加或删除管道的元件可能会更改管道的时钟选择。如果新添加的元件提供了时钟,则管道使用新时钟可能会更好。另一方面,如果删除了为流水线提供时钟的元件,则必须选择新的时钟。
  • 添加和删​​除元件可能会导致上游或下游元件重新协商上限和/或分配器。您实际上不需要从应用程序执行任何操作,插件可以在很大程度上适应新的管道拓扑,从而优化其格式和分配策略。重要的是,当您添加,删除或更改管道中的元件时,管道可能需要协商新格式,这可能会失败。通常,您可以通过在需要的地方插入正确的转换器元件来解决此问题。另请参见更改管道中的元件。

GStreamer提供了对几乎所有动态管道修改的支持,但是您需要了解一些详细信息,然后才能执行此操作而不会引起管道错误

在以下各节中,我们将演示一些典型的修改用例。

更改管道中的元件

在此示例中,我们具有以下元件链:

   - ----.      .----------.      .---- -
element1 |      | element2 |      | element3
       src -> sink       src -> sink
   - ----'      '----------'      '---- -

我们希望在管道处于PLAYING状态时用element4替换element2。假设element2是可视化,并且您想在管道中切换可视化。

我们不能仅仅将element2的sink pad与element1的src pad断开链接,因为这将使element1的src pad保持未链接状态,并在将数据推入src pad时导致流水错误。应该在将element2替换为element4之前,阻止来自element1的src pad的数据流,然后恢复数据流,如以下步骤所示:

  • 用阻塞垫探针阻塞element1的src pad。当pad被阻塞时,探测回调将被调用。
  • 在block回调内部,element1和element2之间没有任何流通,直到被阻塞为止,没有任何流通。
  • 取消链接element1和element2。
  • 确保数据已从element2中清除。有些元件可能会在内部保留一些数据,因此您需要通过推EOS到element2确保不会丢失任何数据,如下所示:将事件探针放在element2的src pad上。发送EOS到element2的sink pad;这样可以确保元件2中的所有数据都被强制删除;等待EOS事件出现在element2的src pad上。当EOS收到,拖放和删除事件探测器。
  • 取消链接element2和element3。现在,您还可以从管道中删除element2并将状态设置为NULL。
  • 如果尚未添加element4,则将其添加到管道中。链接element4和element3,链接element1和element4。
  • 确保element4与管道中其余元件的状态相同。它至少应处于PAUSED可接收缓冲区和事件的状态。
  • 取消阻止element1的src pad,这将使新数据进入element4并继续流式传输。

当source pad被阻塞时,即管道中有数据流时,上述算法将起作用。如果没有数据流,那么更改元件也没有任何意义,因此该算法也可以在PAUSED状态中使用。

本示例每秒更改一次简单管道上的视频效果:

#include 

static gchar *opt_effects = NULL;

#define DEFAULT_EFFECTS "identity,exclusion,navigationtest," \
    "agingtv,videoflip,vertigotv,gaussianblur,shagadelictv,edgetv"

static GstPad *blockpad;
static GstElement *conv_before;
static GstElement *conv_after;
static GstElement *cur_effect;
static GstElement *pipeline;

static GQueue effects = G_QUEUE_INIT;

static GstPadProbeReturn
event_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  GMainLoop *loop = user_data;
  GstElement *next;

  if (GST_EVENT_TYPE (GST_PAD_PROBE_INFO_DATA (info)) != GST_EVENT_EOS)
    return GST_PAD_PROBE_PASS;

  gst_pad_remove_probe (pad, GST_PAD_PROBE_INFO_ID (info));

  /* push current effect back into the queue */
  g_queue_push_tail (&effects, gst_object_ref (cur_effect));
  /* take next effect from the queue */
  next = g_queue_pop_head (&effects);
  if (next == NULL) {
    GST_DEBUG_OBJECT (pad, "no more effects");
    g_main_loop_quit (loop);
    return GST_PAD_PROBE_DROP;
  }

  g_print ("Switching from '%s' to '%s'..\n", GST_OBJECT_NAME (cur_effect),
      GST_OBJECT_NAME (next));

  gst_element_set_state (cur_effect, GST_STATE_NULL);

  /* remove unlinks automatically */
  GST_DEBUG_OBJECT (pipeline, "removing %" GST_PTR_FORMAT, cur_effect);
  gst_bin_remove (GST_BIN (pipeline), cur_effect);

  GST_DEBUG_OBJECT (pipeline, "adding   %" GST_PTR_FORMAT, next);
  gst_bin_add (GST_BIN (pipeline), next);

  GST_DEBUG_OBJECT (pipeline, "linking..");
  gst_element_link_many (conv_before, next, conv_after, NULL);

  gst_element_set_state (next, GST_STATE_PLAYING);

  cur_effect = next;
  GST_DEBUG_OBJECT (pipeline, "done");

  return GST_PAD_PROBE_DROP;
}

static GstPadProbeReturn
pad_probe_cb (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
{
  GstPad *srcpad, *sinkpad;

  GST_DEBUG_OBJECT (pad, "pad is blocked now");

  /* remove the probe first */
  gst_pad_remove_probe (pad, GST_PAD_PROBE_INFO_ID (info));

  /* install new probe for EOS */
  srcpad = gst_element_get_static_pad (cur_effect, "src");
  gst_pad_add_probe (srcpad, GST_PAD_PROBE_TYPE_BLOCK |
      GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM, event_probe_cb, user_data, NULL);
  gst_object_unref (srcpad);

  /* push EOS into the element, the probe will be fired when the
   * EOS leaves the effect and it has thus drained all of its data */
  sinkpad = gst_element_get_static_pad (cur_effect, "sink");
  gst_pad_send_event (sinkpad, gst_event_new_eos ());
  gst_object_unref (sinkpad);

  return GST_PAD_PROBE_OK;
}

static gboolean
timeout_cb (gpointer user_data)
{
  gst_pad_add_probe (blockpad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
      pad_probe_cb, user_data, NULL);

  return TRUE;
}

static gboolean
bus_cb (GstBus * bus, GstMessage * msg, gpointer user_data)
{
  GMainLoop *loop = user_data;

  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_ERROR:{
      GError *err = NULL;
      gchar *dbg;

      gst_message_parse_error (msg, &err, &dbg);
      gst_object_default_error (msg->src, err, dbg);
      g_clear_error (&err);
      g_free (dbg);
      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }
  return TRUE;
}

int
main (int argc, char **argv)
{
  GOptionEntry options[] = {
    {"effects", 'e', 0, G_OPTION_ARG_STRING, &opt_effects,
        "Effects to use (comma-separated list of element names)", NULL},
    {NULL}
  };
  GOptionContext *ctx;
  GError *err = NULL;
  GMainLoop *loop;
  GstElement *src, *q1, *q2, *effect, *filter1, *filter2, *sink;
  gchar **effect_names, **e;

  ctx = g_option_context_new ("");
  g_option_context_add_main_entries (ctx, options, NULL);
  g_option_context_add_group (ctx, gst_init_get_option_group ());
  if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
    g_print ("Error initializing: %s\n", err->message);
    g_clear_error (&err);
    g_option_context_free (ctx);
    return 1;
  }
  g_option_context_free (ctx);

  if (opt_effects != NULL)
    effect_names = g_strsplit (opt_effects, ",", -1);
  else
    effect_names = g_strsplit (DEFAULT_EFFECTS, ",", -1);

  for (e = effect_names; e != NULL && *e != NULL; ++e) {
    GstElement *el;

    el = gst_element_factory_make (*e, NULL);
    if (el) {
      g_print ("Adding effect '%s'\n", *e);
      g_queue_push_tail (&effects, el);
    }
  }

  pipeline = gst_pipeline_new ("pipeline");

  src = gst_element_factory_make ("videotestsrc", NULL);
  g_object_set (src, "is-live", TRUE, NULL);

  filter1 = gst_element_factory_make ("capsfilter", NULL);
  gst_util_set_object_arg (G_OBJECT (filter1), "caps",
      "video/x-raw, width=320, height=240, "
      "format={ I420, YV12, YUY2, UYVY, AYUV, Y41B, Y42B, "
      "YVYU, Y444, v210, v216, NV12, NV21, UYVP, A420, YUV9, YVU9, IYU1 }");

  q1 = gst_element_factory_make ("queue", NULL);

  blockpad = gst_element_get_static_pad (q1, "src");

  conv_before = gst_element_factory_make ("videoconvert", NULL);

  effect = g_queue_pop_head (&effects);
  cur_effect = effect;

  conv_after = gst_element_factory_make ("videoconvert", NULL);

  q2 = gst_element_factory_make ("queue", NULL);

  filter2 = gst_element_factory_make ("capsfilter", NULL);
  gst_util_set_object_arg (G_OBJECT (filter2), "caps",
      "video/x-raw, width=320, height=240, "
      "format={ RGBx, BGRx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR }");

  sink = gst_element_factory_make ("ximagesink", NULL);

  gst_bin_add_many (GST_BIN (pipeline), src, filter1, q1, conv_before, effect,
      conv_after, q2, sink, NULL);

  gst_element_link_many (src, filter1, q1, conv_before, effect, conv_after,
      q2, sink, NULL);

  gst_element_set_state (pipeline, GST_STATE_PLAYING);

  loop = g_main_loop_new (NULL, FALSE);

  gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_cb, loop);

  g_timeout_add_seconds (1, timeout_cb, loop);

  g_main_loop_run (loop);

  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);

  return 0;
}

注意我们如何在videoconvert效果之前和之后添加元件。这是必需的,因为某些元件可能在不同的色彩空间中运行;通过插入转换元件,我们可以帮助确保可以协商适当的格式。

 

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