前言
第一篇介绍了数据的加载流程,但是没有分析具体 数据如何解封装后,拿到需要的数据包,这里稍微介绍一下,本篇大部分代码都集中在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,然后分析内容,具体不在详细介绍,后续补充。
后记
解封装模块比较简单,核心问题是各种封装格式的编码,以及规定,这个各种文档比较充分,这里就不在详细介绍。