编写 GStreamer 插件2:编写插件的基础知识(二)

2. 指定 Pad

如前所述,pad 是数据进出元素的端口,这使得它们成为元素创建过程中非常重要的一项。在样板代码中,我们看到了静态 pad 模板如何负责向 element 类注册 pad 模板。在这里,我们将看到如何创建实际的元素,如何使用一个 _event ()-function 来配置特定的格式,以及如何注册函数来让数据流过元素。

在 element _init () 函数中,可以从 pad 模板创建 pad,该模板已在 _class_init () 函数的 element 类中注册。创建pad之后,必须设置一个 _chain () 函数指针,该指针将接收和处理 sinkpad 上的输入数据。您还可以选择设置一个 _event () 函数指针和一个 _query () 函数指针。或者,pad 也可以在循环模式下运行,这意味着它们可以自己拉取数据。稍后将详细介绍此主题。在那之后,你必须用元素注册 pad。事情是这样的:

static void
gst_my_filter_init (GstMyFilter *filter)
{
  /* pad through which data comes in to the element */
  filter->sinkpad = gst_pad_new_from_static_template (
    &sink_template, "sink");
  /* pads are configured here with gst_pad_set_*_function () */



  gst_element_add_pad (GST_ELEMENT (filter), filter->sinkpad);

  /* pad through which data goes out of the element */
  filter->srcpad = gst_pad_new_from_static_template (
    &src_template, "src");
  /* pads are configured here with gst_pad_set_*_function () */



  gst_element_add_pad (GST_ELEMENT (filter), filter->srcpad);

  /* properties initial value */
  filter->silent = FALSE;
}

3. 链函数

所有数据都要借助于链式函数来处理。对于简单过滤器,_chain () 函数大多是线性函数,因此每进来一个缓冲区,则要送出去一个缓冲区。下面是一个非常简单的链式功能实现:

static GstFlowReturn gst_my_filter_chain (GstPad    *pad,
                                          GstObject *parent,
                                          GstBuffer *buf);

[..]

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  /* configure chain function on the pad before adding
   * the pad to the element */
  gst_pad_set_chain_function (filter->sinkpad,
      gst_my_filter_chain);
[..]
}

static GstFlowReturn
gst_my_filter_chain (GstPad    *pad,
                     GstObject *parent,
             GstBuffer *buf)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);

  if (!filter->silent)
    g_print ("Have data of size %" G_GSIZE_FORMAT" bytes!\n",
        gst_buffer_get_size (buf));

  return gst_pad_push (filter->srcpad, buf);
}

显然,上面的函数没有什么使用价值。你通常会在那里处理数据,而不是打印进来的数据。但是,请记住,缓冲区并不总是可写的。

在更高级的元素(进行事件处理的元素)中,您可能需要另外指定一个事件处理函数,在发送流事件(如caps、流结束、newsegment、标记等)时将调用该函数。

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  gst_pad_set_event_function (filter->sinkpad,
      gst_my_filter_sink_event);
[..]
}



static gboolean
gst_my_filter_sink_event (GstPad    *pad,
                  GstObject *parent,
                  GstEvent  *event)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
      /* we should handle the format here */
      break;
    case GST_EVENT_EOS:
      /* end-of-stream, we should close down all stream leftovers here */
      gst_my_filter_stop_processing (filter);
      break;
    default:
      break;
  }

  return gst_pad_event_default (pad, parent, event);
}

static GstFlowReturn
gst_my_filter_chain (GstPad    *pad,
             GstObject *parent,
             GstBuffer *buf)
{
  GstMyFilter *filter = GST_MY_FILTER (parent);
  GstBuffer *outbuf;

  outbuf = gst_my_filter_process_data (filter, buf);
  gst_buffer_unref (buf);
  if (!outbuf) {
    /* something went wrong - signal an error */
    GST_ELEMENT_ERROR (GST_ELEMENT (filter), STREAM, FAILED, (NULL), (NULL));
    return GST_FLOW_ERROR;
  }

  return gst_pad_push (filter->srcpad, outbuf);
}

