Mediasoup Overview
mediasoup 是一个基于 C++ 实现具有 SFU 的功能库,其 server 端对外提供 Nodejs API。
mediasoup 自身并不提供任何信令协议。所以需要应用程序,来确认 mediasoup 客户端与服务端进行通信的方式,用来协商客户端与服务端的相关参数和信息。在大多数情况下,此通信必须是双向的,因此通常需要全双工信道(如 TCP 或 SCTP 通道)。但是,应用程序可以将同一通道重新用于与非 mediasoup 相关的消息交换(例如身份验证过程,聊天消息,文件传输以及应用程序希望实现的任何功能),这样的多路复用策略,可以提升整个系统的性能。
传统的 VoIP 的语音客户与服务端,信令协议,一般较多采用 SIP 协议,同时通过携带 SDP 数据包,进行 service 与 client 端的 RTP 相关的媒体协商。
- 传统的 VoIP 针对 RTP 相关的协商内容一般包括:
- 通道 (Channel),包含 UDP IP 地址、端口以及通讯方式(sendonly, recvonly, sendrecv)
- 媒体格式及编码 比如 Audio: PCMA/PCMU Vedio: H264/H265
mediasoup 中,针对 RTP 的相关参数,也是需要协商的。
Mediasoup RTP Parameters and Capabilities
- Parameter 参数
包含两个方向的参数:
- 与会者 A 作为 producer / sender (媒体数据或者说音视频数据的生产者,以及发送者)发送给 mediasoup 云服务的 RTP Send Parameter
- 与会者 B 作为 consumer / receiver (媒体数据或者说音视频数据的消费者,以及接收者)接收来自于 mediasoup 云服务的 RTP Receive Parameter
注意: A 和 B 一般既是生产者也是消费者。
- Cpabilities 能力
- mediasoup 服务端,或者与会者 client 端能够接受的,有能力识别并可以顺利处理的 RTP Parameters 描述的媒体,即称之为该端的 RTP Capabilities
Mediasoup RTP Negotiation Overview
当创建一个 Mediasoup 的 Router 后,该 Router 提供一系列音视频 codec 来告知 Router 创建者,该 Router 能够处理的音视频。该 codec 能力,将用 RtpCodecCapability 数据结构表示。
而 Meidasoup 服务端 RTP 能力,定义在了 mediasoup/src/supportedRtpCapabilities.ts 文件中。(不止一个,是一组能力)
Guidelines for mediasoup-client and libmediasoupclient
- mediasoup-client: a client of mediasoup wrriten by JavaScript
- libmediasoupclient: C++ library based on libwebrtc
- 无论 js 版本还是 c++ 版本的库,内部都可以生产 mediasoup 可接受的 RTP Parameter 对象实例,有效的简化了 mediasoup 客户端的编程。
Mediasoup C/S 基于 Websocket 的通讯
- 假设客户端使用 Device 对象提供的方式进行连接。而 mediasoup 服务端已经完成了 Router 的创建。
- 应用程序可以使用 WebSocket 并将每个经过身份验证的 WebSocket 连接与 peer 端关联。
客户端的典型流程
- Device Loading
- Creating Transports
mediasoup client 端,需要独立的 WebRTC 连接,去分别处理数据的发送和接收。
- For sending media
- 服务端 mediasoup router 中,必须首先创建 WebRTC 连接,方式如下:
router.createWebRtcTransport(options) - 客户端中创建 WebRTC 发送连接,方式如下:
device.createSendTransport(options) - 客户端必须订阅(监听)该 WebRTC 连接的如下事件:
“connect” & “produce”
- For receiving media
- 服务端 router 中,首先创建 WebRTC 连接(方式同上)
- 客户端中创建 WebRTC 接收连接,方式如下:
device.createRecvTransport(options) - 客户端必须订阅(监听)该连接的 “connect” 事件
- 以上连接中,如果需要使用 SCTP (AKA DataChannel in WebRTC)(我知道什么是SCTP,但是确实不知道什么是AKA),那么需要进行额外的处理,配置 enableSctp, 设置 SCTP 协议相关参数,给定 SCTP 协议连接池的大小等等。
- 客户端生产数据 Producing Media
一旦 sent 连接被建立,客户端就可以根据该连接,"生产"多轨道的音视频数据了。
- 客户端可以通过比如 navigator.mediaDevices.getUserMedia() API 获取一个轨道。
- 客户端调用 transport.produce(options)
- 如果是第一次的 produce 调用这个,会触发 “connect” 事件。
- produce 调用会触发 “produce” 事件,且该事件相关参数会被发送到服务端,服务端监听到该事件之后,会创建一个 Producer 实例。
- 客户端会根据服务端的 Producer 实例,进行 produce 处理(Finally transport.produce() will resolve with a Producer instance in client side.这是原文,具体后续要看实验的结果,这种表达方式,不易理解)
- 客户端 Consuming Media
一旦 recv 连接被创建,客户端就可以根据该连接,接收并处理媒体数据了。实际过程与 producing 恰恰相反。
- 第一,客户端必须先要把自己的 RTP 能力告知服务端(此步可以提前)。
- 第二,服务端会调用如下接口:
router.canConsume({ producerId, rtpCapabilities })
作用:确定,某个 producer 的媒体 codec ,该客户端是否支持。 - 第三,服务端调用 transport.consume(options) 接口,创建 Consumer 实例。
- 服务端创建 Consumer 时,强烈建议将其设置 paused 置为 true ,然后将该 Consumer 参数传输到客户端,并且一旦客户端创建了其本地 consumer,此时服务单再调用 resume() 方法,激活服务端的 Consumer
- 第四,服务端调用 transport.consume() 接口,将本地的一些参数,发送给客户端。
- 首次调用 transport.consume() 接口,会触发一次 “connect” 事件
- 最后,服务端通过 transport.consume() 接口,与客户端的 Cosumer 进行媒体传输
SCTP DataChannels 流程与上类似,接口略有差别
Communicating Actions and Events
mediasoup 一个核心准则,就是,当主动调用某些实例的方法,进行的一些操作,比如close,并不会触发相应的事件。
close event
无论是 server 端还是 client 端,当关闭了以上流程中涉及到的相应的对象时,那么应当及时的将关闭的状态,“通知”到相关方。
额外的, server 端,相应的对象,需要监听特定的事件,并且需要将该事件及时 notify 给到 client 端,并且,client 端需要进行特定的输出操作。
- Transport “routerclose”. The client should call close() on the corresponding local transport.
- transport 的 routerclose 事件,client 端应及时 close() 本端的 transport。
- Producer “transportclose”. The client should call close() on the corresponding local producer.
- Consumer “transportclose”. The client should call close() on the corresponding local consumer.
- Consumer “producerclose”. The client should call close() on the corresponding local consumer.
- DataProducer “transportclose”. The client should call close() on the corresponding local data producer.
- DataConsumer “transportclose”. The client should call close() on the corresponding local data consumer.
- DataConsumer “dataproducerclose”. The client should call close() on the corresponding local data consumer.
pause event
- The same happens when pausing a RTP producer or consumer in client or server side. The action must be signaled to the other side.
- In addition, the server side application should listen for the following events and notify the client about them:
- Consumer “producerpause”. The client should call pause() on the corresponding local transport.
- Consumer “producerresume”. The client should call resume() on the corresponding local transport (unless the consumer itself was also paused on purpose).
SVC or simulcast
- When simulcast or SVC is in use, the application may be interested in signaling preferred layers and effective layers between client and server side consumers.
- The server side application sets the consumer preferred layers via consumer.setPreferredLayers().
- The server side consumer subscribes to the “layerschange” event and notifies the client application about the effective layers being transmitted.
mediasoup 尝鲜 demo
环境
Ubuntu 16.04 LTS
node: 12.18.2 LTS
npm: 6.15.5
-
新建文件夹,作为我们的工作区
mkdir csrtc
-
进入文件夹,进行准备工作
cd csrtc
touch server.js
sudo npm init
(npm init 是一个交互过程,需要确认输入一些信息,根据提示往下走即可)
-
补充,规避bug
在 package.json 中添加如下内容,主要是在 stripts 中添加 start 的那一行的内容。
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "sudo node server.js"
}
-
安装 mediasoup 第三版
sudo npm install mediasoup@3 --save
-
验证安装,server.js 中编辑以下内容
const mediasoup = require("mediasoup");
console.log(mediasoup.version);
能够看到控制台输出 3.6.9
后续开发
如果涉及到 C++ 层面 mediasoup 内容的修改,不是添加或者删除文件之类(需要修改gyp,现阶段先尽量避免),后回到工作区执行: sudo npm rebuild 即可实现内部模块的重新编译。