当前位置:   article > 正文

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

exoplayer

系列文章目录

系列文章目录

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



前言

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());
            ...
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    上面主要是初始化出一个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) {
          // 保证最后一个的end位置要小于等于下一个的开始位置
          int previousSampleRelativeIndex = getRelativeIndex(length - 1);
          checkArgument(
              offsets[previousSampleRelativeIndex] + sizes[previousSampleRelativeIndex] <= offset);
        }
    
        isLastSampleQueued = (sampleFlags & C.BUFFER_FLAG_LAST_SAMPLE) != 0;
        largestQueuedTimestampUs = max(largestQueuedTimestampUs, timeUs);
    
        int relativeEndIndex = getRelativeIndex(length);//获取Info里的下一个位置索引
        timesUs[relativeEndIndex] = timeUs;//开始赋值
        offsets[relativeEndIndex] = offset;
        sizes[relativeEndIndex] = size;
        flags[relativeEndIndex] = sampleFlags;
        cryptoDatas[relativeEndIndex] = cryptoData;
        sourceIds[relativeEndIndex] = upstreamSourceId;
    
        if (sharedSampleMetadata.isEmpty()
            || !sharedSampleMetadata.getEndValue().format.equals(upstreamFormat)) {
            //开始写入Metadata
          sharedSampleMetadata.appendSpan(
              getWriteIndex(),
              new SharedSampleMetadata(checkNotNull(upstreamFormat), drmSessionReference));
        }
    
        length++;//有效长度++
        if (length == capacity) {//如果写入数据已经超过Info的最大长度
          // Increase the capacity.
          int newCapacity = capacity + SAMPLE_CAPACITY_INCREMENT;//则将Info数组长度扩展至2倍
          long[] newSourceIds = new long[newCapacity];
          long[] newOffsets = new long[newCapacity];
          long[] newTimesUs = new long[newCapacity];
          int[] newFlags = new int[newCapacity];
          int[] newSizes = new int[newCapacity];
          CryptoData[] newCryptoDatas = new CryptoData[newCapacity];
          //将旧的数据,移入新的数组,将相对开始位置作为新数组的第一个位置
          int beforeWrap = capacity - relativeFirstIndex;
          System.arraycopy(offsets, relativeFirstIndex, newOffsets, 0, beforeWrap);
          System.arraycopy(timesUs, relativeFirstIndex, newTimesUs, 0, beforeWrap);
          System.arraycopy(flags, relativeFirstIndex, newFlags, 0, beforeWrap);
          System.arraycopy(sizes, relativeFirstIndex, newSizes, 0, beforeWrap);
          System.arraycopy(cryptoDatas, relativeFirstIndex, newCryptoDatas, 0, beforeWrap);
          System.arraycopy(sourceIds, relativeFirstIndex, newSourceIds, 0, beforeWrap);
          int afterWrap = relativeFirstIndex;
          System.arraycopy(offsets, 0, newOffsets, beforeWrap, afterWrap);
          System.arraycopy(timesUs, 0, newTimesUs, beforeWrap, afterWrap);
          System.arraycopy(flags, 0, newFlags, beforeWrap, afterWrap);
          System.arraycopy(sizes, 0, newSizes, beforeWrap, afterWrap);
          System.arraycopy(cryptoDatas, 0, newCryptoDatas, beforeWrap, afterWrap);
          System.arraycopy(sourceIds, 0, newSourceIds, beforeWrap, afterWrap);
          offsets = newOffsets;
          timesUs = newTimesUs;
          flags = newFlags;
          sizes = newSizes;
          cryptoDatas = newCryptoDatas;
          sourceIds = newSourceIds;
          relativeFirstIndex = 0;
          capacity = newCapacity;
        }
      }
     
      //获取当前Info数组的相对位置,传入相对第一个位置的偏移量
      private int getRelativeIndex(int offset) {
        int relativeIndex = relativeFirstIndex + offset;
        return relativeIndex < capacity ? relativeIndex : relativeIndex - capacity;//环形指针
      }
      //获取当前写入MetaData的绝对位置
      public final int getWriteIndex() {
        return absoluteFirstIndex + length;//等于当前绝开始位置+有效的长度
      }  
      //输入Format
      @Override
      public final void format(Format format) {
        Format adjustedUpstreamFormat = getAdjustedUpstreamFormat(format);
        upstreamFormatAdjustmentRequired = false;
        unadjustedUpstreamFormat = format;
        boolean upstreamFormatChanged = setUpstreamFormat(adjustedUpstreamFormat);
        if (upstreamFormatChangeListener != null && upstreamFormatChanged) {
          upstreamFormatChangeListener.onUpstreamFormatChanged(adjustedUpstreamFormat);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    Metadata输入主要分3部分:

    1. 确保当前为关键帧。
    2. 获取Info环形数组的下一个索引,将当前Sampledata的数据保存到Info数组并记录Meta信息。
    3. 确保Info数组足够大可以容纳足够多的数据。

    下面看下sampleData部分

      @Override
      public final void sampleData(
          ParsableByteArray data, int length, @SampleDataPart int sampleDataPart) {
        sampleDataQueue.sampleData(data, length);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    没了就这么多。

    声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/295853
推荐阅读
相关标签