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 本章知识体系

下面这张图把本章的核心知识点串起来了。你可以把它当作一个思维导图来用。

GStreamer 元素知识体系 GStreamer 元素知识体系 GstElement 创建元素 设置属性 查询能力 gst_element_factory_make gst_element_factory_create g_object_set / g_object_get 静态能力 协商后能力 常用元素分类 源元素 过滤元素 接收元素 videotestsrc filesrc capsfilter videoconvert fakesink autovideosink

这张图把本章内容分成了三大块:创建、配置、查询。最下面还列出了常用元素的分类。你可以把它截图保存,写代码时瞄一眼,思路会清晰很多。

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)。