企鹅FM(Android) 播放成功率从 2 个 9 到 3 个 9 的蜕变

作者:张陈博男

业务层播放器架构演变

企鹅FM android端的播放器架构经历过两次较大的调整

第一次是2.1版本,首次引入了以FFmpeg为基础的腾讯视频SDK,替换了之前一直使用的系统播放器,结束了不同机型上表现不一和调用其api在不同版本上出现莫名其妙崩溃的历史,点播成功率最终优化到99.7%左右,HSL直播的成功率优化到97%附近。

第二次是3.7版本,使用了Google的开源播放器内核ExoPlayer替换了腾讯视频SDK,到目前最新的3.8版本,点播成功率已经优化到99.9%,HLS直播成功率优化到99.2%

最近半年包括更换ExoPlayer的诸多努力,都是朝着99.9%这个方向去的。这个优化过程中,最艰辛的是具体问题case by case的解决,不过回过头从架构上看,也是可以提炼出一些原则,来甄别到底什么样才是一个好的业务播放器。

  1. 一套统一的代码,这也是作为一个优秀的业务播放器的必备条件,否则假如建立在系统播放器基础上(各个厂商都会修改系统播放器代码),同样的实现在不同的机型上几乎无法做到表现一致,更遑论成功率了(常常是改动了调用方式后,在一个机型ok另一个机型不行,不同系统版本间也有此类问题),所以实现一个好的播放器,第一步就是先统一播放器内核。
  2. 完善的错误信息统计,播放是一个复杂的行为,牵扯到数据的预加载,加载,解码和最终给到系统AudioTrack播放,当支持了分片加载和缓存后这个模型就变得更加复杂,于是错误是不能避免的,但最重要的是,如何通过错误的统计上报,能够直指问题的根本,在此基础上,才有空间进一步优化成功率。
  3. 播放器内核和业务层足够解耦,只有设计上的解耦,才能给更换更好的播放器内核打下基础,否则如果每次切换都会带来巨大的业务逻辑调整,本身就会引入很多和播放器无关的问题,对成功率优化会适得其反。
  4. 和播放器内核对接的功能模块尽可能结构简单,这符合KIS原则,要在可扩展性和模块的结构简单易维护上作出协调,当代码足够简单直白,问题往往会更容易暴露和得到解决。

横向对比3中播放器内核:

播放器

代码统一 错误统计

接入层复杂度

系统MediaPlayer

不完善,播放错误码分散而且很多错误错误码相同

腾讯视频SDK

不完善,过滤日志 + 播放错误 转化为业务层错误码

谷歌ExoPlayer

除MediaCodec以外是

完善,所有错误都通过java层异常抛出,直接转化成对应处理逻辑或者业务层错误码

注:这里的接入层指的是为了实现完整的业务逻辑,在播放器内核外围的逻辑层

换ExoPlayer与奥卡姆剃刀

常做优化的同学肯定很清楚,越是小数点后面的9,越来之不易,90%到99%再到99.9%,这其中的困难可以说是指数上升的。那么是什么东西去鼓动我们换掉已经维护的很成熟的腾讯视频SDK而换用谷歌的ExoPlayer呢,动力来自于寻求到3个9的突破,而思想来自于奥卡姆剃刀原则——如无必要,勿增实体。

腾讯视频很完善,具备一切我们需要的功能,但是太过于庞杂:最下层是FFmpeg,然后是C++实现的播放器逻辑,上层一个java接口层和部分逻辑。因为发起请求的逻辑封装在播放器底层,所以为了实现分片下载和缓存的策略,增加了一层本地的Http代理。

这个架构可以完整的实现所有我们要的播放功能,可以处理播放请求,也可以分片下载和缓存,也可以添加音效和改变播放速度,但是问题也有不少:

  1. C/C++层的逻辑过多,首先、这部分逻辑不易维护,产生的逻辑问题,主要通过日志来排查。其次、C/C++层的逻辑一旦出异常,堆栈极其难以定位到原因,而且就算定位到了,FFmpeg带来的问题也比较棘手。再次、处理数据就必须经过多次jni传递,这降低了效率。
  2. 本地代理带来的结构上的冗余。引入本地代理是因为腾讯视频SDK的请求部分是写死的,无法在其中再加入我们自身的比如文件头zip压缩和分片下载缓存的逻辑。但是本地代理本身把一个请求的链路拖长了,而且本地tcp socket同样有这各种各样的断开问题和连接超时问题,实质上增加了整个系统出错的概率。

