Gstreamer pipeline由多个元素组成来处理多媒体数据,元素间通过pad通信。pad 间通信有什么要求?
pad caps
pad capabilities定义了可以流经此pad的数据类型。通过gst-inspect-1.0可以查看某插件支持的pad capabilities,譬如下面查看了audioconvert插件,Capabilities字段显示了src和sink pad支持的capabilities(。。。表示省略部分内容)。
# gst-inspect-1.0 audioconvert
。。。
Pad Templates:
SRC template: 'src'
Availability: Always
Capabilities:
audio/x-raw
format: { (string)S8, (string)U8, (string)S16LE, (string)S16BE, (string)U16LE, (string)U16BE, (string)S24_32LE, (string)S24_32BE, (string)U24_32LE, (string)U24_32BE, (string)S32LE, (str
ing)S32BE, (string)U32LE, (string)U32BE, (string)S24LE, (string)S24BE, (string)U24LE, (string)U24BE, (string)S20LE, (string)S20BE, (string)U20LE, (string)U20BE, (string)S18LE, (string)S18BE, (string)U18
LE, (string)U18BE, (string)F32LE, (string)F32BE, (string)F64LE, (string)F64BE }
rate: [ 1, 2147483647 ]
channels: [ 1, 2147483647 ]
layout: { (string)interleaved, (string)non-interleaved }
SINK template: 'sink'
Availability: Always
Capabilities:
audio/x-raw
format: { (string)S8, (string)U8, (string)S16LE, (string)S16BE, (string)U16LE, (string)U16BE, (string)S24_32LE, (string)S24_32BE, (string)U24_32LE, (string)U24_32BE, (string)S32LE, (str
ing)S32BE, (string)U32LE, (string)U32BE, (string)S24LE, (string)S24BE, (string)U24LE, (string)U24BE, (string)S20LE, (string)S20BE, (string)U20LE, (string)U20BE, (string)S18LE, (string)S18BE, (string)U18
LE, (string)U18BE, (string)F32LE, (string)F32BE, (string)F64LE, (string)F64BE }
rate: [ 1, 2147483647 ]
channels: [ 1, 2147483647 ]
layout: { (string)interleaved, (string)non-interleaved }
。。。
两pad 能够连接的前提条件是它们的pad capabilities有交集,两pad经协商,最终会使用相同的pad capability。我们再看下wavescope插件支持的capabilities
# gst-inspect-1.0 wavescope
。。。
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
audio/x-raw
format: S16LE
layout: interleaved
rate: [ 8000, 96000 ]
channels: 2
channel-mask: 0x0000000000000003
SRC template: 'src'
Availability: Always
Capabilities:
video/x-raw
format: BGRx
width: [ 1, 2147483647 ]
height: [ 1, 2147483647 ]
framerate: [ 0/1, 2147483647/1 ]
。。。
wavescope插件sink pad capabilities和audioconvert插件 src pad capabilities有交集,这两个插件的pad可以连接,其中交集如下:
Capabilities:
audio/x-raw
format: S16LE
layout: interleaved
rate: [ 8000, 96000 ]
channels: 2
channel-mask: 0x0000000000000003
有些插件的pad是sometimes pad,通过gst-inspect-1.0不能直接查看其支持的pad capabilities,譬如查看uridecodebin的pad capabilities如下:
# gst-inspect-1.0 uridecodebin
。。。
Pad Templates:
SRC template: 'src_%u'
Availability: Sometimes
Capabilities:
ANY
。。。
对于此情况,我们可以在pipeline执行时,实时打印某个元素的pad capability。
代码示例
我们创建一个pipeline(代码文件demo.c省略部分代码),观察下souce元素sometimes pad和audio_convert元素 src pad的capabilities
pipeline1
。。。
/* Functions below print the Capabilities in a human-friendly format */
// 以阅读友好方式打印capabilitiy field
static gboolean print_field (GQuark field, const GValue * value, gpointer pfx) {
gchar *str = gst_value_serialize (value);
g_print ("%s %15s: %s\n", (gchar *) pfx, g_quark_to_string (field), str);
g_free (str);
return TRUE;
}
// 打印pad capabilities
static void print_caps (const GstCaps * caps, const gchar * pfx) {
guint i;
g_return_if_fail (caps != NULL);
if (gst_caps_is_any (caps)) {
g_print ("%sANY\n", pfx);
return;
}
if (gst_caps_is_empty (caps)) {
g_print ("%sEMPTY\n", pfx);
return;
}
for (i = 0; i < gst_caps_get_size (caps); i++) {
GstStructure *structure = gst_caps_get_structure (caps, i);
g_print ("%s%s\n", pfx, gst_structure_get_name (structure));
gst_structure_foreach (structure, print_field, (gpointer) pfx);
}
}
/* Shows the CURRENT capabilities of the requested pad in the given element */
// 根据pad 名称获取元素pad,然后打印pad capabilities
static void print_pad_capabilities (GstElement *element, gchar *pad_name) {
GstPad *pad = NULL;
GstCaps *caps = NULL;
/* Retrieve pad */
// 根据pad 名称获取元素pad
pad = gst_element_get_static_pad (element, pad_name);
if (!pad) {
g_printerr ("Could not retrieve pad '%s'\n", pad_name);
return;
}
/* Retrieve negotiated caps (or acceptable caps if negotiation is not finished yet) */
// 若协商完成获取协商好的pad capabilities;若协商未完成获取此pad 可接受的capabilities
caps = gst_pad_get_current_caps (pad);
if (!caps)
caps = gst_pad_query_caps (pad, NULL);
g_print ("Caps for the %s pad:\n", pad_name);
print_caps (caps, " ");
gst_caps_unref (caps);
gst_object_unref (pad);
}
/* Handler for the pad-added signal */
// 定义函数处理pad-added 信号
static void pad_added_handler (GstElement *src, GstPad *pad, CustomData *data);
int main(int argc, char *argv[]) {
。。。。
/* Create the elements */
// 根据插件名称,创建元素
data.source = gst_element_factory_make ("uridecodebin", "source");
data.tee = gst_element_factory_make ("tee", "tee");
data.audio_queue = gst_element_factory_make ("queue", "audio_queue");
data.audio_convert = gst_element_factory_make ("audioconvert", "audio_convert");
data.audio_resample = gst_element_factory_make ("audioresample", "audio_resample");
data.audio_sink = gst_element_factory_make ("autoaudiosink", "audio_sink");
data.video_queue = gst_element_factory_make ("queue", "video_queue");
data.visual = gst_element_factory_make ("wavescope", "visual");
data.video_convert = gst_element_factory_make ("videoconvert", "video_convert");
data.video_sink = gst_element_factory_make ("autovideosink", "video_sink");
。。。
/* Set the URI to play */
// 设置source元素的uri 配置
g_object_set (data.source, "uri",
"https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm", NULL);
/* Configure elements */
// 设置visual元素
g_object_set (data.visual, "shader", 0, "style", 1, NULL);
/* Link all elements that can be automatically linked because they have "Always" pads */
// 含有always pad 的元素可以自动连接
gst_bin_add_many (GST_BIN (data.pipeline), data.source, data.tee, data.audio_queue, data.audio_convert,
data.audio_resample, data.audio_sink, data.video_queue, data.visual, data.video_convert, data.video_sink, NULL);
if (gst_element_link_many (data.audio_convert, data.tee, NULL) != TRUE ||
gst_element_link_many (data.audio_queue, data.audio_resample, data.audio_sink, NULL) != TRUE ||
gst_element_link_many (data.video_queue, data.visual, data.video_convert, data.video_sink, NULL) != TRUE) {
g_printerr ("Elements could not be linked.\n");
gst_object_unref (data.pipeline);
return -1;
}
。。。
/* Connect to the pad-added signal */
// 当source元素发出pad-added信号时,调用pad_added_handler 函数
g_signal_connect (data.source, "pad-added", G_CALLBACK (pad_added_handler), &data);
/* Start playing the pipeline */
// 把gst pipeline 状态设置为GST_STATE_PLAYING
gst_element_set_state (data.pipeline, GST_STATE_PLAYING);
/* Wait until error or EOS */
// 直到出现错误或视频流结束时才退出pipeline
bus = gst_element_get_bus (data.pipeline);
do {
msg = gst_bus_timed_pop_filtered (bus, GST_CLOCK_TIME_NONE, GST_MESSAGE_ERROR | GST_MESSAGE_EOS |
GST_MESSAGE_STATE_CHANGED);
/* Parse message */
// 解析bus 消息
if (msg != NULL) {
GError *err;
gchar *debug_info;
switch (GST_MESSAGE_TYPE (msg)) {
。。。
case GST_MESSAGE_STATE_CHANGED:
/* We are only interested in state-changed messages from the pipeline */
// 这里我们只关注pipeline 的状态变化消息
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
g_print ("\nPipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
// 打印audio_convert元素src pad capabilities
print_pad_capabilities(data.audio_convert, "src");
}
break;
default:
/* We should not reach here because we only asked for ERRORs, EOS and STATE_CHANGED */
g_printerr ("Unexpected message received.\n");
break;
}
gst_message_unref (msg);
}
} while (!terminate);
。。。
}
/* This function will be called by the pad-added signal */
// 这个函数处理pad-added 信号,这里的new_pad 就是sometimes pad
static void pad_added_handler (GstElement *src, GstPad *new_pad, CustomData *data) {
GstPad *sink_pad = gst_element_get_static_pad (data->audio_convert, "sink");
。。。
/* Check the new pad's type */
// 检查new_pad类型是否为audio/x-raw, 若不是则退出
new_pad_caps = gst_pad_get_current_caps (new_pad);
new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
new_pad_type = gst_structure_get_name (new_pad_struct);
if (!g_str_has_prefix (new_pad_type, "audio/x-raw")) {
g_print ("It has type '%s' which is not raw video. Ignoring.\n", new_pad_type);
goto exit;
}
/* Attempt the link */
// 连接new_pad和sink_pad
ret = gst_pad_link (new_pad, sink_pad);
if (GST_PAD_LINK_FAILED (ret)) {
g_print ("Type is '%s' but link failed.\n", new_pad_type);
} else {
g_print ("Link succeeded (type '%s').\n", new_pad_type);
}
// 打印刚产生的audio sometimes pad capabilities
GstCaps *caps = gst_pad_get_current_caps (new_pad);
g_print("-----------------------uridecodebin audio sometimes pad-------------------------\n");
print_caps (caps, " ");
gst_caps_unref (caps);
g_print("-----------------------uridecodebin audio sometimes pad-------------------------\n");
。。。
}
代码解析
在pipeline状态变化时,打印audio_convert元素的src pad capabilities。
case GST_MESSAGE_STATE_CHANGED:
/* We are only interested in state-changed messages from the pipeline */
// 这里我们只关注pipeline 的状态变化消息
if (GST_MESSAGE_SRC (msg) == GST_OBJECT (data.pipeline)) {
GstState old_state, new_state, pending_state;
gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state);
g_print ("\nPipeline state changed from %s to %s:\n",
gst_element_state_get_name (old_state), gst_element_state_get_name (new_state));
// 打印audio_convert元素src pad capabilities
print_pad_capabilities(data.audio_convert, "src");
在source元素解析到audio数据时,会产生sometimes pad,此时我们打印此pad的capabilities
GstCaps *caps = gst_pad_get_current_caps (new_pad);
g_print("-----------------------uridecodebin audio sometimes pad-------------------------\n");
print_caps (caps, " ");
gst_caps_unref (caps);
g_print("-----------------------uridecodebin audio sometimes pad-------------------------\n");
编译并执行demo.c,会有下面日志:
Obtained request pad src_1 for audio branch.
Obtained request pad src_2 for video branch.
Pipeline state changed from NULL to READY:
Caps for the src pad:
audio/x-raw
rate: [ 1, 2147483647 ]
format: { (string)S8, (string)U8, (string)S16LE, (string)S16BE, (string)U16LE, (string)U16BE, (string)S24_32LE, (string)S24_32BE, (string)U24_32LE, (string)U24_32BE, (s
tring)S32LE, (string)S32BE, (string)U32LE, (string)U32BE, (string)S24LE, (string)S24BE, (string)U24LE, (string)U24BE, (string)S20LE, (string)S20BE, (string)U20LE, (string)U20BE, (string
)S18LE, (string)S18BE, (string)U18LE, (string)U18BE, (string)F32LE, (string)F32BE, (string)F64LE, (string)F64BE }
channels: [ 1, 2147483647 ]
layout: { (string)interleaved, (string)non-interleaved }
Received new pad 'src_0' from 'source':
It has type 'video/x-raw' which is not raw video. Ignoring.
Received new pad 'src_1' from 'source':
Link succeeded (type 'audio/x-raw').
-----------------------uridecodebin audio sometimes pad-------------------------
audio/x-raw
format: F32LE
layout: interleaved
rate: 48000
channels: 2
channel-mask: 0x0000000000000003
-----------------------uridecodebin audio sometimes pad-------------------------
Pipeline state changed from READY to PAUSED:
Caps for the src pad:
audio/x-raw
rate: 48000
format: S16LE
channels: 2
layout: interleaved
channel-mask: 0x0000000000000003
Pipeline state changed from PAUSED to PLAYING:
Caps for the src pad:
audio/x-raw
rate: 48000
format: S16LE
channels: 2
layout: interleaved
channel-mask: 0x0000000000000003
End-Of-Stream reached.
(uridecodebin2audioconvert:50010): GStreamer-CRITICAL **: 14:21:19.751: gst_mini_object_unref: assertion 'GST_MINI_OBJECT_REFCOUNT_VALUE (mini_object) > 0' failed
分析日志,audio_convert元素的src capability最终固定为
audio/x-raw
rate: 48000
format: S16LE
channels: 2
layout: interleaved
channel-mask: 0x0000000000000003
source元素的sometimes pad capability为
audio/x-raw
format: F32LE
layout: interleaved
rate: 48000
channels: 2
channel-mask: 0x0000000000000003
这两个capability format 不同,audio_convert元素把 sometimes pad capability转换为自己需要的src pad capability。
audio_convert元素的src pad capability是如何产生的呢?audio_convert与后面audio相关元素(即audio_resample, visual)pad capabilities交集(可通过gst-inspect-1.0自行查看)为
audio/x-raw
format: S16LE
layout: interleaved
rate: [ 8000, 96000 ]
channels: 2
channel-mask: 0x0000000000000003
与source元素的sometimes pad capability交集如下,两者format不同,audio_convert元素需要把format: F32LE转换为format: S16LE
audio/x-raw
rate: 48000
channels: 2
layout: interleaved
channel-mask: 0x0000000000000003
其他分析
pipeline2
若修改pipeline1中元素位置,变为pipeline2,source元素设置的多媒体源仍然为“https://www.freedesktop.org/software/gstreamer-sdk/data/media/sintel_trailer-480p.webm”,pipeline2会运行失败:source元素与tee元素连接失败。为什么呢?
tee后面audio相关元素(即audio_convert, audio_resample, visual)pad capabilities交集(可通过gst-inspect-1.0自行查看)为
audio/x-raw
format: S16LE
layout: interleaved
rate: [ 8000, 96000 ]
channels: 2
channel-mask: 0x0000000000000003
与source元素sometimes pad capability(它的format 为F32LE)的format不同,因此两者连接失败。
通过对pipeline1的分析,我们知道visual元素的 sink pad capabilities会影响到audio_convert的src pad capabilities,其中的实现机制是什么?下节我们会介绍不同元素pad间协商机制