了解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 框架的事件做的处理。

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

你可能感兴趣的:(了解janus)