ExoPlayer里里外外之:自适应码率切换


自适应码率切换是解决在线视频播放卡顿的一个方法,通过带宽估计选择相对应码率的码流播放,使当前播放的码流适用于当前的网络状况,从而减少卡顿。对用户来说,自适应码率切换几乎是无感知的,视频始终是连续播放的,没有中断,只会看到视频图像质量高低的变化。

自适应码率切换需要服务器端和客户端同时支持,首先服务器端需要提供多码率的码流,对HLS来说,就是需要服务器端提供master m3u8 playlist,也就是通常所说的嵌套m3u8;对Dash来说,manifest文件AdaptationSet里面会包含多个Representation,每个Representation对应某一个码率的流的描述。

在服务器端满足了上述条件之后,播放器通常来说需要如下四步完成自适应码率的切换,分别为:

  1. 带宽估计
  2. 切换时机和条件
  3. Buffer数据切换
  4. 无缝切换

1.带宽估计:Bandwidth Estimation

http://en.wikipedia.org/wiki/Moving_average,具体实现如下:

    根据历史数据下载速度估计带宽,使用如下数据结构:

struct Sample

{

    int weight;

    float value;

}

  • 每下载一次数据,生成一个Sample,权重weight为每次下载到的数据的Bytes数的平方根;value为每次下载数据的bps
  • sample按时间顺序加入滑动窗口
  • 滑动窗口的大小为上面结构体中weight权重的和totalWeight,和最大为2000
  • 滑动窗口容纳的sample的weight和totalWeight超过2000后,最先加入滑动窗口的sample移出去,直至和小于2000
  • 预估当前网速方法:
  • Step 1:把滑动窗口中的samples按value(即bps)从小到大排列
    Step 2:totalWeight/2为desiredWeight,排序完的samples, index从0开始,weight相加,当和达到desiredWeight时,此时index所对应的sample的value即为预估的当前网速


上述Sample中的weight和value在ExoPlayer中按照如下方法计算所得:

1) DefaultBandwidthMeter::onTransferStart()在DefaultHttpDataSource::open()调用(每次下载数据起始),记录下载起始时间sampleStartTimeMs

2) DefaultBandwidthMeter::onBytesTransferred()在DefaultHttpDataSource::read()中调用,记录下载的总字节数sampleBytesTransferred

3) DefaultBandwidthMeter::onTransferEnd()在DefaultHttpDataSource::close() 中调用,记录下载结束时间,同时生成本次下载过程的Sample(权重和bps),加入滑动窗口,并调用上面的算法计算出当前的bitrateEstimate;最后清零sampleBytesTransferred,准备计算下个Sample。

注意:播放列表和音视频数据下载的Sample都会统计到计算中。

这一步完成之后,会得到当前的bitrateEstimate。

2.切换时机和条件

以如下HLS的数据流图为例,Dash也是一样的:


深度学习自适应码率图像压缩 自适应码流什么意思_码率

如图中红色字体getNextChunk,在HlsChunkSource::getNextChunk()中判断是否要切换到新track,步骤如下:

1) 在AdaptiveTrackSelection中:根据第一步计算出的bitrateEstimate,估计带宽的固定比例作为有效带宽值:bitrateEstimate*bandwidthFraction(0.75),根据这个“有效带宽”得到ideal track index,根据这个index取得master playlist中该track的bitrate:idealFormat.bitrate;当前正在播放的流用currentFormat标识

2) 如果idealFormat.bitrate > currentFormat.bitrate,但是当前DataQueue中buffer的数据小于10s,判定为不切换;不小于10s,则将要切换到该idealFormat.bitrate对应的高码率码流

3) 如果idealFormat.bitrate < currentFormat.bitrate,但是当前DataQueue中buffer的数据不少于25s,判定为不切换;小于25s,则将要切换到该idealFormat.bitrate对应的低码率码流

