在上一个稳定版本中,Jitsi 启用了一项名为 SSRC 重写的新功能,可提高超大型呼叫的系统性能。该功能通过减少涉及数百个端点的大型呼叫期间交换的信令消息数量,帮助减轻系统的整体负载。通过限制 WebRTC 引擎创建的音频和视频解码器的数量,它还能大幅降低本地端点的负载,从而为大型呼叫提供更好的用户体验。
启用该功能后,无论呼叫大小如何,都只会向呼叫中的下游端点发出一组固定的 SSRC 信号(例如最多 50 个)。SSRC 只是一个唯一的 ID,用于识别属于音频或视频源的 RTP 数据包流。当接收方请求额外的媒体源时,Jitsi Videobridge (JVB) 会用之前已向客户端发出信号且不再需要的媒体流的 SSRC 覆盖新请求的媒体流的 SSRC。因此,即使要路由的媒体源总数远远超过设定的限制,向端点发出的 SSRC 信号也不会超过 50 个。
Jitsi 实施 SSRC 重写是为了解决什么问题?
Jitsi 在为超大型呼叫添加信源信令支持时,面临着两个方面的挑战:一是采用现有方法将每个新信源信令给呼叫中的每个其他参与者。
在客户端层面,无论特定信源的媒体是否被路由到终端,SDP 中的 m 线数都会随着呼叫中添加的每个远程信源而线性增长。当带有 SSRC 的新 m 线被添加到远程 SDP 时,libwebrtc 引擎就会创建一个收发器,并在开始从 JVB 接收媒体流时,对带有给定 SSRC 的媒体流进行解码。这不必要地占用了本地端点的资源,并导致重新协商周期的延迟,给用户带来不愉快的体验。客户端还可能会受到浏览器施加的收发器和 SDP 解析器限制,从而导致意想不到的行为。这些性能问题在移动端点上更为明显,因为与在台式机上运行的端点相比,移动端点的资源本来就比较少。
在后端,随着参与者人数的增加,需要向呼叫中的其他参与者发送信号的音频和视频源数量也在增加。这使得从 prosody(XMPP 通信服务器)到端点的信令流量呈四倍增长。这是一个问题,因为单线程的 Prosody 已经成为扩展呼叫时的瓶颈。以前,我们不得不在信号传递中引入人为延迟,以减少负载。这导致与会者在首次取消音频或视频静音时,媒体的建立会出现长时间的延迟,对大型会议造成了极大的干扰。
解决这两个问题的办法是转而采用基于需求的信令机制,根据端点的实时需要或请求,只向端点发送数量有限的远程音频和视频音轨信号,而不是在呼叫中添加所有已知媒体源时再发送信号。
在 Jitsi Videobridge 中的实现
在决定转发哪些信号源时,Jitsi Videobridge (JVB) 对音频和视频使用了略有不同的方法。音频的转发决定仅仅基于从音频级别 RTP 扩展确定的流的 “响度”。
通过 SSRC 重写,JVB 为每个接收器使用单独的 SSRC 空间。它维护一个从 SSRC 编号到信号源名称的映射。映射的更改通过直接信令通道(SCTP 上的 WebRTC 数据通道)向接收器发出信号,并使用部分更新:
[modules/RTC/BridgeChannel.js] <e.onmessage>: Received AudioSourcesMap: [{"source":"fc63db0f-a0","owner":"fc63db0f","ssrc":2602882473},{"source":"449360a0-a0","owner":"449360a0","ssrc":1358697798}]
[modules/RTC/BridgeChannel.js] <e.onmessage>: Received VideoSourcesMap: [{"source":"e01f2103-v0","owner":"e01f2103","ssrc":3129389873,"rtx":3219602897,"videoType":"CAMERA"},{"source":"9ac8fef2-v0","owner":"9ac8fef2","ssrc":1542056973,"rtx":1571329554,"videoType":"CAMERA"},{"source":"ed6b60f5-v0","owner":"ed6b60f5","ssrc":550523896,"rtx":2808127984,"videoType":"CAMERA"}]
当需要转发一个新的数据流时,就会分配一个 SSRC。在达到上限之前,JVB会简单地生成一个新的SSRC号码,当达到上限时,最旧的条目会被重新使用。看一个例子来更清楚地说明这一点。假设限制设置为 3,可用信源为 A、B、C、D、E。当 A 开始发送数据包时,我们将 SSRC 101 分配给它,并像这样向接收方发出信号:
AudioSourcesMap: [{"source":"A","owner":"endpoint-A","ssrc":101}]
同样,当 B 和 C 开始说话时,为他们分配 SSRC 102 和 103:
AudioSourcesMap: [{"source":"B","owner":"endpoint-B","ssrc":102}]
AudioSourcesMap: [{"source":"C","owner":"endpoint-C","ssrc":103}]
现在我们已经达到了 3 个 SSRC 的限制。当 D 开始说话时,我们将在地图中找到最近最不活跃的源(假设是 B),并为 D 重新使用其 SSRC。我们将发出更新信号(“SSRC 102 现在属于 D ”):
AudioSourcesMap: [{"source":"D","owner":"endpoint-D","ssrc":102}]
该方案与视频相同,只是转发决策的方式不同。接收者使用视频限制明确地表达他们的偏好。当端点将其源信息发送给 Jitsi Conference Focus (Jicofo) 时,源名称及其静音状态会发布,因此该信息已可供呼叫中的所有其他端点使用。根据 UI 中的当前布局和用户首选项,客户端通过桥接通道发送更新的接收器视频约束。
然后,JVB 中的带宽分配算法根据其约束和当前网络条件决定将哪些流转发到特定接收器:
[modules/RTC/BridgeChannel.js] <Fa.sendReceiverVideoConstraintsMessage>: Sending ReceiverVideoConstraints with {"constraints":{"ed6b60f5-v0":{"maxHeight":360},"e01f2103-v0":{"maxHeight":360},"9ac8fef2-v0":{"maxHeight":360}},"defaultConstraints":{"maxHeight":0},"lastN":-1,"onStageSources":[],"selectedSources":[]}
接收端在客户端的实现
在接收到其中一个地图(音频或视频)的更新时,客户端会将发出信号的 SSRC 添加到对等连接上的远程描述中。然后,浏览器会为每个 SSRC 触发一个音轨事件,相应的远程音轨就会被添加到与远程用户相关联的 HTMLElements 中。
因此,当带有该 SSRC 的音频数据包到达时,浏览器就会开始解码媒体,并通过选定的音频输出设备播放。如果 SSRC 已被使用(即已达到桥接器的限制),客户端会更新相关音轨的所有者,以便将其附加到相应的 HTMLAudioElement,并将音频无缝切换到新的扬声器。
视频轨道的创建过程与上述音频轨道的创建过程相同。每当有涉及分配给轨道的 SSRC 的源映射更新时,客户端应用程序都需要更新轨道的所有者,并将其重新附加到相应的 HTMLVideoElement 上,以便在远程参与者的视口中呈现正确的视频流。
挑战
但是,等等,像这样重复使用 SSRC 对于音频来说没有问题,因为流在播放前会被混合,但对于视频来说呢?当信号和媒体竞争时,我们如何避免视频内容在错误的视口中呈现?这就是这种方法的精妙之处,我们只需使用一个足够大的限制(大于同一时间转发的最大流数量),这种情况就会变得极不可能发生。如果限制大于 K,那么在信令到达接收方之前,必须做出 K 个新的转发决定,才会出现问题。
那么,该如何选择限制呢?我们有刚才提到的限制,但也有一个有趣的权衡。如果限制过高,我们就会在接收器上使用不必要的资源。但如果限制太低,就会更频繁地发出更新信号。我们选择将限制默认设置为 50。音频为 50,视频为 50,这远远高于我们随时显示的最大 25 个图块。
启用 SSRC 重写后,源信令消息的数量会根据为会议设置的 SSRC 限制和通话参与者总数而急剧增加。试想一下,在一个有 100 名与会者的通话中,每个人都打开了视频;用户界面显示了一个 25 名与会者的网格,SSRC 限制设置为 25。每当用户滚动到下一个 25 名与会者的网格时,现有的 SSRC 就会重新映射。每次用户来回滚动时都会发生这种情况。这会导致桥接通道上出现大量信令信息。如果此时桥接通道的 websocket 连接断开了怎么办?这将导致无法渲染视频或无法听到来自新的主要扬声器的音频,从而对会议造成极大的干扰。所有音源都会在 websocket 连接改革后立即发出信号,但即使是最小的音频中断也会非常烦人。
为了缓解这些问题,Jitsi 客户端改用 WebRTC 的 SCTP 数据通道来建立桥接通道,而不是使用 Websocket。这可确保只要客户端与 JVB 之间的媒体连接启动,桥接通道就始终启动并运行。这使得从 JVB 到下游端点的信令消息中断最小或没有中断。
该功能的当前状态
此功能已经过充分测试,过去几个月一直在 meet.jit.si 上运行,限制设置为 50。我们还在 Debian 软件包和 Docker 镜像的最新稳定版本中默认启用了它。我们将很快在接下来的几个版本中将其发布到所有生产部署中,等待对我们在 JVB 中看到的一些 SCTP 崩溃进行调查。
作者:Jaya Allamsetty
译自:https://jitsi.org/blog/improving-performance-on-very-large-calls-introducing-ssrc-rewriting/
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/47606.html