赞
踩
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是如何保证这些数据稳定高效的读写。
先预习下上篇的整体结构,本篇主要分析左半部分的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接口,对外提供sampleMetadata、format 函数使得调用者可以输入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); } }
Metadata输入主要分3部分:
下面看下sampleData部分
@Override
public final void sampleData(
ParsableByteArray data, int length, @SampleDataPart int sampleDataPart) {
sampleDataQueue.sampleData(data, length);
}
没了就这么多。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。