mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-10-21 17:33:48 +08:00
177 lines
13 KiB
Markdown
177 lines
13 KiB
Markdown
<audio id="audio" title="29 | 如何使用Medooze 实现多方视频会议?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/02/5a/0270e43cbb16491a8119394771eb4b5a.mp3"></audio>
|
||
|
||
前面我们通过两篇文章详细介绍了 Medooze 的实现逻辑,我相信你现在已经对 Medooze 有了非常深刻的认知。通过对Medooze实现原理和架构的了解,我们可以知道Medooze支持的功能非常多,如录制回放、推流、SFU 等,其中最主要的功能是 SFU 的实现。
|
||
|
||
实际上,**我们要实现的多方音视频会议就是由 SFU 来实现的,或者你可以认为Medooz实现的 SFU 就是一个最简单的音视频会议系统**。Medooze 提供的 SFU Demo 的地址为:[https://github.com/medooze/sfu](https://github.com/medooze/sfu) 。
|
||
|
||
由于 SFU 是一个 Node.js 项目,所以在环境部署、源码分析的时候,需要你有 JavaScript 基础,也需要掌握 NPM 和 Node 的常用命令,比如 npm install 等。接下来,我们主要从以下几方面向你介绍一下多方音视频会议(SFU)项目:
|
||
|
||
- 搭建多方音视频会议系统,让你感受一下多方通信;
|
||
- 分析多方音视频会议实现的架构;
|
||
- 分析 Medooze API 的具体使用;
|
||
- 分析多方音视频会议的入会流程。
|
||
|
||
## 多方音视频会议环境的搭建
|
||
|
||
多方音视频会议(SFU)项目是纯 Node.js 实现的,可以说在目前所有的操作系统上都可以运行。然而,由于该系统依赖 medooze-media-server 项目,而该项目仅支持 macOS X 和 Linux 两种操作系统,所以 SFU 项目也只能在 macOS X 和 Linux 这两种操作系统下运行了。
|
||
|
||
本文我们所搭建的这个 Demo 的实验环境是在 Ubuntu 18.04 系统上,所以接下来的实验步骤和实验结论都是基于 Ubuntu 18.04 得出的,如果你在其他操作系统下搭建可能结果会略有不同,但具体步骤都是一模一样的。
|
||
|
||
下面我们就来分析下这个环境搭建的具体步骤。
|
||
|
||
第一步,打开 Linux 控制终端,下载 SFU 代码,命令如下:
|
||
|
||
```
|
||
git clone https://github.com/medooze/sfu.git
|
||
|
||
```
|
||
|
||
第二步,安装 SFU 依赖的所有包:
|
||
|
||
```
|
||
cd sfu
|
||
npm install
|
||
|
||
```
|
||
|
||
第三步,在 `sfu` 目录下生成自签名证书文件:
|
||
|
||
```
|
||
openssl req -sha256 -days 3650 -newkey rsa:1024 -nodes -new -x509 -keyout server.key -out server.cert
|
||
|
||
```
|
||
|
||
第四步,在 `sfu` 目录下启动服务:
|
||
|
||
```
|
||
node index.js IP
|
||
|
||
```
|
||
|
||
上面命令中的 IP 是测试机器的IP地址,如果用内网测试,你可以将它设置为 127.0.0.1。如果你想用外网访问,则可以指定你的云主机的外网IP地址。另外需要注意的是,该服务启动后默认监听的端口是 8084,**所以你在访问该服务时要指定端口为8084**。
|
||
|
||
通过上面的步骤,我们就将 Medooze 的 SFU 服务器搭建好了,下面我们来测试一下。
|
||
|
||
打开浏览器,在地址栏中输入以下地址就可以打开 Medooze SFU Demo 的测试界面了:
|
||
|
||
```
|
||
https://IP:8084/index.html
|
||
|
||
```
|
||
|
||
在这个地址中需要特别注意的是,**必须使用 HTTPS 协议,否则浏览器无法访问本地音视频设备**。此外,这个地址中的 IP 指的是SFU服务的主机的IP地址,可以是内网地址,也可以是外网地址。
|
||
|
||
当该地址执行后,会弹出登录界面,如下所示:
|
||
|
||
<img src="https://static001.geekbang.org/resource/image/df/73/dfb0c325dfa9eef91cd56041259f2a73.png" alt="">
|
||
|
||
这是一个多人视频会议登录界面。关于登录界面,这里我们做一个简单的说明。
|
||
|
||
- 界面中的Room Id 和 Name 可以随机生成,也可以自己指定。如果需要随机生成,点击后面的 RANDOM 按钮即可。
|
||
- 几个参会人若想要进入同一个房间,那么他们的 Room Id 必须相同。
|
||
- 在 Audio Device 列出了所有麦克风,可供参会人选择。
|
||
- 在Audio Device下面,有一个音量进度条,它可以测试麦克风是否正常工作。
|
||
- 一切准备就绪后,点击右下角的“READY!”按钮,就可以加入房间了。此时,在浏览器左下角显示了自己的本地视频。
|
||
- 由于这是基于浏览器的音视频应用,具有很好的跨平台性,因此你可选择 PC浏览器、手机浏览器一起测试。
|
||
|
||
上面我们就将 Medooze 的音视频会议系统的 Demo 环境搭建好了,并且你可以进行多人的音视频互动了。接下来我们就分析一下这个音视频会议系统(SFU) Demo 是如何实现的吧。
|
||
|
||
## SFU 目录结构
|
||
|
||
首先我们来分析看一下 Medooze 的 SFU Demo 的目录结构,以及每个子目录中代码的作用,为我们后面的分析做好准备。
|
||
|
||
<img src="https://static001.geekbang.org/resource/image/06/29/06c09b7ebbee5cdb4e9c1de8ef42f629.png" alt="">
|
||
|
||
如上图所示,在 SFU Demo 目录下的文件非常少,我们来看一下每个文件或目录的具体作用吧!
|
||
|
||
<img src="https://static001.geekbang.org/resource/image/be/8e/becbb861d9775ad282513b3c451e268e.png" alt="">
|
||
|
||
了解了 SFU Demo 的目录结构及其作用后,我们再来看一下 SFU Demo 的架构是什么样子。
|
||
|
||
## SFU 架构
|
||
|
||
下图就是 Medooze SFU Demo 的架构图,你可以参考下:
|
||
|
||
<img src="https://static001.geekbang.org/resource/image/fc/e4/fc4bcb1763ec26052cfe9de5ca2e58e4.png" alt="">
|
||
|
||
从图中你可以看出,SFU 架构包括了**浏览器客户端**和**SFU服务端**两部分。而SFU服务端又包括两部分功能:WWW 服务、WebSocket信令及业务管理。
|
||
|
||
接下来我们就来分析一下**服务端的处理逻辑**。
|
||
|
||
Node.js中的 Web 服务器(WWW)是在 index.js 文件中实现的。当我们执行`node index.js IP`命令时,该命令启动了一个 HTTPS 服务,监听的端口是 8084。当 HTTPS 启动后,我们就可以从它的 WWW 服务获取到客户端代码了。需要说明的是,HTTPS 服务依赖 server.key 和 server.crt 文件,这两个文件需要我们提前生成好(生成证书的命令在上面 SFU 环境搭建一节已经向你做了说明)。
|
||
|
||
至于WebSocket 服务及业务管理部分,逻辑会稍微复杂些。服务端启动WebSocket服务后,可以通过它来接收客户端发来的信令,同时也可以通过它向客户端发送通知消息;除了对信令的处理外,SFU的业务逻辑也是在这部分实现的,如 Room 和 Participator 实例的创建、参会人进入/离开房间等。
|
||
|
||
这里需要你注意的是,WebSocket服务和业务管理的代码也是在 index.js中实现的。换句话说,在服务端的 index.js 中,实现了 WWW 服务、WebSocket服务和SFU业务管理三块逻辑,这样“大杂烩”的代码实现也只是在 Demo 中才能这么写了。另外,index.js 的实现依赖了 websocket 和 transaction-manager 两个包。
|
||
|
||
分析完服务端的处理逻辑后,接下来我们再来看看**客户端**。浏览器首先从 Medooze SFU 的 WWW服务上获得 sfu.js 代码,在浏览器上运行 sfu.js就可以与 Medooze SFU 建立连接,并进行多方音视频通信了。在 sfu.js 中,主要实现了房间的登录、音频设备选择、采集音视频数据、共享音视频数据等功能。实际上,客户端的实现逻辑就是使用咱们专栏第一模块所介绍的知识,如 RTCPeerConnection 的建立、获取音视频流、媒体协商等。
|
||
|
||
此外,Medooze SFU 还实现了几个信令,如 join 用于加入会议、update 用于通知客户端重新进行媒体协商等等,这几个信令还是非常简单的,不过却非常重要。尤其是在阅读Medooze SFU 代码时,可以按照信令的流转来梳理SFU的代码逻辑,这样会大大提高你的工作效率。
|
||
|
||
以上就是Medooze SFU 的基本架构,接下来我们看一下 SFU 的逻辑处理。
|
||
|
||
## SFU 逻辑处理
|
||
|
||
SFU 的实现很简单。在服务端有两个重要的类,即**Room** 和 **Participator**。它们分别抽象了多人会议中的**房间**和**参会人**。为便于你理解,我画了一个简单类图,如下所示:
|
||
|
||
<img src="https://static001.geekbang.org/resource/image/f2/ad/f23f501a0453d24157633c2a1e62abad.png" alt="">
|
||
|
||
从图中可以看到,index.js 中定义了一个**全局对象 Rooms**,保存多个房间(Room)实例。
|
||
|
||
**Room 类是对房间的抽象**:
|
||
|
||
- Endpoint 属性是 Medooze Endpoint 类型;
|
||
- Participators 属性是一个 Map 类型,保存本房间中所有 Participator 实例;
|
||
- 该类还有两个重要方法,createParticipator 用于创建参会人实例,getStreams 获取所有参会人共享的媒体流。
|
||
|
||
**Participator 类是对参会人的抽象**:
|
||
|
||
- IncomingStreams 属性是 Map 类型,保存自己共享的媒体流,元素属性类型是 Medooze::IncomingStream;
|
||
- OutgoingStreams 属性是 Map 类型,保存自己观看的媒体流,元素属性类型是 Medooze::OutgoingStream;
|
||
- transport 属性是 Medooze::Transport 类型,表示 DTLS 传输;
|
||
- init() 方法,初始化 transport 实例等;
|
||
- publishStream() 方法用于发布自己共享的流,创建 IncomingStream;
|
||
- addStream() 方法用于观看其他参会人共享的流,创建 OutgoingStream 实例,并且 attachTo 到共享人的 IncomingStream 中。
|
||
|
||
## 入会流程
|
||
|
||
这里我们可以结合客户端和服务器的逻辑,分析一下参会人入会的大致流程,如下图所示:
|
||
|
||
<img src="https://static001.geekbang.org/resource/image/97/92/97b0ecb3c212a626f2e29ed1dab84c92.jpg" alt="">
|
||
|
||
图中深粉部分是客户端实现,在 `www/js/sfu.js` 中;绿色部分是服务器实现,在 `index.js` 中;橘色是在 medooze 中实现。那具体的流程是怎样的呢?
|
||
|
||
首先,浏览器从 Node.js 服务器获取到 `www/index.html` 页面及引用的 JavaScript 脚本,然后执行 connect 方法。在 connect 方法中使用 WebSocket 与SFU服务(index.js)建立连接。
|
||
|
||
服务端收到 WebSocket 连接请求后,从 URL 中解析到 roomid 参数。然后,根据该参数判断房间是否在之前已经创建过。如果该房间已创建,则什么都不做;如果没创建,则尝试创建 Room 实例。
|
||
|
||
对于客户端来说,WebSocket 连接建立成功后,客户端会发送 join 信令到服务端。而服务端收到 join 信令后,创建 Participator(参与人)实例,并且调用它的 init 方法进行初始化。
|
||
|
||
之后,Participator 获取 Room 中所有已共享的媒体流,并调用它的 addStream 方法,这样它就可以订阅 Room 中所有的媒体流了。订阅成功后,媒体流的数据就被源源不断地发送给Participator对应的终端了。
|
||
|
||
上面的逻辑完成之后,服务端会通知客户端 join 已经成功。此时,客户端也可以共享它的音视频流给服务器了。
|
||
|
||
此外,在客户端将自己的媒体流推送给服务端之前,服务器会调用 publishStream 方法在服务端创建 IncomingStream 实例,这样当客户端的数据到达服务器端后,就可以将数据交给 IncomingStream 暂存起来,以备后面分发给其他终端。
|
||
|
||
通过上面的步骤,新加入的客户端就可以看到会议中其他人分享的音视频流了,同时还可以将自己的音视频推送到服务器了。但此时,其他参会人还看不到新加入用户的音视频流,因为还有关键的一步没有完成。
|
||
|
||
这**关键的一步**是,当新参会人一切准备就绪后,服务端要发送广播消息(update) 通知所有已经在会中的其他人,有新用户进来了,需要重新进行媒体协商。**只有重新媒体协商之后,其他参会人才能看到新入会人的音视频流**。
|
||
|
||
以上步骤就是一个新参会者加入会议的大致过程,同时也介绍了这个过程中其他已经在会中的参与人需要如何处理。
|
||
|
||
## 小结
|
||
|
||
本文我们首先介绍了通过 Medooze SFU 如何搭建最简单的音视频会议系统,该系统搭建好后就可以进行多人音视频互动的“实验”了。
|
||
|
||
然后,我们对Medooze 的 SFU 项目的逻辑做了详细介绍,分析了Medooze SFU的基本架构、逻辑处理、入会流程等。相信通过本文的学习,你对 Medooze 的使用和实现原理都会有更深刻的认识。
|
||
|
||
当然,如果你要想在自己的产品中更好地应用 Medooze,还需要掌握更多的细节,只有这样才能做到心中有数。比如:如何能够承担百万用户级的负载?如何为不同的的地区、不同的运营商的用户提供优质的服务?这些都是你需要进一步研究和学习的。
|
||
|
||
## 思考时间
|
||
|
||
今天留给你的思考题:一个多人存在的会议中,又有一个人加入进来,其他参会人是如何看到后加入人的视频流的呢?你清楚其中的原理吗?
|
||
|
||
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。
|
||
|
||
|