Commit 24726372 by Oliver Woodman

Add support for extracting Vorbis audio in WebM Extractor.

parent 6a544da2
...@@ -87,7 +87,7 @@ public class DashChunkSource implements ChunkSource { ...@@ -87,7 +87,7 @@ public class DashChunkSource 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);
Extractor extractor = formats[i].mimeType.startsWith(MimeTypes.VIDEO_WEBM) Extractor extractor = mimeTypeIsWebm(formats[i].mimeType)
? new WebmExtractor() : new FragmentedMp4Extractor(); ? new WebmExtractor() : new FragmentedMp4Extractor();
extractors.put(formats[i].id, extractor); extractors.put(formats[i].id, extractor);
this.representations.put(formats[i].id, representations[i]); this.representations.put(formats[i].id, representations[i]);
...@@ -197,6 +197,10 @@ public class DashChunkSource implements ChunkSource { ...@@ -197,6 +197,10 @@ public class DashChunkSource implements ChunkSource {
// Do nothing. // Do nothing.
} }
private boolean mimeTypeIsWebm(String mimeType) {
return mimeType.startsWith(MimeTypes.VIDEO_WEBM) || mimeType.startsWith(MimeTypes.AUDIO_WEBM);
}
private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri, private Chunk newInitializationChunk(RangedUri initializationUri, RangedUri indexUri,
Representation representation, Extractor extractor, DataSource dataSource, Representation representation, Extractor extractor, DataSource dataSource,
int trigger) { int trigger) {
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.parser.webm; package com.google.android.exoplayer.parser.webm;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
...@@ -134,7 +135,7 @@ import java.util.Stack; ...@@ -134,7 +135,7 @@ import java.util.Stack;
} }
@Override @Override
public int read(NonBlockingInputStream inputStream) { public int read(NonBlockingInputStream inputStream) throws ParserException {
Assertions.checkState(eventHandler != null); Assertions.checkState(eventHandler != null);
while (true) { while (true) {
while (!masterElementsStack.isEmpty() while (!masterElementsStack.isEmpty()
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.parser.webm; package com.google.android.exoplayer.parser.webm;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
...@@ -46,41 +47,47 @@ import java.nio.ByteBuffer; ...@@ -46,41 +47,47 @@ import java.nio.ByteBuffer;
* @param elementOffsetBytes The byte offset where this element starts * @param elementOffsetBytes The byte offset where this element starts
* @param headerSizeBytes The byte length of this element's ID and size header * @param headerSizeBytes The byte length of this element's ID and size header
* @param contentsSizeBytes The byte length of this element's children * @param contentsSizeBytes The byte length of this element's children
* @throws ParserException If a parsing error occurs.
*/ */
public void onMasterElementStart( public void onMasterElementStart(
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes); int id, long elementOffsetBytes, int headerSizeBytes,
long contentsSizeBytes) throws ParserException;
/** /**
* Called when a master element has finished reading in all of its children from the * Called when a master element has finished reading in all of its children from the
* {@link NonBlockingInputStream}. * {@link NonBlockingInputStream}.
* *
* @param id The integer ID of this element * @param id The integer ID of this element
* @throws ParserException If a parsing error occurs.
*/ */
public void onMasterElementEnd(int id); public void onMasterElementEnd(int id) throws ParserException;
/** /**
* Called when an integer element is encountered in the {@link NonBlockingInputStream}. * Called when an integer element is encountered in the {@link NonBlockingInputStream}.
* *
* @param id The integer ID of this element * @param id The integer ID of this element
* @param value The integer value this element contains * @param value The integer value this element contains
* @throws ParserException If a parsing error occurs.
*/ */
public void onIntegerElement(int id, long value); public void onIntegerElement(int id, long value) throws ParserException;
/** /**
* Called when a float element is encountered in the {@link NonBlockingInputStream}. * Called when a float element is encountered in the {@link NonBlockingInputStream}.
* *
* @param id The integer ID of this element * @param id The integer ID of this element
* @param value The float value this element contains * @param value The float value this element contains
* @throws ParserException If a parsing error occurs.
*/ */
public void onFloatElement(int id, double value); public void onFloatElement(int id, double value) throws ParserException;
/** /**
* Called when a string element is encountered in the {@link NonBlockingInputStream}. * Called when a string element is encountered in the {@link NonBlockingInputStream}.
* *
* @param id The integer ID of this element * @param id The integer ID of this element
* @param value The string value this element contains * @param value The string value this element contains
* @throws ParserException If a parsing error occurs.
*/ */
public void onStringElement(int id, String value); public void onStringElement(int id, String value) throws ParserException;
/** /**
* Called when a binary element is encountered in the {@link NonBlockingInputStream}. * Called when a binary element is encountered in the {@link NonBlockingInputStream}.
...@@ -109,9 +116,10 @@ import java.nio.ByteBuffer; ...@@ -109,9 +116,10 @@ import java.nio.ByteBuffer;
* @param inputStream The {@link NonBlockingInputStream} from which this * @param inputStream The {@link NonBlockingInputStream} from which this
* element's contents should be read * element's contents should be read
* @return True if the element was read. False otherwise. * @return True if the element was read. False otherwise.
* @throws ParserException If a parsing error occurs.
*/ */
public boolean onBinaryElement( public boolean onBinaryElement(
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes, int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
NonBlockingInputStream inputStream); NonBlockingInputStream inputStream) throws ParserException;
} }
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer.parser.webm; package com.google.android.exoplayer.parser.webm;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
...@@ -53,8 +54,9 @@ import java.nio.ByteBuffer; ...@@ -53,8 +54,9 @@ import java.nio.ByteBuffer;
* *
* @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 interface * @return One of the {@code RESULT_*} flags defined in this interface
* @throws ParserException If parsing fails.
*/ */
public int read(NonBlockingInputStream inputStream); public int read(NonBlockingInputStream inputStream) throws ParserException;
/** /**
* The total number of bytes consumed by the reader since first created or last {@link #reset()}. * The total number of bytes consumed by the reader since first created or last {@link #reset()}.
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer.parser.webm; package com.google.android.exoplayer.parser.webm;
import com.google.android.exoplayer.MediaFormat; import com.google.android.exoplayer.MediaFormat;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.parser.Extractor; import com.google.android.exoplayer.parser.Extractor;
import com.google.android.exoplayer.parser.SegmentIndex; import com.google.android.exoplayer.parser.SegmentIndex;
...@@ -27,6 +28,7 @@ import android.annotation.TargetApi; ...@@ -27,6 +28,7 @@ import android.annotation.TargetApi;
import android.media.MediaExtractor; import android.media.MediaExtractor;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
...@@ -44,6 +46,8 @@ public final class WebmExtractor implements Extractor { ...@@ -44,6 +46,8 @@ public final class WebmExtractor implements Extractor {
private static final String DOC_TYPE_WEBM = "webm"; private static final String DOC_TYPE_WEBM = "webm";
private static final String CODEC_ID_VP9 = "V_VP9"; private static final String CODEC_ID_VP9 = "V_VP9";
private static final String CODEC_ID_VORBIS = "A_VORBIS";
private static final int VORBIS_MAX_INPUT_SIZE = 8192;
private static final int UNKNOWN = -1; private static final int UNKNOWN = -1;
// Element IDs // Element IDs
...@@ -65,9 +69,13 @@ public final class WebmExtractor implements Extractor { ...@@ -65,9 +69,13 @@ public final class WebmExtractor implements Extractor {
private static final int ID_TRACKS = 0x1654AE6B; private static final int ID_TRACKS = 0x1654AE6B;
private static final int ID_TRACK_ENTRY = 0xAE; private static final int ID_TRACK_ENTRY = 0xAE;
private static final int ID_CODEC_ID = 0x86; private static final int ID_CODEC_ID = 0x86;
private static final int ID_CODEC_PRIVATE = 0x63A2;
private static final int ID_VIDEO = 0xE0; private static final int ID_VIDEO = 0xE0;
private static final int ID_PIXEL_WIDTH = 0xB0; private static final int ID_PIXEL_WIDTH = 0xB0;
private static final int ID_PIXEL_HEIGHT = 0xBA; private static final int ID_PIXEL_HEIGHT = 0xBA;
private static final int ID_AUDIO = 0xE1;
private static final int ID_CHANNELS = 0x9F;
private static final int ID_SAMPLING_FREQUENCY = 0xB5;
private static final int ID_CUES = 0x1C53BB6B; private static final int ID_CUES = 0x1C53BB6B;
private static final int ID_CUE_POINT = 0xBB; private static final int ID_CUE_POINT = 0xBB;
...@@ -96,6 +104,10 @@ public final class WebmExtractor implements Extractor { ...@@ -96,6 +104,10 @@ public final class WebmExtractor implements Extractor {
private long durationUs = UNKNOWN; private long durationUs = UNKNOWN;
private int pixelWidth = UNKNOWN; private int pixelWidth = UNKNOWN;
private int pixelHeight = UNKNOWN; private int pixelHeight = UNKNOWN;
private int channelCount = UNKNOWN;
private int sampleRate = UNKNOWN;
private byte[] codecPrivate;
private boolean seenAudioTrack;
private long cuesSizeBytes = UNKNOWN; private long cuesSizeBytes = UNKNOWN;
private long clusterTimecodeUs = UNKNOWN; private long clusterTimecodeUs = UNKNOWN;
private long simpleBlockTimecodeUs = UNKNOWN; private long simpleBlockTimecodeUs = UNKNOWN;
...@@ -114,7 +126,8 @@ public final class WebmExtractor implements Extractor { ...@@ -114,7 +126,8 @@ public final class WebmExtractor implements Extractor {
} }
@Override @Override
public int read(NonBlockingInputStream inputStream, SampleHolder sampleHolder) { public int read(
NonBlockingInputStream inputStream, SampleHolder sampleHolder) throws ParserException {
this.sampleHolder = sampleHolder; this.sampleHolder = sampleHolder;
this.readResults = 0; this.readResults = 0;
while ((readResults & READ_TERMINATING_RESULTS) == 0) { while ((readResults & READ_TERMINATING_RESULTS) == 0) {
...@@ -176,6 +189,7 @@ public final class WebmExtractor implements Extractor { ...@@ -176,6 +189,7 @@ public final class WebmExtractor implements Extractor {
case ID_CLUSTER: case ID_CLUSTER:
case ID_TRACKS: case ID_TRACKS:
case ID_TRACK_ENTRY: case ID_TRACK_ENTRY:
case ID_AUDIO:
case ID_VIDEO: case ID_VIDEO:
case ID_CUES: case ID_CUES:
case ID_CUE_POINT: case ID_CUE_POINT:
...@@ -187,6 +201,7 @@ public final class WebmExtractor implements Extractor { ...@@ -187,6 +201,7 @@ public final class WebmExtractor implements Extractor {
case ID_TIME_CODE: case ID_TIME_CODE:
case ID_PIXEL_WIDTH: case ID_PIXEL_WIDTH:
case ID_PIXEL_HEIGHT: case ID_PIXEL_HEIGHT:
case ID_CHANNELS:
case ID_CUE_TIME: case ID_CUE_TIME:
case ID_CUE_CLUSTER_POSITION: case ID_CUE_CLUSTER_POSITION:
return EbmlReader.TYPE_UNSIGNED_INT; return EbmlReader.TYPE_UNSIGNED_INT;
...@@ -194,8 +209,10 @@ public final class WebmExtractor implements Extractor { ...@@ -194,8 +209,10 @@ public final class WebmExtractor implements Extractor {
case ID_CODEC_ID: case ID_CODEC_ID:
return EbmlReader.TYPE_STRING; return EbmlReader.TYPE_STRING;
case ID_SIMPLE_BLOCK: case ID_SIMPLE_BLOCK:
case ID_CODEC_PRIVATE:
return EbmlReader.TYPE_BINARY; return EbmlReader.TYPE_BINARY;
case ID_DURATION: case ID_DURATION:
case ID_SAMPLING_FREQUENCY:
return EbmlReader.TYPE_FLOAT; return EbmlReader.TYPE_FLOAT;
default: default:
return EbmlReader.TYPE_UNKNOWN; return EbmlReader.TYPE_UNKNOWN;
...@@ -203,11 +220,12 @@ public final class WebmExtractor implements Extractor { ...@@ -203,11 +220,12 @@ public final class WebmExtractor implements Extractor {
} }
/* package */ boolean onMasterElementStart( /* package */ boolean onMasterElementStart(
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes) { int id, long elementOffsetBytes, int headerSizeBytes,
long contentsSizeBytes) throws ParserException {
switch (id) { switch (id) {
case ID_SEGMENT: case ID_SEGMENT:
if (segmentStartOffsetBytes != UNKNOWN || segmentEndOffsetBytes != UNKNOWN) { if (segmentStartOffsetBytes != UNKNOWN || segmentEndOffsetBytes != UNKNOWN) {
throw new IllegalStateException("Multiple Segment elements not supported"); throw new ParserException("Multiple Segment elements not supported");
} }
segmentStartOffsetBytes = elementOffsetBytes + headerSizeBytes; segmentStartOffsetBytes = elementOffsetBytes + headerSizeBytes;
segmentEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes; segmentEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
...@@ -223,31 +241,41 @@ public final class WebmExtractor implements Extractor { ...@@ -223,31 +241,41 @@ public final class WebmExtractor implements Extractor {
return true; return true;
} }
/* package */ boolean onMasterElementEnd(int id) { /* package */ boolean onMasterElementEnd(int id) throws ParserException {
switch (id) { switch (id) {
case ID_CUES: case ID_CUES:
buildCues(); buildCues();
return false; return false;
case ID_VIDEO: case ID_VIDEO:
buildFormat(); buildVideoFormat();
return true;
case ID_AUDIO:
seenAudioTrack = true;
return true;
case ID_TRACK_ENTRY:
if (seenAudioTrack) {
// Audio format has to be built here since codec private may not be available at the end
// of ID_AUDIO.
buildAudioFormat();
}
return true; return true;
default: default:
return true; return true;
} }
} }
/* package */ boolean onIntegerElement(int id, long value) { /* package */ boolean onIntegerElement(int id, long value) throws ParserException {
switch (id) { switch (id) {
case ID_EBML_READ_VERSION: case ID_EBML_READ_VERSION:
// Validate that EBMLReadVersion is supported. This extractor only supports v1. // Validate that EBMLReadVersion is supported. This extractor only supports v1.
if (value != 1) { if (value != 1) {
throw new IllegalArgumentException("EBMLReadVersion " + value + " not supported"); throw new ParserException("EBMLReadVersion " + value + " not supported");
} }
break; break;
case ID_DOC_TYPE_READ_VERSION: case ID_DOC_TYPE_READ_VERSION:
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2. // Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
if (value < 1 || value > 2) { if (value < 1 || value > 2) {
throw new IllegalArgumentException("DocTypeReadVersion " + value + " not supported"); throw new ParserException("DocTypeReadVersion " + value + " not supported");
} }
break; break;
case ID_TIMECODE_SCALE: case ID_TIMECODE_SCALE:
...@@ -259,6 +287,9 @@ public final class WebmExtractor implements Extractor { ...@@ -259,6 +287,9 @@ public final class WebmExtractor implements Extractor {
case ID_PIXEL_HEIGHT: case ID_PIXEL_HEIGHT:
pixelHeight = (int) value; pixelHeight = (int) value;
break; break;
case ID_CHANNELS:
channelCount = (int) value;
break;
case ID_CUE_TIME: case ID_CUE_TIME:
cueTimesUs.add(scaleTimecodeToUs(value)); cueTimesUs.add(scaleTimecodeToUs(value));
break; break;
...@@ -275,24 +306,31 @@ public final class WebmExtractor implements Extractor { ...@@ -275,24 +306,31 @@ public final class WebmExtractor implements Extractor {
} }
/* package */ boolean onFloatElement(int id, double value) { /* package */ boolean onFloatElement(int id, double value) {
if (id == ID_DURATION) { switch (id) {
case ID_DURATION:
durationUs = scaleTimecodeToUs((long) value); durationUs = scaleTimecodeToUs((long) value);
break;
case ID_SAMPLING_FREQUENCY:
sampleRate = (int) value;
break;
default:
// pass
} }
return true; return true;
} }
/* package */ boolean onStringElement(int id, String value) { /* package */ boolean onStringElement(int id, String value) throws ParserException {
switch (id) { switch (id) {
case ID_DOC_TYPE: case ID_DOC_TYPE:
// Validate that DocType is supported. This extractor only supports "webm". // Validate that DocType is supported. This extractor only supports "webm".
if (!DOC_TYPE_WEBM.equals(value)) { if (!DOC_TYPE_WEBM.equals(value)) {
throw new IllegalArgumentException("DocType " + value + " not supported"); throw new ParserException("DocType " + value + " not supported");
} }
break; break;
case ID_CODEC_ID: case ID_CODEC_ID:
// Validate that CodecID is supported. This extractor only supports "V_VP9". // Validate that CodecID is supported. This extractor only supports "V_VP9" and "A_VORBIS".
if (!CODEC_ID_VP9.equals(value)) { if (!CODEC_ID_VP9.equals(value) && !CODEC_ID_VORBIS.equals(value)) {
throw new IllegalArgumentException("CodecID " + value + " not supported"); throw new ParserException("CodecID " + value + " not supported");
} }
break; break;
default: default:
...@@ -303,8 +341,9 @@ public final class WebmExtractor implements Extractor { ...@@ -303,8 +341,9 @@ public final class WebmExtractor implements Extractor {
/* package */ boolean onBinaryElement( /* package */ boolean onBinaryElement(
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes, int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
NonBlockingInputStream inputStream) { NonBlockingInputStream inputStream) throws ParserException {
if (id == ID_SIMPLE_BLOCK) { switch (id) {
case ID_SIMPLE_BLOCK:
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure // Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element. // for info about how data is organized in a SimpleBlock element.
...@@ -344,7 +383,7 @@ public final class WebmExtractor implements Extractor { ...@@ -344,7 +383,7 @@ public final class WebmExtractor implements Extractor {
case LACING_FIXED: case LACING_FIXED:
case LACING_XIPH: case LACING_XIPH:
default: default:
throw new IllegalStateException("Lacing mode " + lacing + " not supported"); throw new ParserException("Lacing mode " + lacing + " not supported");
} }
if (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size) { if (sampleHolder.data == null || sampleHolder.data.capacity() < sampleHolder.size) {
...@@ -359,6 +398,13 @@ public final class WebmExtractor implements Extractor { ...@@ -359,6 +398,13 @@ public final class WebmExtractor implements Extractor {
reader.readBytes(inputStream, outputData, sampleHolder.size); reader.readBytes(inputStream, outputData, sampleHolder.size);
} }
readResults |= RESULT_READ_SAMPLE; readResults |= RESULT_READ_SAMPLE;
break;
case ID_CODEC_PRIVATE:
codecPrivate = new byte[contentsSizeBytes];
reader.readBytes(inputStream, codecPrivate, contentsSizeBytes);
break;
default:
// pass
} }
return true; return true;
} }
...@@ -372,16 +418,38 @@ public final class WebmExtractor implements Extractor { ...@@ -372,16 +418,38 @@ public final class WebmExtractor implements Extractor {
* *
* <p>Replaces the previous {@link #format} only if video width/height have changed. * <p>Replaces the previous {@link #format} only if video width/height have changed.
* {@link #format} is guaranteed to not be null after calling this method. In * {@link #format} is guaranteed to not be null after calling this method. In
* the event that it can't be built, an {@link IllegalStateException} will be thrown. * the event that it can't be built, an {@link ParserException} will be thrown.
*/ */
private void buildFormat() { private void buildVideoFormat() throws ParserException {
if (pixelWidth != UNKNOWN && pixelHeight != UNKNOWN if (pixelWidth != UNKNOWN && pixelHeight != UNKNOWN
&& (format == null || format.width != pixelWidth || format.height != pixelHeight)) { && (format == null || format.width != pixelWidth || format.height != pixelHeight)) {
format = MediaFormat.createVideoFormat( format = MediaFormat.createVideoFormat(
MimeTypes.VIDEO_VP9, MediaFormat.NO_VALUE, pixelWidth, pixelHeight, null); MimeTypes.VIDEO_VP9, MediaFormat.NO_VALUE, pixelWidth, pixelHeight, null);
readResults |= RESULT_READ_INIT; readResults |= RESULT_READ_INIT;
} else if (format == null) { } else if (format == null) {
throw new IllegalStateException("Unable to build format"); throw new ParserException("Unable to build format");
}
}
/**
* Build an audio {@link MediaFormat} containing recently gathered Audio information, if needed.
*
* <p>Replaces the previous {@link #format} only if audio channel count/sample rate have changed.
* {@link #format} is guaranteed to not be null after calling this method.
*
* @throws ParserException If an error occurs when parsing codec's private data or if the format
* can't be built.
*/
private void buildAudioFormat() throws ParserException {
if (channelCount != UNKNOWN && sampleRate != UNKNOWN
&& (format == null || format.channelCount != channelCount
|| format.sampleRate != sampleRate)) {
format = MediaFormat.createAudioFormat(
MimeTypes.AUDIO_VORBIS, VORBIS_MAX_INPUT_SIZE,
sampleRate, channelCount, parseVorbisCodecPrivate());
readResults |= RESULT_READ_INIT;
} else if (format == null) {
throw new ParserException("Unable to build format");
} }
} }
...@@ -389,18 +457,18 @@ public final class WebmExtractor implements Extractor { ...@@ -389,18 +457,18 @@ public final class WebmExtractor implements Extractor {
* Build a {@link SegmentIndex} containing recently gathered Cues information. * Build a {@link SegmentIndex} containing recently gathered Cues information.
* *
* <p>{@link #cues} is guaranteed to not be null after calling this method. In * <p>{@link #cues} is guaranteed to not be null after calling this method. In
* the event that it can't be built, an {@link IllegalStateException} will be thrown. * the event that it can't be built, an {@link ParserException} will be thrown.
*/ */
private void buildCues() { private void buildCues() throws ParserException {
if (segmentStartOffsetBytes == UNKNOWN) { if (segmentStartOffsetBytes == UNKNOWN) {
throw new IllegalStateException("Segment start/end offsets unknown"); throw new ParserException("Segment start/end offsets unknown");
} else if (durationUs == UNKNOWN) { } else if (durationUs == UNKNOWN) {
throw new IllegalStateException("Duration unknown"); throw new ParserException("Duration unknown");
} else if (cuesSizeBytes == UNKNOWN) { } else if (cuesSizeBytes == UNKNOWN) {
throw new IllegalStateException("Cues size unknown"); throw new ParserException("Cues size unknown");
} else if (cueTimesUs == null || cueClusterPositions == null } else if (cueTimesUs == null || cueClusterPositions == null
|| cueTimesUs.size() == 0 || cueTimesUs.size() != cueClusterPositions.size()) { || cueTimesUs.size() == 0 || cueTimesUs.size() != cueClusterPositions.size()) {
throw new IllegalStateException("Invalid/missing cue points"); throw new ParserException("Invalid/missing cue points");
} }
int cuePointsSize = cueTimesUs.size(); int cuePointsSize = cueTimesUs.size();
int[] sizes = new int[cuePointsSize]; int[] sizes = new int[cuePointsSize];
...@@ -424,6 +492,58 @@ public final class WebmExtractor implements Extractor { ...@@ -424,6 +492,58 @@ public final class WebmExtractor implements Extractor {
} }
/** /**
* Parses Vorbis Codec Private data and adds it as initialization data to the {@link #format}.
* WebM Vorbis Codec Private data specification can be found
* <a href="http://matroska.org/technical/specs/codecid/index.html">here</a>.
*
* @return ArrayList of byte arrays containing the initialization data on success.
* @throws ParserException If parsing codec private data fails.
*/
private ArrayList<byte[]> parseVorbisCodecPrivate() throws ParserException {
try {
if (codecPrivate[0] != 0x02) {
throw new ParserException("Error parsing vorbis codec private");
}
int offset = 1;
int vorbisInfoLength = 0;
while (codecPrivate[offset] == (byte) 0xFF) {
vorbisInfoLength += 0xFF;
offset++;
}
vorbisInfoLength += codecPrivate[offset++];
int vorbisSkipLength = 0;
while (codecPrivate[offset] == (byte) 0xFF) {
vorbisSkipLength += 0xFF;
offset++;
}
vorbisSkipLength += codecPrivate[offset++];
if (codecPrivate[offset] != 0x01) {
throw new ParserException("Error parsing vorbis codec private");
}
byte[] vorbisInfo = new byte[vorbisInfoLength];
System.arraycopy(codecPrivate, offset, vorbisInfo, 0, vorbisInfoLength);
offset += vorbisInfoLength;
if (codecPrivate[offset] != 0x03) {
throw new ParserException("Error parsing vorbis codec private");
}
offset += vorbisSkipLength;
if (codecPrivate[offset] != 0x05) {
throw new ParserException("Error parsing vorbis codec private");
}
byte[] vorbisBooks = new byte[codecPrivate.length - offset];
System.arraycopy(codecPrivate, offset, vorbisBooks, 0, codecPrivate.length - offset);
ArrayList<byte[]> initializationData = new ArrayList<byte[]>(2);
initializationData.add(vorbisInfo);
initializationData.add(vorbisBooks);
return initializationData;
} catch (ArrayIndexOutOfBoundsException e) {
throw new ParserException("Error parsing vorbis codec private");
}
}
/**
* Passes events through to {@link WebmExtractor} as * Passes events through to {@link WebmExtractor} as
* callbacks from {@link EbmlReader} are received. * callbacks from {@link EbmlReader} are received.
*/ */
...@@ -436,18 +556,19 @@ public final class WebmExtractor implements Extractor { ...@@ -436,18 +556,19 @@ public final class WebmExtractor implements Extractor {
@Override @Override
public void onMasterElementStart( public void onMasterElementStart(
int id, long elementOffsetBytes, int headerSizeBytes, long contentsSizeBytes) { int id, long elementOffsetBytes, int headerSizeBytes,
long contentsSizeBytes) throws ParserException {
WebmExtractor.this.onMasterElementStart( WebmExtractor.this.onMasterElementStart(
id, elementOffsetBytes, headerSizeBytes, contentsSizeBytes); id, elementOffsetBytes, headerSizeBytes, contentsSizeBytes);
} }
@Override @Override
public void onMasterElementEnd(int id) { public void onMasterElementEnd(int id) throws ParserException {
WebmExtractor.this.onMasterElementEnd(id); WebmExtractor.this.onMasterElementEnd(id);
} }
@Override @Override
public void onIntegerElement(int id, long value) { public void onIntegerElement(int id, long value) throws ParserException {
WebmExtractor.this.onIntegerElement(id, value); WebmExtractor.this.onIntegerElement(id, value);
} }
...@@ -457,14 +578,14 @@ public final class WebmExtractor implements Extractor { ...@@ -457,14 +578,14 @@ public final class WebmExtractor implements Extractor {
} }
@Override @Override
public void onStringElement(int id, String value) { public void onStringElement(int id, String value) throws ParserException {
WebmExtractor.this.onStringElement(id, value); WebmExtractor.this.onStringElement(id, value);
} }
@Override @Override
public boolean onBinaryElement( public boolean onBinaryElement(
int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes, int id, long elementOffsetBytes, int headerSizeBytes, int contentsSizeBytes,
NonBlockingInputStream inputStream) { NonBlockingInputStream inputStream) throws ParserException {
return WebmExtractor.this.onBinaryElement( return WebmExtractor.this.onBinaryElement(
id, elementOffsetBytes, headerSizeBytes, contentsSizeBytes, inputStream); id, elementOffsetBytes, headerSizeBytes, contentsSizeBytes, inputStream);
} }
......
...@@ -34,6 +34,8 @@ public class MimeTypes { ...@@ -34,6 +34,8 @@ public class MimeTypes {
public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm"; public static final String AUDIO_AAC = BASE_TYPE_AUDIO + "/mp4a-latm";
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3"; public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3"; public static final String AUDIO_EC3 = BASE_TYPE_AUDIO + "/eac3";
public static final String AUDIO_WEBM = BASE_TYPE_AUDIO + "/webm";
public static final String AUDIO_VORBIS = BASE_TYPE_AUDIO + "/vorbis";
public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt"; public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment