BigBluebutton框架:Bigbluebutton从最初只有flash客户端的方式,后来又新增了h5的实现方式,这里我们主要研究h5的实现。
BigblueButton使用了多个开源项目,据官网介绍有十多个之多,它是利用webrtc技术实现的教育音视频会议系统。
作用一个webrtc的实现方案,webrtc客户端的实现代码浏览器中已经实现,所以主要的工作是服务器端需要自己实现。而webrtc服务器的核心就是信令服务器,房间服务器,流媒体服务器,bbb方案也主要就是实现这几个服务器并在此基本上进行了功能的扩展,例如电子白板,文档共享等等。
Bbb的源码主要目录如下所示:
Akka-bbb-apps:是bbb应用的主要实现,它沟通了各个模块。
Akka-bbb-fsel:连接了音频会议框架freeswitch。
Bbb-api-demo:主要是放了一些demo的源码。
Bbb-client-check: 主要是放了检测bbb-html5服务的状态。
Bbb-common-message:主要是用来构造一些常用json信息,比如创建会议,删除会议,加入会议等等…
Bbb-common-web: web会议管理的主要实现。
Bbb-scrennshare: 屏幕共享的处理。
Bigbluebutton-html5: H5 webwrtc通信客户端和服务端的源码,采用的是Meteor框架。
Bigbluebutton-web: 提供了一套web api,这套api主要是会议室的创建,销毁,成员的加入,退出等。 这套api是整个bbb项目对外暴露的api,bbb系统通过这套api将自己的能力暴露给外界,开发者可以通过这些api接口构建一套会议系统 ,也就是说bbb上自带的api-demo,greenlight等都是基本这套api来完成的。
labs:这里是信令服务器的实现,它通过调用kurento-media-server来实现webwrtc的服务端。
分析上面的源码可以看出,bbb大部分的源码都是在管理会议室,成员等,这些可以归纳成房间服务器。 而labs目录就是信令服务器的实现,也就是webrtc通信的核心。 然后bbb除了基本的音视频通话之后,还有很多扩展功能 屏幕共享,电子白板等等,所以bbb项目就是webrtc通信系统加上一些扩展的功能。
为了快速了解bbb系统的整体流程我们可以分析 用户A和用户B在一个房间内进行音视频通信的过程。
第一步:用户A打开Chrome浏览器并输入https://serverip 。
这时候我们看到的这个demo service页面。https默认使用的443端口,我们从服务器上看到443端口是nginx在监听。
也就是说是nginx这个程序帮我们返回了这个请求,这个返回的页面是配置的,实际上就是源码中的 /bigbluebutton-config/web/index.html 或者index_html5_vs_flash.html(根据设备类型选择,android中不支持flash所以返回的是index.html,window上是另一个)。
第二步:我们输入用户名 XiaoMing,并点击Join。
可以先看一下点击Join这个事件发生了什么。 ,从源码中可以看出是跳转到了 /demo/demoHtml5.jsp。 这个就是bbb源码中的 /bbb-api-demo/src/main/ demoHtml5.jsp. 这个jsp中干嘛了呢?
主要就是拿到要加入的会议的url然后再跳转。 这里的会议名字是写死的 meetingname = "Demo Meeting"。 如果第一次这个Demo Metting 会议室不存在的话那应该还有一个创建的过程,我们分析 getJoinURLExtended(这个在bbb-api-demo/main/webapp/,前面说过bbb-web暴露了api接口用于开发者对接bbb系统,这个bbb_api.jsp就是用封装了bbb的api接口)
可以看到,它调用了BigBlueButtonURL + "api/create?去请求创建会议室,如果创建成功(不一定会创建,如果之前创建过的话)就进一步组合成api/join?的url,去加入Demo Meeting这个会议。 通过分析可知跳转到 了这个url http://192.168.1.124/bigbluebutton/api/join?meetingID=Demo+Meeting&fullName=XiaoMing&joinViaHtml5=true&password=mp&checksum=c7e9e1207321dcbaf50527e417fa6775319fd6b530937c315600a91f91baf9b2
好了,客户端上的逻辑分析完了,就是请求了两个url,一个api/create,一个api/join。就完成了会议的创建与成员的加入操作,而且直到这里我们也还没涉及到webrtc的东西。
我们再来分析服务器端是如何处理 api/create , api/join这两个请求的。
前面说过实现bbb api的接口是在bbb-web目录。从这可以找到
ApiController.groovy里实现api的接口。 这里的create是调用了 MeetingService,所在位置的createMeeting方法,这个方法发送了一个createMessage的消息,
MeetingMessageReceiver.java
接收这个消息并处理。
Join api的流程也一样。
第三步:
我们点击开始分享开始分享摄像头。
在分析webrtc的通信细节之前,我们首先来了解一下webrtc及kurento的相关的知识点。
首先bbb采用的是SFU的模型,关于P2P,SFU,MCU模型我在网上找了几个图可以很好的阐述:
P2P场景:
SFU场景:
MCU场景:
P2P,SFU,MCU优劣对比。
P2P在多人时上下行压力太大,占用太多带宽,不适合多人聊天场景,且服务端不能对媒体流进行处理,优点是服务器压力小,服务器代码逻辑简单,连接建立好后不需要服务器参与。
SFU:上行只需要一路,相对减小了上行压力。SFU 服务器最核心的特点是把自己 “伪装” 成了一个 WebRTC 的 Peer 客户端,WebRTC 的其他客户端其实并不知道自己通过 P2P 连接过去的是一台真实的客户端还是一台服务器,我们通常把这种连接称之为 P2S,即:Peer to Server。除了 “伪装” 成一个 WebRTC 的 Peer 客户端外,SFU 服务器还有一个最重要的能力就是具备 one-to-many 的能力,即可以将一个 Client 端的数据转发到其他多个 Client 端。相较于MCU的优点是只有服务器无转码,合流,只有转发,压力较小。
MCU:优点是上一行都只有一路,有效减小客户端带宽,缺点是需要在服务器端 转码合流,服务器压力较大。
我们再来了解一下kurent 的api,了解kurent服务器的使用。
媒体元件:
1. 输入端点: 指具有接收媒体并注入到管道能力的媒体元件。有文件,网络,摄像头等。
2. 指具有将媒体流从管道中输出的能力的媒体元件
3. 集线器: 指负责管理一个管道中多个媒体流的媒体对象
4. 滤镜,混流,分析处理流等。
>>> 媒体管道: 媒体管道是一个媒体元件的链, 在这个链中,输出流是由一个源元件输入到一个或多个其它媒体元件处理后再输出的。 因此,管道表示能执行一系列流操作的机器。
例如在一对一的视频聊天中的媒体管道是这样的:
管道中有两个webrtc节点,左边节点与用户A的客户端 RTCPeEr连接,获取到媒体流,然后从输出端口SRC连接到右边节点的输入端口,右边端口与用户B的客户端连接,然后再把A中传来的媒体流输出到用户B的RTCPeer中,这样用户B只就得到了用户A的媒体流,用户A得到用户B的媒体流的过程也是一样的。从这可以看出kurent起到了流的中转作用。从这简单的模型中可以看出WebRtcEndpoing端口的连接决定了流的转输方向,无论是其它复杂的模型也只需要变化节点的端口连接就能实现。
WebRtc建立连接基本过程:
当然在bbb系统 中,这个Callee端实际也在服务器上,也就是说kurento服务器伪造成了Callee端与Caller通信,这样kurent服务器就可以得到Caller的媒体流并进行相应的处理。
弄清了上述原理,我们再来分析bbb中的webrtc过程就变得简单多了。
好了现在我们可以接着前面分析点击开始分享之后的流程,由于这个网页的js文件代码量很大,直接从源码上分析并不太容易,我们知道webrtc是基于websock通信的,也许可以从分析协议的内容来理解这一过程。
我们打开Chrome的调试工具查看网络的ws模块可以看到。
第一条信息的id是start,带了自己的SDP信息,请求的url是wss://192.168.1.124/bbb-webrtc-sfu?sessionToken=dftsqt7nlo9npuyp。
可以根据url来查看服务器端哪里在处理这个请求。
这个是在/labs/bbb-webrtc-sfu/server.js上,从名字上可以看出bbb-webrtc-sfu目录就是SFU服务器了,它是kurent的js api来控制kurent服务器实现的。
分析源码可知SFU服务器初始化主要开启了四个进程:server , process,video,audo(调试这些node进程可参考https://blog.csdn.net/saxihuangxing/article/details/89205592)。 Server进程在接收到这个type为video的消息后,作了如下处理。
可以看到它是将消息分发到了video模块。 再来看VideoManger.js中_onMessage
上如何处理这个消息的。
startResponse将这个sdpAnswer返回给了客户端。 从的webrtc连接过程可以看出当客户端收到answerSdp,之后通过ICE信令得到webrtc本地和远程端的地址之后 就完成了webrtc连接的过程,就可以进行媒体的交互了。
我们现在来着重分析是怎么根据SDP并得到SdpAnswer的,也就是这一个SDP协商的过程。
Video.js 的_addMSCMedia方法调用了MSCAPIWrapper.js的 public方法,是core/lib/media下的mcs-message-router.js下的public方法。 看mcs-core这个名称应该就是MediaCenterServer-Core,这个就是媒体中心服务的地方了吧,终于找到核心了。 看到这里的lib/adapters目录下有freeswitch 和kurent文件夹,这应该就是bbb和freeswitch 和 Kuren服务器交互的接口了,为什么还需要freeswitch呢,看了下接口还有SIP的东西,看它们交互的参数也确实有拨号号码的,所以这里应该也是支持语音SIP拨号连接的,暂未尝试,看官方文档中是支持的。
接着看mcs-message-router.js .
这里有所有房间,用户,事件反馈的所有接口,看来所有的事都是在这里完成的了。 这里调用了media-controller.js的publish,再调用到了user.js的publish. 这里
创建的ssession实际上是sdpSession. 然后调用了sdpSession的 start和process方法得到了sdpanswer. 查看sdpssesion.js可以看到这里最终是调用到了kurento.js的negotiate方法得到了sdpanswer,从函数名可以看出这是协商SDP信息,也就是和kurent服务器器交互的地方。
这里使用mediaPipeline创建了媒体元素,这些都是Kurent api中的概念。它们之间的关系如图所示:
‘如前面所示,媒体元素(MediaElement)中有Endpoint,在webrtc中是WebRtcEndpoint,这个WebrtcEndpoint有输入和输出商战,而媒体管道(MediaPipleline)管理着MediaElent,可以控制这些Endpoint的端口连接。获取sdpAnswer也是调用了mediaElement.processOffer方法。这里没必要在深入到kurent api中去,我们只需要理解 kurent api的使用和原理就行了。然后又调用了media.track(),这个函数是添加媒体的监听函数,也就是从kruento服务器得到媒体的状态,比如这些
还有各种错误事件。 到了这里,我们可以看到媒体元素已经创建成功,也拿到了SdpAnswer,再回到前面startResponse的地方将这个Sdpanwser返回给客户端之后 ,再经过一系列ICE信令的协商,这个Webrtc连接就建立成功了。 再回到video.js的监听函数 _mediaStateWebRTC
。当它监听到从服务器发来的消息 MediaFlowOutStateChange,MediaFlowInStateChange
,detail 为 'FLOWING'。
就会向客户端发送一个playStart的消息,客户端收到这个消息后代表整个通道流程已经跑通,就可以开始播放流了。分析到此处,整个框架脉络也基本清楚了。
第四步:用户B在浏览器客户端也输登录房间
流程基本上和用户A发生的一样。Kurento服务器也为用户B创建了对应的WebrtcEndPoint,并将A的流。当用户B分享摄像头之后 ,会将用户A和用户B的媒体流都注入到A与B结点的输出端口,这样A和B都能同时看到A,B的视频了。
参考资料:
https://cloud.tencent.com/developer/news/393757
http://blog.chinaunix.net/uid-26000296-id-5159352.html
https://www.cnblogs.com/idignew/p/7248257.html
https://www.html5rocks.com/en/tutorials/webrtc/basics/#toc-rtcpeerconnection