下面是播放的完整流程:node
- 播放器加载一个网络url,首先要进行网络请求,网络如何优化,涉及到网络优化的方方面面。
- 网络拉取回来数据以后,识别一下当前视频的具体封装格式,这个能够正式流式视频,也能够是普通视频,优化的手段有点不一样。
- 识别到具体的封装格式,按照封装格式的要求,开始解析封装格式,解析其中的音频流、视频流、字幕流等等。
- 音频流要解码成音频原始数据,视频流要解码成视频原始数据。
- 解码过程当中注意音视频同步。
- 音频播放,同时视频开始渲染。
1、播放痛点
根据咱们平时的开发实践,咱们总结出播放过程当中常见的几类问题:缓存
- 播放失败率高
- 播放首帧慢
- 播放卡顿
- 播放器占用CPU、内存太高
面对这些问题,咱们急切须要知道两方面的数据:性能优化
- 怎么监控这些问题
- 怎么解决这类问题
这两个问题是有有递进关系的,“怎么监控这些问题”就是为了更好地“解决这类问题”。服务器
2、监控手段
咱们得知上面的痛点,在发生这些问题时,咱们要收集相应的数据分析这类问题,否则开发者一头雾水,不知道播放过程的数据信息,解决问题全靠运气。网络
一、网络加载监控
播放视频首要的是网络加载,网络请求是一个复杂的过程,全链路的点太多,将全链路的全部点收集起来,能够在播放器中加上网络的全链路监控:app
这样咱们对网络的总体加载状况有了全面的把握,发生网络加载问题,也知道是哪一个点出现了问题,分析解决问题有了更加全的数据。ide
二、播放器全链路监控
开篇就分析了播放器的完整流程,其实开发者也很是须要当前播放器的运行状态:性能
播放器的工做状态也能够拆解一下:优化
播放器发生状态异常,开发者能够明确获知播放器当前所处的状态。编码
每一个状态均可能发生异常,发生异常都有具体的缘由。利用播放器状态、播放器出错状况构建一个较为完善的播放监控体系。
三、播放器流畅度监控
播放卡顿,就是播放过程当中发生loading,UI直接显示转圈,这对用户体验的损害是巨大的,用户在不断的吐槽中默默地卸载了咱们的app。卡顿的主要缘由是网络情况很差,很小的一部分缘由是源的问题。
- 卡顿的次数
- 卡顿的时长
- 卡顿时的网速
单次播放平均卡顿次数和卡顿时间是咱们衡量播放流畅度的重要指标。
若是是源的问题,例如出现播放视频的时候,进度条在走,可是画面不走,就是视频解码出现问题,可是又没有出错,只是解码出的数据有问题。
解码出的数据有问题,有两种状况:原始数据就存在问题,这种状况下基本没法优化;另外一种状况下是解码线程异常。
- MediaCodec发生异常
- 解码线程异常错误
监控发生问题时系统codec的具体状态,而后上报,便于分析问题。
3、播放成功率优化
播放失败的缘由不少,使用播放器播放视频,最终都会在Player.onError回调中通知开发者播放失败了,最多返回一个错误码,对应一个播放错误。
总结而言,播放错误主要分为下面几类:
- 网络加载错误:网络请求发生问题,多是网络请求的任何一个阶段。
- 视频格式识别错误:不支持当前的格式,或者当前格式识别出错。
- 解码出错:不支持当前视频、音频解码致使的出错,或者系统codec异常致使的问题。
- 文件的IO异常:读取缓存文件发生问题。
网络加载错误通常要视状况而定,网络超时要作好超时重试机制。
视频格式支持使用ffmpeg能解决基本上全部的视频格式的识别和处理工做。
MediaCodec解码受到手机硬件的制约,解码有时候会出错,出错能够切换到软解码。
4、播放性能优化
一、复用连接:
平时刷信息流视频的时候,其实不少视频的域名都是相同的,这些连接都是能够复用的,网络建连的时间须要30ms到200ms不等,若是能复用连接,这部分的时间是能够节省下来的。
二、预加载
一个播放器实例持有的数据很是大,player初始化的时候会初始化MediaCodec,MediaCodec对应底层的AVCodec,操做底层的/dev/codec-node,Android系统规定了系统最大持有的MediaCodec实例是16个,固然每一个手机会有所不一样,但总的来讲不会有大的不一样,就是MediaCodec的实例个数是有限的,不可能无限建立实例。
咱们预加载多个播放器实例的时候,就会建立多个codec实例,超过codec实现限制,系统codec就没法正常工做,极易形成OOM或者ANR。
咱们再平时解决问题的时候常常发现media.codec进程致使SystemServer卡死的。通常都是media.codec使用不当形成的。
那如今可否预加载不起播放器实例?
咱们预加载的目的是为了请求视频资源,其实只须要网路模块就能够的。
本地代理能够实现将播放器的网络模块独立出来:
- 播放器不直接和视频源服务器交互,中间经过本地代理层交互。
- 本地代理层的网络加载模块是独立于播放器的,能够是播放器发起请求,也能够是其余的外部调用发起请求。
- 最终setDataSource到播放器的url是一个http://127.0.0.1:port的请求,本地代理层会经过Socket向这个url中发送数据,播放器能够直接解析数据流。跟正常的播放流程彻底同样。
这样咱们能够全局持有一个播放器实例:既能够作到预加载,并且能够解决播放器占用资源过多的问题。一举多得。
三、指定封装格式和解码格式:
针对一些视频,咱们已经明确知道它们的封装格式和音视频的编解码格式,那咱们就能够提早告知播放器这些信息,播放器直接使用特定的封装格式去嗅探,直接起特定的解码器去解码。
例如信息流视频基本上都是MP4的封装格式,H264的视频编码,AAC的音频编码。
这样咱们能够节省嗅探和MediaCodec检索的时间。
四、MP4视频优化:
MP4格式的视频解析出来以下:
其中moov中包含着MP4文件特有的属性数据,mdat是具体的音频和视频数据,MP4格式规定,只有解析出moov数据以后,才能解析出mdat中具体的音频和视频数据。
可是moov有时候在mdat以前,有时间在mdat以后,如上,moov在mdat以前,那么咱们顺序请求就没有问题。
可是若是moov在mdat以后,咱们顺序请求就播放不出来,这时候须要起双IO缓冲加载:一个从头开始检索moov,另外一个从末尾检索moov,虽找到moov,就能够先解析出moov,而后解析mdat,播放视频了。
可是双IO毕竟比较耗时,若是能在服务器提早将MP4视频的moov移到mdat以前,就能够提高MP4的首帧。
五、流式视频优化:
除了MP4视频,还有一些流式点播的视频,例如HLS格式,这些视频是一个一个的ts分片组成的。针对这些视频首帧的优化,我建议直接将前几个ts的分片的数据压缩,例如针对一个3s的ts视频,原来的分辨率是1280 720,如今能够压缩到320 180的大小,数据量大大下降,这样首帧就能快速加载下来。
六、边下边播
咱们在播放视频的时候,最好能边播放边缓存到本地,这样我二次打开这个视频的时候,就能够不用请求了,直接复用本地的数据。
边下边播可使用本地代理来完成。
七、video-id复用缓存
咱们实行边下边播以后,二次打开能够复用缓存了,可是咱们是根据视频的url来复用的,如今信息流视频的url常常变化的,即便是同一个视频,半小时视频url就发生了变化。
那这样的复用效率不是很低吗?
还好如今信息流都是传过来一个video-id,这个video-id不会随着视频url变化而变化,只要是同一个视频,video-id不会变化,那咱们能够利用这个video-id来实现一次缓存,屡次复用。
5、其余播放体验优化建议
一、播放的时候出现丢帧
播放时丢帧主要是直播应用会出现,当出现播放丢帧的状况,服务器应该主动推流低码率的流,防止客户端出现丢帧严重甚至卡住不走的状况。
二、播放画面出现锯齿
播放视频的时候出现锯齿,这时候通常是两种状况:
- 视频清晰度较高,MediaCodec对搞清的视频解码支持地较弱,画面细腻感不强。建议切换到软解码开始解码,软解码使用CPU解码,对细节的支持度很强,甚至8K的视频都能很好的解析。
- 使用GLSurfaceView替换SurfaceView或者TextureView,GLSurfaceView经过OpenGL绘制纹理实现视频细节的细腻绘制,对视频画面支持效果很好。
做者:Li Tianpeng