mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Refactor WebM extractor.
This commit is contained in:
parent
9e16dec2f8
commit
686ac2a6f5
6 changed files with 1122 additions and 822 deletions
|
|
@ -29,6 +29,7 @@ import com.google.android.exoplayer.chunk.MediaChunk;
|
||||||
import com.google.android.exoplayer.chunk.WebmMediaChunk;
|
import com.google.android.exoplayer.chunk.WebmMediaChunk;
|
||||||
import com.google.android.exoplayer.dash.mpd.Representation;
|
import com.google.android.exoplayer.dash.mpd.Representation;
|
||||||
import com.google.android.exoplayer.parser.SegmentIndex;
|
import com.google.android.exoplayer.parser.SegmentIndex;
|
||||||
|
import com.google.android.exoplayer.parser.webm.DefaultWebmExtractor;
|
||||||
import com.google.android.exoplayer.parser.webm.WebmExtractor;
|
import com.google.android.exoplayer.parser.webm.WebmExtractor;
|
||||||
import com.google.android.exoplayer.upstream.DataSource;
|
import com.google.android.exoplayer.upstream.DataSource;
|
||||||
import com.google.android.exoplayer.upstream.DataSpec;
|
import com.google.android.exoplayer.upstream.DataSpec;
|
||||||
|
|
@ -85,7 +86,7 @@ public class DashWebmChunkSource implements ChunkSource {
|
||||||
formats[i] = representations[i].format;
|
formats[i] = representations[i].format;
|
||||||
maxWidth = Math.max(formats[i].width, maxWidth);
|
maxWidth = Math.max(formats[i].width, maxWidth);
|
||||||
maxHeight = Math.max(formats[i].height, maxHeight);
|
maxHeight = Math.max(formats[i].height, maxHeight);
|
||||||
extractors.put(formats[i].id, new WebmExtractor());
|
extractors.put(formats[i].id, new DefaultWebmExtractor());
|
||||||
this.representations.put(formats[i].id, representations[i]);
|
this.representations.put(formats[i].id, representations[i]);
|
||||||
}
|
}
|
||||||
this.maxWidth = maxWidth;
|
this.maxWidth = maxWidth;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,558 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.parser.webm;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||||
|
import com.google.android.exoplayer.util.Assertions;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default version of a basic event-driven incremental EBML parser which needs an
|
||||||
|
* {@link EbmlEventHandler} to define IDs/types and react to events.
|
||||||
|
*
|
||||||
|
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
|
||||||
|
* It was originally designed for the Matroska container format. More information about EBML and
|
||||||
|
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
|
||||||
|
*/
|
||||||
|
/* package */ final class DefaultEbmlReader implements EbmlReader {
|
||||||
|
|
||||||
|
// State values used in variables state, elementIdState, elementContentSizeState, and
|
||||||
|
// varintBytesState.
|
||||||
|
private static final int STATE_BEGIN_READING = 0;
|
||||||
|
private static final int STATE_READ_CONTENTS = 1;
|
||||||
|
private static final int STATE_FINISHED_READING = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first byte of a variable-length integer (varint) will have one of these bit masks
|
||||||
|
* indicating the total length in bytes.
|
||||||
|
*
|
||||||
|
* <p>{@code 0x80} is a one-byte integer, {@code 0x40} is two bytes, and so on up to eight bytes.
|
||||||
|
*/
|
||||||
|
private static final int[] VARINT_LENGTH_MASKS = new int[] {
|
||||||
|
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final int MAX_INTEGER_ELEMENT_SIZE_BYTES = 8;
|
||||||
|
private static final int VALID_FLOAT32_ELEMENT_SIZE_BYTES = 4;
|
||||||
|
private static final int VALID_FLOAT64_ELEMENT_SIZE_BYTES = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scratch space to read in EBML varints, unsigned ints, and floats - each of which can be
|
||||||
|
* up to 8 bytes.
|
||||||
|
*/
|
||||||
|
private final byte[] tempByteArray = new byte[8];
|
||||||
|
private final Stack<MasterElement> masterElementsStack = new Stack<MasterElement>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current {@link EbmlEventHandler} which is queried for element types
|
||||||
|
* and informed of element events.
|
||||||
|
*/
|
||||||
|
private EbmlEventHandler eventHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overall state for the current element. Must be one of the {@code STATE_*} constants.
|
||||||
|
*/
|
||||||
|
private int state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Total bytes read since starting or the last {@link #reset()}.
|
||||||
|
*/
|
||||||
|
private long bytesRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The starting byte offset of the current element being parsed.
|
||||||
|
*/
|
||||||
|
private long elementOffset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the current element ID after {@link #elementIdState} is {@link #STATE_FINISHED_READING}.
|
||||||
|
*/
|
||||||
|
private int elementId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State for the ID of the current element. Must be one of the {@code STATE_*} constants.
|
||||||
|
*/
|
||||||
|
private int elementIdState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the current element content size after {@link #elementContentSizeState}
|
||||||
|
* is {@link #STATE_FINISHED_READING}.
|
||||||
|
*/
|
||||||
|
private long elementContentSize;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State for the content size of the current element.
|
||||||
|
* Must be one of the {@code STATE_*} constants.
|
||||||
|
*/
|
||||||
|
private int elementContentSizeState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State for the current variable-length integer (varint) being read into
|
||||||
|
* {@link #tempByteArray}. Must be one of the {@code STATE_*} constants.
|
||||||
|
*/
|
||||||
|
private int varintBytesState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length in bytes of the current variable-length integer (varint) being read into
|
||||||
|
* {@link #tempByteArray}.
|
||||||
|
*/
|
||||||
|
private int varintBytesLength;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Counts the number of bytes being contiguously read into either {@link #tempByteArray} or
|
||||||
|
* {@link #stringBytes}. Used to determine when all required bytes have been read across
|
||||||
|
* multiple calls.
|
||||||
|
*/
|
||||||
|
private int bytesState;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds string element bytes as they're being read in. Allocated after the element content
|
||||||
|
* size is known and released after calling {@link EbmlEventHandler#onStringElement(int, String)}.
|
||||||
|
*/
|
||||||
|
private byte[] stringBytes;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEventHandler(EbmlEventHandler eventHandler) {
|
||||||
|
this.eventHandler = eventHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(NonBlockingInputStream inputStream) {
|
||||||
|
Assertions.checkState(eventHandler != null);
|
||||||
|
while (true) {
|
||||||
|
while (!masterElementsStack.isEmpty()
|
||||||
|
&& bytesRead >= masterElementsStack.peek().elementEndOffsetBytes) {
|
||||||
|
if (!eventHandler.onMasterElementEnd(masterElementsStack.pop().elementId)) {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state == STATE_BEGIN_READING) {
|
||||||
|
int idResult = readElementId(inputStream);
|
||||||
|
if (idResult != READ_RESULT_CONTINUE) {
|
||||||
|
return idResult;
|
||||||
|
}
|
||||||
|
int sizeResult = readElementContentSize(inputStream);
|
||||||
|
if (sizeResult != READ_RESULT_CONTINUE) {
|
||||||
|
return sizeResult;
|
||||||
|
}
|
||||||
|
state = STATE_READ_CONTENTS;
|
||||||
|
bytesState = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int type = eventHandler.getElementType(elementId);
|
||||||
|
switch (type) {
|
||||||
|
case TYPE_MASTER:
|
||||||
|
int masterHeaderSize = (int) (bytesRead - elementOffset); // Header size is 12 bytes max.
|
||||||
|
masterElementsStack.add(new MasterElement(elementId, bytesRead + elementContentSize));
|
||||||
|
if (!eventHandler.onMasterElementStart(
|
||||||
|
elementId, elementOffset, masterHeaderSize, elementContentSize)) {
|
||||||
|
prepareForNextElement();
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_UNSIGNED_INT:
|
||||||
|
if (elementContentSize > MAX_INTEGER_ELEMENT_SIZE_BYTES) {
|
||||||
|
throw new IllegalStateException("Invalid integer size " + elementContentSize);
|
||||||
|
}
|
||||||
|
int intResult =
|
||||||
|
readBytesInternal(inputStream, tempByteArray, (int) elementContentSize);
|
||||||
|
if (intResult != READ_RESULT_CONTINUE) {
|
||||||
|
return intResult;
|
||||||
|
}
|
||||||
|
long intValue = getTempByteArrayValue((int) elementContentSize, false);
|
||||||
|
if (!eventHandler.onIntegerElement(elementId, intValue)) {
|
||||||
|
prepareForNextElement();
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_FLOAT:
|
||||||
|
if (elementContentSize != VALID_FLOAT32_ELEMENT_SIZE_BYTES
|
||||||
|
&& elementContentSize != VALID_FLOAT64_ELEMENT_SIZE_BYTES) {
|
||||||
|
throw new IllegalStateException("Invalid float size " + elementContentSize);
|
||||||
|
}
|
||||||
|
int floatResult =
|
||||||
|
readBytesInternal(inputStream, tempByteArray, (int) elementContentSize);
|
||||||
|
if (floatResult != READ_RESULT_CONTINUE) {
|
||||||
|
return floatResult;
|
||||||
|
}
|
||||||
|
long valueBits = getTempByteArrayValue((int) elementContentSize, false);
|
||||||
|
double floatValue;
|
||||||
|
if (elementContentSize == VALID_FLOAT32_ELEMENT_SIZE_BYTES) {
|
||||||
|
floatValue = Float.intBitsToFloat((int) valueBits);
|
||||||
|
} else {
|
||||||
|
floatValue = Double.longBitsToDouble(valueBits);
|
||||||
|
}
|
||||||
|
if (!eventHandler.onFloatElement(elementId, floatValue)) {
|
||||||
|
prepareForNextElement();
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_STRING:
|
||||||
|
if (elementContentSize > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"String element size " + elementContentSize + " is larger than MAX_INT");
|
||||||
|
}
|
||||||
|
if (stringBytes == null) {
|
||||||
|
stringBytes = new byte[(int) elementContentSize];
|
||||||
|
}
|
||||||
|
int stringResult =
|
||||||
|
readBytesInternal(inputStream, stringBytes, (int) elementContentSize);
|
||||||
|
if (stringResult != READ_RESULT_CONTINUE) {
|
||||||
|
return stringResult;
|
||||||
|
}
|
||||||
|
String stringValue = new String(stringBytes, Charset.forName("UTF-8"));
|
||||||
|
stringBytes = null;
|
||||||
|
if (!eventHandler.onStringElement(elementId, stringValue)) {
|
||||||
|
prepareForNextElement();
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_BINARY:
|
||||||
|
if (elementContentSize > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Binary element size " + elementContentSize + " is larger than MAX_INT");
|
||||||
|
}
|
||||||
|
if (inputStream.getAvailableByteCount() < elementContentSize) {
|
||||||
|
return READ_RESULT_NEED_MORE_DATA;
|
||||||
|
}
|
||||||
|
int binaryHeaderSize = (int) (bytesRead - elementOffset); // Header size is 12 bytes max.
|
||||||
|
boolean keepGoing = eventHandler.onBinaryElement(
|
||||||
|
elementId, elementOffset, binaryHeaderSize, (int) elementContentSize, inputStream);
|
||||||
|
long expectedBytesRead = elementOffset + binaryHeaderSize + elementContentSize;
|
||||||
|
if (expectedBytesRead != bytesRead) {
|
||||||
|
throw new IllegalStateException("Incorrect total bytes read. Expected "
|
||||||
|
+ expectedBytesRead + " but actually " + bytesRead);
|
||||||
|
}
|
||||||
|
if (!keepGoing) {
|
||||||
|
prepareForNextElement();
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case TYPE_UNKNOWN:
|
||||||
|
if (elementContentSize > Integer.MAX_VALUE) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Unknown element size " + elementContentSize + " is larger than MAX_INT");
|
||||||
|
}
|
||||||
|
int skipResult = skipBytesInternal(inputStream, (int) elementContentSize);
|
||||||
|
if (skipResult != READ_RESULT_CONTINUE) {
|
||||||
|
return skipResult;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Invalid element type " + type);
|
||||||
|
}
|
||||||
|
prepareForNextElement();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBytesRead() {
|
||||||
|
return bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
prepareForNextElement();
|
||||||
|
masterElementsStack.clear();
|
||||||
|
bytesRead = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readVarint(NonBlockingInputStream inputStream) {
|
||||||
|
varintBytesState = STATE_BEGIN_READING;
|
||||||
|
int result = readVarintBytes(inputStream);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
throw new IllegalStateException("Couldn't read varint");
|
||||||
|
}
|
||||||
|
return getTempByteArrayValue(varintBytesLength, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBytes(NonBlockingInputStream inputStream, ByteBuffer byteBuffer, int totalBytes) {
|
||||||
|
bytesState = 0;
|
||||||
|
int result = readBytesInternal(inputStream, byteBuffer, totalBytes);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
throw new IllegalStateException("Couldn't read bytes into buffer");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readBytes(NonBlockingInputStream inputStream, byte[] byteArray, int totalBytes) {
|
||||||
|
bytesState = 0;
|
||||||
|
int result = readBytesInternal(inputStream, byteArray, totalBytes);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
throw new IllegalStateException("Couldn't read bytes into array");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void skipBytes(NonBlockingInputStream inputStream, int totalBytes) {
|
||||||
|
bytesState = 0;
|
||||||
|
int result = skipBytesInternal(inputStream, totalBytes);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
throw new IllegalStateException("Couldn't skip bytes");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the internal state of {@link #read(NonBlockingInputStream)} so that it can start
|
||||||
|
* reading a new element from scratch.
|
||||||
|
*/
|
||||||
|
private void prepareForNextElement() {
|
||||||
|
state = STATE_BEGIN_READING;
|
||||||
|
elementIdState = STATE_BEGIN_READING;
|
||||||
|
elementContentSizeState = STATE_BEGIN_READING;
|
||||||
|
elementOffset = bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an element ID such that reading can be stopped and started again in a later call
|
||||||
|
* if not enough bytes are available. Returns {@link #READ_RESULT_CONTINUE} if a full element ID
|
||||||
|
* has been read into {@link #elementId}. Reset {@link #elementIdState} to
|
||||||
|
* {@link #STATE_BEGIN_READING} before calling to indicate a new element ID should be read.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream from which an element ID should be read
|
||||||
|
* @return One of the {@code RESULT_*} flags defined in this class
|
||||||
|
*/
|
||||||
|
private int readElementId(NonBlockingInputStream inputStream) {
|
||||||
|
if (elementIdState == STATE_FINISHED_READING) {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
if (elementIdState == STATE_BEGIN_READING) {
|
||||||
|
varintBytesState = STATE_BEGIN_READING;
|
||||||
|
elementIdState = STATE_READ_CONTENTS;
|
||||||
|
}
|
||||||
|
int result = readVarintBytes(inputStream);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
// Element IDs are at most 4 bytes so cast to int now.
|
||||||
|
elementId = (int) getTempByteArrayValue(varintBytesLength, false);
|
||||||
|
elementIdState = STATE_FINISHED_READING;
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an element's content size such that reading can be stopped and started again in a later
|
||||||
|
* call if not enough bytes are available.
|
||||||
|
*
|
||||||
|
* <p>Returns {@link #READ_RESULT_CONTINUE} if an entire element size has been
|
||||||
|
* read into {@link #elementContentSize}. Reset {@link #elementContentSizeState} to
|
||||||
|
* {@link #STATE_BEGIN_READING} before calling to indicate a new element size should be read.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream from which an element size should be read
|
||||||
|
* @return One of the {@code RESULT_*} flags defined in this class
|
||||||
|
*/
|
||||||
|
private int readElementContentSize(NonBlockingInputStream inputStream) {
|
||||||
|
if (elementContentSizeState == STATE_FINISHED_READING) {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
if (elementContentSizeState == STATE_BEGIN_READING) {
|
||||||
|
varintBytesState = STATE_BEGIN_READING;
|
||||||
|
elementContentSizeState = STATE_READ_CONTENTS;
|
||||||
|
}
|
||||||
|
int result = readVarintBytes(inputStream);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
elementContentSize = getTempByteArrayValue(varintBytesLength, true);
|
||||||
|
elementContentSizeState = STATE_FINISHED_READING;
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads an EBML variable-length integer (varint) such that reading can be stopped and started
|
||||||
|
* again in a later call if not enough bytes are available.
|
||||||
|
*
|
||||||
|
* <p>Returns {@link #READ_RESULT_CONTINUE} if an entire varint has been read into
|
||||||
|
* {@link #tempByteArray} and the length of the varint is in {@link #varintBytesLength}.
|
||||||
|
* Reset {@link #varintBytesState} to {@link #STATE_BEGIN_READING} before calling to indicate
|
||||||
|
* a new varint should be read.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream from which a varint should be read
|
||||||
|
* @return One of the {@code RESULT_*} flags defined in this class
|
||||||
|
*/
|
||||||
|
private int readVarintBytes(NonBlockingInputStream inputStream) {
|
||||||
|
if (varintBytesState == STATE_FINISHED_READING) {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read first byte to get length.
|
||||||
|
if (varintBytesState == STATE_BEGIN_READING) {
|
||||||
|
bytesState = 0;
|
||||||
|
int result = readBytesInternal(inputStream, tempByteArray, 1);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
varintBytesState = STATE_READ_CONTENTS;
|
||||||
|
|
||||||
|
int firstByte = tempByteArray[0] & 0xff;
|
||||||
|
varintBytesLength = -1;
|
||||||
|
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
|
||||||
|
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
|
||||||
|
varintBytesLength = i + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (varintBytesLength == -1) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"No valid varint length mask found at bytesRead = " + bytesRead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read remaining bytes.
|
||||||
|
int result = readBytesInternal(inputStream, tempByteArray, varintBytesLength);
|
||||||
|
if (result != READ_RESULT_CONTINUE) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// All bytes have been read.
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a set amount of bytes into a {@link ByteBuffer} such that reading can be stopped
|
||||||
|
* and started again later if not enough bytes are available.
|
||||||
|
*
|
||||||
|
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been read. Reset
|
||||||
|
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream from which bytes should be read
|
||||||
|
* @param byteBuffer The {@link ByteBuffer} into which bytes should be read
|
||||||
|
* @param totalBytes The total size of bytes to be read
|
||||||
|
* @return One of the {@code RESULT_*} flags defined in this class
|
||||||
|
*/
|
||||||
|
private int readBytesInternal(
|
||||||
|
NonBlockingInputStream inputStream, ByteBuffer byteBuffer, int totalBytes) {
|
||||||
|
if (bytesState == STATE_BEGIN_READING && totalBytes > byteBuffer.capacity()) {
|
||||||
|
throw new IllegalArgumentException("Byte buffer not large enough");
|
||||||
|
}
|
||||||
|
if (bytesState >= totalBytes) {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
int remainingBytes = totalBytes - bytesState;
|
||||||
|
int additionalBytesRead = inputStream.read(byteBuffer, remainingBytes);
|
||||||
|
return updateBytesState(additionalBytesRead, totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a set amount of bytes into a {@code byte[]} such that reading can be stopped
|
||||||
|
* and started again later if not enough bytes are available.
|
||||||
|
*
|
||||||
|
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been read. Reset
|
||||||
|
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream from which bytes should be read
|
||||||
|
* @param byteArray The {@code byte[]} into which bytes should be read
|
||||||
|
* @param totalBytes The total size of bytes to be read
|
||||||
|
* @return One of the {@code RESULT_*} flags defined in this class
|
||||||
|
*/
|
||||||
|
private int readBytesInternal(
|
||||||
|
NonBlockingInputStream inputStream, byte[] byteArray, int totalBytes) {
|
||||||
|
if (bytesState == STATE_BEGIN_READING && totalBytes > byteArray.length) {
|
||||||
|
throw new IllegalArgumentException("Byte array not large enough");
|
||||||
|
}
|
||||||
|
if (bytesState >= totalBytes) {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
int remainingBytes = totalBytes - bytesState;
|
||||||
|
int additionalBytesRead = inputStream.read(byteArray, bytesState, remainingBytes);
|
||||||
|
return updateBytesState(additionalBytesRead, totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips a set amount of bytes such that reading can be stopped and started again later if
|
||||||
|
* not enough bytes are available.
|
||||||
|
*
|
||||||
|
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been skipped. Reset
|
||||||
|
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes
|
||||||
|
* should be skipped.
|
||||||
|
*
|
||||||
|
* @param inputStream The input stream from which bytes should be skipped
|
||||||
|
* @param totalBytes The total size of bytes to be skipped
|
||||||
|
* @return One of the {@code RESULT_*} flags defined in this class
|
||||||
|
*/
|
||||||
|
private int skipBytesInternal(NonBlockingInputStream inputStream, int totalBytes) {
|
||||||
|
if (bytesState >= totalBytes) {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
int remainingBytes = totalBytes - bytesState;
|
||||||
|
int additionalBytesRead = inputStream.skip(remainingBytes);
|
||||||
|
return updateBytesState(additionalBytesRead, totalBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates {@link #bytesState} and {@link #bytesRead} after reading bytes in one of the
|
||||||
|
* {@code verbBytesInternal} methods.
|
||||||
|
*
|
||||||
|
* @param additionalBytesRead The number of additional bytes read to be accounted for
|
||||||
|
* @param totalBytes The total size of bytes to be read or skipped
|
||||||
|
* @return One of the {@code RESULT_*} flags defined in this class
|
||||||
|
*/
|
||||||
|
private int updateBytesState(int additionalBytesRead, int totalBytes) {
|
||||||
|
if (additionalBytesRead == -1) {
|
||||||
|
return READ_RESULT_END_OF_FILE;
|
||||||
|
}
|
||||||
|
bytesState += additionalBytesRead;
|
||||||
|
bytesRead += additionalBytesRead;
|
||||||
|
if (bytesState < totalBytes) {
|
||||||
|
return READ_RESULT_NEED_MORE_DATA;
|
||||||
|
} else {
|
||||||
|
return READ_RESULT_CONTINUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses and returns the integer value currently read into the first {@code byteLength} bytes
|
||||||
|
* of {@link #tempByteArray}. EBML varint length masks can optionally be removed.
|
||||||
|
*
|
||||||
|
* @param byteLength The number of bytes to parse from {@link #tempByteArray}
|
||||||
|
* @param removeLengthMask Removes the variable-length integer length mask from the value
|
||||||
|
* @return The resulting integer value. This value could be up to 8-bytes so a Java long is used
|
||||||
|
*/
|
||||||
|
private long getTempByteArrayValue(int byteLength, boolean removeLengthMask) {
|
||||||
|
if (removeLengthMask) {
|
||||||
|
tempByteArray[0] &= ~VARINT_LENGTH_MASKS[varintBytesLength - 1];
|
||||||
|
}
|
||||||
|
long varint = 0;
|
||||||
|
for (int i = 0; i < byteLength; i++) {
|
||||||
|
// Shift all existing bits up one byte and add the next byte at the bottom.
|
||||||
|
varint = (varint << 8) | (tempByteArray[i] & 0xff);
|
||||||
|
}
|
||||||
|
return varint;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used in {@link #masterElementsStack} to track when the current master element ends so that
|
||||||
|
* {@link EbmlEventHandler#onMasterElementEnd(int)} is called.
|
||||||
|
*/
|
||||||
|
private static final class MasterElement {
|
||||||
|
|
||||||
|
private final int elementId;
|
||||||
|
private final long elementEndOffsetBytes;
|
||||||
|
|
||||||
|
private MasterElement(int elementId, long elementEndOffsetBytes) {
|
||||||
|
this.elementId = elementId;
|
||||||
|
this.elementEndOffsetBytes = elementEndOffsetBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,386 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.parser.webm;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.MediaFormat;
|
||||||
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
|
import com.google.android.exoplayer.parser.SegmentIndex;
|
||||||
|
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||||
|
import com.google.android.exoplayer.util.LongArray;
|
||||||
|
import com.google.android.exoplayer.util.MimeTypes;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.media.MediaExtractor;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default version of an extractor to facilitate data retrieval from the WebM container format.
|
||||||
|
*
|
||||||
|
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
|
||||||
|
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
|
||||||
|
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
|
||||||
|
*/
|
||||||
|
@TargetApi(16)
|
||||||
|
public final class DefaultWebmExtractor implements WebmExtractor, EbmlEventHandler {
|
||||||
|
|
||||||
|
private static final String DOC_TYPE_WEBM = "webm";
|
||||||
|
private static final String CODEC_ID_VP9 = "V_VP9";
|
||||||
|
private static final int UNKNOWN = -1;
|
||||||
|
|
||||||
|
// Element IDs
|
||||||
|
private static final int ID_EBML = 0x1A45DFA3;
|
||||||
|
private static final int ID_EBML_READ_VERSION = 0x42F7;
|
||||||
|
private static final int ID_DOC_TYPE = 0x4282;
|
||||||
|
private static final int ID_DOC_TYPE_READ_VERSION = 0x4285;
|
||||||
|
|
||||||
|
private static final int ID_SEGMENT = 0x18538067;
|
||||||
|
|
||||||
|
private static final int ID_INFO = 0x1549A966;
|
||||||
|
private static final int ID_TIMECODE_SCALE = 0x2AD7B1;
|
||||||
|
private static final int ID_DURATION = 0x4489;
|
||||||
|
|
||||||
|
private static final int ID_CLUSTER = 0x1F43B675;
|
||||||
|
private static final int ID_TIME_CODE = 0xE7;
|
||||||
|
private static final int ID_SIMPLE_BLOCK = 0xA3;
|
||||||
|
|
||||||
|
private static final int ID_TRACKS = 0x1654AE6B;
|
||||||
|
private static final int ID_TRACK_ENTRY = 0xAE;
|
||||||
|
private static final int ID_CODEC_ID = 0x86;
|
||||||
|
private static final int ID_VIDEO = 0xE0;
|
||||||
|
private static final int ID_PIXEL_WIDTH = 0xB0;
|
||||||
|
private static final int ID_PIXEL_HEIGHT = 0xBA;
|
||||||
|
|
||||||
|
private static final int ID_CUES = 0x1C53BB6B;
|
||||||
|
private static final int ID_CUE_POINT = 0xBB;
|
||||||
|
private static final int ID_CUE_TIME = 0xB3;
|
||||||
|
private static final int ID_CUE_TRACK_POSITIONS = 0xB7;
|
||||||
|
private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
|
||||||
|
|
||||||
|
// SimpleBlock Lacing Values
|
||||||
|
private static final int LACING_NONE = 0;
|
||||||
|
private static final int LACING_XIPH = 1;
|
||||||
|
private static final int LACING_FIXED = 2;
|
||||||
|
private static final int LACING_EBML = 3;
|
||||||
|
|
||||||
|
private final EbmlReader reader;
|
||||||
|
private final byte[] simpleBlockTimecodeAndFlags = new byte[3];
|
||||||
|
|
||||||
|
private SampleHolder tempSampleHolder;
|
||||||
|
private boolean sampleRead;
|
||||||
|
|
||||||
|
private boolean prepared = false;
|
||||||
|
private long segmentStartOffsetBytes = UNKNOWN;
|
||||||
|
private long segmentEndOffsetBytes = UNKNOWN;
|
||||||
|
private long timecodeScale = 1000000L;
|
||||||
|
private long durationUs = UNKNOWN;
|
||||||
|
private int pixelWidth = UNKNOWN;
|
||||||
|
private int pixelHeight = UNKNOWN;
|
||||||
|
private long cuesSizeBytes = UNKNOWN;
|
||||||
|
private long clusterTimecodeUs = UNKNOWN;
|
||||||
|
private long simpleBlockTimecodeUs = UNKNOWN;
|
||||||
|
private MediaFormat format;
|
||||||
|
private SegmentIndex cues;
|
||||||
|
private LongArray cueTimesUs;
|
||||||
|
private LongArray cueClusterPositions;
|
||||||
|
|
||||||
|
public DefaultWebmExtractor() {
|
||||||
|
this(new DefaultEbmlReader());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ DefaultWebmExtractor(EbmlReader reader) {
|
||||||
|
this.reader = reader;
|
||||||
|
this.reader.setEventHandler(this);
|
||||||
|
this.cueTimesUs = new LongArray();
|
||||||
|
this.cueClusterPositions = new LongArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPrepared() {
|
||||||
|
return prepared;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) {
|
||||||
|
tempSampleHolder = sampleHolder;
|
||||||
|
sampleRead = false;
|
||||||
|
reader.read(inputStream);
|
||||||
|
tempSampleHolder = null;
|
||||||
|
return sampleRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean seekTo(long seekTimeUs, boolean allowNoop) {
|
||||||
|
checkPrepared();
|
||||||
|
if (allowNoop
|
||||||
|
&& simpleBlockTimecodeUs != UNKNOWN
|
||||||
|
&& seekTimeUs >= simpleBlockTimecodeUs) {
|
||||||
|
int clusterIndex = Arrays.binarySearch(cues.timesUs, clusterTimecodeUs);
|
||||||
|
if (clusterIndex >= 0 && seekTimeUs < clusterTimecodeUs + cues.durationsUs[clusterIndex]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SegmentIndex getCues() {
|
||||||
|
checkPrepared();
|
||||||
|
return cues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaFormat getFormat() {
|
||||||
|
checkPrepared();
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getElementType(int id) {
|
||||||
|
switch (id) {
|
||||||
|
case ID_EBML:
|
||||||
|
case ID_SEGMENT:
|
||||||
|
case ID_INFO:
|
||||||
|
case ID_CLUSTER:
|
||||||
|
case ID_TRACKS:
|
||||||
|
case ID_TRACK_ENTRY:
|
||||||
|
case ID_VIDEO:
|
||||||
|
case ID_CUES:
|
||||||
|
case ID_CUE_POINT:
|
||||||
|
case ID_CUE_TRACK_POSITIONS:
|
||||||
|
return EbmlReader.TYPE_MASTER;
|
||||||
|
case ID_EBML_READ_VERSION:
|
||||||
|
case ID_DOC_TYPE_READ_VERSION:
|
||||||
|
case ID_TIMECODE_SCALE:
|
||||||
|
case ID_TIME_CODE:
|
||||||
|
case ID_PIXEL_WIDTH:
|
||||||
|
case ID_PIXEL_HEIGHT:
|
||||||
|
case ID_CUE_TIME:
|
||||||
|
case ID_CUE_CLUSTER_POSITION:
|
||||||
|
return EbmlReader.TYPE_UNSIGNED_INT;
|
||||||
|
case ID_DOC_TYPE:
|
||||||
|
case ID_CODEC_ID:
|
||||||
|
return EbmlReader.TYPE_STRING;
|
||||||
|
case ID_SIMPLE_BLOCK:
|
||||||
|
return EbmlReader.TYPE_BINARY;
|
||||||
|
case ID_DURATION:
|
||||||
|
return EbmlReader.TYPE_FLOAT;
|
||||||
|
default:
|
||||||
|
return EbmlReader.TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMasterElementStart(
|
||||||
|
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes) {
|
||||||
|
switch (id) {
|
||||||
|
case ID_SEGMENT:
|
||||||
|
if (segmentStartOffsetBytes != UNKNOWN || segmentEndOffsetBytes != UNKNOWN) {
|
||||||
|
throw new IllegalStateException("Multiple Segment elements not supported");
|
||||||
|
}
|
||||||
|
segmentStartOffsetBytes = elementOffsetBytes + headerSizeBytes;
|
||||||
|
segmentEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
|
||||||
|
break;
|
||||||
|
case ID_CUES:
|
||||||
|
cuesSizeBytes = headerSizeBytes + contentsSizeBytes;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onMasterElementEnd(int id) {
|
||||||
|
if (id == ID_CUES) {
|
||||||
|
finishPreparing();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onIntegerElement(int id, long value) {
|
||||||
|
switch (id) {
|
||||||
|
case ID_EBML_READ_VERSION:
|
||||||
|
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
|
||||||
|
if (value != 1) {
|
||||||
|
throw new IllegalArgumentException("EBMLReadVersion " + value + " not supported");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ID_DOC_TYPE_READ_VERSION:
|
||||||
|
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
|
||||||
|
if (value < 1 || value > 2) {
|
||||||
|
throw new IllegalArgumentException("DocTypeReadVersion " + value + " not supported");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ID_TIMECODE_SCALE:
|
||||||
|
timecodeScale = value;
|
||||||
|
break;
|
||||||
|
case ID_PIXEL_WIDTH:
|
||||||
|
pixelWidth = (int) value;
|
||||||
|
break;
|
||||||
|
case ID_PIXEL_HEIGHT:
|
||||||
|
pixelHeight = (int) value;
|
||||||
|
break;
|
||||||
|
case ID_CUE_TIME:
|
||||||
|
cueTimesUs.add(scaleTimecodeToUs(value));
|
||||||
|
break;
|
||||||
|
case ID_CUE_CLUSTER_POSITION:
|
||||||
|
cueClusterPositions.add(value);
|
||||||
|
break;
|
||||||
|
case ID_TIME_CODE:
|
||||||
|
clusterTimecodeUs = scaleTimecodeToUs(value);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onFloatElement(int id, double value) {
|
||||||
|
if (id == ID_DURATION) {
|
||||||
|
durationUs = scaleTimecodeToUs((long) value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onStringElement(int id, String value) {
|
||||||
|
switch (id) {
|
||||||
|
case ID_DOC_TYPE:
|
||||||
|
// Validate that DocType is supported. This extractor only supports "webm".
|
||||||
|
if (!DOC_TYPE_WEBM.equals(value)) {
|
||||||
|
throw new IllegalArgumentException("DocType " + value + " not supported");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ID_CODEC_ID:
|
||||||
|
// Validate that CodecID is supported. This extractor only supports "V_VP9".
|
||||||
|
if (!CODEC_ID_VP9.equals(value)) {
|
||||||
|
throw new IllegalArgumentException("CodecID " + value + " not supported");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onBinaryElement(
|
||||||
|
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
|
||||||
|
NonBlockingInputStream inputStream) {
|
||||||
|
if (id == ID_SIMPLE_BLOCK) {
|
||||||
|
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
|
||||||
|
// for info about how data is organized in a SimpleBlock element.
|
||||||
|
|
||||||
|
// Value of trackNumber is not used but needs to be read.
|
||||||
|
reader.readVarint(inputStream);
|
||||||
|
|
||||||
|
// Next three bytes have timecode and flags.
|
||||||
|
reader.readBytes(inputStream, simpleBlockTimecodeAndFlags, 3);
|
||||||
|
|
||||||
|
// First two bytes of the three are the relative timecode.
|
||||||
|
int timecode =
|
||||||
|
(simpleBlockTimecodeAndFlags[0] << 8) | (simpleBlockTimecodeAndFlags[1] & 0xff);
|
||||||
|
long timecodeUs = scaleTimecodeToUs(timecode);
|
||||||
|
|
||||||
|
// Last byte of the three has some flags and the lacing value.
|
||||||
|
boolean keyframe = (simpleBlockTimecodeAndFlags[2] & 0x80) == 0x80;
|
||||||
|
boolean invisible = (simpleBlockTimecodeAndFlags[2] & 0x08) == 0x08;
|
||||||
|
int lacing = (simpleBlockTimecodeAndFlags[2] & 0x06) >> 1;
|
||||||
|
|
||||||
|
// Validate lacing and set info into sample holder.
|
||||||
|
switch (lacing) {
|
||||||
|
case LACING_NONE:
|
||||||
|
long elementEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
|
||||||
|
simpleBlockTimecodeUs = clusterTimecodeUs + timecodeUs;
|
||||||
|
tempSampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
|
||||||
|
tempSampleHolder.decodeOnly = invisible;
|
||||||
|
tempSampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
|
||||||
|
tempSampleHolder.size = (int) (elementEndOffsetBytes - reader.getBytesRead());
|
||||||
|
break;
|
||||||
|
case LACING_EBML:
|
||||||
|
case LACING_FIXED:
|
||||||
|
case LACING_XIPH:
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Lacing mode " + lacing + " not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read video data into sample holder.
|
||||||
|
reader.readBytes(inputStream, tempSampleHolder.data, tempSampleHolder.size);
|
||||||
|
sampleRead = true;
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
reader.skipBytes(inputStream, contentsSizeBytes);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long scaleTimecodeToUs(long unscaledTimecode) {
|
||||||
|
return TimeUnit.NANOSECONDS.toMicros(unscaledTimecode * timecodeScale);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPrepared() {
|
||||||
|
if (!prepared) {
|
||||||
|
throw new IllegalStateException("Parser not yet prepared");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void finishPreparing() {
|
||||||
|
if (prepared) {
|
||||||
|
throw new IllegalStateException("Already prepared");
|
||||||
|
} else if (segmentStartOffsetBytes == UNKNOWN) {
|
||||||
|
throw new IllegalStateException("Segment start/end offsets unknown");
|
||||||
|
} else if (durationUs == UNKNOWN) {
|
||||||
|
throw new IllegalStateException("Duration unknown");
|
||||||
|
} else if (pixelWidth == UNKNOWN || pixelHeight == UNKNOWN) {
|
||||||
|
throw new IllegalStateException("Pixel width/height unknown");
|
||||||
|
} else if (cuesSizeBytes == UNKNOWN) {
|
||||||
|
throw new IllegalStateException("Cues size unknown");
|
||||||
|
} else if (cueTimesUs.size() == 0 || cueTimesUs.size() != cueClusterPositions.size()) {
|
||||||
|
throw new IllegalStateException("Invalid/missing cue points");
|
||||||
|
}
|
||||||
|
|
||||||
|
format = MediaFormat.createVideoFormat(
|
||||||
|
MimeTypes.VIDEO_VP9, MediaFormat.NO_VALUE, pixelWidth, pixelHeight, null);
|
||||||
|
|
||||||
|
int cuePointsSize = cueTimesUs.size();
|
||||||
|
int[] sizes = new int[cuePointsSize];
|
||||||
|
long[] offsets = new long[cuePointsSize];
|
||||||
|
long[] durationsUs = new long[cuePointsSize];
|
||||||
|
long[] timesUs = new long[cuePointsSize];
|
||||||
|
for (int i = 0; i < cuePointsSize; i++) {
|
||||||
|
timesUs[i] = cueTimesUs.get(i);
|
||||||
|
offsets[i] = segmentStartOffsetBytes + cueClusterPositions.get(i);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < cuePointsSize - 1; i++) {
|
||||||
|
sizes[i] = (int) (offsets[i + 1] - offsets[i]);
|
||||||
|
durationsUs[i] = timesUs[i + 1] - timesUs[i];
|
||||||
|
}
|
||||||
|
sizes[cuePointsSize - 1] = (int) (segmentEndOffsetBytes - offsets[cuePointsSize - 1]);
|
||||||
|
durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1];
|
||||||
|
cues = new SegmentIndex((int) cuesSizeBytes, sizes, offsets, durationsUs, timesUs);
|
||||||
|
cueTimesUs = null;
|
||||||
|
cueClusterPositions = null;
|
||||||
|
|
||||||
|
prepared = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2014 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer.parser.webm;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines EBML element IDs/types and reacts to events.
|
||||||
|
*/
|
||||||
|
/* package */ interface EbmlEventHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the type of an element ID.
|
||||||
|
*
|
||||||
|
* <p>If {@link EbmlReader#TYPE_UNKNOWN} is returned then the element is skipped.
|
||||||
|
* Note that all children of a skipped master element are also skipped.
|
||||||
|
*
|
||||||
|
* @param id The integer ID of this element
|
||||||
|
* @return One of the {@code TYPE_} constants defined in this class
|
||||||
|
*/
|
||||||
|
public int getElementType(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a master element is encountered in the {@link NonBlockingInputStream}.
|
||||||
|
*
|
||||||
|
* <p>Following events should be considered as taking place "within" this element until a
|
||||||
|
* matching call to {@link #onMasterElementEnd(int)} is made. Note that it is possible for
|
||||||
|
* another master element of the same ID to be nested within itself.
|
||||||
|
*
|
||||||
|
* @param id The integer ID of this element
|
||||||
|
* @param elementOffsetBytes The byte offset where this element starts
|
||||||
|
* @param headerSizeBytes The byte length of this element's ID and size header
|
||||||
|
* @param contentsSizeBytes The byte length of this element's children
|
||||||
|
* @return {@code false} if parsing should stop right away
|
||||||
|
*/
|
||||||
|
public boolean onMasterElementStart(
|
||||||
|
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a master element has finished reading in all of its children from the
|
||||||
|
* {@link NonBlockingInputStream}.
|
||||||
|
*
|
||||||
|
* @param id The integer ID of this element
|
||||||
|
* @return {@code false} if parsing should stop right away
|
||||||
|
*/
|
||||||
|
public boolean onMasterElementEnd(int id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
|
||||||
|
*
|
||||||
|
* @param id The integer ID of this element
|
||||||
|
* @param value The integer value this element contains
|
||||||
|
* @return {@code false} if parsing should stop right away
|
||||||
|
*/
|
||||||
|
public boolean onIntegerElement(int id, long value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
|
||||||
|
*
|
||||||
|
* @param id The integer ID of this element
|
||||||
|
* @param value The float value this element contains
|
||||||
|
* @return {@code false} if parsing should stop right away
|
||||||
|
*/
|
||||||
|
public boolean onFloatElement(int id, double value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
|
||||||
|
*
|
||||||
|
* @param id The integer ID of this element
|
||||||
|
* @param value The string value this element contains
|
||||||
|
* @return {@code false} if parsing should stop right away
|
||||||
|
*/
|
||||||
|
public boolean onStringElement(int id, String value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
|
||||||
|
*
|
||||||
|
* <p>The element header (containing element ID and content size) will already have been read.
|
||||||
|
* Subclasses must exactly read the entire contents of the element, which is
|
||||||
|
* {@code contentsSizeBytes} in length. It's guaranteed that the full element contents will be
|
||||||
|
* immediately available from {@code inputStream}.
|
||||||
|
*
|
||||||
|
* <p>Several methods in {@link EbmlReader} are available for reading the contents of a
|
||||||
|
* binary element:
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link EbmlReader#readVarint(NonBlockingInputStream)}.
|
||||||
|
* <li>{@link EbmlReader#readBytes(NonBlockingInputStream, byte[], int)}.
|
||||||
|
* <li>{@link EbmlReader#readBytes(NonBlockingInputStream, ByteBuffer, int)}.
|
||||||
|
* <li>{@link EbmlReader#skipBytes(NonBlockingInputStream, int)}.
|
||||||
|
* <li>{@link EbmlReader#getBytesRead()}.
|
||||||
|
*
|
||||||
|
* @param id The integer ID of this element
|
||||||
|
* @param elementOffsetBytes The byte offset where this element starts
|
||||||
|
* @param headerSizeBytes The byte length of this element's ID and size header
|
||||||
|
* @param contentsSizeBytes The byte length of this element's contents
|
||||||
|
* @param inputStream The {@link NonBlockingInputStream} from which this
|
||||||
|
* element's contents should be read
|
||||||
|
* @return {@code false} if parsing should stop right away
|
||||||
|
*/
|
||||||
|
public boolean onBinaryElement(
|
||||||
|
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
|
||||||
|
NonBlockingInputStream inputStream);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -16,528 +16,92 @@
|
||||||
package com.google.android.exoplayer.parser.webm;
|
package com.google.android.exoplayer.parser.webm;
|
||||||
|
|
||||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||||
import com.google.android.exoplayer.util.Assertions;
|
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event-driven incremental EBML reader base class.
|
* Basic event-driven incremental EBML parser which needs an {@link EbmlEventHandler} to
|
||||||
|
* define IDs/types and react to events.
|
||||||
*
|
*
|
||||||
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
|
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
|
||||||
* It was originally designed for the Matroska container format. More information about EBML and
|
* It was originally designed for the Matroska container format. More information about EBML and
|
||||||
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
|
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
|
||||||
*/
|
*/
|
||||||
public abstract class EbmlReader {
|
/* package */ interface EbmlReader {
|
||||||
|
|
||||||
// Element Types
|
// Element Types
|
||||||
protected static final int TYPE_UNKNOWN = 0; // Undefined element.
|
/** Undefined element. */
|
||||||
protected static final int TYPE_MASTER = 1; // Contains child elements.
|
public static final int TYPE_UNKNOWN = 0;
|
||||||
protected static final int TYPE_UNSIGNED_INT = 2;
|
/** Contains child elements. */
|
||||||
protected static final int TYPE_STRING = 3;
|
public static final int TYPE_MASTER = 1;
|
||||||
protected static final int TYPE_BINARY = 4;
|
/** Unsigned integer value of up to 8 bytes. */
|
||||||
protected static final int TYPE_FLOAT = 5;
|
public static final int TYPE_UNSIGNED_INT = 2;
|
||||||
|
public static final int TYPE_STRING = 3;
|
||||||
|
public static final int TYPE_BINARY = 4;
|
||||||
|
/** IEEE floating point value of either 4 or 8 bytes. */
|
||||||
|
public static final int TYPE_FLOAT = 5;
|
||||||
|
|
||||||
// Return values for methods read, readElementId, readElementSize, readVarintBytes, and readBytes.
|
// Return values for reading methods.
|
||||||
protected static final int RESULT_CONTINUE = 0;
|
public static final int READ_RESULT_CONTINUE = 0;
|
||||||
protected static final int RESULT_NEED_MORE_DATA = 1;
|
public static final int READ_RESULT_NEED_MORE_DATA = 1;
|
||||||
protected static final int RESULT_END_OF_FILE = 2;
|
public static final int READ_RESULT_END_OF_FILE = 2;
|
||||||
|
|
||||||
// State values used in variables state, elementIdState, elementContentSizeState, and
|
public void setEventHandler(EbmlEventHandler eventHandler);
|
||||||
// varintBytesState.
|
|
||||||
private static final int STATE_BEGIN_READING = 0;
|
|
||||||
private static final int STATE_READ_CONTENTS = 1;
|
|
||||||
private static final int STATE_FINISHED_READING = 2;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The first byte of a variable-length integer (varint) will have one of these bit masks
|
|
||||||
* indicating the total length in bytes. {@code 0x80} is a one-byte integer,
|
|
||||||
* {@code 0x40} is two bytes, and so on up to eight bytes.
|
|
||||||
*/
|
|
||||||
private static final int[] VARINT_LENGTH_MASKS = new int[] {
|
|
||||||
0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Stack<MasterElement> masterElementsStack = new Stack<MasterElement>();
|
|
||||||
private final byte[] tempByteArray = new byte[8];
|
|
||||||
|
|
||||||
private int state;
|
|
||||||
private long bytesRead;
|
|
||||||
private long elementOffset;
|
|
||||||
private int elementId;
|
|
||||||
private int elementIdState;
|
|
||||||
private long elementContentSize;
|
|
||||||
private int elementContentSizeState;
|
|
||||||
private int varintBytesState;
|
|
||||||
private int varintBytesLength;
|
|
||||||
private int bytesState;
|
|
||||||
private byte[] stringBytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called to retrieve the type of an element ID. If {@link #TYPE_UNKNOWN} is returned then
|
|
||||||
* the element is skipped. Note that all children of a skipped master element are also skipped.
|
|
||||||
*
|
|
||||||
* @param id The integer ID of this element.
|
|
||||||
* @return One of the {@code TYPE_} constants defined in this class.
|
|
||||||
*/
|
|
||||||
protected abstract int getElementType(int id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a master element is encountered in the {@link NonBlockingInputStream}.
|
|
||||||
* Following events should be considered as taking place "within" this element until a
|
|
||||||
* matching call to {@link #onMasterElementEnd(int)} is made. Note that it
|
|
||||||
* is possible for the same master element to be nested within itself.
|
|
||||||
*
|
|
||||||
* @param id The integer ID of this element.
|
|
||||||
* @param elementOffset The byte offset where this element starts.
|
|
||||||
* @param headerSize The byte length of this element's ID and size header.
|
|
||||||
* @param contentsSize The byte length of this element's children.
|
|
||||||
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
|
|
||||||
*/
|
|
||||||
protected abstract boolean onMasterElementStart(
|
|
||||||
int id, long elementOffset, int headerSize, int contentsSize);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a master element has finished reading in all of its children from the
|
|
||||||
* {@link NonBlockingInputStream}.
|
|
||||||
*
|
|
||||||
* @param id The integer ID of this element.
|
|
||||||
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
|
|
||||||
*/
|
|
||||||
protected abstract boolean onMasterElementEnd(int id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
|
|
||||||
*
|
|
||||||
* @param id The integer ID of this element.
|
|
||||||
* @param value The integer value this element contains.
|
|
||||||
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
|
|
||||||
*/
|
|
||||||
protected abstract boolean onIntegerElement(int id, long value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
|
|
||||||
*
|
|
||||||
* @param id The integer ID of this element.
|
|
||||||
* @param value The float value this element contains.
|
|
||||||
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
|
|
||||||
*/
|
|
||||||
protected abstract boolean onFloatElement(int id, double value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
|
|
||||||
*
|
|
||||||
* @param id The integer ID of this element.
|
|
||||||
* @param value The string value this element contains.
|
|
||||||
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
|
|
||||||
*/
|
|
||||||
protected abstract boolean onStringElement(int id, String value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
|
|
||||||
* The element header (containing element ID and content size) will already have been read.
|
|
||||||
* Subclasses must exactly read the entire contents of the element, which is {@code contentsSize}
|
|
||||||
* bytes in length. It's guaranteed that the full element contents will be immediately available
|
|
||||||
* from {@code inputStream}.
|
|
||||||
*
|
|
||||||
* <p>Several methods are available for reading the contents of a binary element:
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #readVarint(NonBlockingInputStream)}.
|
|
||||||
* <li>{@link #readBytes(NonBlockingInputStream, byte[], int)}.
|
|
||||||
* <li>{@link #readBytes(NonBlockingInputStream, ByteBuffer, int)}.
|
|
||||||
* <li>{@link #skipBytes(NonBlockingInputStream, int)}.
|
|
||||||
* <li>{@link #getBytesRead()}.
|
|
||||||
*
|
|
||||||
* @param inputStream The {@link NonBlockingInputStream} from which this
|
|
||||||
* element's contents should be read.
|
|
||||||
* @param id The integer ID of this element.
|
|
||||||
* @param elementOffset The byte offset where this element starts.
|
|
||||||
* @param headerSize The byte length of this element's ID and size header.
|
|
||||||
* @param contentsSize The byte length of this element's contents.
|
|
||||||
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
|
|
||||||
*/
|
|
||||||
protected abstract boolean onBinaryElement(NonBlockingInputStream inputStream,
|
|
||||||
int id, long elementOffset, int headerSize, int contentsSize);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads from a {@link NonBlockingInputStream} and calls event callbacks as needed.
|
* Reads from a {@link NonBlockingInputStream} and calls event callbacks as needed.
|
||||||
*
|
*
|
||||||
* @param inputStream The input stream from which data should be read.
|
* @param inputStream The input stream from which data should be read
|
||||||
* @return One of the {@code RESULT_*} flags defined in this class.
|
* @return One of the {@code RESULT_*} flags defined in this interface
|
||||||
*/
|
*/
|
||||||
protected final int read(NonBlockingInputStream inputStream) {
|
public int read(NonBlockingInputStream inputStream);
|
||||||
while (true) {
|
|
||||||
while (masterElementsStack.size() > 0
|
|
||||||
&& bytesRead >= masterElementsStack.peek().elementEndOffset) {
|
|
||||||
if (!onMasterElementEnd(masterElementsStack.pop().elementId)) {
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state == STATE_BEGIN_READING) {
|
|
||||||
final int resultId = readElementId(inputStream);
|
|
||||||
if (resultId != RESULT_CONTINUE) {
|
|
||||||
return resultId;
|
|
||||||
}
|
|
||||||
final int resultSize = readElementContentSize(inputStream);
|
|
||||||
if (resultSize != RESULT_CONTINUE) {
|
|
||||||
return resultSize;
|
|
||||||
}
|
|
||||||
state = STATE_READ_CONTENTS;
|
|
||||||
bytesState = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
final int type = getElementType(elementId);
|
|
||||||
switch (type) {
|
|
||||||
|
|
||||||
case TYPE_MASTER:
|
|
||||||
final int masterHeaderSize = (int) (bytesRead - elementOffset);
|
|
||||||
masterElementsStack.add(new MasterElement(elementId, bytesRead + elementContentSize));
|
|
||||||
if (!onMasterElementStart(
|
|
||||||
elementId, elementOffset, masterHeaderSize, (int) elementContentSize)) {
|
|
||||||
prepareForNextElement();
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TYPE_UNSIGNED_INT:
|
|
||||||
Assertions.checkState(elementContentSize <= 8);
|
|
||||||
final int resultInt =
|
|
||||||
readBytes(inputStream, null, tempByteArray, (int) elementContentSize);
|
|
||||||
if (resultInt != RESULT_CONTINUE) {
|
|
||||||
return resultInt;
|
|
||||||
}
|
|
||||||
final long intValue = parseTempByteArray((int) elementContentSize, false);
|
|
||||||
if (!onIntegerElement(elementId, intValue)) {
|
|
||||||
prepareForNextElement();
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TYPE_FLOAT:
|
|
||||||
Assertions.checkState(elementContentSize == 4 || elementContentSize == 8);
|
|
||||||
final int resultFloat =
|
|
||||||
readBytes(inputStream, null, tempByteArray, (int) elementContentSize);
|
|
||||||
if (resultFloat != RESULT_CONTINUE) {
|
|
||||||
return resultFloat;
|
|
||||||
}
|
|
||||||
final long valueBits = parseTempByteArray((int) elementContentSize, false);
|
|
||||||
final double floatValue;
|
|
||||||
if (elementContentSize == 4) {
|
|
||||||
floatValue = Float.intBitsToFloat((int) valueBits);
|
|
||||||
} else {
|
|
||||||
floatValue = Double.longBitsToDouble(valueBits);
|
|
||||||
}
|
|
||||||
if (!onFloatElement(elementId, floatValue)) {
|
|
||||||
prepareForNextElement();
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TYPE_STRING:
|
|
||||||
if (stringBytes == null) {
|
|
||||||
stringBytes = new byte[(int) elementContentSize];
|
|
||||||
}
|
|
||||||
final int resultString =
|
|
||||||
readBytes(inputStream, null, stringBytes, (int) elementContentSize);
|
|
||||||
if (resultString != RESULT_CONTINUE) {
|
|
||||||
return resultString;
|
|
||||||
}
|
|
||||||
final String stringValue = new String(stringBytes, Charset.forName("UTF-8"));
|
|
||||||
stringBytes = null;
|
|
||||||
if (!onStringElement(elementId, stringValue)) {
|
|
||||||
prepareForNextElement();
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TYPE_BINARY:
|
|
||||||
if (inputStream.getAvailableByteCount() < elementContentSize) {
|
|
||||||
return RESULT_NEED_MORE_DATA;
|
|
||||||
}
|
|
||||||
final int binaryHeaderSize = (int) (bytesRead - elementOffset);
|
|
||||||
final boolean keepGoing = onBinaryElement(
|
|
||||||
inputStream, elementId, elementOffset, binaryHeaderSize, (int) elementContentSize);
|
|
||||||
Assertions.checkState(elementOffset + binaryHeaderSize + elementContentSize == bytesRead);
|
|
||||||
if (!keepGoing) {
|
|
||||||
prepareForNextElement();
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case TYPE_UNKNOWN:
|
|
||||||
// Unknown elements should be skipped.
|
|
||||||
Assertions.checkState(
|
|
||||||
readBytes(inputStream, null, null, (int) elementContentSize) == RESULT_CONTINUE);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Invalid element type " + type);
|
|
||||||
|
|
||||||
}
|
|
||||||
prepareForNextElement();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The total number of bytes consumed by the reader since first created
|
* The total number of bytes consumed by the reader since first created or last {@link #reset()}.
|
||||||
* or last {@link #reset()}.
|
|
||||||
*/
|
*/
|
||||||
protected final long getBytesRead() {
|
public long getBytesRead();
|
||||||
return bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the entire state of the reader so that it will read a new EBML structure from scratch.
|
* Resets the entire state of the reader so that it will read a new EBML structure from scratch.
|
||||||
* This includes resetting the value returned from {@link #getBytesRead()} to 0 and discarding
|
*
|
||||||
* all pending {@link #onMasterElementEnd(int)} events.
|
* <p>This includes resetting the value returned from {@link #getBytesRead()} to 0 and discarding
|
||||||
|
* all pending {@link EbmlEventHandler#onMasterElementEnd(int)} events.
|
||||||
*/
|
*/
|
||||||
protected final void reset() {
|
public void reset();
|
||||||
prepareForNextElement();
|
|
||||||
masterElementsStack.clear();
|
|
||||||
bytesRead = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads, parses, and returns an EBML variable-length integer (varint) from the contents
|
* Reads, parses, and returns an EBML variable-length integer (varint) from the contents
|
||||||
* of a binary element.
|
* of a binary element.
|
||||||
*
|
*
|
||||||
* @param inputStream The input stream from which data should be read.
|
* @param inputStream The input stream from which data should be read
|
||||||
* @return The varint value at the current position of the contents of a binary element.
|
* @return The varint value at the current position of the contents of a binary element
|
||||||
*/
|
*/
|
||||||
protected final long readVarint(NonBlockingInputStream inputStream) {
|
public long readVarint(NonBlockingInputStream inputStream);
|
||||||
varintBytesState = STATE_BEGIN_READING;
|
|
||||||
Assertions.checkState(readVarintBytes(inputStream) == RESULT_CONTINUE);
|
|
||||||
return parseTempByteArray(varintBytesLength, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a fixed number of bytes from the contents of a binary element into a {@link ByteBuffer}.
|
* Reads a fixed number of bytes from the contents of a binary element into a {@link ByteBuffer}.
|
||||||
*
|
*
|
||||||
* @param inputStream The input stream from which data should be read.
|
* @param inputStream The input stream from which data should be read
|
||||||
* @param byteBuffer The {@link ByteBuffer} to which data should be written.
|
* @param byteBuffer The {@link ByteBuffer} to which data should be written
|
||||||
* @param totalBytes The fixed number of bytes to be read and written.
|
* @param totalBytes The fixed number of bytes to be read and written
|
||||||
*/
|
*/
|
||||||
protected final void readBytes(
|
public void readBytes(NonBlockingInputStream inputStream, ByteBuffer byteBuffer, int totalBytes);
|
||||||
NonBlockingInputStream inputStream, ByteBuffer byteBuffer, int totalBytes) {
|
|
||||||
bytesState = 0;
|
|
||||||
Assertions.checkState(readBytes(inputStream, byteBuffer, null, totalBytes) == RESULT_CONTINUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a fixed number of bytes from the contents of a binary element into a {@code byte[]}.
|
* Reads a fixed number of bytes from the contents of a binary element into a {@code byte[]}.
|
||||||
*
|
*
|
||||||
* @param inputStream The input stream from which data should be read.
|
* @param inputStream The input stream from which data should be read
|
||||||
* @param byteArray The byte array to which data should be written.
|
* @param byteArray The byte array to which data should be written
|
||||||
* @param totalBytes The fixed number of bytes to be read and written.
|
* @param totalBytes The fixed number of bytes to be read and written
|
||||||
*/
|
*/
|
||||||
protected final void readBytes(
|
public void readBytes(NonBlockingInputStream inputStream, byte[] byteArray, int totalBytes);
|
||||||
NonBlockingInputStream inputStream, byte[] byteArray, int totalBytes) {
|
|
||||||
bytesState = 0;
|
|
||||||
Assertions.checkState(readBytes(inputStream, null, byteArray, totalBytes) == RESULT_CONTINUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips a fixed number of bytes from the contents of a binary element.
|
* Skips a fixed number of bytes from the contents of a binary element.
|
||||||
*
|
*
|
||||||
* @param inputStream The input stream from which data should be skipped.
|
* @param inputStream The input stream from which data should be skipped
|
||||||
* @param totalBytes The fixed number of bytes to be skipped.
|
* @param totalBytes The fixed number of bytes to be skipped
|
||||||
*/
|
*/
|
||||||
protected final void skipBytes(NonBlockingInputStream inputStream, int totalBytes) {
|
public void skipBytes(NonBlockingInputStream inputStream, int totalBytes);
|
||||||
bytesState = 0;
|
|
||||||
Assertions.checkState(readBytes(inputStream, null, null, totalBytes) == RESULT_CONTINUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets the internal state of {@link #read(NonBlockingInputStream)} so that it can start
|
|
||||||
* reading a new element from scratch.
|
|
||||||
*/
|
|
||||||
private final void prepareForNextElement() {
|
|
||||||
state = STATE_BEGIN_READING;
|
|
||||||
elementIdState = STATE_BEGIN_READING;
|
|
||||||
elementContentSizeState = STATE_BEGIN_READING;
|
|
||||||
elementOffset = bytesRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an element ID such that reading can be stopped and started again in a later call
|
|
||||||
* if not enough bytes are available. Returns {@link #RESULT_CONTINUE} if a full element ID
|
|
||||||
* has been read into {@link #elementId}. Reset {@link #elementIdState} to
|
|
||||||
* {@link #STATE_BEGIN_READING} before calling to indicate a new element ID should be read.
|
|
||||||
*
|
|
||||||
* @param inputStream The input stream from which an element ID should be read.
|
|
||||||
* @return One of the {@code RESULT_*} flags defined in this class.
|
|
||||||
*/
|
|
||||||
private int readElementId(NonBlockingInputStream inputStream) {
|
|
||||||
if (elementIdState == STATE_FINISHED_READING) {
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
if (elementIdState == STATE_BEGIN_READING) {
|
|
||||||
varintBytesState = STATE_BEGIN_READING;
|
|
||||||
elementIdState = STATE_READ_CONTENTS;
|
|
||||||
}
|
|
||||||
final int result = readVarintBytes(inputStream);
|
|
||||||
if (result != RESULT_CONTINUE) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
elementId = (int) parseTempByteArray(varintBytesLength, false);
|
|
||||||
elementIdState = STATE_FINISHED_READING;
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an element's content size such that reading can be stopped and started again in a later
|
|
||||||
* call if not enough bytes are available. Returns {@link #RESULT_CONTINUE} if an entire element
|
|
||||||
* size has been read into {@link #elementContentSize}. Reset {@link #elementContentSizeState} to
|
|
||||||
* {@link #STATE_BEGIN_READING} before calling to indicate a new element size should be read.
|
|
||||||
*
|
|
||||||
* @param inputStream The input stream from which an element size should be read.
|
|
||||||
* @return One of the {@code RESULT_*} flags defined in this class.
|
|
||||||
*/
|
|
||||||
private int readElementContentSize(NonBlockingInputStream inputStream) {
|
|
||||||
if (elementContentSizeState == STATE_FINISHED_READING) {
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
if (elementContentSizeState == STATE_BEGIN_READING) {
|
|
||||||
varintBytesState = STATE_BEGIN_READING;
|
|
||||||
elementContentSizeState = STATE_READ_CONTENTS;
|
|
||||||
}
|
|
||||||
final int result = readVarintBytes(inputStream);
|
|
||||||
if (result != RESULT_CONTINUE) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
elementContentSize = parseTempByteArray(varintBytesLength, true);
|
|
||||||
elementContentSizeState = STATE_FINISHED_READING;
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads an EBML variable-length integer (varint) such that reading can be stopped and started
|
|
||||||
* again in a later call if not enough bytes are available. Returns {@link #RESULT_CONTINUE} if
|
|
||||||
* an entire varint has been read into {@link #tempByteArray} and the length of the varint is in
|
|
||||||
* {@link #varintBytesLength}. Reset {@link #varintBytesState} to {@link #STATE_BEGIN_READING}
|
|
||||||
* before calling to indicate a new varint should be read.
|
|
||||||
*
|
|
||||||
* @param inputStream The input stream from which a varint should be read.
|
|
||||||
* @return One of the {@code RESULT_*} flags defined in this class.
|
|
||||||
*/
|
|
||||||
private int readVarintBytes(NonBlockingInputStream inputStream) {
|
|
||||||
if (varintBytesState == STATE_FINISHED_READING) {
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read first byte to get length.
|
|
||||||
if (varintBytesState == STATE_BEGIN_READING) {
|
|
||||||
bytesState = 0;
|
|
||||||
final int result = readBytes(inputStream, null, tempByteArray, 1);
|
|
||||||
if (result != RESULT_CONTINUE) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
varintBytesState = STATE_READ_CONTENTS;
|
|
||||||
|
|
||||||
final int firstByte = tempByteArray[0] & 0xff;
|
|
||||||
varintBytesLength = -1;
|
|
||||||
for (int i = 0; i < VARINT_LENGTH_MASKS.length; i++) {
|
|
||||||
if ((VARINT_LENGTH_MASKS[i] & firstByte) != 0) {
|
|
||||||
varintBytesLength = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (varintBytesLength == -1) {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"No valid varint length mask found at bytesRead = " + bytesRead);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read remaining bytes.
|
|
||||||
final int result = readBytes(inputStream, null, tempByteArray, varintBytesLength);
|
|
||||||
if (result != RESULT_CONTINUE) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All bytes have been read.
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads a set amount of bytes into a {@link ByteBuffer}, {@code byte[]}, or nowhere (skipping
|
|
||||||
* the bytes) such that reading can be stopped and started again later if not enough bytes are
|
|
||||||
* available. Returns {@link #RESULT_CONTINUE} if all bytes have been read. Reset
|
|
||||||
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
|
|
||||||
*
|
|
||||||
* <p>If both {@code byteBuffer} and {@code byteArray} are not null then bytes are only read
|
|
||||||
* into {@code byteBuffer}.
|
|
||||||
*
|
|
||||||
* @param inputStream The input stream from which bytes should be read.
|
|
||||||
* @param byteBuffer The optional {@link ByteBuffer} into which bytes should be read.
|
|
||||||
* @param byteArray The optional {@code byte[]} into which bytes should be read.
|
|
||||||
* @param totalBytes The total size of bytes to be read or skipped.
|
|
||||||
* @return One of the {@code RESULT_*} flags defined in this class.
|
|
||||||
*/
|
|
||||||
private int readBytes(
|
|
||||||
NonBlockingInputStream inputStream, ByteBuffer byteBuffer, byte[] byteArray, int totalBytes) {
|
|
||||||
if (bytesState == STATE_BEGIN_READING
|
|
||||||
&& ((byteBuffer != null && totalBytes > byteBuffer.capacity())
|
|
||||||
|| (byteArray != null && totalBytes > byteArray.length))) {
|
|
||||||
throw new IllegalStateException("Byte destination not large enough");
|
|
||||||
}
|
|
||||||
if (bytesState < totalBytes) {
|
|
||||||
final int remainingBytes = totalBytes - bytesState;
|
|
||||||
final int result;
|
|
||||||
if (byteBuffer != null) {
|
|
||||||
result = inputStream.read(byteBuffer, remainingBytes);
|
|
||||||
} else if (byteArray != null) {
|
|
||||||
result = inputStream.read(byteArray, bytesState, remainingBytes);
|
|
||||||
} else {
|
|
||||||
result = inputStream.skip(remainingBytes);
|
|
||||||
}
|
|
||||||
if (result == -1) {
|
|
||||||
return RESULT_END_OF_FILE;
|
|
||||||
}
|
|
||||||
bytesState += result;
|
|
||||||
bytesRead += result;
|
|
||||||
if (bytesState < totalBytes) {
|
|
||||||
return RESULT_NEED_MORE_DATA;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return RESULT_CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses and returns the integer value currently read into the first {@code byteLength} bytes
|
|
||||||
* of {@link #tempByteArray}. EBML varint length masks can optionally be removed.
|
|
||||||
*
|
|
||||||
* @param byteLength The number of bytes to parse from {@link #tempByteArray}.
|
|
||||||
* @param removeLengthMask Removes the variable-length integer length mask from the value.
|
|
||||||
* @return The resulting integer value. This value could be up to 8-bytes so a Java long is used.
|
|
||||||
*/
|
|
||||||
private long parseTempByteArray(int byteLength, boolean removeLengthMask) {
|
|
||||||
if (removeLengthMask) {
|
|
||||||
tempByteArray[0] &= ~VARINT_LENGTH_MASKS[varintBytesLength - 1];
|
|
||||||
}
|
|
||||||
long varint = 0;
|
|
||||||
for (int i = 0; i < byteLength; i++) {
|
|
||||||
// Shift all existing bits up one byte and add the next byte at the bottom.
|
|
||||||
varint = (varint << 8) | (tempByteArray[i] & 0xff);
|
|
||||||
}
|
|
||||||
return varint;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in {@link #masterElementsStack} to track when the current master element ends so that
|
|
||||||
* {@link #onMasterElementEnd(int)} is called.
|
|
||||||
*/
|
|
||||||
private static final class MasterElement {
|
|
||||||
|
|
||||||
private final int elementId;
|
|
||||||
private final long elementEndOffset;
|
|
||||||
|
|
||||||
private MasterElement(int elementId, long elementEndOffset) {
|
|
||||||
this.elementId = elementId;
|
|
||||||
this.elementEndOffset = elementEndOffset;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,97 +19,22 @@ import com.google.android.exoplayer.MediaFormat;
|
||||||
import com.google.android.exoplayer.SampleHolder;
|
import com.google.android.exoplayer.SampleHolder;
|
||||||
import com.google.android.exoplayer.parser.SegmentIndex;
|
import com.google.android.exoplayer.parser.SegmentIndex;
|
||||||
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
|
||||||
import com.google.android.exoplayer.util.LongArray;
|
|
||||||
import com.google.android.exoplayer.util.MimeTypes;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.media.MediaExtractor;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Facilitates the extraction of data from the WebM container format with a
|
* Extractor to facilitate data retrieval from the WebM container format.
|
||||||
* non-blocking, incremental parser based on {@link EbmlReader}.
|
|
||||||
*
|
*
|
||||||
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
|
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
|
||||||
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
|
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
|
||||||
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
|
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
|
||||||
*/
|
*/
|
||||||
@TargetApi(16)
|
public interface WebmExtractor {
|
||||||
public final class WebmExtractor extends EbmlReader {
|
|
||||||
|
|
||||||
private static final String DOC_TYPE_WEBM = "webm";
|
|
||||||
private static final String CODEC_ID_VP9 = "V_VP9";
|
|
||||||
private static final int UNKNOWN = -1;
|
|
||||||
|
|
||||||
// Element IDs
|
|
||||||
private static final int ID_EBML = 0x1A45DFA3;
|
|
||||||
private static final int ID_EBML_READ_VERSION = 0x42F7;
|
|
||||||
private static final int ID_DOC_TYPE = 0x4282;
|
|
||||||
private static final int ID_DOC_TYPE_READ_VERSION = 0x4285;
|
|
||||||
|
|
||||||
private static final int ID_SEGMENT = 0x18538067;
|
|
||||||
|
|
||||||
private static final int ID_INFO = 0x1549A966;
|
|
||||||
private static final int ID_TIMECODE_SCALE = 0x2AD7B1;
|
|
||||||
private static final int ID_DURATION = 0x4489;
|
|
||||||
|
|
||||||
private static final int ID_CLUSTER = 0x1F43B675;
|
|
||||||
private static final int ID_TIME_CODE = 0xE7;
|
|
||||||
private static final int ID_SIMPLE_BLOCK = 0xA3;
|
|
||||||
|
|
||||||
private static final int ID_TRACKS = 0x1654AE6B;
|
|
||||||
private static final int ID_TRACK_ENTRY = 0xAE;
|
|
||||||
private static final int ID_CODEC_ID = 0x86;
|
|
||||||
private static final int ID_VIDEO = 0xE0;
|
|
||||||
private static final int ID_PIXEL_WIDTH = 0xB0;
|
|
||||||
private static final int ID_PIXEL_HEIGHT = 0xBA;
|
|
||||||
|
|
||||||
private static final int ID_CUES = 0x1C53BB6B;
|
|
||||||
private static final int ID_CUE_POINT = 0xBB;
|
|
||||||
private static final int ID_CUE_TIME = 0xB3;
|
|
||||||
private static final int ID_CUE_TRACK_POSITIONS = 0xB7;
|
|
||||||
private static final int ID_CUE_CLUSTER_POSITION = 0xF1;
|
|
||||||
|
|
||||||
// SimpleBlock Lacing Values
|
|
||||||
private static final int LACING_NONE = 0;
|
|
||||||
private static final int LACING_XIPH = 1;
|
|
||||||
private static final int LACING_FIXED = 2;
|
|
||||||
private static final int LACING_EBML = 3;
|
|
||||||
|
|
||||||
private final byte[] simpleBlockTimecodeAndFlags = new byte[3];
|
|
||||||
|
|
||||||
private SampleHolder tempSampleHolder;
|
|
||||||
private boolean sampleRead;
|
|
||||||
|
|
||||||
private boolean prepared = false;
|
|
||||||
private long segmentStartPosition = UNKNOWN;
|
|
||||||
private long segmentEndPosition = UNKNOWN;
|
|
||||||
private long timecodeScale = 1000000L;
|
|
||||||
private long durationUs = UNKNOWN;
|
|
||||||
private int pixelWidth = UNKNOWN;
|
|
||||||
private int pixelHeight = UNKNOWN;
|
|
||||||
private int cuesByteSize = UNKNOWN;
|
|
||||||
private long clusterTimecodeUs = UNKNOWN;
|
|
||||||
private long simpleBlockTimecodeUs = UNKNOWN;
|
|
||||||
private MediaFormat format;
|
|
||||||
private SegmentIndex cues;
|
|
||||||
private LongArray cueTimesUs;
|
|
||||||
private LongArray cueClusterPositions;
|
|
||||||
|
|
||||||
public WebmExtractor() {
|
|
||||||
cueTimesUs = new LongArray();
|
|
||||||
cueClusterPositions = new LongArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the has parsed the cues and sample format from the stream.
|
* Whether the has parsed the cues and sample format from the stream.
|
||||||
*
|
*
|
||||||
* @return True if the extractor is prepared. False otherwise.
|
* @return True if the extractor is prepared. False otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isPrepared() {
|
public boolean isPrepared();
|
||||||
return prepared;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consumes data from a {@link NonBlockingInputStream}.
|
* Consumes data from a {@link NonBlockingInputStream}.
|
||||||
|
|
@ -118,289 +43,36 @@ public final class WebmExtractor extends EbmlReader {
|
||||||
* {@code sampleHolder}. Hence the same {@link SampleHolder} instance must be passed
|
* {@code sampleHolder}. Hence the same {@link SampleHolder} instance must be passed
|
||||||
* in subsequent calls until the whole sample has been read.
|
* in subsequent calls until the whole sample has been read.
|
||||||
*
|
*
|
||||||
* @param inputStream The input stream from which data should be read.
|
* @param inputStream The input stream from which data should be read
|
||||||
* @param sampleHolder A {@link SampleHolder} into which the sample should be read.
|
* @param sampleHolder A {@link SampleHolder} into which the sample should be read
|
||||||
* @return {@code true} if a sample has been read into the sample holder, otherwise {@code false}.
|
* @return {@code true} if a sample has been read into the sample holder
|
||||||
*/
|
*/
|
||||||
public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) {
|
public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder);
|
||||||
tempSampleHolder = sampleHolder;
|
|
||||||
sampleRead = false;
|
|
||||||
super.read(inputStream);
|
|
||||||
tempSampleHolder = null;
|
|
||||||
return sampleRead;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Seeks to a position before or equal to the requested time.
|
* Seeks to a position before or equal to the requested time.
|
||||||
*
|
*
|
||||||
* @param seekTimeUs The desired seek time in microseconds.
|
* @param seekTimeUs The desired seek time in microseconds
|
||||||
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
|
* @param allowNoop Allow the seek operation to do nothing if the seek time is in the current
|
||||||
* segment, is equal to or greater than the time of the current sample, and if there does not
|
* segment, is equal to or greater than the time of the current sample, and if there does not
|
||||||
* exist a sync frame between these two times.
|
* exist a sync frame between these two times
|
||||||
* @return True if the operation resulted in a change of state. False if it was a no-op.
|
* @return True if the operation resulted in a change of state. False if it was a no-op
|
||||||
*/
|
*/
|
||||||
public boolean seekTo(long seekTimeUs, boolean allowNoop) {
|
public boolean seekTo(long seekTimeUs, boolean allowNoop);
|
||||||
checkPrepared();
|
|
||||||
if (allowNoop && simpleBlockTimecodeUs != UNKNOWN && seekTimeUs >= simpleBlockTimecodeUs) {
|
|
||||||
final int clusterIndex = Arrays.binarySearch(cues.timesUs, clusterTimecodeUs);
|
|
||||||
if (clusterIndex >= 0 && seekTimeUs < clusterTimecodeUs + cues.durationsUs[clusterIndex]) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reset();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the cues for the media stream.
|
* Returns the cues for the media stream.
|
||||||
*
|
*
|
||||||
* @return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
|
* @return The cues in the form of a {@link SegmentIndex}, or null if the extractor is not yet
|
||||||
* prepared.
|
* prepared
|
||||||
*/
|
*/
|
||||||
public SegmentIndex getCues() {
|
public SegmentIndex getCues();
|
||||||
checkPrepared();
|
|
||||||
return cues;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the format of the samples contained within the media stream.
|
* Returns the format of the samples contained within the media stream.
|
||||||
*
|
*
|
||||||
* @return The sample media format, or null if the extracted is not yet prepared.
|
* @return The sample media format, or null if the extracted is not yet prepared
|
||||||
*/
|
*/
|
||||||
public MediaFormat getFormat() {
|
public MediaFormat getFormat();
|
||||||
checkPrepared();
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int getElementType(int id) {
|
|
||||||
switch (id) {
|
|
||||||
case ID_EBML:
|
|
||||||
case ID_SEGMENT:
|
|
||||||
case ID_INFO:
|
|
||||||
case ID_CLUSTER:
|
|
||||||
case ID_TRACKS:
|
|
||||||
case ID_TRACK_ENTRY:
|
|
||||||
case ID_VIDEO:
|
|
||||||
case ID_CUES:
|
|
||||||
case ID_CUE_POINT:
|
|
||||||
case ID_CUE_TRACK_POSITIONS:
|
|
||||||
return EbmlReader.TYPE_MASTER;
|
|
||||||
case ID_EBML_READ_VERSION:
|
|
||||||
case ID_DOC_TYPE_READ_VERSION:
|
|
||||||
case ID_TIMECODE_SCALE:
|
|
||||||
case ID_TIME_CODE:
|
|
||||||
case ID_PIXEL_WIDTH:
|
|
||||||
case ID_PIXEL_HEIGHT:
|
|
||||||
case ID_CUE_TIME:
|
|
||||||
case ID_CUE_CLUSTER_POSITION:
|
|
||||||
return EbmlReader.TYPE_UNSIGNED_INT;
|
|
||||||
case ID_DOC_TYPE:
|
|
||||||
case ID_CODEC_ID:
|
|
||||||
return EbmlReader.TYPE_STRING;
|
|
||||||
case ID_SIMPLE_BLOCK:
|
|
||||||
return EbmlReader.TYPE_BINARY;
|
|
||||||
case ID_DURATION:
|
|
||||||
return EbmlReader.TYPE_FLOAT;
|
|
||||||
default:
|
|
||||||
return EbmlReader.TYPE_UNKNOWN;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onMasterElementStart(
|
|
||||||
int id, long elementOffset, int headerSize, int contentsSize) {
|
|
||||||
switch (id) {
|
|
||||||
case ID_SEGMENT:
|
|
||||||
if (segmentStartPosition != UNKNOWN || segmentEndPosition != UNKNOWN) {
|
|
||||||
throw new IllegalStateException("Multiple Segment elements not supported");
|
|
||||||
}
|
|
||||||
segmentStartPosition = elementOffset + headerSize;
|
|
||||||
segmentEndPosition = elementOffset + headerSize + contentsSize;
|
|
||||||
break;
|
|
||||||
case ID_CUES:
|
|
||||||
cuesByteSize = headerSize + contentsSize;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onMasterElementEnd(int id) {
|
|
||||||
switch (id) {
|
|
||||||
case ID_CUES:
|
|
||||||
finishPreparing();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onIntegerElement(int id, long value) {
|
|
||||||
switch (id) {
|
|
||||||
case ID_EBML_READ_VERSION:
|
|
||||||
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
|
|
||||||
if (value != 1) {
|
|
||||||
throw new IllegalStateException("EBMLReadVersion " + value + " not supported");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ID_DOC_TYPE_READ_VERSION:
|
|
||||||
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
|
|
||||||
if (value < 1 || value > 2) {
|
|
||||||
throw new IllegalStateException("DocTypeReadVersion " + value + " not supported");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ID_TIMECODE_SCALE:
|
|
||||||
timecodeScale = value;
|
|
||||||
break;
|
|
||||||
case ID_PIXEL_WIDTH:
|
|
||||||
pixelWidth = (int) value;
|
|
||||||
break;
|
|
||||||
case ID_PIXEL_HEIGHT:
|
|
||||||
pixelHeight = (int) value;
|
|
||||||
break;
|
|
||||||
case ID_CUE_TIME:
|
|
||||||
cueTimesUs.add(scaleTimecodeToUs(value));
|
|
||||||
break;
|
|
||||||
case ID_CUE_CLUSTER_POSITION:
|
|
||||||
cueClusterPositions.add(value);
|
|
||||||
break;
|
|
||||||
case ID_TIME_CODE:
|
|
||||||
clusterTimecodeUs = scaleTimecodeToUs(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onFloatElement(int id, double value) {
|
|
||||||
switch (id) {
|
|
||||||
case ID_DURATION:
|
|
||||||
durationUs = scaleTimecodeToUs(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onStringElement(int id, String value) {
|
|
||||||
switch (id) {
|
|
||||||
case ID_DOC_TYPE:
|
|
||||||
// Validate that DocType is supported. This extractor only supports "webm".
|
|
||||||
if (!DOC_TYPE_WEBM.equals(value)) {
|
|
||||||
throw new IllegalStateException("DocType " + value + " not supported");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ID_CODEC_ID:
|
|
||||||
// Validate that CodecID is supported. This extractor only supports "V_VP9".
|
|
||||||
if (!CODEC_ID_VP9.equals(value)) {
|
|
||||||
throw new IllegalStateException("CodecID " + value + " not supported");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean onBinaryElement(NonBlockingInputStream inputStream,
|
|
||||||
int id, long elementOffset, int headerSize, int contentsSize) {
|
|
||||||
switch (id) {
|
|
||||||
case ID_SIMPLE_BLOCK:
|
|
||||||
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
|
|
||||||
// for info about how data is organized in a SimpleBlock element.
|
|
||||||
|
|
||||||
// Value of trackNumber is not used but needs to be read.
|
|
||||||
readVarint(inputStream);
|
|
||||||
|
|
||||||
// Next three bytes have timecode and flags.
|
|
||||||
readBytes(inputStream, simpleBlockTimecodeAndFlags, 3);
|
|
||||||
|
|
||||||
// First two bytes of the three are the relative timecode.
|
|
||||||
final int timecode =
|
|
||||||
(simpleBlockTimecodeAndFlags[0] << 8) | (simpleBlockTimecodeAndFlags[1] & 0xff);
|
|
||||||
final long timecodeUs = scaleTimecodeToUs(timecode);
|
|
||||||
|
|
||||||
// Last byte of the three has some flags and the lacing value.
|
|
||||||
final boolean keyframe = (simpleBlockTimecodeAndFlags[2] & 0x80) == 0x80;
|
|
||||||
final boolean invisible = (simpleBlockTimecodeAndFlags[2] & 0x08) == 0x08;
|
|
||||||
final int lacing = (simpleBlockTimecodeAndFlags[2] & 0x06) >> 1;
|
|
||||||
//final boolean discardable = (simpleBlockTimecodeAndFlags[2] & 0x01) == 0x01; // Not used.
|
|
||||||
|
|
||||||
// Validate lacing and set info into sample holder.
|
|
||||||
switch (lacing) {
|
|
||||||
case LACING_NONE:
|
|
||||||
final long elementEndOffset = elementOffset + headerSize + contentsSize;
|
|
||||||
simpleBlockTimecodeUs = clusterTimecodeUs + timecodeUs;
|
|
||||||
tempSampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
|
|
||||||
tempSampleHolder.decodeOnly = invisible;
|
|
||||||
tempSampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
|
|
||||||
tempSampleHolder.size = (int) (elementEndOffset - getBytesRead());
|
|
||||||
break;
|
|
||||||
case LACING_EBML:
|
|
||||||
case LACING_FIXED:
|
|
||||||
case LACING_XIPH:
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Lacing mode " + lacing + " not supported");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read video data into sample holder.
|
|
||||||
readBytes(inputStream, tempSampleHolder.data, tempSampleHolder.size);
|
|
||||||
sampleRead = true;
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
skipBytes(inputStream, contentsSize);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long scaleTimecodeToUs(long unscaledTimecode) {
|
|
||||||
return (unscaledTimecode * timecodeScale) / 1000L;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long scaleTimecodeToUs(double unscaledTimecode) {
|
|
||||||
return (long) ((unscaledTimecode * timecodeScale) / 1000.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkPrepared() {
|
|
||||||
if (!prepared) {
|
|
||||||
throw new IllegalStateException("Parser not yet prepared");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finishPreparing() {
|
|
||||||
if (prepared
|
|
||||||
|| segmentStartPosition == UNKNOWN || segmentEndPosition == UNKNOWN
|
|
||||||
|| durationUs == UNKNOWN
|
|
||||||
|| pixelWidth == UNKNOWN || pixelHeight == UNKNOWN
|
|
||||||
|| cuesByteSize == UNKNOWN
|
|
||||||
|| cueTimesUs.size() == 0 || cueTimesUs.size() != cueClusterPositions.size()) {
|
|
||||||
throw new IllegalStateException("Incorrect state in finishPreparing()");
|
|
||||||
}
|
|
||||||
|
|
||||||
format = MediaFormat.createVideoFormat(MimeTypes.VIDEO_VP9, MediaFormat.NO_VALUE, pixelWidth,
|
|
||||||
pixelHeight, null);
|
|
||||||
|
|
||||||
final int cuePointsSize = cueTimesUs.size();
|
|
||||||
final int sizeBytes = cuesByteSize;
|
|
||||||
final int[] sizes = new int[cuePointsSize];
|
|
||||||
final long[] offsets = new long[cuePointsSize];
|
|
||||||
final long[] durationsUs = new long[cuePointsSize];
|
|
||||||
final long[] timesUs = new long[cuePointsSize];
|
|
||||||
for (int i = 0; i < cuePointsSize; i++) {
|
|
||||||
timesUs[i] = cueTimesUs.get(i);
|
|
||||||
offsets[i] = segmentStartPosition + cueClusterPositions.get(i);
|
|
||||||
}
|
|
||||||
for (int i = 0; i < cuePointsSize - 1; i++) {
|
|
||||||
sizes[i] = (int) (offsets[i + 1] - offsets[i]);
|
|
||||||
durationsUs[i] = timesUs[i + 1] - timesUs[i];
|
|
||||||
}
|
|
||||||
sizes[cuePointsSize - 1] = (int) (segmentEndPosition - offsets[cuePointsSize - 1]);
|
|
||||||
durationsUs[cuePointsSize - 1] = durationUs - timesUs[cuePointsSize - 1];
|
|
||||||
cues = new SegmentIndex(sizeBytes, sizes, offsets, durationsUs, timesUs);
|
|
||||||
cueTimesUs = null;
|
|
||||||
cueClusterPositions = null;
|
|
||||||
|
|
||||||
prepared = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue