3. GObject 基础:GObject 类系统、类型注册、信号与属性。GstObject 继承体系。

好,我们正式开始进入 GStreamer 插件开发的核心地基——GObject。说实话,很多初学者一上来就被 GObject 的宏和命名规则搞懵了。我当年第一次看 GStreamer 源码时,心里直犯嘀咕:这玩意儿怎么这么多下划线?

但你别怕。GObject 说白了就是 C 语言里的一套面向对象框架。GStreamer 整个大厦都建在这上面。你写一个插件,本质上就是在 GObject 的体系里注册一个新类型,然后告诉框架:我这个元素能干什么。

3.1 GObject 类系统:C 语言里的“类”

C 语言没有 class 关键字,但 GObject 用结构体和函数指针模拟出了继承、封装和多态。核心就两个结构体:

  • 实例结构体(Instance Struct):存放每个对象的私有数据。相当于 C++ 里的成员变量。
  • 类结构体(Class Struct):存放虚函数表。相当于 C++ 里的虚函数表。

举个例子,你定义一个 GstMyFilter 元素,它的实例结构体里会放缓冲区指针、属性值;类结构体里放 chain 函数、sink_event 函数等虚函数。

核心原则:实例结构体是“数据”,类结构体是“行为”。两者通过类型系统绑定在一起。

3.2 类型注册:让 GObject 认识你

每个 GObject 子类都必须注册到类型系统。注册的核心是一个宏:G_DECLARE_FINAL_TYPE 或 G_DECLARE_DERIVABLE_TYPE。我个人习惯用 G_DECLARE_FINAL_TYPE,因为插件元素通常不需要再被继承。

注册流程分三步:

  1. 定义结构体:写实例结构体和类结构体。
  2. 实现 _get_type() 函数:这个函数是类型系统的入口,里面调用 g_type_register_static()。
  3. 实现 _init() 和 _class_init():初始化实例和类。
// 典型类型注册代码骨架
G_DEFINE_TYPE(GstMyFilter, gst_my_filter, GST_TYPE_ELEMENT)

static void gst_my_filter_init(GstMyFilter *filter) {
    // 初始化实例成员
    filter->sinkpad = NULL;
    filter->srcpad = NULL;
}

static void gst_my_filter_class_init(GstMyFilterClass *klass) {
    // 设置虚函数
    GstElementClass *element_class = GST_ELEMENT_CLASS(klass);
    gst_element_class_set_static_metadata(element_class,
        "My Filter", "Filter/Effect",
        "A simple filter element", "Your Name");
}

小技巧:G_DEFINE_TYPE 宏会自动生成 _get_type() 函数。你不需要手写。但如果你需要自定义父类初始化顺序,可以用 G_DEFINE_TYPE_WITH_CODE。

3.3 信号与属性:对象间的通信

GObject 提供了两种通信机制:

  • 属性(Properties):用于读写对象的配置参数。比如视频元素的宽度、高度。
  • 信号(Signals):用于事件通知。比如“缓冲区满了”、“流结束了”。

属性通过 g_object_class_install_properties() 注册。信号通过 g_signal_new() 注册。我在项目中遇到过一个问题:属性名和信号名不要用驼峰,要用连字符。比如 my-property 而不是 myProperty。否则 GStreamer 的管道描述语法会解析失败。

特性 属性(Properties) 信号(Signals)
用途 配置参数 事件通知
访问方式 get/set 函数 connect/emit
典型例子 bitrate, width pad-added, eos
注册函数 g_object_class_install_properties g_signal_new

避坑指南:我曾经在信号处理函数里直接调用了 g_object_unref(),导致对象在信号传播过程中被销毁,程序崩溃。记住:信号处理期间,对象的引用计数由框架管理,不要手动释放。

3.4 GstObject 继承体系

GStreamer 的所有对象都继承自 GstObject。而 GstObject 本身继承自 GObject。整个继承链是这样的:

GObject
  └── GstObject
        ├── GstElement
        │     └── GstBin
        │           └── GstPipeline
        ├── GstPad
        ├── GstBuffer
        ├── GstCaps
        └── GstEvent

GstObject 在 GObject 基础上增加了:

  • 名称管理:每个 GStreamer 对象都有一个名字,可以通过 gst_object_set_name() 设置。
  • 父子关系:对象可以形成树形结构,比如 bin 包含 element。
  • 锁机制:GST_OBJECT_LOCK() 和 GST_OBJECT_UNLOCK() 用于线程安全。

你写插件时,你的元素类直接继承 GstElement。而 GstElement 又继承 GstObject。所以你天然拥有了名称、锁、父子关系这些能力。

关键点:GstObject 的引用计数是线程安全的。你不需要自己加锁来管理对象生命周期。但要注意,gst_object_ref() 和 gst_object_unref() 必须成对出现。

3.5 本章知识体系图

下面这张图展示了 GObject 类系统、类型注册、信号属性以及 GstObject 继承体系之间的关系。你可以把它当作一张地图,写插件时随时回来对照。

GObject 与 GstObject 核心知识体系 GObject 与 GstObject 核心知识体系 GObject 类系统 类型注册 信号与属性 GstObject 继承体系 GstElement GstPad GstBuffer GstCaps 你的自定义元素 说明:你的自定义元素继承自 GstElement,而 GstElement 继承自 GstObject,GstObject 继承自 GObject。 类型注册、信号、属性是 GObject 层的能力,GstObject 增加了名称、锁和父子关系。

3.6 小结

这一章我们覆盖了三个核心概念:

  • GObject 类系统:实例结构体存数据,类结构体存行为。
  • 类型注册:用 G_DEFINE_TYPE 宏让 GObject 认识你的新类型。
  • 信号与属性:属性用于配置,信号用于通知。
  • GstObject 继承体系:你的元素最终继承自 GObject,但中间经过了 GstObject 和 GstElement。