基于SPICE协议的硬编码流式集成方案在云游戏中的应用,可以在服务宿主端虚拟出更多更细粒度的Android实例。其中,核心技术是图形虚拟化技术。如何在主机端最大化利用GPU资源进行渲染和编码,而不考虑使用软编等CPU资源进行渲染和编码,是因为效率造成的延迟。Linux图形栈再看一个更通用的linux图形栈:X协议:较早的一种协议,Xserver直接管理GPU中的framebuffer和XClient提交命令,通过XClient(Xlib或XCB)向Xserver(Xorg)提交相关命令)实现,并且有很多扩展协议,但缺点是需要额外的Windows管理器来处理多个应用程序。它已被像Wayland这样的扩展协议所取代。Composer处理输入、窗口和复合显示等功能。GLX:因为它用于间接渲染,所以它做两个任务:1)绑定OpenGL和X窗口API2)通过X服务器转发GL调用。本质还是X协议。FB驱动:遗留的显示子系统,提供Framebuffer获取、图像操作基元、电源管理等功能。OpenGL:统一的3D图形渲染API接口,所有主流厂商(Intel、Nvidia、AMD、Qualcomm等)都支持的接口,主流实现是开源的mesa。Mesa3D是其最主流的开源实现。值得注意的是,Mesa不仅支持OpenGL,还支持Vulkan、Direct3D等渲染API。DRM:DirectRenderingManager,目前主流的GPU显示子系统,使用libDRM的DRMAPI在用户态操作DRM设备,通过ioctl等标准文件操作与GPU通信,实现:framebuffer管理。用户态抽象渲染能力:如BufferObject管理、GPU作业命令提交等,一般都与具体驱动有关。VirtualDriversupport:包括vmwgfx(VMwarebridgingdevice)和virgl(Virtobridgingdevice)PrimeZero-copymemory,buffer通过用户态的fd文件描述符表示实际显存中的DMAbuffer,fd可以是通过PrimeAPI导出。IPC之间的传输。包括Wayland在内的所有主流图像栈都变得极其复杂:各个应用程序的图像数据流都比较复杂,但大体的流程是:应用程序(显示客户端)->(显示服务器)->OpenGL/EGL->Mesa3D->libDRM->(内核)DRM->GPU驱动程序。Android图形栈以SurfaceFlinger为核心,维护所有app窗口的重叠覆盖关系:OpenGLES:2D/3D渲染走的路径,使用drm的所有函数进行渲染。GrallocFB:使用drm为app提供显存管理等功能。HWComposer:调用openGL窗口合成RGB或YUV,实现屏幕绘制。结合Linux图形栈和Android图形栈可以发现底层是基于drm的。硬编码解决方案的核心思想是利用host端的GPU进行渲染和编码,渲染和编码零拷贝,所以有两类技术:virto-gpu技术之后exportsOpenGLEScommand(virgl),它依次调用宿主端的virglrenderer,将其翻译回OpenGL和GLSL,然后调用宿主的OpenGL。这部分技术代表了Qemu的解决方案。使用假的抽象GPU。在抽象层GPU层拦截,在host端调用GPU。直接导出DRM句柄,利用DRM的Zero-copy特性进行渲染编码,通过IPC技术传输fd进行渲染编码。这部分代表了AiC(AndroidinContainer)容器化技术。由于以上两类技术最终都是drmimagebuffer,所以可以通过IPC技术在渲染进程和编码进程之间通过IPC传递。渲染过程一般在容器/模拟器内部,编码过程一般在容器外部。多媒体编解码器相关上面的图形栈涉及到显示和渲染。在云游戏场景中,还需要考虑编码设计的技术栈。就编码而言:在Linux中,标准的多媒体框架如ffmepg或gstreamer在上层封装应用程序接口,在下层连接硬件提供CUDANVEnc接口或VAAPI接口等编解码器。Android使用OMX作为它的多媒体框架,MediaCodec驱动连接厂商驱动实现硬编解码能力。如果要在Android中使用硬件编解码,要么实现一套OMX转ffmpeg的转换翻译,要么厂商对接实现OMXvendordriver,否则很难在Android中实现硬件编码(在容器中或模拟器)。比较合理的方式是将libdrm的FD导出,在不同的进程中进行render和encode,在host中选择调用ffmepg或者vender的encodingAPI进行编码,然后完成整个推流。解决方案的硬编码目前主要集中在进程间传输imageprimeFD,以及相应的音频、双向输入通信等。使用统一的Spice协议或修改后的Spice协议来统一Android虚拟化和容器化集成方案。spice协议SPICE,SimpleProtocolforIndependentComputingEnvironment,是Redhat开源的一套远程桌面虚拟化协议,旨在提供商业级的远程桌面体验。Spice协议具有以下优点:开源:易于扩展和定制功能;跨平台:全面兼容Windows/Linux/MacOS平台;支持外接设备:除了常用的USB设备外,还可以远程使用打印机、扫描仪等设备;富媒体支持:包括视频、音频和图像;带宽占用更小:Spice内置图像压缩算法,有效降低数据传输过程中的带宽占用;更安全的数据传输:Spice可以使用OpenSSL来加密数据传输。概述包括四个部分:协议、客户端、服务器端和虚拟机端。其中:protocol:是client端、server端和虚拟机端三部分交互时遵循的准则;客户端:负责接收和转换虚拟机数据,并将用户输入的数据发送给虚拟机,以便用户与虚拟机进行交互;server:集成在hypervisor内部的用户层组件,使hypervisor(如QEMU)支持Spice协议;虚拟机端:指部署在虚拟机内部的所有必要组件,如QXL驱动、SpiceAgent等。Imageflow上图展示了从GuestOS到客户端image的整个图像传输路径,其中:QEMU:虚拟机环境,当前使用GuestOS:虚拟机中运行的操作系统ClientOS:运行的应用程序GDIonthehostside/X:GraphicsDeviceInterface,图像引擎,图像栈提供的显示接口(如mesa)角色。但是,标准VGA也可以在没有驱动程序的情况下支持该设备。该模式还显示了虚拟机启动的引导阶段。QXL设备通过命令和指针环与驱动程序交互,显示中断、指针事件和I/O端口。QXL设备的其他功能包括:初始化和映射设备ROM、RAM和VRAM到物理内存映射I/O端口,处理读取和写入以管理:区域更新、命令、指针通知、IRQ更新、模式设置、设备重置、日志记录日志等ring-初始化和维护命令和指针环,从环中获取命令和指针命令,等待通知。维护资源环。使用QXLWorker接口与对应的redworker通信,在reddispatcher中实现,将设备调用翻译成消息写入redworker通道,或者从redworker通道读取消息。注册一个QXL接口,使工作人员能够与设备进行通信。此接口包括PCI信息和功能(如附加到工作器、从环获取显示和指针命令、显示和指针通知、模式更改通知等)。定义对QXL模式的支持并更改当前模式(例如VGA:所有侦听器反映单个设备)处理VGA模式下显示的初始化、更新、调整大小和刷新。VDagent命令流程SpiceServerSpice协议转换SpiceClient在收到SpiceServer的main、display、playback等通道后:display通道调用默认显示器上的GTKwidget相关组件在屏幕上绘制图像。获取播放音频数据,通过gstreamer的pipeline调用alsa播放音频。其他键鼠加工,不作任何加工。为了适应我们的流媒体改造,如下:原来的协议做了很大的改动:DisplayChannel。得到FD后,通过HwFrame适配模块,将原本在GTK中绘制的流程转换为RTC编码所需的数据源。(YUV或RGBA)。在MainChannel通道中添加VDAgent类型,添加自定义类型传输RTC远程调用指令,通过封装将RTC事件和DataChannel反向传递给GameService(游戏管理服务进程)。spice协议抓包可以使用socat等工具代理domainsocket分析spice协议。一个完整的Spice协议交互流程,通过TCPdump抓取wireshark日志如下:先通过主通道建立连接,进行身份验证,然后依次建立SpiceDisplay、SpicePLAYBACK、SPICERECORD、SPICEINPUT等通道,最后通过每个通道发送特定的消息。这里重点关注MainChannel的VDAgent通道,在CLIPBOARD和FILE_XFER外添加VD_AGENT_VENDOR_DATA,用于远程gRPC调用,接收android端封装的数据。DisplayChannel,GL_SCANOUT_UNIX,屏幕初始化/分辨率改变后的消息,一般在初始化时使用。GL_DRAW_DONE,这个消息是在屏幕内容改变的时候发送的,可以认为是每一个Android屏幕。PlaybackChannel,android系统的声音信息,如音量变化、声音启停等。QEMU方案QEMU方案可以直接复用社区的qemu+kvm方案,除了针对不同的硬件导出不同的fds。与虚拟化相比,AiC容器的特殊之处在于qemu集成了SpiceServer组件。虚拟化容器如果需要在容器外进行编码,需要导出音视频和控制通道,然后实现一套类似于Spice协议的架构。为了统一兼容性,通过增加下图中的转发模块Adapter/SpiceServer,将IPC通道再次转发到Spice通道,从而实现QEMU/AiC方案的统一。方案参数在整个集成方案中,需要考虑以下因素和参数:模拟器或容器环境区分。如果导出的fd与模拟器和容器scheme一致,则无需区分,否则需要通过环境变量或传入启动参数来区分。指定的DRM设备。在携带GTK的版本中需要指定Display设备,去除Xorg依赖后需要指定Render节点。指定编码显卡硬件。由于不同的硬件编码不同,编码模块需要通过当前的硬件信息来确定编码方式。图形导出是从虚拟机或容器中导出的。显存的fd有两种:KHR_STREAM在host端渲染到surface,surface导出EGLSTREAM。通过eglCreateStreamKHR和eglGetStreamFileDescriptorKHR导出对应的EGLStreamKHR文件描述符,适用于NVIDIA显卡。消费端通过eglStreamConsumerAcquireKHR导出对应的流,但是编码不能直接使用流类型。CUDA提供OpenGL和CUDA互操作性API。将纹理或渲染缓冲区绑定到CUDA资源后,CuGraphicsSubResourceGetMappedArray映射出CUarray指针供编码器使用。MESA_IMAGE在host端渲染到texture,texture作为DMAbuffer导出,通过eglExportDMABUFImageQueryMESA导出,eglExportDMABUFImageMESA,适用于AMD/Intel显卡。消费端通过这个创建一个EGLImage,绑定一个2D纹理,将这个纹理的textureID传递给编码器VAAPI通过这个编码器进行编码。优化演化代码重构随着支持方案种类的增多,整个项目在功能满足的情况下逐渐难以维护。通过C++重写各个模块,抽象出HwFrame模块,划分重构log/SRE模块,划分项目模块变更。删除XorgXorg作为X(11)协议中服务器的实现。SpiceClient使用GTKAPI作为客户端,有缺点:默认情况下,导出的fd会通过GTKwidget绘制在默认的Display上,但实际推送流程不需要这一步。部署Xorg也增加了复杂性。需要去除依赖和GTK组件,以降低组件依赖复杂度和性能消耗。特别是:删除了与显示通道相关的GTK小部件依赖项。替换原来的Display,对于Nvidia,getPlatformDisplayEXT(EGL_PLATFORM_DEVICE_EXT,(EGLNativeDisplayType)dev[num],NULL)export。更换原装Display,对于VAAPIAMD或Intel显卡,由于使用了mesagraphicsstack,getPlatformDisplayEXT(EGL_PLATFORM_GBM_MESAexport,需要注意的是在VAAPI接口中,将InitializationDisplay替换为DRMexport。#ifENABLE_GTKintvaapi_init(){x11_display=XOpenDisplay(g_getenv("DISPLAY"));va_display=vaGetDisplay(x11_display);#elseintvaapi_init(intdrm_fd){va_display=(uint64_t)vaGetDisplayDRM(drm_fd);g_messaged("%drm_fdva_dpy:%p",drm_fd,va_??display);#endifintmajor_ver,minor_ver;va_status=vaInitialize(va_display,&major_ver,&minor_ver);return0;}使用gstreamer移除gstreamer音频管道,这部分依赖可以去掉。图形转换优化的总体思路是图像的零拷贝,减少CPU和GPU之间的拷贝和图形格式之间的转换。编码卡支持主机通过PCIE外置硬件编码卡进行硬件编码。混合硬件编码支持的总体思路是利用主机的渲染能力,将渲染后的RGBA或YUV输出到编码卡上,最大限度地利用渲染资源,增加并发通道数。自升级固件通过HostGameservice进程自升级,不依赖pod节点镜像更新的整体部署,可以灵活升级。系统指标和性能监控的监控和SRE管理,完善SRE等监控系统,管理进程崩溃、卡顿、内测泄露等检测。其他整个云游戏的硬编码视频流解决方案的实施和上线,都离不开跨部门的合作。再次感谢从开始设计到上线整个方案的内部兄弟团队,如基础系统部STE、多媒体RTC等部门,推动了整个方案的上线以及后续的持续优化和治理通过团队合作在线。
