2.1 GObject 底座:引用计数与属性系统


2.1 GObject 底座:引用计数与属性系统

本节摘要:GStreamer 的每个对象——元件、衬垫、缓冲区、总线——都是 GObject。GObject 提供三样基础设施:引用计数管生死,属性系统管参数,信号机制管通知。本节从一次真实的内存泄漏讲起,把这三样基础设施讲到能用的深度。它是第二章的地基节,后面每一章的 C 代码都建立在这些机制上。

一切对象的共同祖先

先看一段很多项目都写过的代码。播放器退出时,开发者在日志里看到一行对象残留警告,或者在资源监视器里看到内存随着反复播放缓慢上涨。追到源头,问题往往出在对引用计数的误解上。GObject 用引用计数管理生命周期:对象创建时计数为一,每个持有者加一、放弃时减一,计数归零对象销毁。这套机制把"谁来释放"变成"谁持有",听起来简单,坑藏在细节里。

对象生命周期与属主转移

对象生命周期与属主转移

用最小代码把两种约定走一遍。下面这段是播放器初始化的骨架,注释里标明了每一次引用变化。

// 创建管线与元件:工厂函数返回新引用 计数为一 GstElement *pipeline = gst_pipeline_new("player"); GstElement *src = gst_element_factory_make("filesrc", "my_src"); GstElement *sink = gst_element_factory_make("autovideosink", "out"); // 合入容器:这份引用移交管线 计数由管线持有 gst_bin_add_many(GST_BIN(pipeline), src, sink, NULL); // 链接两元件:只借用指针建立连接 不改变计数 gst_element_link(src, sink); // 程序退出:减掉自己手里那份 之后由容器负责其余 gst_object_unref(pipeline); // 此时再调用 gst_object_unref 就是对已释放对象操作 典型崩溃来源

两种约定混用是初学阶段最常见的泄漏与崩溃来源:该减计的忘了减,内存上涨;不该减计的减了,随机崩溃。规则记一句话——谁加的计数谁负责减,转移出去的不再碰

属性系统:让参数可查可设

GObject 给每个对象挂了一张属性表。元件的所有可调参数——解码器线程数、渲染窗口的显示比例、队列缓冲上限——都以命名属性存在,统一用一对函数读写。这样设计的好处是元件参数的读写方式全框架统一:命令行工具靠它枚举参数,图形化编辑器靠它生成界面,你的代码靠它做配置,一套机制三处受益。

gint threads = 0; // 读属性:按名称取值 类型由属性定义保证 g_object_get(decoder, "max-threads", &threads, NULL); // 写属性:变量清单以 NULL 结尾 可一次读写多个 g_object_set(decoder, "max-threads", 4, NULL);

命令行里查元件参数,底层用的就是这张属性表:

# 查看元件的全部属性:名称 类型 取值范围 默认值 gst-inspect-1.0 x264enc | grep -A 3 "Properties"

实践要点有两条。第一,属性都有默认值,只改你确实理解的参数——把编码器的质量控制参数当玄学乱调,是画面质量事故的高发原因。第二,运行中改属性是允许的,但生效时机取决于元件实现:有的立即生效,有的下帧生效,批量改参数前先在日志里确认行为。

信号机制:让变化可被听见

对象状态发生变化时,GObject 通过信号广播通知。信号与总线消息是两套通道:信号挂在对象上、粒度细、常用于界面同步(比如手写播放器时把元件信号接到界面库的事件循环);总线消息挂在管线级、粒度粗、承载错误与状态迁移。第四章会专门讲总线,这里先建立一个印象——元件上听细节,总线上听大局

// 监听某元件的一个信号:回调在信号发射线程执行 gulong id = g_signal_connect(decoder, "deep-notify", G_CALLBACK(on_notify), NULL); // 不再关心时断开 避免悬空回调 g_signal_handler_disconnect(decoder, id);

⚠️ 常见坑:在信号回调里做重活。回调运行在发射它的线程里,在数据线程的回调中做同步界面操作或文件读写,会直接拖慢数据流动。回调里只置标志或投递事件,重活交给主循环。

给 GStreamer 工程师的最小 GObject 清单

机制 一句话职责 典型误用
引用计数 管对象生死 属主转移后又手动减计
浮动引用 子对象初建时的暂态 合入容器前提前减计
属性系统 统一参数读写 运行中盲改关键参数
信号机制 对象级变化通知 回调里做阻塞操作

浮动引用值得单独一句:新创建的 GObject 常处于"浮动"状态,合入容器的瞬间浮动引用被"沉没"为容器的持有。理解它,才能解释"为什么创建后合入容器只需一次减计"这个日常写法。

引用问题的自检工具与浮动引用补课

引用计数的错误有两副面孔:泄漏(该减没减,内存上涨)与悬空(不该减减了,随机崩溃)。自检工具有两件。其一,对象跟踪日志:打开对象类别日志后,每个对象的创建与销毁全程留痕,程序退出时若有对象未释放,日志会点名残留者的类型与名称,泄漏排查从大海捞针变成按图索骥。其二,长压测曲线:把清理路径写完后反复播放几百次,观察内存曲线是否平稳——平稳说明引用收支平衡,锯齿状上涨说明某处攒了只增不减的引用。

浮动引用值得补一段正式说明。新创建的 GObject 常处于"浮动"状态:这份初始引用没有确定归属,等着第一个容器来认领。元件被合入 Bin 的瞬间,浮动引用"沉没",转为容器持有的正式引用。这套设计让"创建后直接合入"的日常写法只需最后统一减一次计。推论也重要:在合入容器之前不要手动减计浮动引用——那相当于把尚无归属的引用丢掉,元件会在不合预期的时机被销毁。

本节要点回顾

  • 引用计数三规则:谁加谁减、转移不碰、借用的看约定;
  • 合入容器即转交:元件进 Bin 后,那份引用归容器管;
  • 属性表是参数的单一来源:工具、界面、代码共用一套读写接口;
  • 信号听细节、总线上听大局:两套通知通道分工不同,别混用;
  • 回调要轻:信号回调跑在发射线程,重活出队异步处理。

地基打牢,下一节正式翻元件目录:源、变换、汇三类管件,以及每天都要用的验料命令。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U