Gstreamer是一个用于创建流媒体(streaming media)应用的框架。基础的设计思想来源于Oregon Graduate Institute的video pipeline产品,同样也从DirectShow应用中借鉴了一些思想。
Gstreamer的开发框架使得编写任意类型的流媒体应用都变得十分简单。Gstreamer框架的设计思想让包括处理audio和video的应用开发变得不那么困难。并且Gstreamer不仅仅局限于audio和video,可以处理任意(any)类型的数据流(data flow)。 The pipeline design is made to have little overhead above what the applied filters induce(pipeline设计的开销要远小于filter所引起的)。基于此,Gstreamer即使是在设计高延迟或高性能(high-demands on latency or performance)的高端音频应用时,依旧是一个优秀的框架。
Gstreamer最常被使用来构建一个media player(媒体播放器)。Gstreamer中已经包含用于构建一个支持各种格式类型(ncluding MP3, Ogg/Vorbis, MPEG-1/2, AVI, Quicktime, mod, and more.),media player所需的组件components。但是,Gstreamer不仅仅只能做一个media player。其最重要的优势在于,(the pluggable componets)可插拔的plugin可以被mixed和matched到任意pipeline中,从而可以编写成熟的video或者audio编辑应用程序。
接下来的内容是如何开发一个示例:audio filter插件-MyFilter。这个element包含一个input pad和一个output pad。这个filter插件的作用仅仅就是将media和event数据从它的sink pad流入,src pad流出,对其中的数据不做任何处理。但是这个教程的最后,将会学习到如何添加一些有趣的功能:properties and signal handlers。
在这一部分,我们将学到如何使用最少的code来构建一个新的plugin。首先,我们会看到如何得到Gstreamer的template source。然后,知道如何使用一些基础工具来复制和修改一个template plugin从而构建一个新的plugin。通过学习这部分的内容,最后我们将得到一个可以在Gstreamer应用中compile and use的audio filter插件。
Gstreamer说它有两种方式来开发一个plugin,一种是从零开始手撸代码,另一个种是复制一个已经存在的plugin模板然后修改。(说了个寂寞,CV不香)
因此,我们第一步就是去clone一个已经存在的plugin模板gst-template,具体操作方式如下:
$ git clone https://github.com/freedesktop/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.
下载下来项目各目录的含义:
编译插件
cd gst-plugin/
sh autogen.sh
make
创建一个新的element的第一件事是搞清楚一些基本的概念:plugin的name,plugin的作者,plugin的版本等等。同样,也需要定义一个对象来表示element和存储这个element所需要的data。上述这些细节统称为boilerplate。
定义一个boilerplate的基本方式是简单写一些code,同时填充一些structures。编写一个plugin最容易的方式是复制template和添加一些想要的功能。第一步复制模板可以采用工具make_element,位于./gst-plugin/tool目录下,是一个命令行实用程序,可以创建boilerplate代码。
make_element工具接收两个参数:
那么如何使用make_element工具呢?假设我们要创建一个MyFilter插件,通过下面代码就能获得gstmyfilter.c和gstmyfilter.h两个文件
cd gst-template/gst-plugin/src
../tools/make_element MyFilter
需要注意的是,plugin name是大小写敏感的。
上述项目的具体使用过程以及经验可以参考文章【在 Jetson Nano 上编写 GStreamer 插件 ——实现自己的第一个作品】。
//! gstmyfilter.h代码内容
#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)
GST_ELEMENT_REGISTER_DEFINE和GST_ELEMENT_REGISTER_DECLARE一起应用可以通过调用GST_ELEMENT_REGISTER (my_filter)来在plugin内部代码中或者任何其他plugin/applications代码中来注册element。plugin具体的注册过程可以参考文章【gstreamer学习笔记---plugin注册流程分析(超详细)】
// ! gstmyfilter.c
#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);
element metadata提供了element的其他信息。它使用 gst_element_class_set_metadata 或 gst_element_class_set_static_metadata 配置,它采用以下参数:
下面为element metadata的一个示例:
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>");
其中element的细节信息是在_class_init函数中注册到plugin中,_class_init函数是GObject 框架中一部分。示例如下:
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类是用来描述element创建和使用的pad。其中包含:
Existence property. This indicates whether the pad exists always (an “always” pad), only in some cases (a “sometimes” pad) or only if the application requested such a pad (a “request” pad).
该element支持的类型(capabilities)
示例如下:
static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("ANY")
);
这些pad templates在_class_init函数中调用gst_element_class_add_pad_template来注册。gst_element_class_add_pad_template函数需要一个handle指向GstPadTemplate,可以通过gst_static_pad_template_get函数得到。
pad可以通过static template在element的_init函数中被创建出来,通过调用gst_pad_new_from_static_template函数。为了创建一个新的pad从template中使用gst_pad_new_from_static_template函数,需要将这些pad template作为一个全局变量。
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));
}
pad的最后参数指的是支持处理的类型,在上面示例中,使用”ANY“来表示这个element可以接收任何类型作为输入。但是在现实场景下,需要设置媒体类型和可选的一组属性,以确保只有受支持的输入才会进入。这个属性的表示应该是一个以媒体类型开头的字符串,然后是一组逗号分隔的属性及其支持的值。
static GstStaticPadTemplate sink_factory =
GST_STATIC_PAD_TEMPLATE (
"sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (
"audio/x-raw, " // audio媒体
"format = (string) " GST_AUDIO_NE (S16) ", " // supports raw integer 16-bit audio
"channels = (int) { 1, 2 }, "
"rate = (int) [ 8000, 96000 ]"
)
);
type属性的字符串中,使用{}包裹的是一个list(多个值),而采用[]包裹的是range(一个范围)。
每个element使用两个函数来构造这个element。_class_init函数仅仅调用一次被用来初始化class(specifying what signals, arguments and virtual functions the class has and setting up global state)。而_init函数则用来初始化此类型的特定实例。
一旦我们完成了plugin所有部分的定义,我们需要写plugin_init函数。这个函数在plugin加载时被调用,返回值为true或者false来决定是否正确加载依赖项。同时,在这个函数中,任何支持element type将被注册。
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() 函数返回的信息将缓存在中央注册表中。