概述
通过Gstreamer官方文档,我们可以知道很多src类型的element都是Basesrc类的子类,因此对Basesrc源码的了解,有助于我们知pipeline数据源头是如何触发数据产生的。本文承接上文,以Filesrc element作为示例进行解说。管道命令如下:
gst-launch-1.0 --gst-debug=filesrc:5,basesrc:5 filesrc location=/oem/200frames_count.h264 ! filesink location=/tmp/flc.mp4
上一篇Filesrc源码分析中,我们只解析了函数调用的流程。本节我们将会看到Filesrc实现的funcs是如何被触发的。本文将结合log和源码尽可能详细的进行解析。由于Basesrc(Filesrc)仍被包含在Pipeline中,因此还有些函数调用的源头和gstreamer的一些机制仍然是透明的,我们暂且把这些透明的逻辑看做是“上帝之手”,随着笔者后面分析,应该能把这个“上帝之手”也给解剖开来。
关于GObject类的继承知识点,可参考
【GObject 对象-类-实例概念介绍】
【GObject:自定义类创建实例(继承、重载)】
对象创建
【Log文件】
FLC-DBG:[BaseSrc] <gst_base_src_class_init> is called!
FLC-DBG:[GstFileSrc] <gst_file_src_class_init> is called!
FLC-DBG:[BaseSrc] <gst_base_src_init> is called!
0:00:00.047699750 728 0x362e5a90 DEBUG basesrc gstbasesrc.c:447:gst_base_src_init:<GstBaseSrc@0x362f4160> creating src pad
0:00:00.048931750 728 0x362e5a90 DEBUG basesrc gstbasesrc.c:450:gst_base_src_init:<GstBaseSrc@0x362f4160> setting functions on src pad
0:00:00.050285083 728 0x362e5a90 DEBUG basesrc gstbasesrc.c:458:gst_base_src_init:<GstBaseSrc@0x362f4160> adding src pad
FLC-DBG:[BaseSrc] <gst_base_src_set_format> is called!
0:00:00.051997166 728 0x362e5a90 DEBUG basesrc gstbasesrc.c:474:gst_base_src_init:<GstBaseSrc@0x362f4160> init done
FLC-DBG:[GstFileSrc] <gst_file_src_init> is called!
FLC-DBG:[BaseSrc] <gst_base_src_set_blocksize> is called!
【对应代码解析】
在管道命令gst-launch-1.0 gst-launch-1.0 --gst-debug=filesrc:5,basesrc:5 filesrc location=/oem/200frames_count.h264 ! filesink location=/tmp/flc.mp4
中,首先需要创建filesrc对象,filesrc类是basesrc的子类(如下类继承关系所示),因此上述log中我们能看到先创建的是basesrc对象。
GObject
╰──GInitiallyUnowned
╰──GstObject
╰──GstElement
╰──GstBaseSrc
╰──filesrc
- filesrcsrc对象的创建
static void
gst_base_src_class_init (GstBaseSrcClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
//获取不同层级的父类指针。
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
// 注册Debug信息,调试时可通过“basesrc”进行设置该element的debug等级
// 可参考上述管道命令。
GST_DEBUG_CATEGORY_INIT (gst_base_src_debug, "basesrc", 0, "basesrc element");
// 初始化四有指针,指向GstBaseSrcPrivate结构。
g_type_class_add_private (klass, sizeof (GstBaseSrcPrivate));
// 获取GstElementClass指针,该变量为全局变量,可通过该变量调用对应的finalize函数。
parent_class = g_type_class_peek_parent (klass);
/* 设置gobject class的成员函数,对象析构时最终会调用finalize,另外设置属性设置和获取api,
* 这些属性分别对应下面针对basesrc object额外声明的属性,比如“blocksize”、"num-buffers"等
*/
gobject_class->finalize = gst_base_src_finalize;
gobject_class->set_property = gst_base_src_set_property;
gobject_class->get_property = gst_base_src_get_property;
// 声明(创建)basesrc object的属性。
g_object_class_install_property (gobject_class, PROP_BLOCKSIZE,
g_param_spec_uint ("blocksize", "Block size",
"Size in bytes to read per buffer (-1 = default)", 0, G_MAXUINT,
DEFAULT_BLOCKSIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class, PROP_NUM_BUFFERS,
g_param_spec_int ("num-buffers", "num-buffers",
"Number of buffers to output before sending EOS (-1 = unlimited)",
-1, G_MAXINT, DEFAULT_NUM_BUFFERS, G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
#ifndef GST_REMOVE_DEPRECATED
g_object_class_install_property (gobject_class, PROP_TYPEFIND,
g_param_spec_boolean ("typefind", "Typefind",
"Run typefind before negotiating (deprecated, non-functional)", FALSE,
G_PARAM_READWRITE | G_PARAM_DEPRECATED | G_PARAM_STATIC_STRINGS));
#endif
g_object_class_install_property (gobject_class, PROP_DO_TIMESTAMP,
g_param_spec_boolean ("do-timestamp", "Do timestamp",
"Apply current stream time to buffers", DEFAULT_DO_TIMESTAMP,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/* 设置gstelement_class的成员函数。
* change_state为状态切换处理函数,比如“NULL TO READY”的状态切换。
* 状态切换是“上帝之手”触发的。
* send_event用于向下游element发送event
*/
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_base_src_change_state);
gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_base_src_send_event);
/* 设置GstBaseSrcClass的成员函数。具体函数功能可参考函数名,另外在流开始过程我们
* 将会展开这些函数逐个分析。
*/
klass->get_caps = GST_DEBUG_FUNCPTR (gst_base_src_default_get_caps);
klass->negotiate = GST_DEBUG_FUNCPTR (gst_base_src_default_negotiate);
klass->fixate = GST_DEBUG_FUNCPTR (gst_base_src_default_fixate);
klass->prepare_seek_segment =
GST_DEBUG_FUNCPTR (gst_base_src_default_prepare_seek_segment);
klass->do_seek = GST_DEBUG_FUNCPTR (gst_base_src_default_do_seek);
klass->query = GST_DEBUG_FUNCPTR (gst_base_src_default_query);
klass->event = GST_DEBUG_FUNCPTR (gst_base_src_default_event);
klass->create = GST_DEBUG_FUNCPTR (gst_base_src_default_create);
klass->alloc = GST_DEBUG_FUNCPTR (gst_base_src_default_alloc);
klass->decide_allocation =
GST_DEBUG_FUNCPTR (gst_base_src_decide_allocation_default);
// 搜了下面宏定义的说明,但笔者不了解其用途,只是模糊知道在Debug时函数名相关,跳过。
/* Registering debug symbols for function pointers */
GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_activate_mode);
GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_event);
GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_query);
GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_getrange);
GST_DEBUG_REGISTER_FUNCPTR (gst_base_src_fixate);
}
上述代码完成了GstBaseSrcClass的初始化。再叨叨下GObject类的继承特点(可参考概述中的博文):对象创建时,先初始化父类,再初始化子类。因此basesrc class初始化之后,开始filesrc class的初始化。
static void
gst_file_src_class_init (GstFileSrcClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
GstBaseSrcClass *gstbasesrc_class;
// 获取各层父类指针。
gobject_class = G_OBJECT_CLASS (klass);
gstelement_class = GST_ELEMENT_CLASS (klass);
gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
// 【代码块标记:gst_file_src_class_init-01】.
gobject_class->set_property = gst_file_src_set_property;
gobject_class->get_property = gst_file_src_get_property;
g_object_class_install_property (gobject_class, PROP_LOCATION,
g_param_spec_string ("location", "File Location",
"Location of the file to read", NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
GST_PARAM_MUTABLE_READY));
gobject_class->finalize = gst_file_src_finalize;
/ * 对gstelement_class的成员进行设置。
* 首先设置element 的metadata,这个metadata主要用于描述该element的基本信息。
* 然后注册静态pad模板,这个pad模板的定义为:
* static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
* GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY);
* /
gst_element_class_set_static_metadata (gstelement_class,
"File Source",
"Source/File",
"Read from arbitrary point in a file",
"Erik Walthinsen <omega@cse.ogi.edu>");
gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
/* 重载gstbasesrc类成员函数 */
// 【代码块标记:gst_file_src_class_init-02. 】
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_file_src_start);
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_file_src_stop);
gstbasesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_file_src_is_seekable);
gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_file_src_get_size);
gstbasesrc_class->fill = GST_DEBUG_FUNCPTR (gst_file_src_fill);
// 判断off_t类型的位宽,如果太小则声明不支持大文件的读写。
if (sizeof (off_t) < 8) {
GST_LOG ("No large file support, sizeof (off_t) = %" G_GSIZE_FORMAT "!",
sizeof (off_t));
}
}
【代码块标记:gst_file_src_class_init-01】处的代码是针对gobject_class层的操作。其完成如下功能:
1、设置属性操作函数。
2、安装(声明)属性。
3、设置成员函数。
在basesrc类初始化函数gst_base_src_class_init
中已经对gobject_class层的set_property和get_property函数进行设置。此处再次赋值岂不是相当于重载?而事实上却并非是“重载”,而更像是功能的扩展。gst-inspect-1.0 filesrc
我们可以看到父类basesrc和filesrc中声明的属性都存在。并且同在gst_base_src_class_init
中声明的属性和属性操作函数相互对应;同在gst_file_src_class_init
声明的属性和熟悉操作函数相互对应。
笔者在这里有些困惑,对于【代码块标记:gst_file_src_class_init-02】处的代码来说确实是重载了父类basesrc的接口。而【代码块标记:gst_file_src_class_init-01】的代码可以理解为是重载了祖父类(父类的父类)的接口。在gst_base_src_class_init
中已经重载了一次,那么在gst_file_src_class_init
中再次重载不应该是顶替掉父类的重载吗?
笔者为此写了个测试Demo,事实证明确实没出现顶替,而确实是类似功能扩展了。博主这里的疑惑如果有同学知道,还请告知。
上述代码则是完成了filesrc 类的初始化。特别要注意模板类的设置,在实例初始化时我们将能看到其用途。
static void
gst_base_src_init (GstBaseSrc * basesrc, gpointer g_class)
{
GstPad *pad;
GstPadTemplate *pad_template;
// 获取GstBaseSrcClass中注册的private对象地址。
basesrc->priv = GST_BASE_SRC_GET_PRIVATE (basesrc);
// 初始化实例的成员变量。
basesrc->is_live = FALSE;
g_mutex_init (&basesrc->live_lock);
g_cond_init (&basesrc->live_cond);
basesrc->num_buffers = DEFAULT_NUM_BUFFERS;
basesrc->num_buffers_left = -1;
basesrc->priv->automatic_eos = TRUE;
basesrc->can_activate_push = TRUE;
// 获取pad模板,这个模板是在上文中有见到。
pad_template =
gst_element_class_get_pad_template (GST_ELEMENT_CLASS (g_class), "src");
g_return_if_fail (pad_template != NULL);
// 创建pad
GST_DEBUG_OBJECT (basesrc, "creating src pad");
pad = gst_pad_new_from_template (pad_template, "src");
// 设置pad相关函数,这些函数目前也都是由“上帝之手”触发的。
GST_DEBUG_OBJECT (basesrc, "setting functions on src pad");
gst_pad_set_activatemode_function (pad, gst_base_src_activate_mode);
gst_pad_set_event_function (pad, gst_base_src_event);
gst_pad_set_query_function (pad, gst_base_src_query);
gst_pad_set_getrange_function (pad, gst_base_src_getrange);
/* hold pointer to pad */
basesrc->srcpad = pad;
// 安装pad,从这里切入的话,应该能看到“上帝之手”的一些逻辑。对本文仍是透明的。
GST_DEBUG_OBJECT (basesrc, "adding src pad");
gst_element_add_pad (GST_ELEMENT (basesrc), pad);
// 设置属性。特别关注下下DEFAULT_DO_TIMESTAMP,这应该跟pipeline的时间戳有关。
// 此处设个提醒,如果后续看到该变量,再重点分析。
basesrc->blocksize = DEFAULT_BLOCKSIZE;
basesrc->clock_id = NULL;
/* we operate in BYTES by default */
gst_base_src_set_format (basesrc, GST_FORMAT_BYTES);
basesrc->priv->do_timestamp = DEFAULT_DO_TIMESTAMP;
g_atomic_int_set (&basesrc->priv->have_events, FALSE);
// FLUSHING我们也设个特别关注。因为这个词常在pipeline运行时能看到。
g_cond_init (&basesrc->priv->async_cond);
basesrc->priv->start_result = GST_FLOW_FLUSHING;
GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTED);
GST_OBJECT_FLAG_UNSET (basesrc, GST_BASE_SRC_FLAG_STARTING);
GST_OBJECT_FLAG_SET (basesrc, GST_ELEMENT_FLAG_SOURCE);
GST_DEBUG_OBJECT (basesrc, "init done");
}
上述代码是basesrc实例初始化函数,主要完成各个状态的初始化以及pad创建和安装。
static void
gst_file_src_init (GstFileSrc * src)
{
src->filename = NULL;
src->fd = 0;
src->uri = NULL;
src->is_regular = FALSE;
gst_base_src_set_blocksize (GST_BASE_SRC (src), DEFAULT_BLOCKSIZE);
}
上述代码是filesrc实例的初始化函数,并且通过父类basesrc接口gst_base_src_set_blocksize
设置了块大小。这个块大小控制我们每次读取多少数据量。在后续代码中我们将能看到这个变量的作用。
属性设置
【Log文件】
0:00:00.054261666 728 0x362e5a90 INFO filesrc gstfilesrc.c:269:gst_file_src_set_location: filename : /oem/200frames_count.h264
0:00:00.055463333 728 0x362e5a90 INFO filesrc gstfilesrc.c:270:gst_file_src_set_location: uri : file:///oem/200frames_count.h264
【代码解析】
static void
gst_file_src_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstFileSrc *src;
g_return_if_fail (GST_IS_FILE_SRC (object));
src = GST_FILE_SRC (object);
switch (prop_id) {
case PROP_LOCATION:
gst_file_src_set_location (src, g_value_get_string (value), NULL);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
上面这个函数是在filesrc class init时注册的属性操作函数。我们管道命令中有对filesrc的location属性进行设置,因此会触发到这里。不过本篇中仍然是“上帝之手”触发的这个设置。
static gboolean
gst_file_src_set_location (GstFileSrc * src, const gchar * location,
GError ** err)
{
GstState state;
/* 重点关注下这段代码,只有状态为READY和NULL状态时,才能更新location属性。
* 使用playbin时可能会遇到uri更新失败的情况,从而导致播放列表更新下一首失败。
* 从这里可以看出,切换uri之前,一定得关注playbin的状态是否真正切换到READY和NULL?
*/
/* the element must be stopped in order to do this */
GST_OBJECT_LOCK (src);
state = GST_STATE (src);
if (state != GST_STATE_READY && state != GST_STATE_NULL)
goto wrong_state;
GST_OBJECT_UNLOCK (src);
g_free (src->filename);
g_free (src->uri);
/* clear the filename if we get a NULL */
if (location == NULL) {
src->filename = NULL;
src->uri = NULL;
} else {
/* we store the filename as received by the application. On Windows this
* should be UTF8 */
src->filename = g_strdup (location);
src->uri = gst_filename_to_uri (location, NULL);
GST_INFO ("filename : %s", src->filename);
GST_INFO ("uri : %s", src->uri);
}
g_object_notify (G_OBJECT (src), "location");
/* FIXME 2.0: notify "uri" property once there is one */
return TRUE;
/* ERROR */
wrong_state:
{
g_warning ("Changing the `location' property on filesrc when a file is "
"open is not supported.");
if (err)
g_set_error (err, GST_URI_ERROR, GST_URI_ERROR_BAD_STATE,
"Changing the `location' property on filesrc when a file is "
"open is not supported.");
GST_OBJECT_UNLOCK (src);
return FALSE;
}
}
上述代码逻辑简单明了,就设置filesrc对象的location和uri属性。
CAPS查询
【Log文件】
FLC-DBG:[BaseSrc] <gst_base_src_query> is called!
FLC-DBG:[BaseSrc] <gst_base_src_default_query> is called! type=0xaa03
FLC-DBG:[BaseSrc] <gst_base_src_default_get_caps> is called!
0:00:00.058564916 728 0x362e5a90 DEBUG basesrc gstbasesrc.c:1337:gst_base_src_default_query:<filesrc0> query caps returns 1
FLC-DBG:[BaseSrc] <gst_base_src_query> is called!
FLC-DBG:[BaseSrc] <gst_base_src_default_query> is called! type=0xaa03
FLC-DBG:[BaseSrc] <gst_base_src_default_get_caps> is called!
0:00:00.061256416 728 0x362e5a90 DEBUG basesrc gstbasesrc.c:1337:gst_base_src_default_query:<filesrc0> query caps returns 1
上述log中为何两次相同逻辑调用gst_base_src_query尚不清楚,属于“上帝之手”范畴。我们当下只分析函眼前能看到的。gst_base_src_query函数是在pad生成时注册的,从名称来看应该是查询PAD的信息用的。这个pad信息查询应该是用于element连接的。我当前猜测有两种思路,后续我们代码中再去求证。
- 1、pipeline分别查询filesrc和filesink的PAD信息。如果有交集则Link成功,否则Link失败。
- 2、filesink主动查询filesrc的PAD信息,用于判断是否能够Link。
【代码解析】
static gboolean
gst_base_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
{
GstBaseSrc *src;
GstBaseSrcClass *bclass;
gboolean result = FALSE;
// pad的parent是basesrc。
src = GST_BASE_SRC (parent);
bclass = GST_BASE_SRC_GET_CLASS (src);
// 调用basesrc class的query函数。
if (bclass->query)
result = bclass->query (src, query);
return result;
}
上述函数逻辑为:从PAD的query函数,转接到basesrc的gst_base_src_default_query转接函数。
static gboolean
gst_base_src_default_query (GstBaseSrc * src, GstQuery * query)
{
gboolean res;
switch (GST_QUERY_TYPE (query)) {
...略...
case GST_QUERY_CAPS:
{
GstBaseSrcClass *bclass;
GstCaps *caps, *filter;
bclass = GST_BASE_SRC_GET_CLASS (src);
if (bclass->get_caps) {
// query中获取filter,这个filter其实就是请求方的caps。从这里看,
// 上述猜想很接近2.
gst_query_parse_caps (query, &filter);
if ((caps = bclass->get_caps (src, filter))) {
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
res = TRUE;
} else {
res = FALSE;
}
} else
res = FALSE;
break;
}
...略...
default:
res = FALSE;
break;
}
GST_DEBUG_OBJECT (src, "query %s returns %d", GST_QUERY_TYPE_NAME (query),
res);
return res;
}
上述代码的bclass->get_caps
为gst_base_src_default_get_caps
。获取到caps之后,通过gst_query_set_caps_result
反馈结果。
static GstCaps *
gst_base_src_default_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
{
GstCaps *caps = NULL;
GstPadTemplate *pad_template;
GstBaseSrcClass *bclass;
bclass = GST_BASE_SRC_GET_CLASS (bsrc);
pad_template =
gst_element_class_get_pad_template (GST_ELEMENT_CLASS (bclass), "src");
if (pad_template != NULL) {
// 从我们注册的pad模板中获取pad信息
caps = gst_pad_template_get_caps (pad_template);
if (filter) {
GstCaps *intersection;
/* 获取filter和caps的交集, GST_CAPS_INTERSECT_FIRST表示
* 找到第一个匹配的caps就返回
*/
intersection =
gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = intersection;
}
}
return caps;
}
到这里,管道以及完成初始化,当前处于NULL状态。后续的状态切换,以及数据流的流通,还请参见笔者的后续文章。
资源下载
笔者调试的完整log地址:
https://download.csdn.net/download/lyy901135/11839846