GStreamer 开发记录(二):核心概念与手工构建管道

4 阅读4分钟

背景

开发记录一用 playbin 一行搞定,但它是"黑盒"。记录二手工逐个创建元件并链接,真正理解 GStreamer 的四个核心对象:

  • Element(元件)
  • Pad(衬垫)
  • Bin(箱柜)
  • Pipeline(管道)
  • Caps(能力协商)

四个核心对象

概念定义类比
Element管道中的一个功能单元。按数据流方向分 source、filter、sink工厂里的机器
Pad元件的输入/输出端口。sink pad 接收,src pad 产出机器的进料口/出料口
Bin装载元件的容器,对外行为像一个元件车间
Pipeline一种特殊的 Bin,代表完整可运行的数据流,负责时钟与消息总线整条生产线

图示:

        ┌──────────────────── Pipeline (Bin) ────────────────────┐
        │                                                        │
        │  ┌──────────────┐              ┌──────────────┐        │
        │  │ videotestsrc │              │autovideosink │        │
        │  │              │              │              │        │
        │  │       src pad│──── link ───▶│sink pad      │        │
        │  └──────────────┘              └──────────────┘        │
        │       source                          sink             │
        └────────────────────────────────────────────────────────┘

链接的本质 = pad 与 pad 相连。

手工建管道的 API 流程

1. gst_element_factory_make("类型名", "实例名")  创建元件
2. gst_pipeline_new("名字")                      创建空管道
3. gst_bin_add_many(GST_BIN(pipeline), e1, e2, ..., NULL)  加元件
4. gst_element_link(e1, e2) 或 gst_element_link_many(...)  链接
5. g_object_set(element, "属性", 值, NULL)       设属性
6. gst_element_set_state(pipeline, GST_STATE_PLAYING)  播放
7. 等待消息 → 收尾

完整代码

#include <gst/gst.h>

int main (int argc, char *argv[]) {
  GstElement *pipeline, *source, *sink;
  GstBus *bus;
  GstMessage *msg;
  GstStateChangeReturn ret;

  gst_init (&argc, &argv);

  /* 1. 创建元件 */
  source = gst_element_factory_make ("videotestsrc", "source");
  sink   = gst_element_factory_make ("autovideosink", "sink");
  pipeline = gst_pipeline_new ("test-pipeline");

  if (!pipeline || !source || !sink) {
    g_printerr ("Not all elements could be created.\n");
    return -1;
  }

  /* 2. 加入管道 */
  gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);

  /* 3. 链接(内部即 pad 与 pad 相连) */
  if (gst_element_link (source, sink) != TRUE) {
    g_printerr ("Elements could not be linked.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* 4. 设置属性:测试图案类型 */
  g_object_set (source, "pattern", 0, NULL);

  /* 5. 播放 */
  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
  if (ret == GST_STATE_CHANGE_FAILURE) {
    g_printerr ("Unable to set the pipeline to the playing state.\n");
    gst_object_unref (pipeline);
    return -1;
  }

  /* 6. 等待 ERROR 或 EOS */
  bus = gst_element_get_bus (pipeline);
  msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE,
      GST_MESSAGE_ERROR | GST_MESSAGE_EOS);

  if (msg != NULL) gst_message_unref (msg);
  gst_object_unref (bus);
  gst_element_set_state (pipeline, GST_STATE_NULL);
  gst_object_unref (pipeline);
  return 0;
}

逐段解析

1. 创建元件

source = gst_element_factory_make ("videotestsrc", "source");
sink   = gst_element_factory_make ("autovideosink", "sink");
pipeline = gst_pipeline_new ("test-pipeline");
函数作用
gst_element_factory_make(类型, 名字)创建元件
gst_pipeline_new(名字)创建管道

常用元件:

元件作用
videotestsrc测试视频源(彩条)
audiotestsrc测试音频源(正弦波)
filesrc文件源
autovideosink自动视频输出
autoaudiosink自动音频输出
videoconvert视频格式转换

元件名写错,编译不报错,运行时返回 NULL。

2. 加入管道

gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
  • GstBin 是容器基类,GstPipeline 继承自它
  • GST_BIN(pipeline) 是类型转换宏
  • NULL 是可变参数结束标志

为什么必须加入管道? 管道负责元件的生命周期、状态、时钟同步。

3. 链接

if (gst_element_link (source, sink) != TRUE) { ... }

gst_element_link 自动找 source 的 src pad 和 sink 的 sink pad 并连接。

等价于:

gst_element_link_pads(source, "src", sink, "sink");

4. 设置属性

g_object_set (source, "pattern", 0, NULL);

videotestsrc 的 pattern 属性:

值图案
0SMPTE 彩条
1雪花
2纯色
3棋盘

g_object_set 是可变参数函数,最后必须传 NULL。

5. 播放

ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) { ... }

返回值:

返回值含义
GST_STATE_CHANGE_SUCCESS成功
GST_STATE_CHANGE_ASYNC异步,稍后完成
GST_STATE_CHANGE_FAILURE失败

异步返回 ASYNC 不算失败。

6. 等待消息

和开发记录(一)一样,从总线阻塞等待 ERROR 或 EOS。

7. 释放资源

和开发记录(一)一样,注意先停止管道,再释放管道。

为什么需要转换元件(videoconvert / audioconvert)

官方原话:

conversion elements 是必要的,用来保证管道可以链接。因为 sink 的 Caps 取决于硬件,你在设计时并不知道它是否与上游产生的 Caps 匹配。如果 Caps 匹配,这些元件会工作在 'pass-through' 模式,不修改信号,对性能影响可忽略。

经验法则:

只要你在用 autovideosink / autoaudiosink 这类"Caps 设计时不确定的"元件,或解码用户提供的内容,就始终插一个 videoconvert / audioconvert。

Caps 匹配时,转换元件是 pass-through,几乎零开销。

编译与运行

gcc 2-manual_pipeline.c -o 2-manual_pipeline $(pkg-config --cflags --libs gstreamer-1.0)
./2-manual_pipeline

等价命令行(加 -v 可看到协商出的 Caps):

gst-launch-1.0 videotestsrc pattern=0 ! autovideosink

本课小结

点内容
核心对象Element 是功能单元;Pad 是其端口;链接 = pad 相连;Bin 是容器;Pipeline 是可运行的完整 Bin
手工建管道五步factory_make → pipeline_new → bin_add_many → element_link → set_state
转换元件Caps 不确定时就插 videoconvert / audioconvert;匹配时是 pass-through,几乎零开销