企鹅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 条评论
登录 后参与评论

相关文章

来自专栏美团技术团队

【沙龙干货】主题二:一个用户行为分析产品的设计与实现

分享内容 ---- 今天想跟大家分享一下我们目前推出的一个海量用户行为分析产品---“神策分析”的设计与实现。由于脱离需求和产品谈技术是不合时宜的,所以我首先会...

3598
来自专栏Java架构师学习

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

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

4144
来自专栏IT技术精选文摘

日调度5万亿次,腾讯云微服务架构体系TSF深度解读

写在前面 当前,传统企业的IT系统以单体架构为主,在面对互联网业务的冲击时,系统架构的性能瓶颈逐渐显现。云计算、Docker、DevOps、持续交付等概念的深入...

5966
来自专栏子勰随笔

SDK那些事(总纲)

29610
来自专栏developerHaoz 的安卓之旅

如何有效报告 bug

这也是「技术支持」被视为一个可怕工作的原因。然而,并不是所有的 bug 报告都是让人不愉快的。我一直在没赚钱的时候维护开源软件,有时候会收到一些非常清晰的、有帮...

972
来自专栏程序你好

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

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

972
来自专栏北京马哥教育

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

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

45411
来自专栏流柯技术学院

系统吞吐量(TPS)、用户并发量、性能测试概念和公式

  一个系统的吞度量(承压能力)与request对CPU的消耗、外部接口、IO等等紧密关联。

3631
来自专栏大魏分享(微信公众号:david-share)

数据大爆炸,业务怎么办?

744
来自专栏瓜大三哥

VS2详细设计(一)

输入端接入计算机的DVI数据源,输出端可以根据用户设定的不同分辨率和帧频输出相应的视频(本设计可以对1920x1080@60Hz,1280x720@60Hz,8...

1095

扫码关注云+社区