直播间结构
在线教育场景下的直播间不同于泛娱乐类直播 App,其业务的复杂度更高,同时用户的设备分布更分散,有很多低端机及性能差的设备,并且用户的上课时长通常在 1.5 小时左右,长时间的停留让设备发热及耗电明显,最终导致直播间的性能问题成为了根本的瓶颈。
要解决直播间的性能问题,首先要先从直播间的整体构成入手,具体如下图:
从整体 UI 的结构上看整个直播间是横屏,由课件区、主讲的拉流区、用户自己的推流区、同组学员区、聊天区组成,这几个区域都是常驻,从进入直播间开始就存在,其中的课件区是 Webview,上课过程中还会通过 Webview 展示互动题以及画板。
其中课件区承载的是 H5 课件,最上层是常驻透明的笔迹画板,同样是 Webview 承载,具体是由 H5 的 canvas 绘制,课件的复杂度在于有很多转场动画和交互,画板会频繁绘制笔迹,从 Webview 角度看这些高复杂的场景都会导致资源消耗较大且不可控。
内容区分层图
不可见的还有直播的推拉流、编解码、图像的渲染。信令通道,信令的解析、分发、排重、补偿等等,这些逻辑虽不可见,但依然是性能消耗的大户,特别是流媒体相关,一定程度上是性能的黑洞。
性能衡量(APM)
搞清楚了直播间的结构后,接下来我们要定量分析出性能具体如何?衡量直播间的性能,从 App 角度衡量性能有现成的概念 APM。通过 APM 来收集、上报、分析数据,产出对应的报表,来衡量性能,再根据业务场景直播间来输出结论。再进一步可以整合问题定位系统,进行问题的定位及排查,这样就完成了诊断 ->定位 ->优化的闭环。这部分的设计不展开讲,后续可以单独开一篇文章讲一下问题诊断、定位、优化及 APM 的设计。
APM 的核心是监控 CPU、内存、FPS 这些指标,并按照一定的频率及采样率上报分析,最终分析出性能的整体表现。在我们的业务中分析的结果是,从进入直播间开始,运行内存 200MB 的占用,随时间的变化升到 800MB 甚至更高,低端机 CPU 持续占用 80%~90% 左右,帧率方面低端机上低帧率(低于 10)的场景特别频繁。
客服和技术支持系统可以监控到用户的反馈情况,数据包含舆情、客服反馈渠道、用户社群等,可以分析出用户视角的问题。
最后,综合业务、客服、技术支持、用户体验等,最终确定按照如下指标衡量直播间性能:
用户问题反馈率:图像(卡顿或画面问题)、声音、互动,结合业务目标是千分位以下;
性能指标:CPU、内存、FPS,目标是平稳且及时释放;
业务指标:视频卡顿率、信令(到达、接收、展示)、互动成功率,目标是 99.9%。
明确问题
通过业务逻辑、用户视角的现象结合技术监控,进行分析、诊断,最终定位出如下核心问题:
卡顿:由于性能消耗较大且持续濒临性能崩溃的边缘,造成卡顿、卡死、crash 等,最经典的案例是有些低端机运行内存剩余不足 10MB,CPU 几乎在 100%,GPU 内存暴掉出现 OOM;
课件性能:表现是课件区内容展示不全或不连续、卡在某一页、笔迹花屏等等;
黑白屏:如上面的内容区分层图所示,课件、互动题、画板三层均为 Webview,移动端的 Webview 是由系统底 WebGL ES 进行渲染,渲染过程中如果出现 GPU 的 OOM,在 iOS 表现为白屏(WKWebview 进程触发了 Terminate 回调),Android 平台表现为黑屏或部分黑屏;
互动失败:互动题是由信令通道的信令来驱动,信令如果出现延迟到达、丢失、乱序等就会造成互动失败;
优化方案
从业务上的表现及技术上的分析最终确定问题核心是 CPU、运行内存、GPU 这些资源在调度或使用过程中出现资源不足导致,资源上的消耗、占用、竞争是问题的根本,同时资源的释放不及时也会导致问题加剧,所以从资源角度解决问题需要先从各方面进行资源的释放及管控。
独立进程
从平台特性角度首先想到的方案是 Android 系统的独立进程,因为独立进程有自己独立的资源调度及使用控制,在需要的时候申请相对独立的资源,在不需要的时候释放可以更彻底的回收资源。这种资源的管控恰恰适合直播间的场景,用户在进入直播间时创建独立进程,在退出时彻底销毁释放资源,过程中如果资源告警或超过阈值还可以进行销毁重建。
具体的实现是 App 主进程作为核心载体及宿主,将直播间加入到独立的进程中,直播间的进程与主进程建立 AIDL 通信通道,完成进程间的功能调用。使用 MMKV 在进程间共享数据,需要特别注意数据一致性(使用文件锁)。过程中由统一的调度器 (Service Manager) 管理所有的 binder 服务,包括系统服务和应用自定义的服务,具体参考下图:
独立进程架构图
当然由于系统及平台的限制独立进程在 iOS 是实现不了的,iOS 平台需要其他方式,下面就来讲讲适用全平台的优化方案!
容器化
关于资源的管控有很多现成的模式可以参考,容器化是最为直接的方式,以容器为组织单位进行生命周期的管理,可有效的管控资源。譬如将 Webview 看成是一个容器,其生命周期可分为创建、加载、更新、重置、关闭、销毁的整个过程,每个过程都有其存在的价值和互相之间的关系。
创建 负责生成容器对象及基本的资源申请,使容器处于初始化的状态;
加载 负责将内容 load 到容器,渲染展示内容;
更新 与加载对应,负责将老的内容刷新至新的状态,并重新渲染展示内容;
重置 可以将原有的资源释放,使容器重新恢复到初始化状态;
关闭 将容器从可见状态转换为非可见状态,但此时容器的对象依然存在;
销毁 彻底将容器的对象释放,并将其占用的所有资源释放。
其整个生命周期的管理基于平台提供的 API 进行封装,中间使用对应的桥接层进行对接,最上层使用统一调度层将核心的能力提供给上层业务进行整合和调度,具体如下图:
容器化架构图
信令通道
容器是承载内容的载体,而直播场景的内容是有一定连续性的,类似视频播放器,一个完整的视频是由一系列连续的帧图构成,通过时间的纬度将一系列帧图组织在一起,一帧帧播放出来,再配合上音频就构成了视频。容器也类似,使用信令将容器驱动起来,将一页页内容展示出来,譬如主讲老师的课件、笔迹、互动题等,课件的翻页、笔迹的书写、互动题的发起结束都是一系列的信令进行驱动的。
其中信令的通道选择尤为重要,最初信令的通道设计是由直播流的 SEI 通道进行承载,通过时间戳的对齐向上层业务抛信令,这样的设计最初目的是为了保证更实时的传输,但事与愿违,由于直播流的技术采用了 WebRTC,它的网络层实现使用的是 UDP 协议,UDP 本身不保证时序及可靠性,所以会有大量的乱序及丢包存在,导致信令的错乱和丢失,可靠性大打折扣。
使用 SEI 通道的另外一个问题是完全耦合到直播拉流的逻辑中,假如想切换拉流方式需要同时切换信令通道,带来了更多的额外成本。
所以最理想的信令通道应该是独立通道且保证可靠性的方式,业界也有成熟方案——长连接,其实在直播课的业务中,rtmp 拉流方式下已经使用过长连接,可以保证技术可靠性的同时也是相对独立的服务及通道。所以我们最终使用长连接通道,将信令从直播拉流中解耦出来。
渲染优化
解决了内容的承载、驱动,接下来要解决渲染的问题,业务上所有可视化的内容都涉及渲染,主要包括了 Navtive UI 渲染、流媒体渲染、Webview 内容渲染,下面展开讲一下其中的原理及方案:
Native UI
Native 的 UI 渲染由系统提供,对资源的使用相对合理,只要编码上保证没有内存泄漏一般不会出现问题。
流媒体
直播场景占核心地位的渲染来自流媒体,即视频流的渲染,直播视频解码与渲染的 Pipline 流程是视频解码 -> 视频前后处理 -> 视频的渲染,而核心的渲染流程是:
CPU 计算需要显示的内容,通过数据总线传给 GPU;
GPU 拿到数据,开始渲染数据并保存在帧缓存区中;
视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过数模转换传递给显示器显示。
视频渲染图
当前主流的视频渲染技术有 OpenGL、Metal、Vulkan 等,这些技术更多的是依托平台,最常见的是 OpenGL,其最大优势是跨平台。Metal 是苹果公司推出的,具有更好的性能。Vulkan 在安卓平台,与 Metal 类似 Vulkan 可以更详细的向显卡描述你的应用程序打算做什么,从而可以获得更好的性能和更小的驱动开销。
视频渲染的过程其重要的性能消耗在于频繁的计算、拷贝内容,最直接的方案是减少计算及拷贝,在解码上使用硬解将计算逻辑由 CPU 分散 GPU 进行计算,同时使用平台更优的 Metal 或 Vulkan 方式进行渲染。但这这种方式不适用于部分低端机(GPU 相对差的机型),安卓机成为突出,针对部分机型需要单独的适配,成本会比较高。但带来的收益也很明显,硬解更加省电,适合长时间的移动端视频播放器和直播,手机电池有限的情况下,使用硬件解码会更加好。减少 CPU 的占用,可以把 CPU 让给别的线程使用,有利于手机的流畅度。
低端机的渲染最有效的方案是进行降级,目前直播间所采用的是减少不必要的推拉流、减少动画、降低业务复杂度,保证核心体验等。
Webview
Webview 的渲染主要消耗在于图片展示、GIF 播放、DOM 计算等,从资源角度入手减少资源的占用,同时让 CPU、运行内存、GPU 资源能及时释放。在课件及互动场景下更多的采用 cocos runtime 容器平替方案,效果比较明显。具体有相关的专项文章可以参考。
线程优化
除上面比较明显的资源消耗大部头之外,从 CPU 角度关注其核心消耗最直接有效的方式莫过于使用调试工具进行摸排。Android 的可以使用 adb 或 AndroidStudio 提供的工具进行调试,iOS 使用 Instruments 进行调试。重点关注占用 CPU 时间片较长的线程或常驻线程,这部分在直播间场景发现比较重的是大量的常驻 log 线程,通过统一的 log 系统将其收敛管控即可。其他沉重的线程也可以使用统一的线程池管控的方案进行优化。
效果
内存
运行内存的优化收益明显,从是从原来的平均 800MB 降至平均 200MB 左右,并且从之前的进入直播间之后一直上升的趋势转为过程会不断回收释放,维持在相对平稳的状态。
其中独立进程对于 Android 机型的优化最为明显,平均可释放 300MB 内存。容器化对于课件和笔迹等常驻类直播间的场景优化相对更有效,内存上的表现是回收及时。
CPU 和 FPS
低端设备上表现最为突出,从 CPU 和 FPS 的表现上观测到,低帧率的现象明显变少。同时从舆情数据上看,卡顿反馈率降低了 80% 左右,其中渲染优化、线程优化、业务降级最为有效,其他用户反馈率也达到了千分位的目标。
黑白屏
Webview 容器的黑白屏率初期降低了 83% 左右,经过后续的升级迭代最终趋近于 0;
Cocos Runtime 容器黑白屏率 0;
所有方案对此均有效果,整体资源使用的更优会让黑白屏率变得极低甚至趋近于 0。
总结
上述就是我们对直播间性能优化所做的一些实践和积累,性能优化是一个长期的工程,需要结合自身的业务特性不断进行打磨,力求极至。
除了优化方案,良好的性能监控系及开发规范也很重要,是防止劣化的重要方式,否则性能优化方案取得的收益抵不过业务快速迭代带来的性能黑洞,结合 APM 及相关监控指标可以不断的升级优化措施及规范,不断的落地实践。
未来在我们可能还会在资源竞争、CPU 线程调度、GPU 渲染、技术手段简化业务等方面继续深入探究性能优化,后面也会将其中的某些优化方案整理成文章继续分享,也希望各位前辈同行多多指教、探讨!
嘉宾介绍
于晓鹏
作业帮直播移动端负责人,在移动端架构设计、性能优化、网络优化、直播等领域有一定探索和实践,致力打造高性能的移动端体验。
评论