3. Gstreamer核心对象详解
好,咱们今天来啃Gstreamer的五个核心对象。说实话,我刚接触Gstreamer那会儿,最头疼的就是这些概念。什么Element、Pad、Caps...听着就头大。但后来我发现,搞懂了这五个东西,整个Gstreamer框架就通透了一半。
我个人习惯把这五个对象比作一个水管系统:GstElement是水管里的各种设备(水泵、过滤器、阀门),GstPad是设备的接口(进水口、出水口),GstCaps是水流能通过的规格(管径、水质要求),GstBuffer是水管里流动的水,而GstBus就是设备上的报警灯和喇叭。
嗯,这个比喻不一定完全准确,但能帮你快速建立印象。咱们一个一个来看。
3.1 GstElement:一切的基础
GstElement,说白了就是Gstreamer里的"积木块"。你搭建一个管道,就是在拼这些积木。每个Element负责一件事:读取文件、解码视频、渲染画面等等。
我在项目中遇到过一个问题:一个Element创建出来,但怎么都加不进管道。后来发现是状态没设置对。Element有四种状态:
| 状态 | 含义 | 说明 |
|---|---|---|
| GST_STATE_NULL | 初始状态 | 刚创建出来,啥也没干 |
| GST_STATE_READY | 就绪状态 | 资源已分配,但还没开始跑 |
| GST_STATE_PAUSED | 暂停状态 | 数据流已准备好,但没在播放 |
| GST_STATE_PLAYING | 播放状态 | 数据正在流动 |
你想想看,Element的状态切换是有顺序的:NULL→READY→PAUSED→PLAYING。不能跳着来。我见过有人直接从NULL跳到PLAYING,结果程序直接崩了。
// 创建Element
GstElement *source = gst_element_factory_make("filesrc", "file-source");
if (!source) {
g_printerr("创建filesrc失败,检查插件是否安装\n");
return -1;
}
// 设置属性
g_object_set(source, "location", "/home/test.mp4", NULL);
// 状态切换的正确姿势
gst_element_set_state(source, GST_STATE_READY);
gst_element_set_state(source, GST_STATE_PAUSED);
gst_element_set_state(source, GST_STATE_PLAYING);
3.2 GstPad:Element的"手脚"
GstPad就是Element上的接口。每个Element可以有多个Pad,分为两种:Sink Pad(输入)和Source Pad(输出)。
举个例子:一个解码器Element,它有一个Sink Pad接收压缩数据,一个Source Pad输出解码后的数据。说白了,数据从Sink Pad流进来,从Source Pad流出去。
我记得有一次调试一个视频播放器,画面就是出不来。后来用gst-inspect一看,发现两个Element的Pad类型不匹配——一个是video/x-raw,另一个是video/x-h264,根本连不上。
// 获取Pad
GstPad *sink_pad = gst_element_get_static_pad(decoder, "sink");
GstPad *src_pad = gst_element_get_static_pad(decoder, "src");
// 检查Pad是否存在
if (!sink_pad || !src_pad) {
g_printerr("获取Pad失败\n");
return -1;
}
// 连接两个Element的Pad
GstPadLinkReturn ret = gst_pad_link(src_pad_of_element1, sink_pad_of_element2);
if (GST_PAD_LINK_FAILED(ret)) {
g_printerr("Pad连接失败,检查Caps是否匹配\n");
}
gst-inspect-1.0 videotestsrc,就能看到它的Source Pad支持哪些格式。
3.3 GstCaps:数据格式的"说明书"
GstCaps,全称是GstCapabilities,它描述了数据流的格式。两个Pad能不能连上,就看它们的Caps是否兼容。
你想想看,一个输出RGB的Element,连到一个只接受YUV的Element,那肯定不行。Caps就是用来做这个匹配的。
// 创建一个Caps
GstCaps *caps = gst_caps_new_simple("video/x-raw",
"format", G_TYPE_STRING, "I420",
"width", G_TYPE_INT, 1920,
"height", G_TYPE_INT, 1080,
"framerate", GST_TYPE_FRACTION, 30, 1,
NULL);
// 设置到Element上
g_object_set(encoder, "caps", caps, NULL);
// 用完释放
gst_caps_unref(caps);
我在项目中遇到过最坑的一件事:Caps里有个字段叫"pixel-aspect-ratio",默认是1/1。但有个视频源是4/3的,我没设置这个字段,结果画面被拉伸了。查了两天才发现是Caps没写对。
3.4 GstBuffer:流动的数据
GstBuffer就是实际的数据包。它里面装着音频采样、视频帧、或者任何你需要处理的数据。
嗯,这里要注意:GstBuffer不是简单的内存块,它有自己的生命周期管理。每个Buffer都有引用计数,当引用计数归零时,内存会被自动释放。
// 创建一个Buffer
GstBuffer *buffer = gst_buffer_new_allocate(NULL, 1024, NULL);
// 获取Buffer的数据
GstMapInfo map;
gst_buffer_map(buffer, &map, GST_MAP_WRITE);
// 往map.data里写数据...
memcpy(map.data, "hello", 5);
gst_buffer_unmap(buffer, &map);
// 发送Buffer到下游
GstFlowReturn ret = gst_pad_push(src_pad, buffer);
if (ret != GST_FLOW_OK) {
g_printerr("推送Buffer失败\n");
}
3.5 GstBus:消息的"快递员"
GstBus是Gstreamer的消息系统。Element通过Bus向应用程序发送消息:播放结束了、出错了、状态变了等等。
说白了,Bus就是Element和应用程序之间的通信桥梁。你不需要轮询Element的状态,只需要监听Bus上的消息就行了。
// 获取Bus
GstBus *bus = gst_element_get_bus(pipeline);
// 监听消息
GstMessage *msg = gst_bus_timed_pop_filtered(bus,
GST_CLOCK_TIME_NONE,
GST_MESSAGE_ERROR | GST_MESSAGE_EOS);
if (msg != NULL) {
switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR: {
GError *err;
gchar *debug;
gst_message_parse_error(msg, &err, &debug);
g_printerr("错误: %s\n", err->message);
g_error_free(err);
g_free(debug);
break;
}
case GST_MESSAGE_EOS:
g_print("播放结束\n");
break;
default:
break;
}
gst_message_unref(msg);
}
gst_object_unref(bus);
我记得有一次,客户反馈说播放器偶尔会卡死。我加了一堆日志,最后发现是Bus消息处理线程里做了耗时操作,导致消息队列堵住了。解决方案很简单:把耗时操作放到另一个线程去处理。
3.6 五个对象的关系
最后,咱们把这五个对象串起来看看:
- GstElement是积木块,拼成管道
- GstPad是积木块的接口,负责连接
- GstCaps是接口的规格,决定能不能连
- GstBuffer是流动的数据,在Pad之间传递
- GstBus是消息通道,让应用知道管道里发生了什么
你想想看,一个典型的播放流程是这样的:
应用程序创建管道(一堆Element),通过Pad把它们连起来(Caps要匹配),然后启动播放。数据以Buffer的形式在Element之间流动,同时Element通过Bus向应用报告状态。就这么简单。
嗯,这五个对象是Gstreamer的基石。搞懂了它们,后面的学习就会顺畅很多。下一章咱们会讲怎么用这些对象搭建一个完整的管道,到时候你会看到它们是怎么协同工作的。