于是当发现ExoPlayer能够很完善解决这两个问题的时候,我们就进行了替换

  1. 得益于ExoPlayer高度可扩展的特性,我们去除了本地代理模块,将分片加载和缓存以及音频的变速和特效处理模块直接集成进来,砍掉了很多冗余的通信
  2. 而且采用了ExoPlayer以后去除了大部分的C/C++层逻辑,剩下的jni通信基本都属于系统组件,譬如MediaCodec和AudioTrack,对于实现者来说可以当做透明,目前日登陆百万的用户量来看,MediaCodec在各个机型上兼容性较好,投诉较少(目前仅收到两例初始化MediaCodec失败的投诉)
  3. ExoPlayer纯Java的实现,也帮助了我们尽可能收归各种错误信息,转换成业务错误码

总体来讲,剔除掉了不必要的逻辑后,代码更加的简洁,而且数据的路径也更加简短,这提升了可维护度和降低了出错概率。

其他的补充手段

仅仅靠换播放器内核和重新设计业务逻辑接入是没法做到极致的,这里还针对点播和广播(HLS)做了一些额外的优化

  1. ExoPlayer是通过抛异常来上报各种播放错误的,起初我们把全部的异常都算在播放错误中,导致播放错误偏高,后来发现这里面很多异常其实是自己代码实现的逻辑问题,需要解决,所以播放错误仅仅应当统计播放下载过程中无法解决的问题,而不应该包括代码的逻辑缺陷,后者应当继续抛出crash,由bugly上报解决。
  2. MediaExtractorPeriod和HlsMediaChunk的cancelLoadable()方法都没有调用dataSource的close()方法,这里我们加上了这个调用,原因在于快速切换节目时,如果不关闭前一个正在进行的连接有可能导致大量连接堆积会耗尽socket或者是Http连接池中的资源
  3. 针对播放HLS中的BehindLiveWindow异常进行一定次数重试,该问题通常是资源问题或者连接太慢导致,可以通过重试恢复
  4. 针对免流带来的连接超时问题,3.8版本加入了针对王卡优先直连(联通王卡类支持腾讯IP免流)的策略,也进一步提升了成功率

总结

只要保持代码架构的简洁和解耦,有着良好的错误信息反馈机制,加上长期的问题跟进,打造99.9%的成功率也不是难事,对吗。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏TEG云端专业号的专栏

深入解析文件存储服务

文件存储服务平台更关注数据的存储和全局分布调度,同时支持全局排重和跨业务转存能力,在保证数据足够安全可靠的情况下做到成本收益最大化,另外平台可支撑任意数量、任意...

79430
来自专栏北京马哥教育

大数据怎样帮助运维工程师实现无死角监控?

今天一大早就看到了一篇文章,叫【大数据对于运维的意义】。该文章基本上是从三个层面阐述的: 工程数据,譬如工单数量,SLA可用性,基础资源,故障率,报警统计 业务...

559110
来自专栏13blog.site

Spring+SpringMVC+MyBatis+easyUI整合优化篇(三)代码测试

前言 看到标题你可能会问为什么这一篇会谈到代码测试,不是说代码优化么?前两篇主要是讲了程序的输出及Log4j的使用,Log能够帮助我们进行bug的定位,优化开发...

293100
来自专栏区块链

IoT安全之设备安全性亟需提高

IoT设备厂商制造的产品集成了很多流行的网络应用,越来越多的用户看到了IoT设备的实用性和价值。易于集成和是否能加入到现有家庭网络中是许多用户购买的主要考虑因素...

32060
来自专栏娱乐心理测试

关于iOS实现前台,后台,锁屏或关闭app语音播报

99640
来自专栏杨建荣的学习笔记

运维开发流程梳理和思考

记得之前梳理过一个运维开发流程,也做了一些实践,从我的认识和理解来看,其实这更适合一个团队内的协作。

27130
来自专栏程序你好

数据库设计中的6个最佳实践步骤

如果设计得当,数据库是记录、存储、检索和比较数据的强大工具。然而,一个没有经过精心设计和目的的数据库不仅仅是无效的,它对那些使用它最多的人(开发人员)来说是一个...

15620
来自专栏IT大咖说

经历了研发困局、运维之痛,同程微服务从1到1w的旅程

内容来源:2017 年 9 月 9 日,前同程艺龙架构师谢康在“ArchData技术大会上海站”进行《同程微服务从1到1w的旅程》演讲分享。IT 大咖说(微信i...

18430
来自专栏Java架构师学习

七年的资深架构师告诉你成为架构师的知识体系

架构师是一个充满挑战的职业,知识面的宽窄往往决定着一个架构师的架构能力 知识面的宽广对于一名出色的架构师来说是必不可少的技能,也许很多人对架构的理解还停留在设...

49040
来自专栏服务端技术杂谈

我的软件架构方法论

我们公司内部职级晋升中,当目标职级比较资深或者专家后,有一项考察内容是:有自己的方法论。

29020

扫码关注云+社区

领取腾讯云代金券