Exoplayer源码解析3之解封装器解析
Posted 白嫩豆腐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Exoplayer源码解析3之解封装器解析相关的知识,希望对你有一定的参考价值。
前言
第一篇介绍了数据的加载流程,但是没有分析具体 数据如何解封装后,拿到需要的数据包,这里稍微介绍一下,本篇大部分代码都集中在extractor模块当中。
正文
根本就是Extractor接口,以及配合使用的ExtractorInput和ExtractorOutput。对外提供的接口则是通过DefaultExtractorsFactory和DefaultExtractorInput。这里代码结构非常简单,不在详细介绍.
一个典型的的extral初始化代码如下:
ExtractorInput extractorInput = new DefaultExtractorInput(dataReader, position, length);
this.extractorInput = extractorInput;
if (extractor != null)
return;
Extractor[] extractors = extractorsFactory.createExtractors(uri, responseHeaders);
if (extractors.length == 1)
this.extractor = extractors[0];
else
for (Extractor extractor : extractors)
try
if (extractor.sniff(extractorInput))
this.extractor = extractor;
break;
catch (EOFException e)
// Do nothing.
finally
Assertions.checkState(this.extractor != null || extractorInput.getPosition() == position);
extractorInput.resetPeekPosition();
if (extractor == null)
throw new UnrecognizedInputFormatException(
"None of the available extractors ("
+ Util.getCommaDelimitedSimpleClassNames(extractors)
+ ") could read the stream.",
Assertions.checkNotNull(uri));
extractor.init(output);
先拿到input和output。然后根据特定的格式加载解封装器,最终通过sniff接口测试次文件是否可以通过我们解封装器的解析,如果资源文件无法通过后缀或者header拿到格式,则吧所有解封装器列表返还回来,挨个测试,output真实类其实就是ProgressiveMediaPeriod。核心接口就是track。返回各个多媒体轨道的容器。代码如下
@Override
public TrackOutput track(int id, int type)
return prepareTrackOutput(new TrackId(id, /* isIcyTrack= */ false));
private TrackOutput prepareTrackOutput(TrackId id)
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++)
if (id.equals(sampleQueueTrackIds[i]))
return sampleQueues[i];
SampleQueue trackOutput =
SampleQueue.createWithDrm(
allocator,
/* playbackLooper= */ handler.getLooper(),
drmSessionManager,
drmEventDispatcher);
trackOutput.setUpstreamFormatChangeListener(this);
@NullableType
TrackId[] sampleQueueTrackIds = Arrays.copyOf(this.sampleQueueTrackIds, trackCount + 1);
sampleQueueTrackIds[trackCount] = id;
this.sampleQueueTrackIds = Util.castNonNullTypeArray(sampleQueueTrackIds);
@NullableType SampleQueue[] sampleQueues = Arrays.copyOf(this.sampleQueues, trackCount + 1);
sampleQueues[trackCount] = trackOutput;
this.sampleQueues = Util.castNonNullTypeArray(sampleQueues);
return trackOutput;
这代码其实就是比较简单,把数据存在sampleQueues这个数组中,然后供Renderer取。而SampleQueue 其实也不算复杂,主要提供一个read接口,返回SampleDataQueue中的元素,而SampleDataQueue是一个多媒体数据的队列,方便管理缓存。这些代码都比较简单,不在详细介绍。
下面回到DefaultExtractorInput,这是一个核心是一个Filedatasource。其实主要就是实现了read的接口如下:
@Override
public int read(byte[] buffer, int offset, int length) throws FileDataSourceException
if (length == 0)
return 0;
else if (bytesRemaining == 0)
return C.RESULT_END_OF_INPUT;
else
int bytesRead;
try
bytesRead = castNonNull(file).read(buffer, offset, (int) min(bytesRemaining, length));
catch (IOException e)
throw new FileDataSourceException(e, PlaybackException.ERROR_CODE_IO_UNSPECIFIED);
if (bytesRead > 0)
bytesRemaining -= bytesRead;
bytesTransferred(bytesRead);
return bytesRead;
而DefaultExtractorInput的核心接口则同样是read,不过额外提供一些方便一些的接口,比如skip等,方便使用(内置offsite)。
而真的解封装则是通过read实现的,
我们只介绍一下mkv的解封装模块,只是简要介绍,
检测机制则比较简单:
public boolean sniff(ExtractorInput input) throws IOException
long inputLength = input.getLength();
//最多寻找1024个字节,其实就是找mkv的标志位,就是0x1A45DFA3
int bytesToSearch =
(int)
(inputLength == C.LENGTH_UNSET || inputLength > SEARCH_LENGTH
? SEARCH_LENGTH
: inputLength);
// Find four bytes equal to ID_EBML near the start of the input.
input.peekFully(scratch.getData(), 0, 4);
long tag = scratch.readUnsignedInt();
peekLength = 4;
while (tag != ID_EBML)
if (++peekLength == bytesToSearch)
return false;
input.peekFully(scratch.getData(), 0, 1);
tag = (tag << 8) & 0xFFFFFF00;
tag |= scratch.getData()[0] & 0xFF;
// Read the size of the EBML header and make sure it is within the stream.这里是确认mkv是否有头
long headerSize = readUint(input);
long headerStart = peekLength;
if (headerSize == Long.MIN_VALUE
|| (inputLength != C.LENGTH_UNSET && headerStart + headerSize >= inputLength))
return false;
// Read the payload elements in the EBML header.跳过头,供后续步骤读取内容
while (peekLength < headerStart + headerSize)
long id = readUint(input);
if (id == Long.MIN_VALUE)
return false;
long size = readUint(input);
if (size < 0 || size > Integer.MAX_VALUE)
return false;
if (size != 0)
int sizeInt = (int) size;
input.advancePeekPosition(sizeInt);
peekLength += sizeInt;
return peekLength == headerStart + headerSize;
核心就是检测mkv标志位以及跳过mkv头信息。
真的读取则则需要解析EBML,然后分析内容,具体不在详细介绍,后续补充。
后记
解封装模块比较简单,核心问题是各种封装格式的编码,以及规定,这个各种文档比较充分,这里就不在详细介绍。
以上是关于Exoplayer源码解析3之解封装器解析的主要内容,如果未能解决你的问题,请参考以下文章