ExoPlayer 漫谈之解码器复用

2021-01-12 10:20:13 浏览数 (1)

播放器性能的极致优化,就是要发现播放流程中一点一滴的耗时,然后分析这些耗时,并提出有效的方式解决这些耗时.了解过ExoPlayer播放器的同学们都知道ExoPlayer的解码是依赖Android系统提供的原生的解码模块,即MediaCodec来实行视频和音频解码的.

基于我们对MediaCodec使用方式和原理的熟悉,我们在使用MediaCodec的时候,首先要初始化,codec configure, codec start等流程,经过这些流程,codec分配内存来存储input buffer 和 output buffer

这块耗时对视频播放而言是无法避免的,不管是网络视频还是本地视频, codec 初始化--->configure ---> start ---> 上屏这部分的耗时都是无法避免的.

我们抓一下一个1080P的本地视频的systrace, 显示如下:

可以发现: video decoder和audio decoder创建/configure/start耗时了170ms, 直接占用了绝大部分耗时, 确实是耗时的大头.

目前ExoPlayer最新的版本是2.12.2, 而且保持着凉州一个小版本, 两个月一个大版本的更新节奏, google的工作效率还是比较高的.

针对codec 复用, 在2.10版本之前和2.10之后的版本, 使用方式和原理还是有所不同的.

ExoPlayer 2.10版本之前的codec复用

disabled状态, 在此状态下, 渲染器没有要播放的媒体流并且不保存解码器实例。 enabled状态, 在此状态下,渲染器可以播放媒体流,并在可能的情况下(例如,在读取流的格式之后)获取解码器实例。 started状态, 在此状态下,渲染器使用持有的解码器实例来播放视频

在2.10之前的版本中,只要视频渲染器保持在启用和启动状态,ExoPlayer就会尽可能重复使用视频解码器。特别是,这意味着从播放列表中的一种媒体过渡到另一种媒体时,视频解码器将被重用。在渲染列表过渡到禁用状态时,音频解码器不会在此类播放列表过渡中重用,视频或音频解码器也不会重用。重要的是,重新准备播放器以播放其他MediaSource会导致这种类型的状态转换,因此在这种情况下不会重复使用解码器。

一句话, 想复用codec实例,不能调用stop方法,不能将codec置为disabled状态

ExoPlayer 2.10版本之后的codec复用

2.10版本可以保证在播放新的视频源的时候复用播放器解码器资源.即使当前的渲染器持有的解码器实例已经被置为disabled状态

复用解码器实例可以减少多个视频源之间切换导致的丢帧的问题,更加可以节省时间.下面是复用解码器实例之后的systrace耗时:

解码器实例复用要求

解码器实例可以复用,肯定是有条件的,例如上一个视频播放的是VP9的视频,下一个视频播放的是H265的视频,那么解码器实例肯定不能复用.

具体是什么条件了,我们还是查看源码确认一下:

MediaCodecRenderer.java有两个子类, MediaCodecVideoRenderer.java和MediaCodecAudioRenderer.java

MediaCodecVideoRenderer.java

代码语言:txt复制
  @Override
  protected DecoderReuseEvaluation canReuseCodec(
      MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
    DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);

    @DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
    if (newFormat.width > codecMaxValues.width || newFormat.height > codecMaxValues.height) {
      discardReasons |= DISCARD_REASON_VIDEO_MAX_RESOLUTION_EXCEEDED;
    }
    if (getMaxInputSize(codecInfo, newFormat) > codecMaxValues.inputSize) {
      discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
    }

    return new DecoderReuseEvaluation(
        codecInfo.name,
        oldFormat,
        newFormat,
        discardReasons != 0 ? REUSE_RESULT_NO : evaluation.result,
        discardReasons);
  }

首先当前的视频的最大分辨率不能超过codec支持的最大解码分辨率 其次视频的输入size不能超过codec的input buffer size

MediaCodecAudioRenderer.java

代码语言:txt复制
  @Override
  protected DecoderReuseEvaluation canReuseCodec(
      MediaCodecInfo codecInfo, Format oldFormat, Format newFormat) {
    DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);

    @DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
    if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) {
      discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
    }

    return new DecoderReuseEvaluation(
        codecInfo.name,
        oldFormat,
        newFormat,
        discardReasons != 0 ? REUSE_RESULT_NO : evaluation.result,
        discardReasons);
  }

音频的输入size不能超过codec的input buffer size

我们如何复用解码器

保证你播放的视频的格式是相同的,例如短视频信息流都是H264/AAC编码格式 如果使用ExoPlayer播放器,最好使用同一个ExoPlayer实例,因为codec实例是封装在ExoPlayer实例中的 当重新prepare新的视频时,在调用prepare之前不能调用stop函数,因为stop函数会时方解码器实例 如果你要调用stop函数的情况下还想保持解码器实例不被释放,你需要调用setForegroundMode(...)函数,这个函数的使用方式是比较复杂的,不能误用

https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/ExoPlayer.html#setForegroundMode-boolean-

打开了ForegroundMode模式,意味着解码器资源在调用stop的时候都不会被释放,当然这是在复用解码器资源的场景下使用的,使用的时候要小心一些,在最终的时候还是要记得释放资源的,不然会出现问题.

0 人点赞