ExoPlayer缓冲策略(三)

ExoPlayer是根据什么策略来缓冲媒体数据的?

在之前的文章中我们描述过,LoadControl是控制媒体数据缓冲的,它有一个默认的实现DefaulLoadControl,默认的缓冲策略就在其中。

默认参数信息

缓冲策略控制需要基于一定的数据基础,根据数据来确定是否满足一定的条件(是否可以缓冲,是否可以播放等),以下是在DefaultLoadControl中定义的初始信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 播放开始后,播放器缓冲的最小缓冲时间。
public static final int DEFAULT_MIN_BUFFER_MS = 15000;

// 播放开始后,播放器缓冲的最大缓冲时间(DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS为true时)
public static final int DEFAULT_MAX_BUFFER_MS = 50000;

// 播放前需缓冲的最小时间
public static final int DEFAULT_BUFFER_FOR_PLAYBACK_MS = 2500;

// rebuffer后播放器播放时需要缓冲的最小时间
// A rebuffer is defined to be caused by buffer depletion rather than a user action.
public static final int DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS = 5000;

// 默认的缓冲大小,当设置为C.LENGTH_UNSET时,由加载控制器自动检测目标大小
public static final int DEFAULT_TARGET_BUFFER_BYTES = C.LENGTH_UNSET;

// 缓冲时间优先级是否大于缓冲大小优先级。
public static final boolean DEFAULT_PRIORITIZE_TIME_OVER_SIZE_THRESHOLDS = true;

默认加载控制器如何控制缓冲

继续缓冲
  1. 当缓冲时间为高优先级时:
    • 未达到最小缓冲时间;
  2. 当缓冲大小为高优先级时:
    • 未达到最小缓冲时间,并且未达到目标缓冲大小。
停止缓冲
  1. 已缓冲时间超过了设置的最大缓冲时间;
  2. 超过最小缓冲时间:达到最大缓冲大小;
  3. 当设置了缓存大小为高优先级,即使未达到最小缓冲时间,但达到了最大缓冲大小。

详细实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public boolean shouldContinueLoading(long bufferedDurationUs, float playbackSpeed) {
boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferSize;
boolean wasBuffering = isBuffering;
long minBufferUs = this.minBufferUs;
if (playbackSpeed > 1) {
// 当播放速率变大时,最小缓冲时间需要重新计算(Math.round((double) playoutDuration * speed);)
long mediaDurationMinBufferUs =
Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed);
minBufferUs = Math.min(mediaDurationMinBufferUs, maxBufferUs);
}
// 判断缓冲时间并根据优先级处理。
if (bufferedDurationUs < minBufferUs) {
isBuffering = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
} else if (bufferedDurationUs > maxBufferUs || targetBufferSizeReached) {
// 判断最大缓冲时间和目标缓冲大小。
isBuffering = false;
} // Else don't change the buffering state
if (priorityTaskManager != null && isBuffering != wasBuffering) {
if (isBuffering) {
priorityTaskManager.add(C.PRIORITY_PLAYBACK);
} else {
priorityTaskManager.remove(C.PRIORITY_PLAYBACK);
}
}
return isBuffering;
}

如何计算目标缓冲大小

目标缓冲大小是判断是否继续缓冲的一个很重要的参数,它是如何计算的呢?

当设置了DEFAULT_TARGET_BUFFER_BYTES = C.LENGTH_UNSET; 需要计算期待达到的目标缓冲大小:

1
2
3
4
5
6
7
8
9
10
// 轨道选择器准备就绪,调用此方法。
@Override
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) {
targetBufferSize =
targetBufferBytesOverwrite == C.LENGTH_UNSET
? calculateTargetBufferSize(renderers, trackSelections)
: targetBufferBytesOverwrite;
allocator.setTargetBufferSize(targetBufferSize);
}

