文章

gstreamer插件编写指南:构建模板

在本章中,你将学习如何为一个新插件构建最基本的代码。从零开始,你将了解如何获取 GStreamer 模板源代码。然后,你将学习如何使用一些基本工具来复制和修改模板插件,以创建一个新插件。如果你能按照这里的示例进行操作,那么在本章结束时,你将拥有一个可以编译并在 GStreamer 应用程序中使用的功能性音频过滤器插件。

获取 GStreamer 插件模板

目前有两种方法为 GStreamer 开发新插件:一种是手工编写整个插件,另一种是复制现有插件模板,然后编写所需的插件代码。第二种方法是迄今为止最简单的方法,所以这里就不介绍第一种方法了。(呃,也就是 “留给读者练习”)。

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

1
2
3
4
5
6
7
$ git clone https://gitlab.freedesktop.org/gstreamer/gst-template.git
Initialized empty Git repository in /some/path/gst-template/.git/
remote: Counting objects: 373, done.
remote: Compressing objects: 100% (114/114), done.
remote: Total 373 (delta 240), reused 373 (delta 240)
Receiving objects: 100% (373/373), 75.16 KiB | 78 KiB/s, done.
Resolving deltas: 100% (240/240), done.

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

如果由于某种原因无法访问 git 仓库,也可以通过 gitlab 网页界面下载最新版本的快照

使用项目模板

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

定义模板的标准方法是编写一些代码并填充一些结构。如上一节所述,最简单的方法是复制一个模板,然后根据自己的需要添加功能。在./gst-plugin/tools/目录中,有一个工具可以帮助你完成这项工作。这个名为make_element 的工具是一个命令行工具,可以为你创建模板代码。

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

  1. 插件的名称
  2. 工具将使用的源文件。默认使用gstplugin

例如,以下命令根据插件模板创建 MyFilter 插件,并将输出文件放到gst-template/gst-plugin/src目录中:

1
2
cd gst-template/gst-plugin/src
../tools/make_element MyFilter

备注 大写对于插件名称很重要。请记住,在某些操作系统下,一般指定目录和文件名时,大写也很重要。

最后一条命令会创建两个文件:gstmyfilter.cgstmyfilter.h

备注 建议您在继续之前创建一份gst-plugin目录副本。

现在,我们需要在父目录下运行meson build来引导构建环境。之后,就可以使用众所周知的ninja -C build命令来构建和安装项目了。

备注 请注意,默认情况下,meson会选择/usr/local作为默认位置。我们需要在GST_PLUGIN_PATH环境变量中添加/usr/local/lib/gstreamer-1.0,这样新插件才能在从软件包安装的 gstreamer 中显示出来。

备注 gst-template 作为最小插件构建系统骨架的范例仍然有用。不过,现在推荐使用 gst-plugins-bad 中的 gst-element-maker 工具来创建元素。

检查基本代码

首先,我们将检查您可能会放在头文件中的代码(不过,由于代码的接口完全由插件系统定义,并不依赖于读取头文件,所以这一点并不重要)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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)

使用该头文件,您可以使用以下宏来设置源文件中的基本Element,以便所有的函数被正确调用:

1
2
3
4
#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_DEFINEGST_ELEMENT_REGISTER_DECLARE结合使用,可通过调用GST_ELEMENT_REGISTER (my_filter)从插件内部或其他插件/应用程序注册元素。

元素元数据

元素元数据提供额外的元素信息。元数据可通过gst_element_class_set_metadatagst_element_class_set_static_metadata进行配置:

  • 元素的英文长名。

  • 元素的类型,详情和示例请参阅 GStreamer 核心源代码树中的 docs/additional/design/draft-klass.txt 文档。

  • 简要说明元素的用途。

  • 元素作者的姓名,可选择在其后用角括弧标出电子邮件地址。

例如:

1
2
3
4
5
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 ()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
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>");

}

GstStaticPadTemplate

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

  • 衬底的简称。

  • 衬底的方向

  • 存在属性。这表明该衬底是否始终存在(an always pad),或仅在某些情况下存在(a sometimes pad),或仅在应用程序请求时存在(a request pad)。

  • 该元素能支持的类型。

例如:

1
2
3
4
5
6
7
static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
  "sink",
  GST_PAD_SINK,
  GST_PAD_ALWAYS,
  GST_STATIC_CAPS ("ANY")
);

这些 pad 模板是在_class_init ()函数中使用gst_element_class_add_pad_template () 注册的。为此,您需要一个GstPadTemplate句柄,您可以使用gst_static_pad_template_get () 从静态 pad 模板创建该句柄。有关详情,请参阅下文。

在元素的_init () 函数中,使用gst_pad_new_from_static_template () 从这些静态模板创建衬底。要使用gst_pad_new_from_static_template ()从该模板创建新衬底,需要将衬底模板声明为全局变量。有关这方面的更多信息,请参阅指定衬底。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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));
}

模板中的最后一个参数是其类型或支持的类型列表。 在本例中,我们使用了 “ANY”,这意味着该元素将接受所有输入。在现实生活中,你会设置一个媒体类型,并选择性地设置一组属性,以确保只有受支持的输入才会被输入。这种表示法应该是一个以媒体类型开头的字符串,然后是一组用逗号分隔的属性及其支持值。如果音频过滤器支持任何采样率的原始整数 16 位音频、单声道或立体声,正确的模板应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
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 ]"
  )
);

用大括号(“{”和“}”)包围的值是列表,用方括号(“[”和“]”)包围的值是范围。也支持多组类型,并用分号(“;”)分隔。稍后,我们将在“pads”一章中了解如何使用类型来确定数据流的确切格式:指定衬底。

构造函数

每个元素都有两个用于构建元素的函数。_class_init()函数只用于对类进行一次初始化(指定类的信号、参数和虚拟函数,并设置全局状态);_init()函数用于初始化该类型的一个特定实例。

插件初始化函数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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",
  "https://gstreamer.net/"
)

请注意,plugin_init() 函数返回的信息将缓存在中央注册表中。因此,该函数必须始终返回相同的信息:例如,它不能根据运行时的条件使元素工厂可用。 如果一个元素只能在某些条件下工作(例如,如果声卡没有被其他进程使用),这必须反映在元素不可用时无法进入 READY 状态,而不是插件试图否认插件的存在。

本文由作者按照 CC BY 4.0 进行授权