Pipeline构建入门:gst_parse_launch()的使用、手动构建Pipeline、Pad与Caps的概念
好,咱们正式开始动手了。这一章我带你走进 GStreamer 的 Pipeline 世界。说白了,Pipeline 就是一条数据流水线——你把源文件丢进去,经过各种处理,最后输出到屏幕或文件。
构建 Pipeline 有两种方式:一种是偷懒的字符串解析法,另一种是手撸代码的硬核构建法。两种我都用过,各有各的妙处。咱们先看第一种。
一、gst_parse_launch():快速原型的神器
我个人习惯在调试阶段先用 gst_parse_launch()。它接受一个字符串,像写命令行一样描述 Pipeline。GStreamer 内部会帮你解析并创建好所有元素。
举个例子,你想播放一个视频文件:
gst-launch-1.0 filesrc location=test.mp4 ! qtdemux ! h264parse ! avdec_h264 ! videoconvert ! autovideosink
在代码里,你只需要写:
GstElement *pipeline;
pipeline = gst_parse_launch("filesrc location=test.mp4 ! qtdemux ! h264parse ! avdec_h264 ! videoconvert ! autovideosink", NULL);
嗯,就这么简单。但这里有个坑——错误处理。我曾经在项目里直接传字符串,结果路径写错了,pipeline 返回 NULL,程序直接崩溃。所以记得加个判断:
if (!pipeline) {
g_printerr("Pipeline 构建失败,请检查字符串语法\n");
return -1;
}
二、手动构建 Pipeline:掌控每一处细节
手动构建虽然代码量多,但你能精确控制每个元素的属性和连接方式。咱们来手动实现上面那个播放 Pipeline。
第一步,创建所有元素:
GstElement *pipeline, *source, *demuxer, *parser, *decoder, *converter, *sink;
pipeline = gst_pipeline_new("my-pipeline");
source = gst_element_factory_make("filesrc", "file-source");
demuxer = gst_element_factory_make("qtdemux", "qt-demuxer");
parser = gst_element_factory_make("h264parse", "h264-parser");
decoder = gst_element_factory_make("avdec_h264", "h264-decoder");
converter = gst_element_factory_make("videoconvert", "video-converter");
sink = gst_element_factory_make("autovideosink", "video-sink");
第二步,设置属性。比如 filesrc 需要知道文件路径:
g_object_set(source, "location", "test.mp4", NULL);
第三步,把元素加入 Pipeline:
gst_bin_add_many(GST_BIN(pipeline), source, demuxer, parser, decoder, converter, sink, NULL);
第四步,连接元素。这里有个关键点——qtdemux 是动态 Pad,它要等到解析完文件头才知道输出什么格式。所以不能直接连,得用信号回调:
g_signal_connect(demuxer, "pad-added", G_CALLBACK(on_pad_added), parser);
回调函数里,你拿到 demuxer 新创建的 src pad,然后连接到 parser 的 sink pad:
static void on_pad_added(GstElement *element, GstPad *pad, gpointer data) {
GstElement *parser = (GstElement *)data;
GstPad *sink_pad = gst_element_get_static_pad(parser, "sink");
gst_pad_link(pad, sink_pad);
gst_object_unref(sink_pad);
}
g_print("新 Pad: %s\n", GST_PAD_NAME(pad));
其他静态连接就简单了:
gst_element_link(parser, decoder);
gst_element_link(decoder, converter);
gst_element_link(converter, sink);
三、Pad 与 Caps:数据流动的桥梁和规则
你想想看,Pipeline 里的元素就像一个个黑盒子,它们之间怎么传递数据?靠的就是 Pad(垫片)。每个元素有输入 Pad(sink pad)和输出 Pad(src pad)。
Pad 分两种:
- 静态 Pad:元素创建时就固定存在,比如 filesrc 的 src pad、videoconvert 的 sink 和 src pad。
- 动态 Pad:运行时才出现,比如 qtdemux 的 src pad,要等解析完才知道有几个视频流、音频流。
那 Caps(Capabilities)又是什么?说白了,它就是数据格式的“身份证”。Caps 描述了 Pad 能处理或产生的媒体类型、分辨率、帧率、编码格式等。
举个例子,一个视频解码器的 sink pad 的 Caps 可能是:
video/x-h264, width=1920, height=1080, framerate=30/1
它的 src pad 的 Caps 则是:
video/x-raw, format=I420, width=1920, height=1080
GStreamer 在连接两个 Pad 时,会检查它们的 Caps 是否兼容。如果不兼容,gst_pad_link() 会返回 GST_PAD_LINK_NOT_NEGOTIATED。
gst_pad_get_current_caps() 打印出来才发现问题。
四、实战:检查 Caps 的小工具
调试时,我经常用这个代码片段来查看某个 Pad 的 Caps:
void print_pad_caps(GstPad *pad) {
GstCaps *caps = gst_pad_get_current_caps(pad);
if (caps) {
gchar *caps_str = gst_caps_to_string(caps);
g_print("Pad %s 的 Caps: %s\n", GST_PAD_NAME(pad), caps_str);
g_free(caps_str);
gst_caps_unref(caps);
} else {
g_print("Pad %s 还没有协商好 Caps\n", GST_PAD_NAME(pad));
}
}
你可以在 Pipeline 运行后,在回调里调用这个函数,看看数据到底长什么样。
五、两种构建方式的对比
| 对比项 | gst_parse_launch() | 手动构建 |
|---|---|---|
| 代码量 | 少,一行搞定 | 多,每个元素都要创建和连接 |
| 灵活性 | 低,难以处理动态 Pad | 高,可以精确控制每个细节 |
| 调试难度 | 字符串错误难排查 | 可以加断点、打印日志 |
| 适用场景 | 快速原型、简单 Pipeline | 生产环境、复杂链路 |
我个人建议:原型阶段用 gst_parse_launch(),正式代码用手动构建。这样既快又稳。
好了,这一章的内容就到这里。你掌握了 Pipeline 的两种构建方式,也理解了 Pad 和 Caps 的核心概念。下一章咱们会深入多线程——Pipeline 跑起来之后,怎么让各个元素并行工作,不卡顿。