ExoPlayer架构详解与源码分析(16)——LoadControl

1,127 阅读7分钟

系列文章目录


前言

本篇来了解下四大组件中的LoadControl,这个组件和其他几个比起来算是比较小巧的了,简略过下

LoadControl

播放前媒体数据加载控制

  • onPrepared 当准备完成时被播放器调用
  • onTracksSelected 选择轨道时调用
  • onStopped 播放停止时调用
  • onReleased 播放释放时调用
  • getAllocator 返回一个Allocator,用于媒体数据的缓存管理
  • getBackBufferDurationUs 返回在当前播放位置之前保留在缓冲区中的媒体持续时间,用以进行快速向后搜索。 注意:如果retainBackBufferFromKeyframe()=false,则只有当后台缓冲区包含查找位置之前的关键帧时,后台缓冲区中的查找才会很快。
  • retainBackBufferFromKeyframe 返回是否应保留当前播放位置减去getBackBufferDurationUs()之前的关键帧中的媒体,而不是保留该位置之前或该位置处的任何样本。返回 true 将导致后台缓冲区大小取决于正在播放的媒体中关键帧的间距。不建议返回 true
  • shouldContinueLoading 由播放器调用以确定是否应继续加载源。如果此方法返回 true,则将继续加载最近一次onTracksSelected调用中标识的MediaPeriod 。
  • shouldStartPlayback 播放器在加载源、尚未开始播放且具有开始播放所需的最小数据量时调用。返回的值决定是否真正开始播放。负载控制可以选择返回false直到满足某些条件(例如,缓冲了一定量的媒体)。

可以看出LoadControl在播放器的关键时候进行了调用,播放前决定是否可以播放,加载过程中决定是否继续加载数据,下面看它的默认实现类DefaultLoadControl

