系列文章目录
- ExoPlayer架构详解与源码分析(1)——前言
- ExoPlayer架构详解与源码分析(2)——Player
- ExoPlayer架构详解与源码分析(3)——Timeline
- ExoPlayer架构详解与源码分析(4)——整体架构
- ExoPlayer架构详解与源码分析(5)——MediaSource
- ExoPlayer架构详解与源码分析(6)——MediaPeriod
- ExoPlayer架构详解与源码分析(7)——SampleQueue
- ExoPlayer架构详解与源码分析(8)——Loader
- ExoPlayer架构详解与源码分析(9)——TsExtractor
- ExoPlayer架构详解与源码分析(10)——H264Reader
- ExoPlayer架构详解与源码分析(11)——DataSource
- ExoPlayer架构详解与源码分析(12)——Cache
- ExoPlayer架构详解与源码分析(13)——TeeDataSource和CacheDataSource
- ExoPlayer架构详解与源码分析(14)——ProgressiveMediaPeriod
- ExoPlayer架构详解与源码分析(15)——Renderer
- ExoPlayer架构详解与源码分析(16)——LoadControl
- ExoPlayer架构详解与源码分析(17)——TrackSelector
前言
本篇来了解下四大组件中的LoadControl,这个组件和其他几个比起来算是比较小巧的了,简略过下
LoadControl
播放前媒体数据加载控制
- onPrepared 当准备完成时被播放器调用
- onTracksSelected 选择轨道时调用
- onStopped 播放停止时调用
- onReleased 播放释放时调用
- getAllocator 返回一个Allocator,用于媒体数据的缓存管理
- getBackBufferDurationUs 返回在当前播放位置之前保留在缓冲区中的媒体持续时间,用以进行快速向后搜索。 注意:如果retainBackBufferFromKeyframe()=false,则只有当后台缓冲区包含查找位置之前的关键帧时,后台缓冲区中的查找才会很快。
- retainBackBufferFromKeyframe 返回是否应保留当前播放位置减去getBackBufferDurationUs()之前的关键帧中的媒体,而不是保留该位置之前或该位置处的任何样本。返回 true 将导致后台缓冲区大小取决于正在播放的媒体中关键帧的间距。不建议返回 true
- shouldContinueLoading 由播放器调用以确定是否应继续加载源。如果此方法返回 true,则将继续加载最近一次onTracksSelected调用中标识的MediaPeriod 。
- shouldStartPlayback 播放器在加载源、尚未开始播放且具有开始播放所需的最小数据量时调用。返回的值决定是否真正开始播放。负载控制可以选择返回false直到满足某些条件(例如,缓冲了一定量的媒体)。
可以看出LoadControl在播放器的关键时候进行了调用,播放前决定是否可以播放,加载过程中决定是否继续加载数据,下面看它的默认实现类DefaultLoadControl
DefaultLoadControl
private final DefaultAllocator allocator;//缓存分配器,默认初始化出了一个单个Allocation块大小为64KB的DefaultAllocator
private final long minBufferUs;//最小缓存的时长,默认值是50秒
private final long maxBufferUs;//最大缓存的时长,默认值是50秒
private final long bufferForPlaybackUs;//开始播放前至少要缓存的时长,默认值2.5秒
private final long bufferForPlaybackAfterRebufferUs;//重新缓存时至少需要的缓冲时长,默认值5秒
private final int targetBufferBytesOverwrite;//初始设置的缓存大小,默认值LENGTH_UNSET=-1
private final boolean prioritizeTimeOverSizeThresholds;//缓存时间的优先级是否高于缓存大小,默认值fasle,就是默认优先使用缓存的大小
private final long backBufferDurationUs;//已缓存数据的时长,默认值0秒
private final boolean retainBackBufferFromKeyframe;//缓冲区市场从前一个关键帧保留,默认值false
private int targetBufferBytes;//缓存大小,默认值LENGTH_UNSET=-1
private boolean isLoading;//是否继续加载
@Override
public void onPrepared() {
reset(false);//完成后重置数据
}
private void reset(boolean resetAllocator) {
targetBufferBytes =//将缓存大小设置为初始值,如果初始值未设置,则设置为默认最小值50秒
targetBufferBytesOverwrite == C.LENGTH_UNSET
? DEFAULT_MIN_BUFFER_SIZE
: targetBufferBytesOverwrite;
isLoading = false;
if (resetAllocator) {
allocator.reset();//重置缓存,播放停止或者释放时才需要
}
}
@Override
public void onTracksSelected(
Timeline timeline,
MediaPeriodId mediaPeriodId,
Renderer[] renderers,
TrackGroupArray trackGroups,
ExoTrackSelection[] trackSelections) {
targetBufferBytes =//轨道选择时,在MediaPeriod选择完轨道后调用,此时播放媒体的相关数据已经可以获取
targetBufferBytesOverwrite == C.LENGTH_UNSET
? calculateTargetBufferBytes(renderers, trackSelections)//可以用这些数据获取缓存大小
: targetBufferBytesOverwrite;
//设置单个allocator总大小
allocator.setTargetBufferSize(targetBufferBytes);
}
protected int calculateTargetBufferBytes(
Renderer[] renderers, ExoTrackSelection[] trackSelectionArray) {
int targetBufferSize = 0;
for (int i = 0; i < renderers.length; i++) {
if (trackSelectionArray[i] != null) {
//计算所有轨道数据缓存的总大小
targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType());
}
}
//DEFAULT_MIN_BUFFER_SIZE= 200 * C.DEFAULT_BUFFER_SEGMENT_SIZE也就是至少有200个Allocation,每个Allocation包含64KB的block,总大小就是12.5MB
return max(DEFAULT_MIN_BUFFER_SIZE, targetBufferSize);
}
//根据不同轨道类型获取默认大小
private static int getDefaultBufferSize(@C.TrackType int trackType) {
switch (trackType) {
case C.TRACK_TYPE_DEFAULT:
//DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE
return DEFAULT_MUXED_BUFFER_SIZE;
case C.TRACK_TYPE_AUDIO:
//音轨200个Allocation,200*64KB=12.5M
return DEFAULT_AUDIO_BUFFER_SIZE;
case C.TRACK_TYPE_VIDEO:
//视频轨2000个Allocation,2000*64KB=125M
return DEFAULT_VIDEO_BUFFER_SIZE;
case C.TRACK_TYPE_TEXT:
//字幕频轨2个Allocation,2*64kb=128KB
return DEFAULT_TEXT_BUFFER_SIZE;
case C.TRACK_TYPE_METADATA:
//metadata轨2个Allocation,2*64kb=128KB
return DEFAULT_METADATA_BUFFER_SIZE;
case C.TRACK_TYPE_CAMERA_MOTION:
//camera motion 2个Allocation,2*64kb=128KB
return DEFAULT_CAMERA_MOTION_BUFFER_SIZE;
case C.TRACK_TYPE_IMAGE:
//image 2个Allocation,2*64kb=128KB
return DEFAULT_IMAGE_BUFFER_SIZE;
case C.TRACK_TYPE_NONE:
return 0;
case C.TRACK_TYPE_UNKNOWN:
default:
throw new IllegalArgumentException();
}
}
@Override
//判断是否继续加载数据
public boolean shouldContinueLoading(
long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
//当前已经加载的数据是否已经达到targetBufferBytes
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferBytes;
long minBufferUs = this.minBufferUs;
if (playbackSpeed > 1) {
// 播放速度如果大于1,则minBufferUs 也要相应的增大以保证有足够的缓存供播放
long mediaDurationMinBufferUs =
Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed);
minBufferUs = min(mediaDurationMinBufferUs, maxBufferUs);
}
// minBufferUs不能太小
minBufferUs = max(minBufferUs, 500_000);
if (bufferedDurationUs < minBufferUs) {//缓存的时长不足minBufferUs了
//时长优先模式没有达到targetBufferSizeReached
isLoading = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
if (!isLoading && bufferedDurationUs < 500_000) {
//不需要继续加载,但是提示用户当前已经targetBufferSizeReached,但是总时长不足minBufferUs最小值,这样容易卡顿,可以需要修改LoadControl配置
Log.w(
"DefaultLoadControl",
"Target buffer size reached with less than 500ms of buffered media data.");
}
} else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
//缓存的时长超过maxBufferUs 或者已经targetBufferSizeReached,不能继续加载了
isLoading = false;
} // 其他情况不改变isLoading值
return isLoading;
}
@Override
//判断当前缓存是否满足播放需求
public boolean shouldStartPlayback(
Timeline timeline,
MediaPeriodId mediaPeriodId,
long bufferedDurationUs,
float playbackSpeed,
boolean rebuffering,
long targetLiveOffsetUs) {
//使用播放速度等量计算当前已缓存的bufferedDurationUs 时长
bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed);
//获取最小可播放时长
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
if (targetLiveOffsetUs != C.TIME_UNSET) {
minBufferDurationUs = min(targetLiveOffsetUs / 2, minBufferDurationUs);
}
return minBufferDurationUs <= 0
|| bufferedDurationUs >= minBufferDurationUs//已经大于最小可播放时常,返回true
|| (!prioritizeTimeOverSizeThresholds//缓存大小优先模式需要已缓存的总大小大于等于targetBufferBytes
&& allocator.getTotalBytesAllocated() >= targetBufferBytes);
}
下我们来整理下,继续播放的条件主要分3种情况讨论
-
bufferedDurationUs < minBufferUs ,当前缓存时长小于最小缓存时长,这里分2种情况
- 时长优先模式则直接继续加载
- 大小优先模式,如果已经缓存到targetBufferBytes,则停止加载,否则继续加载
可见此时,是否加载数据需要根据prioritizeTimeOverSizeThresholds决定通过时长还是缓存大小来判断
-
minBufferUs <= bufferedDurationUs < maxBufferUs ,当前缓存时长介于最小和最大之间,此时如果已经缓存到targetBufferBytes,则停止加载,否则保持现状。
-
bufferedDurationUs >=maxBufferUs ,当前缓存时长大于等于最大缓存时长,此时无论是否已经存到targetBufferBytes都停止加载
prioritizeTimeOverSizeThresholds只有在缓存时长小于最小缓存时长才会起作用。
总结
LoadControl到这里就讲完了,可以通过设置其中的几个参数来控制ExoPlayer缓存的大小,也可以通过自己实现LoadControl,来决定什么时候继续加载数据。但是这里的缓存指的是内存中的数据,这些参数并不是随意设置的,不然随时OOM。
版权声明 ©
本文为作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持