搭载WebRTC的开源云游戏

目前我们看到了很多软件服务、基础设施服务、平台服务、通信平台服务、视频会议服务等等,那么是否能提供游戏服务呢?现在已有一些云游戏方面的尝试,其中最出名的就是谷歌最近推出的Stadia。虽然Stadia 能够玩转WebRTC,但是其他游戏平台能否以同样的方式把WebRTC玩起来还尚未可知。

Thanh Nguyen建立了开源项目CloudRetro,来测试上述想法是否可行。CloudRetro以基于Go的最流行WebRTC库——pion而建立。在本篇文章中,Thanh介绍了他是如何建立该项目,以及他在过程中收获的经验和教训。

cloudretro.io 上的 megaman x4 截图

简介

去年,谷歌官宣了Stadia。我震惊于这个想法既独特又创新。但我心存疑虑,以目前的技术水平,怎么可能实现这个项目呢?因为想要解密这个技术,我创建了一个开源版本的云游戏。效果非常好。下文中我会分享自己过去一年在这个项目上的探索经历。

为什么云游戏是大势所趋

我认为云游戏不仅会很快引领新一代游戏的发展,也会引领计算机科学其他领域的发展。云游戏是客户端/服务器模式的巅峰之作。它通过把游戏逻辑放在远程服务器上,把图像/音频流传输到客户端这些操作,最大化了后端控制,前端工作量被降到最低。这样,服务器负责了繁重的处理过程,客户端就不再受硬件限制了。

以Google Stadia为例,它能让你在像YouTube这样的界面上玩AAA游戏(即流行的高端游戏)。这个思路同样可以应用于其他大型离线APP,如OS或2D/3D图形设计等。这样我们就可以通过各类平台一致地运行许多低规格设备。

说到该技术未来的应用,不妨畅想一下在Chrome浏览器上运行微软Windows 10的景象?

云游戏面临的技术难题

游戏是为数不多的需要用户持续快速反应的应用之一。如果我们点击一个页面,偶尔有2秒的延迟是可以接受的。如果是现场直播,视频流通常会延迟好几秒,但仍可用,用户也接受。然而如果一个游戏经常延迟500ms,这个游戏就没法玩下去了。所以我们的目标是实现极低的延迟,以确保游戏输入和媒体之间的差距尽可能的小。因此传统的视频流方法并不适用。

云游戏常见模式

CloudRetro开源项目

我决定创建一个云游戏的POC,以便验证在这些严格的网络限制下该项目是否可行。我选择Golang作为POC,因为它是我最熟悉的语言,而且诸多实例也证实了用它开发效果极佳。Go简单,开发速度快,而且Go通道也非常适合处理并发和流操作。

我的开源项目叫CloudRetro.io,一款基于Web开发的、为经典电子游戏提供云游戏的开源服务,该项目旨在给经典电子游戏玩家带来最舒适的游戏体验,并引入互动游戏的玩法,如在线多人游戏等。

项目repo参见 :GitHub - giongto35/cloud-game: Web-based Cloud Gaming service for Retro Game

CloudRetro 功能简介

CloudRetro项目选用经典电子游戏来展现云游戏的效果,该方式能够为玩家带来多种独特的游戏体验。

  • 快速游戏体验

——打开页面后立即开始游戏。无需下载安装;

——在手机浏览器上即可运行,所以不需要任何软件就能开始游戏

  • 游戏进度可以在多个设备上共享,也可以存储在云里,以便下次使用。
  • 游戏可以边玩边直播,多个用户可以加入同一个游戏。

——类似Twitch Plays Pokemon的Crowdplay,但体验更实时更丝滑;

——之前只能离线玩的单机游戏,现在无需网络设置即可在线进行多人游戏。比如现在就能在CloudRetro中多人联网一起玩侍魂系列游戏了。

不同设备上多人在线玩经典电子游戏的demo

基础架构

标准和技术栈

以下是我在项目开始前设定的标准清单:

1. 单人游戏