在某些情况下,元素也可以控制输入数据速率。在这种情况下,您可能需要编写一个所谓的基于循环的元素。源元素(只有源代码填充)也可以是基于get的元素。这些概念将在本指南的高级部分以及专门讨论源代码板的部分进行解释。

4. 事件函数

事件函数通知您数据流中发生的特殊事件(如caps、流结束、newsegment、标记等)。事件可以向上游和下游传播,因此您可以在接收器板和源板上接收它们。

下面是一个非常简单的事件函数,我们将它安装在元素的sink pad上。

static gboolean gst_my_filter_sink_event (GstPad    *pad,
                                          GstObject *parent,
                                          GstEvent  *event);

[..]

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  /* configure event function on the pad before adding
   * the pad to the element */
  gst_pad_set_event_function (filter->sinkpad,
      gst_my_filter_sink_event);
[..]
}

static gboolean
gst_my_filter_sink_event (GstPad    *pad,
                  GstObject *parent,
                  GstEvent  *event)
{
  gboolean ret;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_CAPS:
      /* we should handle the format here */

      /* push the event downstream */
      ret = gst_pad_push_event (filter->srcpad, event);
      break;
    case GST_EVENT_EOS:
      /* end-of-stream, we should close down all stream leftovers here */
      gst_my_filter_stop_processing (filter);

      ret = gst_pad_event_default (pad, parent, event);
      break;
    default:
      /* just call the default handler */
      ret = gst_pad_event_default (pad, parent, event);
      break;
  }
  return ret;
}

对于未知事件,最好调用默认事件处理程序 gst_pad_event_default ()。根据事件类型,默认处理程序将转发事件或只是取消转发。CAPS 事件在默认情况下是不转发的,因此我们需要自己在事件处理程序中执行此操作。

5. 查询函数

通过 query 函数,元素将接收它必须回复的查询。这些查询包括位置、持续时间以及元素支持的格式和调度模式。查询可以向上游和下游移动,因此您可以在接收器板和源板上接收它们。

下面是一个非常简单的查询函数,我们安装在元素的源代码板上。

static gboolean gst_my_filter_src_query (GstPad    *pad,
                                         GstObject *parent,
                                         GstQuery  *query);

[..]

static void
gst_my_filter_init (GstMyFilter * filter)
{
[..]
  /* configure event function on the pad before adding
   * the pad to the element */
  gst_pad_set_query_function (filter->srcpad,
      gst_my_filter_src_query);
[..]
}

static gboolean
gst_my_filter_src_query (GstPad    *pad,
                 GstObject *parent,
                 GstQuery  *query)
{
  gboolean ret;
  GstMyFilter *filter = GST_MY_FILTER (parent);

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_POSITION:
      /* we should report the current position */
      [...]
      break;
    case GST_QUERY_DURATION:
      /* we should report the duration here */
      [...]
      break;
    case GST_QUERY_CAPS:
      /* we should report the supported caps here */
      [...]
      break;
    default:
      /* just call the default handler */
      ret = gst_pad_query_default (pad, parent, query);
      break;
  }
  return ret;
}

对于未知查询,最好调用默认查询处理程序 gst_pad_query_default ()。根据查询类型,默认处理程序将转发查询或简单地取消查询。

6. 什么是状态?

状态描述元素实例是否初始化,是否准备好传输数据,以及它是否当前正在处理数据。GStreamer中定义了四种状态:

  • GST_STATE_NULL
  • GST_STATE_READY
  • GST_STATE_PAUSED
  • GST_STATE_PLAYING

从现在起,它将被简单地称为“NULL”、“READY”、“pause”和“PLAYING”。

GST_STATE_NULL 是元素的默认状态。在这种状态下,它没有分配任何运行时资源,它没有加载任何运行时库,显然无法处理数据。

