4. GStreamer 元素详解:创建元素、设置属性、查询能力,常用元素一览
好,咱们进入正题。元素(Element)是 GStreamer 管道里最基础的积木块。你想想看,一个管道无非就是把一堆元素串起来,让数据从一头流到另一头。所以,搞懂元素怎么创建、怎么配置、怎么查它支持什么,是写 GStreamer 代码的第一步。
我个人习惯把元素分成三类:源元素(数据从哪来)、过滤元素(数据怎么变)、接收元素(数据去哪)。这个分类虽然简单,但很实用。
核心要点:元素是 GStreamer 中一切功能的最小单元。每个元素都有一个名字、一组属性、一组 Pad(端口),以及一组能力(Caps)。
4.1 创建元素:两种方式,各有千秋
创建元素最常用的两个函数是 gst_element_factory_make() 和 gst_element_factory_create()。前者是后者的简化版,我 90% 的场景都用前者。
// 方式一:一步到位(推荐)
GstElement *source = gst_element_factory_make("videotestsrc", "my_source");
if (!source) {
g_printerr("创建 videotestsrc 失败,请检查插件是否安装\n");
return -1;
}
// 方式二:先拿工厂,再创建(需要更多控制时用)
GstElementFactory *factory = gst_element_factory_find("videotestsrc");
if (!factory) {
g_printerr("找不到 videotestsrc 工厂\n");
return -1;
}
GstElement *source2 = gst_element_factory_create(factory, "my_source_2");
gst_object_unref(factory); // 记得释放工厂引用
第二个参数是元素的名字,可以为 NULL。但我建议你总是给元素起个有意义的名字。为什么?调试的时候你就知道了——当管道里有几十个元素,报错信息里显示的是名字,不是类型。我曾经在一个项目里偷懒没起名,结果排查一个内存泄漏问题花了整整一下午,最后发现是名字冲突导致的引用计数异常。嗯,从那以后我再也不省这个功夫了。
小技巧:用 gst_element_factory_make() 时,如果返回 NULL,别急着崩溃。先检查一下插件是否注册了:gst_registry_find_plugin(gst_registry_get(), "videotestsrc")。很多时候只是插件没装全。
4.2 设置属性:GObject 的那一套
GStreamer 元素本质上是 GObject 的子类。所以设置属性用的就是 GObject 的属性系统。最常用的两个函数是 g_object_set() 和 g_object_get()。
// 设置单个属性
g_object_set(source, "pattern", 0, NULL); // 0 = 彩条模式
// 设置多个属性(注意要以 NULL 结尾)
g_object_set(source,
"pattern", 1, // 1 = 雪花模式
"is-live", FALSE,
NULL);
// 读取属性
gint current_pattern;
g_object_get(source, "pattern", ¤t_pattern, NULL);
g_print("当前 pattern = %d\n", current_pattern);
这里有个坑:属性名是字符串,拼错了不会编译报错,但运行时会静默失败。GStreamer 不会因为你写了个不存在的属性名就崩溃,它只是忽略掉。我曾经在调试一个视频延迟问题时,发现 sync 属性怎么设都不生效,查了半天才发现我把属性名写成了 sync-mode(实际是 sync)。
避坑指南:设置属性后,建议用 g_object_get() 读回来验证一下。特别是那些枚举类型属性,你传的整数值可能跟实际枚举值对不上。我曾经遇到过 videotestsrc 的 pattern 在不同版本里枚举值不一样的情况。
4.3 查询能力:元素能干什么,不能干什么
每个元素都有它的能力(Caps),说白了就是它支持什么格式的数据。查询能力有两个层面:静态能力(元素出厂时声明的能力)和 协商后能力(实际连接后确定的能力)。
// 获取元素的 Pad
GstPad *src_pad = gst_element_get_static_pad(source, "src");
if (!src_pad) {
g_printerr("获取 src pad 失败\n");
return;
}
// 查询当前能力
GstCaps *caps = gst_pad_get_current_caps(src_pad);
if (caps) {
g_print("当前能力: %s\n", gst_caps_to_string(caps));
gst_caps_unref(caps);
} else {
g_print("还没有协商好能力(可能没连接)\n");
}
// 查询所有可能的能力
GstCaps *all_caps = gst_pad_get_pad_template_caps(src_pad);
g_print("所有可能的能力: %s\n", gst_caps_to_string(all_caps));
gst_caps_unref(all_caps);
gst_object_unref(src_pad);
输出大概长这样:
当前能力: video/x-raw, format=(string)I420, width=(int)320, height=(int)240, ...
所有可能的能力: video/x-raw, format=(string){ I420, YV12, YUY2, ... }, width=(int)[ 1, 2147483647 ], ...
看到那个花括号和方括号了吗?花括号表示枚举(多种可能),方括号表示范围。这是 GStreamer 能力描述的语法,你得学会读它。
实用场景:在写动态管道时,经常需要查询元素的能力来决定怎么连接。比如你有一个编码器,它支持 H.264 和 H.265,但你的解码器只支持 H.264。这时候就得查能力,然后手动设置 Caps 来强制协商。
4.4 常用元素一览
下面这张表是我做项目时最常用的一些元素,按功能分类列出来。你不需要全记住,但至少要知道它们存在,用的时候能想起来去查文档。
| 分类 | 元素名 | 功能说明 | 常用属性 |
|---|---|---|---|
| 源元素 | videotestsrc |
生成测试视频信号 | pattern(图案模式) |
audiotestsrc |
生成测试音频信号 | freq(频率)、wave(波形) |
|
filesrc |
从文件读取数据 | location(文件路径) |
|
uridecodebin |
自动解码 URI 指向的媒体 | uri(媒体 URI) |
|
| 过滤元素 | capsfilter |
强制限制数据格式 | caps(目标能力) |
videoconvert |
视频格式转换(颜色空间、像素格式) | 无常用属性 | |
audioconvert |
音频格式转换 | 无常用属性 | |
videoscale |
视频缩放 | method(缩放算法) |
|
| 接收元素 | fakesink |
丢弃所有数据(用于测试) | sync(是否同步) |
autovideosink |
自动选择视频输出窗口 | 无常用属性 | |
autoaudiosink |
自动选择音频输出设备 | 无常用属性 | |
filesink |
将数据写入文件 | location(输出路径) |
个人经验:调试阶段我特别喜欢用 fakesink 和 videotestsrc 这对组合。它们不需要任何外部设备或文件,就能快速验证管道逻辑是否正确。等逻辑跑通了,再换成真实的源和接收端。
4.5 本章知识体系
下面这张图把本章的核心知识点串起来了。你可以把它当作一个思维导图来用。
这张图把本章内容分成了三大块:创建、配置、查询。最下面还列出了常用元素的分类。你可以把它截图保存,写代码时瞄一眼,思路会清晰很多。
4.6 实战小练习
光看不练假把式。我建议你动手写一个最简单的程序:创建一个 videotestsrc,设置 pattern 为 1(雪花),然后通过 fakesink 把数据丢掉。跑起来后,用 gst_pad_get_current_caps() 打印出协商后的能力。
#include <gst/gst.h>
int main(int argc, char *argv[]) {
gst_init(&argc, &argv);
GstElement *pipeline = gst_pipeline_new("test-pipeline");
GstElement *src = gst_element_factory_make("videotestsrc", "src");
GstElement *sink = gst_element_factory_make("fakesink", "sink");
if (!src || !sink) {
g_printerr("创建元素失败\n");
return -1;
}
g_object_set(src, "pattern", 1, NULL);
gst_bin_add_many(GST_BIN(pipeline), src, sink, NULL);
gst_element_link(src, sink);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
// 等一会,让协商完成
g_usleep(500000);
// 查询能力
GstPad *pad = gst_element_get_static_pad(src, "src");
GstCaps *caps = gst_pad_get_current_caps(pad);
if (caps) {
g_print("协商后的能力: %s\n", gst_caps_to_string(caps));
gst_caps_unref(caps);
}
gst_object_unref(pad);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}
编译命令:
gcc test_element.c -o test_element `pkg-config --cflags --libs gstreamer-1.0`
跑一下看看输出。你会看到类似 video/x-raw, format=(string)I420, width=(int)320, height=(int)240 这样的信息。这就是你的元素在说:「嘿,我现在输出的是 320x240 的 I420 视频。」
延伸思考:试试把 fakesink 换成 autovideosink,看看能不能看到雪花画面。如果看不到,检查一下你的系统有没有安装视频输出插件(比如 ximagesink 或 waylandsink)。