4、Caps协商机制:什么是Caps、Caps的结构、Caps协商过程、固定Caps与可变Caps、实战:查看Caps信息
好,咱们今天聊聊Caps协商。说实话,这是GStreamer里最容易让人懵圈的概念之一。我刚开始接触的时候,也被它绕得晕头转向。但等你真正理解了,就会发现它其实特别巧妙——说白了,它就是管道里各个元件之间互相确认「你能给我什么格式的数据?我该给你什么格式?」的一套沟通规则。
什么是Caps?
Caps,全称是Capabilities,翻译过来就是「能力」。但我觉得叫「格式描述」更贴切。每个元件都有自己支持的格式范围,比如视频解码器能输出什么分辨率、什么色彩空间,编码器能接受什么输入格式。Caps就是用来描述这些信息的。
我打个比方你就明白了。你想想看,这就像两个人要合作干活,一个人说「我只会用中文交流」,另一个说「我只会用英文」。那他俩就没法沟通。但如果一个人说「我可以用中文或英文」,另一个说「我英文还行」,那就能用英文合作。Caps协商干的就是这个事——让上下游元件找到双方都支持的格式。
核心理解:Caps不是数据本身,而是对数据格式的描述。它告诉管道:「我这边出来的数据长什么样」。
Caps的结构
Caps的结构其实不复杂。我拆开给你看:
- Caps:最外层,可以包含多个结构体(Structure)
- Structure:每个结构体描述一种可能的格式,比如视频宽高、编码类型
- Field:结构体里的字段,比如width=1920, height=1080
举个例子,一个典型的视频Caps长这样:
video/x-raw, format=I420, width=[16, 1920], height=[16, 1080], framerate=[0/1, 30/1]
这里面:
video/x-raw是媒体类型,表示未压缩的视频format=I420是色彩格式width=[16, 1920]表示宽度范围从16到1920framerate=[0/1, 30/1]表示帧率范围
嗯,这里要注意:方括号表示这是一个范围,不是固定值。这在协商时特别重要。
Caps协商过程
协商过程,说白了就是上下游元件互相「讨价还价」。我画个流程给你看:
- 上游元件说:「我能输出这些格式」——它把自己的Caps贴出来
- 下游元件说:「我只能接受这些格式」——它也贴出自己的Caps
- 双方取交集:找到共同支持的格式
- 固定Caps:从交集中选一个具体值,确定下来
我在项目中遇到过一个问题:一个解码器输出Caps是 width=[16, 3840],下游的显示元件只支持 width=[320, 1920]。按理说取交集应该是 [320, 1920],但当时因为一个字段没对齐,协商失败了。排查了半天才发现是framerate字段的格式写错了。
我的经验:调试Caps协商问题时,最常用的命令就是 GST_DEBUG=*CAPS*:5。它能打印出所有Caps协商的细节,谁拒绝了谁、为什么拒绝,一目了然。
固定Caps与可变Caps
这个区分很重要,我当年就吃过亏。
固定Caps(Fixed Caps):所有字段都是具体值,没有范围。比如:
video/x-raw, format=I420, width=1920, height=1080, framerate=30/1
这种Caps表示「我只能处理这个精确的格式」,没得商量。
可变Caps(Variable Caps):包含范围或列表。比如:
video/x-raw, format={I420, NV12}, width=[16, 1920], height=[16, 1080]
这种Caps表示「我比较灵活,这些格式我都能接受」。
你想想看,为什么要有这个区分?因为有些元件是固定的,比如一个硬件编码器,它可能只支持一种分辨率。而有些元件是灵活的,比如一个缩放器,它能处理各种尺寸。协商的过程,就是把可变Caps逐步缩小,最终变成固定Caps。
避坑指南:我曾经写了一个自定义元件,Caps里忘了加framerate字段。结果下游元件一直协商失败,报错信息也不明确。后来才发现,GStreamer要求某些字段必须存在。所以写Caps时,尽量把关键字段都带上,别偷懒。
实战:查看Caps信息
光说不练假把式。咱们来点实际的。怎么查看管道里各个元件的Caps?
方法一:命令行查看
用 gst-inspect-1.0 可以查看元件的模板Caps:
gst-inspect-1.0 videotestsrc | grep "Pad Templates" -A 20
输出会显示这个元件的src pad支持哪些格式。
方法二:运行时查看
写个小程序,在管道运行时打印Caps:
#include <gst/gst.h>
static void on_pad_added(GstElement *src, GstPad *new_pad, gpointer data) {
GstCaps *caps = gst_pad_get_current_caps(new_pad);
if (caps) {
gchar *caps_str = gst_caps_to_string(caps);
g_print("当前Caps: %s\n", caps_str);
g_free(caps_str);
gst_caps_unref(caps);
}
}
int main(int argc, char *argv[]) {
gst_init(&argc, &argv);
GstElement *pipeline = gst_parse_launch(
"videotestsrc ! videoconvert ! autovideosink", NULL);
GstElement *src = gst_bin_get_by_name(GST_BIN(pipeline), "videotestsrc0");
g_signal_connect(src, "pad-added", G_CALLBACK(on_pad_added), NULL);
gst_element_set_state(pipeline, GST_STATE_PLAYING);
// 跑几秒后退出
g_usleep(5000000);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
return 0;
}
方法三:调试日志
这是我最常用的方法:
GST_DEBUG=*CAPS*:5 ./my_app
你会看到类似这样的输出:
0:00:00.123456789 12345 0x7f1234567890 capsnegotiation gstpad.c:XXXX:gst_pad_negotiate<videotestsrc0:src> 进行Caps协商...
0:00:00.123456890 12345 0x7f1234567890 capsnegotiation gstcaps.c:XXXX:gst_caps_intersect 取交集结果: video/x-raw, format=I420, width=1920, height=1080
小技巧:如果你只想看某个特定元件的Caps协商,可以这样:GST_DEBUG=videotestsrc*CAPS*:5。这样日志就不会被其他元件的输出淹没了。
好了,关于Caps协商的核心内容就这些。记住一句话:Caps就是格式的「身份证」,协商就是「对暗号」。对上了,数据就能流通;对不上,管道就罢工。下次遇到管道跑不起来,先查Caps,十有八九能解决问题。