索引:https://blog.csdn.net/knowledgebao/article/details/84621238
文档教程:https://gstreamer.freedesktop.org/documentation/plugin-development/basics/index.html
目录
Writing a Plugin
Constructing the Boilerplate
Getting the GStreamer Plugin Templates
Using the Project Stamp
Examining the Basic Code
Element metadata
GstStaticPadTemplate
Constructor Functions
The plugin_init function
Specifying the pads
The chain function
The event function
The query function
What are states?
Managing filter state
Adding Properties
Signals
Building a Test Application
您现在已准备好学习如何构建插件。在本指南的这一部分中,您将学习如何应用基本的GStreamer编程概念来编写一个简单的插件。本指南的前几部分没有包含明确的示例代码,可能使事情有点抽象,难以理解。相比之下,本节将通过开发名为“MyFilter”的示例音频过滤器插件来呈现应用程序和代码。
示例滤波器元件将以单个输入板和单个输出板开始。首先,滤波器将简单地将媒体和事件数据从其sink pad传递到其src pad而无需修改。但是在本指南的这一部分结束时,您将学习添加一些更有趣的功能,包括属性和信号处理程序。阅读本指南的下一部分“ 高级过滤器概念”后,您将能够为插件添加更多功能。
You are now ready to learn how to build a plugin. In this part of the guide, you will learn how to apply basic GStreamer programming concepts to write a simple plugin. The previous parts of the guide have contained no explicit example code, perhaps making things a bit abstract and difficult to understand. In contrast, this section will present both applications and code by following the development of an example audio filter plugin called “MyFilter”.
The example filter element will begin with a single input pad and a single output pad. The filter will, at first, simply pass media and event data from its sink pad to its source pad without modification. But by the end of this part of the guide, you will learn to add some more interesting functionality, including properties and signal handlers. And after reading the next part of the guide, Advanced Filter Concepts, you will be able to add even more functionality
在本章中,您将学习如何构建新插件的最小代码。从零开始,您将看到如何获取GStreamer模板源。然后,您将学习如何使用一些基本工具来复制和修改模板插件以创建新插件。如果您按照此处的示例进行操作,那么在本章结尾处您将获得一个功能音频过滤器插件,您可以在GStreamer应用程序中编译和使用它。
In this chapter you will learn how to construct the bare minimum code for a new plugin. Starting from ground zero, you will see how to get the GStreamer template source. Then you will learn how to use a few basic tools to copy and modify a template plugin to create a new plugin. If you follow the examples here, then by the end of this chapter you will have a functional audio filter plugin that you can compile and use in GStreamer applications.
目前有两种方法可以为GStreamer开发新的插件:您可以手动编写整个插件,也可以复制现有的插件模板并编写所需的插件代码。到目前为止,第二种方法比较简单,因此这里不会描述第一种方法。(呃,就是说,“它留给了读者一个练习。”)
第一步是查看gst-template
git模块的副本,以获得一个重要的工具和基本GStreamer插件的源代码模板。要获取该gst-template
模块,请确保已连接到Internet,并在命令控制台中键入以下命令:
There are currently two ways to develop a new plugin for GStreamer: You can write the entire plugin by hand, or you can copy an existing plugin template and write the plugin code you need. The second method is by far the simpler of the two, so the first method will not even be described here. (Errm, that is, “it is left as an exercise to the reader.”)
The first step is to check out a copy of the gst-template
git module to get an important tool and the source code template for a basic GStreamer plugin. To check out the gst-template
module, make sure you are connected to the internet, and type the following commands at a command console:
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界面下载最新版本的快照。
This command will check out a series of files and directories into gst-template
. The template you will be using is in the gst-template/gst-plugin/
directory. You should look over the files in that directory to get a general idea of the structure of a source tree for a plugin.
If for some reason you can't access the git repository, you can also download a snapshot of the latest revision via the gitlab web interface.
制作新元素时要做的第一件事是指定一些关于它的基本细节:它的名字是什么,是谁编写的,它是什么版本号等等。我们还需要定义一个对象来表示元素和存储元素需要的数据。这些细节统称为样板。
定义样板的标准方法只是编写一些代码,并填写一些结构。如上一节所述,最简单的方法是复制模板并根据需要添加功能。为了帮助您这样做,./gst-plugin/tools/
目录中有一个工具 。此工具make_element
是一个命令行实用程序,可为您创建样板代码。
要使用make_element
,首先打开一个终端窗口。切换到 gst-template/gst-plugin/src
目录,然后运行该make_element
命令。make_element的参数如下
:
插件的名称
该工具将使用的源文件。默认情况下,gstplugin
使用。
例如,以下命令基于插件模板创建MyFilter插件,并将输出文件放在 gst-template/gst-plugin/src
目录中:
The first thing to do when making a new element is to specify some basic details about it: what its name is, who wrote it, what version number it is, etc. We also need to define an object to represent the element and to store the data the element needs. These details are collectively known as the boilerplate.
The standard way of defining the boilerplate is simply to write some code, and fill in some structures. As mentioned in the previous section, the easiest way to do this is to copy a template and add functionality according to your needs. To help you do so, there is a tool in the ./gst-plugin/tools/
directory. This tool, make_element
, is a command line utility that creates the boilerplate code for you.
To use make_element
, first open up a terminal window. Change to the gst-template/gst-plugin/src
directory, and then run the make_element
command. The arguments to the make_element
are:
the name of the plugin, and
the source file that the tool will use. By default, gstplugin
is used.
For example, the following commands create the MyFilter plugin based on the plugin template and put the output files in the gst-template/gst-plugin/src
directory:
shell $ cd gst-template/gst-plugin/src
shell $ ../tools/make_element MyFilter
Note
大写对于插件的名称很重要。请记住,在某些操作系统下,通常在指定目录和文件名时,大小写也很重要。
Capitalization is important for the name of the plugin. Keep in mind that under some operating systems, capitalization is also important when specifying directory and file names in general.
最后一个命令创建两个文件:gstmyfilter.c
和gstmyfilter.h
。
The last command creates two files: gstmyfilter.c
and gstmyfilter.h
.
Note
建议您
gst-plugin
在继续之前创建目录的副本It is recommended that you create a copy of the
gst-plugin
directory before continuing.
现在需要调整Makefile.am
以使用新文件名并且从父目录运行autogen.sh
以引导构建环境。之后,可以使用众所周知的make && sudo make install
命令构建和安装项目。
Now one needs to adjust the Makefile.am
to use the new filenames and run autogen.sh
from the parent directory to bootstrap the build environment. After that, the project can be built and installed using the well known make && sudo make install
commands.
Note
要知道,在默认情况下
autogen.sh
,并configure
会选择/usr/local
作为默认位置。为了使新的插件可以在已安装的GStreamer中使用,需要添加/usr/local/lib/gstreamer-1.0
到GST_PLUGIN_PATH
。Be aware that by default
autogen.sh
andconfigure
would choose/usr/local
as a default location. One would need to add/usr/local/lib/gstreamer-1.0
toGST_PLUGIN_PATH
in order to make the new plugin show up in a gstreamer that's been installed from packages.
Note
FIXME:这部分略显过时。gst-template仍然可用作最小插件构建系统框架的示例。但是,为了创建元素,最近推荐使用gst-plugins-bad的工具gst-element-maker。
FIXME: this section is slightly outdated. gst-template is still useful as an example for a minimal plugin build system skeleton. However, for creating elements the tool gst-element-maker from gst-plugins-bad is recommended these days.
首先,我们将检查您可能放在头文件中的代码(尽管由于代码的接口完全由插件系统定义,并且不依赖于读取头文件,因此这并不重要。)
First we will examine the code you would be likely to place in a header file (although since the interface to the code is entirely defined by the plugin system, and doesn't depend on reading a header file, this is not crucial.)
#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);
使用此头文件,您可以使用以下宏来设置GObject
源文件中的 基础知识,以便适当地调用所有函数:
Using this header file, you can use the following macro to setup the GObject
basics in your source file so that all functions will be called appropriately:
#include "filter.h"
G_DEFINE_TYPE (GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT);
元素元数据提供额外的元素信息。它配置有gst_element_class_set_metadata
或 带有gst_element_class_set_static_metadata
以下参数:
例如:
The Element metadata provides extra element information. It is configured with gst_element_class_set_metadata
or gst_element_class_set_static_metadata
which takes the following parameters:
For example:
gst_element_class_set_static_metadata (klass,
"An example plugin",
"Example/FirstExample",
"Shows the basic structure of a plugin",
"your name ");
在_class_init ()
函数期间,元素详细信息在插件中注册,该 函数是GObject系统的一部分。该 _class_init ()
功能应该为此GObject的在你注册GLib的类型的功能进行设置。
The element details are registered with the plugin during the _class_init ()
function, which is part of the GObject system. The _class_init ()
function should be set for this GObject in the function where you register the type with GLib.
static void
gst_my_filter_class_init (GstMyFilterClass * klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
[..]
gst_element_class_set_static_metadata (element_klass,
"An example plugin",
"Example/FirstExample",
"Shows the basic structure of a plugin",
"your name ");
}
GstStaticPadTemplate是pad的描述,被element创建和使用。它包含:
- pad的简短名称。
- pad方向。
- 存在属性。这表示垫是否总是存在(“始终”垫),或仅在某些情况下(“有时”垫)或仅在应用请求这样的垫(“请求”垫)时。
- 此元素支持的类型(功能)。
例如:
A GstStaticPadTemplate is a description of a pad that the element will (or might) create and use. It contains:
For example:
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模板。有关详细信息,请参见下文。
Pads通过函数 gst_pad_new_from_static_template ()
使用这些静态模板在_init ()
中创建。使用gst_pad_new_from_static_template ()可以从
此模板中创建新的pad,您需要将pad模板声明为全局变量。有关此主题的更多信息,请参阅指定pad。
Those pad templates are registered during the _class_init ()
function with the gst_element_class_add_pad_template ()
. For this function you need a handle the GstPadTemplate
which you can create from the static pad template with gst_static_pad_template_get ()
. See below for more details on this.
Pads are created from these static templates in the element's _init ()
function using gst_pad_new_from_static_template ()
. In order to create a new pad from this template using gst_pad_new_from_static_template ()
, you will need to declare the pad template as a global variable. More on this subject in 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位音频,单声道或立体声,正确的模板将如下所示:
The last argument in a template is its type or list of supported types. In this example, we use 'ANY', which means that this element will accept all input. In real-life situations, you would set a media type and optionally a set of properties to make sure that only supported input will come in. This representation should be a string that starts with a media type, then a set of comma-separates properties with their supported values. In case of an audio filter that supports raw integer 16-bit audio, mono or stereo at any samplerate, the correct template would look like this:
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 ]"
)
);
由大括号(“{”和“}”)包围的值是列表,由方括号(“[”和“]”)包围的值是范围。也支持多组类型,并且应以分号(“;”)分隔。稍后,在有关pad的章节中,我们将看到如何使用类型来了解流的确切格式:特定打击垫。
Values surrounded by curly brackets (“{” and “}”) are lists, values surrounded by square brackets (“[” and “]”) are ranges. Multiple sets of types are supported too, and should be separated by a semicolon (“;”). Later, in the chapter on pads, we will see how to use types to know the exact format of a stream: Specifying the pads.
每个元素都有两个用于构造元素的函数。该_class_init()
函数,用于仅初始化类一次(指定类具有哪些信号,参数和虚函数以及设置全局状态); 和_init()
函数,用于初始化此类型的特定实例。
Each element has two functions which are used for construction of an element. The _class_init()
function, which is used to initialise the class only once (specifying what signals, arguments and virtual functions the class has and setting up global state); and the _init()
function, which is used to initialise a specific instance of this type.
一旦我们编写了定义插件所有部分的代码,我们就需要编写plugin_init()函数。这是一个特殊的函数,只要加载插件就会调用它,并且应该返回TRUE或FALSE,具体取决于它是否正确加载了初始化任何依赖项。此外,在此函数中,应该注册插件中任何受支持的元素类型。
Once we have written code defining all the parts of the plugin, we need to write the plugin_init() function. This is a special function, which is called as soon as the plugin is loaded, and should return TRUE or FALSE depending on whether it loaded initialized any dependencies correctly. Also, in this function, any supported element type in the plugin should be registered.
static gboolean
plugin_init (GstPlugin *plugin)
{
return gst_element_register (plugin, "my_filter",
GST_RANK_NONE,
GST_TYPE_MY_FILTER);
}
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状态的元素来反映,而不是插件试图否认存在插件
Note that the information returned by the plugin_init() function will be cached in a central registry. For this reason, it is important that the same information is always returned by the function: for example, it must not make element factories available based on runtime conditions. If an element can only work in certain conditions (for example, if the soundcard is not being used by some other process) this must be reflected by the element being unable to enter the READY state if unavailable, rather than the plugin attempting to deny existence of the plugin.
如前所述,pad是数据进出元素的端口,这使得它们成为元素创建过程中非常重要的内容。在样例代码中,我们已经看到静态填充模板如何处理使用元素类注册填充模板。在这里,我们将看到如何创建实际元素,使用 _event ()
-function配置特定格式以及如何注册函数以让数据流过元素。
在元素_init ()
函数中,您可以通过pad末班来创建pad,pan模板在函数_class_init ()中已经注册
。创建pad
后,您必须设置一个_chain ()
函数指针,该指针将接收并处理接收器上的输入数据。您也可以选择设置_event ()
函数指针和_query ()
函数指针。或者,pad也可以在循环模式下工作,这意味着它们可以自己提取数据。稍后将详细介绍此主题。之后,您必须使用元素注册pad。具体如下:
As explained before, pads are the port through which data goes in and out of your element, and that makes them a very important item in the process of element creation. In the boilerplate code, we have seen how static pad templates take care of registering pad templates with the element class. Here, we will see how to create actual elements, use an _event ()
-function to configure for a particular format and how to register functions to let data flow through the element.
In the element _init ()
function, you create the pad from the pad template that has been registered with the element class in the _class_init ()
function. After creating the pad, you have to set a _chain ()
function pointer that will receive and process the input data on the sinkpad. You can optionally also set an _event ()
function pointer and a _query ()
function pointer. Alternatively, pads can also operate in looping mode, which means that they can pull data themselves. More on this topic later. After that, you have to register the pad with the element. This happens like this:
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;
}
链函数是进行所有数据处理的函数。在简单过滤器的情况下,_chain ()
函数主要是线性函数 - 因此一个数据进入,一个数据就送出。下面是一个非常简单的链函数实现:
The chain function is the function in which all data processing takes place. In the case of a simple filter, _chain ()
functions are mostly linear functions - so for each incoming buffer, one buffer will go out, too. Below is a very simple implementation of a chain function:
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);
}
显然,上面没有多大用处。您通常会在那里处理数据,而不是打印数据。但请记住,缓冲区并不总是可写的。
在更高级的元素(执行事件处理的元素)中,您可能还需要另外指定一个事件处理函数,该函数将在发送流事件时调用(例如上限,流末尾,新段,标记,流类型等) 。
Obviously, the above doesn't do much useful. Instead of printing that the data is in, you would normally process the data there. Remember, however, that buffers are not always writeable.
In more advanced elements (the ones that do event processing), you may want to additionally specify an event handling function, which will be called when stream-events are sent (such as caps, end-of-stream, newsegment, tags, etc.).
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);
}
在某些情况下,元素也可以控制输入数据速率。在这种情况下,您可能想要编写一个所谓的基于循环的元素。源元素(仅具有src pad)也可以是基于get的元素。这些概念将在本指南的高级部分以及专门讨论src pad的部分中进行说明。
In some cases, it might be useful for an element to have control over the input data rate, too. In that case, you probably want to write a so-called loop-based element. Source elements (with only source pads) can also be get-based elements. These concepts will be explained in the advanced section of this guide, and in the section that specifically discusses source pads.
事件函数会通知您数据流中发生的特殊事件(例如上限,流末尾,新段,标记等)。事件可以在上游和下游传播,因此您可以在sink pad和src pad上接收它们。
下面是一个非常简单的事件函数,我们安装在元素的接收器垫上。
The event function notifies you of special events that happen in the datastream (such as caps, end-of-stream, newsegment, tags, etc.). Events can travel both upstream and downstream, so you can receive them on sink pads as well as source pads.
Below follows a very simple event function that we install on the sink pad of our element.
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事件默认不转发,因此我们需要在事件处理程序中自己执行此操作。
It is a good idea to call the default event handler gst_pad_event_default ()
for unknown events. Depending on the event type, the default handler will forward the event or simply unref it. The CAPS event is by default not forwarded so we need to do this in the event handler ourselves.
通过查询功能,您的元素将接收必须回复的查询。这些是诸如位置,持续时间之类的查询,但也包括元素支持的支持格式和调度模式。查询可以在上游和下游传输,因此您可以在sink pad和src pad上接收它们。
下面是一个非常简单的查询函数,我们在元素的源代码上安装它。
Through the query function, your element will receive queries that it has to reply to. These are queries like position, duration but also about the supported formats and scheduling modes your element supports. Queries can travel both upstream and downstream, so you can receive them on sink pads as well as source pads.
Below follows a very simple query function that we install on the source pad of our element.
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 ()
为未知查询调用默认查询处理程序是个好主意 。根据查询类型,默认处理程序将转发查询或只是取消它。
It is a good idea to call the default query handler gst_pad_query_default ()
for unknown queries. Depending on the query type, the default handler will forward the query or simply unref it.
状态描述元素实例是否已初始化,是否准备好传输数据以及它当前是否正在处理数据。GStreamer中定义了四种状态:
GST_STATE_NULL
GST_STATE_READY
GST_STATE_PAUSED
GST_STATE_PLAYING
从现在开始,它将简称为“NULL”,“READY”,“PAUSED”和“PLAYING”。
GST_STATE_NULL
是元素的默认状态。在这种状态下,它没有分配任何运行时资源,它没有加载任何运行时库,它显然不能处理数据。
GST_STATE_READY
是元素可以进入的下一个状态。在READY状态中,元素具有分配的所有默认资源(运行时库,运行时内存)。但是,它尚未分配或定义任何特定于流的内容。当从NULL转到READY state时,元素应该分配任何非特定于流的资源,并且应该加载运行时可加载的库(如果有的话)。当反过来时(从READY到NUL),元素应该卸载这些库并释放所有分配的资源。这种资源的示例是硬件设备。请注意,文件通常是流,因此应将其视为特定于流的资源; 因此,它们不应该在这种状态下分配。
GST_STATE_PAUSED
是元素准备接受和处理数据的状态。对于大多数元素,此状态与播放相同。此规则的唯一例外是接收器元素。接收器元素只接受一个数据缓冲区然后阻塞。此时,管道已准备好立即呈现数据。
GST_STATE_PLAYING
是元素可以处于的最高状态。对于大多数元素,此状态与PAUSED完全相同,它们接受并处理带有数据的事件和缓冲区。只有sink element需要区分PAUSED和PLAYING状态。在播放状态中,sink element实际上渲染输入数据,例如将音频输出到声卡或将视频图像渲染到显卡。
A state describes whether the element instance is initialized, whether it is ready to transfer data and whether it is currently handling data. There are four states defined in GStreamer:
GST_STATE_NULL
GST_STATE_READY
GST_STATE_PAUSED
GST_STATE_PLAYING
which will from now on be referred to simply as “NULL”, “READY”, “PAUSED” and “PLAYING”.
GST_STATE_NULL
is the default state of an element. In this state, it has not allocated any runtime resources, it has not loaded any runtime libraries and it can obviously not handle data.
GST_STATE_READY
is the next state that an element can be in. In the READY state, an element has all default resources (runtime-libraries, runtime-memory) allocated. However, it has not yet allocated or defined anything that is stream-specific. When going from NULL to READY state (GST_STATE_CHANGE_NULL_TO_READY
), an element should allocate any non-stream-specific resources and should load runtime-loadable libraries (if any). When going the other way around (from READY to NULL, GST_STATE_CHANGE_READY_TO_NULL
), an element should unload these libraries and free all allocated resources. Examples of such resources are hardware devices. Note that files are generally streams, and these should thus be considered as stream-specific resources; therefore, they should not be allocated in this state.
GST_STATE_PAUSED
is the state in which an element is ready to accept and handle data. For most elements this state is the same as PLAYING. The only exception to this rule are sink elements. Sink elements only accept one single buffer of data and then block. At this point the pipeline is 'prerolled' and ready to render data immediately.
GST_STATE_PLAYING
is the highest state that an element can be in. For most elements this state is exactly the same as PAUSED, they accept and process events and buffers with data. Only sink elements need to differentiate between PAUSED and PLAYING state. In PLAYING state, sink elements actually render incoming data, e.g. output audio to a sound card or render video pictures to an image sink.
如果可能的话,您的元素应该派生自一个新的基类(预制基类)。有针对不同类型的源,接收器和过滤器/转换元件的现成通用基类。除此之外,还存在音频和视频元素等专用基类。
如果使用基类,则很少需要自己处理状态更改。您所要做的就是覆盖基类的start()和stop()虚函数(根据基类可能会有不同的调用),基类将为您处理所有事情。
但是,如果您不是从现成的基类派生,而是从GstElement或其他不基于基类构建的类派生,那么您很可能必须实现自己的状态更改函数以通知状态更改。如果您的插件是多路分离器或多路复用器,这绝对是必要的,因为还没有用于多路复用器或多路分离器的基类。
可以通过虚函数指针通知元素状态变化。在此函数内部,元素可以初始化元素所需的任何类型的特定数据,并且可以选择无法从一个状态转到另一个状态。
对于未处理的状态更改,请不要g_assert; 这由GstElement基类处理。
If at all possible, your element should derive from one of the new base classes (Pre-made base classes). There are ready-made general purpose base classes for different types of sources, sinks and filter/transformation elements. In addition to those, specialised base classes exist for audio and video elements and others.
If you use a base class, you will rarely have to handle state changes yourself. All you have to do is override the base class's start() and stop() virtual functions (might be called differently depending on the base class) and the base class will take care of everything for you.
If, however, you do not derive from a ready-made base class, but from GstElement or some other class not built on top of a base class, you will most likely have to implement your own state change function to be notified of state changes. This is definitively necessary if your plugin is a demuxer or a muxer, as there are no base classes for muxers or demuxers yet.
An element can be notified of state changes through a virtual function pointer. Inside this function, the element can initialize any sort of specific data needed by the element, and it can optionally fail to go from one state to another.
Do not g_assert for unhandled state changes; this is taken care of by the GstElement base class.
static GstStateChangeReturn
gst_my_filter_change_state (GstElement *element, GstStateChange transition);
static void
gst_my_filter_class_init (GstMyFilterClass *klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
element_class->change_state = gst_my_filter_change_state;
}
static GstStateChangeReturn
gst_my_filter_change_state (GstElement *element, GstStateChange transition)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
GstMyFilter *filter = GST_MY_FILTER (element);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
if (!gst_my_filter_allocate_memory (filter))
return GST_STATE_CHANGE_FAILURE;
break;
default:
break;
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (ret == GST_STATE_CHANGE_FAILURE)
return ret;
switch (transition) {
case GST_STATE_CHANGE_READY_TO_NULL:
gst_my_filter_free_memory (filter);
break;
default:
break;
}
return ret;
}
请注意,向上(NULL => READY,READY => PAUSED,PAUSED => PLAYING)和向下(PLAYING => PAUSED,PAUSED => READY,READY => NULL)状态更改在两个单独的块中处理,向下状态更改只有在我们链接到父类的状态更改函数之后才会处理。这是为了安全地处理多个线程的并发访问所必需的。
这样做的原因是,在向下状态更改的情况下,您不希望销毁已分配的资源,而插件的链函数(例如)仍在另一个线程中访问这些资源。您的链功能是否正在运行取决于插件的pad的状态,并且这些pad的状态与元件的状态紧密相关。Pad状态在GstElement类的状态更改函数中处理,包括正确的锁定,这就是在销毁分配的资源之前必须进行链接的原因。
Note that upwards (NULL=>READY, READY=>PAUSED, PAUSED=>PLAYING) and downwards (PLAYING=>PAUSED, PAUSED=>READY, READY=>NULL) state changes are handled in two separate blocks with the downwards state change handled only after we have chained up to the parent class's state change function. This is necessary in order to safely handle concurrent access by multiple threads.
The reason for this is that in the case of downwards state changes you don't want to destroy allocated resources while your plugin's chain function (for example) is still accessing those resources in another thread. Whether your chain function might be running or not depends on the state of your plugin's pads, and the state of those pads is closely linked to the state of the element. Pad states are handled in the GstElement class's state change function, including proper locking, that's why it is essential to chain up before destroying allocated resources.
控制元素行为方式的主要和最重要的方法是通过GObject属性。GObject属性在_class_init ()
函数中定义。该元素可选地实现一个 _get_property ()
和一个_set_property ()
函数。如果应用程序更改或请求属性的值,将通知这些函数,然后可以填写值或采取该属性所需的操作以在内部更改值。
您可能还希望保留一个实例变量,其中包含您在get和set函数中使用的属性的当前配置值。请注意,GObject
不会自动将实例变量设置为默认值,您必须在_init ()
元素的功能中执行此操作 。
The primary and most important way of controlling how an element behaves, is through GObject properties. GObject properties are defined in the _class_init ()
function. The element optionally implements a_get_property ()
and a _set_property ()
function. These functions will be notified if an application changes or requests the value of a property, and can then fill in the value or take action required for that property to change value internally.
You probably also want to keep an instance variable around with the currently configured value of the property that you use in the get and set functions. Note that GObject
will not automatically set your instance variable to the default value, you will have to do that in the _init ()
function of your element.
/* 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文档以非常完整的方式描述了这些内容,但在下面,我们将给出一个简短的示例,说明它的用处。请注意,在这里使用整数可能会完全混淆用户,因为在这种背景下它们毫无意义。这个例子是从video test src偷来的。
The above is a very simple example of how properties are used. Graphical applications will use these properties and will display a user-controllable widget with which these properties can be changed. This means that - for the property to be as user-friendly as possible - you should be as exact as possible in the definition of the property. Not only in defining ranges in between which valid properties can be located (for integers, floats, etc.), but also in using very descriptive (better yet: internationalized) strings in the definition of the property, and if possible using enums and flags instead of integers. The GObject documentation describes these in a very complete way, but below, we'll give a short example of where this is useful. Note that using integers here would probably completely confuse the user, because they make no sense in this context. The example is stolen from 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));
[..]
}
GObject信号可用于通知应用程序特定于此对象的事件。但请注意,应用程序需要了解信号及其含义,因此,如果您正在寻找应用程序元素交互的通用方法,那么信号可能不是您正在寻找的。然而,在许多情况下,信号非常有用。 有关信号的所有内部信息,请参阅 GObject文档。
GObject signals can be used to notify applications of events specific to this object. Note, however, that the application needs to be aware of signals and their meaning, so if you're looking for a generic way for application-element interaction, signals are probably not what you're looking for. In many cases, however, signals can be very useful. See the GObject documentation for all internals about signals.
通常,您需要在尽可能小的设置中测试新编写的插件。通常,这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提供的更多测试功能,例如搜索,事件,交互性等。编写自己的小型测试程序是实现这一目标的最简单方法。本节解释 - 用几句话 - 如何做到这一点。有关完整的应用程序开发指南,请参阅应用开发手册。
Often, you will want to test your newly written plugin in an as small setting as possible. Usually, gst-launch-1.0
is a good first step at testing a plugin. If you have not installed your plugin in a directory that GStreamer searches, then you will need to set the plugin path. Either set GST_PLUGIN_PATH to the directory containing your plugin, or use the command-line option --gst-plugin-path. If you based your plugin off of the gst-plugin template, then this will look something like gst-launch-1.0 --gst-plugin-path=$HOME/gst-template/gst-plugin/src/.libs TESTPIPELINE
However, you will often need more testing features than gst-launch-1.0 can provide, such as seeking, events, interactivity and more. Writing your own small testing program is the easiest way to accomplish this. This section explains - in a few words - how to do that. For a complete application development guide, see the Application Development Manual.
首先,您需要通过调用初始化GStreamer核心库gst_init ()
。您也可以调用 gst_init_get_option_group ()
,它将返回指向GOptionGroup的指针。然后,您可以使用GOption来处理初始化,这将完成GStreamer初始化。
您可以使用创建元素gst_element_factory_make ()
,其中第一个参数是您要创建的元素类型,第二个参数是自由格式名称。最后的示例使用简单的文件源 - 解码器 - 声卡输出管道,但如果需要,您可以使用特定的调试元素。例如, identity
元素可以在管道的中间使用,以充当数据到应用程序的发送器。这可用于检查测试应用程序中的数据是否存在错误或正确性。此外,您可以使用fake sink
管道末尾的元素将数据转储到stdout(为此,请将该dump
属性设置为TRUE)。最后,您可以使用valgrind来检查内存错误。
在链接期间,您的测试应用程序可以使用过滤的大写字母作为向元素或从元素驱动特定类型数据的方法。这是检查元素中多种输入和输出的非常简单有效的方法。
请注意,在运行期间,您应该至少侦听总线和/或插件/元素上的“错误”和“eos”消息,以检查是否正确处理了此消息。此外,您应该将事件添加到管道中,并确保您的插件正确处理这些事件(关于时钟,内部缓存等)。
永远不要忘记清理插件或测试应用程序中的内存。进入NULL状态时,您的元素应该清理已分配的内存和缓存。此外,它应该关闭对可能的支持库的任何引用。您的应用程序应该unref ()
管道并确保它不会崩溃。
At the start, you need to initialize the GStreamer core library by calling gst_init ()
. You can alternatively callgst_init_get_option_group ()
, which will return a pointer to GOptionGroup. You can then use GOption to handle the initialization, and this will finish the GStreamer initialization.
You can create elements using gst_element_factory_make ()
, where the first argument is the element type that you want to create, and the second argument is a free-form name. The example at the end uses a simple filesource - decoder - soundcard output pipeline, but you can use specific debugging elements if that's necessary. For example, an identity
element can be used in the middle of the pipeline to act as a data-to-application transmitter. This can be used to check the data for misbehaviours or correctness in your test application. Also, you can use a fakesink
element at the end of the pipeline to dump your data to the stdout (in order to do this, set the dump
property to TRUE). Lastly, you can use valgrind to check for memory errors.
During linking, your test application can use filtered caps as a way to drive a specific type of data to or from your element. This is a very simple and effective way of checking multiple types of input and output in your element.
Note that during running, you should listen for at least the “error” and “eos” messages on the bus and/or your plugin/element to check for correct handling of this. Also, you should add events into the pipeline and make sure your plugin handles these correctly (with respect to clocking, internal caching, etc.).
Never forget to clean up memory in your plugin or your test application. When going to the NULL state, your element should clean up allocated memory and caches. Also, it should close down any references held to possible support libraries. Your application should unref ()
the pipeline and make sure it doesn't crash.
#include
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 \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;
}