DefaultLoadControl

  private final DefaultAllocator allocator;//缓存分配器,默认初始化出了一个单个Allocation块大小为64KB的DefaultAllocator 
  private final long minBufferUs;//最小缓存的时长,默认值是50秒
  private final long maxBufferUs;//最大缓存的时长,默认值是50秒
  private final long bufferForPlaybackUs;//开始播放前至少要缓存的时长,默认值2.5秒
  private final long bufferForPlaybackAfterRebufferUs;//重新缓存时至少需要的缓冲时长,默认值5秒
  private final int targetBufferBytesOverwrite;//初始设置的缓存大小,默认值LENGTH_UNSET=-1
  private final boolean prioritizeTimeOverSizeThresholds;//缓存时间的优先级是否高于缓存大小,默认值fasle,就是默认优先使用缓存的大小
  private final long backBufferDurationUs;//已缓存数据的时长,默认值0秒
  private final boolean retainBackBufferFromKeyframe;//缓冲区市场从前一个关键帧保留,默认值false

  private int targetBufferBytes;//缓存大小,默认值LENGTH_UNSET=-1
  private boolean isLoading;//是否继续加载
  @Override
  public void onPrepared() {
    reset(false);//完成后重置数据
  }
  
  private void reset(boolean resetAllocator) {
    targetBufferBytes =//将缓存大小设置为初始值,如果初始值未设置,则设置为默认最小值50秒
        targetBufferBytesOverwrite == C.LENGTH_UNSET
            ? DEFAULT_MIN_BUFFER_SIZE
            : targetBufferBytesOverwrite;
    isLoading = false;
    if (resetAllocator) {
      allocator.reset();//重置缓存,播放停止或者释放时才需要
    }
  }
  
  @Override
  public void onTracksSelected(
      Timeline timeline,
      MediaPeriodId mediaPeriodId,
      Renderer[] renderers,
      TrackGroupArray trackGroups,
      ExoTrackSelection[] trackSelections) {
    targetBufferBytes =//轨道选择时,在MediaPeriod选择完轨道后调用,此时播放媒体的相关数据已经可以获取
        targetBufferBytesOverwrite == C.LENGTH_UNSET
            ? calculateTargetBufferBytes(renderers, trackSelections)//可以用这些数据获取缓存大小
            : targetBufferBytesOverwrite;
    //设置单个allocator总大小
    allocator.setTargetBufferSize(targetBufferBytes);
  }
  protected int calculateTargetBufferBytes(
      Renderer[] renderers, ExoTrackSelection[] trackSelectionArray) {
    int targetBufferSize = 0;
    for (int i = 0; i < renderers.length; i++) {
      if (trackSelectionArray[i] != null) {
        //计算所有轨道数据缓存的总大小
        targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType());
      }
    }
    //DEFAULT_MIN_BUFFER_SIZE= 200 * C.DEFAULT_BUFFER_SEGMENT_SIZE也就是至少有200个Allocation,每个Allocation包含64KB的block,总大小就是12.5MB
    return max(DEFAULT_MIN_BUFFER_SIZE, targetBufferSize);
  }
  //根据不同轨道类型获取默认大小
  private static int getDefaultBufferSize(@C.TrackType int trackType) {
    switch (trackType) {
      case C.TRACK_TYPE_DEFAULT:
        //DEFAULT_VIDEO_BUFFER_SIZE + DEFAULT_AUDIO_BUFFER_SIZE + DEFAULT_TEXT_BUFFER_SIZE
        return DEFAULT_MUXED_BUFFER_SIZE;
      case C.TRACK_TYPE_AUDIO:
        //音轨200个Allocation,200*64KB=12.5M
        return DEFAULT_AUDIO_BUFFER_SIZE;
      case C.TRACK_TYPE_VIDEO:
        //视频轨2000个Allocation,2000*64KB=125M
        return DEFAULT_VIDEO_BUFFER_SIZE;
      case C.TRACK_TYPE_TEXT:
        //字幕频轨2个Allocation,2*64kb=128KB
        return DEFAULT_TEXT_BUFFER_SIZE;
      case C.TRACK_TYPE_METADATA:
         //metadata轨2个Allocation,2*64kb=128KB
        return DEFAULT_METADATA_BUFFER_SIZE;
      case C.TRACK_TYPE_CAMERA_MOTION:
         //camera motion 2个Allocation,2*64kb=128KB
        return DEFAULT_CAMERA_MOTION_BUFFER_SIZE;
      case C.TRACK_TYPE_IMAGE:
        //image 2个Allocation,2*64kb=128KB
        return DEFAULT_IMAGE_BUFFER_SIZE;
      case C.TRACK_TYPE_NONE:
        return 0;
      case C.TRACK_TYPE_UNKNOWN:
      default:
        throw new IllegalArgumentException();
    }
  }
  
  @Override
  //判断是否继续加载数据
  public boolean shouldContinueLoading(
      long playbackPositionUs, long bufferedDurationUs, float playbackSpeed) {
    //当前已经加载的数据是否已经达到targetBufferBytes
    boolean targetBufferSizeReached = allocator.getTotalBytesAllocated() >= targetBufferBytes;
    long minBufferUs = this.minBufferUs;
    if (playbackSpeed > 1) {
      // 播放速度如果大于1,则minBufferUs 也要相应的增大以保证有足够的缓存供播放
      long mediaDurationMinBufferUs =
          Util.getMediaDurationForPlayoutDuration(minBufferUs, playbackSpeed);
      minBufferUs = min(mediaDurationMinBufferUs, maxBufferUs);
    }
    // minBufferUs不能太小
    minBufferUs = max(minBufferUs, 500_000);
    if (bufferedDurationUs < minBufferUs) {//缓存的时长不足minBufferUs了
      //时长优先模式没有达到targetBufferSizeReached
      isLoading = prioritizeTimeOverSizeThresholds || !targetBufferSizeReached;
      if (!isLoading && bufferedDurationUs < 500_000) {
        //不需要继续加载,但是提示用户当前已经targetBufferSizeReached,但是总时长不足minBufferUs最小值,这样容易卡顿,可以需要修改LoadControl配置
        Log.w(
            "DefaultLoadControl",
            "Target buffer size reached with less than 500ms of buffered media data.");
      }
    } else if (bufferedDurationUs >= maxBufferUs || targetBufferSizeReached) {
      //缓存的时长超过maxBufferUs 或者已经targetBufferSizeReached,不能继续加载了
      isLoading = false;
    } // 其他情况不改变isLoading值
    return isLoading;
  }
  
  @Override
  //判断当前缓存是否满足播放需求
  public boolean shouldStartPlayback(
      Timeline timeline,
      MediaPeriodId mediaPeriodId,
      long bufferedDurationUs,
      float playbackSpeed,
      boolean rebuffering,
      long targetLiveOffsetUs) {
    //使用播放速度等量计算当前已缓存的bufferedDurationUs 时长
    bufferedDurationUs = Util.getPlayoutDurationForMediaDuration(bufferedDurationUs, playbackSpeed);
    //获取最小可播放时长
    long minBufferDurationUs = rebuffering ? bufferForPlaybackAfterRebufferUs : bufferForPlaybackUs;
    if (targetLiveOffsetUs != C.TIME_UNSET) {
      minBufferDurationUs = min(targetLiveOffsetUs / 2, minBufferDurationUs);
    }
    return minBufferDurationUs <= 0
        || bufferedDurationUs >= minBufferDurationUs//已经大于最小可播放时常,返回true
        || (!prioritizeTimeOverSizeThresholds//缓存大小优先模式需要已缓存的总大小大于等于targetBufferBytes
            && allocator.getTotalBytesAllocated() >= targetBufferBytes);
  }

下我们来整理下,继续播放的条件主要分3种情况讨论

  1. bufferedDurationUs < minBufferUs ,当前缓存时长小于最小缓存时长,这里分2种情况

    • 时长优先模式则直接继续加载
    • 大小优先模式,如果已经缓存到targetBufferBytes,则停止加载,否则继续加载

    可见此时,是否加载数据需要根据prioritizeTimeOverSizeThresholds决定通过时长还是缓存大小来判断

  2. minBufferUs <= bufferedDurationUs < maxBufferUs ,当前缓存时长介于最小和最大之间,此时如果已经缓存到targetBufferBytes,则停止加载,否则保持现状。

  3. bufferedDurationUs >=maxBufferUs ,当前缓存时长大于等于最大缓存时长,此时无论是否已经存到targetBufferBytes都停止加载

prioritizeTimeOverSizeThresholds只有在缓存时长小于最小缓存时长才会起作用。

总结

LoadControl到这里就讲完了,可以通过设置其中的几个参数来控制ExoPlayer缓存的大小,也可以通过自己实现LoadControl,来决定什么时候继续加载数据。但是这里的缓存指的是内存中的数据,这些参数并不是随意设置的,不然随时OOM。


版权声明 ©

本文为作者山雨楼原创文章

转载请注明出处

原创不易,觉得有用的话,收藏转发点赞支持