了解janus

目标:Janus is an open source, general purpose, WebRTC server designed and developed by Meetecho.
技术栈:C,jcfg(libconfig)
代码结构

代码结构图

描述:janus由Core实现了WebRTC所支持的所有协议(SDP, ICE, DTLS-SRTP, RTP/RTCP),并且管理插件的初始化,管理,关闭等。我们可以看到具体业务功能有Plugin提供,Core本身不能进行任何具体业务实现。为了详细了解这个系统,我们可以带着问题来了解:

  1. 系统的编译依赖,及编译先后顺序是怎么样的?
  2. 系统的Core的启动,及Plugin启动都做了那些事情?
  3. 简单的单路通话(VideoCall)实现原理?
  4. 多路通话(VideoRoom) 实现原理?

1. 系统的编译依赖,及编译先后顺序是怎么样的?

我们来看右Doxygen生成的头文件依赖


Doxygen头文件依赖

我们看到依赖很简单:

  1. plugins/plugin.h
  2. transports/transport.h
  3. loggers/logger.h
  4. events/eventhandler.h

所以在编译时,Core是依赖到Plugins,Transports, Loggers,Events模块的。

分析 Makefile.am 知道:
janus_SOURCES 包含了 plugins/plugin.c, transports/transport.c这两个实现。所以Core只是把plugin.c, transport.c放到了这两个子目录中,实际他俩是Core的源文件。

然后看Plugin的编辑脚本,发现一个很神奇的事情,Plugin居然不依赖Core,不管是头文件还是链接。这是不是跟接口编程一样,框架提供接口,找一个现成的库适配了这个接口就能在这个库里面使用,现在厉害的在于,适配的时候都不依赖框架。

所以可以很明确的知道,他们的编译可以分开,即先编译Core,然后并行编译Plugins,Transports,EventHandlers。

2. 系统的Core的启动,及Plugin启动都做了那些事情?

我们来分析main函数:

  1. 首先在解析命令行参数,和配置文件。
  2. 然后调用janus_log_init初始化日志模块,这里面启用了一个日志打印现成,并维护了一个log_buffer链表,及log_buffer pool。
  3. 加载外部logger的so,并且初始化,最后调用janus_log_set_loggers关联到janus_log中去管理。
  4. 调用janus_auth_init初始化授权系统。
  5. 调用janus_recorder_init初始化路由系统。
  6. 调用janus_ice_init初始化ICE协议栈。
  7. 调用SSL_library_init, SSL_load_error_strings, OpenSSL_add_all_algorithms初始化OpenSSL环境。janus_dtls_srtp_init初始化DTLS功能。
  8. 调用janus_sctp_init初始化SCTP功能,多播通道。
  9. 初始化Core的全局变量。sessions 系统会话hash表,及sessions_watchdog_context,并启动session watchdog线程。requests系统请求异步队列,并启动请求分配线程。tasks系统任务线程池,用来分配请求。
  10. 加载EventHandlers的so,并初始化。 调用janus_events_init关联到管理中。
  11. 加载外部Plugins的so,并初始化。
  12. 加载外部Transports的so,并初始化。
  13. 给系统发送第一个Event,janus started。
  14. 启动主线程循环 g_main_loop_run

3. 简单的单路通话(VideoCall)实现原理?

首先我们抛开系统来说,一个通话是由Alice(终端A)发起,然后服务器路由到Bob(终端B),这其中附带SDP(Session Description Protocol)。我们先说最简单的方式

        Server
     /          \
Alice  <--rtp--> Bob

其中SDP携带了Alice RTP/RTCP 的端口及IP,然后Bob回给服务器信令也携带了自己的SDP,同样服务器把这个信令路由到Alice。Alice收到后,对着Bob给的端口RTP/RTCP通信。这样就建立起来了Session,相互的数据互通了。

现在我们开始研究Janus怎么把这个过程实现的。

我们先查看Websocket Transport + VideoCall Plugin。

