GStreamer是一个开源的多媒体框架,用于创建各种媒体处理组件。它的最大亮点就是其管道 (pipeline)与插件(plugin)机制, 为扩展提供了多种可能.
Plugin 创建的基本步骤
-
环境设置:
- 确保你的开发环境中安装了GStreamer开发库和头文件。
-
创建插件基础结构:
- 使用
gst-plugin
模板生成插件的基础结构。
- 使用
-
编写插件代码:
- 定义插件的元数据,如名称、描述、版本等。
- 创建一个或多个元素,每个元素可以是源(source)、汇(sink)、过滤器(filter)等。
-
注册元素:
- 在插件的初始化函数中注册你的元素。
-
实现元素功能:
- 根据元素的类型,实现必要的功能,比如数据的输入、处理和输出。
-
编译插件:
- 使用
pkg-config
和make
来编译你的插件。
- 使用
-
测试插件:
- 使用GStreamer的命令行工具
gst-launch-1.0
来测试你的插件。
- 使用GStreamer的命令行工具
大致框架
GStreamer 插件有三大类
- source: 产生媒体数据
- sink: 接收媒体数据
- filter: 处理媒体数据
以 filter 元素中最常见的 Transform (filter 插件的一种)为例 , 它的继承结构如下
GObject
╰──GInitiallyUnowned
╰──GstObject
╰──GstElement
╰──GstBaseTransform
从 GstBaseTransform 继承下来写个插件, 也就是一个用于处理数据的过滤器元素。而使用 GstBaseTransform 实现的元素, 其输出的大小和上限完全由输入上限和缓冲区大小决定的。
这些元素包括直接将一个缓冲区转换为另一个缓冲区、就地修改缓冲区内容的元素,以及将多个输入缓冲区整理为一个输出缓冲区或将一个输入缓冲区扩展为多个输出缓冲区的元素。它提供的能力如下:
- 一进一出: 一个 sinkpad 和一个 srcpad
- 使用自定义 transform_caps 函数实现 sink pad和 src_pad 的可能的媒体格式。
默认情况下,sink_pad 和 src_pad 会使用相同的格式。 - 处理状态更改
- 执行刷新 (flushing)
- 默认采用推送模式 (push mode)
- 如果子类转换可以对任意数据进行操作,则使用拉取模式 (pull mode)
它有 2 个主要处理函数:
- transform():将输入缓冲区转换为输出缓冲区。输出缓冲区保证可写且不同于输入缓冲区。
- transform_ip():就地转换输入缓冲区。输入缓冲区可写且大小大于或等于输出缓冲区。
转换 transform 可以以以下的模式运行:
passthrough 直通:元素不会对缓冲区进行更改,缓冲区直接推送,两侧的容量必须相同。元素可以选择实现 transform_ip() 函数来查看数据,缓冲区不必可写。
in-place 就地:可以直接对输入缓冲区进行更改以获取输出缓冲区。转换必须实现 transform_ip() 函数。
copy-transform 复制转换:通过将输入缓冲区复制并转换为新的输出缓冲区来执行转换。转换必须实现 transform() 函数。
当未提供 transform() 函数时,仅允许就地和直通操作,这意味着源和目标容量必须相等,或者源缓冲区大小大于或等于目标缓冲区。
当未提供 transform_ip() 函数时,仅支持直通和复制转换。提供此功能是一种可以避免缓冲区复制的优化。
GStreamer 插件的代码可以从模板代码生成, 一般有如下三个工具可供使用
gst-template:这是一个Git仓库,包含了GStreamer插件的源代码模板。你可以通过Git克隆这个仓库来获取样板代码。使用
make_element
工具,可以基于模板创建新的插件代码。例如,克隆gst-template
仓库后,进入gst-template/gst-plugin/src
目录,然后运行make_element
命令来生成插件的基础代码 。gst-plugins-bad:在
gst-plugins-bad
项目中,有一个名为gst-project-maker
的工具,它可以用来创建一个新的GStreamer项目,包括必要的构建系统和插件框架。gst-element-maker:这是一个命令行工具,位于
gst-plugins-bad/tools
目录下。使用这个工具,你可以通过命令行指定元素的名称和基类来生成样板代码。例如,使用./gst-element-maker my_element audiosink
命令可以生成一个音频接收器(audiosink)元素的样板代码 。
而创建一个基本的GStreamer 的代码大致如下:
#include <gst/gst.h>
#include <gst/base/gstbasetransform.h>
/* 定义一个结构体来存储你的元素的私有数据 */
typedef struct _MyElement {
GstBaseTransform base_transform;
/* <私有成员> */
} MyElement;
/* 定义一个结构体来存储你的元素的类数据 */
typedef struct _MyElementClass {
GstBaseTransformClass base_transform_class;
/* <私有成员> */
} MyElementClass;
/* 定义GST_PLUGIN_DEFINE宏来注册插件 */
GST_PLUGIN_DEFINE (
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
myelement,
"My GStreamer Element Plugin",
plugin_init,
"1.0",
"LGPL",
"GStreamer",
"http://gstreamer.net/"
)
/* 插件初始化函数 */
static gboolean plugin_init (GstPlugin * plugin) {
/* 这里注册你的元素 */
return gst_element_register (plugin, "myelement", GST_RANK_NONE, MY_ELEMENT_GET_TYPE());
}
/* 定义元素的类初始化函数 */
static void my_element_class_init (MyElementClass *klass) {
/* 初始化你的元素的类数据 */
}
/* 定义元素的实例初始化函数 */
static void my_element_init (MyElement *myelement) {
/* 初始化你的元素的实例数据 */
}
/* 定义元素的类型 */
G_DEFINE_TYPE (MyElement, my_element, GST_TYPE_BASE_TRANSFORM);
/* 元素的实现函数,例如transform_ip */
static GstFlowReturn my_element_transform_ip (GstBaseTransform *trans, GstBuffer *buf) {
/* 处理输入缓冲区 */
return GST_FLOW_OK;
}
/* 编译插件时的额外标志 */
#define PACKAGE "myelement"
#define ELEMENT_LONGNAME "My Element"
#define ELEMENT_CLASS my_element_parent_class
#define ELEMENT_TYPE (my_element_get_type ())
#define ELEMENT_NAME "myelement"
/* 插件的入口点 */
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
myelement,
ELEMENT_LONGNAME,
plugin_init,
"1.0",
"LGPL",
"GStreamer",
"http://gstreamer.net/")
例如想把一个彩色视频变成黑白视频的插件
#include <gst/gst.h>
#include <gst/video/gstvideofilter.h>
/* 定义灰度转换元素的类和实例 */
typedef struct _GrayscaleFilter {
GstVideoFilter videofilter;
} GrayscaleFilter;
typedef struct _GrayscaleFilterClass {
GstVideoFilterClass parent_class;
} GrayscaleFilterClass;
/* 定义GST_PLUGIN_DEFINE宏来注册插件 */
GST_PLUGIN_DEFINE (
GST_VERSION_MAJOR,
GST_VERSION_MINOR,
grayscalefilter,
"Grayscale video filter",
plugin_init,
"1.0",
"LGPL",
"GStreamer",
"http://gstreamer.net/"
)
/* 初始化插件 */
static gboolean plugin_init (GstPlugin * plugin) {
return gst_element_register (plugin, "grayscalefilter", GST_RANK_NONE, GRAYSCALEFILTER_TYPE);
}
/* 初始化元素类型 */
G_DEFINE_TYPE (GrayscaleFilter, grayscalefilter, GST_TYPE_VIDEO_FILTER);
/* 重写父类的transform_frame_ip方法,实现灰度转换逻辑 */
static GstFlowReturn grayscalefilter_transform_frame_ip (GstVideoFilter * filter, GstVideoFrame * frame) {
GstVideoFormat format = GST_VIDEO_FRAME_FORMAT (frame);
const int width = GST_VIDEO_FRAME_WIDTH (frame);
const int height = GST_VIDEO_FRAME_HEIGHT (frame);
GstMapInfo map;
/* 检查是否是支持的格式 */
if (format != GST_VIDEO_FORMAT_RGB && format != GST_VIDEO_FORMAT_BGR) {
return GST_FLOW_ERROR;
}
/* 映射帧的内存 */
if (!gst_buffer_map (frame->buffer, &map, GST_MAP_READWRITE)) {
return GST_FLOW_ERROR;
}
/* 转换为灰度,这里以RGB为例,BGR类似 */
if (format == GST_VIDEO_FORMAT_RGB) {
guint8 *data = map.data;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
guint8 y = 0.299 * data[0] + 0.587 * data[1] + 0.114 * data[2];
data[0] = y; // R
data[1] = y; // G
data[2] = y; // B
data += 3;
}
}
}
/* 解除映射 */
gst_buffer_unmap (frame->buffer, &map);
return GST_FLOW_OK;
}
/* 初始化类 */
static void grayscalefilter_class_init (GrayscaleFilterClass *klass) {
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstVideoFilterClass *video_filter_class = GST_VIDEO_FILTER_CLASS (klass);
gst_element_class_set_static_metadata (element_class,
"Grayscale Filter",
"Filter/Effect/Video",
"Convert video to grayscale",
"Your Name <your.email@example.com>");
video_filter_class->transform_frame_ip = GST_DEBUG_FUNCPTR (grayscalefilter_transform_frame_ip);
}
/* 初始化实例 */
static void grayscalefilter_init (GrayscaleFilter *filter) {
// 初始化实例数据(如果需要)
}
-- 本文参考月之暗面 AI 的回答