使用gstreamer播放音视频都知道,当我们直接通过playbin播放视频的时候,playbin会根据当前播放的音视频数据自动查找相应的element添加到pipeline对数据进行下一步处理,那么,当playbin在解析数据的时候,发现上一个element需要某一个caps时,是谁来查找,究竟有哪个element支持这个caps的处理,然后又选择它添加到pipeline中呢?这个,就是我们今天的主角----typefind完成的。下面,我们通过使用playbin播放一个编码格式为H264、封装格式为MP4的本地文件,了解playbin是如何根据播放的内容逐个查找相应的element,它又是如何完成这些操作的呢。
首先,你希望可以查找别人,前提你得是合法的,所以也会将自己注册到gstreamer系统。首先的,typefind的plugin注册函数在gstreamer-xxx/plugins/elements目录下的gstelements.c,在该文件中,就有一个plugin_init()
函数将它注册到gstreamer core。注册完成之后,gstreamer就可以使用该类型的element。
playbin,在gstreamer中是一个element,只不过是特殊的element,一个bin,这个bin中还会包含着其他的bin,比如说uridecodebin将会解析源的uri以及解码,而decodebin又会比它小一层,将会负责解封装解码等操作。而typefind又是什么时候登场的呢,它是包含在decodebin中,在decodebin的init()函数有以下操作。
static void
gst_decode_bin_init (GstDecodeBin * decode_bin)
{
...
/* we create the typefind element only once */
decode_bin->typefind = gst_element_factory_make ("typefind", "typefind");
...
}
在uridecodebin创建decodebin时,会将其添加到uridecodebin,而decodebin也会将typefind添加到自身,接下来就会是bin中的各个element状态切换。
在decodebin状态切换到PAUSED的时候,也会带动typefind的状态切换为PAUSED。在激活pad的时候,有查询scheduling,确定使用push模式还是pull模式。而typefind将会选择PULL模式,将会创建task运行gst_type_find_element_loop()函数。
typefind的功能,很像一个查找器,是从source读取到数据,然后从这部分数据解析信息,查找相应的caps,知道相应的caps之后,接着将会把caps信息通过信号发送出去,等待别人的处理。下面来看看在task中是如何查找的。
static void
gst_type_find_element_loop (GstPad * pad)
{
if (typefind->mode == MODE_TYPEFIND) {
/* 这个peer一般是proxy pad */
peer = gst_pad_get_peer (pad);
if (peer) {
gchar *ext;
...
/* 查询文件的大小等信息 */
if (!gst_pad_query_duration (peer, GST_FORMAT_BYTES, &size)) {
GST_WARNING_OBJECT (typefind, "Could not query upstream length!");
gst_object_unref (peer);
ret = GST_FLOW_ERROR;
goto pause;
}
/* 通过上游的uri,查询source的文件后缀,比如返回mp4 */
ext = gst_type_find_get_extension (typefind, pad);
/* 查找caps */
found_caps =
gst_type_find_helper_get_range (GST_OBJECT_CAST (peer),
GST_OBJECT_PARENT (peer),
(GstTypeFindHelperGetRangeFunction) (GST_PAD_GETRANGEFUNC (peer)),
(guint64) size, ext, &probability);
...
}
...
}
...
}
gst_type_find_helper_get_range()函数又是如何查找caps的呢,将会是先根据后缀名,对gstreamer支持的caps进行一个排序,方便后续对比,排序之后,将会预读取数据,分析具体的数据,是否可以使用相应的caps。下面我们将一起来看代码。
GstCaps *
gst_type_find_helper_get_range (GstObject * obj, GstObject * parent,
GstTypeFindHelperGetRangeFunction func, guint64 size,
const gchar * extension, GstTypeFindProbability * prob)
{
...
/* 1. 通过该函数,将会返回注册到gstreamer core的
* GST_TYPE_TYPE_FIND_FACTORY型GstPluginFeature */
type_list = gst_type_find_factory_get_list ();
/* 2. 将会在这里,根据之前从uri得到的文件后缀,对Feature链表
* 进行排序,文件后缀名与feature的extension信息对比,相同的
* 将会将feature移动到表头 */
if (extension) {
for (l = type_list; l; l = next) {
...
}
}
...
/* 3. 将会在这里调用feature的功能探测函数预读取数据,确认文件数据格式 */
for (l = type_list; l; l = l->next) {
helper.factory = GST_TYPE_FIND_FACTORY (l->data);
gst_type_find_factory_call_function (helper.factory, &find);
if (helper.best_probability >= GST_TYPE_FIND_MAXIMUM)
break;
}
...
}
可能看到上面的代码注释觉得很困惑,第1和第3步,调用到的究竟是什么,看完下面就知道了。
第一步的操作,上面已经说了,该函数将会返回GST_TYPE_TYPE_FIND_FACTORY
类型的feature,这些feature都是查找类型的feature。它们在注册feature的时候,将会注册相应的探测函数,在探测函数中通过读取数据,然后判断类型,是否需要该feature可以解析处理的,就是这样的一个功能,gstreamer才会知道,当前数据该用那个caps。
这些feature的注册是在gst-plugins-base-1.xx.xx/gst/typefind目录下的gsttypefindfunctions.c
文件注册的,在该文件的plugin_init()
函数中分别通过宏TYPE_FIND_REGISTER_START_WITH
、TYPE_FIND_REGISTER_RIFF
、TYPE_FIND_REGISTER
向gstreamer core注册了相应的GST_TYPE_TYPE_FIND_FACTORY
类型的feature,在注册的时候,赋值了相应的探测function、extension等信息。接着,在我们通过gst_type_find_helper_get_range()
函数探测caps的时候,就会有第一步的获取feature,第二步的根据extension排序,第三步的通过功能探测函数确认最终的feature。
介绍到这里,小伙伴们应该知道gst_type_find_helper_get_range()返回的是什么了吧,它返回的就是支持解析、处理输入数据的caps,比如输入的是封装格式为MOV的mp4文件,那么返回的是video/quicktime;输入的是编码格式为H264的RAW数据,返回的是video/x-h264,这样说,应该大家都清楚了吧。
回到gst_type_find_element_loop()函数,当查找到caps的时候,又将会发送相应的信号,代码如下:
static void
gst_type_find_element_loop (GstPad * pad)
{
if (typefind->mode == MODE_TYPEFIND) {
...
/* 查找caps */
...
...
/* Set to MODE_NORMAL before emitting have-type, in case it triggers a seek */
typefind->mode = MODE_NORMAL;
gst_type_find_element_emit_have_type (typefind, probability, found_caps);
}
...
}
在gst_type_find_element_loop()函数中通过gst_type_find_element_emit_have_type()
函数将caps信息生成caps EVENT然后保存在pad,接着发送have-type
信号。谁来接收这个信号呢,往下看。
上面说到的,typefind发送信号,哪里接收呢。之前有提到过,这个typefind是包含在decodebin,在decodebin的init()函数创建typefind实例对象。而在decodebin的状态切换函数,从READY切换到PAUSED
的时候将会监听该信号,接收到该信号将会调用decodebin文件的type_found()
函数。
static GstStateChangeReturn
gst_decode_bin_change_state (GstElement * element, GstStateChange transition)
{
switch (transition) {
case GST_STATE_CHANGE_READY_TO_PAUSED:
...
/* connect a signal to find out when the typefind element found
* a type */
dbin->have_type_id =
g_signal_connect (dbin->typefind, "have-type",
G_CALLBACK (type_found), dbin);
...
break;
...
}
...
}
在type_found()
函数中,最终将会调用analyze_new_pad()
函数。在该函数进行了许多操作,会检查当前decodebin的elements状态,检查现在的数据状态是流通到什么位置,typefind传递过来的是什么类型的feature,是Parser还是Converter还是其他,当前的caps是什么类型的caps,最后确认之后才会根据caps查找feature。我们重点介绍一下根据caps查找feature,在该函数将会发送autoplug-factories
信号。最终的,逐个传递将会在playbin真正处理这个信号,处理该信号的函数是autoplug_factories_cb()
。
static gboolean
analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad,
GstCaps * caps, GstDecodeChain * chain, GstDecodeChain ** new_chain)
{
...
/* 1.d else get the factories and if there's no compatible factory goto
* unknown_type */
g_signal_emit (G_OBJECT (dbin),
gst_decode_bin_signals[SIGNAL_AUTOPLUG_FACTORIES], 0, dpad, caps,
&factories);
...
}
autoplug_factories_cb()函数将会从gstreamer core中获取所有的feature,然后对比它们的sink pad caps,如果一致,将会把该feature保存到链表,进行相应的检查之后将返回feature链表。
static GValueArray *
autoplug_factories_cb (GstElement * decodebin, GstPad * pad,
GstCaps * caps, GstSourceGroup * group)
{
/* 将根据caps查找sink pad支持该caps的element返回factory_list */
g_mutex_lock (&playbin->elements_lock);
gst_play_bin_update_elements_list (playbin);
factory_list =
gst_element_factory_list_filter (playbin->elements, caps, GST_PAD_SINK,
gst_caps_is_fixed (caps));
g_mutex_unlock (&playbin->elements_lock);
...
}
得到支持caps的element之后,回到analyze_new_pad()函数,在该函数中将会通过connect_pad()
函数创建并连接该element,创建的函数是element = gst_element_factory_create (factory, NULL)
,得到element之后,将其添加到bin。最后就是element的状态切换与查询了。在这个element查找的功能,bin进行了很多工作,会有根据caps查找到的element进行不同的分类查找处理等操作,感兴趣的具体可以看看代码。
同时的,decodebin还监控pad-added
信号,在相应的element添加pad时,发送该信号,调用到decodebin的pad_added_cb()
函数,在该函数中最终也还是会调用到analyze_new_pad()函数进行查找element以及link等操作,具体的操作,可以看代码,这一块没有往下跟踪了。
typefind的功能,并不是它单纯就完成相应的element查找功能,与bin交互较多,它更多的是根据注册的caps探测函数,查找相应的caps,然后将caps返回给bin,bin再根据caps从gstreamer core查找element并创建连接。而typefind则像完成一部分功能一样,往前推进,继续查找解析数据的caps。