这个标准似乎和这个项目并不相关。但我发现这是使云游戏区别于传统流媒体服务的关键点之一。如果我们专注于单人游戏,我们可以摆脱集中式服务器和CDN。因为我们不需要将流媒体会话分配给大量用户。我们的服务不再是将流媒体上传到 ingest服务器,或将数据包传输给集中式WebSocket服务器,而是直接通过WebRTC端对端连接给用户提供流媒体。

2. 低延迟的媒体流

在我研究Stadia时,有些文章提到了WebRTC的应用。我发现WebRTC是一项了不起的技术,很适合我的云游戏用例。WebRTC是一个通过简易API,为web浏览器和手机应用程序提供实时通信的项目。它实现了端对端通信,优化了媒介,内置了标准的编解码器,如VP8和H264。

我认为,应该把为用户提供最流畅的游戏体验放在首位,图像是否高清不如前者重要。我的算法里,图像质量上的一些损失是可以接受的。但Google Stadia设计了额外一步来缩小服务器上的图像。之后,图像帧会被重新缩放到更高质量,再渲染给对等端。

3. 具有地理路由的分布式基础架构

无论如何压缩算法,代码如何优化,网络仍然是导致延迟的最关键因素。我们的架构需要一种机制来配对离用户最近的服务器,以减少往返时间(Round Trip Time)。该架构要包含一个协调者,以及分布在世界各地的多个流媒体服务器——美国西部、美国东部、欧洲、新加坡、中国等等。所有的流媒体服务器完全独立。如果一个服务器加入或离开网络,系统可以调整其分布。因此,在高流量的情况下,增加更多的服务器可以进行横向扩展。

4. 浏览器兼容

因为云游戏对用户端要求低,所以它自身能发挥最大效力——能在浏览器上运行。由于不必安装游戏软件和硬件,浏览器作为助力,给用户带来了最舒适的游戏体验。除此之外,浏览器还帮助提供了手机端和pc端之间跨平台的灵活性。正巧WebRTC支持各类不同的浏览器。

5. 游戏界面和服务的切分

我认为云游戏服务是一个平台,人们应该能在平台上插入任何界面。目前我是把LibRetro和云游戏服务整合在了一起,因为LibRetro曾为SNES、GBA、PS等经典游戏设计了很棒的游戏模拟器界面。

6. 为多人游戏、crowd play和deep link游戏提供的基于房间的机制

CloudRetro实现了很多新颖的游戏方式,比如经典电子游戏的CrowdPlay和在线多人游戏。如果多个用户在不同设备打开同一个deep link,他们会看到同一个正在运行的游戏视频流,甚至能够作为玩家加入游戏。

此外,游戏状态被储存在云中。这样,不论何时何地,用户都能继续他们的游戏。

7. 横向扩展

和如今每个SAAS一样,该项目必须设计成可横向扩展的。协调者-开发者的设计可以调整开发者数量,以服务更多的流量。

8. 云无关策略

针对不同地区,CloudRetro的基础架构被托管给了不同的云供应商(Digital Ocean,、Alibaba和自定义供应商)。我通过bash脚本docker化了基础架构,并配置了网络设置,以避免对任何一个云提供商形成依赖。加以WebRTC的NAT遍历,我们可以在任何云平台,甚至是任何用户的设备上灵活部署CloudRetro。

架构设计

worker :(或上面提到的流媒体服务器)生成游戏,运行编码管道,并将编码后的媒体流式传输给用户。worker实例分布在世界各地,每个worker可以同时处理多个用户会话。

协调员:
负责把新用户与最合适的worker配对,进行流式传输。协调员通过WebSocket与工作者交互。

游戏状态存储: 所有游戏状态的远程中央存储。该存储可以实现一些基本功能,如在线保存/加载。

CloudRetro高级架构

用户流程

