kurento代码分析(一)C++与C的交互

 简单阅读了下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>
                                        mediaPipeline, bool recvonly,
                                        bool sendonly, bool useDataChannels,
                                        std::shared_ptr<CertificateKeyType> certificateKeyType) :
  BaseRtpEndpointImpl (conf,
                       std::dynamic_pointer_cast<MediaObjectImpl>
                       (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<MediaObjectImpl> 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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,654评论 18 139
  • 我从毕业开始就一直不安分,总处在一个创业的过程中,而且通过与不同的人合作,一直在思考创业发起人的合作模式:与“亲人...
    秋月白1979阅读 435评论 0 1
  • (一) 猝不及防,父亲生病住院了。 而这前一天,他还来找我,期期艾艾地试着和我说到:“我怀疑自己内脏出血……”正在...
    A_范范阅读 204评论 0 3
  • What Significant Information Is Omitted? Summary To judge...
    读者_在路上阅读 639评论 0 1
  • 最近对运营很感兴趣,第一本看的是《运营之光》,第二本是快要看完的《零基础学运营》,边烧脑边总结的过程。 现在很多业...
    李Polly阅读 198评论 1 1