在本章中,你将学习如何为一个新插件构建最少的代码。从零开始,您将看到如何获得GStreamer模板源。然后,您将学习如何使用一些基本工具来复制和修改模板插件以创建新插件。如果你遵循这里的例子,那么在本章结束时,你将有一个功能音频过滤器插件,你可以在GStreamer应用程序中编译和使用。
目前有两种方法可以为GStreamer开发一个新的插件:你可以手工编写整个插件,或者你可以复制一个现有的插件模板并编写你需要的插件代码。第二种方法是这两种方法中最简单的,所以第一种方法在这里就不再介绍了。(嗯,也就是说,“留给读者作为练习。”)
第一步是从git上拉取gst-template模块的副本,以获得一个重要的工具和基本GStreamer插件的源代码模板。要拉取gst-template模块,请确保您已连接到internet,并在命令控制台中键入以下命令:
shell $ 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 web界面下载最新版本的快照。
创建新元素时要做的第一件事是指定它的一些基本细节:它的名字是什么,谁写的,它的版本号是什么,等等。我们还需要定义一个对象来表示元素并存储元素所需的数据。这些细节统称为样板文件。
定义样板文件的标准方法是简单地编写一些代码,并填充一些结构。如前一节所述,最简单的方法是复制模板并根据需要添加功能。为了帮助你做到这一点,在./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
Note:大写对于插件的名称很重要。请记住,在某些操作系统下,通常在指定目录和文件名时,大写也很重要。
最后一个命令创建两个文件:gstmyfilter.c和gstmyfilter.h。
Note:建议您在继续之前创建一个gst-plugin目录的副本。
现在需要从父目录运行meson build来引导构建环境。之后,可以使用众所周知的ninja -C build命令来构建和安装项目。
Note:请注意,默认情况下,meson将选择/usr/local作为默认位置。需要将/usr/local/lib/gstreamer-1.0添加到GST_PLUGIN_PATH,以便使新插件显示在从包中安装的gstreamer中。
Note:修正:这个部分有点过时。Gst-template作为一个最小插件构建系统骨架的例子仍然很有用。不过,现在推荐使用gst-plugins-bad中的gst-element-maker工具来创建元素。
首先,我们将检查您可能放置在头文件中的代码(尽管由于代码的接口完全由插件系统定义,并且不依赖于读取头文件,因此这不是至关重要的)。
#include
/* 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)从插件或任何其他插件/应用程序中注册元素。
Element元数据提供额外的元素信息。它由gst_element_class_set_metadata或gst_element_class_set_static_metadata配置,包含以下参数:
- 元素的长英文名称。
- 元素的类型,请参阅GStreamer核心源代码树中的docs/additional/design/draft- class .txt文档,了解详细信息和示例。
- 元素用途的简要描述。
- 元素作者的姓名,可选地后跟尖括号中的联系人电子邮件地址。
例如:
gst_element_class_set_static_metadata (klass,
"An example plugin",
"Example/FirstExample",
"Shows the basic structure of a plugin",
"your name ");
元素的详细信息在_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 ");
}
GstStaticPadTemplate是元素将要(或可能)创建和使用的pad的描述。它包含:
- pad的简称。
- pad的方向。
- 存在的属性。这表明该pad是始终存在(“always”pad),还是仅在某些情况下存在(“sometimes”pad),或者仅在应用程序请求此类垫时存在(“request”pad)。
- 该元素支持的类型(能力)。
tips:pad可翻译为垫/衬垫
例如:
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模板创建该句柄。有关这方面的更多细节,请参阅下文。
pad是在元素的_init()函数中使用gst_pad_new_from_static_template()从这些静态模板创建的。为了使用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));
}
模板中的最后一个参数是它的类型或支持的类型列表。在本例中,我们使用’ANY’,这意味着该元素将接受所有输入。在实际情况中,您需要设置媒体类型和可选的一组属性,以确保只输入受支持的输入。这种表示应该是一个以媒体类型开头的字符串,然后是一组以逗号分隔的属性及其支持的值。如果音频过滤器在任何采样点都支持原始整数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 ]"
)
);
用大括号(“{”和“}”)括起来的值是列表,用方括号(“[”和“]”)括起来的值是范围。也支持多组类型,并且应该用分号(“;”)分隔。稍后,在关于pads的章节中,我们将看到如何使用类型来了解流的确切格式:Specifying the pads
每个元素都有两个用于构造元素的函数。_class_init()函数,仅用于初始化类一次(指定类具有哪些信号、参数和虚函数,并设置全局状态);以及_init()函数,该函数用于初始化此类型的特定实例。
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()函数返回的信息将缓存在中央注册表中。由于这个原因,函数总是返回相同的信息是很重要的:例如,它不能根据运行时条件使元素工厂可用。如果一个元素只能在某些条件下工作(例如,如果声卡没有被其他进程使用),这必须反映为元素无法进入READY状态,而不是插件试图否认插件的存在。