随着虚拟化技术如模拟器,容器化等技术等发展,在安卓云游戏/云手机场景中,可以在服务宿主侧虚拟出更多更小颗粒度的 Android 实例。其中比较核心的技术是图形虚拟化技术,如何最大限度利用宿主侧的 GPU 资源进行渲染和编码,不考虑软编等利用 CPU 资源进行渲染编码是因为效率带来的延迟问题。
先看一个比较通用的 linux 图形栈:
包含 Wayland 比较主流的所有图像栈变得异常复杂:
每种应用的图像数据流都比较复杂,但大致流线是:应用(显示 Client)->(显示 Server)->OpenGL/EGL->Mesa 3D->libDRM->(内核)DRM->GPU 驱动。
以 SurfaceFlinger 为核心,维护了所有 app 窗口的交叠覆盖关系:
综合 Linux 图形栈和 Android 图形栈可以发现在底层都是基于 drm 实现,实现硬编方案的核心思想就是渲染和编码都利用宿主侧的 GPU,并且渲染和编码 Zero-copy,所以有两类技术:
2. 直接导出 DRM 句柄,利用 DRM 的 Zero-copy 的特性进行渲染和编码,渲染和编码通过 IPC 技术传递 fd,这部分代表是 AiC(Android in Container)容器化技术。
3. 以上两类技术由于最终都是 drm 图像 buffer,故都可以通过 IPC 技术在渲染进程和编码进程之间通过 IPC 传递。渲染进程一般在容器/模拟器内,编码进程一般在容器外。
以上图形栈涉及显示和渲染,在云游戏的场景中,还需考虑编码设计的技术栈。就编码而言:
如果想要在 Android 内使用硬件编解码,要么实现一套 OMX 到 ffmpeg 的转换翻译,要么厂商对接实现 OMX 的 vendor 驱动,否则很难在 Android(容器或模拟器中)内硬件编码。比较合理的方式是导出 libdrm 的 FD,渲染和编码在不同的进程中,编码选择在 host 中调用 ffmepg 或者 vender 的编码 API 进行编码,进而完成整个推流。
硬编目前精力放在处理进程间传递图像 prime FD,还有相应的音频,双向 input 通信等。采用统一的 Spice 协议或者改造的 Spice 协议统一 Android 虚拟化和容器化整合方案。
SPICE,Simple Protocol for Independent Computing Environment,是 Redhat 公司开源的一套远程桌面虚拟化协议,旨在提供商业级别的远程桌面体验。Spice 协议具有如下优点:
概述
包含四个部分:协议、客户端侧、服务端侧和虚拟机侧。其中:
图像流
上图示意了整个图像从 Guest OS 到客户端图像传输通路,其中:
QXL 设备的其他功能包括:
VDagent 命令流
Spice Server
Spice 协议改造
Spice Client 收到 Spice Server 端发过来的 main,display,playback 等通道后:
为适合我们的推流改造如下:
对原协议改动比较大的:
Spice 协议抓包
可以通过 socat 等工具代理 domain socket 来分析 spice 协议,对一个完整的 Spice 协议交互流程,通过 TCP dump 抓取 wireshark 日志如下:
先通过 main channel 建立连接,认证,然后依次建立 Spice Display, Spice PLAYBACK, SPICE RECORD, SPICE INPUT 等通道,最后通过各通道发送特定的消息。
这里重点关注以下:
2. Display Channel
3. Playback Channel,android 系统的声音消息,如音量变化,声音开始与停止等。
QEMU 方案可以直接复用社区的 qemu+kvm 方案,除了针对不同硬件导出不同的 fd 之外。
相比于虚拟化,容器化的特殊之处在于 qemu 已经集成 Spice Server 组件,虚拟化的容器需要容器外编码的话需要导出音频,视频和控制通道,然后实现一套类似 Spice 协议的架构,为统一兼容性通过增加下图的转发模块 Adapter/SpiceServer,将 IPC 通道再次转发至 Spice 通道,实现 QEMU/AiC 方案的统一。
在整个整合方案中,有如下因素和参数需考虑:
从虚机或者容器导出,有两种类型的图形显存的 fd:
KHR_STREAM
渲染到宿主侧的 surface,suface 导出 EGLSTREAM,通过eglCreateStreamKHR
和eglGetStreamFileDescriptorKHR
导出对应的 EGLStreamKHR 文件描述符,适用于 NVIDIA 显卡。消费侧通过eglStreamConsumerAcquireKHR
导出对应的 stream,但编码不能直接使用 stream 类型, CUDA 提供了 OpenGL 与 CUDA 互操作 API,将 texture 或者 render buffer 绑定 CUDA 资源之后,CuGraphicsSubResourceGetMappedArray
映射出 CUarray 指针供编码器使用。
MESA_IMAGE
渲染到宿主侧的 texture,texture 导出为 DMA buffer,通过eglExportDMABUFImageQueryMESA
,eglExportDMABUFImageMESA
导出,适用于 AMD/Intel 显卡。消费侧通过此创建 EGLImage, 并绑定 2D 纹理,将此纹理的 textureID 传递给编码器 VAAPI 通过此编码器进行编码。
随着支撑的方案类型增加,整个工程在满足功能情况下逐渐难以维护,通过 C++重写各个模块,将 HwFrame 模块抽象,对日志/SRE 各模块划分重构,将工程模块化。
Xorg 作为 X(11)协议中的 Server 的实现,Spice Client 的通过调用 GTK API 端做 client,存在弊端:
需要将依赖和 GTK 的组件移除,降低组件依赖复杂性和性能消耗。
具体而言:
getPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT, (EGLNativeDisplayType)dev[num], NULL)
导出。getPlatformDisplayEXT(EGL_PLATFORM_GBM_MESA
导出,需要注意的一点是在 VAAPI 接口中,将初始化用的 Display 换成 DRM 导出。音频的 pipeline 使用了 gstreamer,这部分依赖可以去掉。
总体想法就是图像的 Zero-copy,减少在 CPU 与 GPU 之间的拷贝与图形格式之间转换。
支持主机通过 PCIE 外插硬件编码卡进行硬件编码。
总体想法就是利用 host 渲染能力,将渲染后的 RGBA 或者 YUV 导出给编码卡,达到最大限度利用渲染资源,提高并发路数的工作。
通过 Host Gameservice 进程自我升级固件,不依赖整体部署 pod 节点镜像更新,可以灵活实现升级。
对系统指标的打点和性能的监控,完善 SRE 等监控体系,治理进程崩溃,卡死,内测泄漏等检测。
整个云游戏的视频流硬编码方案的实现和上线部署离不开跨部门的合作,再次感谢一起将整个方案从开始设计到到上线的内部兄弟团队如基础系统部门 STE,多媒体 RTC 等部门,通过团队协作推动整个方案上线以及后续线上持续优化和治理。
作为字节跳动的视频中台部门,视频架构支持了字节全系产品的点播、直播、实时通信、图片、云游戏、多媒体业务发展,目标成为业界多媒体解决方案领导者,构建极致的视频技术/产品服务体验!
视频架构-设备与服务团队聚焦多媒体+IoT/5G 相关领域,孵化能够赋能业务的新场景和核心技术,打造极致的、软硬件结合的技术解决方案,上线了云游戏、云手机、视联网、多屏互动等多款服务,支持了抖音、西瓜等众多内部产品,同时也通过火山引擎提供 toB 服务。欢迎更多同学加入我们,构建行业顶尖的视频创新技术,联系luxuguang@bytedance.com 注明“设备与服务方向”。