简单阅读了下kurento的代码,因为自身也是小白,许多地方也是一知半解的。它的代码不容易理清逻辑,它采用gstreamer的流媒体处理框架,信令处理部分主要由c++负责,而媒体处理部分则由c层的gst-plugins完成。gst-plugins本身基于GObject和gstreamer,GObject采用C语言来实现面向对象编程思想。
关于GObject实现的面向对象,可以参考[1]。[2]是作者的练手代码,里面有对象的创建g_object_new,信号的创建g_signal_new,信号与回调的关联g_signal_connect,信号的发射g_signal_emit_by_name。它的信号机制应该和QT中的信号槽机制类似,可以参看[3]。当然,kurento的c++代码里面也大量使用信号槽的机制,它采用的库是libsigc++。
回到[2]的代码,看看它的对象创建部分:
media = g_object_new(TYPE_MEDIA,
"inventory-id", 42,
"orig-package", FALSE,
NULL);
TYPE_MEDIA是对象的标识,GObject可以根据这个标识,可以对对象进行实例化,简单地说,就是给结构体分配内存,注册结构体相关的处理函数。
#define TYPE_MEDIA (media_get_type())
GType media_get_type (void) {
static GType type = 0;
if (type) return type;
static const GTypeInfo media_info = {
sizeof (MediaClass), /* class structure size */
NULL, /* base class initializer */
NULL, /* base class finalizer */
(GClassInitFunc) media_class_init, /* class initializer */
NULL, /* class finalizer */
NULL, /* class data */
sizeof (Media), /* instance struct size */
0, /* preallocated instances */
NULL, /* instance initializer */
NULL /* function table */
};
type = g_type_register_static(
G_TYPE_OBJECT, /* parent class */
"Media", /* type name */
&media_info, /* GtypeInfo struct */
0); /* flags */
const GInterfaceInfo cleanable_info = {media_cleanable_init, NULL, NULL};
g_type_add_interface_static(type, TYPE_CLEANABLE, &cleanable_info);
return type;
}
GObject中将对象和对象方法进行抽离。好处就是,结构体里面不用携带每一个函数指针,而可以通过一个指针指向所有的函数表,就是模拟了c++中虚函数表的概念,在大量对象创建中,可以节省内存占用。
struct _MediaClass {
GObjectClass parent_class;
void (*unpacked) (Media *media);
void (*throw_out) (Media *media, gboolean permanent);
};
struct _Media {
GObject parent_instance;
guint inv_nr;
GString *location;
GString *title;
gboolean orig_package;
};
但是阅读kurento代码,要是按照先前说的,g_object_new(TYPE_XXX,...)是很少找到的,似乎没有函数调用,就让人怀疑,它的c层对象到底是怎么创建的。而且可以找到很多XXX_get_type只有头文件里有定义,却在c文件里找不到实现。例如这一个:
GType kms_webrtc_endpoint_get_type (void);
这个疑惑就需要参考[5],利用到了GObject中的一个宏定义G_DEFINE_TYPE。
#define kms_webrtc_endpoint_parent_class parent_class
G_DEFINE_TYPE (KmsWebrtcEndpoint, kms_webrtc_endpoint,
KMS_TYPE_BASE_RTP_ENDPOINT);
仔细看下kmswebrtcendpoint.c中的函数实现,会发现所有的函数都是 kms_webrtc_endpoint打头的。G_DEFINE_TYPE这个宏帮助实现了kms_webrtc_endpoint_get_type这个函数。
疑惑依然没有解决,c对象是怎么创建的。kurento使用了gstreamer中的一个奇技淫巧,就是它的plugin动态加载机制。
可以看下,kms-core/src/gst-plugins目录下的的c文件,几乎每一个文件都有这个宏 #define PLUGIN_NAME "xxxxxxx",例如这个:
#define PLUGIN_NAME "webrtcendpoint"
//对应的还有这个函数
gboolean
kms_webrtc_endpoint_plugin_init (GstPlugin * plugin)
{
return gst_element_register (plugin, PLUGIN_NAME, GST_RANK_NONE,
KMS_TYPE_WEBRTC_ENDPOINT);
}
所以在C++代码里,看不到g_object_new这个函数的调用,但是有大量的gst_element_factory_make函数调用。例如kms-elements/src/server/implementation/objects/WebRtcEndpointImpl.cpp中
#define FACTORY_NAME "webrtcendpoint"
WebRtcEndpointImpl::WebRtcEndpointImpl (const boost::property_tree::ptree &conf,
std::shared_ptr
mediaPipeline, bool recvonly,
bool sendonly, bool useDataChannels,
std::shared_ptr certificateKeyType) :
BaseRtpEndpointImpl (conf,
std::dynamic_pointer_cast
(mediaPipeline), FACTORY_NAME)
{
if (recvonly) {
g_object_set (element, "offer-dir", GST_SDP_DIRECTION_RECVONLY, NULL);
}
if (sendonly) {
g_object_set (element, "offer-dir", GST_SDP_DIRECTION_SENDONLY, NULL);
}
if (useDataChannels) {
g_object_set (element, "use-data-channels", TRUE, NULL);
}
}
BaseRtpEndpointImpl这个对象最终继承自MediaElementImpl,这个类会在其构造函数里创建GElement对象,这个对象是属于C层的。
MediaElementImpl::MediaElementImpl (const boost::property_tree::ptree &config,
std::shared_ptr parent,
const std::string &factoryName) : MediaObjectImpl (config, parent)
{
element = gst_element_factory_make(factoryName.c_str(), nullptr);
}
在这个例子下,最终调用的就是"webrtcendpoint"这个plugin,创建 KmsWebrtcEndpoint这个C层的对象。
创建完成后,就可以对这个element设置属性,例如
g_object_set (element, "offer-dir", GST_SDP_DIRECTION_SENDONLY, NULL);
这个属性,在kms-core/src/gst-plugins/commons/kmsbasertpendpoint.c中有定义,可以全局搜索下"offer-dir":
g_object_class_install_property (object_class, PROP_OFFER_DIR,
g_param_spec_enum ("offer-dir", "Offer direction", "Offer direction",
KMS_TYPE_SDP_DIRECTION, DEFAULT_OFFER_DIR,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
static void
kms_base_rtp_endpoint_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
case PROP_OFFER_DIR:
self->priv->offer_dir = g_value_get_enum (value);//对这个参数进行配置
break;
}
同c层的交互,是通过信号来完成的,例如:
void
WebRtcEndpointImpl::gatherCandidates ()
{
gboolean ret;
g_signal_emit_by_name (element, "gather-candidates", this->sessId.c_str (),
&ret);
if (!ret) {
throw KurentoException (ICE_GATHER_CANDIDATES_ERROR,
"Error gathering candidates");
}
}
c层接收到"gather-candidates"信号后,会调用相应的函数进行处理。G_STRUCT_OFFSET (KmsWebrtcEndpointClass, gather_candidates),获取的是回调函数在结构体中的地址偏移信息,也就是调用KmsWebrtcEndpointClass中的 gather_candidates这个函数进行处理。
kms_webrtc_endpoint_signals[SIGNAL_GATHER_CANDIDATES] =
g_signal_new ("gather-candidates",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_ACTION | G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (KmsWebrtcEndpointClass, gather_candidates), NULL, NULL,
__kms_webrtc_marshal_BOOLEAN__STRING, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
关于插件的动态加载机制,可以参考[7]。在kms-core.c中有这样一个宏GST_PLUGIN_DEFINE。可以在kurento_init中看看都加载了那些插件。kurento_init应该是gstreamer查找到动态库路径后会执行的函数。
static gboolean
kurento_init (GstPlugin * kurento)
{
if (!kms_agnostic_bin2_plugin_init (kurento))
return FALSE;
if (!kms_agnostic_bin3_plugin_init (kurento))
return FALSE;
if (!kms_filter_element_plugin_init (kurento))
return FALSE;
if (!kms_hub_port_plugin_init (kurento))
return FALSE;
if (!kms_audio_mixer_plugin_init (kurento))
return FALSE;
if (!kms_audio_mixer_bin_plugin_init (kurento))
return FALSE;
if (!kms_bitrate_filter_plugin_init (kurento))
return FALSE;
if (!kms_buffer_injector_plugin_init (kurento))
return FALSE;
if (!kms_pass_through_plugin_init (kurento))
return FALSE;
if (!kms_dummy_src_plugin_init (kurento))
return FALSE;
if (!kms_dummy_sink_plugin_init (kurento))
return FALSE;
if (!kms_dummy_duplex_plugin_init (kurento))
return FALSE;
if (!kms_dummy_sdp_plugin_init (kurento))
return FALSE;
if (!kms_dummy_rtp_plugin_init (kurento))
return FALSE;
if (!kms_dummy_uri_plugin_init (kurento))
return FALSE;
return TRUE;
}
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
GST_VERSION_MINOR,
kmscore,
"Kurento core",
kurento_init, VERSION, GST_LICENSE_UNKNOWN, "Kurento",
"http://kurento.com/")
在kms-elements中这个宏GST_PLUGIN_DEFINE出现多次,读者可以全局搜索一下。
总结,kurento的这种松耦合的代码处理逻辑,增加了学习难度。至于其逻辑处理部分,我还没有看。学习kurento,需要学习一些基础知识,例如GObject和GStreamer。kurento在编译中会自动生成一些代码,在各个文件夹下的generated-cpp,要是没有这个文件,代码读起来逻辑不完整,找不到线索。我编译后的代码,已上传[8]。
[1] GObject对象系统
[2] gobject-example
[3] webrtc sigslot 使用以及源码分析
[4] libsigc++库的使用
[5] gobject中G_DEFINE_TYPE和g_object_new流程简介
[6] GStreamer插件架构简析
[7] gstreamer插件工作原理与流程分析
[8]kms-for-reading