当前位置: 首页 > 工具软件 > more plugin > 使用案例 >

【gstreamer之plugin教程一】

詹正浩
2023-12-01

1. 前言

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编辑应用程序。

2. 编写一个audio filter插件

接下来的内容是如何开发一个示例:audio filter插件-MyFilter。这个element包含一个input pad和一个output pad。这个filter插件的作用仅仅就是将media和event数据从它的sink pad流入,src pad流出,对其中的数据不做任何处理。但是这个教程的最后,将会学习到如何添加一些有趣的功能:properties and signal handlers。

2.1 构建模板

在这一部分,我们将学到如何使用最少的code来构建一个新的plugin。首先,我们会看到如何得到Gstreamer的template source。然后,知道如何使用一些基础工具来复制和修改一个template plugin从而构建一个新的plugin。通过学习这部分的内容,最后我们将得到一个可以在Gstreamer应用中compile and use的audio filter插件。

2.1.1 克隆一个template plugin源码

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.

下载下来项目各目录的含义:

  • gst-app:构建gstreamer应用程序的基本代码
  • gst-plugin:plugin模板代码

编译插件

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工具接收两个参数:

  1. plugin name
  2. the source file that the tool will use,使用哪些源代码创建插件,默认是gstplugin(即采用的是gst-plugin/src/gstplugin.c 和gstplugin.h代码)

那么如何使用make_element工具呢?假设我们要创建一个MyFilter插件,通过下面代码就能获得gstmyfilter.c和gstmyfilter.h两个文件

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

需要注意的是,plugin name是大小写敏感的

上述项目的具体使用过程以及经验可以参考文章【在 Jetson Nano 上编写 GStreamer 插件 ——实现自己的第一个作品】。

2.1.2  MyFilter插件代码分析

basic code

//! 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 metadata提供了element的其他信息。它使用 gst_element_class_set_metadata 或 gst_element_class_set_static_metadata 配置,它采用以下参数:

  1. element name
  2. element type 
  3. 对element功能的一个简单清晰的描述
  4. element作者名字,以及通讯地址

下面为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 

 GstStaticPadTemplate类是用来描述element创建和使用的pad。其中包含:

  1. 一个short name
  2. pad的方向
  3. 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).

  4. 该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(一个范围)。

Constructor Functions构造函数 

每个element使用两个函数来构造这个element。_class_init函数仅仅调用一次被用来初始化class(specifying what signals, arguments and virtual functions the class has and setting up global state)。而_init函数则用来初始化此类型的特定实例。

plugin_init function

一旦我们完成了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() 函数返回的信息将缓存在中央注册表中。 

 类似资料: