软件即服务,基础架构即服务,平台即服务,通信平台即服务,视频会议即服务,那么,游戏即服务(Game as a Service)如何呢?已经有不少科技公司试水云游戏,最著名的要数Google的Stadia。对WebRTC来说,Stadia已经算是老朋友了,但是其他云游戏也能以同样的方式运用WebRTC吗? Thanh Nguyen研究了他自己的开源项目CloudRetro在这方面是否可行。CloudRetro基于很受欢迎的WebRTC的pion库。在这篇文章中,Thanh对他如何构建项目进行了框架性的回顾与思考,以及他在此过程中遇到的一些挑战。
作者:Thanh Nguyen
翻译:LiveVideoStack
原文链接:https://webrtchacks.com/open-source-cloud-gaming-with-webrtc/
简介
去年,谷歌发布了Stadia,这个想法的独特性和创新性颠覆了我的认知。我一直在质疑当前的技术状态怎么可能支持Stadia。为了揭开Stadia的神秘面纱,我创建了Cloud Gaming的开源版本。我想在接下来的文章中分享有关自己开发Cloud Gaming的一年时间的大冒险。
为什么云游戏才是未来
我相信云游戏不仅将很快成为新一代游戏,而且还将成为新一代的计算机科学甚至其他领域。云游戏是客户端/服务器模型的顶峰。通过将游戏逻辑放在远程服务器上并将图像/音频流传输到客户端,它可以最大化后端控制并最小化前端工作。由此,服务器将负责处理繁重的任务,而客户端将不再受硬件限制。
就Google Stadia而言,它实际上使用户可以在YouTube等界面上玩3A游戏。可以将相同的方法应用于其他繁重的脱机应用程序,例如操作系统或2D / 3D图形设计等,以便我们可以跨平台在低规格设备上一致地运行它们。
云游戏仍然面临技术挑战
游戏是少数需要用户持续且快速反应的应用之一。如果我们单击页面时出现2秒钟的延迟,这是可以接受的。直播视频流通常会延迟很多秒,但仍然具有可用性。但是,如果游戏频繁延迟500毫秒,该游戏将无法播放。
当前的目标是实现极低的延迟,以确保游戏输入与媒体之间的gap尽可能小。因此,传统的视频流传输方法不适用于将图像/音频流传输到客户端的情况。
开源项目CloudRetro
我决定创建一个云游戏的POC,这样我就可以验证在这些严格的网络限制下是否仍有可能实现以上所说的低延迟。我选择了Golang作为我的POC,因为这是我最熟悉的语言,当然也因为它便于运作且开发速度快。当处理并发和流操作时,Go通道也非常有用。
这个项目就是CloudRetro.io:针对怀旧游戏的基于Web的云游戏服务开源项目。我的目标是希望带来最舒适的游戏体验,并将在线多人游戏等网络游戏引入传统的复古游戏。
你可以引用整个项目库:https://github.com/giongto35/cloud-game
CloudRetro的功能性
CloudRetro使用Retro游戏演示了Cloud Gaming的强大功能并带来了许多独特的游戏体验。
- 便携式游戏体验
- 即点即玩,无需下载安装
- 在浏览器上运行,无需任何软件即可启动
- 游戏会话可以在多个设备之间共享,并存储在云中方便下次游戏
- 游戏可播可玩,并且多个用户可以加入同一游戏
- 类似于TwitchPlayPokemon的人群播放,但更实时,更无缝
- 在线多人游戏,无需网络设置即可进行离线游戏。现在可以在CloudRetro上通过网络与2位玩家一起玩《武士对决》
架构
要求与技术配置
以下是我在开始这个项目前列出的一些要求。
- 单人游戏
这项要求听起来并不相关且非常直接,但这是我的主要发现之一,它使云游戏摆脱了传统的流媒体服务。如果我们专注于单人游戏,就可以摆脱集中式服务器或CDN,因为我们不需要将会话流分配给大量用户。该服务不是通过将流上传到摄取服务器或将数据包传递到集中式WebSocket服务器,而是通过WebRTC对等连接直接流向用户。
- 低延迟媒体流
当我研究Stadia时,有些文章提到了WebRTC的应用。我发现WebRTC是一项非凡的技术,而且非常适合云游戏。
WebRTC是一个通过简单的API为Web浏览器和移动应用程序提供实时通信的项目。它支持对等通信,并针对媒体进行了优化,并具有内置的标准编解码器,例如VP8和H264。
我优先考虑为用户提供最流畅的体验,而不是保留高质量的图形。该算法中有一些损失是可接受的。在Google Stadia上,还有一个步骤来减小服务器上的图像大小,并且图像帧在渲染给对等对象之前被重新缩放为更高的质量。
- 具有地理路由的分布式架构
无论压缩算法和代码如何优化,网络仍然是导致延迟最关键的因素。该体系结构需要一种将最近的服务器与用户配对的机制,以减少往返时间(RTT)。这样的体系结构包含单个协调器和分布在世界各地的多个流服务器:美国西部,美国东部,欧洲,新加坡,中国。所有流服务器完全被隔离开来了。当服务器加入或离开网络时,系统可以调整其分布。因此,在超高流量下,添加更多服务器可实现水平扩展。
- 浏览器兼容
在用户需求极少的情况下,云游戏的表现是最好的。这也意味着能够在浏览器上运行。浏览器通过删除软件和硬件安装为用户带来最舒适的游戏体验,同时,它还有助于在移动设备和台式机之间提供跨平台的灵活性。幸运的是,WebRTC在不同的浏览器中都具有出色的支持能力。
- 明确划分游戏界面及服务
我将云游戏服务看作是一个平台,一个能够将任何插件插入的平台。目前,我将LibRetro(https://www.libretro.com/)与云游服务集成在一起,因为LibRetro为SNES,GBA,PS等复古游戏提供了美观的游戏。
- 为多人、群体游戏和深层游戏链接服务的基于房间的机制
CloudRetro支持许多新颖的游戏玩法,例如用于复古游戏的CrowdPlay和线上多人游戏。如果多个用户在不同计算机上打开相同的深层链接,他们将看到的正在运行的游戏与视频流相同,而且他们可以像视频中任何一个玩家一样加入游戏。
此外,游戏状态存储在云中使用户可以随时在任何其他设备上继续他们的游戏。
- 水平缩放
像今天的每一个SAAS一样,云游戏必须被设计为可水平扩展。协调器-工作器的设计允许增加更多的工作器以服务更多的流量。
- 不可知的云
CloudRetro的基础架构托管在各种云提供商(Digital Ocean,阿里巴巴,定制提供商)上,以对标不同的区域。我通过bash脚本对基础架构进行了dockerize和配置网络设置,以避免依赖任何一个云提供商。结合使用WebRTC的NAT遍历,我们可以灵活地将CloudRetro部署在任何云平台甚至任何用户的计算机上。
架构设计
- worker:(或者是上面提到的流服务器)生成游戏、运行编码管道、并将编码的媒体流传输给用户。Worker分布在世界各地,每一个都可以同时处理多个用户会话。
- Coordinator:负责将新用户与最适合的Worker配对并进行流传输,通过WebSocket与worker进行交互。
- Game state storage:所有游戏状态的中央远程存储。该存储实现了一些基本功能,例如远程保存/加载。
用户流
当新用户在下图所示的步骤1和2中打开CloudRetro时,协调器将被要求提供前端页面以及可用Worker列表。之后,在第3步,客户端使用HTTP ping请求计算所有候选者的延迟。此延迟列表随后发送回协调器,以便它可以确定最适合为用户服务的worker。在下面的步骤4中,游戏生成。WebRTC流连接是在用户和指定worker之间建立的。
Inside the worker
在worker内部,游戏和流管道保持隔离状态,并通过接口交换信息。当前,该通信是通过Golang通道上的内存传输过程完成的。下一个目标是进一步隔离–即以不同的过程独立运行游戏。
主要部分是:
- WebRTC:面向客户端的组件,用户输入进入,服务器的编码媒体输出。
- 游戏模拟器:游戏组件。借助Libretro库,该系统能够在同一进程内运行游戏,并在内部挂钩媒体和输入流。游戏中的帧被捕获并被发送到编码器。
- 图像/音频编码器:编码管道,它在其中接收媒体帧、在后台进行编码并输出编码的图像/音频。
应用
CloudRetro依靠WebRTC作为骨干,因此在详细介绍我在Golang中的实现之前,第一部分要专门介绍WebRTC技术。这是一项很棒的技术,可以极大地帮助我实现亚秒级的延迟流。
WebRTC
WebRTC旨在通过简单的API在本机移动设备和浏览器上实现高质量的对等连接。
NAT Traversal
WebRTC以其NAT Traversal功能而闻名,它被设计用于对等通信,旨在找到最合适的直接路由,避免NAT网关和防火墙通过名为ICE的进程进行对等通信。作为此过程的一部分,WebRTC API使用STUN服务器找到您的公共IP地址,并在无法建立直接通信时回退到中继服务器(TURN)。
但是,CloudRetro没有充分利用此功能。它的对等连接不是在用户与用户之间,而是在用户与云服务器之间。与典型的用户设备相比,该模型的服务器端对直接通信的限制较少。服务器不在NAT之后,可以进行预打开入站端口或直接使用公共IP地址等操作。
以前,我曾经有让这个项目成为云游戏分发平台的野心。这个想法是想让游戏创作者贡献游戏和流媒体资源,用户将直接与游戏创作者的提供者配对。以这种分散的方式,CloudRetro只是将第三方流资源与用户连接的一种媒介。
因此当托管的负担不再依赖CloudRetro时,它会具有更高的可扩展性。WebRTC NAT Traversal在简化第三方流资源上的对等连接初始化时将发挥重要作用,进而使创建者毫不费力地加入网络。
视频压缩
视频压缩是管道中必不可少的部分,它极大地有助于流畅的流媒体体验。尽管不一定要完全了解VP8 / H264的所有视频编码细节,但了解其概念有助于阐明流速度参数、调试意外行为并调整延迟。
用于流服务的视频压缩具有挑战性,因为该算法需要确保总编码时间 网络传输 解码时间的总和尽可能短。另外,编码过程需要是连续并有次序的。某些传统的编码折衷方法并不适用–例如用较长的编码时间换取较小的文件大小和解码时间,或者是无序压缩。
视频压缩需要忽略不必要的信息,同时将保真度控制在保持用户可以理解和接受的范围。除了对单个静态图像帧进行编码之外,该算法还根据先前和将来的帧对当前帧进行了推断,因此仅发送差异。如同在下面的Pacman示例中看到的,仅有差分点被传输。
音频压缩
同样,音频压缩算法会忽略人类无法感知的数据。目前性能最佳的音频编解码器是Opus。Opus旨在通过有序数据报协议(例如RTP实时传输协议)传输音频波。它比(mp3,aac)具有更高的质量、产生更低的延迟(通常约为5〜66.5 ms)
Pion是一个将WebRTC引入Golang的开源项目。Pion不是简单地包装本机C WebRTC库,而是一种本机Golang实现,可以实现更好的性能、更好的Golang集成以及对基本WebRTC协议的版本控制。
该库还提供具有许多出色内置功能的亚秒级延迟流。它具有自己的STUN,DTLS,SCTP等实现,以及QUIC和WebAssembly的一些实验。这个开源库本身是一个很好的学习资源,其中包含出色的文档、网络协议实现和示例。
由非常热情的创建者领导的Pion社区非常活跃,并且对WebRTC进行了许多高质量的讨论。
如果你对此技术感兴趣,请加入http://pion.ly/slack –你将学到许多新东西。
Write CloudRetro in Golang
Go Channel In Action
由于Go的通道设计精美,事件流和并发问题得到了极大的简化。如图所示,在不同的GoRoutine中有多个并行运行的组件,每个组件管理自己的状态并通过通道进行通信。
Golang的select语句强制每个game tick都处理一个原子事件,这意味着此设计不需要锁定。例如,当用户保存时,需要一个完整的游戏状态快照。该状态需要通过运行输入保持不间断,直到保存完成。在每个game tick中,后端只能处理保存操作或输入操作,因此它也是同时安全的。
Fan-in / Fan-out
这个Golang模式与我的CrowdPlay和Multiple Player用例完全匹配。按照这种模式,同一房间中的所有用户输入都扇入一个中央输入通道,然后将游戏媒体分发给同一房间中的所有用户。因此,我们实现了来自不同用户的多个游戏会话之间的游戏状态共享。
Golang的劣势
Golang并不完美,它的通道缓慢。与锁定相比,Go通道只是处理并发和流事件的更简单方法,但是通道并不能提供最佳性能。通道下有一个复杂的锁定逻辑。因此,我通过在替换通道时重新应用锁定和原子值来对性能进行一些调整,以优化性能。
此外,Golang垃圾收集器是无法控制的,因此有时会有一些可疑的长时间停顿。这极大地损害了该应用程序流的实时性。
CGO
该项目使用一些现有的Golang开源VP8 / H264库进行媒体压缩,并使用Libretro作为游戏模拟器。所有这些库都只是使用CGO在Go中对C库的包装。你可以参考Dave的这篇博客文章(https://dave.cheney.net/2019/10/06/use-internal-packages-to-reduce-your-public-api-surface)。我现在面临的问题是:
- 即使使用Golang Recovery,也无法捕获CGO的崩溃
- 无法确定CGO下的细粒度问题就无法定义性能瓶颈
总结
我实现了揭开云游戏服务神秘面纱的目标,并创建了一个平台,可以帮助我和朋友们在线玩怀旧的复古游戏。没有Pion库和Pion社区的支持,这个项目是不可能实现的。
我非常感谢Pion及其密集的开发,WebRTC和Pion提供的简单API也可以实现平稳的集成。
尽管集成起来很简单,但是P2P流媒体的确是计算机科学中一个非常具有挑战性的领域。它必须处理IP和NAT等常年网络架构的复杂性才能创建对等会话。在从事此项目的过程中,我积累了许多有关网络和性能优化的宝贵知识,因此,我建议所有人尝试使用WebRTC构建一些P2P产品。
CloudRetro可满足我作为复古游戏玩家的所有用例。但是,我认为我可以改进项目中的许多方面,例如使网络更可靠、性能更高、提供更高图形质量的游戏或在用户之间共享游戏。我正在为此而努力。