系列文章目录
- 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
前言
好了四大组件就剩TrackSelector没讲了。这里回顾下系列开头的构架图。
TrackSelector负责选择每个播放器渲染器要使用的轨道。
与播放器交互
在播放期间,播放器与TrackSelector之间会发生以下交互:
- 创建播放器时,它将通过调用 init 来初始化 TrackSelector。
- 当播放器需要进行轨道选择时,它将调用 selectTracks。通常发生在播放开始的时候。
- 播放器需要在 Renderer 渲染数据前选择好轨道。例如播放器在缓存数据时,当MediaPeriod preare完成时就会选择轨道。播放器通过调用 onSelectionActivated 告诉TrackSelector当前变为Activated状态。
- 如果TrackSelector希望向播放器指出以前做出的选择是无效的,TrackSelector 可以通过调用onTrackSelectionsInvalidated。这个监听是在播放器调用 TrackSelector.init 时注册给 TrackSelector 的。如果轨道选择器的配置已更改,则调用intrackSelectionsInvaliDated通知,例如,如果现在希望使用特定语言的音轨。这将触发播放器进行新的轨道选择。请注意,如果当前播放期的新选择的规定与之前轨道选择不同,则播放前将重新缓冲。
- 当播放器被释放时,它会通过调用release来释放曲目选择器。
渲染器配置
selectTracks 返回的 TrackSelectorResult 不仅包含每个渲染器的 TrackSelections,还包含定义Renderer在使用相应媒体时应应用的配置参数的 RendererConfigurations。TrackSelector指定 Renderer 配置信息似乎不符合常识,但实际上两者紧密结合在一起。如果以特定方式配置Renderer,则可能只能播放某些组合轨道。同样,如果选择了某些轨道,则可能只能以特定方式配置渲染器。因此,需要同步确定轨道选择和相应的 Renderer 配置。
线程模型
播放器对TrackSelector进行的所有调用都在播放器的内部播放线程上进行。TrackSelector可能从任何线程回调 onTrackSelectionsInvalidated,线程模型参考ExoPlayer架构详解与源码分析(4)——整体架构。
MappingTrackSelector
MappingTrackSelector首先在TrackGroup和Renderer之间建立映射,然后根据该映射为每个渲染器创建ExoTrackSelection 。
@Override
//播放器调用执行轨道选择
public final TrackSelectorResult selectTracks(
RendererCapabilities[] rendererCapabilities,//包含了所有Renderer的信息,播放器初始化时会获取,RendererCapabilities定义了Renderer 的功能相关信息
TrackGroupArray trackGroups,//MediaPeriod prepared 后,播放器通过getTrackGroups从中获取的所有轨道信息
MediaPeriodId periodId,//关联当前的MediaPeriod
Timeline timeline)//当前的Timeline
throws ExoPlaybackException {
//选择期间被写入的数据结构,最后一位用来记录可以与任何renderer关联的轨道相关数据
int[] rendererTrackGroupCounts = new int[rendererCapabilities.length + 1];
TrackGroup[][] rendererTrackGroups = new TrackGroup[rendererCapabilities.length + 1][];
@Capabilities int[][][] rendererFormatSupports = new int[rendererCapabilities.length + 1][][];
for (int i = 0; i < rendererTrackGroups.length; i++) {
rendererTrackGroups[i] = new TrackGroup[trackGroups.length];
rendererFormatSupports[i] = new int[trackGroups.length][];
}
//确定每个renderer 能适应混合 mimeType,默认的MediaCodec能适应但是可能会出现短暂的不连续性返回8,字幕和其他渲染器一般不支持返回0
@AdaptiveSupport
int[] rendererMixedMimeTypeAdaptationSupports =
getMixedMimeTypeAdaptationSupports(rendererCapabilities);
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
TrackGroup group = trackGroups.get(groupIndex);
// 为每个轨道 查找一个合适的renderer
boolean preferUnassociatedRenderer = group.type == C.TRACK_TYPE_METADATA;
int rendererIndex =
findRenderer(
rendererCapabilities, group, rendererTrackGroupCounts, preferUnassociatedRenderer);
// 确定查找出的Renderer是否支持当前轨道 格式
@Capabilities
int[] rendererFormatSupport =
rendererIndex == rendererCapabilities.length
? new int[group.length]
: getFormatSupport(rendererCapabilities[rendererIndex], group);
// 保存结果
int rendererTrackGroupCount = rendererTrackGroupCounts[rendererIndex];
rendererTrackGroups[rendererIndex][rendererTrackGroupCount] = group;
rendererFormatSupports[rendererIndex][rendererTrackGroupCount] = rendererFormatSupport;
rendererTrackGroupCounts[rendererIndex]++;
}
// 为每个renderer创建一个TrackGroupArray
TrackGroupArray[] rendererTrackGroupArrays = new TrackGroupArray[rendererCapabilities.length];
String[] rendererNames = new String[rendererCapabilities.length];
int[] rendererTrackTypes = new int[rendererCapabilities.length];
for (int i = 0; i < rendererCapabilities.length; i++) {
int rendererTrackGroupCount = rendererTrackGroupCounts[i];
rendererTrackGroupArrays[i] =
new TrackGroupArray(
Util.nullSafeArrayCopy(rendererTrackGroups[i], rendererTrackGroupCount));
rendererFormatSupports[i] =
Util.nullSafeArrayCopy(rendererFormatSupports[i], rendererTrackGroupCount);
rendererNames[i] = rendererCapabilities[i].getName();
rendererTrackTypes[i] = rendererCapabilities[i].getTrackType();
}
// Create a track group array for track groups not mapped to a renderer.
int unmappedTrackGroupCount = rendererTrackGroupCounts[rendererCapabilities.length];
TrackGroupArray unmappedTrackGroupArray =
new TrackGroupArray(
Util.nullSafeArrayCopy(
rendererTrackGroups[rendererCapabilities.length], unmappedTrackGroupCount));
// 构建出mappedTrackInfo
MappedTrackInfo mappedTrackInfo =
new MappedTrackInfo(
rendererNames,
rendererTrackTypes,
rendererTrackGroupArrays,
rendererMixedMimeTypeAdaptationSupports,
rendererFormatSupports,
unmappedTrackGroupArray);
Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]> result =
selectTracks(//调用子类选择轨道
mappedTrackInfo,
rendererFormatSupports,
rendererMixedMimeTypeAdaptationSupports,
periodId,
timeline);
//最终获取Tracks
Tracks tracks = TrackSelectionUtil.buildTracks(mappedTrackInfo, result.second);
return new TrackSelectorResult(result.first, result.second, tracks, mappedTrackInfo);
}
MappingTrackSelector主要作用就是将MediaPeriod获取到的trackGroups找到对应的Renderer然后映射到Map中。
DefaultTrackSelector
DefaultTrackSelector继承自MappingTrackSelector,可以实现大部分媒体的轨道选择
@Override
protected final Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]>
selectTracks(
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] rendererFormatSupports,
@AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupport,
MediaPeriodId mediaPeriodId,
Timeline timeline)
throws ExoPlaybackException {
Parameters parameters;
synchronized (lock) {
parameters = this.parameters;//选择器的选择策略配置参数
//是否限制音轨选择,以使所选轨道的通道数不超过设备的输出能力。默认值为true
if (parameters.constrainAudioChannelCountToDeviceCapabilities
&& Util.SDK_INT >= 32
&& spatializer != null) {
// 空间音频初始化
spatializer.ensureInitialized(this, checkStateNotNull(Looper.myLooper()));
}
}
int rendererCount = mappedTrackInfo.getRendererCount();
//开始选择轨道
ExoTrackSelection.@NullableType Definition[] definitions =
selectAllTracks(
mappedTrackInfo,
rendererFormatSupports,
rendererMixedMimeTypeAdaptationSupport,
parameters);
applyTrackSelectionOverrides(mappedTrackInfo, parameters, definitions);
applyLegacyRendererOverrides(mappedTrackInfo, parameters, definitions);
// 禁用parameters指定的轨道
for (int i = 0; i < rendererCount; i++) {
@C.TrackType int rendererType = mappedTrackInfo.getRendererType(i);
if (parameters.getRendererDisabled(i)
|| parameters.disabledTrackTypes.contains(rendererType)) {
definitions[i] = null;
}
}
@NullableType
//通过上面选择的结果,创建TrackSelections
ExoTrackSelection[] rendererTrackSelections =
trackSelectionFactory.createTrackSelections(
definitions, getBandwidthMeter(), mediaPeriodId, timeline);
//将所有渲染器的渲染器配置初始化为默认配置,否则为 null。
@NullableType
RendererConfiguration[] rendererConfigurations = new RendererConfiguration[rendererCount];
for (int i = 0; i < rendererCount; i++) {
@C.TrackType int rendererType = mappedTrackInfo.getRendererType(i);
boolean forceRendererDisabled =
parameters.getRendererDisabled(i) || parameters.disabledTrackTypes.contains(rendererType);
boolean rendererEnabled =
!forceRendererDisabled
&& (mappedTrackInfo.getRendererType(i) == C.TRACK_TYPE_NONE
|| rendererTrackSelections[i] != null);
rendererConfigurations[i] = rendererEnabled ? RendererConfiguration.DEFAULT : null;
}
// 配置音频和视频渲染器以使用隧道(如果适用)。
if (parameters.tunnelingEnabled) {
maybeConfigureRenderersForTunneling(
mappedTrackInfo, rendererFormatSupports, rendererConfigurations, rendererTrackSelections);
}
return Pair.create(rendererConfigurations, rendererTrackSelections);
}
protected ExoTrackSelection.@NullableType Definition[] selectAllTracks(
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] rendererFormatSupports,
@AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupports,
Parameters params)
throws ExoPlaybackException {
int rendererCount = mappedTrackInfo.getRendererCount();
ExoTrackSelection.@NullableType Definition[] definitions =
new ExoTrackSelection.Definition[rendererCount];
@Nullable
Pair<ExoTrackSelection.Definition, Integer> selectedVideo =
//选择视频轨道
selectVideoTrack(
mappedTrackInfo,
rendererFormatSupports,
rendererMixedMimeTypeAdaptationSupports,
params);
if (selectedVideo != null) {
definitions[selectedVideo.second] = selectedVideo.first;
}
@Nullable
Pair<ExoTrackSelection.Definition, Integer> selectedAudio =
//选择音频轨道
selectAudioTrack(
mappedTrackInfo,
rendererFormatSupports,
rendererMixedMimeTypeAdaptationSupports,
params);
if (selectedAudio != null) {
definitions[selectedAudio.second] = selectedAudio.first;
}
@Nullable
String selectedAudioLanguage =
selectedAudio == null
? null
: selectedAudio.first.group.getFormat(selectedAudio.first.tracks[0]).language;
@Nullable
Pair<ExoTrackSelection.Definition, Integer> selectedText =
//选择字幕轨道
selectTextTrack(mappedTrackInfo, rendererFormatSupports, params, selectedAudioLanguage);
if (selectedText != null) {
definitions[selectedText.second] = selectedText.first;
}
for (int i = 0; i < rendererCount; i++) {
int trackType = mappedTrackInfo.getRendererType(i);
if (trackType != C.TRACK_TYPE_VIDEO
&& trackType != C.TRACK_TYPE_AUDIO
&& trackType != C.TRACK_TYPE_TEXT) {
definitions[i] =
//其他轨道选择
selectOtherTrack(
trackType, mappedTrackInfo.getTrackGroups(i), rendererFormatSupports[i], params);
}
}
return definitions;
}
@Nullable
protected Pair<ExoTrackSelection.Definition, Integer> selectVideoTrack(
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] rendererFormatSupports,
@AdaptiveSupport int[] mixedMimeTypeSupports,
Parameters params)
throws ExoPlaybackException {
return selectTracksForType(
C.TRACK_TYPE_VIDEO,
mappedTrackInfo,
rendererFormatSupports,
(int rendererIndex, TrackGroup group, @Capabilities int[] support) ->
VideoTrackInfo.createForTrackGroup(
rendererIndex, group, params, support, mixedMimeTypeSupports[rendererIndex]),
VideoTrackInfo::compareSelections);
}
通过设置DefaultTrackSelector的parameters,来自定义轨道的选择策略,然后通过这些策略和内置的一些规则来选择出符合要求的轨道。
VideoTrackInfo
视频轨道的选择策略主要定义在VideoTrackInfo中
public VideoTrackInfo(
int rendererIndex,
TrackGroup trackGroup,
int trackIndex,
Parameters parameters,
@Capabilities int formatSupport,
@AdaptiveSupport int mixedMimeTypeAdaptationSupport,
boolean isSuitableForViewport) {
super(rendererIndex, trackGroup, trackIndex);
this.parameters = parameters;
@SuppressLint("WrongConstant")
int requiredAdaptiveSupport =
parameters.allowVideoNonSeamlessAdaptiveness
? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS
| RendererCapabilities.ADAPTIVE_SEAMLESS)
: RendererCapabilities.ADAPTIVE_SEAMLESS;
allowMixedMimeTypes =
parameters.allowVideoMixedMimeTypeAdaptiveness
&& (mixedMimeTypeAdaptationSupport & requiredAdaptiveSupport) != 0;
isWithinMaxConstraints =//是否符合最大参数的约束
isSuitableForViewport
&& (format.width == Format.NO_VALUE || format.width <= parameters.maxVideoWidth)
&& (format.height == Format.NO_VALUE || format.height <= parameters.maxVideoHeight)
&& (format.frameRate == Format.NO_VALUE
|| format.frameRate <= parameters.maxVideoFrameRate)
&& (format.bitrate == Format.NO_VALUE
|| format.bitrate <= parameters.maxVideoBitrate);
isWithinMinConstraints =//是否符合最小参数的约束
isSuitableForViewport
&& (format.width == Format.NO_VALUE || format.width >= parameters.minVideoWidth)
&& (format.height == Format.NO_VALUE || format.height >= parameters.minVideoHeight)
&& (format.frameRate == Format.NO_VALUE
|| format.frameRate >= parameters.minVideoFrameRate)
&& (format.bitrate == Format.NO_VALUE
|| format.bitrate >= parameters.minVideoBitrate);
isWithinRendererCapabilities =//是否在渲染器的能力范围内
isSupported(formatSupport, /* allowExceedsCapabilities= */ false);
bitrate = format.bitrate;//视频比特率
pixelCount = format.getPixelCount();//视频分辨率
preferredRoleFlagsScore =//format指定了优先级
getRoleFlagMatchScore(format.roleFlags, parameters.preferredVideoRoleFlags);
hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0;
int bestMimeTypeMatchIndex = Integer.MAX_VALUE;
for (int i = 0; i < parameters.preferredVideoMimeTypes.size(); i++) {
if (format.sampleMimeType != null
&& format.sampleMimeType.equals(parameters.preferredVideoMimeTypes.get(i))) {
bestMimeTypeMatchIndex = i;
break;
}
}
preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex;//最符合parameters期望格式的索引
usesPrimaryDecoder =//渲染器能够使用主解码器解码该格式的
RendererCapabilities.getDecoderSupport(formatSupport)
== RendererCapabilities.DECODER_SUPPORT_PRIMARY;
usesHardwareAcceleration =//渲染器是否能够使用硬件加速
RendererCapabilities.getHardwareAccelerationSupport(formatSupport)
== RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED;
codecPreferenceScore = getVideoCodecPreferenceScore(format.sampleMimeType);//不同视频类型对应的优先级DOLBY>AV1>H265>VP9>H264
selectionEligibility = evaluateSelectionEligibility(formatSupport, requiredAdaptiveSupport);
}
public static int compareSelections(List<VideoTrackInfo> infos1, List<VideoTrackInfo> infos2) {
return ComparisonChain.start()
// 先通过非质量因素比较
.compare(
max(infos1, VideoTrackInfo::compareNonQualityPreferences),
max(infos2, VideoTrackInfo::compareNonQualityPreferences),
VideoTrackInfo::compareNonQualityPreferences)
// 如果非质量因素都相等则优先选择Format更多的轨道
.compare(infos1.size(), infos2.size())
// 最后获取质量最好的轨道
.compare(
max(infos1, VideoTrackInfo::compareQualityPreferences),
max(infos2, VideoTrackInfo::compareQualityPreferences),
VideoTrackInfo::compareQualityPreferences)
.result();
}
}
//非质量因素比较
private static int compareNonQualityPreferences(VideoTrackInfo info1, VideoTrackInfo info2) {
ComparisonChain chain =
ComparisonChain.start()
.compareFalseFirst(
info1.isWithinRendererCapabilities, info2.isWithinRendererCapabilities)
// 1. 如果parameters设置了选择优先,则使用parameters设置的优先级比较
.compare(info1.preferredRoleFlagsScore, info2.preferredRoleFlagsScore)
// 2. 如果轨道本身定义了优先级,则优先使用
.compareFalseFirst(info1.hasMainOrNoRoleFlag, info2.hasMainOrNoRoleFlag)
// 3. 是否符合parameters规定最大最小参数约束
.compareFalseFirst(info1.isWithinMaxConstraints, info2.isWithinMaxConstraints)
.compareFalseFirst(info1.isWithinMinConstraints, info2.isWithinMinConstraints)
.compare(
info1.preferredMimeTypeMatchIndex,
info2.preferredMimeTypeMatchIndex,
Ordering.natural().reverse())
// 4. 比较渲染器设置
.compareFalseFirst(info1.usesPrimaryDecoder, info2.usesPrimaryDecoder)
.compareFalseFirst(info1.usesHardwareAcceleration, info2.usesHardwareAcceleration);
if (info1.usesPrimaryDecoder && info1.usesHardwareAcceleration) {
chain = chain.compare(info1.codecPreferenceScore, info2.codecPreferenceScore);
}
return chain.result();
}
//质量因素比较
private static int compareQualityPreferences(VideoTrackInfo info1, VideoTrackInfo info2) {
//按视频质量比较原则:
// - 不在渲染器能力范围内:优先选择较低质量,因为它更有可能播放。
// - 在最小和最大限制内:更喜欢更高的质量。
// - 仅在最大约束内:更喜欢更高的质量,因为它让我们最接近满足违反的最小约束。
// - 仅在最小约束内:更喜欢较低的质量,因为它使我们最接近满足违反的最大约束。
// - 超出最小和最大限制:任意选择较低质量
Ordering<Integer> qualityOrdering =//通过是否在渲染器能力范围内,决定优先最高还是最低质量
info1.isWithinMaxConstraints && info1.isWithinRendererCapabilities
? FORMAT_VALUE_ORDERING
: FORMAT_VALUE_ORDERING.reverse();
return ComparisonChain.start()
.compare(//比较比特率
info1.bitrate,
info2.bitrate,
info1.parameters.forceLowestBitrate ? FORMAT_VALUE_ORDERING.reverse() : NO_ORDER)
.compare(info1.pixelCount, info2.pixelCount, qualityOrdering)//比较分辨率
.compare(info1.bitrate, info2.bitrate, qualityOrdering)
.result();
}
这里可以总结下VideoTrackInfo选择视频轨道的策略:
-
比较选择器parameters设置的选择规则去选定符合规则的轨道
-
如果规道本身定义了优先级则优先使用
-
如果符合parameters规定最大最小参数约束(组要包含parameters定义的最大最小宽高、帧率、比特率),优先使用
-
如果在parameters.preferredVideoMimeTypes规定的期望格式中更靠前则优先使用
-
如果渲染器能够使用主解码器或者支持硬件解码来播放当前轨道则优先使用
-
如果支持主解码器和硬解则按照DOLBY>AV1>H265>VP9>H264选择轨道
-
如果以上因素都相等则优先选择Format更多的轨道
-
最后判断当前轨道是否超出渲染器能力范围,超出则选择较低质量(包含分辨率,比特率)视频,这样更有可能成功播放,如果未超出则考虑是否在最小最大约束范围内
- 在最小和最大限制内:优先高的质量轨道
- 仅在最大约束内:优先高的质量
- 仅在最小约束内:优先较低的质量
- 超出最小和最大限制:优先较低质量
AudioTrackInfo
音频轨道的选择策略主要定义在AudioTrackInfo中
public AudioTrackInfo(
int rendererIndex,
TrackGroup trackGroup,
int trackIndex,
Parameters parameters,
@Capabilities int formatSupport,
boolean hasMappedVideoTracks,
Predicate<Format> withinAudioChannelCountConstraints) {
super(rendererIndex, trackGroup, trackIndex);
this.parameters = parameters;
this.language = normalizeUndeterminedLanguageToNull(format.language);
isWithinRendererCapabilities =
isSupported(formatSupport, /* allowExceedsCapabilities= */ false);
int bestLanguageScore = 0;
int bestLanguageIndex = Integer.MAX_VALUE;
for (int i = 0; i < parameters.preferredAudioLanguages.size(); i++) {
int score =
getFormatLanguageScore(
format,
parameters.preferredAudioLanguages.get(i),
/* allowUndeterminedFormatLanguage= */ false);
if (score > 0) {
bestLanguageIndex = i;
bestLanguageScore = score;
break;
}
}
preferredLanguageIndex = bestLanguageIndex;
preferredLanguageScore = bestLanguageScore;
preferredRoleFlagsScore =
getRoleFlagMatchScore(format.roleFlags, parameters.preferredAudioRoleFlags);
hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0;
isDefaultSelectionFlag = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0;
channelCount = format.channelCount;
sampleRate = format.sampleRate;
bitrate = format.bitrate;
isWithinConstraints =
(format.bitrate == Format.NO_VALUE || format.bitrate <= parameters.maxAudioBitrate)
&& (format.channelCount == Format.NO_VALUE
|| format.channelCount <= parameters.maxAudioChannelCount)
&& withinAudioChannelCountConstraints.apply(format);
String[] localeLanguages = Util.getSystemLanguageCodes();
int bestLocaleMatchIndex = Integer.MAX_VALUE;
int bestLocaleMatchScore = 0;
for (int i = 0; i < localeLanguages.length; i++) {
int score =
getFormatLanguageScore(
format, localeLanguages[i], /* allowUndeterminedFormatLanguage= */ false);
if (score > 0) {
bestLocaleMatchIndex = i;
bestLocaleMatchScore = score;
break;
}
}
localeLanguageMatchIndex = bestLocaleMatchIndex;
localeLanguageScore = bestLocaleMatchScore;
int bestMimeTypeMatchIndex = Integer.MAX_VALUE;
for (int i = 0; i < parameters.preferredAudioMimeTypes.size(); i++) {
if (format.sampleMimeType != null
&& format.sampleMimeType.equals(parameters.preferredAudioMimeTypes.get(i))) {
bestMimeTypeMatchIndex = i;
break;
}
}
preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex;
usesPrimaryDecoder =
RendererCapabilities.getDecoderSupport(formatSupport)
== RendererCapabilities.DECODER_SUPPORT_PRIMARY;
usesHardwareAcceleration =
RendererCapabilities.getHardwareAccelerationSupport(formatSupport)
== RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED;
selectionEligibility = evaluateSelectionEligibility(formatSupport, hasMappedVideoTracks);
}
@Override
public int compareTo(AudioTrackInfo other) {
// 如果格式在限制和渲染器功能范围内,则优先选择通道数、采样率和比特率的较高值(按该顺序)。否则,请选择较低的值
Ordering<Integer> qualityOrdering =
isWithinConstraints && isWithinRendererCapabilities
? FORMAT_VALUE_ORDERING
: FORMAT_VALUE_ORDERING.reverse();
return ComparisonChain.start()
.compareFalseFirst(this.isWithinRendererCapabilities, other.isWithinRendererCapabilities)
//1. parameters设置的特定内容偏好进行比较匹配
.compare(//偏好的语言和规则
this.preferredLanguageIndex,
other.preferredLanguageIndex,
Ordering.natural().reverse())
.compare(this.preferredLanguageScore, other.preferredLanguageScore)
.compare(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore)
// 2. 如果规道本身定义了优先级则优先使用
.compareFalseFirst(this.isDefaultSelectionFlag, other.isDefaultSelectionFlag)
.compareFalseFirst(this.hasMainOrNoRoleFlag, other.hasMainOrNoRoleFlag)
.compare(
this.localeLanguageMatchIndex,
other.localeLanguageMatchIndex,
Ordering.natural().reverse())
.compare(this.localeLanguageScore, other.localeLanguageScore)
// 3. 如果在parameters.preferredVideoMimeTypes规定的期望格式中更靠前则优先使用
.compareFalseFirst(this.isWithinConstraints, other.isWithinConstraints)
.compare(
this.preferredMimeTypeMatchIndex,
other.preferredMimeTypeMatchIndex,
Ordering.natural().reverse())
.compare(//优先高比特率
this.bitrate,
other.bitrate,
parameters.forceLowestBitrate ? FORMAT_VALUE_ORDERING.reverse() : NO_ORDER)
// 4. 如果渲染器能够使用主解码器或者支持硬件解码来播放当前轨道则优先使用
.compareFalseFirst(this.usesPrimaryDecoder, other.usesPrimaryDecoder)
.compareFalseFirst(this.usesHardwareAcceleration, other.usesHardwareAcceleration)
// 5. 比较轨道质量参数,音频的通道数、音频采样率
.compare(this.channelCount, other.channelCount, qualityOrdering)
.compare(this.sampleRate, other.sampleRate, qualityOrdering)
.compare(
this.bitrate,
other.bitrate,
// 只有在符合语言选择的情况下才会比较比特率
Util.areEqual(this.language, other.language) ? qualityOrdering : NO_ORDER)
.result();
}
音频的轨道选择和视频类似,其中多考虑了语言优先级。 其他轨道选择类似这里不再分析。
总结
到本篇四大组件就到讲完了,但是ExoPlayer还没讲完哦,四大组件最终都是给ExoPlayer调用的,ExoPlayer相当于一个中央控制器,控制协调着这些组件运转。铺垫了这么多,下篇将分析下ExoPlayer具体是如何运用这四大组件完成播放的。
版权声明 ©
本文为作者山雨楼原创文章
转载请注明出处
原创不易,觉得有用的话,收藏转发点赞支持