为什么视频聊天是一个技术难题
今年夏天,我开始了一系列实验,探索居家隔离期间同步在线社交互动的新形式。这些实验的范围很广,包括在一款自定义的基于文本的大型多人在线角色扮演游戏中举办的虚拟会议,以及在浏览器中使用实时动作捕捉技术制作 2D 动画头像:
在这些早期实验中,我使用了WebRTC,这是一种基于浏览器的点对点视频聊天技术。由于我快速地进行了一些小型实验,所以我希望能够尽快构建出一些东西,并且理想情况下不需要启动复杂且/或昂贵的服务器。
WebRTC 听起来非常适合这个!点对点意味着你不需要复杂或昂贵的服务器基础设施,而且作为一项得到良好支持的浏览器技术,意味着有大量的教育资源可用。
直接说重点:我们为Roguelike Celebration的活动平台搭建了一个基于 WebRTC 的视频聊天服务,之后却把它拆掉,用一系列 Zoom 链接替换了实际活动的内容。我们的 WebRTC 设置根本不适合生产环境。
此后,我与许多其他构建过 WebRTC 系统的人进行了交流,这些系统从简单到复杂,都遇到了同样令人无法接受的性能陷阱。这并不意味着 WebRTC 作为一项技术不适用于此类情况——本文后面我推荐的所有解决方案最终仍然在底层使用 WebRTC——但实际情况远比仅仅阅读 WebRTC API 规范并基于其进行构建要复杂得多。
本文的其余部分将带您了解我们的学习过程,以及我们学到的在生产环境中构建 WebRTC 视频聊天所需的知识。我们实现视频聊天功能的道路漫长而曲折;我想概述一下我们的经验,以避免其他人花费与我们相同的时间和精力来理解这一点。
问题 1:访问 AV 硬件
在通过网络发送音频和视频流之前,我们需要音频和视频流。这意味着要使用浏览器的MediaDevices API,而不是 WebRTC。但这有一个问题!
该 API 很简单。您可以调用navigator.mediaDevices.getUserMedia()
并访问音频和视频流。问题是:用户无法指定要使用的具体输入设备,因此拥有多个麦克风或网络摄像头的用户将很难使用。您可能认为 Web 浏览器会提供自己的 UI 来让用户选择设备,但实际情况却很复杂。
如果有人使用 Firefox,他们实际上会弹出一个友好的窗口,询问他们想要使用哪种音频和视频输入。如果他们使用 Chrome,这个选项会隐藏在设置菜单的深处,而且它很难记住你的偏好设置。Safari 浏览器根本没有这个用户界面。
解决方案:构建可投入生产的应用程序意味着您需要为可用的音频和视频输入构建自己的应用内设备选择器。
这是可行的,但很麻烦。您还必须处理不同浏览器访问 MediaDevices API 的方式不一致的问题。理想情况下,您应该使用某种持久本地存储(例如 localStorage API),这样您就可以记住用户的偏好设置,而不必让他们每次进入聊天时都要浏览下拉菜单。
问题 2:建立连接
好的,现在你已经获得了来自正确本地输入设备的正确音频和视频流。现在我们需要一种方法来将它们发送给其他用户!
在 WebRTC 中进行群组视频聊天最直接的方式是使用所谓的全网状网络拓扑。这听起来很复杂,但它的意思是“每个客户端都连接到其他所有客户端”。如果我们三个人同时在线,我们每个人的浏览器都会直接连接到其他两个人的浏览器,而新加入的人会立即与我们每个人建立三个新的连接。
要在两个客户端之间建立 WebRTC 连接,一个客户端需要发出一个请求。另一个客户端接受该请求并生成一个响应。发起该请求的客户端接受该响应,然后就可以开始连接了。
要在客户端之间来回发送这些请求和响应,您需要某种数据传输机制。由于您尚未建立可用的 WebRTC 数据连接,这意味着您需要某种服务器基础设施。构建和扩展一个用于在客户端之间交换握手字符串的后端比构建一个用于发送视频数据的后端要少得多,但这并非毫无意义。
解决方案:您需要构建自己的服务器后端,该后端可以在客户端之间传输字符串,直到它们成功打开点对点连接。
WebSocket 是一个很好的选择,但与常规 HTTP 服务器相比,WebSocket 的扩展也很困难。我个人使用Azure Functions和Azure SignalR 服务的组合来进行这种握手(其架构类似于我在本文中概述的架构),但这仍然需要维护服务器端服务!
问题 3:如果网络设置导致客户端无法连接怎么办?
假设你构建了一个简单的 WebRTC 流程,其中 4 个不同的人彼此连接。这意味着所有参与者之间将有 6 个不同的 WebRTC 连接。你很快就会发现一些非常奇怪的事情:很有可能这 6 个连接中至少有一个会失败,导致其中两个人无法进行视频聊天。
简单来说,这是路由器设置的问题。WebRTC 信号握手完成后,一个名为 ICE 的远程服务会尝试通过获取两个客户端的公共 IP 地址来直接连接它们。
ICE 服务首先会尝试使用 STUN 服务器,该服务器的作用是告知客户端其公网 IP 地址。理想情况下,它能为两个客户端提供可用的 IP 地址,一切就绪。
如果一个或两个客户端位于高度防护的 NAT 层之后(例如由于公司防火墙),STUN 公网 IP 协议将无法正常工作。在这种情况下,由于客户端无法直接连接,因此两个客户端都需要连接到一个称为 TURN 服务器的中继服务器,该服务器负责转发两者之间的所有消息。
如果您对该问题的更详细的技术解释感兴趣,那么这篇文章是一个很好的资源。
传统观点认为,仅使用 STUN 协议,大约 80% 的 WebRTC 连接都能成功。这意味着,除非您有 TURN 服务器可以回退,否则大约 20% 的连接将会失败!
解决方案:当客户端的 NAT 设置不允许它们直接连接时,运行您自己的TURN 中继服务器。
STUN 服务运行成本低,而且很容易找到可以配合你的原型扩展的免费服务。由于 TURN 服务器占用更多资源(因为它们在连接的握手阶段之后仍然活跃),你可能需要自己托管,而不是寻找免费的社区选项。
一种选择是使用Twilio 托管的 TURN 服务。另一种是在 Azure 等云提供商上托管您自己的 Docker 镜像。
问题四:连接的人太多怎么办?
至此,您已经拥有一个可以运行的视频聊天应用。您已经构建了自己的 AV 选择器 UI,以便用户选择自己的设备。您已经构建了服务器基础设施,以便客户端完成报价握手。您正在运行一个 TURN 服务器,以确保每个人都能连接到网络,无论他们的网络设置如何。这一切听起来很棒。
然后,您尝试与 4 个人以上进行视频通话,但您的计算机却突然停止运行。
这种“全网状”设置(4 人视频聊天中的每个人都在向其他三名参与者发送和接收视频数据)非常浪费。
每增加一个参与者,您的带宽和 CPU/GPU 消耗都会线性增加。即使是性能强劲、网络连接稳定快速的电脑,在视频参与者数量达到 4 人左右或纯音频参与者数量达到 10 人左右时,性能通常也会开始下降。
这需要稳定的网络连接。如果一个参与者的网速较慢,理想情况下,其他客户端会开始向他们发送较低比特率的视频流,但这种选择性实时转码在浏览器中实际上是不可行的。
值得注意的是,这不仅仅是一个技术问题,而是一个可访问性问题:通过构建一个除非您拥有顶级计算机和超快的互联网连接否则就会崩溃的系统,您构建的系统仅为最有特权的人服务。
除了不必发送相同的音频/视频流 N 次并同时解码和呈现 N 个远程 A/V 流之外,没有明确的解决办法。
解决方案:放弃全网状对等系统,转而采用集中式系统,最有可能的是选择性转发单元(SFU)。
SFU 是一个服务器,充当单个 WebRTC 对等体,用于发送和接收视频数据。您的客户端无需直接连接到所有使用聊天应用的用户,只需连接到 SFU 并将其 A/V 流发送到该单一源即可。SFU 会选择性地决定哪些其他连接的客户端应该接收给定的音频或视频流,并且还可以智能地执行诸如动态视频重新编码之类的操作,以便为带宽上限较低的客户端提供较低比特率的流。
运行 SFU 的方法有很多种,但一种常见的方法是将mediasoup库集成到您自己的 Node.js 服务器中,以便您可以按照自己想要的方式配置和扩展它。
...但这对于进行基本的视频聊天来说已经太多了!
我同意!我最初的目标是构建一些有趣的、新颖的社交互动模式的小原型,结果却发现自己深陷在网络协议和点对点网络拓扑的技术泥潭中。
我希望这个关于实现 WebRTC 的棘手部分的全面概述至少可以让您理解为什么这是一个难题,并为您提供提出自己的解决方案的思路。
具体来说,我有两条具体的建议:
-
如果您只是在尝试,可以先使用完全托管的视频解决方案,例如Azure 通信服务或Twilio 可编程视频。您将获得一个易于集成的 API,它不需要运行您自己的服务器后端,音频和视频聊天功能可以自动扩展到任意数量的同时用户,并且原型规模的使用成本相对较低。
-
如果您正在构建一个以视频或音频聊天为核心组件的生产软件,托管解决方案仍然是最省力的选择,但您可能希望构建自己的解决方案以节省成本并更好地控制基础架构。如果是这样,请直接运行自己的 SFU。仅仅尝试使用全网状拓扑和 TURN 服务器最终是不够的。从我和无数其他人的经验中学习,节省您的时间和精力。
这有帮助吗?你想出自己的解决方案来推荐吗?请在Twitter上告诉我,我很高兴听到更多人解决这些难题 :)
文章来源:https://dev.to/lazerwalker/why-video-chat-is-a-hard-technical-problem-43gj