00 前言
2019 年以前,基于当时的基础情况判断,大部分人不看好云游戏的产品形态。但是云游戏用的技术其实是很有含量,很值得研究的。不少云游戏开发者,能够冒着产品不被看好的风险硬啃这块,有很大原因是,其技术本身很有价值,很有挑战性。俗话说,高风险高回报,难道云游戏行业工资高这个秘密,我也要告诉您?
2020 年上半年,由于疫情影响,实体娱乐业受到很大冲击,反而计算机游戏因此得利,不少上市游戏公司迎来一波股价上升行情。同时由于大头公司积极布局云游戏,大众开始对云游戏产品有所改观。到下半年,GPU、5G、边缘计算等领域的各种迹象已经表明云游戏起飞的时机大约就在 2021-2022 年。如果说之前,云游戏开发者是靠稀缺和承担高风险拿到高薪,那么今后两年,靠的就是趋势已来,赌对了!
01 演示视频
国内首款开源云游戏引擎【鎏光】演示街头霸王对战 - 西瓜视频
02 开源
相信大部分开发者接到一个任务时,第一想法就是先找找有没有符合需求的现成的开源项目,如果有很多个,就做选型。即使没有完全符合要求的,接下来做开发,也可能是在拿一些开源的基础库做组合。
很多时候,一个行业发达时,就必然会有很多相关开源项目。有些只是提供基础库,有些是产品级别的完整项目。
今天咱们要介绍的,是一个准产品级别的完整项目——鎏光云游戏引擎。它大量依赖一些协议兼容的优秀基础开源库,不管是本身,还是其依赖,都是很值得参考的。
https://github.com/ksyun-kenc/liuguang
当您 clone 好代码,把它们编译出一套可玩的“云游戏”成品后,可能会大呼过瘾,有种用零件造出变形金刚的快感,甚至很想参与完善它。我们很高兴地宣布,它的开源协议是 Apache 2.0,您可以尽情改造它。
03 技术介绍
项目的 ReadMe 上已有相关说明,大家可以先大概看一下,再继续阅读。
从最简化的模型上看,云游戏做的两件事是:把服务端的游戏画面“搬运”到客户端、把客户端的输入“搬运”到服务端。下面将按顺序介绍这两件事背后的细节。
Easyhook
要“搬运”游戏画面,首先就得想办法抓取画面。大部分人会想起 QQ、飞书之类常用软件带的截图功能。这当然也可以,但考虑到“效率”,咱们不得不对各种截图技术做一些评估。GDI 抓图、NVIDIA FBC、MirrorDriver、DDA(Desktop Duplication API)、IDD(Indirect Display Driver),这么多手段都可以抓图,但我们用的却是 Hook 抓图。举个例子,D3D 游戏本来调用一个叫 Present 的函数,告诉底层,我的数据准备好了,你可以拿去显示。云游戏引擎就 Hook 这个 Present 函数,抢先把游戏数据取走。
Hook 方案有三个好处:
最接近画面源头,延迟最小;
只抓游戏画面,不受遮挡影响。
黑科技:Hook 技术能控制游戏的垂直同步开关,使游戏按照特定规范运行,减少运营时的差异。可以还阻止游戏在本地显示,即在图中的渲染完成后,取得图像,之后的流程都抹掉,可以节省 GPU 资源,这是其它技术做不到的。
我们选择的 Hook 库是 Easyhook,它是 MIT 协议:
EasyHook - The reinvention of Windows API Hooking
FFmpeg
接下来把画面流化属于流媒体范畴,不得不先提到大名鼎鼎的 FFmpeg。由于它属于 GPL/LGPL 协议,所以我们的代码内并没有放任何 FFmpeg 的文件,这需要开发者自己去放置。
目前鎏光支持 H264 和 HEVC 两类编码,当采用 2020 年主流消费级 NVIDIA GPU 时,我们建议采用 HEVC 编码。如果您的 GPU 是其它品牌,还请自行修改代码,理论上只要是 FFmpeg 支持的硬件编码器,工作量几乎就是改个编码器的名字,也欢迎您调试好之后贡献代码。
IAudioCaptureClient
这是 Windows 上的一个 COM 接口,用于抓声音。我们会采用 opus 或 aac 来编码声音,所以采集声音后会统一做个 resample,使数据符合编码器的要求。
另外,鎏光的 Pro 版本还有针对单个进程抓声音的方案,采用 Hook IAudioRenderClient 的方式实现。参考:云录音
WebSocket
画面和声音流化之后得到一个个 AVPacket 数据块,当然还得把它们通过网络传输到客户端。这部分我们采用 WebSocket 协议,实现用的是 Boost.Beast。采用 Boost 的好处是,如果您想换成裸 TCP 传输,可以把 Boost.Beast 换成 Boost.Asio,改动很小。还有一个类似 Boost.Asio 的 kcp 库,是 GLP 协议的,所以我们没采用,但我们建议在互联网传输时使用 kcp,如果您想自己换,也是很方便的。
SDL
客户端通过 WebScoket 拿到 AVPacket,同样采用 FFmpeg 解码得到 AVFrame,再从中拿到原始画面和声音,接下来该呈现给玩家了!我们采用 SDL 呈现画面和声音,它有跨平台的好处。
值得一提的是,视频解码这步,我们是支持硬件解码的,并且我们通过对 SDL 实现的 hack,能够把硬解出来的视频帧直接丢给 SDL 去显示。
玩家的操作,比如键盘、鼠标、手柄等外设的消息收集,也是通过 SDL 实现。
UDP
SDL 采集的外设的消息封装后,通过裸 UDP 发送给服务端。采用 UDP 是为了保证实时性,并且丢包的代价不高,用户可以多按几次键盘鼠标就纠正丢包带来的问题。实现采用 Boost.Asio,和前面提过的一样,您可以很方便地自行把裸 UDP 改为 kcp。
外设消息重放
外设消息达到服务端后,还得将其发送给游戏。我们有两种做法,一种是通过 HID 驱动重放,一种是 Hook 游戏的外设 API,把客户端发来的消息返回给游戏。
HID 驱动方案需要 WDK 开发,开发和部署的成本较大,但兼容性比较好,可以支持大部分游戏。Hook 方案的本质是:游戏用什么 API 读写外设消息,我们就 Hook 什么 API!它的好处是延迟低,然而游戏用的 API 还是蛮多可能的,DInput?RawInput?XInput?所以需要做多套 Hook。
04 探讨
您可能注意到前面 Hook 这个词出现挺多次。这其实是云游戏的重点和难点。如果采用云桌面思路来实现云游戏,其实可以不需要 Hook,而且一个桌面能干的事情更多,应用场景也会更多。按照这个路线发展的话,驱动会是重点和难点。
但同时我们还应该注意到“原生云游戏”路线。原生云游戏不会采用任何驱动,甚至它的服务端不需要运行在 Windows 上。从原生云游戏 SDK 的角度看,它整个思路、流程和 Hook 方案的云游戏更像一些。
您对哪者更感兴趣呢?欢迎在这里留言告诉我们。