Gstreamer学习笔记(11):typefind功能流程简单分析

使用gstreamer播放音视频都知道,当我们直接通过playbin播放视频的时候,playbin会根据当前播放的音视频数据自动查找相应的element添加到pipeline对数据进行下一步处理,那么,当playbin在解析数据的时候,发现上一个element需要某一个caps时,是谁来查找,究竟有哪个element支持这个caps的处理,然后又选择它添加到pipeline中呢?这个,就是我们今天的主角----typefind完成的。下面,我们通过使用playbin播放一个编码格式为H264、封装格式为MP4的本地文件,了解playbin是如何根据播放的内容逐个查找相应的element,它又是如何完成这些操作的呢。

一、typefind的创建

首先,你希望可以查找别人,前提你得是合法的,所以也会将自己注册到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查找

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_WITHTYPE_FIND_REGISTER_RIFFTYPE_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信号。谁来接收这个信号呢,往下看。

三、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。

你可能感兴趣的:(GStreamer,typefind,gstdecoderbin,element)