背景

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在解码器初始化的时候,并不会强制使用硬解码器,而是随机选择软解和硬解码器

Android 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;
    }