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
4a1fed9e
authored
Apr 11, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add new style WebM extractor.
parent
6c5af232
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
2799 additions
and
7 deletions
library/src/main/java/com/google/android/exoplayer/extractor/ChunkIndex.java
library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java
library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java
library/src/main/java/com/google/android/exoplayer/extractor/ExtractorOutput.java
library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java
library/src/main/java/com/google/android/exoplayer/extractor/SeekMap.java
library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java
library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/extractor/webm/DefaultEbmlReader.java
library/src/main/java/com/google/android/exoplayer/extractor/webm/EbmlReader.java
library/src/main/java/com/google/android/exoplayer/extractor/webm/EbmlReaderOutput.java
library/src/main/java/com/google/android/exoplayer/extractor/webm/VarintReader.java
library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java
library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java
library/src/test/java/com/google/android/exoplayer/extractor/webm/DefaultEbmlReaderTest.java
library/src/test/java/com/google/android/exoplayer/extractor/webm/VarintReaderTest.java
library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java
library/src/test/java/com/google/android/exoplayer/testutil/FakeDataSource.java
library/src/main/java/com/google/android/exoplayer/extractor/ChunkIndex.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
;
import
com.google.android.exoplayer.util.Util
;
/**
* Defines chunks of samples within a media stream.
*/
public
final
class
ChunkIndex
implements
SeekMap
{
/**
* The number of chunks.
*/
public
final
int
length
;
/**
* The chunk sizes, in bytes.
*/
public
final
int
[]
sizes
;
/**
* The chunk byte offsets.
*/
public
final
long
[]
offsets
;
/**
* The chunk durations, in microseconds.
*/
public
final
long
[]
durationsUs
;
/**
* The start time of each chunk, in microseconds.
*/
public
final
long
[]
timesUs
;
/**
* @param sizes The chunk sizes, in bytes.
* @param offsets The chunk byte offsets.
* @param durationsUs The chunk durations, in microseconds.
* @param timesUs The start time of each chunk, in microseconds.
*/
public
ChunkIndex
(
int
[]
sizes
,
long
[]
offsets
,
long
[]
durationsUs
,
long
[]
timesUs
)
{
this
.
length
=
sizes
.
length
;
this
.
sizes
=
sizes
;
this
.
offsets
=
offsets
;
this
.
durationsUs
=
durationsUs
;
this
.
timesUs
=
timesUs
;
}
/**
* Obtains the index of the chunk corresponding to a given time.
*
* @param timeUs The time, in microseconds.
* @return The index of the corresponding chunk.
*/
public
int
getChunkIndex
(
long
timeUs
)
{
return
Util
.
binarySearchFloor
(
timesUs
,
timeUs
,
true
,
true
);
}
@Override
public
long
getPosition
(
long
timeUs
)
{
return
offsets
[
getChunkIndex
(
timeUs
)];
}
}
library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java
View file @
4a1fed9e
...
...
@@ -213,6 +213,11 @@ public final class DefaultTrackOutput implements TrackOutput {
}
@Override
public
int
sampleData
(
ExtractorInput
input
,
int
length
)
throws
IOException
,
InterruptedException
{
return
rollingBuffer
.
appendData
(
input
,
length
);
}
@Override
public
void
sampleData
(
ParsableByteArray
buffer
,
int
length
)
{
rollingBuffer
.
appendData
(
buffer
,
length
);
}
...
...
library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java
View file @
4a1fed9e
...
...
@@ -55,4 +55,14 @@ public interface Extractor {
*/
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
;
/**
* Notifies the extractor that a seek has occurred.
* <p>
* Following a call to this method, the {@link ExtractorInput} passed to the next invocation of
* {@link #read(ExtractorInput)} is required to provide data starting from any random access
* position in the stream. Random access positions can be obtained from a {@link SeekMap} that
* has been extracted and passed to the {@link ExtractorOutput}.
*/
void
seek
();
}
library/src/main/java/com/google/android/exoplayer/extractor/ExtractorOutput.java
View file @
4a1fed9e
...
...
@@ -15,6 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer
.
extractor
;
import
com.google.android.exoplayer.drm.DrmInitData
;
/**
* Receives stream level data extracted by an {@link Extractor}.
*/
...
...
@@ -36,4 +38,18 @@ public interface ExtractorOutput {
*/
void
endTracks
();
/**
* Invoked when a {@link SeekMap} has been extracted from the stream.
*
* @param seekMap The extracted {@link SeekMap}.
*/
void
seekMap
(
SeekMap
seekMap
);
/**
* Invoked when {@link DrmInitData} has been extracted from the stream.
*
* @param drmInitData The extracted {@link DrmInitData}.
*/
void
drmInitData
(
DrmInitData
drmInitData
);
}
library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java
View file @
4a1fed9e
...
...
@@ -315,6 +315,23 @@ import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Appends data to the rolling buffer.
*
* @param input The source from which to read.
* @param length The maximum length of the read.
* @return The number of bytes appended.
* @throws IOException If an error occurs reading from the source.
*/
public
int
appendData
(
ExtractorInput
input
,
int
length
)
throws
IOException
,
InterruptedException
{
ensureSpaceForWrite
();
int
thisWriteLength
=
Math
.
min
(
length
,
fragmentLength
-
lastFragmentOffset
);
input
.
readFully
(
lastFragment
,
lastFragmentOffset
,
thisWriteLength
);
lastFragmentOffset
+=
thisWriteLength
;
totalBytesWritten
+=
thisWriteLength
;
return
thisWriteLength
;
}
/**
* Appends data to the rolling buffer.
*
* @param buffer A buffer containing the data to append.
* @param length The length of the data to append.
*/
...
...
library/src/main/java/com/google/android/exoplayer/extractor/SeekMap.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
;
/**
* Maps seek positions (in microseconds) to corresponding positions (byte offsets) in the stream.
*/
public
interface
SeekMap
{
/**
* Maps a seek position in microseconds to a corresponding position (byte offset) in the stream
* from which data can be provided to the extractor.
*
* @param timeUs A seek position in microseconds.
* @return The corresponding position (byte offset) in the stream from which data can be provided
* to the extractor.
*/
long
getPosition
(
long
timeUs
);
}
library/src/main/java/com/google/android/exoplayer/extractor/TrackOutput.java
View file @
4a1fed9e
...
...
@@ -19,6 +19,8 @@ import com.google.android.exoplayer.MediaFormat;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.io.IOException
;
/**
* Receives track level data extracted by an {@link Extractor}.
*/
...
...
@@ -34,6 +36,17 @@ public interface TrackOutput {
/**
* Invoked to write sample data to the output.
*
* @param input An {@link ExtractorInput} from which to read the sample data.
* @param length The maximum length to read from the input.
* @return The number of bytes appended.
* @throws IOException If an error occurred reading from the input.
* @throws InterruptedException If the thread was interrupted.
*/
int
sampleData
(
ExtractorInput
input
,
int
length
)
throws
IOException
,
InterruptedException
;
/**
* Invoked to write sample data to the output.
*
* @param data A {@link ParsableByteArray} from which to read the sample data.
* @param length The number of bytes to read.
*/
...
...
@@ -43,14 +56,14 @@ public interface TrackOutput {
* Invoked when metadata associated with a sample has been extracted from the stream.
* <p>
* The corresponding sample data will have already been passed to the output via calls to
* {@link #sampleData(ParsableByteArray, int)}.
* {@link #sampleData(
ExtractorInput, int)} or {@link #sampleData(
ParsableByteArray, int)}.
*
* @param timeUs The media timestamp associated with the sample, in microseconds.
* @param flags Flags associated with the sample. See {@link SampleHolder#flags}.
* @param size The size of the sample data, in bytes.
* @param offset The number of bytes that have been passed to
* {@link #sampleData(
ParsableByteArray, int)} since the last byte belonging to the sample
* whose metadata is being passed.
* {@link #sampleData(
ExtractorInput, int)} or {@link #sampleData(ParsableByteArray, int)}
*
since the last byte belonging to the sample
whose metadata is being passed.
* @param encryptionKey The encryption key associated with the sample. May be null.
*/
void
sampleMetadata
(
long
timeUs
,
int
flags
,
int
size
,
int
offset
,
byte
[]
encryptionKey
);
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java
View file @
4a1fed9e
...
...
@@ -50,8 +50,12 @@ public class AdtsExtractor implements Extractor {
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
public
void
seek
()
{
throw
new
UnsupportedOperationException
();
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
int
bytesRead
=
input
.
read
(
packetBuffer
.
data
,
0
,
MAX_PACKET_SIZE
);
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_INPUT
;
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java
View file @
4a1fed9e
...
...
@@ -72,8 +72,12 @@ public final class TsExtractor implements Extractor {
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
public
void
seek
()
{
throw
new
UnsupportedOperationException
();
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
if
(!
input
.
readFully
(
tsPacketBuffer
.
data
,
0
,
TS_PACKET_SIZE
,
true
))
{
return
RESULT_END_OF_INPUT
;
}
...
...
library/src/main/java/com/google/android/exoplayer/extractor/webm/DefaultEbmlReader.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
.
webm
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.io.IOException
;
import
java.nio.charset.Charset
;
import
java.util.Stack
;
/**
* Default implementation of {@link EbmlReader}.
*/
/* package */
final
class
DefaultEbmlReader
implements
EbmlReader
{
private
static
final
int
ELEMENT_STATE_READ_ID
=
0
;
private
static
final
int
ELEMENT_STATE_READ_CONTENT_SIZE
=
1
;
private
static
final
int
ELEMENT_STATE_READ_CONTENT
=
2
;
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
;
private
final
byte
[]
scratch
=
new
byte
[
8
];
private
final
Stack
<
MasterElement
>
masterElementsStack
=
new
Stack
<
MasterElement
>();
private
final
VarintReader
varintReader
=
new
VarintReader
();
private
EbmlReaderOutput
output
;
private
int
elementState
;
private
int
elementId
;
private
long
elementContentSize
;
@Override
public
void
init
(
EbmlReaderOutput
eventHandler
)
{
this
.
output
=
eventHandler
;
}
@Override
public
void
reset
()
{
elementState
=
ELEMENT_STATE_READ_ID
;
masterElementsStack
.
clear
();
varintReader
.
reset
();
}
@Override
public
boolean
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
Assertions
.
checkState
(
output
!=
null
);
while
(
true
)
{
if
(!
masterElementsStack
.
isEmpty
()
&&
input
.
getPosition
()
>=
masterElementsStack
.
peek
().
elementEndPosition
)
{
output
.
endMasterElement
(
masterElementsStack
.
pop
().
elementId
);
return
true
;
}
if
(
elementState
==
ELEMENT_STATE_READ_ID
)
{
long
result
=
varintReader
.
readUnsignedVarint
(
input
,
true
,
false
);
if
(
result
==
-
1
)
{
return
false
;
}
// Element IDs are at most 4 bytes, so we can cast to integers.
elementId
=
(
int
)
result
;
elementState
=
ELEMENT_STATE_READ_CONTENT_SIZE
;
}
if
(
elementState
==
ELEMENT_STATE_READ_CONTENT_SIZE
)
{
elementContentSize
=
varintReader
.
readUnsignedVarint
(
input
,
false
,
true
);
elementState
=
ELEMENT_STATE_READ_CONTENT
;
}
int
type
=
output
.
getElementType
(
elementId
);
switch
(
type
)
{
case
TYPE_MASTER:
long
elementContentPosition
=
input
.
getPosition
();
long
elementEndPosition
=
elementContentPosition
+
elementContentSize
;
masterElementsStack
.
add
(
new
MasterElement
(
elementId
,
elementEndPosition
));
output
.
startMasterElement
(
elementId
,
elementContentPosition
,
elementContentSize
);
elementState
=
ELEMENT_STATE_READ_ID
;
return
true
;
case
TYPE_UNSIGNED_INT:
if
(
elementContentSize
>
MAX_INTEGER_ELEMENT_SIZE_BYTES
)
{
throw
new
IllegalStateException
(
"Invalid integer size: "
+
elementContentSize
);
}
output
.
integerElement
(
elementId
,
readInteger
(
input
,
(
int
)
elementContentSize
));
elementState
=
ELEMENT_STATE_READ_ID
;
return
true
;
case
TYPE_FLOAT:
if
(
elementContentSize
!=
VALID_FLOAT32_ELEMENT_SIZE_BYTES
&&
elementContentSize
!=
VALID_FLOAT64_ELEMENT_SIZE_BYTES
)
{
throw
new
IllegalStateException
(
"Invalid float size: "
+
elementContentSize
);
}
output
.
floatElement
(
elementId
,
readFloat
(
input
,
(
int
)
elementContentSize
));
elementState
=
ELEMENT_STATE_READ_ID
;
return
true
;
case
TYPE_STRING:
if
(
elementContentSize
>
Integer
.
MAX_VALUE
)
{
throw
new
IllegalStateException
(
"String element size: "
+
elementContentSize
);
}
output
.
stringElement
(
elementId
,
readString
(
input
,
(
int
)
elementContentSize
));
elementState
=
ELEMENT_STATE_READ_ID
;
return
true
;
case
TYPE_BINARY:
output
.
binaryElement
(
elementId
,
(
int
)
elementContentSize
,
input
);
elementState
=
ELEMENT_STATE_READ_ID
;
return
true
;
case
TYPE_UNKNOWN:
input
.
skipFully
((
int
)
elementContentSize
);
elementState
=
ELEMENT_STATE_READ_ID
;
break
;
default
:
throw
new
IllegalStateException
(
"Invalid element type "
+
type
);
}
}
}
/**
* Reads and returns an integer of length {@code byteLength} from the {@link ExtractorInput}.
*
* @param input The {@link ExtractorInput} from which to read.
* @param byteLength The length of the integer being read.
* @return The read integer value.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
private
long
readInteger
(
ExtractorInput
input
,
int
byteLength
)
throws
IOException
,
InterruptedException
{
input
.
readFully
(
scratch
,
0
,
byteLength
);
long
value
=
0
;
for
(
int
i
=
0
;
i
<
byteLength
;
i
++)
{
value
=
(
value
<<
8
)
|
(
scratch
[
i
]
&
0xFF
);
}
return
value
;
}
/**
* Reads and returns a float of length {@code byteLength} from the {@link ExtractorInput}.
*
* @param input The {@link ExtractorInput} from which to read.
* @param byteLength The length of the float being read.
* @return The read float value.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
private
double
readFloat
(
ExtractorInput
input
,
int
byteLength
)
throws
IOException
,
InterruptedException
{
long
integerValue
=
readInteger
(
input
,
byteLength
);
double
floatValue
;
if
(
byteLength
==
VALID_FLOAT32_ELEMENT_SIZE_BYTES
)
{
floatValue
=
Float
.
intBitsToFloat
((
int
)
integerValue
);
}
else
{
floatValue
=
Double
.
longBitsToDouble
(
integerValue
);
}
return
floatValue
;
}
/**
* Reads and returns a string of length {@code byteLength} from the {@link ExtractorInput}.
*
* @param input The {@link ExtractorInput} from which to read.
* @param byteLength The length of the float being read.
* @return The read string value.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
private
String
readString
(
ExtractorInput
input
,
int
byteLength
)
throws
IOException
,
InterruptedException
{
byte
[]
stringBytes
=
new
byte
[
byteLength
];
input
.
readFully
(
stringBytes
,
0
,
byteLength
);
return
new
String
(
stringBytes
,
Charset
.
forName
(
C
.
UTF8_NAME
));
}
/**
* Used in {@link #masterElementsStack} to track when the current master element ends, so that
* {@link EbmlReaderOutput#endMasterElement(int)} can be called.
*/
private
static
final
class
MasterElement
{
private
final
int
elementId
;
private
final
long
elementEndPosition
;
private
MasterElement
(
int
elementId
,
long
elementEndPosition
)
{
this
.
elementId
=
elementId
;
this
.
elementEndPosition
=
elementEndPosition
;
}
}
}
library/src/main/java/com/google/android/exoplayer/extractor/webm/EbmlReader.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
.
webm
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
java.io.IOException
;
/**
* Event-driven EBML reader that delivers events to an {@link EbmlReaderOutput}.
* <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 */
interface
EbmlReader
{
/**
* Type for unknown elements.
*/
public
static
final
int
TYPE_UNKNOWN
=
0
;
/**
* Type for elements that contain child elements.
*/
public
static
final
int
TYPE_MASTER
=
1
;
/**
* Type for integer value elements of up to 8 bytes.
*/
public
static
final
int
TYPE_UNSIGNED_INT
=
2
;
/**
* Type for string elements.
*/
public
static
final
int
TYPE_STRING
=
3
;
/**
* Type for binary elements.
*/
public
static
final
int
TYPE_BINARY
=
4
;
/**
* Type for IEEE floating point value elements of either 4 or 8 bytes.
*/
public
static
final
int
TYPE_FLOAT
=
5
;
/**
* Initializes the extractor with an {@link EbmlReaderOutput}.
*
* @param output An {@link EbmlReaderOutput} to receive events.
*/
public
void
init
(
EbmlReaderOutput
output
);
/**
* Resets the state of the reader.
* <p>
* Subsequent calls to {@link #read(ExtractorInput)} will start reading a new EBML structure
* from scratch.
*/
public
void
reset
();
/**
* Reads from an {@link ExtractorInput}, invoking an event callback if possible.
*
* @param input The {@link ExtractorInput} from which data should be read.
* @return True if data can continue to be read. False if the end of the input was encountered.
* @throws ParserException If parsing fails.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
public
boolean
read
(
ExtractorInput
input
)
throws
ParserException
,
IOException
,
InterruptedException
;
}
library/src/main/java/com/google/android/exoplayer/extractor/webm/EbmlReaderOutput.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
.
webm
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
java.io.IOException
;
/**
* Defines EBML element IDs/types and reacts to events.
*/
/* package */
interface
EbmlReaderOutput
{
/**
* Maps an element ID to a corresponding type.
* <p>
* If {@link EbmlReader#TYPE_UNKNOWN} is returned then the element is skipped. Note that all
* children of a skipped element are also skipped.
*
* @param id The element ID to map.
* @return One of the {@code TYPE_} constants defined in {@link EbmlReader}.
*/
int
getElementType
(
int
id
);
/**
* Called when the start of a master element is encountered.
* <p>
* Following events should be considered as taking place within this element until a matching call
* to {@link #endMasterElement(int)} is made.
* <p>
* Note that it is possible for another master element of the same element ID to be nested within
* itself.
*
* @param id The element ID.
* @param contentPosition The position of the start of the element's content in the stream.
* @param contentSize The size of the element's content in bytes.
* @throws ParserException If a parsing error occurs.
*/
void
startMasterElement
(
int
id
,
long
contentPosition
,
long
contentSize
)
throws
ParserException
;
/**
* Called when the end of a master element is encountered.
*
* @param id The element ID.
* @throws ParserException If a parsing error occurs.
*/
void
endMasterElement
(
int
id
)
throws
ParserException
;
/**
* Called when an integer element is encountered.
*
* @param id The element ID.
* @param value The integer value that the element contains.
* @throws ParserException If a parsing error occurs.
*/
void
integerElement
(
int
id
,
long
value
)
throws
ParserException
;
/**
* Called when a float element is encountered.
*
* @param id The element ID.
* @param value The float value that the element contains
* @throws ParserException If a parsing error occurs.
*/
void
floatElement
(
int
id
,
double
value
)
throws
ParserException
;
/**
* Called when a string element is encountered.
*
* @param id The element ID.
* @param value The string value that the element contains.
* @throws ParserException If a parsing error occurs.
*/
void
stringElement
(
int
id
,
String
value
)
throws
ParserException
;
/**
* Called when a binary element is encountered.
* <p>
* The element header (containing the element ID and content size) will already have been read.
* Implementations are required to consume the whole remainder of the element, which is
* {@code contentSize} bytes in length, before returning. Implementations are permitted to fail
* (by throwing an exception) having partially consumed the data, however if they do this, they
* must consume the remainder of the content when invoked again.
*
* @param id The element ID.
* @param contentsSize The element's content size.
* @param input The {@link ExtractorInput} from which data should be read.
* @throws ParserException If a parsing error occurs.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
void
binaryElement
(
int
id
,
int
contentsSize
,
ExtractorInput
input
)
throws
ParserException
,
IOException
,
InterruptedException
;
}
library/src/main/java/com/google/android/exoplayer/extractor/webm/VarintReader.java
0 → 100644
View file @
4a1fed9e
package
com
.
google
.
android
.
exoplayer
.
extractor
.
webm
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
java.io.EOFException
;
import
java.io.IOException
;
/**
* Reads EBML variable-length integers (varints) from an {@link ExtractorInput}.
*/
/* package */
class
VarintReader
{
private
static
final
int
STATE_BEGIN_READING
=
0
;
private
static
final
int
STATE_READ_CONTENTS
=
1
;
/**
* 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
final
byte
[]
scratch
;
private
int
state
;
private
int
length
;
public
VarintReader
()
{
scratch
=
new
byte
[
8
];
}
/**
* Resets the reader to start reading a new variable-length integer.
*/
public
void
reset
()
{
state
=
STATE_BEGIN_READING
;
length
=
0
;
}
/**
* Reads an EBML variable-length integer (varint) from an {@link ExtractorInput} such that
* reading can be resumed later if an error occurs having read only some of it.
* <p>
* If an value is successfully read, then the reader will automatically reset itself ready to
* read another value.
* <p>
* If an {@link IOException} or {@link InterruptedException} is throw, the read can be resumed
* later by calling this method again, passing an {@link ExtractorInput} providing data starting
* where the previous one left off.
*
* @param input The {@link ExtractorInput} from which the integer should be read.
* @param allowEndOfInput True if encountering the end of the input having read no data is
* allowed, and should result in {@code -1} being returned. False if it should be
* considered an error, causing an {@link EOFException} to be thrown.
* @param removeLengthMask Removes the variable-length integer length mask from the value
* @return The read value, or -1 if {@code allowEndOfStream} is true and the end of the input was
* encountered.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
public
long
readUnsignedVarint
(
ExtractorInput
input
,
boolean
allowEndOfInput
,
boolean
removeLengthMask
)
throws
IOException
,
InterruptedException
{
if
(
state
==
STATE_BEGIN_READING
)
{
// Read the first byte to establish the length.
if
(!
input
.
readFully
(
scratch
,
0
,
1
,
allowEndOfInput
))
{
return
-
1
;
}
int
firstByte
=
scratch
[
0
]
&
0xFF
;
length
=
-
1
;
for
(
int
i
=
0
;
i
<
VARINT_LENGTH_MASKS
.
length
;
i
++)
{
if
((
VARINT_LENGTH_MASKS
[
i
]
&
firstByte
)
!=
0
)
{
length
=
i
+
1
;
break
;
}
}
if
(
length
==
-
1
)
{
throw
new
IllegalStateException
(
"No valid varint length mask found"
);
}
state
=
STATE_READ_CONTENTS
;
}
// Read the remaining bytes.
input
.
readFully
(
scratch
,
1
,
length
-
1
);
// Parse the value.
if
(
removeLengthMask
)
{
scratch
[
0
]
&=
~
VARINT_LENGTH_MASKS
[
length
-
1
];
}
long
varint
=
0
;
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
varint
=
(
varint
<<
8
)
|
(
scratch
[
i
]
&
0xFF
);
}
state
=
STATE_BEGIN_READING
;
return
varint
;
}
/**
* Returns the number of bytes occupied by the most recently parsed varint.
*/
public
int
getLastLength
()
{
return
length
;
}
}
library/src/main/java/com/google/android/exoplayer/extractor/webm/WebmExtractor.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
.
webm
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.drm.DrmInitData
;
import
com.google.android.exoplayer.extractor.ChunkIndex
;
import
com.google.android.exoplayer.extractor.Extractor
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
com.google.android.exoplayer.extractor.ExtractorOutput
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.concurrent.TimeUnit
;
/**
* 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>.
* RFC on encrypted WebM can be found
* <a href="http://wiki.webmproject.org/encryption/webm-encryption-rfc">here</a>.
*/
public
final
class
WebmExtractor
implements
Extractor
{
private
static
final
int
SAMPLE_STATE_START
=
0
;
private
static
final
int
SAMPLE_STATE_HEADER
=
1
;
private
static
final
int
SAMPLE_STATE_DATA
=
2
;
private
static
final
String
DOC_TYPE_WEBM
=
"webm"
;
private
static
final
String
CODEC_ID_VP9
=
"V_VP9"
;
private
static
final
String
CODEC_ID_VORBIS
=
"A_VORBIS"
;
private
static
final
String
CODEC_ID_OPUS
=
"A_OPUS"
;
private
static
final
int
VORBIS_MAX_INPUT_SIZE
=
8192
;
private
static
final
int
OPUS_MAX_INPUT_SIZE
=
5760
;
private
static
final
int
ENCRYPTION_IV_SIZE
=
8
;
private
static
final
int
UNKNOWN
=
-
1
;
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_BLOCK_GROUP
=
0xA0
;
private
static
final
int
ID_BLOCK
=
0xA1
;
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_CODEC_PRIVATE
=
0x63A2
;
private
static
final
int
ID_CODEC_DELAY
=
0x56AA
;
private
static
final
int
ID_SEEK_PRE_ROLL
=
0x56BB
;
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_AUDIO
=
0xE1
;
private
static
final
int
ID_CHANNELS
=
0x9F
;
private
static
final
int
ID_SAMPLING_FREQUENCY
=
0xB5
;
private
static
final
int
ID_CONTENT_ENCODINGS
=
0x6D80
;
private
static
final
int
ID_CONTENT_ENCODING
=
0x6240
;
private
static
final
int
ID_CONTENT_ENCODING_ORDER
=
0x5031
;
private
static
final
int
ID_CONTENT_ENCODING_SCOPE
=
0x5032
;
private
static
final
int
ID_CONTENT_ENCODING_TYPE
=
0x5033
;
private
static
final
int
ID_CONTENT_ENCRYPTION
=
0x5035
;
private
static
final
int
ID_CONTENT_ENCRYPTION_ALGORITHM
=
0x47E1
;
private
static
final
int
ID_CONTENT_ENCRYPTION_KEY_ID
=
0x47E2
;
private
static
final
int
ID_CONTENT_ENCRYPTION_AES_SETTINGS
=
0x47E7
;
private
static
final
int
ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE
=
0x47E8
;
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
;
private
static
final
int
LACING_NONE
=
0
;
private
final
EbmlReader
reader
;
private
final
VarintReader
varintReader
;
private
final
ParsableByteArray
sampleHeaderScratch
;
private
long
segmentContentPosition
=
UNKNOWN
;
private
long
segmentContentSize
=
UNKNOWN
;
private
long
timecodeScale
=
1000000L
;
private
long
durationUs
=
C
.
UNKNOWN_TIME_US
;
private
int
pixelWidth
=
UNKNOWN
;
private
int
pixelHeight
=
UNKNOWN
;
private
int
channelCount
=
UNKNOWN
;
private
int
sampleRate
=
UNKNOWN
;
private
byte
[]
codecPrivate
;
private
String
codecId
;
private
long
codecDelayNs
;
private
long
seekPreRollNs
;
private
boolean
isAudioTrack
;
private
boolean
hasContentEncryption
;
private
byte
[]
encryptionKeyId
;
private
long
clusterTimecodeUs
=
UNKNOWN
;
private
LongArray
cueTimesUs
;
private
LongArray
cueClusterPositions
;
// Sample reading state.
private
int
blockBytesRead
;
private
int
sampleState
;
private
int
sampleSize
;
private
int
sampleFlags
;
private
long
sampleTimeUs
;
private
boolean
sampleRead
;
// Extractor outputs.
private
ExtractorOutput
extractorOutput
;
private
TrackOutput
trackOutput
;
public
WebmExtractor
()
{
this
(
new
DefaultEbmlReader
());
}
/* package */
WebmExtractor
(
EbmlReader
reader
)
{
this
.
reader
=
reader
;
this
.
reader
.
init
(
new
InnerEbmlReaderOutput
());
varintReader
=
new
VarintReader
();
sampleHeaderScratch
=
new
ParsableByteArray
(
4
);
}
@Override
public
void
init
(
ExtractorOutput
output
)
{
extractorOutput
=
output
;
trackOutput
=
output
.
track
(
0
);
extractorOutput
.
endTracks
();
}
@Override
public
void
seek
()
{
clusterTimecodeUs
=
UNKNOWN
;
sampleState
=
SAMPLE_STATE_START
;
reader
.
reset
();
varintReader
.
reset
();
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
sampleRead
=
false
;
boolean
inputHasData
=
true
;
while
(!
sampleRead
&&
inputHasData
)
{
inputHasData
=
reader
.
read
(
input
);
}
return
inputHasData
?
Extractor
.
RESULT_CONTINUE
:
Extractor
.
RESULT_END_OF_INPUT
;
}
/* package */
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_AUDIO:
case
ID_VIDEO:
case
ID_CONTENT_ENCODINGS:
case
ID_CONTENT_ENCODING:
case
ID_CONTENT_ENCRYPTION:
case
ID_CONTENT_ENCRYPTION_AES_SETTINGS:
case
ID_CUES:
case
ID_CUE_POINT:
case
ID_CUE_TRACK_POSITIONS:
case
ID_BLOCK_GROUP:
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_CODEC_DELAY:
case
ID_SEEK_PRE_ROLL:
case
ID_CHANNELS:
case
ID_CONTENT_ENCODING_ORDER:
case
ID_CONTENT_ENCODING_SCOPE:
case
ID_CONTENT_ENCODING_TYPE:
case
ID_CONTENT_ENCRYPTION_ALGORITHM:
case
ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE:
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_CONTENT_ENCRYPTION_KEY_ID:
case
ID_SIMPLE_BLOCK:
case
ID_BLOCK:
case
ID_CODEC_PRIVATE:
return
EbmlReader
.
TYPE_BINARY
;
case
ID_DURATION:
case
ID_SAMPLING_FREQUENCY:
return
EbmlReader
.
TYPE_FLOAT
;
default
:
return
EbmlReader
.
TYPE_UNKNOWN
;
}
}
/* package */
void
startMasterElement
(
int
id
,
long
contentPosition
,
long
contentSize
)
throws
ParserException
{
switch
(
id
)
{
case
ID_SEGMENT:
if
(
segmentContentPosition
!=
UNKNOWN
)
{
throw
new
ParserException
(
"Multiple Segment elements not supported"
);
}
segmentContentPosition
=
contentPosition
;
segmentContentSize
=
contentSize
;
return
;
case
ID_CUES:
cueTimesUs
=
new
LongArray
();
cueClusterPositions
=
new
LongArray
();
return
;
case
ID_CONTENT_ENCODING:
// TODO: check and fail if more than one content encoding is present.
return
;
case
ID_CONTENT_ENCRYPTION:
hasContentEncryption
=
true
;
return
;
default
:
return
;
}
}
/* package */
void
endMasterElement
(
int
id
)
throws
ParserException
{
switch
(
id
)
{
case
ID_CUES:
extractorOutput
.
seekMap
(
buildCues
());
return
;
case
ID_CONTENT_ENCODING:
if
(!
hasContentEncryption
)
{
// We found a ContentEncoding other than Encryption.
throw
new
ParserException
(
"Found an unsupported ContentEncoding"
);
}
if
(
encryptionKeyId
==
null
)
{
throw
new
ParserException
(
"Encrypted Track found but ContentEncKeyID was not found"
);
}
extractorOutput
.
drmInitData
(
new
DrmInitData
.
Universal
(
MimeTypes
.
VIDEO_WEBM
,
encryptionKeyId
));
return
;
case
ID_AUDIO:
isAudioTrack
=
true
;
return
;
case
ID_TRACK_ENTRY:
trackOutput
.
format
(
isAudioTrack
?
buildAudioFormat
()
:
buildVideoFormat
());
return
;
default
:
return
;
}
}
/* package */
void
integerElement
(
int
id
,
long
value
)
throws
ParserException
{
switch
(
id
)
{
case
ID_EBML_READ_VERSION:
// Validate that EBMLReadVersion is supported. This extractor only supports v1.
if
(
value
!=
1
)
{
throw
new
ParserException
(
"EBMLReadVersion "
+
value
+
" not supported"
);
}
return
;
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
ParserException
(
"DocTypeReadVersion "
+
value
+
" not supported"
);
}
return
;
case
ID_TIMECODE_SCALE:
timecodeScale
=
value
;
return
;
case
ID_PIXEL_WIDTH:
pixelWidth
=
(
int
)
value
;
return
;
case
ID_PIXEL_HEIGHT:
pixelHeight
=
(
int
)
value
;
return
;
case
ID_CODEC_DELAY:
codecDelayNs
=
value
;
return
;
case
ID_SEEK_PRE_ROLL:
seekPreRollNs
=
value
;
return
;
case
ID_CHANNELS:
channelCount
=
(
int
)
value
;
return
;
case
ID_CONTENT_ENCODING_ORDER:
// This extractor only supports one ContentEncoding element and hence the order has to be 0.
if
(
value
!=
0
)
{
throw
new
ParserException
(
"ContentEncodingOrder "
+
value
+
" not supported"
);
}
return
;
case
ID_CONTENT_ENCODING_SCOPE:
// This extractor only supports the scope of all frames (since that's the only scope used
// for Encryption).
if
(
value
!=
1
)
{
throw
new
ParserException
(
"ContentEncodingScope "
+
value
+
" not supported"
);
}
return
;
case
ID_CONTENT_ENCODING_TYPE:
// This extractor only supports Encrypted ContentEncodingType.
if
(
value
!=
1
)
{
throw
new
ParserException
(
"ContentEncodingType "
+
value
+
" not supported"
);
}
return
;
case
ID_CONTENT_ENCRYPTION_ALGORITHM:
// Only the value 5 (AES) is allowed according to the WebM specification.
if
(
value
!=
5
)
{
throw
new
ParserException
(
"ContentEncAlgo "
+
value
+
" not supported"
);
}
return
;
case
ID_CONTENT_ENCRYPTION_AES_SETTINGS_CIPHER_MODE:
// Only the value 1 is allowed according to the WebM specification.
if
(
value
!=
1
)
{
throw
new
ParserException
(
"AESSettingsCipherMode "
+
value
+
" not supported"
);
}
return
;
case
ID_CUE_TIME:
cueTimesUs
.
add
(
scaleTimecodeToUs
(
value
));
return
;
case
ID_CUE_CLUSTER_POSITION:
cueClusterPositions
.
add
(
value
);
return
;
case
ID_TIME_CODE:
clusterTimecodeUs
=
scaleTimecodeToUs
(
value
);
return
;
default
:
return
;
}
}
/* package */
void
floatElement
(
int
id
,
double
value
)
{
switch
(
id
)
{
case
ID_DURATION:
durationUs
=
scaleTimecodeToUs
((
long
)
value
);
return
;
case
ID_SAMPLING_FREQUENCY:
sampleRate
=
(
int
)
value
;
return
;
default
:
return
;
}
}
/* package */
void
stringElement
(
int
id
,
String
value
)
throws
ParserException
{
switch
(
id
)
{
case
ID_DOC_TYPE:
// Validate that DocType is supported. This extractor only supports "webm".
if
(!
DOC_TYPE_WEBM
.
equals
(
value
))
{
throw
new
ParserException
(
"DocType "
+
value
+
" not supported"
);
}
return
;
case
ID_CODEC_ID:
// Validate that CodecID is supported.
if
(!
isCodecSupported
(
value
))
{
throw
new
ParserException
(
"CodecID "
+
value
+
" not supported"
);
}
codecId
=
value
;
return
;
default
:
return
;
}
}
/* package */
void
binaryElement
(
int
id
,
int
contentSize
,
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
switch
(
id
)
{
case
ID_CODEC_PRIVATE:
codecPrivate
=
new
byte
[
contentSize
];
input
.
readFully
(
codecPrivate
,
0
,
contentSize
);
return
;
case
ID_CONTENT_ENCRYPTION_KEY_ID:
encryptionKeyId
=
new
byte
[
contentSize
];
input
.
readFully
(
encryptionKeyId
,
0
,
contentSize
);
return
;
case
ID_SIMPLE_BLOCK:
case
ID_BLOCK:
// Please refer to http://www.matroska.org/technical/specs/index.html#simpleblock_structure
// and http://matroska.org/technical/specs/index.html#block_structure
// for info about how data is organized in SimpleBlock and Block elements respectively. They
// differ only in the way flags are specified.
if
(
sampleState
==
SAMPLE_STATE_START
)
{
// Value of trackNumber is not used but needs to be read.
varintReader
.
readUnsignedVarint
(
input
,
false
,
false
);
blockBytesRead
=
varintReader
.
getLastLength
();
sampleState
=
SAMPLE_STATE_HEADER
;
}
if
(
sampleState
==
SAMPLE_STATE_HEADER
)
{
byte
[]
sampleHeaderScratchData
=
sampleHeaderScratch
.
data
;
// Next 3 bytes have timecode and flags. If encrypted, the 4th byte is a signal byte.
int
remainingHeaderLength
=
hasContentEncryption
?
4
:
3
;
input
.
readFully
(
sampleHeaderScratchData
,
0
,
remainingHeaderLength
);
blockBytesRead
+=
remainingHeaderLength
;
// First two bytes are the relative timecode.
int
timecode
=
(
sampleHeaderScratchData
[
0
]
<<
8
)
|
(
sampleHeaderScratchData
[
1
]
&
0xFF
);
sampleTimeUs
=
clusterTimecodeUs
+
scaleTimecodeToUs
(
timecode
);
// Third byte contains the lacing value and some flags.
int
lacing
=
(
sampleHeaderScratchData
[
2
]
&
0x06
)
>>
1
;
if
(
lacing
!=
LACING_NONE
)
{
throw
new
ParserException
(
"Lacing mode not supported: "
+
lacing
);
}
boolean
isInvisible
=
(
sampleHeaderScratchData
[
2
]
&
0x08
)
==
0x08
;
boolean
isKeyframe
;
if
(
id
==
ID_BLOCK
)
{
// Matroska Block element does not self-sufficiently say whether it is a keyframe. It
// depends on the existence of another element (ReferenceBlock) which may occur after
// the Block element. Since this extractor uses Block element only for Opus, we set the
// keyframe to be true always since all Opus frames are key frames.
isKeyframe
=
true
;
}
else
{
isKeyframe
=
(
sampleHeaderScratchData
[
2
]
&
0x80
)
==
0x80
;
}
boolean
isEncrypted
=
false
;
// If encrypted, the fourth byte is an encryption signal byte.
if
(
hasContentEncryption
)
{
if
((
sampleHeaderScratchData
[
3
]
&
0x80
)
==
0x80
)
{
throw
new
ParserException
(
"Extension bit is set in signal byte"
);
}
isEncrypted
=
(
sampleHeaderScratchData
[
3
]
&
0x01
)
==
0x01
;
}
sampleFlags
=
(
isKeyframe
?
C
.
SAMPLE_FLAG_SYNC
:
0
)
|
(
isInvisible
?
C
.
SAMPLE_FLAG_DECODE_ONLY
:
0
)
|
(
isEncrypted
?
C
.
SAMPLE_FLAG_ENCRYPTED
:
0
);
sampleSize
=
contentSize
-
blockBytesRead
;
if
(
isEncrypted
)
{
// Write the vector size.
sampleHeaderScratch
.
data
[
0
]
=
(
byte
)
ENCRYPTION_IV_SIZE
;
sampleHeaderScratch
.
setPosition
(
0
);
trackOutput
.
sampleData
(
sampleHeaderScratch
,
1
);
sampleSize
++;
}
sampleState
=
SAMPLE_STATE_DATA
;
}
while
(
blockBytesRead
<
contentSize
)
{
blockBytesRead
+=
trackOutput
.
sampleData
(
input
,
contentSize
-
blockBytesRead
);
}
trackOutput
.
sampleMetadata
(
sampleTimeUs
,
sampleFlags
,
sampleSize
,
0
,
null
);
sampleState
=
SAMPLE_STATE_START
;
sampleRead
=
true
;
return
;
default
:
throw
new
IllegalStateException
(
"Unexpected id: "
+
id
);
}
}
/**
* Builds an video {@link MediaFormat} containing recently gathered Audio information.
*
* @return The built {@link MediaFormat}.
* @throws ParserException If the codec is unsupported.
*/
private
MediaFormat
buildVideoFormat
()
throws
ParserException
{
if
(
CODEC_ID_VP9
.
equals
(
codecId
))
{
return
MediaFormat
.
createVideoFormat
(
MimeTypes
.
VIDEO_VP9
,
MediaFormat
.
NO_VALUE
,
durationUs
,
pixelWidth
,
pixelHeight
,
null
);
}
else
{
throw
new
ParserException
(
"Unable to build format"
);
}
}
/**
* Builds an audio {@link MediaFormat} containing recently gathered Audio information.
*
* @return The built {@link MediaFormat}.
* @throws ParserException If the codec is unsupported.
*/
private
MediaFormat
buildAudioFormat
()
throws
ParserException
{
if
(
CODEC_ID_VORBIS
.
equals
(
codecId
))
{
return
MediaFormat
.
createAudioFormat
(
MimeTypes
.
AUDIO_VORBIS
,
VORBIS_MAX_INPUT_SIZE
,
durationUs
,
channelCount
,
sampleRate
,
parseVorbisCodecPrivate
());
}
else
if
(
CODEC_ID_OPUS
.
equals
(
codecId
))
{
ArrayList
<
byte
[]>
opusInitializationData
=
new
ArrayList
<
byte
[]>(
3
);
opusInitializationData
.
add
(
codecPrivate
);
opusInitializationData
.
add
(
ByteBuffer
.
allocate
(
Long
.
SIZE
).
putLong
(
codecDelayNs
).
array
());
opusInitializationData
.
add
(
ByteBuffer
.
allocate
(
Long
.
SIZE
).
putLong
(
seekPreRollNs
).
array
());
return
MediaFormat
.
createAudioFormat
(
MimeTypes
.
AUDIO_OPUS
,
OPUS_MAX_INPUT_SIZE
,
durationUs
,
channelCount
,
sampleRate
,
opusInitializationData
);
}
else
{
throw
new
ParserException
(
"Unable to build format"
);
}
}
/**
* Builds a {@link ChunkIndex} containing recently gathered Cues information.
*
* @return The built {@link ChunkIndex}.
* @throws ParserException If the index could not be built.
*/
private
ChunkIndex
buildCues
()
throws
ParserException
{
if
(
segmentContentPosition
==
UNKNOWN
)
{
throw
new
ParserException
(
"Segment start/end offsets unknown"
);
}
else
if
(
durationUs
==
C
.
UNKNOWN_TIME_US
)
{
throw
new
ParserException
(
"Duration unknown"
);
}
else
if
(
cueTimesUs
==
null
||
cueClusterPositions
==
null
||
cueTimesUs
.
size
()
==
0
||
cueTimesUs
.
size
()
!=
cueClusterPositions
.
size
())
{
throw
new
ParserException
(
"Invalid/missing cue points"
);
}
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
]
=
segmentContentPosition
+
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
)
(
segmentContentPosition
+
segmentContentSize
-
offsets
[
cuePointsSize
-
1
]);
durationsUs
[
cuePointsSize
-
1
]
=
durationUs
-
timesUs
[
cuePointsSize
-
1
];
cueTimesUs
=
null
;
cueClusterPositions
=
null
;
return
new
ChunkIndex
(
sizes
,
offsets
,
durationsUs
,
timesUs
);
}
/**
* Builds initialization data for a {@link MediaFormat} from Vorbis codec private data.
*
* @return The initialization data for the {@link MediaFormat}.
* @throws ParserException If the initialization data could not be built.
*/
private
ArrayList
<
byte
[]>
parseVorbisCodecPrivate
()
throws
ParserException
{
try
{
if
(
codecPrivate
[
0
]
!=
0x02
)
{
throw
new
ParserException
(
"Error parsing vorbis codec private"
);
}
int
offset
=
1
;
int
vorbisInfoLength
=
0
;
while
(
codecPrivate
[
offset
]
==
(
byte
)
0xFF
)
{
vorbisInfoLength
+=
0xFF
;
offset
++;
}
vorbisInfoLength
+=
codecPrivate
[
offset
++];
int
vorbisSkipLength
=
0
;
while
(
codecPrivate
[
offset
]
==
(
byte
)
0xFF
)
{
vorbisSkipLength
+=
0xFF
;
offset
++;
}
vorbisSkipLength
+=
codecPrivate
[
offset
++];
if
(
codecPrivate
[
offset
]
!=
0x01
)
{
throw
new
ParserException
(
"Error parsing vorbis codec private"
);
}
byte
[]
vorbisInfo
=
new
byte
[
vorbisInfoLength
];
System
.
arraycopy
(
codecPrivate
,
offset
,
vorbisInfo
,
0
,
vorbisInfoLength
);
offset
+=
vorbisInfoLength
;
if
(
codecPrivate
[
offset
]
!=
0x03
)
{
throw
new
ParserException
(
"Error parsing vorbis codec private"
);
}
offset
+=
vorbisSkipLength
;
if
(
codecPrivate
[
offset
]
!=
0x05
)
{
throw
new
ParserException
(
"Error parsing vorbis codec private"
);
}
byte
[]
vorbisBooks
=
new
byte
[
codecPrivate
.
length
-
offset
];
System
.
arraycopy
(
codecPrivate
,
offset
,
vorbisBooks
,
0
,
codecPrivate
.
length
-
offset
);
ArrayList
<
byte
[]>
initializationData
=
new
ArrayList
<
byte
[]>(
2
);
initializationData
.
add
(
vorbisInfo
);
initializationData
.
add
(
vorbisBooks
);
return
initializationData
;
}
catch
(
ArrayIndexOutOfBoundsException
e
)
{
throw
new
ParserException
(
"Error parsing vorbis codec private"
);
}
}
private
long
scaleTimecodeToUs
(
long
unscaledTimecode
)
{
return
TimeUnit
.
NANOSECONDS
.
toMicros
(
unscaledTimecode
*
timecodeScale
);
}
private
boolean
isCodecSupported
(
String
codecId
)
{
return
CODEC_ID_VP9
.
equals
(
codecId
)
||
CODEC_ID_OPUS
.
equals
(
codecId
)
||
CODEC_ID_VORBIS
.
equals
(
codecId
);
}
/**
* Passes events through to the outer {@link WebmExtractor}.
*/
private
final
class
InnerEbmlReaderOutput
implements
EbmlReaderOutput
{
@Override
public
int
getElementType
(
int
id
)
{
return
WebmExtractor
.
this
.
getElementType
(
id
);
}
@Override
public
void
startMasterElement
(
int
id
,
long
contentPosition
,
long
contentSize
)
throws
ParserException
{
WebmExtractor
.
this
.
startMasterElement
(
id
,
contentPosition
,
contentSize
);
}
@Override
public
void
endMasterElement
(
int
id
)
throws
ParserException
{
WebmExtractor
.
this
.
endMasterElement
(
id
);
}
@Override
public
void
integerElement
(
int
id
,
long
value
)
throws
ParserException
{
WebmExtractor
.
this
.
integerElement
(
id
,
value
);
}
@Override
public
void
floatElement
(
int
id
,
double
value
)
{
WebmExtractor
.
this
.
floatElement
(
id
,
value
);
}
@Override
public
void
stringElement
(
int
id
,
String
value
)
throws
ParserException
{
WebmExtractor
.
this
.
stringElement
(
id
,
value
);
}
@Override
public
void
binaryElement
(
int
id
,
int
contentsSize
,
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
WebmExtractor
.
this
.
binaryElement
(
id
,
contentsSize
,
input
);
}
}
}
library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java
View file @
4a1fed9e
...
...
@@ -17,10 +17,12 @@ package com.google.android.exoplayer.hls;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.drm.DrmInitData
;
import
com.google.android.exoplayer.extractor.DefaultTrackOutput
;
import
com.google.android.exoplayer.extractor.Extractor
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
com.google.android.exoplayer.extractor.ExtractorOutput
;
import
com.google.android.exoplayer.extractor.SeekMap
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.util.Assertions
;
...
...
@@ -209,4 +211,14 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
this
.
tracksBuilt
=
true
;
}
@Override
public
void
seekMap
(
SeekMap
seekMap
)
{
// Do nothing.
}
@Override
public
void
drmInitData
(
DrmInitData
drmInit
)
{
// Do nothing.
}
}
library/src/test/java/com/google/android/exoplayer/extractor/webm/DefaultEbmlReaderTest.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
.
webm
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.extractor.DefaultExtractorInput
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
com.google.android.exoplayer.testutil.FakeDataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
android.net.Uri
;
import
junit.framework.TestCase
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
/**
* Tests {@link DefaultEbmlReader}.
*/
public
class
DefaultEbmlReaderTest
extends
TestCase
{
public
void
testMasterElement
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x1A
,
0x45
,
0xDF
,
0xA3
,
0x84
,
0x42
,
0x85
,
0x81
,
0x01
);
TestOutput
expected
=
new
TestOutput
();
expected
.
startMasterElement
(
TestOutput
.
ID_EBML
,
5
,
4
);
expected
.
integerElement
(
TestOutput
.
ID_DOC_TYPE_READ_VERSION
,
1
);
expected
.
endMasterElement
(
TestOutput
.
ID_EBML
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testMasterElementEmpty
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x18
,
0x53
,
0x80
,
0x67
,
0x80
);
TestOutput
expected
=
new
TestOutput
();
expected
.
startMasterElement
(
TestOutput
.
ID_SEGMENT
,
5
,
0
);
expected
.
endMasterElement
(
TestOutput
.
ID_SEGMENT
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testUnsignedIntegerElement
()
throws
IOException
,
InterruptedException
{
// 0xFE is chosen because for signed integers it should be interpreted as -2
ExtractorInput
input
=
createTestInput
(
0x42
,
0xF7
,
0x81
,
0xFE
);
TestOutput
expected
=
new
TestOutput
();
expected
.
integerElement
(
TestOutput
.
ID_EBML_READ_VERSION
,
254
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testUnsignedIntegerElementLarge
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x42
,
0xF7
,
0x88
,
0x7F
,
0xFF
,
0xFF
,
0xFF
,
0xFF
,
0xFF
,
0xFF
,
0xFF
);
TestOutput
expected
=
new
TestOutput
();
expected
.
integerElement
(
TestOutput
.
ID_EBML_READ_VERSION
,
Long
.
MAX_VALUE
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testUnsignedIntegerElementTooLargeBecomesNegative
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x42
,
0xF7
,
0x88
,
0xFF
,
0xFF
,
0xFF
,
0xFF
,
0xFF
,
0xFF
,
0xFF
,
0xFF
);
TestOutput
expected
=
new
TestOutput
();
expected
.
integerElement
(
TestOutput
.
ID_EBML_READ_VERSION
,
-
1
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testStringElement
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x42
,
0x82
,
0x86
,
0x41
,
0x62
,
0x63
,
0x31
,
0x32
,
0x33
);
TestOutput
expected
=
new
TestOutput
();
expected
.
stringElement
(
TestOutput
.
ID_DOC_TYPE
,
"Abc123"
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testStringElementEmpty
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x42
,
0x82
,
0x80
);
TestOutput
expected
=
new
TestOutput
();
expected
.
stringElement
(
TestOutput
.
ID_DOC_TYPE
,
""
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testFloatElementFourBytes
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x44
,
0x89
,
0x84
,
0x3F
,
0x80
,
0x00
,
0x00
);
TestOutput
expected
=
new
TestOutput
();
expected
.
floatElement
(
TestOutput
.
ID_DURATION
,
1.0
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testFloatElementEightBytes
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0x44
,
0x89
,
0x88
,
0xC0
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
);
TestOutput
expected
=
new
TestOutput
();
expected
.
floatElement
(
TestOutput
.
ID_DURATION
,
-
2.0
);
assertEvents
(
input
,
expected
.
events
);
}
public
void
testBinaryElement
()
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
0xA3
,
0x88
,
0x01
,
0x02
,
0x03
,
0x04
,
0x05
,
0x06
,
0x07
,
0x08
);
TestOutput
expected
=
new
TestOutput
();
expected
.
binaryElement
(
TestOutput
.
ID_SIMPLE_BLOCK
,
8
,
createTestInput
(
0x01
,
0x02
,
0x03
,
0x04
,
0x05
,
0x06
,
0x07
,
0x08
));
assertEvents
(
input
,
expected
.
events
);
}
private
static
void
assertEvents
(
ExtractorInput
input
,
List
<
String
>
expectedEvents
)
throws
IOException
,
InterruptedException
{
DefaultEbmlReader
reader
=
new
DefaultEbmlReader
();
TestOutput
output
=
new
TestOutput
();
reader
.
init
(
output
);
// We expect the number of successful reads to equal the number of expected events.
for
(
int
i
=
0
;
i
<
expectedEvents
.
size
();
i
++)
{
assertTrue
(
reader
.
read
(
input
));
}
// The next read should be unsuccessful.
assertFalse
(
reader
.
read
(
input
));
// Check that we really did get to the end of input.
assertFalse
(
input
.
readFully
(
new
byte
[
1
],
0
,
1
,
true
));
assertEquals
(
expectedEvents
.
size
(),
output
.
events
.
size
());
for
(
int
i
=
0
;
i
<
expectedEvents
.
size
();
i
++)
{
assertEquals
(
expectedEvents
.
get
(
i
),
output
.
events
.
get
(
i
));
}
}
/**
* Helper to build an {@link ExtractorInput} from byte data.
* <p>
* Each argument must be able to cast to a byte value.
*
* @param data Zero or more integers with values between {@code 0x00} and {@code 0xFF}.
* @return An {@link ExtractorInput} from which the data can be read.
* @throws IOException If an error occurs creating the input.
*/
private
static
ExtractorInput
createTestInput
(
int
...
data
)
throws
IOException
{
byte
[]
bytes
=
new
byte
[
data
.
length
];
for
(
int
i
=
0
;
i
<
data
.
length
;
i
++)
{
bytes
[
i
]
=
(
byte
)
data
[
i
];
}
DataSource
dataSource
=
new
FakeDataSource
.
Builder
().
appendReadData
(
bytes
).
build
();
dataSource
.
open
(
new
DataSpec
(
Uri
.
parse
(
"http://www.google.com"
)));
ExtractorInput
input
=
new
DefaultExtractorInput
(
dataSource
,
0
,
C
.
LENGTH_UNBOUNDED
);
return
input
;
}
/**
* An {@link EbmlReaderOutput} that records each event callback.
*/
private
static
final
class
TestOutput
implements
EbmlReaderOutput
{
// 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_DURATION
=
0x4489
;
private
static
final
int
ID_SIMPLE_BLOCK
=
0xA3
;
private
final
List
<
String
>
events
=
new
ArrayList
<
String
>();
@Override
public
int
getElementType
(
int
id
)
{
switch
(
id
)
{
case
ID_EBML:
case
ID_SEGMENT:
return
EbmlReader
.
TYPE_MASTER
;
case
ID_EBML_READ_VERSION:
case
ID_DOC_TYPE_READ_VERSION:
return
EbmlReader
.
TYPE_UNSIGNED_INT
;
case
ID_DOC_TYPE:
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
void
startMasterElement
(
int
id
,
long
contentPosition
,
long
contentSize
)
{
events
.
add
(
formatEvent
(
id
,
"start contentPosition="
+
contentPosition
+
" contentSize="
+
contentSize
));
}
@Override
public
void
endMasterElement
(
int
id
)
{
events
.
add
(
formatEvent
(
id
,
"end"
));
}
@Override
public
void
integerElement
(
int
id
,
long
value
)
{
events
.
add
(
formatEvent
(
id
,
"integer="
+
String
.
valueOf
(
value
)));
}
@Override
public
void
floatElement
(
int
id
,
double
value
)
{
events
.
add
(
formatEvent
(
id
,
"float="
+
String
.
valueOf
(
value
)));
}
@Override
public
void
stringElement
(
int
id
,
String
value
)
{
events
.
add
(
formatEvent
(
id
,
"string="
+
value
));
}
@Override
public
void
binaryElement
(
int
id
,
int
contentSize
,
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
byte
[]
bytes
=
new
byte
[
contentSize
];
input
.
readFully
(
bytes
,
0
,
contentSize
);
events
.
add
(
formatEvent
(
id
,
"bytes="
+
Arrays
.
toString
(
bytes
)));
}
private
static
String
formatEvent
(
int
id
,
String
event
)
{
return
"["
+
Integer
.
toHexString
(
id
)
+
"] "
+
event
;
}
}
}
library/src/test/java/com/google/android/exoplayer/extractor/webm/VarintReaderTest.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
.
webm
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.extractor.DefaultExtractorInput
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
com.google.android.exoplayer.testutil.FakeDataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
android.net.Uri
;
import
junit.framework.TestCase
;
import
java.io.EOFException
;
import
java.io.IOException
;
import
java.util.Arrays
;
/**
* Tests for {@link VarintReader}.
*/
public
class
VarintReaderTest
extends
TestCase
{
private
static
final
String
TEST_URI
=
"http://www.google.com"
;
private
static
final
byte
MAX_BYTE
=
(
byte
)
0xFF
;
private
static
final
byte
[]
DATA_1_BYTE_0
=
new
byte
[]
{(
byte
)
0x80
};
private
static
final
byte
[]
DATA_2_BYTE_0
=
new
byte
[]
{
0x40
,
0
};
private
static
final
byte
[]
DATA_3_BYTE_0
=
new
byte
[]
{
0x20
,
0
,
0
};
private
static
final
byte
[]
DATA_4_BYTE_0
=
new
byte
[]
{
0x10
,
0
,
0
,
0
};
private
static
final
byte
[]
DATA_5_BYTE_0
=
new
byte
[]
{
0x08
,
0
,
0
,
0
,
0
};
private
static
final
byte
[]
DATA_6_BYTE_0
=
new
byte
[]
{
0x04
,
0
,
0
,
0
,
0
,
0
};
private
static
final
byte
[]
DATA_7_BYTE_0
=
new
byte
[]
{
0x02
,
0
,
0
,
0
,
0
,
0
,
0
};
private
static
final
byte
[]
DATA_8_BYTE_0
=
new
byte
[]
{
0x01
,
0
,
0
,
0
,
0
,
0
,
0
,
0
};
private
static
final
byte
[]
DATA_1_BYTE_64
=
new
byte
[]
{(
byte
)
0xC0
};
private
static
final
byte
[]
DATA_2_BYTE_64
=
new
byte
[]
{
0x40
,
0x40
};
private
static
final
byte
[]
DATA_3_BYTE_64
=
new
byte
[]
{
0x20
,
0
,
0x40
};
private
static
final
byte
[]
DATA_4_BYTE_64
=
new
byte
[]
{
0x10
,
0
,
0
,
0x40
};
private
static
final
byte
[]
DATA_5_BYTE_64
=
new
byte
[]
{
0x08
,
0
,
0
,
0
,
0x40
};
private
static
final
byte
[]
DATA_6_BYTE_64
=
new
byte
[]
{
0x04
,
0
,
0
,
0
,
0
,
0x40
};
private
static
final
byte
[]
DATA_7_BYTE_64
=
new
byte
[]
{
0x02
,
0
,
0
,
0
,
0
,
0
,
0x40
};
private
static
final
byte
[]
DATA_8_BYTE_64
=
new
byte
[]
{
0x01
,
0
,
0
,
0
,
0
,
0
,
0
,
0x40
};
private
static
final
byte
[]
DATA_1_BYTE_MAX
=
new
byte
[]
{
MAX_BYTE
};
private
static
final
byte
[]
DATA_2_BYTE_MAX
=
new
byte
[]
{
0x7F
,
MAX_BYTE
};
private
static
final
byte
[]
DATA_3_BYTE_MAX
=
new
byte
[]
{
0x3F
,
MAX_BYTE
,
MAX_BYTE
};
private
static
final
byte
[]
DATA_4_BYTE_MAX
=
new
byte
[]
{
0x1F
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
};
private
static
final
byte
[]
DATA_5_BYTE_MAX
=
new
byte
[]
{
0x0F
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
};
private
static
final
byte
[]
DATA_6_BYTE_MAX
=
new
byte
[]
{
0x07
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
};
private
static
final
byte
[]
DATA_7_BYTE_MAX
=
new
byte
[]
{
0x03
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
};
private
static
final
byte
[]
DATA_8_BYTE_MAX
=
new
byte
[]
{
0x01
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
,
MAX_BYTE
};
private
static
final
long
VALUE_1_BYTE_MAX
=
0x7F
;
private
static
final
long
VALUE_1_BYTE_MAX_WITH_MASK
=
0xFF
;
private
static
final
long
VALUE_2_BYTE_MAX
=
0x3FFF
;
private
static
final
long
VALUE_2_BYTE_MAX_WITH_MASK
=
0x7FFF
;
private
static
final
long
VALUE_3_BYTE_MAX
=
0x1FFFFF
;
private
static
final
long
VALUE_3_BYTE_MAX_WITH_MASK
=
0x3FFFFF
;
private
static
final
long
VALUE_4_BYTE_MAX
=
0xFFFFFFF
;
private
static
final
long
VALUE_4_BYTE_MAX_WITH_MASK
=
0x1FFFFFFF
;
private
static
final
long
VALUE_5_BYTE_MAX
=
0x7FFFFFFFF
L
;
private
static
final
long
VALUE_5_BYTE_MAX_WITH_MASK
=
0xFFFFFFFFF
L
;
private
static
final
long
VALUE_6_BYTE_MAX
=
0x3FFFFFFFFFF
L
;
private
static
final
long
VALUE_6_BYTE_MAX_WITH_MASK
=
0x7FFFFFFFFFF
L
;
private
static
final
long
VALUE_7_BYTE_MAX
=
0x1FFFFFFFFFFFF
L
;
private
static
final
long
VALUE_7_BYTE_MAX_WITH_MASK
=
0x3FFFFFFFFFFFF
L
;
private
static
final
long
VALUE_8_BYTE_MAX
=
0xFFFFFFFFFFFFFF
L
;
private
static
final
long
VALUE_8_BYTE_MAX_WITH_MASK
=
0x1FFFFFFFFFFFFFF
L
;
public
void
testReadVarintEndOfInputAtStart
()
throws
IOException
,
InterruptedException
{
VarintReader
reader
=
new
VarintReader
();
// Build an input, and read to the end.
DataSource
dataSource
=
buildDataSource
(
new
byte
[
1
]);
dataSource
.
open
(
new
DataSpec
(
Uri
.
parse
(
TEST_URI
)));
ExtractorInput
input
=
new
DefaultExtractorInput
(
dataSource
,
0
,
C
.
LENGTH_UNBOUNDED
);
int
bytesRead
=
input
.
read
(
new
byte
[
1
],
0
,
1
);
assertEquals
(
1
,
bytesRead
);
// End of input allowed.
long
result
=
reader
.
readUnsignedVarint
(
input
,
true
,
false
);
assertEquals
(-
1
,
result
);
// End of input not allowed.
try
{
reader
.
readUnsignedVarint
(
input
,
false
,
false
);
fail
();
}
catch
(
EOFException
e
)
{
// Expected.
}
}
public
void
testReadVarint
()
throws
IOException
,
InterruptedException
{
VarintReader
reader
=
new
VarintReader
();
testReadVarint
(
reader
,
true
,
DATA_1_BYTE_0
,
1
,
0
);
testReadVarint
(
reader
,
true
,
DATA_2_BYTE_0
,
2
,
0
);
testReadVarint
(
reader
,
true
,
DATA_3_BYTE_0
,
3
,
0
);
testReadVarint
(
reader
,
true
,
DATA_4_BYTE_0
,
4
,
0
);
testReadVarint
(
reader
,
true
,
DATA_5_BYTE_0
,
5
,
0
);
testReadVarint
(
reader
,
true
,
DATA_6_BYTE_0
,
6
,
0
);
testReadVarint
(
reader
,
true
,
DATA_7_BYTE_0
,
7
,
0
);
testReadVarint
(
reader
,
true
,
DATA_8_BYTE_0
,
8
,
0
);
testReadVarint
(
reader
,
true
,
DATA_1_BYTE_64
,
1
,
64
);
testReadVarint
(
reader
,
true
,
DATA_2_BYTE_64
,
2
,
64
);
testReadVarint
(
reader
,
true
,
DATA_3_BYTE_64
,
3
,
64
);
testReadVarint
(
reader
,
true
,
DATA_4_BYTE_64
,
4
,
64
);
testReadVarint
(
reader
,
true
,
DATA_5_BYTE_64
,
5
,
64
);
testReadVarint
(
reader
,
true
,
DATA_6_BYTE_64
,
6
,
64
);
testReadVarint
(
reader
,
true
,
DATA_7_BYTE_64
,
7
,
64
);
testReadVarint
(
reader
,
true
,
DATA_8_BYTE_64
,
8
,
64
);
testReadVarint
(
reader
,
true
,
DATA_1_BYTE_MAX
,
1
,
VALUE_1_BYTE_MAX
);
testReadVarint
(
reader
,
true
,
DATA_2_BYTE_MAX
,
2
,
VALUE_2_BYTE_MAX
);
testReadVarint
(
reader
,
true
,
DATA_3_BYTE_MAX
,
3
,
VALUE_3_BYTE_MAX
);
testReadVarint
(
reader
,
true
,
DATA_4_BYTE_MAX
,
4
,
VALUE_4_BYTE_MAX
);
testReadVarint
(
reader
,
true
,
DATA_5_BYTE_MAX
,
5
,
VALUE_5_BYTE_MAX
);
testReadVarint
(
reader
,
true
,
DATA_6_BYTE_MAX
,
6
,
VALUE_6_BYTE_MAX
);
testReadVarint
(
reader
,
true
,
DATA_7_BYTE_MAX
,
7
,
VALUE_7_BYTE_MAX
);
testReadVarint
(
reader
,
true
,
DATA_8_BYTE_MAX
,
8
,
VALUE_8_BYTE_MAX
);
testReadVarint
(
reader
,
false
,
DATA_1_BYTE_MAX
,
1
,
VALUE_1_BYTE_MAX_WITH_MASK
);
testReadVarint
(
reader
,
false
,
DATA_2_BYTE_MAX
,
2
,
VALUE_2_BYTE_MAX_WITH_MASK
);
testReadVarint
(
reader
,
false
,
DATA_3_BYTE_MAX
,
3
,
VALUE_3_BYTE_MAX_WITH_MASK
);
testReadVarint
(
reader
,
false
,
DATA_4_BYTE_MAX
,
4
,
VALUE_4_BYTE_MAX_WITH_MASK
);
testReadVarint
(
reader
,
false
,
DATA_5_BYTE_MAX
,
5
,
VALUE_5_BYTE_MAX_WITH_MASK
);
testReadVarint
(
reader
,
false
,
DATA_6_BYTE_MAX
,
6
,
VALUE_6_BYTE_MAX_WITH_MASK
);
testReadVarint
(
reader
,
false
,
DATA_7_BYTE_MAX
,
7
,
VALUE_7_BYTE_MAX_WITH_MASK
);
testReadVarint
(
reader
,
false
,
DATA_8_BYTE_MAX
,
8
,
VALUE_8_BYTE_MAX_WITH_MASK
);
}
public
void
testReadVarintFlaky
()
throws
IOException
,
InterruptedException
{
VarintReader
reader
=
new
VarintReader
();
testReadVarintFlaky
(
reader
,
true
,
DATA_1_BYTE_0
,
1
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_2_BYTE_0
,
2
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_3_BYTE_0
,
3
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_4_BYTE_0
,
4
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_5_BYTE_0
,
5
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_6_BYTE_0
,
6
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_7_BYTE_0
,
7
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_8_BYTE_0
,
8
,
0
);
testReadVarintFlaky
(
reader
,
true
,
DATA_1_BYTE_64
,
1
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_2_BYTE_64
,
2
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_3_BYTE_64
,
3
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_4_BYTE_64
,
4
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_5_BYTE_64
,
5
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_6_BYTE_64
,
6
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_7_BYTE_64
,
7
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_8_BYTE_64
,
8
,
64
);
testReadVarintFlaky
(
reader
,
true
,
DATA_1_BYTE_MAX
,
1
,
VALUE_1_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
true
,
DATA_2_BYTE_MAX
,
2
,
VALUE_2_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
true
,
DATA_3_BYTE_MAX
,
3
,
VALUE_3_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
true
,
DATA_4_BYTE_MAX
,
4
,
VALUE_4_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
true
,
DATA_5_BYTE_MAX
,
5
,
VALUE_5_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
true
,
DATA_6_BYTE_MAX
,
6
,
VALUE_6_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
true
,
DATA_7_BYTE_MAX
,
7
,
VALUE_7_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
true
,
DATA_8_BYTE_MAX
,
8
,
VALUE_8_BYTE_MAX
);
testReadVarintFlaky
(
reader
,
false
,
DATA_1_BYTE_MAX
,
1
,
VALUE_1_BYTE_MAX_WITH_MASK
);
testReadVarintFlaky
(
reader
,
false
,
DATA_2_BYTE_MAX
,
2
,
VALUE_2_BYTE_MAX_WITH_MASK
);
testReadVarintFlaky
(
reader
,
false
,
DATA_3_BYTE_MAX
,
3
,
VALUE_3_BYTE_MAX_WITH_MASK
);
testReadVarintFlaky
(
reader
,
false
,
DATA_4_BYTE_MAX
,
4
,
VALUE_4_BYTE_MAX_WITH_MASK
);
testReadVarintFlaky
(
reader
,
false
,
DATA_5_BYTE_MAX
,
5
,
VALUE_5_BYTE_MAX_WITH_MASK
);
testReadVarintFlaky
(
reader
,
false
,
DATA_6_BYTE_MAX
,
6
,
VALUE_6_BYTE_MAX_WITH_MASK
);
testReadVarintFlaky
(
reader
,
false
,
DATA_7_BYTE_MAX
,
7
,
VALUE_7_BYTE_MAX_WITH_MASK
);
testReadVarintFlaky
(
reader
,
false
,
DATA_8_BYTE_MAX
,
8
,
VALUE_8_BYTE_MAX_WITH_MASK
);
}
private
static
void
testReadVarint
(
VarintReader
reader
,
boolean
removeMask
,
byte
[]
data
,
int
expectedLength
,
long
expectedValue
)
throws
IOException
,
InterruptedException
{
DataSource
dataSource
=
buildDataSource
(
data
);
dataSource
.
open
(
new
DataSpec
(
Uri
.
parse
(
TEST_URI
)));
ExtractorInput
input
=
new
DefaultExtractorInput
(
dataSource
,
0
,
C
.
LENGTH_UNBOUNDED
);
long
result
=
reader
.
readUnsignedVarint
(
input
,
false
,
removeMask
);
assertEquals
(
expectedLength
,
input
.
getPosition
());
assertEquals
(
expectedValue
,
result
);
}
private
static
void
testReadVarintFlaky
(
VarintReader
reader
,
boolean
removeMask
,
byte
[]
data
,
int
expectedLength
,
long
expectedValue
)
throws
IOException
,
InterruptedException
{
DataSource
dataSource
=
buildFlakyDataSource
(
data
);
ExtractorInput
input
=
null
;
long
position
=
0
;
long
result
=
-
1
;
while
(
result
==
-
1
)
{
dataSource
.
open
(
new
DataSpec
(
Uri
.
parse
(
TEST_URI
),
position
,
C
.
LENGTH_UNBOUNDED
,
null
));
input
=
new
DefaultExtractorInput
(
dataSource
,
position
,
C
.
LENGTH_UNBOUNDED
);
try
{
result
=
reader
.
readUnsignedVarint
(
input
,
false
,
removeMask
);
position
=
input
.
getPosition
();
}
catch
(
IOException
e
)
{
// Expected. We'll try again from the position that the input was advanced to.
position
=
input
.
getPosition
();
dataSource
.
close
();
}
}
assertEquals
(
expectedLength
,
input
.
getPosition
());
assertEquals
(
expectedValue
,
result
);
}
private
static
DataSource
buildDataSource
(
byte
[]
data
)
{
FakeDataSource
.
Builder
builder
=
new
FakeDataSource
.
Builder
();
builder
.
appendReadData
(
data
);
return
builder
.
build
();
}
private
static
DataSource
buildFlakyDataSource
(
byte
[]
data
)
{
FakeDataSource
.
Builder
builder
=
new
FakeDataSource
.
Builder
();
builder
.
appendReadError
(
new
IOException
(
"A"
));
builder
.
appendReadData
(
new
byte
[]
{
data
[
0
]});
if
(
data
.
length
>
1
)
{
builder
.
appendReadError
(
new
IOException
(
"B"
));
builder
.
appendReadData
(
new
byte
[]
{
data
[
1
]});
}
if
(
data
.
length
>
2
)
{
builder
.
appendReadError
(
new
IOException
(
"C"
));
builder
.
appendReadData
(
Arrays
.
copyOfRange
(
data
,
2
,
data
.
length
));
}
return
builder
.
build
();
}
}
library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
extractor
.
webm
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.ParserException
;
import
com.google.android.exoplayer.drm.DrmInitData
;
import
com.google.android.exoplayer.extractor.ChunkIndex
;
import
com.google.android.exoplayer.extractor.DefaultExtractorInput
;
import
com.google.android.exoplayer.extractor.Extractor
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
com.google.android.exoplayer.extractor.ExtractorOutput
;
import
com.google.android.exoplayer.extractor.SeekMap
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.testutil.FakeDataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
android.net.Uri
;
import
android.test.InstrumentationTestCase
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.util.Arrays
;
import
java.util.UUID
;
/**
* Tests for {@link WebmExtractor}.
*/
public
class
WebmExtractorTest
extends
InstrumentationTestCase
{
private
static
final
int
CUE_POINT_ELEMENT_BYTE_SIZE
=
31
;
private
static
final
int
DEFAULT_TIMECODE_SCALE
=
1000000
;
private
static
final
long
TEST_DURATION_US
=
9920000L
;
private
static
final
int
TEST_WIDTH
=
1280
;
private
static
final
int
TEST_HEIGHT
=
720
;
private
static
final
int
TEST_CHANNEL_COUNT
=
1
;
private
static
final
int
TEST_SAMPLE_RATE
=
48000
;
private
static
final
long
TEST_CODEC_DELAY
=
6500000
;
private
static
final
long
TEST_SEEK_PRE_ROLL
=
80000000
;
private
static
final
int
TEST_OPUS_CODEC_PRIVATE_SIZE
=
2
;
private
static
final
String
TEST_VORBIS_CODEC_PRIVATE
=
"webm/vorbis_codec_private"
;
private
static
final
int
TEST_VORBIS_INFO_SIZE
=
30
;
private
static
final
int
TEST_VORBIS_BOOKS_SIZE
=
4140
;
private
static
final
byte
[]
TEST_ENCRYPTION_KEY_ID
=
{
0x00
,
0x01
,
0x02
,
0x03
};
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
byte
[]
TEST_INITIALIZATION_VECTOR
=
{
0x00
,
0x01
,
0x02
,
0x03
,
0x04
,
0x05
,
0x06
,
0x07
};
private
static
final
int
ID_VP9
=
0
;
private
static
final
int
ID_OPUS
=
1
;
private
static
final
int
ID_VORBIS
=
2
;
private
WebmExtractor
extractor
;
private
TestOutput
output
;
@Override
public
void
setUp
()
{
extractor
=
new
WebmExtractor
();
output
=
new
TestOutput
();
extractor
.
init
(
output
);
}
@Override
public
void
tearDown
()
{
extractor
=
null
;
output
=
null
;
}
public
void
testReadInitializationSegment
()
throws
IOException
,
InterruptedException
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
null
));
assertFormat
();
assertIndex
(
new
IndexPoint
(
0
,
0
,
TEST_DURATION_US
));
}
public
void
testPrepareOpus
()
throws
IOException
,
InterruptedException
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_OPUS
,
null
));
assertAudioFormat
(
ID_OPUS
);
assertIndex
(
new
IndexPoint
(
0
,
0
,
TEST_DURATION_US
));
}
public
void
testPrepareVorbis
()
throws
IOException
,
InterruptedException
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VORBIS
,
null
));
assertAudioFormat
(
ID_VORBIS
);
assertIndex
(
new
IndexPoint
(
0
,
0
,
TEST_DURATION_US
));
}
public
void
testPrepareContentEncodingEncryption
()
throws
IOException
,
InterruptedException
{
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
0
,
1
,
1
,
5
,
1
);
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
));
assertFormat
();
assertIndex
(
new
IndexPoint
(
0
,
0
,
TEST_DURATION_US
));
DrmInitData
drmInitData
=
output
.
drmInitData
;
assertNotNull
(
output
.
drmInitData
);
android
.
test
.
MoreAsserts
.
assertEquals
(
TEST_ENCRYPTION_KEY_ID
,
drmInitData
.
get
(
WIDEVINE_UUID
));
android
.
test
.
MoreAsserts
.
assertEquals
(
TEST_ENCRYPTION_KEY_ID
,
drmInitData
.
get
(
ZERO_UUID
));
}
public
void
testPrepareThreeCuePoints
()
throws
IOException
,
InterruptedException
{
consume
(
createInitializationSegment
(
3
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
null
));
assertFormat
();
assertIndex
(
new
IndexPoint
(
0
,
0
,
10000
),
new
IndexPoint
(
10000
,
0
,
10000
),
new
IndexPoint
(
20000
,
0
,
TEST_DURATION_US
-
20000
));
}
public
void
testPrepareCustomTimecodeScale
()
throws
IOException
,
InterruptedException
{
consume
(
createInitializationSegment
(
3
,
0
,
true
,
1000
,
ID_VP9
,
null
));
assertFormat
();
assertIndex
(
new
IndexPoint
(
0
,
0
,
10
),
new
IndexPoint
(
10
,
0
,
10
),
new
IndexPoint
(
20
,
0
,
(
TEST_DURATION_US
/
1000
)
-
20
));
}
public
void
testPrepareNoCuePoints
()
throws
IOException
,
InterruptedException
{
try
{
consume
(
createInitializationSegment
(
0
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
null
));
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"Invalid/missing cue points"
,
exception
.
getMessage
());
}
}
public
void
testPrepareInvalidDocType
()
throws
IOException
,
InterruptedException
{
try
{
consume
(
createInitializationSegment
(
1
,
0
,
false
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
null
));
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"DocType webB not supported"
,
exception
.
getMessage
());
}
}
public
void
testPrepareInvalidContentEncodingOrder
()
throws
IOException
,
InterruptedException
{
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
1
,
1
,
1
,
5
,
1
);
try
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
));
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"ContentEncodingOrder 1 not supported"
,
exception
.
getMessage
());
}
}
public
void
testPrepareInvalidContentEncodingScope
()
throws
IOException
,
InterruptedException
{
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
0
,
0
,
1
,
5
,
1
);
try
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
));
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"ContentEncodingScope 0 not supported"
,
exception
.
getMessage
());
}
}
public
void
testPrepareInvalidContentEncodingType
()
throws
IOException
,
InterruptedException
{
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
0
,
1
,
0
,
5
,
1
);
try
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
));
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"ContentEncodingType 0 not supported"
,
exception
.
getMessage
());
}
}
public
void
testPrepareInvalidContentEncAlgo
()
throws
IOException
,
InterruptedException
{
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
0
,
1
,
1
,
4
,
1
);
try
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
));
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"ContentEncAlgo 4 not supported"
,
exception
.
getMessage
());
}
}
public
void
testPrepareInvalidAESSettingsCipherMode
()
throws
IOException
,
InterruptedException
{
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
0
,
1
,
1
,
5
,
0
);
try
{
consume
(
createInitializationSegment
(
1
,
0
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
));
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"AESSettingsCipherMode 0 not supported"
,
exception
.
getMessage
());
}
}
public
void
testReadSampleKeyframe
()
throws
IOException
,
InterruptedException
{
MediaSegment
mediaSegment
=
createMediaSegment
(
100
,
0
,
0
,
true
,
false
,
true
,
false
,
false
);
byte
[]
testInputData
=
joinByteArrays
(
createInitializationSegment
(
1
,
mediaSegment
.
clusterBytes
.
length
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
null
),
mediaSegment
.
clusterBytes
);
consume
(
testInputData
);
assertFormat
();
assertSample
(
mediaSegment
,
0
,
true
,
false
,
false
);
}
public
void
testReadBlock
()
throws
IOException
,
InterruptedException
{
MediaSegment
mediaSegment
=
createMediaSegment
(
100
,
0
,
0
,
true
,
false
,
false
,
false
,
false
);
byte
[]
testInputData
=
joinByteArrays
(
createInitializationSegment
(
1
,
mediaSegment
.
clusterBytes
.
length
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_OPUS
,
null
),
mediaSegment
.
clusterBytes
);
consume
(
testInputData
);
assertAudioFormat
(
ID_OPUS
);
assertSample
(
mediaSegment
,
0
,
true
,
false
,
false
);
}
public
void
testReadEncryptedFrame
()
throws
IOException
,
InterruptedException
{
MediaSegment
mediaSegment
=
createMediaSegment
(
100
,
0
,
0
,
true
,
false
,
true
,
true
,
true
);
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
0
,
1
,
1
,
5
,
1
);
byte
[]
testInputData
=
joinByteArrays
(
createInitializationSegment
(
1
,
mediaSegment
.
clusterBytes
.
length
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
),
mediaSegment
.
clusterBytes
);
consume
(
testInputData
);
assertFormat
();
assertSample
(
mediaSegment
,
0
,
true
,
false
,
true
);
}
public
void
testReadEncryptedFrameWithInvalidSignalByte
()
throws
IOException
,
InterruptedException
{
MediaSegment
mediaSegment
=
createMediaSegment
(
100
,
0
,
0
,
true
,
false
,
true
,
true
,
false
);
ContentEncodingSettings
settings
=
new
ContentEncodingSettings
(
0
,
1
,
1
,
5
,
1
);
byte
[]
testInputData
=
joinByteArrays
(
createInitializationSegment
(
1
,
mediaSegment
.
clusterBytes
.
length
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
settings
),
mediaSegment
.
clusterBytes
);
try
{
consume
(
testInputData
);
fail
();
}
catch
(
ParserException
exception
)
{
assertEquals
(
"Extension bit is set in signal byte"
,
exception
.
getMessage
());
}
}
public
void
testReadSampleInvisible
()
throws
IOException
,
InterruptedException
{
MediaSegment
mediaSegment
=
createMediaSegment
(
100
,
12
,
13
,
false
,
true
,
true
,
false
,
false
);
byte
[]
testInputData
=
joinByteArrays
(
createInitializationSegment
(
1
,
mediaSegment
.
clusterBytes
.
length
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
null
),
mediaSegment
.
clusterBytes
);
consume
(
testInputData
);
assertFormat
();
assertSample
(
mediaSegment
,
25000
,
false
,
true
,
false
);
}
public
void
testReadSampleCustomTimescale
()
throws
IOException
,
InterruptedException
{
MediaSegment
mediaSegment
=
createMediaSegment
(
100
,
12
,
13
,
false
,
false
,
true
,
false
,
false
);
byte
[]
testInputData
=
joinByteArrays
(
createInitializationSegment
(
1
,
mediaSegment
.
clusterBytes
.
length
,
true
,
1000
,
ID_VP9
,
null
),
mediaSegment
.
clusterBytes
);
consume
(
testInputData
);
assertFormat
();
assertSample
(
mediaSegment
,
25
,
false
,
false
,
false
);
}
public
void
testReadSampleNegativeSimpleBlockTimecode
()
throws
IOException
,
InterruptedException
{
MediaSegment
mediaSegment
=
createMediaSegment
(
100
,
13
,
-
12
,
true
,
true
,
true
,
false
,
false
);
byte
[]
testInputData
=
joinByteArrays
(
createInitializationSegment
(
1
,
mediaSegment
.
clusterBytes
.
length
,
true
,
DEFAULT_TIMECODE_SCALE
,
ID_VP9
,
null
),
mediaSegment
.
clusterBytes
);
consume
(
testInputData
);
assertFormat
();
assertSample
(
mediaSegment
,
1000
,
true
,
true
,
false
);
}
private
void
consume
(
byte
[]
data
)
throws
IOException
,
InterruptedException
{
ExtractorInput
input
=
createTestInput
(
data
);
int
readResult
=
Extractor
.
RESULT_CONTINUE
;
while
(
readResult
==
Extractor
.
RESULT_CONTINUE
)
{
readResult
=
extractor
.
read
(
input
);
}
assertEquals
(
Extractor
.
RESULT_END_OF_INPUT
,
readResult
);
}
private
static
ExtractorInput
createTestInput
(
byte
[]
data
)
throws
IOException
{
DataSource
dataSource
=
new
FakeDataSource
.
Builder
().
appendReadData
(
data
).
build
();
dataSource
.
open
(
new
DataSpec
(
Uri
.
parse
(
"http://www.google.com"
)));
ExtractorInput
input
=
new
DefaultExtractorInput
(
dataSource
,
0
,
C
.
LENGTH_UNBOUNDED
);
return
input
;
}
private
void
assertFormat
()
{
MediaFormat
format
=
output
.
format
;
assertEquals
(
TEST_WIDTH
,
format
.
width
);
assertEquals
(
TEST_HEIGHT
,
format
.
height
);
assertEquals
(
MimeTypes
.
VIDEO_VP9
,
format
.
mimeType
);
}
private
void
assertAudioFormat
(
int
codecId
)
{
MediaFormat
format
=
output
.
format
;
assertEquals
(
TEST_CHANNEL_COUNT
,
format
.
channelCount
);
assertEquals
(
TEST_SAMPLE_RATE
,
format
.
sampleRate
);
if
(
codecId
==
ID_OPUS
)
{
assertEquals
(
MimeTypes
.
AUDIO_OPUS
,
format
.
mimeType
);
assertEquals
(
3
,
format
.
initializationData
.
size
());
assertEquals
(
TEST_OPUS_CODEC_PRIVATE_SIZE
,
format
.
initializationData
.
get
(
0
).
length
);
assertEquals
(
TEST_CODEC_DELAY
,
ByteBuffer
.
wrap
(
format
.
initializationData
.
get
(
1
)).
getLong
());
assertEquals
(
TEST_SEEK_PRE_ROLL
,
ByteBuffer
.
wrap
(
format
.
initializationData
.
get
(
2
)).
getLong
());
}
else
if
(
codecId
==
ID_VORBIS
)
{
assertEquals
(
MimeTypes
.
AUDIO_VORBIS
,
format
.
mimeType
);
assertEquals
(
2
,
format
.
initializationData
.
size
());
assertEquals
(
TEST_VORBIS_INFO_SIZE
,
format
.
initializationData
.
get
(
0
).
length
);
assertEquals
(
TEST_VORBIS_BOOKS_SIZE
,
format
.
initializationData
.
get
(
1
).
length
);
}
}
private
void
assertIndex
(
IndexPoint
...
indexPoints
)
{
ChunkIndex
index
=
(
ChunkIndex
)
output
.
seekMap
;
assertEquals
(
indexPoints
.
length
,
index
.
length
);
for
(
int
i
=
0
;
i
<
indexPoints
.
length
;
i
++)
{
IndexPoint
indexPoint
=
indexPoints
[
i
];
assertEquals
(
indexPoint
.
timeUs
,
index
.
timesUs
[
i
]);
assertEquals
(
indexPoint
.
size
,
index
.
sizes
[
i
]);
assertEquals
(
indexPoint
.
durationUs
,
index
.
durationsUs
[
i
]);
}
}
private
void
assertSample
(
MediaSegment
mediaSegment
,
int
timeUs
,
boolean
keyframe
,
boolean
invisible
,
boolean
encrypted
)
{
byte
[]
expectedOutput
=
mediaSegment
.
videoBytes
;
if
(
encrypted
)
{
expectedOutput
=
joinByteArrays
(
new
byte
[]
{(
byte
)
TEST_INITIALIZATION_VECTOR
.
length
},
TEST_INITIALIZATION_VECTOR
,
expectedOutput
);
}
assertTrue
(
Arrays
.
equals
(
expectedOutput
,
output
.
sampleData
));
assertEquals
(
timeUs
,
output
.
sampleTimeUs
);
assertEquals
(
keyframe
,
(
output
.
sampleFlags
&
C
.
SAMPLE_FLAG_SYNC
)
!=
0
);
assertEquals
(
invisible
,
(
output
.
sampleFlags
&
C
.
SAMPLE_FLAG_DECODE_ONLY
)
!=
0
);
assertEquals
(
encrypted
,
(
output
.
sampleFlags
&
C
.
SAMPLE_FLAG_ENCRYPTED
)
!=
0
);
}
private
byte
[]
createInitializationSegment
(
int
cuePoints
,
int
mediaSegmentSize
,
boolean
docTypeIsWebm
,
int
timecodeScale
,
int
codecId
,
ContentEncodingSettings
contentEncodingSettings
)
{
byte
[]
tracksElement
=
null
;
switch
(
codecId
)
{
case
ID_VP9:
tracksElement
=
createTracksElementWithVideo
(
true
,
TEST_WIDTH
,
TEST_HEIGHT
,
contentEncodingSettings
);
break
;
case
ID_OPUS:
tracksElement
=
createTracksElementWithOpusAudio
(
TEST_CHANNEL_COUNT
);
break
;
case
ID_VORBIS:
tracksElement
=
createTracksElementWithVorbisAudio
(
TEST_CHANNEL_COUNT
);
break
;
}
byte
[]
infoElement
=
createInfoElement
(
timecodeScale
);
byte
[]
cuesElement
=
createCuesElement
(
CUE_POINT_ELEMENT_BYTE_SIZE
*
cuePoints
);
int
initalizationSegmentSize
=
infoElement
.
length
+
tracksElement
.
length
+
cuesElement
.
length
+
CUE_POINT_ELEMENT_BYTE_SIZE
*
cuePoints
;
byte
[]
segmentElement
=
createSegmentElement
(
initalizationSegmentSize
+
mediaSegmentSize
);
byte
[]
bytes
=
joinByteArrays
(
createEbmlElement
(
1
,
docTypeIsWebm
,
2
),
segmentElement
,
infoElement
,
tracksElement
,
cuesElement
);
for
(
int
i
=
0
;
i
<
cuePoints
;
i
++)
{
bytes
=
joinByteArrays
(
bytes
,
createCuePointElement
(
10
*
i
,
initalizationSegmentSize
));
}
return
bytes
;
}
private
static
MediaSegment
createMediaSegment
(
int
videoBytesLength
,
int
clusterTimecode
,
int
blockTimecode
,
boolean
keyframe
,
boolean
invisible
,
boolean
simple
,
boolean
encrypted
,
boolean
validSignalByte
)
{
byte
[]
videoBytes
=
createVideoBytes
(
videoBytesLength
);
byte
[]
blockBytes
;
if
(
simple
)
{
blockBytes
=
createSimpleBlockElement
(
videoBytes
.
length
,
blockTimecode
,
keyframe
,
invisible
,
true
,
encrypted
,
validSignalByte
);
}
else
{
blockBytes
=
createBlockElement
(
videoBytes
.
length
,
blockTimecode
,
invisible
,
true
);
}
byte
[]
clusterBytes
=
createClusterElement
(
blockBytes
.
length
+
videoBytes
.
length
,
clusterTimecode
);
return
new
MediaSegment
(
joinByteArrays
(
clusterBytes
,
blockBytes
,
videoBytes
),
videoBytes
);
}
private
static
byte
[]
joinByteArrays
(
byte
[]...
byteArrays
)
{
int
length
=
0
;
for
(
byte
[]
byteArray
:
byteArrays
)
{
length
+=
byteArray
.
length
;
}
byte
[]
joined
=
new
byte
[
length
];
length
=
0
;
for
(
byte
[]
byteArray
:
byteArrays
)
{
System
.
arraycopy
(
byteArray
,
0
,
joined
,
length
,
byteArray
.
length
);
length
+=
byteArray
.
length
;
}
return
joined
;
}
private
static
byte
[]
createEbmlElement
(
int
ebmlReadVersion
,
boolean
docTypeIsWebm
,
int
docTypeReadVersion
)
{
return
createByteArray
(
0x1A
,
0x45
,
0xDF
,
0xA3
,
// EBML
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x0F
,
// size=15
0x42
,
0xF7
,
// EBMLReadVersion
0x81
,
ebmlReadVersion
,
// size=1
0x42
,
0x82
,
// DocType
0x84
,
0x77
,
0x65
,
0x62
,
docTypeIsWebm
?
0x6D
:
0x42
,
// size=4 value=webm/B
0x42
,
0x85
,
// DocTypeReadVersion
0x81
,
docTypeReadVersion
);
// size=1
}
private
static
byte
[]
createSegmentElement
(
int
size
)
{
byte
[]
sizeBytes
=
getIntegerBytes
(
size
);
return
createByteArray
(
0x18
,
0x53
,
0x80
,
0x67
,
// Segment
0x01
,
0x00
,
0x00
,
0x00
,
sizeBytes
[
0
],
sizeBytes
[
1
],
sizeBytes
[
2
],
sizeBytes
[
3
]);
}
private
static
byte
[]
createInfoElement
(
int
timecodeScale
)
{
byte
[]
scaleBytes
=
getIntegerBytes
(
timecodeScale
);
return
createByteArray
(
0x15
,
0x49
,
0xA9
,
0x66
,
// Info
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x13
,
// size=19
0x2A
,
0xD7
,
0xB1
,
// TimecodeScale
0x84
,
scaleBytes
[
0
],
scaleBytes
[
1
],
scaleBytes
[
2
],
scaleBytes
[
3
],
// size=4
0x44
,
0x89
,
// Duration
0x88
,
0x40
,
0xC3
,
0x60
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
);
// size=8 value=9920.0
}
private
static
byte
[]
createTracksElementWithVideo
(
boolean
codecIsVp9
,
int
pixelWidth
,
int
pixelHeight
,
ContentEncodingSettings
contentEncodingSettings
)
{
byte
[]
widthBytes
=
getIntegerBytes
(
pixelWidth
);
byte
[]
heightBytes
=
getIntegerBytes
(
pixelHeight
);
if
(
contentEncodingSettings
!=
null
)
{
byte
[]
orderBytes
=
getIntegerBytes
(
contentEncodingSettings
.
order
);
byte
[]
scopeBytes
=
getIntegerBytes
(
contentEncodingSettings
.
scope
);
byte
[]
typeBytes
=
getIntegerBytes
(
contentEncodingSettings
.
type
);
byte
[]
algorithmBytes
=
getIntegerBytes
(
contentEncodingSettings
.
algorithm
);
byte
[]
cipherModeBytes
=
getIntegerBytes
(
contentEncodingSettings
.
aesCipherMode
);
return
createByteArray
(
0x16
,
0x54
,
0xAE
,
0x6B
,
// Tracks
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x48
,
// size=72
0xAE
,
// TrackEntry
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x3F
,
// size=63
0x86
,
// CodecID
0x85
,
0x56
,
0x5F
,
0x56
,
0x50
,
codecIsVp9
?
0x39
:
0x30
,
// size=5 value=V_VP9/0
0x6D
,
0x80
,
// ContentEncodings
0xA4
,
// size=36
0x62
,
0x40
,
// ContentEncoding
0xA1
,
// size=33
0x50
,
0x31
,
// ContentEncodingOrder
0x81
,
orderBytes
[
3
],
0x50
,
0x32
,
// ContentEncodingScope
0x81
,
scopeBytes
[
3
],
0x50
,
0x33
,
// ContentEncodingType
0x81
,
typeBytes
[
3
],
0x50
,
0x35
,
// ContentEncryption
0x92
,
// size=18
0x47
,
0xE1
,
// ContentEncAlgo
0x81
,
algorithmBytes
[
3
],
0x47
,
0xE2
,
// ContentEncKeyID
0x84
,
// size=4
TEST_ENCRYPTION_KEY_ID
[
0
],
TEST_ENCRYPTION_KEY_ID
[
1
],
TEST_ENCRYPTION_KEY_ID
[
2
],
TEST_ENCRYPTION_KEY_ID
[
3
],
// value=binary
0x47
,
0xE7
,
// ContentEncAESSettings
0x84
,
// size=4
0x47
,
0xE8
,
// AESSettingsCipherMode
0x81
,
cipherModeBytes
[
3
],
0xE0
,
// Video
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x08
,
// size=8
0xB0
,
// PixelWidth
0x82
,
widthBytes
[
2
],
widthBytes
[
3
],
// size=2
0xBA
,
// PixelHeight
0x82
,
heightBytes
[
2
],
heightBytes
[
3
]);
// size=2
}
else
{
return
createByteArray
(
0x16
,
0x54
,
0xAE
,
0x6B
,
// Tracks
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x24
,
// size=36
0xAE
,
// TrackEntry
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x1B
,
// size=27
0x86
,
// CodecID
0x85
,
0x56
,
0x5F
,
0x56
,
0x50
,
codecIsVp9
?
0x39
:
0x30
,
// size=5 value=V_VP9/0
0xE0
,
// Video
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x08
,
// size=8
0xB0
,
// PixelWidth
0x82
,
widthBytes
[
2
],
widthBytes
[
3
],
// size=2
0xBA
,
// PixelHeight
0x82
,
heightBytes
[
2
],
heightBytes
[
3
]);
// size=2
}
}
private
static
byte
[]
createTracksElementWithOpusAudio
(
int
channelCount
)
{
byte
[]
channelCountBytes
=
getIntegerBytes
(
channelCount
);
return
createByteArray
(
0x16
,
0x54
,
0xAE
,
0x6B
,
// Tracks
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x39
,
// size=57
0xAE
,
// TrackEntry
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x30
,
// size=48
0x86
,
// CodecID
0x86
,
0x41
,
0x5F
,
0x4F
,
0x50
,
0x55
,
0x53
,
// size=6 value=A_OPUS
0x56
,
0xAA
,
// CodecDelay
0x83
,
0x63
,
0x2E
,
0xA0
,
// size=3 value=6500000
0x56
,
0xBB
,
// SeekPreRoll
0x84
,
0x04
,
0xC4
,
0xB4
,
0x00
,
// size=4 value=80000000
0xE1
,
// Audio
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x0D
,
// size=13
0x9F
,
// Channels
0x81
,
channelCountBytes
[
3
],
// size=1
0xB5
,
// SamplingFrequency
0x88
,
0x40
,
0xE7
,
0x70
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
// size=8 value=48000
0x63
,
0xA2
,
// CodecPrivate
0x82
,
0x00
,
0x00
);
// size=2
}
private
byte
[]
createTracksElementWithVorbisAudio
(
int
channelCount
)
{
byte
[]
channelCountBytes
=
getIntegerBytes
(
channelCount
);
byte
[]
tracksElement
=
createByteArray
(
0x16
,
0x54
,
0xAE
,
0x6B
,
// Tracks
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x10
,
0x9C
,
// size=4252
0xAE
,
// TrackEntry
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x10
,
0x93
,
// size=4243 (36+4207)
0x86
,
// CodecID
0x88
,
0x41
,
0x5f
,
0x56
,
0x4f
,
0x52
,
0x42
,
0x49
,
0x53
,
// size=8 value=A_VORBIS
0xE1
,
// Audio
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x0D
,
// size=13
0x9F
,
// Channels
0x81
,
channelCountBytes
[
3
],
// size=1
0xB5
,
// SamplingFrequency
0x88
,
0x40
,
0xE7
,
0x70
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
// size=8 value=48000
0x63
,
0xA2
,
// CodecPrivate
0x50
,
0x6F
);
// size=4207
byte
[]
codecPrivate
=
new
byte
[
4207
];
try
{
getInstrumentation
().
getContext
().
getResources
().
getAssets
().
open
(
TEST_VORBIS_CODEC_PRIVATE
)
.
read
(
codecPrivate
);
}
catch
(
IOException
e
)
{
fail
();
// should never happen
}
return
joinByteArrays
(
tracksElement
,
codecPrivate
);
}
private
static
byte
[]
createCuesElement
(
int
size
)
{
byte
[]
sizeBytes
=
getIntegerBytes
(
size
);
return
createByteArray
(
0x1C
,
0x53
,
0xBB
,
0x6B
,
// Cues
0x01
,
0x00
,
0x00
,
0x00
,
sizeBytes
[
0
],
sizeBytes
[
1
],
sizeBytes
[
2
],
sizeBytes
[
3
]);
// size=31
}
private
static
byte
[]
createCuePointElement
(
int
cueTime
,
int
cueClusterPosition
)
{
byte
[]
positionBytes
=
getIntegerBytes
(
cueClusterPosition
);
return
createByteArray
(
0xBB
,
// CuePoint
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x16
,
// size=22
0xB3
,
// CueTime
0x81
,
cueTime
,
// size=1
0xB7
,
// CueTrackPositions
0x01
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x0A
,
// size=10
0xF1
,
// CueClusterPosition
0x88
,
0x00
,
0x00
,
0x00
,
0x00
,
positionBytes
[
0
],
positionBytes
[
1
],
positionBytes
[
2
],
positionBytes
[
3
]);
// size=8
}
private
static
byte
[]
createClusterElement
(
int
size
,
int
timecode
)
{
byte
[]
sizeBytes
=
getIntegerBytes
(
size
);
byte
[]
timeBytes
=
getIntegerBytes
(
timecode
);
return
createByteArray
(
0x1F
,
0x43
,
0xB6
,
0x75
,
// Cluster
0x01
,
0x00
,
0x00
,
0x00
,
sizeBytes
[
0
],
sizeBytes
[
1
],
sizeBytes
[
2
],
sizeBytes
[
3
],
0xE7
,
// Timecode
0x84
,
timeBytes
[
0
],
timeBytes
[
1
],
timeBytes
[
2
],
timeBytes
[
3
]);
// size=4
}
private
static
byte
[]
createSimpleBlockElement
(
int
size
,
int
timecode
,
boolean
keyframe
,
boolean
invisible
,
boolean
noLacing
,
boolean
encrypted
,
boolean
validSignalByte
)
{
byte
[]
sizeBytes
=
getIntegerBytes
(
size
+
4
+
(
encrypted
?
9
:
0
));
byte
[]
timeBytes
=
getIntegerBytes
(
timecode
);
byte
flags
=
(
byte
)
((
keyframe
?
0x80
:
0x00
)
|
(
invisible
?
0x08
:
0x00
)
|
(
noLacing
?
0x00
:
0x06
));
byte
[]
simpleBlock
=
createByteArray
(
0xA3
,
// SimpleBlock
0x01
,
0x00
,
0x00
,
0x00
,
sizeBytes
[
0
],
sizeBytes
[
1
],
sizeBytes
[
2
],
sizeBytes
[
3
],
0x81
,
// Track number value=1
timeBytes
[
2
],
timeBytes
[
3
],
flags
);
// Timecode and flags
if
(
encrypted
)
{
simpleBlock
=
joinByteArrays
(
simpleBlock
,
createByteArray
(
validSignalByte
?
0x01
:
0x80
),
Arrays
.
copyOfRange
(
TEST_INITIALIZATION_VECTOR
,
0
,
8
));
}
return
simpleBlock
;
}
private
static
byte
[]
createBlockElement
(
int
size
,
int
timecode
,
boolean
invisible
,
boolean
noLacing
)
{
int
blockSize
=
size
+
4
;
byte
[]
blockSizeBytes
=
getIntegerBytes
(
blockSize
);
byte
[]
timeBytes
=
getIntegerBytes
(
timecode
);
int
blockElementSize
=
1
+
8
+
blockSize
;
// id + size + length of data
byte
[]
sizeBytes
=
getIntegerBytes
(
blockElementSize
);
byte
flags
=
(
byte
)
((
invisible
?
0x08
:
0x00
)
|
(
noLacing
?
0x00
:
0x06
));
return
createByteArray
(
0xA0
,
// BlockGroup
0x01
,
0x00
,
0x00
,
0x00
,
sizeBytes
[
0
],
sizeBytes
[
1
],
sizeBytes
[
2
],
sizeBytes
[
3
],
0xA1
,
// Block
0x01
,
0x00
,
0x00
,
0x00
,
blockSizeBytes
[
0
],
blockSizeBytes
[
1
],
blockSizeBytes
[
2
],
blockSizeBytes
[
3
],
0x81
,
// Track number value=1
timeBytes
[
2
],
timeBytes
[
3
],
flags
);
// Timecode and flags
}
private
static
byte
[]
createVideoBytes
(
int
size
)
{
byte
[]
videoBytes
=
new
byte
[
size
];
for
(
int
i
=
0
;
i
<
size
;
i
++)
{
videoBytes
[
i
]
=
(
byte
)
i
;
}
return
videoBytes
;
}
private
static
byte
[]
getIntegerBytes
(
int
value
)
{
return
createByteArray
(
(
value
&
0xFF000000
)
>>
24
,
(
value
&
0x00FF0000
)
>>
16
,
(
value
&
0x0000FF00
)
>>
8
,
(
value
&
0x000000FF
));
}
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
;
}
/** Used by {@link #createMediaSegment} to return both cluster and video bytes together. */
private
static
final
class
MediaSegment
{
private
final
byte
[]
clusterBytes
;
private
final
byte
[]
videoBytes
;
private
MediaSegment
(
byte
[]
clusterBytes
,
byte
[]
videoBytes
)
{
this
.
clusterBytes
=
clusterBytes
;
this
.
videoBytes
=
videoBytes
;
}
}
/** Used by {@link #assertIndex(IndexPoint...)} to validate index elements. */
private
static
final
class
IndexPoint
{
private
final
long
timeUs
;
private
final
int
size
;
private
final
long
durationUs
;
private
IndexPoint
(
long
timeUs
,
int
size
,
long
durationUs
)
{
this
.
timeUs
=
timeUs
;
this
.
size
=
size
;
this
.
durationUs
=
durationUs
;
}
}
/** Used by {@link #createTracksElementWithVideo} to create a Track header with Encryption. */
private
static
final
class
ContentEncodingSettings
{
private
final
int
order
;
private
final
int
scope
;
private
final
int
type
;
private
final
int
algorithm
;
private
final
int
aesCipherMode
;
private
ContentEncodingSettings
(
int
order
,
int
scope
,
int
type
,
int
algorithm
,
int
aesCipherMode
)
{
this
.
order
=
order
;
this
.
scope
=
scope
;
this
.
type
=
type
;
this
.
algorithm
=
algorithm
;
this
.
aesCipherMode
=
aesCipherMode
;
}
}
/** Implements {@link ExtractorOutput} and {@link TrackOutput} for test purposes. */
public
static
class
TestOutput
implements
ExtractorOutput
,
TrackOutput
{
public
boolean
tracksEnded
;
public
SeekMap
seekMap
;
public
DrmInitData
drmInitData
;
public
MediaFormat
format
;
private
long
sampleTimeUs
;
private
int
sampleFlags
;
private
byte
[]
sampleData
;
@Override
public
TrackOutput
track
(
int
trackId
)
{
return
this
;
}
@Override
public
void
endTracks
()
{
tracksEnded
=
true
;
}
@Override
public
void
seekMap
(
SeekMap
seekMap
)
{
this
.
seekMap
=
seekMap
;
}
@Override
public
void
drmInitData
(
DrmInitData
drmInitData
)
{
this
.
drmInitData
=
drmInitData
;
}
@Override
public
void
format
(
MediaFormat
format
)
{
this
.
format
=
format
;
}
@Override
public
int
sampleData
(
ExtractorInput
input
,
int
length
)
throws
IOException
,
InterruptedException
{
byte
[]
newData
=
new
byte
[
length
];
input
.
readFully
(
newData
,
0
,
length
);
sampleData
=
sampleData
==
null
?
newData
:
joinByteArrays
(
sampleData
,
newData
);
return
length
;
}
@Override
public
void
sampleData
(
ParsableByteArray
data
,
int
length
)
{
byte
[]
newData
=
new
byte
[
length
];
data
.
readBytes
(
newData
,
0
,
length
);
sampleData
=
sampleData
==
null
?
newData
:
joinByteArrays
(
sampleData
,
newData
);
}
@Override
public
void
sampleMetadata
(
long
timeUs
,
int
flags
,
int
size
,
int
offset
,
byte
[]
encryptionKey
)
{
this
.
sampleTimeUs
=
timeUs
;
this
.
sampleFlags
=
flags
;
}
}
}
library/src/test/java/com/google/android/exoplayer/testutil/FakeDataSource.java
0 → 100644
View file @
4a1fed9e
/*
* 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
.
testutil
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.util.Assertions
;
import
java.io.IOException
;
import
java.util.ArrayList
;
/**
* A fake {@link DataSource} capable of simulating various scenarios.
* <p>
* The data that will be read from the source can be constructed by calling
* {@link Builder#appendReadData(byte[])}. Calls to {@link #read(byte[], int, int)} will not span
* the boundaries between arrays passed to successive calls, and hence the boundaries control the
* positions at which read requests to the source may only be partially satisfied.
* <p>
* Errors can be inserted by calling {@link Builder#appendReadError(IOException)}. An inserted error
* will be thrown from the first call to {@link #read(byte[], int, int)} that attempts to read from
* the corresponding position, and from all subsequent calls to {@link #read(byte[], int, int)}
* until the source is closed. If the source is closed and re-opened having encountered an error,
* that error will not be thrown again.
*/
public
final
class
FakeDataSource
implements
DataSource
{
private
final
ArrayList
<
Segment
>
segments
;
private
final
boolean
simulateUnknownLength
;
private
final
long
totalLength
;
private
boolean
opened
;
private
int
currentSegmentIndex
;
private
long
bytesRemaining
;
public
FakeDataSource
(
boolean
simulateUnknownLength
,
ArrayList
<
Segment
>
segments
)
{
this
.
simulateUnknownLength
=
simulateUnknownLength
;
this
.
segments
=
segments
;
long
totalLength
=
0
;
for
(
Segment
segment
:
segments
)
{
totalLength
+=
segment
.
length
;
}
this
.
totalLength
=
totalLength
;
}
@Override
public
long
open
(
DataSpec
dataSpec
)
throws
IOException
{
Assertions
.
checkState
(!
opened
);
// DataSpec requires a matching close call even if open fails.
opened
=
true
;
// If the source knows that the request is unsatisfiable then fail.
if
(
dataSpec
.
position
>=
totalLength
)
{
throw
new
IOException
(
"Unsatisfiable position"
);
}
else
if
(
dataSpec
.
length
!=
C
.
LENGTH_UNBOUNDED
&&
dataSpec
.
position
+
dataSpec
.
length
>=
totalLength
)
{
throw
new
IOException
(
"Unsatisfiable range"
);
}
// Scan through the segments, configuring them for the current read.
boolean
findingCurrentSegmentIndex
=
true
;
currentSegmentIndex
=
0
;
int
scannedLength
=
0
;
for
(
Segment
segment
:
segments
)
{
segment
.
bytesRead
=
(
int
)
Math
.
min
(
Math
.
max
(
0
,
dataSpec
.
position
-
scannedLength
),
segment
.
length
);
scannedLength
+=
segment
.
length
;
findingCurrentSegmentIndex
&=
segment
.
isErrorSegment
()
?
segment
.
exceptionCleared
:
segment
.
bytesRead
==
segment
.
length
;
if
(
findingCurrentSegmentIndex
)
{
currentSegmentIndex
++;
}
}
// Configure bytesRemaining, and return.
if
(
dataSpec
.
length
==
C
.
LENGTH_UNBOUNDED
)
{
bytesRemaining
=
totalLength
-
dataSpec
.
position
;
return
simulateUnknownLength
?
C
.
LENGTH_UNBOUNDED
:
bytesRemaining
;
}
else
{
bytesRemaining
=
dataSpec
.
length
;
return
bytesRemaining
;
}
}
@Override
public
void
close
()
throws
IOException
{
Assertions
.
checkState
(
opened
);
opened
=
false
;
if
(
currentSegmentIndex
<
segments
.
size
())
{
Segment
current
=
segments
.
get
(
currentSegmentIndex
);
if
(
current
.
isErrorSegment
()
&&
current
.
exceptionThrown
)
{
current
.
exceptionCleared
=
true
;
}
}
}
@Override
public
int
read
(
byte
[]
buffer
,
int
offset
,
int
readLength
)
throws
IOException
{
Assertions
.
checkState
(
opened
);
while
(
true
)
{
if
(
currentSegmentIndex
==
segments
.
size
()
||
bytesRemaining
==
0
)
{
return
-
1
;
}
Segment
current
=
segments
.
get
(
currentSegmentIndex
);
if
(
current
.
exception
!=
null
)
{
if
(!
current
.
exceptionCleared
)
{
current
.
exceptionThrown
=
true
;
throw
current
.
exception
;
}
else
{
currentSegmentIndex
++;
}
}
else
{
// Read at most bytesRemaining.
readLength
=
(
int
)
Math
.
min
(
readLength
,
bytesRemaining
);
// Do not allow crossing of the segment boundary.
readLength
=
Math
.
min
(
readLength
,
current
.
length
-
current
.
bytesRead
);
// Perform the read and return.
System
.
arraycopy
(
current
.
data
,
current
.
bytesRead
,
buffer
,
offset
,
readLength
);
bytesRemaining
-=
readLength
;
current
.
bytesRead
+=
readLength
;
if
(
current
.
bytesRead
==
current
.
length
)
{
currentSegmentIndex
++;
}
return
readLength
;
}
}
}
private
static
class
Segment
{
public
final
IOException
exception
;
public
final
byte
[]
data
;
public
final
int
length
;
private
boolean
exceptionThrown
;
private
boolean
exceptionCleared
;
private
int
bytesRead
;
public
Segment
(
byte
[]
data
,
IOException
exception
)
{
this
.
data
=
data
;
this
.
exception
=
exception
;
length
=
data
!=
null
?
data
.
length
:
0
;
}
public
boolean
isErrorSegment
()
{
return
exception
!=
null
;
}
}
/**
* Builder of {@link FakeDataSource} instances.
*/
public
static
class
Builder
{
private
final
ArrayList
<
Segment
>
segments
;
private
boolean
simulateUnknownLength
;
public
Builder
()
{
segments
=
new
ArrayList
<
Segment
>();
}
/**
* When set, {@link FakeDataSource#open(DataSpec)} will behave as though the source is unable to
* determine the length of the underlying data. Hence the return value will always be equal to
* the {@link DataSpec#length} of the argument, including the case where the length is equal to
* {@link C#LENGTH_UNBOUNDED}.
*/
public
Builder
setSimulateUnknownLength
(
boolean
simulateUnknownLength
)
{
this
.
simulateUnknownLength
=
simulateUnknownLength
;
return
this
;
}
/**
* Appends to the underlying data.
*/
public
Builder
appendReadData
(
byte
[]
data
)
{
Assertions
.
checkState
(
data
!=
null
&&
data
.
length
>
0
);
segments
.
add
(
new
Segment
(
data
,
null
));
return
this
;
}
/**
* Appends an error in the underlying data.
*/
public
Builder
appendReadError
(
IOException
exception
)
{
segments
.
add
(
new
Segment
(
null
,
exception
));
return
this
;
}
public
FakeDataSource
build
()
{
return
new
FakeDataSource
(
simulateUnknownLength
,
segments
);
}
}
}
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