GST_STATE_READY 是元素可以处于的下一个状态。在就绪状态下,元素分配了所有默认资源(运行时库、运行时内存)。但是,它尚未分配或定义任何特定于流的内容。当从NULL转到就绪状态(GST_STATE_CHANGE_NULL_TO_READY)时,元素应该分配任何非流特定资源,并应加载运行时可加载库(如果有)。在另一个方向(从 READYNULLGST_STATE_CHANGE_READY_TO_NULL)时,元素应该卸载这些库并释放所有分配的资源。这些资源的示例是硬件设备。注意,文件通常是流,因此这些文件应被视为流特定资源;因此,不应在这种状态下分配它们。

GST_STATE_PAUSED 是元素准备接受和处理数据的状态。对于大多数元素,这种状态与播放相同。此规则的唯一例外是 sink 元素。接收器元素只接受一个数据缓冲区,然后阻塞。此时管道已“预卷(prerolled)”,并准备立即呈现数据。

GST_STATE_PLAYING 是元素可以处于的最高状态。对于大多数元素,这种状态与暂停完全相同,它们接受并处理带有数据的事件和缓冲区。只有接收器元素需要区分暂停状态和播放状态。在播放状态下,接收器元素实际上呈现传入数据,例如将音频输出到声卡或将视频图片渲染到图像接收器。

7. 添加属性

控制元素行为的主要也是最重要的方法是通过 GObject 属性。GObject 属性在 _class_init () 函数中定义。元素可选地实现了一个 _get_property () 和一个 _set_property () 函数。如果应用程序更改或请求某个属性的值,则会通知这些函数,然后可以填写该值或执行该属性内部更改值所需的操作。

您可能还希望保留一个实例变量,其中包含在 get 和 set 函数中使用的属性的当前配置值。请注意,GObject 不会自动将实例变量设置为默认值,您必须在元素的 _init () 函数中执行此操作。

/* properties */
enum {
  PROP_0,
  PROP_SILENT
  /* FILL ME */
};

static void gst_my_filter_set_property  (GObject      *object,
                         guint         prop_id,
                         const GValue *value,
                         GParamSpec   *pspec);
static void gst_my_filter_get_property  (GObject      *object,
                         guint         prop_id,
                         GValue       *value,
                         GParamSpec   *pspec);