第一步,Alice和Bob怎么和Janus建立信令通道呢?
1. Websocket Transport 用 libwebsockets 建立 ws/wss 服务器。
2. Alice和Bob建立webscoket连接,libwebsockets会分配一个ws_client,并回调WST(Websocket Transport)。WST在ws_client上建立messages队列和用janus_transport_session_create建立Core Session。最后给Core发送一个connected消息,这个消息啥也没干。
3. Alice建立连接后,会立马发送一个create请求,WST收到请求后,把请求发送到Core。Core会立即创建一个Core Session,同时发送一个 Session 类型的created 消息。然后回复Alice建立连接成功。
4. 然后,Alice发送attach请求。Core会立即创建一个ICE Handle,准备处理RTP会话,同时发送一个Handle类型的attach消息。然后回复成功。这一步很关键,create只是建立Core Session,并没有做任何路由关联。而attach则告诉Core,我要用的Plugin。这样就在ICE Handle中建立了Plugin指针,并调用Plugin创建一个Plugin Session, 方便后续的message直接给Plugin
5. 至此连接就建立了。当然Janus还有一步,注册用户名,方便呼叫。Alice发送一个message请求,并且带上body:{request:'register", username:"Alice"}。Core根据attach的信息,把消息给 VideoCall PluginVideoCall则建立一个用户名对应Session的hash表。

第二步,Alice和Bob怎么建立呼叫会话呢?(注意这里的会话与代码中的会话不是一个意思)
1. Alice发送一个{janus:"message",body:{request:"call",username:"Bob"},jseq:{sdp:"xxx",type:"offer"}}的消息。这个是message消息,Core会直接给Plugin处理。VideoCall Plugin先回复Boback,再根据username找到Bob的Session,然后验证sdp,给两个Session做上通话标记与关联。然后给Bob发送一个叫incomingcallevent,带上Alice发送过来的SDP。
2. Bob回一个{janus:"message",body:{request:"accept"},jseq:{sdp:"xxx",type:"answer"}}的消息。VideoCall Plugin先回复Boback,再解析sdp,提取相关信息。然后给Alice发送一个叫acceptevent,带上Bob发送过来的SDP。
3. 这样就建立一个通话。

第三步,Alice或者Bob如何挂机呢?
1. Bob发送一个{janus:"message",body:{request:"hangup"}}的消息。VideoCall Plugin收到消息后,先回复Bob ack,然后清除现场,然后给Alice发送发送一个hangupevent
2. 这样电话就挂断了。

上面的都是信令层面如何交互,涉及RTP/RTCP的只有SDP,那么SDP如何在ICE框架下,把RTP会话建立起来的呢?

4. 多路通话(VideoRoom) 实现原理?

VideoRoom Plugin实现了SFU(Selective Forwarding Unit)功能。它实现了会议功能,基于发布/订阅模式,任何一个参会者都可以推送自己的流,并订阅别人的流。

我们先看看信令交互:
第一步,Alice和Bob怎么和Janus建立信令通道呢?
1. 同单路通话一样,建立起来用户通道。只是最后一步注册用户名不一样了。它把这一步直接整合进后面的join请求中去了,因为会议不需要像单路通话一样去找对方,而是给一个会议id,用其它方式告知对方后,让他加入进来。
第二步,如何创建会议,并且加入会议呢?
1. 调式发现,VideoRoom的示例并没有实现创建会议的功能,而是直接加入了一个叫1234的默认会议。
2. Alice发送{janus:"message",body:{request:"join",room:1234,ptype:"publisher",display:"Alice"}}的消息,VideoRoom Plugin收到消息后,会建立一个Publisher,已用户id作为key,并且与Core Session关联起来。这样就Alice就加入了会议
3. 设置自己发布的推流,发送{janus:"message",jseq:{sdp:"xxx",type:"offer"},body:{request:"configure",audio:true,video:true}}的消息,VideoRoom Plugin根据参数设置一些参数,然后通知其它参会者。
4. 退出会议。Alice发送一个{janus:"destory"}的请求。Core拿到消息后,首先调用WST结束连接。然后清理Core Session,然后清理ICE Handle。这里有个问题,Plugin Session 并没有清理,在哪里,什么时机做的清理呢?这里用的是 ICE 框架的事件做的处理。

至此,我们了解了视频通话,主要信令的交互过程,如何创建连接,如何建立会话,如何销毁等等。

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

推荐阅读更多精彩内容