背景
ExoPlayer是Google推出的强大的开源媒体播放器,它提供了灵活的API和丰富的功能,支持多种媒体格式和网络流媒体的播放。
Exoplayer 播放器在一些平板或者电视设备上,会出现花屏或者黑屏的问题,本文主要讨论Exoplayer产生花屏的原因以及优化的方案
解码器获取和初始化流程
//获取解码,对MediaCodec进行初始化
private void maybeInitCodecWithFallback(
MediaCrypto crypto, boolean mediaCryptoRequiresSecureDecoder)
throws DecoderInitializationException {
if (availableCodecInfos == null) {
try {
//获取解码器列表
List<MediaCodecInfo> allAvailableCodecInfos =
getAvailableCodecInfos(mediaCryptoRequiresSecureDecoder);
availableCodecInfos = new ArrayDeque<>();
if (enableDecoderFallback) {
//允许获取所有解码器
availableCodecInfos.addAll(allAvailableCodecInfos);
} else if (!allAvailableCodecInfos.isEmpty()) {
//获取解码器列表中的第一个解码器availableCodecInfos.add(allAvailableCodecInfos.get(0));
}
} catch (DecoderQueryException e) {
}
}
MediaCodecInfo codecInfo = availableCodecInfos.peekFirst();
try {
//初始化解码器
initCodec(codecInfo, crypto);
} catch (Exception e) {
}
视频渲染器会调用到
MediaCodecVideoRenderer.getDecoderInfos() 代码如下:
protected List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector, Format format, boolean requiresSecureDecoder)
throws DecoderQueryException {
return MediaCodecUtil.getDecoderInfosSortedByFormatSupport(
getDecoderInfos(mediaCodecSelector, format, requiresSecureDecoder, tunneling), format);
}
最终会调用到MediaCodecUtil::getDecoderInfos(), 代码如下:
private static List<MediaCodecInfo> getDecoderInfos(
MediaCodecSelector mediaCodecSelector,
Format format,
boolean requiresSecureDecoder,
boolean requiresTunnelingDecoder)
throws DecoderQueryException {
List<MediaCodecInfo> decoderInfos =
//通过mediaCodecSelector 获取解码器信息列表, 实际调用 MediaCodecUtil::getDecoderInfos, mediaCodecSelector 在MediaCodecVideoRenderer 进行构建初始化
mediaCodecSelector.getDecoderInfos(
mimeType, requiresSecureDecoder, requiresTunnelingDecoder);
return ImmutableList.<MediaCodecInfo>builder()
.addAll(decoderInfos)
.addAll(alternativeDecoderInfos)
.build();
}
//返回解码器列表
public static synchronized List<MediaCodecInfo> getDecoderInfos(
String mimeType, boolean secure, boolean tunneling) throws DecoderQueryException {
CodecKey key = new CodecKey(mimeType, secure, tunneling);
@Nullable List<MediaCodecInfo> cachedDecoderInfos = decoderInfosCache.get(key);
if (cachedDecoderInfos != null) {
return cachedDecoderInfos;
}
MediaCodecListCompat mediaCodecList =
Util.SDK_INT >= 21
? new MediaCodecListCompatV21(secure, tunneling)
: new MediaCodecListCompatV16();
// 获取解码器列表
ArrayList<MediaCodecInfo> decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
if (secure && decoderInfos.isEmpty() && 21 <= Util.SDK_INT && Util.SDK_INT <= 23) {
mediaCodecList = new MediaCodecListCompatV16();
decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
ImmutableList<MediaCodecInfo> immutableDecoderInfos = ImmutableList.copyOf(decoderInfos);
decoderInfosCache.put(key, immutableDecoderInfos);
return immutableDecoderInfos;
}
从上面的初始化流程代码以及下面的断点分析我们可以看到,android系统会提供硬件厂商的解码器(c2.mtk.avc.decoder) 和系统自身默认解码器(c2.android.avc.decoder)其中c2.mtk.avc.decoder 是硬解码器,解码效率较高,c2.android.avc.decoder 是软解码器,效率相对硬解码器较低, 从源码断点分析,Exoplayer在解码器初始化的时候,并不会强制使用硬解码器,而是随机选择软解和硬解码器
优化方案
为了更好的提高Exoplayer播放效率,需要将Exoplayer的解码器强制切换为硬解码器, 从上面的代码分析流程可以得出, 播放器解码获取列表是由mediaCodecSelector 获取,mediaCodecSelector调用getDecoderInfos() 获取解码列表,所以优化方案就变成 (1)如何创建新的mediaCodecSelector (2)重写 MediaCodecSelector::getDecoderInfos() 解码器获取逻辑,修改为只返回硬解码器列表
mediaCodecSelector 创建流程
分析Exoplayer源码我们可以得知,在DefaultRenderersFactory创建MediaCodecVideoRenderer 时会注入MediaCodecSelector, DefaultRenderersFactory 中mediaCodecSelector 有默认MediaCodecSelector 和手动注入MediaCodecSelector 两种方式,开发者可以手动注入自定义MediaCodecSelector
public class DefaultRenderersFactory implements RenderersFactory {
private MediaCodecSelector mediaCodecSelector;
//外部注入自定义MediaCodecSelector
public DefaultRenderersFactory setMediaCodecSelector(MediaCodecSelector mediaCodecSelector) {
this.mediaCodecSelector = mediaCodecSelector;
return this;
}
protected void buildVideoRenderers(
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
boolean enableDecoderFallback,
Handler eventHandler,
VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs,
ArrayList<Renderer> out) {
//mediaCodecSelector 作为参数构建 MediaCodecVideoRenderer
MediaCodecVideoRenderer videoRenderer =
new MediaCodecVideoRenderer(
context,
getCodecAdapterFactory(),
mediaCodecSelector,
allowedVideoJoiningTimeMs,
enableDecoderFallback,
eventHandler,
eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY);
out.add(videoRenderer);
}
}
重写MediaCodecSelector逻辑
由上面的分析可知MediaCodecSelector::getDecoderInfos() 的默认逻辑是由MediaCodecUtil::getDecoderInfos()实现,所以我们只需要拷贝MediaCodecUtil,调整getDecoderInfos()返回结果逻辑即可
代码如下:
public static synchronized List<MediaCodecInfo> getDecoderInfos(
String mimeType, boolean secure, boolean tunneling) throws com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException {
MediaCodecListCompat mediaCodecList =
Util.SDK_INT >= 21
? new MediaCodecListCompatV21(secure, tunneling)
: new MediaCodecListCompatV16();
ArrayList<MediaCodecInfo> decoderInfos = getDecoderInfosInternal(key, mediaCodecList);
if (decoderInfos != null && decoderInfos.size() > 1) {
for (int i = 0; i < decoderInfos.size(); i++) {
//提出Android默认软解码器c2.android.avc.decoder
if (decoderInfos.get(i).name.contains("c2.android.avc.decoder")) {
decoderInfos.remove(i);
}
}
}
ImmutableList<MediaCodecInfo> immutableDecoderInfos = ImmutableList.copyOf(decoderInfos);
return immutableDecoderInfos;
}