当新用户在下图所示的步骤1和2中打开CloudRetro时,协调员会被要求提供前台页面以及可用的worker列表。第3步,客户端使用HTTP ping请求来计算所有候选者的延迟时间。延迟时间列表随后会传回给协调员,这样它就可以确定哪个worker最适合去服务用户。第4步,游戏生成。与此同时,用户和指定worker之间的WebRTC流连接也建立完成了。

接入后的CloudRetro用户流

worker内部

在每个worker内部,游戏和流媒体管道是分开的,它们通过一个接口交换信息。目前,这种通信是在同一进程中通过Golang通道进行内存传输。进一步分离是下一个目标–即在不同的进程中独立运行游戏。

worker中各部分如何交互

其中最主要的几部分是:

  • **WebRTC:**面向客户,用户输入的入口,以及服务器编码媒体的出口。
  • **游戏模拟器:**游戏组件。得益于Libretro库,我们的系统能够在同一进程中运行游戏,并在内部连接媒体和输入流,还能捕获游戏中的帧将其发送至编码器。
  • **图像/音频编码器:**编码管道。它接收媒体帧,在后台进行编码,并输出编码后的图像/音频。

实现

WebRTC 是CloudRetro的核心。所以我先来介绍一下WebRTC技术,再详述我在Golang的实现。WebRTC是一项了不起的技术,在我实现亚秒级延迟流媒体的过程中给予了很大帮助。

WebRTC

WebRTC旨在通过简易API,在手机和浏览器上实现高质量的端到端连接。

NAT遍历

WebRTC以其NAT遍历功能闻名。WebRTC是为端到端通信而设计的,旨在通过ICE过程找到最合适的直接路由,避开NAT网关和防火墙,进行端到端通信。WebRTC APIs作为整个过程中的一份子,使用STUN服务器找到你的公共IP地址,并在无法建立直接通信时退回到中继服务器(TURN)。

但CloudRetro并没有最大化利用遍历功能。因为它的端到端连接不是在用户和用户之间,而是在用户和云服务器之间。与典型的用户设备相比,该模型的服务器端对直接通信的限制较少。这允许预先开放入站端口,或直接使用公共IP地址,因为服务器并不位于NAT后面。

我之前设想把这个项目发展成一个云游戏分发平台。具体来说就是让游戏开发者提供游戏和流媒体资源,用户直接和游戏供应商匹配即可。在这种去中心化的方式中,CloudRetro只是一个连接第三方流媒体资源和用户的媒介,这样,托管不再依靠CloudRetro,其可扩展性就更强了。WebRTC NAT遍历会减轻第三方流媒体资源端到端连接初始化的负担,从而发挥重要作用,让开发者更顺畅地接入网络。

视频压缩

视频压缩是管道中必不可少的一部分,极大地促进了流畅的流媒体体验。虽然我们不必完全理解VP8/H264的所有视频编码细节,但了解其概念有助于明白流媒体速度参数,调试意外行为并调整延迟。

流媒体服务的视频压缩并不容易,因为该算法需要确保编码时间+网络传输+解码时间总和尽可能的小。此外,编码过程需要按序列顺序连续进行。一些传统的编码折中方式并不适用于该情景,比如用较长的编码时间换取较小的文件内存和解码时间,或者不按顺序进行压缩。

视频压缩是为了省略非必要的信息位,同时把视频保持在用户可接受的保真度水平。除了对单个静态图像帧进行编码外,该算法还根据之前和之后的帧,对当前帧进行推断,因此只会发送差异。以吃豆人为例,只有差异点被传送了。

以吃豆人为例进行视频帧比较

音频压缩

同样,音频压缩算法也会省略那些我们无法感知的数据。目前性能最好的音频编解码器是Opus。它可以通过有序的数据报协议如RTP(实时传输协议)来传输音频波。产生的延迟比(mp3,aac)低,质量更高。延迟通常在5~66.5ms左右。

Pion——Golang中的WebRTC

Pion是一个将WebRTC引入Golang的开源项目。它并不是简单包装原生的C++ WebRTC库,而是一个原生的Golang实现。因此它可以获得更好的性能、更好的Golang集成和对WebRTC构成协议的版本控制。

