编写一个GStreamer插件

20 篇文章 8 订阅

1. 构建样板文件

在本章中,您将学习如何为一个新插件构造最简单的代码。从零开始,您将看到如何获取GStreamer模板源代码。然后您将学习如何使用一些基本工具来复制和修改模板插件以创建新插件。如果您遵循这里的示例,那么在本章结束时,您将拥有一个功能强大的音频过滤器插件,可以在GStreamer应用程序中编译和使用。

1.1 获取GStreamer插件模板

目前有两种方法可以为GStreamer开发新的插件:您可以手工编写整个插件,或者您可以复制现有的插件模板并编写所需的插件代码。第二种方法是两种方法中最简单的一种,因此第一种方法在这里甚至不作描述(嗯,也就是说,“这是留给读者的练习。”)

第一步是签出 gst-template 的 git模块的副本,以获得一个重要的工具和基本GStreamer插件的源代码模板。要签出gst-template模块,请确保已连接到internet,并在命令控制台键入以下命令:

lucky@ubuntu:~/temp/gstreamer$  git clone https://gitlab.freedesktop.org/gstreamer/gst-template.git
Cloning into 'gst-template'...
remote: Enumerating objects: 549, done.
remote: Total 549 (delta 0), reused 0 (delta 0), pack-reused 549
Receiving objects: 100% (549/549), 112.11 KiB | 11.00 KiB/s, done.
Resolving deltas: 100% (356/356), done.

这个命令将把一系列文件和目录签出到 gst-template 中。您将使用的模板位于gst-template/gst-plugin/目录中。您应该查看该目录中的文件,以大致了解插件的源代码树的结构。

如果由于某种原因无法访问git存储库,还可以通过gitlab web界面下载最新版本的快照。

1.2 使用项目 Stamp

1.2.1 创建样板代码

创建新元素时要做的第一件事是指定有关它的一些基本细节:它的名称、编写者、版本号等。我们还需要定义一个对象来表示元素并存储元素所需的数据。这些细节统称为样板。

定义样板文件的标准方法只是编写一些代码,并填充一些结构。如前一节所述,最简单的方法是复制模板并根据需要添加功能。为了帮助您这样做,./gst-plugin/tools/目录中有一个工具。这个工具make_ element是一个命令行实用程序,它为您创建样板代码。

要使用make_element,首先打开一个终端窗口。切换到gst-template/gst-plugin/src目录,然后运行make_element命令。make_element的参数是:

插件的名称,以及工具将使用的源文件。默认情况下,使用 gstplugin。
例如,以下命令基于插件模板创建MyFilter插件,并将输出文件放在gst-template/gst-plugin/src目录中:

shell $ cd gst-template/gst-plugin/src
shell $ ../tools/make_element MyFilter

注意
大写对于插件的名称很重要。请记住,在某些操作系统中,通常在指定目录名和文件名时,大写也很重要。

最后一个命令创建两个文件:gstmyfilter.c 和 gstmyfilter.h。

注意
建议您在继续之前创建 gst-plugin 目录的副本。

1.3 检查基本代码

首先,我们将检查您可能放置在头文件中的代码(尽管由于代码的接口完全由插件系统定义,并且不依赖于读取头文件,这并不重要)

#include <gst/gst.h>
 
/* Definition of structure storing data for this element. */
typedef struct _GstMyFilter {
  GstElement element;
 
  GstPad *sinkpad, *srcpad;
 
  gboolean silent;
 
 
 
} GstMyFilter;
 
/* Standard definition defining a class for this element. */
typedef struct _GstMyFilterClass {
  GstElementClass parent_class;
} GstMyFilterClass;
 
/* Standard macros for defining types for this element.  */
#define GST_TYPE_MY_FILTER (gst_my_filter_get_type())
#define GST_MY_FILTER(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MY_FILTER,GstMyFilter))
#define GST_MY_FILTER_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MY_FILTER,GstMyFilterClass))
#define GST_IS_MY_FILTER(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MY_FILTER))
#define GST_IS_MY_FILTER_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MY_FILTER))
 
/* Standard function returning type information. */
GType gst_my_filter_get_type (void);
 
GST_ELEMENT_REGISTER_DECLARE(my_filter)

使用此头文件,您可以使用以下宏来设置源文件中的元素基础,以便适当地调用所有函数:

#include "filter.h"
 
G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);
GST_ELEMENT_REGISTER_DEFINE(my_filter, "my-filter", GST_RANK_NONE, GST_TYPE_MY_FILTER);

宏GST_ELEMENT_REGISTER_DEFINE与GST_ELEMENT_REGISTER_DECLARE结合使用,可以通过调用GST_ELEMENT_REGISTER (my_filter)从插件内或任何其他插件/应用程序注册元素。

1.4 元素元数据

元素元数据提供额外的元素信息。它配置有gst_element_class_set_metadata或gst_element_class_set_static_metadata,其参数如下:

  • 元素的英文长名称。
  • 元素的类型,请参阅GStreamer核心源代码树中的docs/additional/design/draft-klass.txt文档以获取详细信息和示例。
  • 元素用途的简要说明。
  • 元素的作者的名称,后跟尖括号中的联系人电子邮件地址(可选)。

例如:

gst_element_class_set_static_metadata (klass,
  "An example plugin",
  "Example/FirstExample",
  "Shows the basic structure of a plugin",
  "your name <your.name@your.isp>");