static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  /* define virtual function pointers */
  object_class->set_property = gst_my_filter_set_property;
  object_class->get_property = gst_my_filter_get_property;

  /* define properties */
  g_object_class_install_property (object_class, PROP_SILENT,
    g_param_spec_boolean ("silent", "Silent",
              "Whether to be very verbose or not",
              FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

static void
gst_my_filter_set_property (GObject      *object,
                guint         prop_id,
                const GValue *value,
                GParamSpec   *pspec)
{
  GstMyFilter *filter = GST_MY_FILTER (object);

  switch (prop_id) {
    case PROP_SILENT:
      filter->silent = g_value_get_boolean (value);
      g_print ("Silent argument was changed to %s\n",
           filter->silent ? "true" : "false");
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_my_filter_get_property (GObject    *object,
                guint       prop_id,
                GValue     *value,
                GParamSpec *pspec)
{
  GstMyFilter *filter = GST_MY_FILTER (object);

  switch (prop_id) {
    case PROP_SILENT:
      g_value_set_boolean (value, filter->silent);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

以上是如何使用属性的一个非常简单的示例。图形应用程序将使用这些属性,并将显示一个用户可控制的小部件,可以用它来更改这些属性。这意味着-为了使属性尽可能的用户友好-您应该尽可能精确地定义属性。不仅在定义有效属性可以定位的范围(对于整数、浮点等),而且在属性定义中使用非常描述性(更好的是:国际化)的字符串,如果可能的话,使用枚举和标志代替整数。GObject 文档以一种非常完整的方式描述了这些,但是下面,我们将给出一个简短的示例,说明这在哪里是有用的。请注意,在这里使用整数可能会完全迷惑用户,因为在这种情况下它们毫无意义。这个例子是从 videotestsrc 偷来的。

typedef enum {
  GST_VIDEOTESTSRC_SMPTE,
  GST_VIDEOTESTSRC_SNOW,
  GST_VIDEOTESTSRC_BLACK
} GstVideotestsrcPattern;

[..]

#define GST_TYPE_VIDEOTESTSRC_PATTERN (gst_videotestsrc_pattern_get_type ())
static GType
gst_videotestsrc_pattern_get_type (void)
{
  static GType videotestsrc_pattern_type = 0;

  if (!videotestsrc_pattern_type) {
    static GEnumValue pattern_types[] = {
      { GST_VIDEOTESTSRC_SMPTE, "SMPTE 100% color bars",    "smpte" },
      { GST_VIDEOTESTSRC_SNOW,  "Random (television snow)", "snow"  },
      { GST_VIDEOTESTSRC_BLACK, "0% Black",                 "black" },
      { 0, NULL, NULL },
    };

    videotestsrc_pattern_type =
    g_enum_register_static ("GstVideotestsrcPattern",
                pattern_types);
  }

  return videotestsrc_pattern_type;
}

[..]

static void
gst_videotestsrc_class_init (GstvideotestsrcClass *klass)
{
[..]
  g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PATTERN,
    g_param_spec_enum ("pattern", "Pattern",
               "Type of test pattern to generate",
                       GST_TYPE_VIDEOTESTSRC_PATTERN, GST_VIDEOTESTSRC_SMPTE,
                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
[..]
}

8. 信号

GObject 信号可用于通知应用程序特定于此对象的事件。但是,请注意,应用程序需要知道信号及其含义,因此如果您正在寻找应用程序元素交互的通用方法,那么信号可能不是您要寻找的。然而,在许多情况下,信号可能非常有用。有关信号的所有内部信息,请参阅 GObject 文档。

9. 构建测试应用程序

通常,您会希望在尽可能小的设置中测试新编写的插件。通常,gst-launch-1.0 是测试插件的第一步。如果您没有在 GStreamer 搜索的目录中安装插件,那么您需要设置插件路径。将 GST_PLUGIN_PATH 设置为包含插件的目录,或者使用命令行选项 ---gst-plugin-path 。如果你的插件是基于 gst-plugin 模板的,那么这看起来像 gst-launch-1.0 --gst-plugin-path=$HOME/gst-template/gst-plugin/src/.libs TESTPIPELINE,然而,你通常需要比 gst-launch-1.0 更多的测试特性,比如查找、事件、交互性等等。编写自己的小型测试程序是实现这一点的最简单方法。本节用几句话解释了如何做到这一点。有关完整的应用程序开发指南,请参阅应用程序开发手册。

首先,需要通过调用 gst_init () 初始化 GStreamer 核心库。您也可以调用 gst_init_get_option_group (),它将返回一个指向 GOptionGroup 的指针。然后可以使用 GOption 来处理初始化,这将完成 GStreamer 初始化。

可以使用 gst_element_factory_make () 创建元素,其中第一个参数是要创建的元素类型,第二个参数是自由格式名称。最后的示例使用了一个简单的 filesource - decoder - soundcard 输出管道,但是如果需要,可以使用特定的调试元素。例如,可以在管道的中间使用一个标识元素作为应用程序发送器的数据。这可用于检查测试应用程序中的错误行为或正确性数据。此外,还可以使用管道末尾的 fakesink 元素将数据转储到 stdout(为此,请将 dump 属性设置为TRUE)。最后,可以使用 valgrind 检查内存错误。

在链接过程中,您的测试应用程序可以使用过滤的 caps 作为一种方式来驱动特定类型的数据进出您的元素。这是检查元素中多种类型的输入和输出的一种非常简单有效的方法。

请注意,在运行过程中,您至少应该侦听总线和/或插件/元素上的 “error” 和 “eos” 消息,以检查是否对此进行了正确处理。此外,您应该将事件添加到管道中,并确保您的插件正确处理这些事件(关于时钟、内部缓存等)。

永远不要忘记清理插件或测试应用程序中的内存。当进入空状态时,元素应该清理分配的内存和缓存。同时,它应该关闭所有可能的支持库的引用。您的应用程序应该 unref () 管道,并确保它不会崩溃。

#include <gst/gst.h>

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

  switch (GST_MESSAGE_TYPE (msg)) {
    case GST_MESSAGE_EOS:
      g_print ("End-of-stream\n");
      g_main_loop_quit (loop);
      break;
    case GST_MESSAGE_ERROR: {
      gchar *debug = NULL;
      GError *err = NULL;

      gst_message_parse_error (msg, &err, &debug);

      g_print ("Error: %s\n", err->message);
      g_error_free (err);

      if (debug) {
        g_print ("Debug details: %s\n", debug);
        g_free (debug);
      }

      g_main_loop_quit (loop);
      break;
    }
    default:
      break;
  }

  return TRUE;
}

gint
main (gint   argc,
      gchar *argv[])
{
  GstStateChangeReturn ret;
  GstElement *pipeline, *filesrc, *decoder, *filter, *sink;
  GstElement *convert1, *convert2, *resample;
  GMainLoop *loop;
  GstBus *bus;
  guint watch_id;

  /* initialization */
  gst_init (&argc, &argv);
  loop = g_main_loop_new (NULL, FALSE);
  if (argc != 2) {
    g_print ("Usage: %s <mp3 filename>\n", argv[0]);
    return 01;
  }

  /* create elements */
  pipeline = gst_pipeline_new ("my_pipeline");

  /* watch for messages on the pipeline's bus (note that this will only
   * work like this when a GLib main loop is running) */
  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  watch_id = gst_bus_add_watch (bus, bus_call, loop);
  gst_object_unref (bus);

  filesrc  = gst_element_factory_make ("filesrc", "my_filesource");
  decoder  = gst_element_factory_make ("mad", "my_decoder");

  /* putting an audioconvert element here to convert the output of the
   * decoder into a format that my_filter can handle (we are assuming it
   * will handle any sample rate here though) */
  convert1 = gst_element_factory_make ("audioconvert", "audioconvert1");

  /* use "identity" here for a filter that does nothing */
  filter   = gst_element_factory_make ("my_filter", "my_filter");

  /* there should always be audioconvert and audioresample elements before
   * the audio sink, since the capabilities of the audio sink usually vary
   * depending on the environment (output used, sound card, driver etc.) */
  convert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
  resample = gst_element_factory_make ("audioresample", "audioresample");
  sink     = gst_element_factory_make ("pulsesink", "audiosink");

  if (!sink || !decoder) {
    g_print ("Decoder or output could not be found - check your install\n");
    return -1;
  } else if (!convert1 || !convert2 || !resample) {
    g_print ("Could not create audioconvert or audioresample element, "
             "check your installation\n");
    return -1;
  } else if (!filter) {
    g_print ("Your self-written filter could not be found. Make sure it "
             "is installed correctly in $(libdir)/gstreamer-1.0/ or "
             "~/.gstreamer-1.0/plugins/ and that gst-inspect-1.0 lists it. "
             "If it doesn't, check with 'GST_DEBUG=*:2 gst-inspect-1.0' for "
             "the reason why it is not being loaded.");
    return -1;
  }

  g_object_set (G_OBJECT (filesrc), "location", argv[1], NULL);

  gst_bin_add_many (GST_BIN (pipeline), filesrc, decoder, convert1, filter,
                    convert2, resample, sink, NULL);

  /* link everything together */
  if (!gst_element_link_many (filesrc, decoder, convert1, filter, convert2,
                              resample, sink, NULL)) {
    g_print ("Failed to link one or more elements!\n");
    return -1;
  }

  /* run */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    GstMessage *msg;

    g_print ("Failed to start up pipeline!\n");

    /* check if there is an error message with details on the bus */
    msg = gst_bus_poll (bus, GST_MESSAGE_ERROR, 0);
    if (msg) {
      GError *err = NULL;

      gst_message_parse_error (msg, &err, NULL);
      g_print ("ERROR: %s\n", err->message);
      g_error_free (err);
      gst_message_unref (msg);
    }
    return -1;
  }

  g_main_loop_run (loop);

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

  return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许野平

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值