ExoPlayer架构详解与源码分析(7)——SampleQueue

系列文章目录

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



前言

ProgressiveMediaPeriod中的SampleQueue部分相对其他部分,结构相对完整独立,没有像加载媒体那部分拆分出很多其他的概念,所以优先了解下SampleQueue。本篇主要解答媒体数据是如何在播放器内部缓存的,以及ExoPlayer是如何保证这些数据稳定高效的读写。

ProgressiveMediaPeriod

先预习下上篇的整体结构,本篇主要分析左半部分的SampleQueue:
在这里插入图片描述

SampleQueue

这是一个保存Sample的队列。MediaoPeriod向外提供的SampleStream其实就是从SampleQueue中读取的数据,一个SampleQueue就对应一个SampleStream。
在这里插入图片描述

SampleQueue主要有3大功能:

  • 管理 通过内部的一个环形Info数组(包含offsets数组、sizes数等sampleData数据)管理SampleDataQueue和SharedSampleMetadata这2个数据源。SampleQueue实际的数据其实是保存在SampleDataQueue和SharedSampleMetadata中的,数据的管理实现在SampleQueue里。
    这部分可以从SampleQueue初始化部分源码看出来:

      private final SampleDataQueue sampleDataQueue;//用于播放的数据
      private final SampleExtrasHolder extrasHolder;
      private final SpannedData<SharedSampleMetadata> sharedSampleMetadata;//Meta数据
      private int capacity;//Info数组的总长度
      private long[] offsets;//每段SampleData的数据偏移量
      private int[] sizes;//每段SampleData的数据大小
      private int[] flags;//每段SampleData flags 数据
      private long[] timesUs;//每段SampleData 时间戳
      private int length;//有效的(没有被释放且已分配的数据)Info数组数据的长度
      private int absoluteFirstIndex;//绝对的开始位置,指向数据段的开始位置,+readPosition就是当前读取的绝对位置
      private int relativeFirstIndex;//一个在Info数据上循环的相对位置
      private int readPosition;//当前的读取位置,这个值是相对relativeFirstIndex的位置偏移量
      protected SampleQueue(
          Allocator allocator,
          @Nullable DrmSessionManager drmSessionManager,
          @Nullable DrmSessionEventListener.EventDispatcher drmEventDispatcher) {
         
          ...
        sampleDataQueue = new SampleDataQueue(allocator);//内存分配器供SampleDataQueue使用
        extrasHolder = new SampleExtrasHolder();
        capacity = SAMPLE_CAPACITY_INCREMENT;//默认的分段属是1000
    
        sourceIds = new long[capacity];
        offsets = new long[capacity];
        timesUs = new long[capacity];
        flags = new int[capacity];
        sizes = new int[capacity];
        
        sharedSampleMetadata =
            new SpannedData<>(/* removeCallback= */ metadata -> metadata.drmSessionReference.release());
            ...
      }
    

    上面主要是初始化出一个SampleDataQueue和一个sharedSampleMetadata数据集,然后初始化出一个1000个块的Info数组,用于管理这2块数据。这里将offsets、timesUs、sourceIds 、flags、sizes 几个数组统称为 Info数组,因为这里面共同保存着每个Sample的信息。

  • 输入 同时SampleQueue实现了TrackOutput接口,对外提供sampleMetadataformat 函数使得调用者可以输入Meta信息,sampleData函数可以输入播放数据。这里的输入调用者主要是后面要说的ProgressiveMediaPeriod另一部分。
    下面分析下源码数据是如何输入的:

    //输入Metadata
      @Override
      public void sampleMetadata(
          long timeUs,//与当前数据关联的媒体时间戳
          @C.BufferFlags int flags,//是否关键帧
          int size,//样本数据大小
          int offset,//块间偏移量,距离上一次已经SmapleMeta的SampleData的偏移量,我们知道媒体文件中用于播放数据不一定是连续的,其中可能包含一些其他数据,这些数据可以看成是之间的偏移量
          @Nullable CryptoData cryptoData) {
         
        if (upstreamFormatAdjustmentRequired) {
         
          format(Assertions.checkStateNotNull(unadjustedUpstreamFormat));
        }
    
        boolean isKeyframe = (flags & C.BUFFER_FLAG_KEY_FRAME) != 0;
        if (upstreamKeyframeRequired) {
         //从关键帧开始Sample
          if (!isKeyframe) {
         
            return;
          }
          upstreamKeyframeRequired = false;
        }
    
        timeUs += sampleOffsetUs;
        if (upstreamAllSamplesAreSyncSamples) {
         
          if (timeUs < startTimeUs) {
         
            // 如果所有轨道都是同步的,那么在当前Smaple点之前的时间数据就可以丢弃了
            return;
          }
          if ((flags & C.BUFFER_FLAG_KEY_FRAME) == 0) {
         
            if (!loggedUnexpectedNonSyncSample) {
         
              Log.w(TAG, "Overriding unexpected non-sync sample for format: " + upstreamFormat);
              loggedUnexpectedNonSyncSample = true;
            }
            flags |= C.BUFFER_FLAG_KEY_FRAME;//保证设置为关键帧
          }
        }
        if (pendingSplice) {
         //判断是否是拼接数据,如HLS切换流的时候就会用到
          if (!isKeyframe || !attemptSplice(timeUs)) {
         
            return;
          }
          pendingSplice = false;
        }
    
         //当前Info的偏移量=数据总长度-样本数据长度-块间偏移量
        long absoluteOffset = sampleDataQueue.getTotalBytesWritten() - size - offset;
        commitSample(timeUs, flags, absoluteOffset, size, cryptoData);
      }
    
    
    private synchronized void commitSample(
          long timeUs,
          @C.BufferFlags int sampleFlags,
          long offset,
          int size,
          @Nullable CryptoData cryptoData) {
         
        if (length > 0) 
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值