元素详细信息是在_class_init()函数中注册的,它是GObject系统的一部分。应该在使用glib注册类型的函数中为这个GObject设置_class_init()函数。

static void
gst_my_filter_class_init (GstMyFilterClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
 
[..]
  gst_element_class_set_static_metadata (element_class,
    "An example plugin",
    "Example/FirstExample",
    "Shows the basic structure of a plugin",
    "your name <your.name@your.isp>");
 
}

1.5 GstStaticPadTemplate

GstStaticPadTemplate 是元素将(或可能)创建和使用的 pad 的描述。它包含:

  • Pad 的简称。
  • Pad 方向。
  • 存在属性。这表示 pad 是否总是存在(an “always” pad)、仅在某些情况下存在(a “sometimes” pad)还是仅在应用程序请求此类pad(a “request” pad)时存在。
  • 此元素支持的类型(功能)。

例如:

static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
  "sink",
  GST_PAD_SINK,
  GST_PAD_ALWAYS,
  GST_STATIC_CAPS ("ANY")
);

这些pad模板在使用 gst_element_class_add_pad_template() 的 _class_init () 函数期间注册。对于此函数,您需要 GstPadTemplate 的句柄,您可以使用 gst_static_pad_template_get() 从静态 pad 模板创建该句柄。详见下文。

pad 是使用 gst_pad_new_from_static_template() 从元素的 _init() 函数中的这些静态模板创建的。为了使用 gst_pad_new_from_static_template() 从这个模板创建一个新的 pad,您需要将 pad 模板声明为一个全局变量。更多关于这个主题在Specifying the pads.。

static GstStaticPadTemplate sink_factory = [..],
    src_factory = [..];
 
static void
gst_my_filter_class_init (GstMyFilterClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
[..]
 
  gst_element_class_add_pad_template (element_class,
    gst_static_pad_template_get (&src_factory));
  gst_element_class_add_pad_template (element_class,
    gst_static_pad_template_get (&sink_factory));
}

最后再讨论一下template的类型或者说它支持的类型 ,在上面的示例中我们使用了ANY,意味着改template可以接收所有类型的输入,在实际使用场景中,你应该设置媒体类型和一些可选的属性来确认template的输入类型。表示应该是一个以媒体类型开头的字符串,然后一组逗号-将属性与其支持的值分开。如果音频过滤器支持任何采样时的原始整数16位音频、单声道或立体声,则模板看起来如下:

static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
  "sink",
  GST_PAD_SINK,
  GST_PAD_ALWAYS,
  GST_STATIC_CAPS (
    "audio/x-raw, "
      "format = (string) " GST_AUDIO_NE (S16) ", "
      "channels = (int) { 1, 2 }, "
      "rate = (int) [ 8000, 96000 ]"
  )
);

被花括号(“{” and “}”) 包围的值是列表,被方括号(“[” and “]”)所包围的值是范围。 多个类型集也是受支持的,应该用分号(“;”)分隔。在pad一章的最后一节中,我们将看到如何使用类型来知道流的确切格式:Specifying the pads.

1.6 构造函数

每个元素有两个用于构造元素的函数。_class_init() 函数,仅用于初始化类一次(指定类具有哪些信号、参数和虚拟函数,并设置全局状态);以及 _init() 函数,用于初始化此类型的特定实例。

1.6.1 plugin_init 函数

一旦我们编写了定义插件所有部分的代码,我们就需要编写 plugin_init() 函数。这是一个特殊的函数,在加载插件后立即调用,并根据是否正确加载了任何依赖项返回 TRUE 或 FALSE。另外,在这个函数中,插件中任何支持的元素类型都应该被注册。

static gboolean
plugin_init (GstPlugin *plugin)
{
  return GST_ELEMENT_REGISTER (my_filter, plugin);
}
 
GST_PLUGIN_DEFINE (
  GST_VERSION_MAJOR,
  GST_VERSION_MINOR,
  my_filter,
  "My filter plugin",
  plugin_init,
  VERSION,
  "LGPL",
  "GStreamer",
  "http://gstreamer.net/"
)

请注意,plugin_init返回的信息将被缓存在central registry中。由于这个原因,同样的信息总是由函数返回是很重要的:例如,它不能使元素工厂基于运行时代码而变得不可执行。如果一个元素只能在某些条件下工作(例如,如果声卡没有被其他进程使用),这必须反映在元素无法进入就绪状态(如果不可用的话),而不是试图否认插件存在的插件。 

2. 指定 pads

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

在元素的 _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 pads和source pads上接收它们。

下面是一个非常简单的事件函数,我们将它安装在元素的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 函数,元素将接收它必须回复的查询。这些查询包括位置、持续时间以及元素支持的格式和调度模式。查询可以向上游和下游移动,因此您可以在sink pads和source pads上接收它们。

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

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)时,元素应该分配任何非流特定资源,并应加载运行时可加载库(如果有)。在另一个方向(从 READY 到 NULL、GST_STATE_CHANGE_READY_TO_NULL)时,元素应该卸载这些库并释放所有分配的资源。这些资源的示例是硬件设备。注意,文件通常是流,因此这些文件应被视为流特定资源;因此,不应在这种状态下分配它们。

GST_STATE_PAUSED 是元素准备接受和处理数据的状态。对于大多数元素,这种状态与播放相同。此规则的唯一例外是 sink 元素。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 documentation 。

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;
}



原文链接:https://blog.csdn.net/zhangkun_share/article/details/125521871

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值