重点在于calculateTargetBufferSize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
* Calculate target buffer size in bytes based on the selected tracks. The player will try not to
* exceed this target buffer. Only used when {@code targetBufferBytes} is {@link C#LENGTH_UNSET}.
*
* @param renderers The renderers for which the track were selected.
* @param trackSelectionArray The selected tracks.
* @return The target buffer size in bytes.
*/
protected int calculateTargetBufferSize(
Renderer[] renderers, TrackSelectionArray trackSelectionArray) {
int targetBufferSize = 0;
for (int i = 0; i < renderers.length; i++) {
if (trackSelectionArray.get(i) != null) {
targetBufferSize += Util.getDefaultBufferSize(renderers[i].getTrackType());
}
}
return targetBufferSize;
}
/**
* Maps a {@link C} {@code TRACK_TYPE_*} constant to the corresponding {@link C}
* {@code DEFAULT_*_BUFFER_SIZE} constant.
*
* @param trackType The track type.
* @return The corresponding default buffer size in bytes.
*/
public static int getDefaultBufferSize(int trackType) {
switch (trackType) {
case C.TRACK_TYPE_DEFAULT:
return C.DEFAULT_MUXED_BUFFER_SIZE;
case C.TRACK_TYPE_AUDIO:
return C.DEFAULT_AUDIO_BUFFER_SIZE;
case C.TRACK_TYPE_VIDEO:
return C.DEFAULT_VIDEO_BUFFER_SIZE;
case C.TRACK_TYPE_TEXT:
return C.DEFAULT_TEXT_BUFFER_SIZE;
case C.TRACK_TYPE_METADATA:
return C.DEFAULT_METADATA_BUFFER_SIZE;
default:
throw new IllegalStateException();
}
}

由上可以看到,目标缓冲大小的计算其实是根据默认的视频缓冲区/音频缓冲区等来确定的。

如何判断是否可以播放?

该功能由shouldStartPlayback来实现:

1
2
3
4
5
6
7
8
9
10
@Override
public boolean shouldStartPlayback(
long bufferedDurationUs, float playbackSpeed, boolean rebuffering) {
bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed);
long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
return minBufferDurationUs <= 0
|| bufferedDurationUs >= minBufferDurationUs
|| (!prioritizeTimeOverSizeThresholds
&& allocator.getTotalBytesAllocated() >= targetBufferSize);
}

满足的条件如下:

  1. 最小缓冲时间小于等于0;
  2. 缓冲时间达到了设置的最小缓冲时间;
  3. 以缓冲大小为优先级时,当前缓冲大小已达到了设置的目标缓冲大小。

综合以上,LoadControl控制器是针对缓冲和播放的策略控制;其主要根据缓冲时间和缓冲大小,并结合两者的优先级进行管理。另外在默认参数的设置上,根据该链接中的说明:

There are arguments that mobile carriers prefer this kind of traffic pattern over their networks (i.e. bursts rather than drip-feeding). Which is an important consideration given ExoPlayer is used by some very popular services. It may also be more battery efficient.

Whether these arguments are still valid is something we should probably take another look at fairly soon, since the information we used when making this decision is 3-4 years old now. We should also figure out whether we should adjust the policy dynamically based on network type (e.g. even if the arguments are still valid, they may only hold for mobile networks and not for WiFi).

note:2016year.

目前的参数设置应该根据网络类型及不同的场景动态设置。

在最近(20180309)的更新中,官方人员已将maxBuffered设置为50000ms,其commit说明如下:

The value is increased to 50 seconds. With that the player can better handle short network problems. This does NOT increase the maximum memory used as we still apply the seperate DEFAULT_TARGET_BUFFER_BYTES.

在以上的描述中,涉及到两个重要的方法shouldContinueLoadingshouldStartPlayback,它们是在什么样的场景下触发的?

shouldContinueLoading的触发时机

shouldContinueLoading的调用是在ExoPlayerImplInternal中;

  1. Period prepared/update;
  2. Tracker reselcted;
  3. Seek to target position;
  4. Media source refresh.

以上都是在播放器播放未结束的前提条件下。

shouldStartPlayback的触发时机

ExoPlayerImplInternal中的doSomeWork()时,shouldStartPlayback是播放器的状态从STATE_BUFFERING切换到STATE_READY时需满足的一个条件。

0%