对于亚秒级延迟的流媒体,该库还提供了许多优秀的内置功能。它有自己的STUN、DTLS、SCTP等实现,QUIC和WebAssembly的一些实验。该开源库本身确实是很好的学习资源,内含清晰的说明文档,网络协议的实现和很酷的示例。

Pion社区的开发者很热情,这里非常活跃,有很多关于WebRTC的高质量探讨帖。如果你对这项技术感兴趣,可以加入http://pion.ly/slack,你会学到很多新东西。

用Golang编写CloudRetro

Worker内部Go的实现

Go频道大放异彩

多亏Go通道设计完美,事件流和并发问题被大大简化了。如下图所示,有多个组件在不同的GoRoutine中并行运行。每个组件管理自己的状态,并通过通道进行通信。Golang的select语句强制要求每个游戏的tick都要处理一个基本事件。也就是说在这种设计下没必要进行锁定了。例如,当用户存档游戏时,需要一个完整游戏状态的快照。直到保存完成,这个状态都需要不受运行输入的干扰。每个游戏tick里,后端只能处理保存操作或输入操作,所以该状态是并发安全的。

func (e *gameEmulator) gameUpdate() {

for {

select {

case <-e.saveOperation:

e.saveGameState()

case key := <-e.input:

e.updateGameState(key)

case <-e.done:

e.close()

return

}

}

}

扇入/扇出

这个Golang模式和我的Crowd Play和Multiple Player的用例完美匹配。按照这个模式,同一房间内的所有用户输入都被扇入至一个中央输入通道。然后游戏媒体会被扇出至同一房间的所有用户。这样,我们就实现了不同用户在多个游戏会话之间的游戏状态共享。

不同会话间的同步

Golang的缺点

Golang并不完美,它的通道很慢。和锁定相比,Go通道只是一种处理并发和流式事件的更简单的方式,但通道并没有发挥出最好的性能。通道下还有有一个复杂的逻辑锁定。因此我在实现上做了一些调整,重新应用锁定和基本事件来替代通道,优化性能。

此外,Golang的垃圾收集器是不可控的,所以有时会出现一些奇怪长时间停顿。这大大降低了这个流媒体应用的实时性。

CGO

该项目使用一些现存Golang开源VP8/H264库进行媒体压缩,用Libretro实现游戏模拟器。所有这些库都是通过CGO对Go中的C库进行包装。但也有一些缺点,你可以参考Dave的这篇博文。我所面临的问题是:

  • 即使用Golang Recovery也不能及时发现CGO崩溃。
  • 如果我们无法检测到CGO的细微问题时,也无法定义性能瓶颈。

总结

我实现了探索云游戏服务的目标,并创建了一个平台,让我和我的朋友可以在线一起玩经典小游戏。十分感谢Pion库和Pion社区的支持,有了他们这个项目才得以实现。特别感谢Pion和Pion 内的丰富资源。WebRTC和Pion提供的简易API帮助我们顺利集成。 尽管我之前并不了解端到端(P2P)知识,但我的第一个POC在同一周内就发布了。

虽然集成的操作很简单,但P2P流媒体确实是计算机科学中一个非常有挑战性的领域。它必须处理像IP和NAT这样经年网络架构的复杂性,才能创建一个端对端会话。在做这个项目的时候,我积累了很多关于网络和性能优化的宝贵知识,所以在这里推荐大家尝试用WebRTC构建一些P2P产品。

作为一个经典小游戏的玩家,CloudRetro可以满足我预设的所有使用情况。然而,我认为项目中还有很多地方可以改进,比如可以让网络更可靠、性能更强,提供更高清图形的游戏,让用户之间可以共享游戏。我正在努力优化这些功能。如果你对这个项目感兴趣,请关注并支持它。

文章地址:Open Source Cloud Gaming with WebRTC - webrtcHacks

原文作者:Thanh Nguyen

推荐阅读
相关专栏
开源技术
106 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。