4) 切换之前下载的最后一个ts文件对应的Sequence Number的ts文件,切换之后新码流中该Sequence Number对应的ts会再下载一次;这么做的好处是希望切换尽快完成,就是在当前ts就完成;这是有可能的,下一节看完切换实现的细节,就会说到。

3.Buffer数据切换

自适应码率切换不需要同时下载两个码率的码流数据,前一个码流的最后一个chunk下载完成后,如果满足第二步中描述的切换条件,再去下载新码流的数据,是串行的,所以播放器自始至终只需要维护一套DataQueue即可(video和audio各一个Queue)。

这个Queue就是上一篇说到的DataQueue和InfoQueue,这里可以看到在做码率切换的时候,如何使用它们。

由于只有一套DataQueue,在自适应码率切换的情况下,从某一时刻起DataQueue开始存放新码流的数据,下面描述“某一时刻”如何确定。

前一篇有提到,播放器从服务器读回来的音视频ES数据只管往DataQueue后面Add就好,其他Metadata信息由InfoQueue管理,在切换到新码流并开始下载之后,sampleMetadata会调用attemptSplice尝试拼接,video和audio各自处理,规则如下:

1) Video:在解析到关键帧的时候调用attemptSplice()尝试拼接,如果当前关键帧的timeUs<=largestDequeuedTimestampUs,则不会拼接;相反,如果当前timeUs的帧还在queue里,则开始拼接;找到InfoQueue中所有>= timeUs的帧,把这些帧都discard掉,discard的处理就是更新InfoQueue中的relativeWriteIndex和largestQueuedTimestampUs;物理上删除DataQueue中前一个ts中所有>= timeUs的帧,则是在relativeReadIndex到达拼接起始位置的relativeWriteIndex的时候,根据index得到的offset数据,直接调用dropDownstreamTo() (通过调用dataQueue.remove())扔掉DataQueue直到当前读取数据位置offset之前所有的数据。这里有一个优化点,即DataQueue物理上的remove能否提前做?大家可以考虑下

2) Audio:Audio则要简单的多,由于audio所有sample都可以开始解码,所以只要新码流当前sampleMetadata的timeUs的对应的前一个码流的sample还在queue里,就找到InfoQueue所有>= timeUs的sample,把这些sample都discard掉,方法同Video

基于前一篇描述的Queue的灵活设计,自适应码率切换时buffer数据切换就如此容易,但简单中蕴藏着的结论却非常有用:

video和audio的切换可以各自进行,互不干涉;实际中我们看到各自完成切换的sample对应的PTS并不相同;但这并不影响结果

切换是在关键帧位置发生的,并不一定在TS切片的起始位置发生

4.无缝切换:seamless switching

无缝切换的含义有两点:

1) 在发生码流切换的位置,没有黑屏现象

2) 在发生码流切换的位置,画面和声音是连续的

第一点发生黑屏可能的原因之一是发生了video decoder reset,码流切换通常伴随着resolution切换,如果decoder不支持,就需要重新configure codec,这会导致短暂的黑屏;好在,从android 5.0开始,MediaCodec开始支持adaptiveCodec,只需要init一次,在码流切换之后,只需要保证新码流的第一帧数据是跟CSI(对H264就是SPS,PPS,H265还有VPS)一起送给decoder的,decoder就可以adaptive的工作,无需重新配置。

第二点要求画面和声音是连续的,这就要求不同码率的码流要严格对齐,就是说每个chunk中包含的Sample数量是一样的,并且对应Sample的PTS都是相同的。

自适应码流现状


很遗憾,国内提供在线视频服务的大厂们,当前还没有运用自适应码率的,做的最好的,是在用户手动切换两个清晰度的视频码流的时候做到了无缝;不启用自适应的原因不一而足,但最主要的因素可能是网络状况,当前仍然有好多用户的网络标称带宽为10M以及以下的,而实际上的带宽可能更低。

但这并不会阻碍自适应码流逐渐成为主流的趋势,随着带宽越来越不成为瓶颈,以及人们对在线视频的播放体验的追求,尤其是在线直播视频的体验,一定会有厂商率先启用自适应码流系统。