插件骨架:GstPlugin、GstElement、GstPad 基础概念
好,咱们正式开始写代码了。这一章我带你搭起 GStreamer 插件的最小骨架。说白了,就是让 GStreamer 能认出你的插件,并且知道它是个什么东西。
很多初学者一上来就盯着数据处理逻辑写,结果插件加载就崩了。我当年也犯过这毛病——花了两天调试一个 pad 不链接的问题,最后发现是插件注册时类型名字写错了。嗯,咱们先把地基打牢。
三个核心概念:Plugin、Element、Pad
GStreamer 的插件体系其实就三层,你记住这张图就行:
我习惯把 Plugin 比作一个工具箱,Element 是工具箱里的扳手、螺丝刀,Pad 就是扳手咬合螺丝的那个口。没有 Pad,数据就流不出去。
GstPlugin:插件的身份证
每个插件必须有一个 plugin_init 函数。这是 GStreamer 加载插件时的入口点。你想想看,GStreamer 拿到一个 .so 文件,它怎么知道里面有什么元素?就是靠这个函数告诉它的。
核心要点: plugin_init 是插件与 GStreamer 框架之间的契约。名字不能改,签名不能错。
函数签名长这样:
static gboolean
plugin_init(GstPlugin *plugin)
{
/* 在这里注册你的元素 */
return TRUE;
}
参数 GstPlugin *plugin 是框架传给你的,你不需要自己创建。返回值 TRUE 表示加载成功,FALSE 表示加载失败——GStreamer 会立刻卸载这个插件。
注意: 我曾经见过有人在这个函数里做复杂的初始化操作,比如打开文件、连接网络。千万别这么干!plugin_init 只应该做注册工作,耗时操作会导致整个管道启动变慢。
GstElement:插件的核心单元
Element 是真正干活的。GStreamer 内置了几百种 Element,从文件读取、解码、编码到渲染,应有尽有。我们写自定义插件,本质上就是定义一个新的 Element 类型。
每个 Element 需要有一个类型结构体,以及对应的初始化函数。看个最简单的例子:
/* 定义元素类型 */
#define GST_TYPE_MY_FILTER (my_filter_get_type())
G_DECLARE_FINAL_TYPE(MyFilter, my_filter, MY, FILTER, GstElement)
/* 元素结构体 */
struct _MyFilter {
GstElement parent;
/* 以后会加 pad、属性、内部状态 */
};
/* 类型注册 */
G_DEFINE_TYPE(MyFilter, my_filter, GST_TYPE_ELEMENT)
/* 初始化函数 */
static void
my_filter_init(MyFilter *filter)
{
/* 这里创建 pad */
}
/* 类初始化函数 */
static void
my_filter_class_init(MyFilterClass *klass)
{
/* 这里设置元数据 */
}
嗯,这里要注意:G_DECLARE_FINAL_TYPE 和 G_DEFINE_TYPE 是 GLib 的宏,它们帮你生成了类型注册的样板代码。我刚开始写的时候觉得这些宏很烦人,但用久了发现它们确实能避免很多手写错误。
GstPad:数据的出入口
Pad 分两种:
| Pad 类型 | 方向 | 典型用途 |
|---|---|---|
| Src Pad | 输出数据 | 源元素(如文件读取)、滤镜的输出端 |
| Sink Pad | 接收数据 | 接收端元素(如渲染器)、滤镜的输入端 |
一个 Element 可以有多个 Src Pad 和多个 Sink Pad。比如一个视频解码器,可能有 1 个 Sink Pad 接收压缩数据,2 个 Src Pad 分别输出视频和音频。
创建 Pad 的代码:
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE(
"sink", /* Pad 名字 */
GST_PAD_SINK, /* Pad 方向 */
GST_PAD_ALWAYS, /* 存在性:ALWAYS/SOMETIMES/REQUEST */
GST_STATIC_CAPS_ANY /* 支持的 Caps,后面章节细讲 */
);
static void
my_filter_init(MyFilter *filter)
{
/* 创建 Sink Pad */
filter->sinkpad = gst_pad_new_from_static_template(
&sink_template, "sink");
gst_element_add_pad(GST_ELEMENT(filter), filter->sinkpad);
}
个人经验: Pad 的名字不要随便起。GStreamer 的 bin 元素(比如 playbin)会通过名字来查找特定的 Pad。如果你写了一个视频滤镜,Pad 叫 "video_sink" 和 "video_src",别人一看就明白。我曾经见过有人把 Pad 命名为 "aaa" 和 "bbb",调试的时候差点崩溃。
编写第一个空插件
好了,理论说完了,咱们动手写一个完整的空插件。这个插件什么都不做,但它能成功加载到 GStreamer 中。
完整的代码:
#include <gst/gst.h>
/* 元素类型声明 */
#define GST_TYPE_MY_FIRST_PLUGIN (my_first_plugin_get_type())
G_DECLARE_FINAL_TYPE(MyFirstPlugin, my_first_plugin, MY, FIRST_PLUGIN, GstElement)
/* 元素结构体 */
struct _MyFirstPlugin {
GstElement parent;
};
/* 类型注册宏 */
G_DEFINE_TYPE(MyFirstPlugin, my_first_plugin, GST_TYPE_ELEMENT)
/* 元素初始化 */
static void
my_first_plugin_init(MyFirstPlugin *self)
{
/* 空插件,暂时不创建 Pad */
(void)self;
}
/* 类初始化 */
static void
my_first_plugin_class_init(MyFirstPluginClass *klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
/* 设置元数据:长名、分类、描述、作者 */
gst_element_class_set_static_metadata(
element_class,
"My First Plugin", /* 长名 */
"Filter", /* 分类 */
"An empty plugin for demo", /* 描述 */
"Your Name <email@example.com>" /* 作者 */
);
}
/* 插件入口函数 */
static gboolean
plugin_init(GstPlugin *plugin)
{
return gst_element_register(
plugin, /* 插件指针 */
"myfirstplugin", /* 元素名字,管道中用这个名字 */
GST_RANK_NONE, /* 优先级 */
GST_TYPE_MY_FIRST_PLUGIN /* 元素类型 */
);
}
/* 插件描述信息,GStreamer 通过这个宏找到 plugin_init */
GST_PLUGIN_DEFINE(
GST_VERSION_MAJOR, /* 主版本号 */
GST_VERSION_MINOR, /* 次版本号 */
my_first_plugin, /* 插件名字(小写) */
"My first GStreamer plugin", /* 插件描述 */
plugin_init, /* 入口函数 */
VERSION, /* 版本号,由构建系统定义 */
"LGPL", /* 许可证 */
"GStreamer", /* 包名 */
"http://gstreamer.net/" /* 项目网址 */
)
关键点:
GST_PLUGIN_DEFINE宏必须放在源文件末尾,它展开后会生成插件加载所需的符号。gst_element_register把元素类型注册到插件中,这样 GStreamer 才能通过名字创建它。- 元数据中的分类("Filter")会影响
gst-inspect-1.0的搜索结果。
编译与测试
写完了怎么验证?我建议你用 Meson 构建系统,这是 GStreamer 官方推荐的。但为了快速测试,你也可以手动编译:
gcc -shared -fPIC \
-o libmyfirstplugin.so \
my_first_plugin.c \
`pkg-config --cflags --libs gstreamer-1.0`
然后设置环境变量让 GStreamer 能找到你的插件:
export GST_PLUGIN_PATH=$(pwd)
gst-inspect-1.0 myfirstplugin
如果一切正常,你会看到类似这样的输出:
Factory Details:
Long name: My First Plugin
Classification: Filter
Description: An empty plugin for demo
Author: Your Name <email@example.com>
Plugin Details:
Name: my_first_plugin
Description: My first GStreamer plugin
Filename: /path/to/libmyfirstplugin.so
Version: 1.20.0
License: LGPL
Source module: GStreamer
Source release date: unknown
Binary package: GStreamer
Origin URL: http://gstreamer.net/
避坑指南: 我曾经因为 GST_PLUGIN_PATH 没设置对,折腾了半小时。GStreamer 默认只搜索系统路径和 GST_PLUGIN_PATH 指定的路径。如果你把 .so 文件放在当前目录但没设环境变量,gst-inspect 是找不到的。
另外,注意插件名字的对应关系:GST_PLUGIN_DEFINE 的第二个参数是 my_first_plugin,生成的 .so 文件名必须是 libmyfirstplugin.so。GStreamer 会通过文件名推断插件名,如果对不上,加载会失败。
小结一下
这一章我们搭好了插件的最小骨架。你掌握了:
- Plugin、Element、Pad 三层架构的关系
plugin_init函数的写法- Element 的类型注册和初始化
- Pad 的基本概念和创建方法
- 一个完整的空插件代码