mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add support for extracting Vorbis audio in WebM Extractor.
This commit is contained in:
parent
6a544da2f8
commit
2472637264
6 changed files with 224 additions and 86 deletions
|
|
@ -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 {
|
||||||
// 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 @@
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 @@
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
* @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;
|
||||||
* @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 @@
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
*
|
*
|
||||||
* @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 @@
|
||||||
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;
|
||||||
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 {
|
||||||
|
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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 {
|
||||||
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 {
|
||||||
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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ boolean onFloatElement(int id, double value) {
|
/* package */ boolean onFloatElement(int id, double value) {
|
||||||
if (id == ID_DURATION) {
|
switch (id) {
|
||||||
durationUs = scaleTimecodeToUs((long) value);
|
case ID_DURATION:
|
||||||
|
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,62 +341,70 @@ 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) {
|
||||||
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
|
case ID_SIMPLE_BLOCK:
|
||||||
// for info about how data is organized in a SimpleBlock element.
|
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
|
||||||
|
// for info about how data is organized in a SimpleBlock element.
|
||||||
|
|
||||||
// If we don't have a sample holder then don't consume the data.
|
// If we don't have a sample holder then don't consume the data.
|
||||||
if (sampleHolder == null) {
|
if (sampleHolder == null) {
|
||||||
readResults |= RESULT_NEED_SAMPLE_HOLDER;
|
readResults |= RESULT_NEED_SAMPLE_HOLDER;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Value of trackNumber is not used but needs to be read.
|
// Value of trackNumber is not used but needs to be read.
|
||||||
reader.readVarint(inputStream);
|
reader.readVarint(inputStream);
|
||||||
|
|
||||||
// Next three bytes have timecode and flags.
|
// Next three bytes have timecode and flags.
|
||||||
reader.readBytes(inputStream, simpleBlockTimecodeAndFlags, 3);
|
reader.readBytes(inputStream, simpleBlockTimecodeAndFlags, 3);
|
||||||
|
|
||||||
// First two bytes of the three are the relative timecode.
|
// First two bytes of the three are the relative timecode.
|
||||||
int timecode =
|
int timecode =
|
||||||
(simpleBlockTimecodeAndFlags[0] << 8) | (simpleBlockTimecodeAndFlags[1] & 0xff);
|
(simpleBlockTimecodeAndFlags[0] << 8) | (simpleBlockTimecodeAndFlags[1] & 0xff);
|
||||||
long timecodeUs = scaleTimecodeToUs(timecode);
|
long timecodeUs = scaleTimecodeToUs(timecode);
|
||||||
|
|
||||||
// Last byte of the three has some flags and the lacing value.
|
// Last byte of the three has some flags and the lacing value.
|
||||||
boolean keyframe = (simpleBlockTimecodeAndFlags[2] & 0x80) == 0x80;
|
boolean keyframe = (simpleBlockTimecodeAndFlags[2] & 0x80) == 0x80;
|
||||||
boolean invisible = (simpleBlockTimecodeAndFlags[2] & 0x08) == 0x08;
|
boolean invisible = (simpleBlockTimecodeAndFlags[2] & 0x08) == 0x08;
|
||||||
int lacing = (simpleBlockTimecodeAndFlags[2] & 0x06) >> 1;
|
int lacing = (simpleBlockTimecodeAndFlags[2] & 0x06) >> 1;
|
||||||
|
|
||||||
// Validate lacing and set info into sample holder.
|
// Validate lacing and set info into sample holder.
|
||||||
switch (lacing) {
|
switch (lacing) {
|
||||||
case LACING_NONE:
|
case LACING_NONE:
|
||||||
long elementEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
|
long elementEndOffsetBytes = elementOffsetBytes + headerSizeBytes + contentsSizeBytes;
|
||||||
simpleBlockTimecodeUs = clusterTimecodeUs + timecodeUs;
|
simpleBlockTimecodeUs = clusterTimecodeUs + timecodeUs;
|
||||||
sampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
|
sampleHolder.flags = keyframe ? MediaExtractor.SAMPLE_FLAG_SYNC : 0;
|
||||||
sampleHolder.decodeOnly = invisible;
|
sampleHolder.decodeOnly = invisible;
|
||||||
sampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
|
sampleHolder.timeUs = clusterTimecodeUs + timecodeUs;
|
||||||
sampleHolder.size = (int) (elementEndOffsetBytes - reader.getBytesRead());
|
sampleHolder.size = (int) (elementEndOffsetBytes - reader.getBytesRead());
|
||||||
break;
|
break;
|
||||||
case LACING_EBML:
|
case LACING_EBML:
|
||||||
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) {
|
||||||
sampleHolder.replaceBuffer(sampleHolder.size);
|
sampleHolder.replaceBuffer(sampleHolder.size);
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer outputData = sampleHolder.data;
|
ByteBuffer outputData = sampleHolder.data;
|
||||||
if (outputData == null) {
|
if (outputData == null) {
|
||||||
reader.skipBytes(inputStream, sampleHolder.size);
|
reader.skipBytes(inputStream, sampleHolder.size);
|
||||||
sampleHolder.size = 0;
|
sampleHolder.size = 0;
|
||||||
} else {
|
} else {
|
||||||
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 {
|
||||||
*
|
*
|
||||||
* <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 {
|
||||||
* 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];
|
||||||
|
|
@ -423,6 +491,58 @@ public final class WebmExtractor implements Extractor {
|
||||||
readResults |= RESULT_READ_INDEX;
|
readResults |= RESULT_READ_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
||||||
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";
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue