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
8c98c588
authored
Jun 18, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add support for fixed-size lacing in Matroska streams.
parent
4c4782c7
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
329 additions
and
148 deletions
library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java
library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java
library/src/test/java/com/google/android/exoplayer/extractor/webm/StreamBuilder.java
library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java
library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java
View file @
8c98c588
...
@@ -52,9 +52,10 @@ public interface Extractor {
...
@@ -52,9 +52,10 @@ public interface Extractor {
/**
/**
* Extracts data read from a provided {@link ExtractorInput}.
* Extracts data read from a provided {@link ExtractorInput}.
* <p>
* <p>
* Each read will extract at most one sample from the stream before returning.
* A single call to this method will block until some progress has been made, but will not block
* for longer than this. Hence each call will consume only a small amount of input data.
* <p>
* <p>
* In the common case, {@link #RESULT_CONTINUE} is returned to indicate that
* In the common case, {@link #RESULT_CONTINUE} is returned to indicate that
the
* {@link ExtractorInput} passed to the next read is required to provide data continuing from the
* {@link ExtractorInput} passed to the next read is required to provide data continuing from the
* position in the stream reached by the returning call. If the extractor requires data to be
* position in the stream reached by the returning call. If the extractor requires data to be
* provided from a different position, then that position is set in {@code seekPosition} and
* provided from a different position, then that position is set in {@code seekPosition} and
...
...
library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java
View file @
8c98c588
...
@@ -52,9 +52,9 @@ import java.util.concurrent.TimeUnit;
...
@@ -52,9 +52,9 @@ import java.util.concurrent.TimeUnit;
*/
*/
public
final
class
WebmExtractor
implements
Extractor
{
public
final
class
WebmExtractor
implements
Extractor
{
private
static
final
int
SAMPLE
_STATE_START
=
0
;
private
static
final
int
BLOCK
_STATE_START
=
0
;
private
static
final
int
SAMPLE
_STATE_HEADER
=
1
;
private
static
final
int
BLOCK
_STATE_HEADER
=
1
;
private
static
final
int
SAMPLE
_STATE_DATA
=
2
;
private
static
final
int
BLOCK
_STATE_DATA
=
2
;
private
static
final
int
CUES_STATE_NOT_BUILT
=
0
;
private
static
final
int
CUES_STATE_NOT_BUILT
=
0
;
private
static
final
int
CUES_STATE_BUILDING
=
1
;
private
static
final
int
CUES_STATE_BUILDING
=
1
;
...
@@ -98,6 +98,7 @@ public final class WebmExtractor implements Extractor {
...
@@ -98,6 +98,7 @@ public final class WebmExtractor implements Extractor {
private
static
final
int
ID_TRACK_ENTRY
=
0xAE
;
private
static
final
int
ID_TRACK_ENTRY
=
0xAE
;
private
static
final
int
ID_TRACK_NUMBER
=
0xD7
;
private
static
final
int
ID_TRACK_NUMBER
=
0xD7
;
private
static
final
int
ID_TRACK_TYPE
=
0x83
;
private
static
final
int
ID_TRACK_TYPE
=
0x83
;
private
static
final
int
ID_DEFAULT_DURATION
=
0x23E383
;
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_CODEC_PRIVATE
=
0x63A2
;
private
static
final
int
ID_CODEC_DELAY
=
0x56AA
;
private
static
final
int
ID_CODEC_DELAY
=
0x56AA
;
...
@@ -125,6 +126,7 @@ public final class WebmExtractor implements Extractor {
...
@@ -125,6 +126,7 @@ public final class WebmExtractor implements Extractor {
private
static
final
int
ID_CUE_CLUSTER_POSITION
=
0xF1
;
private
static
final
int
ID_CUE_CLUSTER_POSITION
=
0xF1
;
private
static
final
int
LACING_NONE
=
0
;
private
static
final
int
LACING_NONE
=
0
;
private
static
final
int
LACING_FIXED_SIZE
=
2
;
private
final
EbmlReader
reader
;
private
final
EbmlReader
reader
;
private
final
VarintReader
varintReader
;
private
final
VarintReader
varintReader
;
...
@@ -132,7 +134,7 @@ public final class WebmExtractor implements Extractor {
...
@@ -132,7 +134,7 @@ public final class WebmExtractor implements Extractor {
// Temporary arrays.
// Temporary arrays.
private
final
ParsableByteArray
nalStartCode
;
private
final
ParsableByteArray
nalStartCode
;
private
final
ParsableByteArray
nalLength
;
private
final
ParsableByteArray
nalLength
;
private
final
ParsableByteArray
s
ampleHeaderS
cratch
;
private
final
ParsableByteArray
scratch
;
private
final
ParsableByteArray
vorbisNumPageSamples
;
private
final
ParsableByteArray
vorbisNumPageSamples
;
private
final
ParsableByteArray
seekEntryIdBytes
;
private
final
ParsableByteArray
seekEntryIdBytes
;
...
@@ -161,15 +163,22 @@ public final class WebmExtractor implements Extractor {
...
@@ -161,15 +163,22 @@ public final class WebmExtractor implements Extractor {
private
LongArray
cueClusterPositions
;
private
LongArray
cueClusterPositions
;
private
boolean
seenClusterPositionForCurrentCuePoint
;
private
boolean
seenClusterPositionForCurrentCuePoint
;
// Block reading state.
private
int
blockState
;
private
long
blockTimeUs
;
private
int
blockLacingSampleIndex
;
private
int
blockLacingSampleCount
;
private
int
blockLacingSampleSize
;
private
int
blockTrackNumber
;
private
int
blockTrackNumberLength
;
private
int
blockFlags
;
private
byte
[]
blockEncryptionKeyId
;
// Sample reading state.
// Sample reading state.
private
int
blockBytesRead
;
private
int
sampleBytesRead
;
private
int
sampleState
;
private
boolean
sampleEncryptionDataRead
;
private
int
sampleSize
;
private
int
sampleCurrentNalBytesRemaining
;
private
int
sampleCurrentNalBytesRemaining
;
private
int
sampleTrackNumber
;
private
int
sampleBytesWritten
;
private
int
sampleFlags
;
private
byte
[]
sampleEncryptionKeyId
;
private
long
sampleTimeUs
;
private
boolean
sampleRead
;
private
boolean
sampleRead
;
private
boolean
sampleSeenReferenceBlock
;
private
boolean
sampleSeenReferenceBlock
;
...
@@ -184,7 +193,7 @@ public final class WebmExtractor implements Extractor {
...
@@ -184,7 +193,7 @@ public final class WebmExtractor implements Extractor {
this
.
reader
=
reader
;
this
.
reader
=
reader
;
this
.
reader
.
init
(
new
InnerEbmlReaderOutput
());
this
.
reader
.
init
(
new
InnerEbmlReaderOutput
());
varintReader
=
new
VarintReader
();
varintReader
=
new
VarintReader
();
s
ampleHeaderS
cratch
=
new
ParsableByteArray
(
4
);
scratch
=
new
ParsableByteArray
(
4
);
vorbisNumPageSamples
=
new
ParsableByteArray
(
ByteBuffer
.
allocate
(
4
).
putInt
(-
1
).
array
());
vorbisNumPageSamples
=
new
ParsableByteArray
(
ByteBuffer
.
allocate
(
4
).
putInt
(-
1
).
array
());
seekEntryIdBytes
=
new
ParsableByteArray
(
4
);
seekEntryIdBytes
=
new
ParsableByteArray
(
4
);
nalStartCode
=
new
ParsableByteArray
(
NalUnitUtil
.
NAL_START_CODE
);
nalStartCode
=
new
ParsableByteArray
(
NalUnitUtil
.
NAL_START_CODE
);
...
@@ -199,10 +208,13 @@ public final class WebmExtractor implements Extractor {
...
@@ -199,10 +208,13 @@ public final class WebmExtractor implements Extractor {
@Override
@Override
public
void
seek
()
{
public
void
seek
()
{
clusterTimecodeUs
=
UNKNOWN
;
clusterTimecodeUs
=
UNKNOWN
;
sampleState
=
SAMPLE
_STATE_START
;
blockState
=
BLOCK
_STATE_START
;
reader
.
reset
();
reader
.
reset
();
varintReader
.
reset
();
varintReader
.
reset
();
sampleCurrentNalBytesRemaining
=
0
;
sampleCurrentNalBytesRemaining
=
0
;
sampleBytesRead
=
0
;
sampleBytesWritten
=
0
;
sampleEncryptionDataRead
=
false
;
}
}
@Override
@Override
...
@@ -249,6 +261,7 @@ public final class WebmExtractor implements Extractor {
...
@@ -249,6 +261,7 @@ public final class WebmExtractor implements Extractor {
case
ID_PIXEL_HEIGHT:
case
ID_PIXEL_HEIGHT:
case
ID_TRACK_NUMBER:
case
ID_TRACK_NUMBER:
case
ID_TRACK_TYPE:
case
ID_TRACK_TYPE:
case
ID_DEFAULT_DURATION:
case
ID_CODEC_DELAY:
case
ID_CODEC_DELAY:
case
ID_SEEK_PRE_ROLL:
case
ID_SEEK_PRE_ROLL:
case
ID_CHANNELS:
case
ID_CHANNELS:
...
@@ -342,17 +355,18 @@ public final class WebmExtractor implements Extractor {
...
@@ -342,17 +355,18 @@ public final class WebmExtractor implements Extractor {
}
}
return
;
return
;
case
ID_BLOCK_GROUP:
case
ID_BLOCK_GROUP:
if
(
sampleState
!=
SAMPLE
_STATE_DATA
)
{
if
(
blockState
!=
BLOCK
_STATE_DATA
)
{
// We've skipped this
sample
(due to incompatible track number).
// We've skipped this
block
(due to incompatible track number).
return
;
return
;
}
}
// If the ReferenceBlock element was not found for this sample, then it is a keyframe.
// If the ReferenceBlock element was not found for this sample, then it is a keyframe.
if
(!
sampleSeenReferenceBlock
)
{
if
(!
sampleSeenReferenceBlock
)
{
sample
Flags
|=
C
.
SAMPLE_FLAG_SYNC
;
block
Flags
|=
C
.
SAMPLE_FLAG_SYNC
;
}
}
outputSampleMetadata
(
outputSampleMetadata
(
(
audioTrackFormat
!=
null
&&
sampleTrackNumber
==
audioTrackFormat
.
number
)
(
audioTrackFormat
!=
null
&&
blockTrackNumber
==
audioTrackFormat
.
number
)
?
audioTrackFormat
.
trackOutput
:
videoTrackFormat
.
trackOutput
);
?
audioTrackFormat
.
trackOutput
:
videoTrackFormat
.
trackOutput
,
blockTimeUs
);
blockState
=
BLOCK_STATE_START
;
return
;
return
;
case
ID_CONTENT_ENCODING:
case
ID_CONTENT_ENCODING:
if
(!
trackFormat
.
hasContentEncryption
)
{
if
(!
trackFormat
.
hasContentEncryption
)
{
...
@@ -436,6 +450,9 @@ public final class WebmExtractor implements Extractor {
...
@@ -436,6 +450,9 @@ public final class WebmExtractor implements Extractor {
case
ID_TRACK_TYPE:
case
ID_TRACK_TYPE:
trackFormat
.
type
=
(
int
)
value
;
trackFormat
.
type
=
(
int
)
value
;
return
;
return
;
case
ID_DEFAULT_DURATION:
trackFormat
.
defaultSampleDurationNs
=
(
int
)
value
;
break
;
case
ID_CODEC_DELAY:
case
ID_CODEC_DELAY:
trackFormat
.
codecDelayNs
=
value
;
trackFormat
.
codecDelayNs
=
value
;
return
;
return
;
...
@@ -552,76 +569,130 @@ public final class WebmExtractor implements Extractor {
...
@@ -552,76 +569,130 @@ public final class WebmExtractor implements Extractor {
// for info about how data is organized in SimpleBlock and Block elements respectively. They
// for info about how data is organized in SimpleBlock and Block elements respectively. They
// differ only in the way flags are specified.
// differ only in the way flags are specified.
if
(
sampleState
==
SAMPLE_STATE_START
)
{
if
(
blockState
==
BLOCK_STATE_START
)
{
sampleTrackNumber
=
(
int
)
varintReader
.
readUnsignedVarint
(
input
,
false
,
true
);
blockTrackNumber
=
(
int
)
varintReader
.
readUnsignedVarint
(
input
,
false
,
true
);
blockBytesRead
=
varintReader
.
getLastLength
();
blockTrackNumberLength
=
varintReader
.
getLastLength
();
sampleState
=
SAMPLE_STATE_HEADER
;
blockState
=
BLOCK_STATE_HEADER
;
scratch
.
reset
();
}
}
// Ignore the
frame
if the track number equals neither the audio track nor the video track.
// Ignore the
block
if the track number equals neither the audio track nor the video track.
if
((
audioTrackFormat
!=
null
&&
videoTrackFormat
!=
null
if
((
audioTrackFormat
!=
null
&&
videoTrackFormat
!=
null
&&
audioTrackFormat
.
number
!=
sample
TrackNumber
&&
audioTrackFormat
.
number
!=
block
TrackNumber
&&
videoTrackFormat
.
number
!=
sample
TrackNumber
)
&&
videoTrackFormat
.
number
!=
block
TrackNumber
)
||
(
audioTrackFormat
!=
null
&&
videoTrackFormat
==
null
||
(
audioTrackFormat
!=
null
&&
videoTrackFormat
==
null
&&
audioTrackFormat
.
number
!=
sample
TrackNumber
)
&&
audioTrackFormat
.
number
!=
block
TrackNumber
)
||
(
audioTrackFormat
==
null
&&
videoTrackFormat
!=
null
||
(
audioTrackFormat
==
null
&&
videoTrackFormat
!=
null
&&
videoTrackFormat
.
number
!=
sample
TrackNumber
))
{
&&
videoTrackFormat
.
number
!=
block
TrackNumber
))
{
input
.
skipFully
(
contentSize
-
block
BytesRead
);
input
.
skipFully
(
contentSize
-
block
TrackNumberLength
);
sampleState
=
SAMPLE
_STATE_START
;
blockState
=
BLOCK
_STATE_START
;
return
;
return
;
}
}
TrackFormat
sampleTrackFormat
=
TrackFormat
sampleTrackFormat
=
(
audioTrackFormat
!=
null
&&
sample
TrackNumber
==
audioTrackFormat
.
number
)
(
audioTrackFormat
!=
null
&&
block
TrackNumber
==
audioTrackFormat
.
number
)
?
audioTrackFormat
:
videoTrackFormat
;
?
audioTrackFormat
:
videoTrackFormat
;
TrackOutput
trackOutput
=
sampleTrackFormat
.
trackOutput
;
TrackOutput
trackOutput
=
sampleTrackFormat
.
trackOutput
;
if
(
sampleState
==
SAMPLE_STATE_HEADER
)
{
if
(
blockState
==
BLOCK_STATE_HEADER
)
{
byte
[]
sampleHeaderScratchData
=
sampleHeaderScratch
.
data
;
// Read the relative timecode (2 bytes) and flags (1 byte).
// Next 3 bytes have timecode and flags. If encrypted, the 4th byte is a signal byte.
readScratch
(
input
,
3
);
int
remainingHeaderLength
=
sampleTrackFormat
.
hasContentEncryption
?
4
:
3
;
int
lacing
=
(
scratch
.
data
[
2
]
&
0x06
)
>>
1
;
input
.
readFully
(
sampleHeaderScratchData
,
0
,
remainingHeaderLength
);
if
(
lacing
==
LACING_NONE
)
{
blockBytesRead
+=
remainingHeaderLength
;
blockLacingSampleCount
=
1
;
blockLacingSampleSize
=
contentSize
-
blockTrackNumberLength
-
3
;
// First two bytes are the relative timecode.
}
else
if
(
lacing
==
LACING_FIXED_SIZE
)
{
int
timecode
=
(
sampleHeaderScratchData
[
0
]
<<
8
)
if
(
id
!=
ID_SIMPLE_BLOCK
)
{
|
(
sampleHeaderScratchData
[
1
]
&
0xFF
);
throw
new
ParserException
(
"Lacing only supported in SimpleBlocks."
);
sampleTimeUs
=
clusterTimecodeUs
+
scaleTimecodeToUs
(
timecode
);
}
// Third byte contains the lacing value and some flags.
// Read the sample count (1 byte).
int
lacing
=
(
sampleHeaderScratchData
[
2
]
&
0x06
)
>>
1
;
readScratch
(
input
,
4
);
if
(
lacing
!=
LACING_NONE
)
{
blockLacingSampleCount
=
(
scratch
.
data
[
3
]
&
0xFF
)
+
1
;
blockLacingSampleSize
=
(
contentSize
-
blockTrackNumberLength
-
4
)
/
blockLacingSampleCount
;
}
else
{
throw
new
ParserException
(
"Lacing mode not supported: "
+
lacing
);
throw
new
ParserException
(
"Lacing mode not supported: "
+
lacing
);
}
}
boolean
isInvisible
=
(
sampleHeaderScratchData
[
2
]
&
0x08
)
==
0x08
;
boolean
isKeyframe
=
(
id
==
ID_SIMPLE_BLOCK
&&
(
sampleHeaderScratchData
[
2
]
&
0x80
)
==
0x80
);
boolean
isEncrypted
=
false
;
// If encrypted, the fourth byte is an encryption signal byte.
int
timecode
=
(
scratch
.
data
[
0
]
<<
8
)
|
(
scratch
.
data
[
1
]
&
0xFF
);
if
(
sampleTrackFormat
.
hasContentEncryption
)
{
blockTimeUs
=
clusterTimecodeUs
+
scaleTimecodeToUs
(
timecode
);
if
((
sampleHeaderScratchData
[
3
]
&
0x80
)
==
0x80
)
{
boolean
isInvisible
=
(
scratch
.
data
[
2
]
&
0x08
)
==
0x08
;
throw
new
ParserException
(
"Extension bit is set in signal byte"
);
boolean
isKeyframe
=
(
id
==
ID_SIMPLE_BLOCK
&&
(
scratch
.
data
[
2
]
&
0x80
)
==
0x80
);
blockFlags
=
(
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
)
|
(
isInvisible
?
C
.
SAMPLE_FLAG_DECODE_ONLY
:
0
);
blockEncryptionKeyId
=
sampleTrackFormat
.
encryptionKeyId
;
blockState
=
BLOCK_STATE_DATA
;
blockLacingSampleIndex
=
0
;
}
if
(
id
==
ID_SIMPLE_BLOCK
)
{
// For SimpleBlock, we have metadata for each sample here.
while
(
blockLacingSampleIndex
<
blockLacingSampleCount
)
{
writeSampleData
(
input
,
trackOutput
,
sampleTrackFormat
,
blockLacingSampleSize
);
long
sampleTimeUs
=
this
.
blockTimeUs
+
(
blockLacingSampleIndex
*
sampleTrackFormat
.
defaultSampleDurationNs
)
/
1000
;
outputSampleMetadata
(
trackOutput
,
sampleTimeUs
);
blockLacingSampleIndex
++;
}
blockState
=
BLOCK_STATE_START
;
}
else
{
// For Block, we send the metadata at the end of the BlockGroup element since we'll know
// if the sample is a keyframe or not only at that point.
writeSampleData
(
input
,
trackOutput
,
sampleTrackFormat
,
blockLacingSampleSize
);
}
return
;
default
:
throw
new
ParserException
(
"Unexpected id: "
+
id
);
}
}
private
void
outputSampleMetadata
(
TrackOutput
trackOutput
,
long
timeUs
)
{
trackOutput
.
sampleMetadata
(
timeUs
,
blockFlags
,
sampleBytesWritten
,
0
,
blockEncryptionKeyId
);
sampleRead
=
true
;
sampleBytesRead
=
0
;
sampleBytesWritten
=
0
;
sampleEncryptionDataRead
=
false
;
}
/**
* Ensures {@link #scratch} contains at least {@code requiredLength} bytes of data, reading from
* the extractor input if necessary.
*/
private
void
readScratch
(
ExtractorInput
input
,
int
requiredLength
)
throws
IOException
,
InterruptedException
{
if
(
scratch
.
limit
()
>=
requiredLength
)
{
return
;
}
}
isEncrypted
=
(
sampleHeaderScratchData
[
3
]
&
0x01
)
==
0x01
;
input
.
readFully
(
scratch
.
data
,
scratch
.
limit
(),
requiredLength
-
scratch
.
limit
());
scratch
.
setLimit
(
requiredLength
);
}
}
sampleFlags
=
(
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
)
private
void
writeSampleData
(
ExtractorInput
input
,
TrackOutput
output
,
TrackFormat
format
,
|
(
isInvisible
?
C
.
SAMPLE_FLAG_DECODE_ONLY
:
0
)
int
size
)
throws
IOException
,
InterruptedException
{
|
(
isEncrypted
?
C
.
SAMPLE_FLAG_ENCRYPTED
:
0
);
// Read the sample's encryption signal byte and set the IV size if necessary.
sampleEncryptionKeyId
=
sampleTrackFormat
.
encryptionKeyId
;
if
(
format
.
hasContentEncryption
&&
!
sampleEncryptionDataRead
)
{
sampleSize
=
contentSize
-
blockBytesRead
;
// Clear the encrypted flag.
if
(
isEncrypted
)
{
blockFlags
&=
~
C
.
SAMPLE_FLAG_ENCRYPTED
;
// Write the vector size.
input
.
readFully
(
scratch
.
data
,
0
,
1
);
sampleHeaderScratch
.
data
[
0
]
=
(
byte
)
ENCRYPTION_IV_SIZE
;
sampleBytesRead
++;
sampleHeaderScratch
.
setPosition
(
0
);
if
((
scratch
.
data
[
0
]
&
0x80
)
==
0x80
)
{
trackOutput
.
sampleData
(
sampleHeaderScratch
,
1
);
throw
new
ParserException
(
"Extension bit is set in signal byte"
);
sampleSize
++;
}
sampleEncryptionDataRead
=
true
;
// If the sample is encrypted, write the IV size instead of the signal byte, and set the flag.
if
((
scratch
.
data
[
0
]
&
0x01
)
==
0x01
)
{
scratch
.
data
[
0
]
=
(
byte
)
ENCRYPTION_IV_SIZE
;
scratch
.
setPosition
(
0
);
output
.
sampleData
(
scratch
,
1
);
sampleBytesWritten
++;
blockFlags
|=
C
.
SAMPLE_FLAG_ENCRYPTED
;
}
}
sampleState
=
SAMPLE_STATE_DATA
;
}
}
if
(
CODEC_ID_H264
.
equals
(
sampleTrackF
ormat
.
codecId
))
{
if
(
CODEC_ID_H264
.
equals
(
f
ormat
.
codecId
))
{
// TODO: Deduplicate with Mp4Extractor.
// TODO: Deduplicate with Mp4Extractor.
// Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case
// Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case
...
@@ -630,64 +701,50 @@ public final class WebmExtractor implements Extractor {
...
@@ -630,64 +701,50 @@ public final class WebmExtractor implements Extractor {
nalLengthData
[
0
]
=
0
;
nalLengthData
[
0
]
=
0
;
nalLengthData
[
1
]
=
0
;
nalLengthData
[
1
]
=
0
;
nalLengthData
[
2
]
=
0
;
nalLengthData
[
2
]
=
0
;
int
nalUnitLengthFieldLength
=
sampleTrackF
ormat
.
nalUnitLengthFieldLength
;
int
nalUnitLengthFieldLength
=
f
ormat
.
nalUnitLengthFieldLength
;
int
nalUnitLengthFieldLengthDiff
=
4
-
sampleTrackF
ormat
.
nalUnitLengthFieldLength
;
int
nalUnitLengthFieldLengthDiff
=
4
-
f
ormat
.
nalUnitLengthFieldLength
;
// NAL units are length delimited, but the decoder requires start code delimited units.
// NAL units are length delimited, but the decoder requires start code delimited units.
// Loop until we've written the sample to the track output, replacing length delimiters
// Loop until we've written the sample to the track output, replacing length delimiters with
// with
start codes as we encounter them.
//
start codes as we encounter them.
while
(
blockBytesRead
<
contentS
ize
)
{
while
(
sampleBytesRead
<
s
ize
)
{
if
(
sampleCurrentNalBytesRemaining
==
0
)
{
if
(
sampleCurrentNalBytesRemaining
==
0
)
{
// Read the NAL length so that we know where we find the next one.
// Read the NAL length so that we know where we find the next one.
input
.
readFully
(
nalLengthData
,
nalUnitLengthFieldLengthDiff
,
input
.
readFully
(
nalLengthData
,
nalUnitLengthFieldLengthDiff
,
nalUnitLengthFieldLength
);
nalUnitLengthFieldLength
);
blockBytesRead
+=
nalUnitLengthFieldLength
;
nalLength
.
setPosition
(
0
);
nalLength
.
setPosition
(
0
);
sampleCurrentNalBytesRemaining
=
nalLength
.
readUnsignedIntToInt
();
sampleCurrentNalBytesRemaining
=
nalLength
.
readUnsignedIntToInt
();
// Write a start code for the current NAL unit.
// Write a start code for the current NAL unit.
nalStartCode
.
setPosition
(
0
);
nalStartCode
.
setPosition
(
0
);
trackOutput
.
sampleData
(
nalStartCode
,
4
);
output
.
sampleData
(
nalStartCode
,
4
);
sampleSize
+=
nalUnitLengthFieldLengthDiff
;
sampleBytesRead
+=
nalUnitLengthFieldLength
;
sampleBytesWritten
+=
4
;
}
else
{
}
else
{
// Write the payload of the NAL unit.
// Write the payload of the NAL unit.
int
writtenBytes
=
trackOutput
.
sampleData
(
input
,
sampleCurrentNalBytesRemaining
);
int
writtenBytes
=
output
.
sampleData
(
input
,
sampleCurrentNalBytesRemaining
);
blockBytesRead
+=
writtenBytes
;
sampleCurrentNalBytesRemaining
-=
writtenBytes
;
sampleCurrentNalBytesRemaining
-=
writtenBytes
;
sampleBytesRead
+=
writtenBytes
;
sampleBytesWritten
+=
writtenBytes
;
}
}
}
}
}
else
{
}
else
{
while
(
blockBytesRead
<
contentSize
)
{
while
(
sampleBytesRead
<
size
)
{
blockBytesRead
+=
trackOutput
.
sampleData
(
input
,
contentSize
-
blockBytesRead
);
int
writtenBytes
=
output
.
sampleData
(
input
,
size
-
sampleBytesRead
);
sampleBytesRead
+=
writtenBytes
;
sampleBytesWritten
+=
writtenBytes
;
}
}
}
}
if
(
CODEC_ID_VORBIS
.
equals
(
sampleTrackF
ormat
.
codecId
))
{
if
(
CODEC_ID_VORBIS
.
equals
(
f
ormat
.
codecId
))
{
// Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to b
e
// Vorbis decoder in android MediaCodec [1] expects the last 4 bytes of the sample to be th
e
// the
number of samples in the current page. This definition holds good only for Ogg and
//
number of samples in the current page. This definition holds good only for Ogg and
// irrelevant for WebM. So we always set this to -1 (the decoder will ignore this value if
// irrelevant for WebM. So we always set this to -1 (the decoder will ignore this value if we
// we
set it to -1). The android platform media extractor [2] does the same.
//
set it to -1). The android platform media extractor [2] does the same.
// [1] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314
// [1] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/codecs/vorbis/dec/SoftVorbis.cpp#314
// [2] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474
// [2] https://android.googlesource.com/platform/frameworks/av/+/lollipop-release/media/libstagefright/NuMediaExtractor.cpp#474
vorbisNumPageSamples
.
setPosition
(
0
);
vorbisNumPageSamples
.
setPosition
(
0
);
trackO
utput
.
sampleData
(
vorbisNumPageSamples
,
4
);
o
utput
.
sampleData
(
vorbisNumPageSamples
,
4
);
sampleSize
+=
4
;
sampleBytesWritten
+=
4
;
}
}
// For SimpleBlock, we send the metadata here as we have all the information. For Block, we
// send the metadata at the end of the BlockGroup element since we'll know if the frame is a
// keyframe or not only at that point.
if
(
id
==
ID_SIMPLE_BLOCK
)
{
outputSampleMetadata
(
trackOutput
);
}
return
;
default
:
throw
new
IllegalStateException
(
"Unexpected id: "
+
id
);
}
}
private
void
outputSampleMetadata
(
TrackOutput
trackOutput
)
{
trackOutput
.
sampleMetadata
(
sampleTimeUs
,
sampleFlags
,
sampleSize
,
0
,
sampleEncryptionKeyId
);
sampleState
=
SAMPLE_STATE_START
;
sampleRead
=
true
;
}
}
/**
/**
...
@@ -817,6 +874,7 @@ public final class WebmExtractor implements Extractor {
...
@@ -817,6 +874,7 @@ public final class WebmExtractor implements Extractor {
public
String
codecId
;
public
String
codecId
;
public
int
number
=
UNKNOWN
;
public
int
number
=
UNKNOWN
;
public
int
type
=
UNKNOWN
;
public
int
type
=
UNKNOWN
;
public
int
defaultSampleDurationNs
=
UNKNOWN
;
public
boolean
hasContentEncryption
;
public
boolean
hasContentEncryption
;
public
byte
[]
encryptionKeyId
;
public
byte
[]
encryptionKeyId
;
public
byte
[]
codecPrivate
;
public
byte
[]
codecPrivate
;
...
...
library/src/test/java/com/google/android/exoplayer/extractor/webm/StreamBuilder.java
View file @
8c98c588
...
@@ -47,6 +47,14 @@ import java.util.List;
...
@@ -47,6 +47,14 @@ import java.util.List;
}
}
public
static
byte
[]
createByteArray
(
int
...
intArray
)
{
byte
[]
byteArray
=
new
byte
[
intArray
.
length
];
for
(
int
i
=
0
;
i
<
byteArray
.
length
;
i
++)
{
byteArray
[
i
]
=
(
byte
)
intArray
[
i
];
}
return
byteArray
;
}
public
static
byte
[]
joinByteArrays
(
byte
[]...
byteArrays
)
{
public
static
byte
[]
joinByteArrays
(
byte
[]...
byteArrays
)
{
int
length
=
0
;
int
length
=
0
;
for
(
byte
[]
byteArray
:
byteArrays
)
{
for
(
byte
[]
byteArray
:
byteArrays
)
{
...
@@ -94,16 +102,28 @@ import java.util.List;
...
@@ -94,16 +102,28 @@ import java.util.List;
return
this
;
return
this
;
}
}
public
StreamBuilder
addH264Track
(
int
width
,
int
height
,
byte
[]
codecPrivate
)
{
trackEntries
.
add
(
createVideoTrackEntry
(
"V_MPEG4/ISO/AVC"
,
width
,
height
,
null
,
codecPrivate
));
return
this
;
}
public
StreamBuilder
addOpusTrack
(
int
channelCount
,
int
sampleRate
,
int
codecDelay
,
public
StreamBuilder
addOpusTrack
(
int
channelCount
,
int
sampleRate
,
int
codecDelay
,
int
seekPreRoll
,
byte
[]
codecPrivate
)
{
int
seekPreRoll
,
byte
[]
codecPrivate
)
{
trackEntries
.
add
(
createAudioTrackEntry
(
"A_OPUS"
,
channelCount
,
sampleRate
,
codecPrivate
,
trackEntries
.
add
(
createAudioTrackEntry
(
"A_OPUS"
,
channelCount
,
sampleRate
,
codecPrivate
,
codecDelay
,
seekPreRoll
));
codecDelay
,
seekPreRoll
,
NO_VALUE
));
return
this
;
}
public
StreamBuilder
addOpusTrack
(
int
channelCount
,
int
sampleRate
,
int
codecDelay
,
int
seekPreRoll
,
byte
[]
codecPrivate
,
int
defaultDurationNs
)
{
trackEntries
.
add
(
createAudioTrackEntry
(
"A_OPUS"
,
channelCount
,
sampleRate
,
codecPrivate
,
codecDelay
,
seekPreRoll
,
defaultDurationNs
));
return
this
;
return
this
;
}
}
public
StreamBuilder
addVorbisTrack
(
int
channelCount
,
int
sampleRate
,
byte
[]
codecPrivate
)
{
public
StreamBuilder
addVorbisTrack
(
int
channelCount
,
int
sampleRate
,
byte
[]
codecPrivate
)
{
trackEntries
.
add
(
createAudioTrackEntry
(
"A_VORBIS"
,
channelCount
,
sampleRate
,
codecPrivate
,
trackEntries
.
add
(
createAudioTrackEntry
(
"A_VORBIS"
,
channelCount
,
sampleRate
,
codecPrivate
,
NO_VALUE
,
NO_VALUE
));
NO_VALUE
,
NO_VALUE
,
NO_VALUE
));
return
this
;
return
this
;
}
}
...
@@ -120,7 +140,7 @@ import java.util.List;
...
@@ -120,7 +140,7 @@ import java.util.List;
byte
[]
data
)
{
byte
[]
data
)
{
byte
flags
=
(
byte
)
((
keyframe
?
0x80
:
0x00
)
|
(
invisible
?
0x08
:
0x00
));
byte
flags
=
(
byte
)
((
keyframe
?
0x80
:
0x00
)
|
(
invisible
?
0x08
:
0x00
));
EbmlElement
simpleBlockElement
=
createSimpleBlock
(
trackNumber
,
blockTimecode
,
flags
,
EbmlElement
simpleBlockElement
=
createSimpleBlock
(
trackNumber
,
blockTimecode
,
flags
,
true
,
validSignalByte
,
data
);
true
,
validSignalByte
,
1
,
data
);
mediaSegments
.
add
(
createCluster
(
clusterTimecode
,
simpleBlockElement
));
mediaSegments
.
add
(
createCluster
(
clusterTimecode
,
simpleBlockElement
));
return
this
;
return
this
;
}
}
...
@@ -130,7 +150,15 @@ import java.util.List;
...
@@ -130,7 +150,15 @@ import java.util.List;
int
blockTimecode
,
boolean
keyframe
,
boolean
invisible
,
byte
[]
data
)
{
int
blockTimecode
,
boolean
keyframe
,
boolean
invisible
,
byte
[]
data
)
{
byte
flags
=
(
byte
)
((
keyframe
?
0x80
:
0x00
)
|
(
invisible
?
0x08
:
0x00
));
byte
flags
=
(
byte
)
((
keyframe
?
0x80
:
0x00
)
|
(
invisible
?
0x08
:
0x00
));
EbmlElement
simpleBlockElement
=
createSimpleBlock
(
trackNumber
,
blockTimecode
,
flags
,
EbmlElement
simpleBlockElement
=
createSimpleBlock
(
trackNumber
,
blockTimecode
,
flags
,
false
,
true
,
data
);
false
,
true
,
1
,
data
);
mediaSegments
.
add
(
createCluster
(
clusterTimecode
,
simpleBlockElement
));
return
this
;
}
public
StreamBuilder
addSimpleBlockMediaWithFixedSizeLacing
(
int
trackNumber
,
int
clusterTimecode
,
int
blockTimecode
,
int
lacingFrameCount
,
byte
[]
data
)
{
EbmlElement
simpleBlockElement
=
createSimpleBlock
(
trackNumber
,
blockTimecode
,
0x80
/* flags = keyframe */
,
false
,
true
,
lacingFrameCount
,
data
);
mediaSegments
.
add
(
createCluster
(
clusterTimecode
,
simpleBlockElement
));
mediaSegments
.
add
(
createCluster
(
clusterTimecode
,
simpleBlockElement
));
return
this
;
return
this
;
}
}
...
@@ -248,7 +276,7 @@ import java.util.List;
...
@@ -248,7 +276,7 @@ import java.util.List;
}
}
private
static
EbmlElement
createAudioTrackEntry
(
String
codecId
,
int
channelCount
,
int
sampleRate
,
private
static
EbmlElement
createAudioTrackEntry
(
String
codecId
,
int
channelCount
,
int
sampleRate
,
byte
[]
codecPrivate
,
int
codecDelay
,
int
seekPreRoll
)
{
byte
[]
codecPrivate
,
int
codecDelay
,
int
seekPreRoll
,
int
defaultDurationNs
)
{
byte
channelCountByte
=
(
byte
)
(
channelCount
&
0xFF
);
byte
channelCountByte
=
(
byte
)
(
channelCount
&
0xFF
);
byte
[]
sampleRateDoubleBytes
=
getLongBytes
(
Double
.
doubleToLongBits
(
sampleRate
));
byte
[]
sampleRateDoubleBytes
=
getLongBytes
(
Double
.
doubleToLongBits
(
sampleRate
));
return
element
(
0xAE
,
// TrackEntry
return
element
(
0xAE
,
// TrackEntry
...
@@ -262,6 +290,9 @@ import java.util.List;
...
@@ -262,6 +290,9 @@ import java.util.List;
element
(
0xE1
,
// Audio
element
(
0xE1
,
// Audio
element
(
0x9F
,
channelCountByte
),
// Channels
element
(
0x9F
,
channelCountByte
),
// Channels
element
(
0xB5
,
sampleRateDoubleBytes
)),
// SamplingFrequency
element
(
0xB5
,
sampleRateDoubleBytes
)),
// SamplingFrequency
// DefaultDuration
defaultDurationNs
!=
NO_VALUE
?
element
(
0x23E383
,
getIntegerBytes
(
defaultDurationNs
))
:
empty
(),
element
(
0x63A2
,
codecPrivate
));
// CodecPrivate
element
(
0x63A2
,
codecPrivate
));
// CodecPrivate
}
}
...
@@ -272,12 +303,20 @@ import java.util.List;
...
@@ -272,12 +303,20 @@ import java.util.List;
}
}
private
static
EbmlElement
createSimpleBlock
(
int
trackNumber
,
int
timecode
,
int
flags
,
private
static
EbmlElement
createSimpleBlock
(
int
trackNumber
,
int
timecode
,
int
flags
,
boolean
encrypted
,
boolean
validSignalByte
,
byte
[]
data
)
{
boolean
encrypted
,
boolean
validSignalByte
,
int
lacingFrameCount
,
byte
[]
data
)
{
byte
[]
trackNumberBytes
=
getIntegerBytes
(
trackNumber
);
byte
[]
trackNumberBytes
=
getIntegerBytes
(
trackNumber
);
byte
[]
timeBytes
=
getIntegerBytes
(
timecode
);
byte
[]
timeBytes
=
getIntegerBytes
(
timecode
);
byte
[]
simpleBlockBytes
=
createByteArray
(
byte
[]
simpleBlockBytes
;
if
(
lacingFrameCount
>
1
)
{
flags
|=
0x04
;
// Fixed-size lacing
simpleBlockBytes
=
createByteArray
(
0x40
,
trackNumberBytes
[
3
],
// Track number size=2
timeBytes
[
2
],
timeBytes
[
3
],
flags
,
lacingFrameCount
-
1
);
// Timecode, flags and lacing.
}
else
{
simpleBlockBytes
=
createByteArray
(
0x40
,
trackNumberBytes
[
3
],
// Track number size=2
0x40
,
trackNumberBytes
[
3
],
// Track number size=2
timeBytes
[
2
],
timeBytes
[
3
],
flags
);
// Timecode and flags
timeBytes
[
2
],
timeBytes
[
3
],
flags
);
// Timecode and flags
}
if
(
encrypted
)
{
if
(
encrypted
)
{
simpleBlockBytes
=
joinByteArrays
(
simpleBlockBytes
=
joinByteArrays
(
simpleBlockBytes
,
createByteArray
(
validSignalByte
?
0x01
:
0x80
),
simpleBlockBytes
,
createByteArray
(
validSignalByte
?
0x01
:
0x80
),
...
@@ -302,14 +341,6 @@ import java.util.List;
...
@@ -302,14 +341,6 @@ import java.util.List;
block
);
block
);
}
}
private
static
byte
[]
createByteArray
(
int
...
intArray
)
{
byte
[]
byteArray
=
new
byte
[
intArray
.
length
];
for
(
int
i
=
0
;
i
<
byteArray
.
length
;
i
++)
{
byteArray
[
i
]
=
(
byte
)
intArray
[
i
];
}
return
byteArray
;
}
private
static
byte
[]
getIntegerBytes
(
int
value
)
{
private
static
byte
[]
getIntegerBytes
(
int
value
)
{
return
createByteArray
(
return
createByteArray
(
(
value
&
0xFF000000
)
>>
24
,
(
value
&
0xFF000000
)
>>
24
,
...
...
library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java
View file @
8c98c588
...
@@ -37,9 +37,13 @@ import com.google.android.exoplayer.util.ParsableByteArray;
...
@@ -37,9 +37,13 @@ import com.google.android.exoplayer.util.ParsableByteArray;
import
android.net.Uri
;
import
android.net.Uri
;
import
android.test.InstrumentationTestCase
;
import
android.test.InstrumentationTestCase
;
import
android.test.MoreAsserts
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.nio.ByteBuffer
;
import
java.util.Arrays
;
import
java.util.LinkedList
;
import
java.util.Queue
;
import
java.util.UUID
;
import
java.util.UUID
;
/**
/**
...
@@ -59,9 +63,16 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -59,9 +63,16 @@ public class WebmExtractorTest extends InstrumentationTestCase {
private
static
final
int
TEST_VORBIS_INFO_SIZE
=
30
;
private
static
final
int
TEST_VORBIS_INFO_SIZE
=
30
;
private
static
final
int
TEST_VORBIS_BOOKS_SIZE
=
4140
;
private
static
final
int
TEST_VORBIS_BOOKS_SIZE
=
4140
;
private
static
final
byte
[]
TEST_OPUS_CODEC_PRIVATE
=
new
byte
[]
{
0
,
0
};
private
static
final
byte
[]
TEST_OPUS_CODEC_PRIVATE
=
new
byte
[]
{
0
,
0
};
private
static
final
int
TEST_DEFAULT_DURATION_NS
=
33
*
1000
*
1000
;
private
static
final
byte
[]
TEST_H264_CODEC_PRIVATE
=
StreamBuilder
.
createByteArray
(
0x01
,
0x4D
,
0x40
,
0x1E
,
0xFF
,
0xE1
,
0x00
,
0x17
,
0x67
,
0x4D
,
0x40
,
0x1E
,
0xE8
,
0x80
,
0x50
,
0x17
,
0xFC
,
0xB8
,
0x08
,
0x80
,
0x00
,
0x01
,
0xF4
,
0x80
,
0x00
,
0x75
,
0x30
,
0x07
,
0x8B
,
0x16
,
0x89
,
0x01
,
0x00
,
0x04
,
0x68
,
0xEB
,
0xEF
,
0x20
);
private
static
final
UUID
WIDEVINE_UUID
=
new
UUID
(
0xEDEF8BA979D64ACE
L
,
0xA3C827DCD51D21ED
L
);
private
static
final
UUID
WIDEVINE_UUID
=
new
UUID
(
0xEDEF8BA979D64ACE
L
,
0xA3C827DCD51D21ED
L
);
private
static
final
UUID
ZERO_UUID
=
new
UUID
(
0
,
0
);
private
static
final
UUID
ZERO_UUID
=
new
UUID
(
0
,
0
);
private
static
final
String
WEBM_DOC_TYPE
=
"webm"
;
private
static
final
String
WEBM_DOC_TYPE
=
"webm"
;
private
static
final
String
MATROSKA_DOC_TYPE
=
"matroska"
;
private
WebmExtractor
extractor
;
private
WebmExtractor
extractor
;
private
TestExtractorOutput
extractorOutput
;
private
TestExtractorOutput
extractorOutput
;
...
@@ -125,6 +136,19 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -125,6 +136,19 @@ public class WebmExtractorTest extends InstrumentationTestCase {
assertIndex
(
new
IndexPoint
(
0
,
0
,
TEST_DURATION_US
));
assertIndex
(
new
IndexPoint
(
0
,
0
,
TEST_DURATION_US
));
}
}
public
void
testPrepareH264
()
throws
IOException
,
InterruptedException
{
byte
[]
data
=
new
StreamBuilder
()
.
setHeader
(
MATROSKA_DOC_TYPE
)
.
setInfo
(
DEFAULT_TIMECODE_SCALE
,
TEST_DURATION_US
)
.
addH264Track
(
TEST_WIDTH
,
TEST_HEIGHT
,
TEST_H264_CODEC_PRIVATE
)
.
build
(
1
);
consume
(
data
);
assertH264VideoFormat
();
assertIndex
(
new
IndexPoint
(
0
,
0
,
TEST_DURATION_US
));
}
public
void
testPrepareTwoTracks
()
throws
IOException
,
InterruptedException
{
public
void
testPrepareTwoTracks
()
throws
IOException
,
InterruptedException
{
byte
[]
data
=
new
StreamBuilder
()
byte
[]
data
=
new
StreamBuilder
()
.
setHeader
(
WEBM_DOC_TYPE
)
.
setHeader
(
WEBM_DOC_TYPE
)
...
@@ -257,6 +281,17 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -257,6 +281,17 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
}
}
public
void
testAcceptsMatroskaDocType
()
throws
IOException
,
InterruptedException
{
byte
[]
data
=
new
StreamBuilder
()
.
setHeader
(
MATROSKA_DOC_TYPE
)
.
setInfo
(
DEFAULT_TIMECODE_SCALE
,
TEST_DURATION_US
)
.
addVp9Track
(
TEST_WIDTH
,
TEST_HEIGHT
,
null
)
.
build
(
1
);
// No exception is thrown.
consume
(
data
);
}
public
void
testPrepareInvalidDocType
()
throws
IOException
,
InterruptedException
{
public
void
testPrepareInvalidDocType
()
throws
IOException
,
InterruptedException
{
byte
[]
data
=
new
StreamBuilder
()
byte
[]
data
=
new
StreamBuilder
()
.
setHeader
(
"webB"
)
.
setHeader
(
"webB"
)
...
@@ -359,7 +394,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -359,7 +394,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertSample
(
media
,
0
,
true
,
false
,
false
,
videoOutput
);
assertSample
(
media
,
0
,
true
,
false
,
null
,
videoOutput
);
}
}
public
void
testReadTwoTrackSamples
()
throws
IOException
,
InterruptedException
{
public
void
testReadTwoTrackSamples
()
throws
IOException
,
InterruptedException
{
...
@@ -381,8 +416,8 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -381,8 +416,8 @@ public class WebmExtractorTest extends InstrumentationTestCase {
assertEquals
(
2
,
extractorOutput
.
numberOfTracks
);
assertEquals
(
2
,
extractorOutput
.
numberOfTracks
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertAudioFormat
(
MimeTypes
.
AUDIO_OPUS
);
assertAudioFormat
(
MimeTypes
.
AUDIO_OPUS
);
assertSample
(
media
,
0
,
true
,
false
,
false
,
videoOutput
);
assertSample
(
media
,
0
,
true
,
false
,
null
,
videoOutput
);
assertSample
(
media
,
0
,
true
,
false
,
false
,
audioOutput
);
assertSample
(
media
,
0
,
true
,
false
,
null
,
audioOutput
);
}
}
public
void
testReadTwoTrackSamplesWithSkippedTrack
()
throws
IOException
,
InterruptedException
{
public
void
testReadTwoTrackSamplesWithSkippedTrack
()
throws
IOException
,
InterruptedException
{
...
@@ -407,8 +442,8 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -407,8 +442,8 @@ public class WebmExtractorTest extends InstrumentationTestCase {
assertEquals
(
2
,
extractorOutput
.
numberOfTracks
);
assertEquals
(
2
,
extractorOutput
.
numberOfTracks
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertAudioFormat
(
MimeTypes
.
AUDIO_OPUS
);
assertAudioFormat
(
MimeTypes
.
AUDIO_OPUS
);
assertSample
(
media
,
0
,
true
,
false
,
false
,
videoOutput
);
assertSample
(
media
,
0
,
true
,
false
,
null
,
videoOutput
);
assertSample
(
media
,
0
,
true
,
false
,
false
,
audioOutput
);
assertSample
(
media
,
0
,
true
,
false
,
null
,
audioOutput
);
}
}
public
void
testReadBlock
()
throws
IOException
,
InterruptedException
{
public
void
testReadBlock
()
throws
IOException
,
InterruptedException
{
...
@@ -425,7 +460,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -425,7 +460,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
assertAudioFormat
(
MimeTypes
.
AUDIO_OPUS
);
assertAudioFormat
(
MimeTypes
.
AUDIO_OPUS
);
assertSample
(
media
,
0
,
true
,
false
,
false
,
audioOutput
);
assertSample
(
media
,
0
,
true
,
false
,
null
,
audioOutput
);
}
}
public
void
testReadBlockNonKeyframe
()
throws
IOException
,
InterruptedException
{
public
void
testReadBlockNonKeyframe
()
throws
IOException
,
InterruptedException
{
...
@@ -441,7 +476,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -441,7 +476,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertSample
(
media
,
0
,
false
,
false
,
false
,
videoOutput
);
assertSample
(
media
,
0
,
false
,
false
,
null
,
videoOutput
);
}
}
public
void
testReadEncryptedFrame
()
throws
IOException
,
InterruptedException
{
public
void
testReadEncryptedFrame
()
throws
IOException
,
InterruptedException
{
...
@@ -459,7 +494,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -459,7 +494,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertSample
(
media
,
0
,
true
,
false
,
true
,
videoOutput
);
assertSample
(
media
,
0
,
true
,
false
,
TEST_ENCRYPTION_KEY_ID
,
videoOutput
);
}
}
public
void
testReadEncryptedFrameWithInvalidSignalByte
()
public
void
testReadEncryptedFrameWithInvalidSignalByte
()
...
@@ -496,7 +531,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -496,7 +531,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertSample
(
media
,
25000
,
false
,
true
,
false
,
videoOutput
);
assertSample
(
media
,
25000
,
false
,
true
,
null
,
videoOutput
);
}
}
public
void
testReadSampleCustomTimescale
()
throws
IOException
,
InterruptedException
{
public
void
testReadSampleCustomTimescale
()
throws
IOException
,
InterruptedException
{
...
@@ -512,7 +547,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -512,7 +547,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertSample
(
media
,
25
,
false
,
false
,
false
,
videoOutput
);
assertSample
(
media
,
25
,
false
,
false
,
null
,
videoOutput
);
}
}
public
void
testReadSampleNegativeSimpleBlockTimecode
()
throws
IOException
,
InterruptedException
{
public
void
testReadSampleNegativeSimpleBlockTimecode
()
throws
IOException
,
InterruptedException
{
...
@@ -528,7 +563,28 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -528,7 +563,28 @@ public class WebmExtractorTest extends InstrumentationTestCase {
consume
(
data
);
consume
(
data
);
assertVp9VideoFormat
();
assertVp9VideoFormat
();
assertSample
(
media
,
1000
,
true
,
true
,
false
,
videoOutput
);
assertSample
(
media
,
1000
,
true
,
true
,
null
,
videoOutput
);
}
public
void
testReadSampleWithLacing
()
throws
IOException
,
InterruptedException
{
byte
[]
media
=
createFrameData
(
100
);
byte
[]
data
=
new
StreamBuilder
()
.
setHeader
(
WEBM_DOC_TYPE
)
.
setInfo
(
DEFAULT_TIMECODE_SCALE
,
TEST_DURATION_US
)
.
addOpusTrack
(
TEST_CHANNEL_COUNT
,
TEST_SAMPLE_RATE
,
TEST_CODEC_DELAY
,
TEST_SEEK_PRE_ROLL
,
TEST_OPUS_CODEC_PRIVATE
,
TEST_DEFAULT_DURATION_NS
)
.
addSimpleBlockMediaWithFixedSizeLacing
(
2
/* trackNumber */
,
0
/* clusterTimecode */
,
0
/* blockTimecode */
,
20
,
media
)
.
build
(
1
);
consume
(
data
);
assertAudioFormat
(
MimeTypes
.
AUDIO_OPUS
);
for
(
int
i
=
0
;
i
<
20
;
i
++)
{
long
expectedTimeUs
=
i
*
TEST_DEFAULT_DURATION_NS
/
1000
;
assertSample
(
Arrays
.
copyOfRange
(
media
,
i
*
5
,
i
*
5
+
5
),
expectedTimeUs
,
true
,
false
,
null
,
audioOutput
);
}
}
}
private
void
consume
(
byte
[]
data
)
throws
IOException
,
InterruptedException
{
private
void
consume
(
byte
[]
data
)
throws
IOException
,
InterruptedException
{
...
@@ -554,6 +610,13 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -554,6 +610,13 @@ public class WebmExtractorTest extends InstrumentationTestCase {
assertEquals
(
MimeTypes
.
VIDEO_VP9
,
format
.
mimeType
);
assertEquals
(
MimeTypes
.
VIDEO_VP9
,
format
.
mimeType
);
}
}
private
void
assertH264VideoFormat
()
{
MediaFormat
format
=
videoOutput
.
format
;
assertEquals
(
TEST_WIDTH
,
format
.
width
);
assertEquals
(
TEST_HEIGHT
,
format
.
height
);
assertEquals
(
MimeTypes
.
VIDEO_H264
,
format
.
mimeType
);
}
private
void
assertAudioFormat
(
String
expectedMimeType
)
{
private
void
assertAudioFormat
(
String
expectedMimeType
)
{
MediaFormat
format
=
audioOutput
.
format
;
MediaFormat
format
=
audioOutput
.
format
;
assertEquals
(
TEST_CHANNEL_COUNT
,
format
.
channelCount
);
assertEquals
(
TEST_CHANNEL_COUNT
,
format
.
channelCount
);
...
@@ -583,18 +646,18 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -583,18 +646,18 @@ public class WebmExtractorTest extends InstrumentationTestCase {
}
}
}
}
private
void
assertSample
(
byte
[]
expectedMedia
,
int
timeUs
,
boolean
keyfram
e
,
private
void
assertSample
(
byte
[]
expectedMedia
,
long
timeUs
,
boolean
keyframe
,
boolean
invisibl
e
,
b
oolean
invisible
,
boolean
encrypted
,
TestTrackOutput
output
)
{
b
yte
[]
encryptionKey
,
TestTrackOutput
output
)
{
if
(
encrypt
ed
)
{
if
(
encrypt
ionKey
!=
null
)
{
expectedMedia
=
StreamBuilder
.
joinByteArrays
(
expectedMedia
=
StreamBuilder
.
joinByteArrays
(
new
byte
[]
{(
byte
)
StreamBuilder
.
TEST_INITIALIZATION_VECTOR
.
length
},
new
byte
[]
{(
byte
)
StreamBuilder
.
TEST_INITIALIZATION_VECTOR
.
length
},
StreamBuilder
.
TEST_INITIALIZATION_VECTOR
,
expectedMedia
);
StreamBuilder
.
TEST_INITIALIZATION_VECTOR
,
expectedMedia
);
}
}
android
.
test
.
MoreAsserts
.
assertEquals
(
expectedMedia
,
output
.
sampleData
)
;
int
flags
=
0
;
assertEquals
(
timeUs
,
output
.
sampleTimeUs
)
;
flags
|=
keyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
;
assertEquals
(
keyframe
,
(
output
.
sampleFlags
&
C
.
SAMPLE_FLAG_SYNC
)
!=
0
)
;
flags
|=
invisible
?
C
.
SAMPLE_FLAG_DECODE_ONLY
:
0
;
assertEquals
(
invisible
,
(
output
.
sampleFlags
&
C
.
SAMPLE_FLAG_DECODE_ONLY
)
!=
0
)
;
flags
|=
encryptionKey
!=
null
?
C
.
SAMPLE_FLAG_ENCRYPTED
:
0
;
assertEquals
(
encrypted
,
(
output
.
sampleFlags
&
C
.
SAMPLE_FLAG_ENCRYPTED
)
!=
0
);
output
.
assertNextSample
(
expectedMedia
,
timeUs
,
flags
,
encryptionKey
);
}
}
private
byte
[]
getVorbisCodecPrivate
()
{
private
byte
[]
getVorbisCodecPrivate
()
{
...
@@ -666,10 +729,21 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -666,10 +729,21 @@ public class WebmExtractorTest extends InstrumentationTestCase {
/** Implements {@link TrackOutput} for test purposes. */
/** Implements {@link TrackOutput} for test purposes. */
public
static
class
TestTrackOutput
implements
TrackOutput
{
public
static
class
TestTrackOutput
implements
TrackOutput
{
private
final
Queue
<
byte
[]>
sampleData
;
private
final
Queue
<
Long
>
sampleTimesUs
;
private
final
Queue
<
Integer
>
sampleFlags
;
private
final
Queue
<
Integer
>
sampleSizes
;
private
final
Queue
<
byte
[]>
sampleEncryptionKeys
;
public
MediaFormat
format
;
public
MediaFormat
format
;
private
long
sampleTimeUs
;
private
byte
[]
currentSampleData
;
private
int
sampleFlags
;
private
byte
[]
sampleData
;
public
TestTrackOutput
()
{
sampleData
=
new
LinkedList
<
byte
[]>();
sampleTimesUs
=
new
LinkedList
<
Long
>();
sampleFlags
=
new
LinkedList
<
Integer
>();
sampleSizes
=
new
LinkedList
<
Integer
>();
sampleEncryptionKeys
=
new
LinkedList
<
byte
[]>();
}
@Override
@Override
public
void
format
(
MediaFormat
format
)
{
public
void
format
(
MediaFormat
format
)
{
...
@@ -681,8 +755,8 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -681,8 +755,8 @@ public class WebmExtractorTest extends InstrumentationTestCase {
InterruptedException
{
InterruptedException
{
byte
[]
newData
=
new
byte
[
length
];
byte
[]
newData
=
new
byte
[
length
];
input
.
readFully
(
newData
,
0
,
length
);
input
.
readFully
(
newData
,
0
,
length
);
sampleData
=
currentSampleData
=
currentSampleData
==
null
sampleData
==
null
?
newData
:
StreamBuilder
.
joinByteArrays
(
s
ampleData
,
newData
);
?
newData
:
StreamBuilder
.
joinByteArrays
(
currentS
ampleData
,
newData
);
return
length
;
return
length
;
}
}
...
@@ -690,14 +764,31 @@ public class WebmExtractorTest extends InstrumentationTestCase {
...
@@ -690,14 +764,31 @@ public class WebmExtractorTest extends InstrumentationTestCase {
public
void
sampleData
(
ParsableByteArray
data
,
int
length
)
{
public
void
sampleData
(
ParsableByteArray
data
,
int
length
)
{
byte
[]
newData
=
new
byte
[
length
];
byte
[]
newData
=
new
byte
[
length
];
data
.
readBytes
(
newData
,
0
,
length
);
data
.
readBytes
(
newData
,
0
,
length
);
sampleData
=
currentSampleData
=
currentSampleData
==
null
sampleData
==
null
?
newData
:
StreamBuilder
.
joinByteArrays
(
s
ampleData
,
newData
);
?
newData
:
StreamBuilder
.
joinByteArrays
(
currentS
ampleData
,
newData
);
}
}
@Override
@Override
public
void
sampleMetadata
(
long
timeUs
,
int
flags
,
int
size
,
int
offset
,
byte
[]
encryptionKey
)
{
public
void
sampleMetadata
(
long
timeUs
,
int
flags
,
int
size
,
int
offset
,
byte
[]
encryptionKey
)
{
this
.
sampleTimeUs
=
timeUs
;
sampleData
.
add
(
currentSampleData
);
this
.
sampleFlags
=
flags
;
sampleTimesUs
.
add
(
timeUs
);
sampleFlags
.
add
(
flags
);
sampleSizes
.
add
(
size
);
sampleEncryptionKeys
.
add
(
encryptionKey
);
currentSampleData
=
null
;
}
public
void
assertNextSample
(
byte
[]
data
,
Long
timeUs
,
Integer
flags
,
byte
[]
encryptionKey
)
{
assertEquals
((
Integer
)
data
.
length
,
sampleSizes
.
poll
());
MoreAsserts
.
assertEquals
(
data
,
sampleData
.
poll
());
assertEquals
(
timeUs
,
sampleTimesUs
.
poll
());
assertEquals
(
flags
,
sampleFlags
.
poll
());
byte
[]
sampleEncryptionKey
=
sampleEncryptionKeys
.
poll
();
if
(
encryptionKey
==
null
)
{
assertEquals
(
null
,
sampleEncryptionKey
);
}
else
{
MoreAsserts
.
assertEquals
(
encryptionKey
,
sampleEncryptionKey
);
}
}
}
}
}
...
...
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