Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
686ac2a6
authored
Jul 09, 2014
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Refactor WebM extractor.
parent
9e16dec2
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1122 additions
and
822 deletions
library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
library/src/main/java/com/google/android/exoplayer/dash/DashWebmChunkSource.java
View file @
686ac2a6
...
...
@@ -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
Default
WebmExtractor
());
this
.
representations
.
put
(
formats
[
i
].
id
,
representations
[
i
]);
}
this
.
maxWidth
=
maxWidth
;
...
...
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultEbmlReader.java
0 → 100644
View file @
686ac2a6
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
webm
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.nio.ByteBuffer
;
import
java.nio.charset.Charset
;
import
java.util.Stack
;
/**
* Default version of a basic event-driven incremental EBML parser which needs an
* {@link EbmlEventHandler} to define IDs/types and react to events.
*
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
* It was originally designed for the Matroska container format. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
*/
/* package */
final
class
DefaultEbmlReader
implements
EbmlReader
{
// State values used in variables state, elementIdState, elementContentSizeState, and
// varintBytesState.
private
static
final
int
STATE_BEGIN_READING
=
0
;
private
static
final
int
STATE_READ_CONTENTS
=
1
;
private
static
final
int
STATE_FINISHED_READING
=
2
;
/**
* The first byte of a variable-length integer (varint) will have one of these bit masks
* indicating the total length in bytes.
*
* <p>{@code 0x80} is a one-byte integer, {@code 0x40} is two bytes, and so on up to eight bytes.
*/
private
static
final
int
[]
VARINT_LENGTH_MASKS
=
new
int
[]
{
0x80
,
0x40
,
0x20
,
0x10
,
0x08
,
0x04
,
0x02
,
0x01
};
private
static
final
int
MAX_INTEGER_ELEMENT_SIZE_BYTES
=
8
;
private
static
final
int
VALID_FLOAT32_ELEMENT_SIZE_BYTES
=
4
;
private
static
final
int
VALID_FLOAT64_ELEMENT_SIZE_BYTES
=
8
;
/**
* Scratch space to read in EBML varints, unsigned ints, and floats - each of which can be
* up to 8 bytes.
*/
private
final
byte
[]
tempByteArray
=
new
byte
[
8
];
private
final
Stack
<
MasterElement
>
masterElementsStack
=
new
Stack
<
MasterElement
>();
/**
* Current {@link EbmlEventHandler} which is queried for element types
* and informed of element events.
*/
private
EbmlEventHandler
eventHandler
;
/**
* Overall state for the current element. Must be one of the {@code STATE_*} constants.
*/
private
int
state
;
/**
* Total bytes read since starting or the last {@link #reset()}.
*/
private
long
bytesRead
;
/**
* The starting byte offset of the current element being parsed.
*/
private
long
elementOffset
;
/**
* Holds the current element ID after {@link #elementIdState} is {@link #STATE_FINISHED_READING}.
*/
private
int
elementId
;
/**
* State for the ID of the current element. Must be one of the {@code STATE_*} constants.
*/
private
int
elementIdState
;
/**
* Holds the current element content size after {@link #elementContentSizeState}
* is {@link #STATE_FINISHED_READING}.
*/
private
long
elementContentSize
;
/**
* State for the content size of the current element.
* Must be one of the {@code STATE_*} constants.
*/
private
int
elementContentSizeState
;
/**
* State for the current variable-length integer (varint) being read into
* {@link #tempByteArray}. Must be one of the {@code STATE_*} constants.
*/
private
int
varintBytesState
;
/**
* Length in bytes of the current variable-length integer (varint) being read into
* {@link #tempByteArray}.
*/
private
int
varintBytesLength
;
/**
* Counts the number of bytes being contiguously read into either {@link #tempByteArray} or
* {@link #stringBytes}. Used to determine when all required bytes have been read across
* multiple calls.
*/
private
int
bytesState
;
/**
* Holds string element bytes as they're being read in. Allocated after the element content
* size is known and released after calling {@link EbmlEventHandler#onStringElement(int, String)}.
*/
private
byte
[]
stringBytes
;
@Override
public
void
setEventHandler
(
EbmlEventHandler
eventHandler
)
{
this
.
eventHandler
=
eventHandler
;
}
@Override
public
int
read
(
NonBlockingInputStream
inputStream
)
{
Assertions
.
checkState
(
eventHandler
!=
null
);
while
(
true
)
{
while
(!
masterElementsStack
.
isEmpty
()
&&
bytesRead
>=
masterElementsStack
.
peek
().
elementEndOffsetBytes
)
{
if
(!
eventHandler
.
onMasterElementEnd
(
masterElementsStack
.
pop
().
elementId
))
{
return
READ_RESULT_CONTINUE
;
}
}
if
(
state
==
STATE_BEGIN_READING
)
{
int
idResult
=
readElementId
(
inputStream
);
if
(
idResult
!=
READ_RESULT_CONTINUE
)
{
return
idResult
;
}
int
sizeResult
=
readElementContentSize
(
inputStream
);
if
(
sizeResult
!=
READ_RESULT_CONTINUE
)
{
return
sizeResult
;
}
state
=
STATE_READ_CONTENTS
;
bytesState
=
0
;
}
int
type
=
eventHandler
.
getElementType
(
elementId
);
switch
(
type
)
{
case
TYPE_MASTER:
int
masterHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
// Header size is 12 bytes max.
masterElementsStack
.
add
(
new
MasterElement
(
elementId
,
bytesRead
+
elementContentSize
));
if
(!
eventHandler
.
onMasterElementStart
(
elementId
,
elementOffset
,
masterHeaderSize
,
elementContentSize
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_UNSIGNED_INT:
if
(
elementContentSize
>
MAX_INTEGER_ELEMENT_SIZE_BYTES
)
{
throw
new
IllegalStateException
(
"Invalid integer size "
+
elementContentSize
);
}
int
intResult
=
readBytesInternal
(
inputStream
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
intResult
!=
READ_RESULT_CONTINUE
)
{
return
intResult
;
}
long
intValue
=
getTempByteArrayValue
((
int
)
elementContentSize
,
false
);
if
(!
eventHandler
.
onIntegerElement
(
elementId
,
intValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_FLOAT:
if
(
elementContentSize
!=
VALID_FLOAT32_ELEMENT_SIZE_BYTES
&&
elementContentSize
!=
VALID_FLOAT64_ELEMENT_SIZE_BYTES
)
{
throw
new
IllegalStateException
(
"Invalid float size "
+
elementContentSize
);
}
int
floatResult
=
readBytesInternal
(
inputStream
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
floatResult
!=
READ_RESULT_CONTINUE
)
{
return
floatResult
;
}
long
valueBits
=
getTempByteArrayValue
((
int
)
elementContentSize
,
false
);
double
floatValue
;
if
(
elementContentSize
==
VALID_FLOAT32_ELEMENT_SIZE_BYTES
)
{
floatValue
=
Float
.
intBitsToFloat
((
int
)
valueBits
);
}
else
{
floatValue
=
Double
.
longBitsToDouble
(
valueBits
);
}
if
(!
eventHandler
.
onFloatElement
(
elementId
,
floatValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_STRING:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
"String element size "
+
elementContentSize
+
" is larger than MAX_INT"
);
}
if
(
stringBytes
==
null
)
{
stringBytes
=
new
byte
[(
int
)
elementContentSize
];
}
int
stringResult
=
readBytesInternal
(
inputStream
,
stringBytes
,
(
int
)
elementContentSize
);
if
(
stringResult
!=
READ_RESULT_CONTINUE
)
{
return
stringResult
;
}
String
stringValue
=
new
String
(
stringBytes
,
Charset
.
forName
(
"UTF-8"
));
stringBytes
=
null
;
if
(!
eventHandler
.
onStringElement
(
elementId
,
stringValue
))
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_BINARY:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
"Binary element size "
+
elementContentSize
+
" is larger than MAX_INT"
);
}
if
(
inputStream
.
getAvailableByteCount
()
<
elementContentSize
)
{
return
READ_RESULT_NEED_MORE_DATA
;
}
int
binaryHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
// Header size is 12 bytes max.
boolean
keepGoing
=
eventHandler
.
onBinaryElement
(
elementId
,
elementOffset
,
binaryHeaderSize
,
(
int
)
elementContentSize
,
inputStream
);
long
expectedBytesRead
=
elementOffset
+
binaryHeaderSize
+
elementContentSize
;
if
(
expectedBytesRead
!=
bytesRead
)
{
throw
new
IllegalStateException
(
"Incorrect total bytes read. Expected "
+
expectedBytesRead
+
" but actually "
+
bytesRead
);
}
if
(!
keepGoing
)
{
prepareForNextElement
();
return
READ_RESULT_CONTINUE
;
}
break
;
case
TYPE_UNKNOWN:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
"Unknown element size "
+
elementContentSize
+
" is larger than MAX_INT"
);
}
int
skipResult
=
skipBytesInternal
(
inputStream
,
(
int
)
elementContentSize
);
if
(
skipResult
!=
READ_RESULT_CONTINUE
)
{
return
skipResult
;
}
break
;
default
:
throw
new
IllegalStateException
(
"Invalid element type "
+
type
);
}
prepareForNextElement
();
}
}
@Override
public
long
getBytesRead
()
{
return
bytesRead
;
}
@Override
public
void
reset
()
{
prepareForNextElement
();
masterElementsStack
.
clear
();
bytesRead
=
0
;
}
@Override
public
long
readVarint
(
NonBlockingInputStream
inputStream
)
{
varintBytesState
=
STATE_BEGIN_READING
;
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't read varint"
);
}
return
getTempByteArrayValue
(
varintBytesLength
,
true
);
}
@Override
public
void
readBytes
(
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
int
totalBytes
)
{
bytesState
=
0
;
int
result
=
readBytesInternal
(
inputStream
,
byteBuffer
,
totalBytes
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't read bytes into buffer"
);
}
}
@Override
public
void
readBytes
(
NonBlockingInputStream
inputStream
,
byte
[]
byteArray
,
int
totalBytes
)
{
bytesState
=
0
;
int
result
=
readBytesInternal
(
inputStream
,
byteArray
,
totalBytes
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't read bytes into array"
);
}
}
@Override
public
void
skipBytes
(
NonBlockingInputStream
inputStream
,
int
totalBytes
)
{
bytesState
=
0
;
int
result
=
skipBytesInternal
(
inputStream
,
totalBytes
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
throw
new
IllegalStateException
(
"Couldn't skip bytes"
);
}
}
/**
* Resets the internal state of {@link #read(NonBlockingInputStream)} so that it can start
* reading a new element from scratch.
*/
private
void
prepareForNextElement
()
{
state
=
STATE_BEGIN_READING
;
elementIdState
=
STATE_BEGIN_READING
;
elementContentSizeState
=
STATE_BEGIN_READING
;
elementOffset
=
bytesRead
;
}
/**
* Reads an element ID such that reading can be stopped and started again in a later call
* if not enough bytes are available. Returns {@link #READ_RESULT_CONTINUE} if a full element ID
* has been read into {@link #elementId}. Reset {@link #elementIdState} to
* {@link #STATE_BEGIN_READING} before calling to indicate a new element ID should be read.
*
* @param inputStream The input stream from which an element ID should be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readElementId
(
NonBlockingInputStream
inputStream
)
{
if
(
elementIdState
==
STATE_FINISHED_READING
)
{
return
READ_RESULT_CONTINUE
;
}
if
(
elementIdState
==
STATE_BEGIN_READING
)
{
varintBytesState
=
STATE_BEGIN_READING
;
elementIdState
=
STATE_READ_CONTENTS
;
}
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
// Element IDs are at most 4 bytes so cast to int now.
elementId
=
(
int
)
getTempByteArrayValue
(
varintBytesLength
,
false
);
elementIdState
=
STATE_FINISHED_READING
;
return
READ_RESULT_CONTINUE
;
}
/**
* Reads an element's content size such that reading can be stopped and started again in a later
* call if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if an entire element size has been
* read into {@link #elementContentSize}. Reset {@link #elementContentSizeState} to
* {@link #STATE_BEGIN_READING} before calling to indicate a new element size should be read.
*
* @param inputStream The input stream from which an element size should be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readElementContentSize
(
NonBlockingInputStream
inputStream
)
{
if
(
elementContentSizeState
==
STATE_FINISHED_READING
)
{
return
READ_RESULT_CONTINUE
;
}
if
(
elementContentSizeState
==
STATE_BEGIN_READING
)
{
varintBytesState
=
STATE_BEGIN_READING
;
elementContentSizeState
=
STATE_READ_CONTENTS
;
}
int
result
=
readVarintBytes
(
inputStream
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
elementContentSize
=
getTempByteArrayValue
(
varintBytesLength
,
true
);
elementContentSizeState
=
STATE_FINISHED_READING
;
return
READ_RESULT_CONTINUE
;
}
/**
* Reads an EBML variable-length integer (varint) such that reading can be stopped and started
* again in a later call if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if an entire varint has been read into
* {@link #tempByteArray} and the length of the varint is in {@link #varintBytesLength}.
* Reset {@link #varintBytesState} to {@link #STATE_BEGIN_READING} before calling to indicate
* a new varint should be read.
*
* @param inputStream The input stream from which a varint should be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readVarintBytes
(
NonBlockingInputStream
inputStream
)
{
if
(
varintBytesState
==
STATE_FINISHED_READING
)
{
return
READ_RESULT_CONTINUE
;
}
// Read first byte to get length.
if
(
varintBytesState
==
STATE_BEGIN_READING
)
{
bytesState
=
0
;
int
result
=
readBytesInternal
(
inputStream
,
tempByteArray
,
1
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
varintBytesState
=
STATE_READ_CONTENTS
;
int
firstByte
=
tempByteArray
[
0
]
&
0xff
;
varintBytesLength
=
-
1
;
for
(
int
i
=
0
;
i
<
VARINT_LENGTH_MASKS
.
length
;
i
++)
{
if
((
VARINT_LENGTH_MASKS
[
i
]
&
firstByte
)
!=
0
)
{
varintBytesLength
=
i
+
1
;
break
;
}
}
if
(
varintBytesLength
==
-
1
)
{
throw
new
IllegalStateException
(
"No valid varint length mask found at bytesRead = "
+
bytesRead
);
}
}
// Read remaining bytes.
int
result
=
readBytesInternal
(
inputStream
,
tempByteArray
,
varintBytesLength
);
if
(
result
!=
READ_RESULT_CONTINUE
)
{
return
result
;
}
// All bytes have been read.
return
READ_RESULT_CONTINUE
;
}
/**
* Reads a set amount of bytes into a {@link ByteBuffer} such that reading can be stopped
* and started again later if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been read. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
*
* @param inputStream The input stream from which bytes should be read
* @param byteBuffer The {@link ByteBuffer} into which bytes should be read
* @param totalBytes The total size of bytes to be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readBytesInternal
(
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
int
totalBytes
)
{
if
(
bytesState
==
STATE_BEGIN_READING
&&
totalBytes
>
byteBuffer
.
capacity
())
{
throw
new
IllegalArgumentException
(
"Byte buffer not large enough"
);
}
if
(
bytesState
>=
totalBytes
)
{
return
READ_RESULT_CONTINUE
;
}
int
remainingBytes
=
totalBytes
-
bytesState
;
int
additionalBytesRead
=
inputStream
.
read
(
byteBuffer
,
remainingBytes
);
return
updateBytesState
(
additionalBytesRead
,
totalBytes
);
}
/**
* Reads a set amount of bytes into a {@code byte[]} such that reading can be stopped
* and started again later if not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been read. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
*
* @param inputStream The input stream from which bytes should be read
* @param byteArray The {@code byte[]} into which bytes should be read
* @param totalBytes The total size of bytes to be read
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
readBytesInternal
(
NonBlockingInputStream
inputStream
,
byte
[]
byteArray
,
int
totalBytes
)
{
if
(
bytesState
==
STATE_BEGIN_READING
&&
totalBytes
>
byteArray
.
length
)
{
throw
new
IllegalArgumentException
(
"Byte array not large enough"
);
}
if
(
bytesState
>=
totalBytes
)
{
return
READ_RESULT_CONTINUE
;
}
int
remainingBytes
=
totalBytes
-
bytesState
;
int
additionalBytesRead
=
inputStream
.
read
(
byteArray
,
bytesState
,
remainingBytes
);
return
updateBytesState
(
additionalBytesRead
,
totalBytes
);
}
/**
* Skips a set amount of bytes such that reading can be stopped and started again later if
* not enough bytes are available.
*
* <p>Returns {@link #READ_RESULT_CONTINUE} if all bytes have been skipped. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes
* should be skipped.
*
* @param inputStream The input stream from which bytes should be skipped
* @param totalBytes The total size of bytes to be skipped
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
skipBytesInternal
(
NonBlockingInputStream
inputStream
,
int
totalBytes
)
{
if
(
bytesState
>=
totalBytes
)
{
return
READ_RESULT_CONTINUE
;
}
int
remainingBytes
=
totalBytes
-
bytesState
;
int
additionalBytesRead
=
inputStream
.
skip
(
remainingBytes
);
return
updateBytesState
(
additionalBytesRead
,
totalBytes
);
}
/**
* Updates {@link #bytesState} and {@link #bytesRead} after reading bytes in one of the
* {@code verbBytesInternal} methods.
*
* @param additionalBytesRead The number of additional bytes read to be accounted for
* @param totalBytes The total size of bytes to be read or skipped
* @return One of the {@code RESULT_*} flags defined in this class
*/
private
int
updateBytesState
(
int
additionalBytesRead
,
int
totalBytes
)
{
if
(
additionalBytesRead
==
-
1
)
{
return
READ_RESULT_END_OF_FILE
;
}
bytesState
+=
additionalBytesRead
;
bytesRead
+=
additionalBytesRead
;
if
(
bytesState
<
totalBytes
)
{
return
READ_RESULT_NEED_MORE_DATA
;
}
else
{
return
READ_RESULT_CONTINUE
;
}
}
/**
* Parses and returns the integer value currently read into the first {@code byteLength} bytes
* of {@link #tempByteArray}. EBML varint length masks can optionally be removed.
*
* @param byteLength The number of bytes to parse from {@link #tempByteArray}
* @param removeLengthMask Removes the variable-length integer length mask from the value
* @return The resulting integer value. This value could be up to 8-bytes so a Java long is used
*/
private
long
getTempByteArrayValue
(
int
byteLength
,
boolean
removeLengthMask
)
{
if
(
removeLengthMask
)
{
tempByteArray
[
0
]
&=
~
VARINT_LENGTH_MASKS
[
varintBytesLength
-
1
];
}
long
varint
=
0
;
for
(
int
i
=
0
;
i
<
byteLength
;
i
++)
{
// Shift all existing bits up one byte and add the next byte at the bottom.
varint
=
(
varint
<<
8
)
|
(
tempByteArray
[
i
]
&
0xff
);
}
return
varint
;
}
/**
* Used in {@link #masterElementsStack} to track when the current master element ends so that
* {@link EbmlEventHandler#onMasterElementEnd(int)} is called.
*/
private
static
final
class
MasterElement
{
private
final
int
elementId
;
private
final
long
elementEndOffsetBytes
;
private
MasterElement
(
int
elementId
,
long
elementEndOffsetBytes
)
{
this
.
elementId
=
elementId
;
this
.
elementEndOffsetBytes
=
elementEndOffsetBytes
;
}
}
}
library/src/main/java/com/google/android/exoplayer/parser/webm/DefaultWebmExtractor.java
0 → 100644
View file @
686ac2a6
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
webm
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.parser.SegmentIndex
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
java.util.Arrays
;
import
java.util.concurrent.TimeUnit
;
/**
* Default version of an extractor to facilitate data retrieval from the WebM container format.
*
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
*/
@TargetApi
(
16
)
public
final
class
DefaultWebmExtractor
implements
WebmExtractor
,
EbmlEventHandler
{
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
private
static
final
String
CODEC_ID_VP9
=
"V_VP9"
;
private
static
final
int
UNKNOWN
=
-
1
;
// Element IDs
private
static
final
int
ID_EBML
=
0x1A45DFA3
;
private
static
final
int
ID_EBML_READ_VERSION
=
0x42F7
;
private
static
final
int
ID_DOC_TYPE
=
0x4282
;
private
static
final
int
ID_DOC_TYPE_READ_VERSION
=
0x4285
;
private
static
final
int
ID_SEGMENT
=
0x18538067
;
private
static
final
int
ID_INFO
=
0x1549A966
;
private
static
final
int
ID_TIMECODE_SCALE
=
0x2AD7B1
;
private
static
final
int
ID_DURATION
=
0x4489
;
private
static
final
int
ID_CLUSTER
=
0x1F43B675
;
private
static
final
int
ID_TIME_CODE
=
0xE7
;
private
static
final
int
ID_SIMPLE_BLOCK
=
0xA3
;
private
static
final
int
ID_TRACKS
=
0x1654AE6B
;
private
static
final
int
ID_TRACK_ENTRY
=
0xAE
;
private
static
final
int
ID_CODEC_ID
=
0x86
;
private
static
final
int
ID_VIDEO
=
0xE0
;
private
static
final
int
ID_PIXEL_WIDTH
=
0xB0
;
private
static
final
int
ID_PIXEL_HEIGHT
=
0xBA
;
private
static
final
int
ID_CUES
=
0x1C53BB6B
;
private
static
final
int
ID_CUE_POINT
=
0xBB
;
private
static
final
int
ID_CUE_TIME
=
0xB3
;
private
static
final
int
ID_CUE_TRACK_POSITIONS
=
0xB7
;
private
static
final
int
ID_CUE_CLUSTER_POSITION
=
0xF1
;
// SimpleBlock Lacing Values
private
static
final
int
LACING_NONE
=
0
;
private
static
final
int
LACING_XIPH
=
1
;
private
static
final
int
LACING_FIXED
=
2
;
private
static
final
int
LACING_EBML
=
3
;
private
final
EbmlReader
reader
;
private
final
byte
[]
simpleBlockTimecodeAndFlags
=
new
byte
[
3
];
private
SampleHolder
tempSampleHolder
;
private
boolean
sampleRead
;
private
boolean
prepared
=
false
;
private
long
segmentStartOffsetBytes
=
UNKNOWN
;
private
long
segmentEndOffsetBytes
=
UNKNOWN
;
private
long
timecodeScale
=
1000000L
;
private
long
durationUs
=
UNKNOWN
;
private
int
pixelWidth
=
UNKNOWN
;
private
int
pixelHeight
=
UNKNOWN
;
private
long
cuesSizeBytes
=
UNKNOWN
;
private
long
clusterTimecodeUs
=
UNKNOWN
;
private
long
simpleBlockTimecodeUs
=
UNKNOWN
;
private
MediaFormat
format
;
private
SegmentIndex
cues
;
private
LongArray
cueTimesUs
;
private
LongArray
cueClusterPositions
;
public
DefaultWebmExtractor
()
{
this
(
new
DefaultEbmlReader
());
}
/* package */
DefaultWebmExtractor
(
EbmlReader
reader
)
{
this
.
reader
=
reader
;
this
.
reader
.
setEventHandler
(
this
);
this
.
cueTimesUs
=
new
LongArray
();
this
.
cueClusterPositions
=
new
LongArray
();
}
@Override
public
boolean
isPrepared
()
{
return
prepared
;
}
@Override
public
boolean
read
(
NonBlockingInputStream
inputStream
,
SampleHolder
sampleHolder
)
{
tempSampleHolder
=
sampleHolder
;
sampleRead
=
false
;
reader
.
read
(
inputStream
);
tempSampleHolder
=
null
;
return
sampleRead
;
}
@Override
public
boolean
seekTo
(
long
seekTimeUs
,
boolean
allowNoop
)
{
checkPrepared
();
if
(
allowNoop
&&
simpleBlockTimecodeUs
!=
UNKNOWN
&&
seekTimeUs
>=
simpleBlockTimecodeUs
)
{
int
clusterIndex
=
Arrays
.
binarySearch
(
cues
.
timesUs
,
clusterTimecodeUs
);
if
(
clusterIndex
>=
0
&&
seekTimeUs
<
clusterTimecodeUs
+
cues
.
durationsUs
[
clusterIndex
])
{
return
false
;
}
}
reader
.
reset
();
return
true
;
}
@Override
public
SegmentIndex
getCues
()
{
checkPrepared
();
return
cues
;
}
@Override
public
MediaFormat
getFormat
()
{
checkPrepared
();
return
format
;
}
@Override
public
int
getElementType
(
int
id
)
{
switch
(
id
)
{
case
ID_EBML:
case
ID_SEGMENT:
case
ID_INFO:
case
ID_CLUSTER:
case
ID_TRACKS:
case
ID_TRACK_ENTRY:
case
ID_VIDEO:
case
ID_CUES:
case
ID_CUE_POINT:
case
ID_CUE_TRACK_POSITIONS:
return
EbmlReader
.
TYPE_MASTER
;
case
ID_EBML_READ_VERSION:
case
ID_DOC_TYPE_READ_VERSION:
case
ID_TIMECODE_SCALE:
case
ID_TIME_CODE:
case
ID_PIXEL_WIDTH:
case
ID_PIXEL_HEIGHT:
case
ID_CUE_TIME:
case
ID_CUE_CLUSTER_POSITION:
return
EbmlReader
.
TYPE_UNSIGNED_INT
;
case
ID_DOC_TYPE:
case
ID_CODEC_ID:
return
EbmlReader
.
TYPE_STRING
;
case
ID_SIMPLE_BLOCK:
return
EbmlReader
.
TYPE_BINARY
;
case
ID_DURATION:
return
EbmlReader
.
TYPE_FLOAT
;
default
:
return
EbmlReader
.
TYPE_UNKNOWN
;
}
}
@Override
public
boolean
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
)
{
switch
(
id
)
{
case
ID_SEGMENT:
if
(
segmentStartOffsetBytes
!=
UNKNOWN
||
segmentEndOffsetBytes
!=
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Multiple Segment elements not supported"
);
}
segmentStartOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
;
segmentEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
break
;
case
ID_CUES:
cuesSizeBytes
=
headerSizeBytes
+
contentsSizeBytes
;
break
;
default
:
// pass
}
return
true
;
}
@Override
public
boolean
onMasterElementEnd
(
int
id
)
{
if
(
id
==
ID_CUES
)
{
finishPreparing
();
return
false
;
}
return
true
;
}
@Override
public
boolean
onIntegerElement
(
int
id
,
long
value
)
{
switch
(
id
)
{
case
ID_EBML_READ_VERSION:
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
if
(
value
!=
1
)
{
throw
new
IllegalArgumentException
(
"EBMLReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_DOC_TYPE_READ_VERSION:
// Validate that DocTypeReadVersion is supported. This extractor only supports up to v2.
if
(
value
<
1
||
value
>
2
)
{
throw
new
IllegalArgumentException
(
"DocTypeReadVersion "
+
value
+
" not supported"
);
}
break
;
case
ID_TIMECODE_SCALE:
timecodeScale
=
value
;
break
;
case
ID_PIXEL_WIDTH:
pixelWidth
=
(
int
)
value
;
break
;
case
ID_PIXEL_HEIGHT:
pixelHeight
=
(
int
)
value
;
break
;
case
ID_CUE_TIME:
cueTimesUs
.
add
(
scaleTimecodeToUs
(
value
));
break
;
case
ID_CUE_CLUSTER_POSITION:
cueClusterPositions
.
add
(
value
);
break
;
case
ID_TIME_CODE:
clusterTimecodeUs
=
scaleTimecodeToUs
(
value
);
break
;
default
:
// pass
}
return
true
;
}
@Override
public
boolean
onFloatElement
(
int
id
,
double
value
)
{
if
(
id
==
ID_DURATION
)
{
durationUs
=
scaleTimecodeToUs
((
long
)
value
);
}
return
true
;
}
@Override
public
boolean
onStringElement
(
int
id
,
String
value
)
{
switch
(
id
)
{
case
ID_DOC_TYPE:
// Validate that DocType is supported. This extractor only supports "webm".
if
(!
DOC_TYPE_WEBM
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"DocType "
+
value
+
" not supported"
);
}
break
;
case
ID_CODEC_ID:
// Validate that CodecID is supported. This extractor only supports "V_VP9".
if
(!
CODEC_ID_VP9
.
equals
(
value
))
{
throw
new
IllegalArgumentException
(
"CodecID "
+
value
+
" not supported"
);
}
break
;
default
:
// pass
}
return
true
;
}
@Override
public
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
)
{
if
(
id
==
ID_SIMPLE_BLOCK
)
{
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// for info about how data is organized in a SimpleBlock element.
// Value of trackNumber is not used but needs to be read.
reader
.
readVarint
(
inputStream
);
// Next three bytes have timecode and flags.
reader
.
readBytes
(
inputStream
,
simpleBlockTimecodeAndFlags
,
3
);
// First two bytes of the three are the relative timecode.
int
timecode
=
(
simpleBlockTimecodeAndFlags
[
0
]
<<
8
)
|
(
simpleBlockTimecodeAndFlags
[
1
]
&
0xff
);
long
timecodeUs
=
scaleTimecodeToUs
(
timecode
);
// Last byte of the three has some flags and the lacing value.
boolean
keyframe
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x80
)
==
0x80
;
boolean
invisible
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x08
)
==
0x08
;
int
lacing
=
(
simpleBlockTimecodeAndFlags
[
2
]
&
0x06
)
>>
1
;
// Validate lacing and set info into sample holder.
switch
(
lacing
)
{
case
LACING_NONE:
long
elementEndOffsetBytes
=
elementOffsetBytes
+
headerSizeBytes
+
contentsSizeBytes
;
simpleBlockTimecodeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempSampleHolder
.
flags
=
keyframe
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
tempSampleHolder
.
decodeOnly
=
invisible
;
tempSampleHolder
.
timeUs
=
clusterTimecodeUs
+
timecodeUs
;
tempSampleHolder
.
size
=
(
int
)
(
elementEndOffsetBytes
-
reader
.
getBytesRead
());
break
;
case
LACING_EBML:
case
LACING_FIXED:
case
LACING_XIPH:
default
:
throw
new
IllegalStateException
(
"Lacing mode "
+
lacing
+
" not supported"
);
}
// Read video data into sample holder.
reader
.
readBytes
(
inputStream
,
tempSampleHolder
.
data
,
tempSampleHolder
.
size
);
sampleRead
=
true
;
return
false
;
}
else
{
reader
.
skipBytes
(
inputStream
,
contentsSizeBytes
);
return
true
;
}
}
private
long
scaleTimecodeToUs
(
long
unscaledTimecode
)
{
return
TimeUnit
.
NANOSECONDS
.
toMicros
(
unscaledTimecode
*
timecodeScale
);
}
private
void
checkPrepared
()
{
if
(!
prepared
)
{
throw
new
IllegalStateException
(
"Parser not yet prepared"
);
}
}
private
void
finishPreparing
()
{
if
(
prepared
)
{
throw
new
IllegalStateException
(
"Already prepared"
);
}
else
if
(
segmentStartOffsetBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Segment start/end offsets unknown"
);
}
else
if
(
durationUs
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Duration unknown"
);
}
else
if
(
pixelWidth
==
UNKNOWN
||
pixelHeight
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Pixel width/height unknown"
);
}
else
if
(
cuesSizeBytes
==
UNKNOWN
)
{
throw
new
IllegalStateException
(
"Cues size unknown"
);
}
else
if
(
cueTimesUs
.
size
()
==
0
||
cueTimesUs
.
size
()
!=
cueClusterPositions
.
size
())
{
throw
new
IllegalStateException
(
"Invalid/missing cue points"
);
}
format
=
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_VP9
,
MediaFormat
.
NO_VALUE
,
pixelWidth
,
pixelHeight
,
null
);
int
cuePointsSize
=
cueTimesUs
.
size
();
int
[]
sizes
=
new
int
[
cuePointsSize
];
long
[]
offsets
=
new
long
[
cuePointsSize
];
long
[]
durationsUs
=
new
long
[
cuePointsSize
];
long
[]
timesUs
=
new
long
[
cuePointsSize
];
for
(
int
i
=
0
;
i
<
cuePointsSize
;
i
++)
{
timesUs
[
i
]
=
cueTimesUs
.
get
(
i
);
offsets
[
i
]
=
segmentStartOffsetBytes
+
cueClusterPositions
.
get
(
i
);
}
for
(
int
i
=
0
;
i
<
cuePointsSize
-
1
;
i
++)
{
sizes
[
i
]
=
(
int
)
(
offsets
[
i
+
1
]
-
offsets
[
i
]);
durationsUs
[
i
]
=
timesUs
[
i
+
1
]
-
timesUs
[
i
];
}
sizes
[
cuePointsSize
-
1
]
=
(
int
)
(
segmentEndOffsetBytes
-
offsets
[
cuePointsSize
-
1
]);
durationsUs
[
cuePointsSize
-
1
]
=
durationUs
-
timesUs
[
cuePointsSize
-
1
];
cues
=
new
SegmentIndex
((
int
)
cuesSizeBytes
,
sizes
,
offsets
,
durationsUs
,
timesUs
);
cueTimesUs
=
null
;
cueClusterPositions
=
null
;
prepared
=
true
;
}
}
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlEventHandler.java
0 → 100644
View file @
686ac2a6
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
parser
.
webm
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
java.nio.ByteBuffer
;
/**
* Defines EBML element IDs/types and reacts to events.
*/
/* package */
interface
EbmlEventHandler
{
/**
* Retrieves the type of an element ID.
*
* <p>If {@link EbmlReader#TYPE_UNKNOWN} is returned then the element is skipped.
* Note that all children of a skipped master element are also skipped.
*
* @param id The integer ID of this element
* @return One of the {@code TYPE_} constants defined in this class
*/
public
int
getElementType
(
int
id
);
/**
* Called when a master element is encountered in the {@link NonBlockingInputStream}.
*
* <p>Following events should be considered as taking place "within" this element until a
* matching call to {@link #onMasterElementEnd(int)} is made. Note that it is possible for
* another master element of the same ID to be nested within itself.
*
* @param id The integer ID of this element
* @param elementOffsetBytes The byte offset where this element starts
* @param headerSizeBytes The byte length of this element's ID and size header
* @param contentsSizeBytes The byte length of this element's children
* @return {@code false} if parsing should stop right away
*/
public
boolean
onMasterElementStart
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
long
contentsSizeBytes
);
/**
* Called when a master element has finished reading in all of its children from the
* {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @return {@code false} if parsing should stop right away
*/
public
boolean
onMasterElementEnd
(
int
id
);
/**
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The integer value this element contains
* @return {@code false} if parsing should stop right away
*/
public
boolean
onIntegerElement
(
int
id
,
long
value
);
/**
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The float value this element contains
* @return {@code false} if parsing should stop right away
*/
public
boolean
onFloatElement
(
int
id
,
double
value
);
/**
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element
* @param value The string value this element contains
* @return {@code false} if parsing should stop right away
*/
public
boolean
onStringElement
(
int
id
,
String
value
);
/**
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
*
* <p>The element header (containing element ID and content size) will already have been read.
* Subclasses must exactly read the entire contents of the element, which is
* {@code contentsSizeBytes} in length. It's guaranteed that the full element contents will be
* immediately available from {@code inputStream}.
*
* <p>Several methods in {@link EbmlReader} are available for reading the contents of a
* binary element:
* <ul>
* <li>{@link EbmlReader#readVarint(NonBlockingInputStream)}.
* <li>{@link EbmlReader#readBytes(NonBlockingInputStream, byte[], int)}.
* <li>{@link EbmlReader#readBytes(NonBlockingInputStream, ByteBuffer, int)}.
* <li>{@link EbmlReader#skipBytes(NonBlockingInputStream, int)}.
* <li>{@link EbmlReader#getBytesRead()}.
*
* @param id The integer ID of this element
* @param elementOffsetBytes The byte offset where this element starts
* @param headerSizeBytes The byte length of this element's ID and size header
* @param contentsSizeBytes The byte length of this element's contents
* @param inputStream The {@link NonBlockingInputStream} from which this
* element's contents should be read
* @return {@code false} if parsing should stop right away
*/
public
boolean
onBinaryElement
(
int
id
,
long
elementOffsetBytes
,
int
headerSizeBytes
,
int
contentsSizeBytes
,
NonBlockingInputStream
inputStream
);
}
library/src/main/java/com/google/android/exoplayer/parser/webm/EbmlReader.java
View file @
686ac2a6
...
...
@@ -16,528 +16,92 @@
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
;
/**
* An event-driven incremental EBML reader base class.
* Basic event-driven incremental EBML parser which needs an {@link EbmlEventHandler} to
* define IDs/types and react to events.
*
* <p>EBML can be summarized as a binary XML format somewhat similar to Protocol Buffers.
* It was originally designed for the Matroska container format. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
*/
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 readByte
s.
p
rotected
static
final
int
RESULT_CONTINUE
=
0
;
p
rotected
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
p
rotected
static
final
int
RESULT_END_OF_FILE
=
2
;
// Return values for
reading method
s.
p
ublic
static
final
int
READ_
RESULT_CONTINUE
=
0
;
p
ublic
static
final
int
READ_
RESULT_NEED_MORE_DATA
=
1
;
p
ublic
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
<
MasterElement
>
masterElementsStack
=
new
Stack
<
MasterElement
>();
private
final
byte
[]
tempByteArray
=
new
byte
[
8
];
private
int
state
;
private
long
bytesRead
;
private
long
elementOffset
;
private
int
elementId
;
private
int
elementIdState
;
private
long
elementContentSize
;
private
int
elementContentSizeState
;
private
int
varintBytesState
;
private
int
varintBytesLength
;
private
int
bytesState
;
private
byte
[]
stringBytes
;
/**
* Called to retrieve the type of an element ID. If {@link #TYPE_UNKNOWN} is returned then
* the element is skipped. Note that all children of a skipped master element are also skipped.
*
* @param id The integer ID of this element.
* @return One of the {@code TYPE_} constants defined in this class.
*/
protected
abstract
int
getElementType
(
int
id
);
/**
* Called when a master element is encountered in the {@link NonBlockingInputStream}.
* Following events should be considered as taking place "within" this element until a
* matching call to {@link #onMasterElementEnd(int)} is made. Note that it
* is possible for the same master element to be nested within itself.
*
* @param id The integer ID of this element.
* @param elementOffset The byte offset where this element starts.
* @param headerSize The byte length of this element's ID and size header.
* @param contentsSize The byte length of this element's children.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onMasterElementStart
(
int
id
,
long
elementOffset
,
int
headerSize
,
int
contentsSize
);
/**
* Called when a master element has finished reading in all of its children from the
* {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onMasterElementEnd
(
int
id
);
/**
* Called when an integer element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @param value The integer value this element contains.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onIntegerElement
(
int
id
,
long
value
);
/**
* Called when a float element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @param value The float value this element contains.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onFloatElement
(
int
id
,
double
value
);
/**
* Called when a string element is encountered in the {@link NonBlockingInputStream}.
*
* @param id The integer ID of this element.
* @param value The string value this element contains.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onStringElement
(
int
id
,
String
value
);
/**
* Called when a binary element is encountered in the {@link NonBlockingInputStream}.
* The element header (containing element ID and content size) will already have been read.
* Subclasses must exactly read the entire contents of the element, which is {@code contentsSize}
* bytes in length. It's guaranteed that the full element contents will be immediately available
* from {@code inputStream}.
*
* <p>Several methods are available for reading the contents of a binary element:
* <ul>
* <li>{@link #readVarint(NonBlockingInputStream)}.
* <li>{@link #readBytes(NonBlockingInputStream, byte[], int)}.
* <li>{@link #readBytes(NonBlockingInputStream, ByteBuffer, int)}.
* <li>{@link #skipBytes(NonBlockingInputStream, int)}.
* <li>{@link #getBytesRead()}.
*
* @param inputStream The {@link NonBlockingInputStream} from which this
* element's contents should be read.
* @param id The integer ID of this element.
* @param elementOffset The byte offset where this element starts.
* @param headerSize The byte length of this element's ID and size header.
* @param contentsSize The byte length of this element's contents.
* @return {@code true} if parsing should continue or {@code false} if it should stop right away.
*/
protected
abstract
boolean
onBinaryElement
(
NonBlockingInputStream
inputStream
,
int
id
,
long
elementOffset
,
int
headerSize
,
int
contentsSize
);
public
void
setEventHandler
(
EbmlEventHandler
eventHandler
);
/**
* Reads from a {@link NonBlockingInputStream} and calls event callbacks as needed.
*
* @param inputStream The input stream from which data should be read
.
* @return One of the {@code RESULT_*} flags defined in this
class.
* @param inputStream The input stream from which data should be read
* @return One of the {@code RESULT_*} flags defined in this
interface
*/
protected
final
int
read
(
NonBlockingInputStream
inputStream
)
{
while
(
true
)
{
while
(
masterElementsStack
.
size
()
>
0
&&
bytesRead
>=
masterElementsStack
.
peek
().
elementEndOffset
)
{
if
(!
onMasterElementEnd
(
masterElementsStack
.
pop
().
elementId
))
{
return
RESULT_CONTINUE
;
}
}
if
(
state
==
STATE_BEGIN_READING
)
{
final
int
resultId
=
readElementId
(
inputStream
);
if
(
resultId
!=
RESULT_CONTINUE
)
{
return
resultId
;
}
final
int
resultSize
=
readElementContentSize
(
inputStream
);
if
(
resultSize
!=
RESULT_CONTINUE
)
{
return
resultSize
;
}
state
=
STATE_READ_CONTENTS
;
bytesState
=
0
;
}
final
int
type
=
getElementType
(
elementId
);
switch
(
type
)
{
case
TYPE_MASTER:
final
int
masterHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
masterElementsStack
.
add
(
new
MasterElement
(
elementId
,
bytesRead
+
elementContentSize
));
if
(!
onMasterElementStart
(
elementId
,
elementOffset
,
masterHeaderSize
,
(
int
)
elementContentSize
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_UNSIGNED_INT:
Assertions
.
checkState
(
elementContentSize
<=
8
);
final
int
resultInt
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
resultInt
!=
RESULT_CONTINUE
)
{
return
resultInt
;
}
final
long
intValue
=
parseTempByteArray
((
int
)
elementContentSize
,
false
);
if
(!
onIntegerElement
(
elementId
,
intValue
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_FLOAT:
Assertions
.
checkState
(
elementContentSize
==
4
||
elementContentSize
==
8
);
final
int
resultFloat
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
(
int
)
elementContentSize
);
if
(
resultFloat
!=
RESULT_CONTINUE
)
{
return
resultFloat
;
}
final
long
valueBits
=
parseTempByteArray
((
int
)
elementContentSize
,
false
);
final
double
floatValue
;
if
(
elementContentSize
==
4
)
{
floatValue
=
Float
.
intBitsToFloat
((
int
)
valueBits
);
}
else
{
floatValue
=
Double
.
longBitsToDouble
(
valueBits
);
}
if
(!
onFloatElement
(
elementId
,
floatValue
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_STRING:
if
(
stringBytes
==
null
)
{
stringBytes
=
new
byte
[(
int
)
elementContentSize
];
}
final
int
resultString
=
readBytes
(
inputStream
,
null
,
stringBytes
,
(
int
)
elementContentSize
);
if
(
resultString
!=
RESULT_CONTINUE
)
{
return
resultString
;
}
final
String
stringValue
=
new
String
(
stringBytes
,
Charset
.
forName
(
"UTF-8"
));
stringBytes
=
null
;
if
(!
onStringElement
(
elementId
,
stringValue
))
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_BINARY:
if
(
inputStream
.
getAvailableByteCount
()
<
elementContentSize
)
{
return
RESULT_NEED_MORE_DATA
;
}
final
int
binaryHeaderSize
=
(
int
)
(
bytesRead
-
elementOffset
);
final
boolean
keepGoing
=
onBinaryElement
(
inputStream
,
elementId
,
elementOffset
,
binaryHeaderSize
,
(
int
)
elementContentSize
);
Assertions
.
checkState
(
elementOffset
+
binaryHeaderSize
+
elementContentSize
==
bytesRead
);
if
(!
keepGoing
)
{
prepareForNextElement
();
return
RESULT_CONTINUE
;
}
break
;
case
TYPE_UNKNOWN:
// Unknown elements should be skipped.
Assertions
.
checkState
(
readBytes
(
inputStream
,
null
,
null
,
(
int
)
elementContentSize
)
==
RESULT_CONTINUE
);
break
;
default
:
throw
new
IllegalStateException
(
"Invalid element type "
+
type
);
}
prepareForNextElement
();
}
}
public
int
read
(
NonBlockingInputStream
inputStream
);
/**
* @return 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()}.
*/
protected
final
long
getBytesRead
()
{
return
bytesRead
;
}
public
long
getBytesRead
();
/**
* Resets the entire state of the reader so that it will read a new EBML structure from scratch.
* This includes resetting the value returned from {@link #getBytesRead()} to 0 and discarding
* all pending {@link #onMasterElementEnd(int)} events.
*
* <p>This includes resetting the value returned from {@link #getBytesRead()} to 0 and discarding
* all pending {@link EbmlEventHandler#onMasterElementEnd(int)} events.
*/
protected
final
void
reset
()
{
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.
*/
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.
* @param inputStream The input stream from which data should be skipped
* @param totalBytes The fixed number of bytes to be skipped
*/
private
int
readVarintBytes
(
NonBlockingInputStream
inputStream
)
{
if
(
varintBytesState
==
STATE_FINISHED_READING
)
{
return
RESULT_CONTINUE
;
}
// Read first byte to get length.
if
(
varintBytesState
==
STATE_BEGIN_READING
)
{
bytesState
=
0
;
final
int
result
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
1
);
if
(
result
!=
RESULT_CONTINUE
)
{
return
result
;
}
varintBytesState
=
STATE_READ_CONTENTS
;
final
int
firstByte
=
tempByteArray
[
0
]
&
0xff
;
varintBytesLength
=
-
1
;
for
(
int
i
=
0
;
i
<
VARINT_LENGTH_MASKS
.
length
;
i
++)
{
if
((
VARINT_LENGTH_MASKS
[
i
]
&
firstByte
)
!=
0
)
{
varintBytesLength
=
i
+
1
;
break
;
}
}
if
(
varintBytesLength
==
-
1
)
{
throw
new
IllegalStateException
(
"No valid varint length mask found at bytesRead = "
+
bytesRead
);
}
}
// Read remaining bytes.
final
int
result
=
readBytes
(
inputStream
,
null
,
tempByteArray
,
varintBytesLength
);
if
(
result
!=
RESULT_CONTINUE
)
{
return
result
;
}
// All bytes have been read.
return
RESULT_CONTINUE
;
}
/**
* Reads a set amount of bytes into a {@link ByteBuffer}, {@code byte[]}, or nowhere (skipping
* the bytes) such that reading can be stopped and started again later if not enough bytes are
* available. Returns {@link #RESULT_CONTINUE} if all bytes have been read. Reset
* {@link #bytesState} to {@code 0} before calling to indicate a new set of bytes should be read.
*
* <p>If both {@code byteBuffer} and {@code byteArray} are not null then bytes are only read
* into {@code byteBuffer}.
*
* @param inputStream The input stream from which bytes should be read.
* @param byteBuffer The optional {@link ByteBuffer} into which bytes should be read.
* @param byteArray The optional {@code byte[]} into which bytes should be read.
* @param totalBytes The total size of bytes to be read or skipped.
* @return One of the {@code RESULT_*} flags defined in this class.
*/
private
int
readBytes
(
NonBlockingInputStream
inputStream
,
ByteBuffer
byteBuffer
,
byte
[]
byteArray
,
int
totalBytes
)
{
if
(
bytesState
==
STATE_BEGIN_READING
&&
((
byteBuffer
!=
null
&&
totalBytes
>
byteBuffer
.
capacity
())
||
(
byteArray
!=
null
&&
totalBytes
>
byteArray
.
length
)))
{
throw
new
IllegalStateException
(
"Byte destination not large enough"
);
}
if
(
bytesState
<
totalBytes
)
{
final
int
remainingBytes
=
totalBytes
-
bytesState
;
final
int
result
;
if
(
byteBuffer
!=
null
)
{
result
=
inputStream
.
read
(
byteBuffer
,
remainingBytes
);
}
else
if
(
byteArray
!=
null
)
{
result
=
inputStream
.
read
(
byteArray
,
bytesState
,
remainingBytes
);
}
else
{
result
=
inputStream
.
skip
(
remainingBytes
);
}
if
(
result
==
-
1
)
{
return
RESULT_END_OF_FILE
;
}
bytesState
+=
result
;
bytesRead
+=
result
;
if
(
bytesState
<
totalBytes
)
{
return
RESULT_NEED_MORE_DATA
;
}
}
return
RESULT_CONTINUE
;
}
/**
* Parses and returns the integer value currently read into the first {@code byteLength} bytes
* of {@link #tempByteArray}. EBML varint length masks can optionally be removed.
*
* @param byteLength The number of bytes to parse from {@link #tempByteArray}.
* @param removeLengthMask Removes the variable-length integer length mask from the value.
* @return The resulting integer value. This value could be up to 8-bytes so a Java long is used.
*/
private
long
parseTempByteArray
(
int
byteLength
,
boolean
removeLengthMask
)
{
if
(
removeLengthMask
)
{
tempByteArray
[
0
]
&=
~
VARINT_LENGTH_MASKS
[
varintBytesLength
-
1
];
}
long
varint
=
0
;
for
(
int
i
=
0
;
i
<
byteLength
;
i
++)
{
// Shift all existing bits up one byte and add the next byte at the bottom.
varint
=
(
varint
<<
8
)
|
(
tempByteArray
[
i
]
&
0xff
);
}
return
varint
;
}
/**
* Used in {@link #masterElementsStack} to track when the current master element ends so that
* {@link #onMasterElementEnd(int)} is called.
*/
private
static
final
class
MasterElement
{
private
final
int
elementId
;
private
final
long
elementEndOffset
;
private
MasterElement
(
int
elementId
,
long
elementEndOffset
)
{
this
.
elementId
=
elementId
;
this
.
elementEndOffset
=
elementEndOffset
;
}
}
public
void
skipBytes
(
NonBlockingInputStream
inputStream
,
int
totalBytes
);
}
library/src/main/java/com/google/android/exoplayer/parser/webm/WebmExtractor.java
View file @
686ac2a6
...
...
@@ -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.
*
* <p>WebM is a subset of the EBML elements defined for Matroska. More information about EBML and
* Matroska is available <a href="http://www.matroska.org/technical/specs/index.html">here</a>.
* More info about WebM is <a href="http://www.webmproject.org/code/specs/container/">here</a>.
*/
@TargetApi
(
16
)
public
final
class
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
();
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment