随着短视频的流行,用户在碎片化场景下消费的视频内容越来越多。短视频本身时长较短,首帧体验尤为重要。随着预加载、预下载、IP直通车等传统优化手段使用,首帧体验有了明显提升。但经过进一步的数据分析,在手Q中长尾中低端机上,首帧表现依然不够理想。首帧优化已经进入深水区,受Google ExoPlayer切换清晰度方案(不用重启解码器)的启发,我们探索出一种适合短视频场景的,基于Android平台的跨播放器解码器复用方案,对中低端机首帧性能提升明显。本文是对整体方案的介绍,希望能帮助大家在首帧优化方向上提供新的思路,欢迎多多交流。
本文看点视频特指手Q、快报、看点视频等腾讯看点内的视频场景业务,包含Feeds流、视频详情页、栏目详情页等场景,以短视频为主,类似抖音、微视等形态。由于手Q用户基数较多,机型覆盖面广,下面以手Q为例,逐步展开介绍解码器复用优化方案。
由于短视频时长较短,用户对于首帧加载时长容忍度更低。下面表格体现了在不同的首帧下,用户的主观感受。首帧时长越短越好,一般1s以内相对更容易接受。
为了更贴近用户的实际感受,下面的首帧主要是指从用户点击播放到第一帧画面渲染出来的时长(未经特殊说明,都指此段时长,而非一般情况下prepared的时长)。通常情况下,衡量首帧体验,主要是两个指标,一是平均首帧,主要表现首帧时长的平均水平;二是首帧秒开率(首帧1s内的播放数/总播放数),主要衡量首帧体验的稳定性。
经过多轮优化,目前平均首帧为333ms,秒开率为89%。只看这两个数据,总体指标已经比较优秀,但从首帧耗时分布来看,长尾机型首帧依然较差,且大于1s。
根据手Q版本大盘数据,首帧分布情况如下:
从上图来看,大于1s的首帧占比约11%,这部分耗时主要集中在长尾中低端机。
为了进一步寻找优化空间,我们将首帧流程进行拆解,目前已有的预换链、ip直通车、预下载等优化手段基本和业界内对齐。除了解码器初始化(指MediaCodec的create、configure()、start()等耗时操作)以外,其他流程几乎已经全部覆盖。
那么解码器初始化的耗时是否有优化的可能性呢?受到Google ExoPlayer优化切换清晰度时耗时的方案启发(具体可见《Improved decoder reuse in ExoPlayer》),解码器可以在一定条件下不经过重启,直接播放不同清晰度的视频,避免耗时带来的黑屏等问题。以ExoPlayer文章中数据为例,播放器在数据准备好后还有一段耗时不容忽略,其中 Galaxy S8播放1080p的H.264视频,音视频解码器初始化总和耗时高达170ms,占启动总耗时的60%(170ms / 280ms)。
为了进一步摸清中低端机解码器初始化对首帧耗时的影响,我们对常见低端机解码器初始化耗时进行统计,发现部分机型耗时超过1s,其中用户占比较大的vivo X5L(TOP30)解码器初始化耗时也高达1146ms。以下为不同低端机解码器初始化耗时排行的情况,单位为ms(当日播放次数>=10000认为数据有效)。由此可见,如果能优化掉中低端机解码器初始化的耗时时长,对首帧会有较大的提升。
通常情况下,视频正常播放时解码器都需要进行create()、configure()、start()等初始化操作,而播放其他视频时,每次播放都必须重新进行这一初始化流程。
不重新初始化解码器MediaCodec,而将解码器直接用于其他播放器解码,称为解码器复用。而Google为了优化清晰度无缝切换时的耗时,在ExoPlayer上实现了播放器内的解码器复用。
Google已经在ExoPlayer上进行了解码器复用实践,那么是否可以将对应方案直接应用到我们的业务呢?答案是不能的。ExoPlayer是一种播放器内部解码器复用方案,即解码器和播放器实例绑定。对于不同分辨率的视频A、B、C,下次播放时判断该播放器实例是否可以复用,如果可以,进行复用,否则重新初始化解码器。
总而言之,基于短视频切换频繁的特点,直接使用ExoPlayer方案会存在以下困难:
基于前面的问题,需要探索出一种更适合短视频场景的,可以跨播放器全局共享的解码器复用方案。目标如下:
为了满足多player架构,解码器在多个player之间复用,理想的模型是全局只需要一个解码器,进行复用即可。
上文中的理想模型因为解码条件限制(即解码器能否复用受一些条件限制)难以满足要求,因此,我们加入了解码器复用池,可以让多个播放器共享解码器。当新播放器起播时,可以根据视频特征选择合适的解码器。
以顺序播放A、B、C视频为例,一个完善的复用流程需要下面一些步骤:
传统的方案解码器复用逻辑和播放器耦合严重,且复用逻辑复杂,无法通用。要实现通用方案,并且所有基于MediaCodec的播放器可用,需要对MediaCodec的API进行hook并且将复用逻辑与播放器隔离。也就是说,理想的复用模型应该是无入侵,不修改现有播放器代码,复用逻辑通过独立模块hook MediaCodec实现。
我们的目标是hook MediaCodec的API,这里调研了一些常见的hook手段,并做出对比。
以上方案都有一些限制,比如Legend和Whale的支持系统版本限制,Whale增量较大以及编译时方案javaassist不支持系统类等问题。而MediaCodec属于系统类,且是final类型,使用任何一种方案都会有各种各样的问题,为了保证方案的轻量,以上的常见的hook方案都不适用。
以上方案都被否定,我们这里考虑使用代理方案,让代理类TMediaCodec和MediaCodec使用完全一致的接口,这样在接入TMediaCodec时只需要代理内部实现逻辑即可,也可以很好的将复用逻辑和外部隔离,也有较低的入侵性。
首先我们定义了和MediaCodec有完全一致的API的CodecWrapper,具体如下:
其中ReuseCodecWrapper为复用的codec的包装类,DirectCodecWrapper为非复用的codec的包装类。即需要复用时,会获取ReuseCodecWrapper进行是使用,其中的初始化逻辑是不耗时的,非复用时会直接使用DirectCodecWrapper,里面直接进行了系统MediaCodec的API代理。具体的类关系如下图:
经过反复的实验和结合ExoPlayer的落地经验,摸索出解码器复用需要遵循下面条件:
解码器复用的核心条件是支持自适应播放属性,此属性是指Android提供的一种无缝切换不同分辨率视频的能力,可以由系统接口(Seeking & Adaptive Playback Support)查询是否支持。
由于篇幅有限,这里对解码器复用条件不做展开,具体可以期待下一实践篇文章。
由上表格可以看出,能不能复用除了机器本身是否支持自适应播放属性外,最主要受编码格式和分辨率以及MAXINPUTSIZE影响。
1. 编码格式
为了兼顾各个业务的实际情况,比如看点常见编码格式是H264和H265,解码器池支持自定义编码格式和解码器池大小,这里默认是优先保留一个H264和一个H265两个解码器,以便复用时提高复用率。当然业务也可以根据实际情况进行设置。
2. MAXINPUTSIZE
自适应播放虽然可以适应不同分辨率的解码,但是受最大分辨率(MAXWIDTH\MAXHEIGHT)以及MAXINPUTSIZE限制,这里只需要确定了最大解码的分辨率,MAXINPUTSIZE可以推导得出。所以我们也暴露了业务支持最大分辨率的接口,在MediaCodec.configure()时,根据最大分辨率设置MAXWIDTH\MAXHEIGHT\MAXINPUTSIZE相关信息,保证所有视频复用解码器时不受分辨率影响。
默认解码器池由两个codec实例组成,整个方案默认是采用相同codec优先淘汰的策略,也就是保证解码器池中有不同的解码器格式类型,尽可能保证不同的编码格式都能进行复用。整个方案也将具体的淘汰策略暴露给业务,可供配置。
由于Android机型众多,而MediaCodec贴近硬件,且上层业务较为复杂,所以在实践过程中难免遇到一些bug。所以需要一种简单高效的手段来保证解码质量,在测试阶段尽可能发现问题,在线上可以监控问题。
由于解码问题比较底层,且不是很好发现,为了更快速地定位分析问题,我们引入现象和原理同时检测。现象检测是指检测视频播放中出现的问题,比如场景的黑屏、画面卡住等,这里主要是利用截屏检测思路。对于更深层次的原因,我们对常规解码API进行梳理,并自定义了错误码,方便分析问题。
如果用传统的测试手工测试办法,效率很低,且很难复现问题。我们基于wetest探索出了一种自动化检测方案,主要用于及时复现和检测测试阶段的问题,具体内容可参考《看点视频基于WeTest的播放自动化测试实践》。相对传统方案,有以下优点:
为了保证线上质量,我们采用白名单逐步放量的方式,并利用线上分析实时监控来反馈。将现象检测和原理检测结合,利用错误率报表监控,来保证解码质量。
线上监控解码错误率:当线上分析监控解码错误率超过1%,会进行该机型的预警,在放量过程中会自动加入黑名单。这里的错误是指MediaCodec硬解错误,就算出错,播放器也有切换软解策略。
目前在手Q上已有TOP500机型落地,覆盖用户达到的96.14%。
目前整个解码器复用方案命名为TMediaCodec,包含以下一些模块。Hook模块用于MediaCodec类以及TextureView的功能代理。策略模块用于各个业务具体视频播放场景的策略配置,保证复用率最大化。监控模块用于监控解码中的错误,保证播放质量。解码器包装模块主要用于用户代理MediaCodec的功能。解码器池用于保存闲置的解码器。
整个方案有以下优点:
优点:
接入简单,灵活配置,入侵小,学习成本低,机型兼容性良好。
解决痛点:
跨播放器复用,全局选择解码器,支持业务配置,几乎支持所有基于MediaCodec的播放器。
其他播放器接入:如果没有使用中台播放器,我们也可以支持其他播放器的接入。由于使用了TMediaCodec使用了和MediaCodec完全一致的API,只需要将系统的MediaCoedc替换成TMediaCodec即可。具体细节可以参考工程TMediaCodec。
以手Q中的一款用户占比较多的经典机型vivo Y66为例,优化效果视觉上可感知。对于中低端机的首帧体验有明显提升。
以手Q841版本大盘数据为例,对于中低端机,平均首帧提升显著,几乎对半提升。首先我们对机型按照下面标准进行了分类,将手机分成:极低端机、低端机、中端机和高端机。
极低端机的优化幅度约39%,低端机的优化幅度约33%,中端机的优化幅度约29%。而秒开率方面,中低端机从88.79%提升到96.57%。
方案的本质是空间换时间,经过对vivo Y66机型的视频场景峰值测试,内存占用影响不是很大。
不复用内存占用:
复用内存占用:
常见的首帧优化手段已经相对较为成熟,我们团队在优化进入深水区时,受益于ExoPlayer视频清晰度切换的方案,经过实践,探索出特有的适合短视频场景的跨播放器解码器复用方案,算是对传统优化手段的创新和突破。传统手段的优化会有部分限制以及适用场景,解码器复用方案以空间换时间,对性能较差的机型的首帧耗时也有实际的提升。当我们的优化进入瓶颈时,不妨打开思路,去关注业界内比较领先的优化手段,针对自身业务场景结合改进,也不失为一种好的思路。
目前已在看点视频、手Q、快报等App全面落地,方案能够突破中低端机性能限制,优化效果较为显著。也欢迎对技术有兴趣的同学随时交流。在方案实际落地的时候,遇到了一些困难,在解决问题时也收获颇多,欢迎大家期待《看点视频秒开优化:解码器复用优化实践篇》。