diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java index 57714f380e..24173d3617 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java @@ -29,6 +29,7 @@ import com.google.android.exoplayer.chunk.MediaChunk; import com.google.android.exoplayer.chunk.WebmMediaChunk; import com.google.android.exoplayer.dash.mpd.Representation; 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.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSpec; @@ -85,7 +86,7 @@ public class DashWebmChunkSource implements ChunkSource { formats[i] = representations[i].format; maxWidth = Math.max(formats[i].width, maxWidth); 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.maxWidth = maxWidth; diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java new file mode 100644 index 0000000000..f66b83293f --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java @@ -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. + * + *
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 here. + */ +/* 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. + * + *
{@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 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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;
+ }
+
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
new file mode 100644
index 0000000000..b0b0936fe1
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
@@ -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.
+ *
+ * WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
+ * Matroska is available here.
+ * More info about WebM is here.
+ */
+@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;
+ }
+
+}
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
new file mode 100644
index 0000000000..1ddb51c589
--- /dev/null
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
@@ -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.
+ *
+ * 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}.
+ *
+ * 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}.
+ *
+ * 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}.
+ *
+ * Several methods in {@link EbmlReader} are available for reading the contents of a
+ * binary element:
+ * 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 here.
*/
-public abstract class EbmlReader {
+/* package */ interface EbmlReader {
// Element Types
- protected static final int TYPE_UNKNOWN = 0; // Undefined element.
- protected static final int TYPE_MASTER = 1; // Contains child elements.
- protected static final int TYPE_UNSIGNED_INT = 2;
- protected static final int TYPE_STRING = 3;
- protected static final int TYPE_BINARY = 4;
- protected static final int TYPE_FLOAT = 5;
+ /** Undefined element. */
+ public static final int TYPE_UNKNOWN = 0;
+ /** Contains child elements. */
+ public static final int TYPE_MASTER = 1;
+ /** Unsigned integer value of up to 8 bytes. */
+ 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.
- protected static final int RESULT_CONTINUE = 0;
- protected static final int RESULT_NEED_MORE_DATA = 1;
- protected static final int RESULT_END_OF_FILE = 2;
+ // Return values for reading methods.
+ public static final int READ_RESULT_CONTINUE = 0;
+ public static final int READ_RESULT_NEED_MORE_DATA = 1;
+ public static final int READ_RESULT_END_OF_FILE = 2;
- // 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. {@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 Several methods are available for reading the contents of a binary element:
- * This includes resetting the value returned from {@link #getBytesRead()} to 0 and discarding
+ * all pending {@link EbmlEventHandler#onMasterElementEnd(int)} events.
*/
- protected final void reset() {
- prepareForNextElement();
- masterElementsStack.clear();
- bytesRead = 0;
- }
+ public void reset();
/**
* Reads, parses, and returns an EBML variable-length integer (varint) from the contents
* of a binary element.
*
- * @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.
+ * @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
*/
- protected final long readVarint(NonBlockingInputStream inputStream) {
- varintBytesState = STATE_BEGIN_READING;
- Assertions.checkState(readVarintBytes(inputStream) == RESULT_CONTINUE);
- return parseTempByteArray(varintBytesLength, true);
- }
+ public long readVarint(NonBlockingInputStream inputStream);
/**
* 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 byteBuffer The {@link ByteBuffer} to which data should be written.
- * @param totalBytes The fixed number of bytes to be read and written.
+ * @param inputStream The input stream from which data should be read
+ * @param byteBuffer The {@link ByteBuffer} to which data should be written
+ * @param totalBytes The fixed number of bytes to be read and written
*/
- protected final void readBytes(
- NonBlockingInputStream inputStream, ByteBuffer byteBuffer, int totalBytes) {
- bytesState = 0;
- Assertions.checkState(readBytes(inputStream, byteBuffer, null, totalBytes) == RESULT_CONTINUE);
- }
+ public void readBytes(NonBlockingInputStream inputStream, ByteBuffer byteBuffer, int totalBytes);
/**
* 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 byteArray The byte array to which data should be written.
- * @param totalBytes The fixed number of bytes to be read and written.
+ * @param inputStream The input stream from which data should be read
+ * @param byteArray The byte array to which data should be written
+ * @param totalBytes The fixed number of bytes to be read and written
*/
- protected final void readBytes(
- NonBlockingInputStream inputStream, byte[] byteArray, int totalBytes) {
- bytesState = 0;
- Assertions.checkState(readBytes(inputStream, null, byteArray, totalBytes) == RESULT_CONTINUE);
- }
+ public void readBytes(NonBlockingInputStream inputStream, byte[] byteArray, int totalBytes);
/**
* 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 totalBytes The fixed number of bytes to be skipped.
+ * @param inputStream The input stream from which data should be skipped
+ * @param totalBytes The fixed number of bytes to be skipped
*/
- protected final 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.
- *
- * 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;
- }
-
- }
+ public void skipBytes(NonBlockingInputStream inputStream, int totalBytes);
}
diff --git a/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java b/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
index 49b82f4a16..4ecefe7906 100644
--- a/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
+++ b/library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
@@ -19,97 +19,22 @@ 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;
/**
- * Facilitates the extraction of data from the WebM container format with a
- * non-blocking, incremental parser based on {@link EbmlReader}.
+ * Extractor to facilitate data retrieval from the WebM container format.
*
* WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* Matroska is available here.
* More info about WebM is here.
*/
-@TargetApi(16)
-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();
- }
+public interface WebmExtractor {
/**
* 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() {
- return prepared;
- }
+ public boolean isPrepared();
/**
* 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
* in subsequent calls until the whole sample has been read.
*
- * @param inputStream The input stream from which data 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}.
+ * @param inputStream The input stream from which data 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
*/
- public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) {
- tempSampleHolder = sampleHolder;
- sampleRead = false;
- super.read(inputStream);
- tempSampleHolder = null;
- return sampleRead;
- }
+ public boolean read(NonBlockingInputStream inputStream, SampleHolder sampleHolder);
/**
* 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
* 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.
- * @return True if the operation resulted in a change of state. False if it was a no-op.
+ * 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
*/
- 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;
- }
+ public boolean seekTo(long seekTimeUs, boolean allowNoop);
/**
* 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
- * prepared.
+ * prepared
*/
- public SegmentIndex getCues() {
- checkPrepared();
- return cues;
- }
+ public SegmentIndex getCues();
/**
* 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() {
- 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;
- }
+ public MediaFormat getFormat();
}
+ *
- *