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
587edf8e
authored
Apr 11, 2015
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add new style mp4/fmp4 extractors.
parent
f002e6a7
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
1950 additions
and
940 deletions
demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
demo/src/main/java/com/google/android/exoplayer/demo/player/Mp4RendererBuilder.java → demo/src/main/java/com/google/android/exoplayer/demo/player/ExtractorRendererBuilder.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.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/ExtractorSampleSource.java
library/src/main/java/com/google/android/exoplayer/extractor/PositionHolder.java
library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java
library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java
library/src/main/java/com/google/android/exoplayer/mp4/Atom.java → library/src/main/java/com/google/android/exoplayer/extractor/mp4/Atom.java
library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java → library/src/main/java/com/google/android/exoplayer/extractor/mp4/AtomParsers.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/DefaultSampleValues.java → library/src/main/java/com/google/android/exoplayer/extractor/mp4/DefaultSampleValues.java
library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java
library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java
library/src/main/java/com/google/android/exoplayer/mp4/Track.java → library/src/main/java/com/google/android/exoplayer/extractor/mp4/Track.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/TrackEncryptionBox.java → library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackEncryptionBox.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/TrackFragment.java → library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackFragment.java
library/src/main/java/com/google/android/exoplayer/mp4/Mp4TrackSampleTable.java → library/src/main/java/com/google/android/exoplayer/extractor/mp4/TrackSampleTable.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/WebmExtractor.java
library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
library/src/main/java/com/google/android/exoplayer/source/Mp4SampleExtractor.java
library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java
library/src/test/java/com/google/android/exoplayer/extractor/ExtractorTest.java
library/src/test/java/com/google/android/exoplayer/source/Mp4SampleExtractorTest.java → library/src/test/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java
library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java
demo/src/main/java/com/google/android/exoplayer/demo/DemoUtil.java
View file @
587edf8e
...
...
@@ -49,6 +49,7 @@ public class DemoUtil {
public
static
final
int
TYPE_OTHER
=
2
;
public
static
final
int
TYPE_HLS
=
3
;
public
static
final
int
TYPE_MP4
=
4
;
public
static
final
int
TYPE_MP3
=
5
;
private
static
final
CookieManager
defaultCookieManager
;
...
...
demo/src/main/java/com/google/android/exoplayer/demo/PlayerActivity.java
View file @
587edf8e
...
...
@@ -23,10 +23,12 @@ import com.google.android.exoplayer.demo.player.DashRendererBuilder;
import
com.google.android.exoplayer.demo.player.DefaultRendererBuilder
;
import
com.google.android.exoplayer.demo.player.DemoPlayer
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.player.ExtractorRendererBuilder
;
import
com.google.android.exoplayer.demo.player.HlsRendererBuilder
;
import
com.google.android.exoplayer.demo.player.Mp4RendererBuilder
;
import
com.google.android.exoplayer.demo.player.SmoothStreamingRendererBuilder
;
import
com.google.android.exoplayer.demo.player.UnsupportedDrmException
;
import
com.google.android.exoplayer.extractor.mp3.Mp3Extractor
;
import
com.google.android.exoplayer.extractor.mp4.Mp4Extractor
;
import
com.google.android.exoplayer.metadata.GeobMetadata
;
import
com.google.android.exoplayer.metadata.PrivMetadata
;
import
com.google.android.exoplayer.metadata.TxxxMetadata
;
...
...
@@ -217,7 +219,11 @@ public class PlayerActivity extends Activity implements SurfaceHolder.Callback,
case
DemoUtil
.
TYPE_HLS
:
return
new
HlsRendererBuilder
(
userAgent
,
contentUri
.
toString
());
case
DemoUtil
.
TYPE_MP4
:
return
new
Mp4RendererBuilder
(
contentUri
,
debugTextView
);
return
new
ExtractorRendererBuilder
(
userAgent
,
contentUri
,
debugTextView
,
new
Mp4Extractor
());
case
DemoUtil
.
TYPE_MP3
:
return
new
ExtractorRendererBuilder
(
userAgent
,
contentUri
,
debugTextView
,
new
Mp3Extractor
());
default
:
return
new
DefaultRendererBuilder
(
this
,
contentUri
,
debugTextView
);
}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/Samples.java
View file @
587edf8e
...
...
@@ -135,12 +135,15 @@ import java.util.Locale;
new
Sample
(
"Apple AAC 10s"
,
"https://devimages.apple.com.edgekey.net/"
+
"streaming/examples/bipbop_4x3/gear0/fileSequence0.aac"
,
DemoUtil
.
TYPE_OTHER
),
new
Sample
(
"Big Buck Bunny (MP4)"
,
new
Sample
(
"Big Buck Bunny (MP4
Video
)"
,
"http://redirector.c.youtube.com/videoplayback?id=604ed5ce52eda7ee&itag=22&source=youtube"
+
"&sparams=ip,ipbits,expire&ip=0.0.0.0&ipbits=0&expire=19000000000&signature="
+
"2E853B992F6CAB9D28CA3BEBD84A6F26709A8A55.94344B0D8BA83A7417AAD24DACC8C71A9A878ECE"
+
"&key=ik0"
,
DemoUtil
.
TYPE_MP4
),
new
Sample
(
"Google Play (MP3 Audio)"
,
"http://storage.googleapis.com/exoplayer-test-media-0/play.mp3"
,
DemoUtil
.
TYPE_MP3
),
};
private
Samples
()
{}
...
...
demo/src/main/java/com/google/android/exoplayer/demo/player/
Mp4
RendererBuilder.java
→
demo/src/main/java/com/google/android/exoplayer/demo/player/
Extractor
RendererBuilder.java
View file @
587edf8e
...
...
@@ -20,9 +20,9 @@ import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.player.DemoPlayer.RendererBuilderCallback
;
import
com.google.android.exoplayer.
source.DefaultSampleSource
;
import
com.google.android.exoplayer.
source.Mp4SampleExtractor
;
import
com.google.android.exoplayer.upstream.DataS
pec
;
import
com.google.android.exoplayer.
extractor.Extractor
;
import
com.google.android.exoplayer.
extractor.ExtractorSampleSource
;
import
com.google.android.exoplayer.upstream.DataS
ource
;
import
com.google.android.exoplayer.upstream.UriDataSource
;
import
android.media.MediaCodec
;
...
...
@@ -30,23 +30,31 @@ import android.net.Uri;
import
android.widget.TextView
;
/**
* A {@link RendererBuilder} for streams that can be read using
{@link Mp4Sample
Extractor}.
* A {@link RendererBuilder} for streams that can be read using
an {@link
Extractor}.
*/
public
class
Mp4
RendererBuilder
implements
RendererBuilder
{
public
class
Extractor
RendererBuilder
implements
RendererBuilder
{
private
static
final
int
BUFFER_SIZE
=
10
*
1024
*
1024
;
private
final
String
userAgent
;
private
final
Uri
uri
;
private
final
TextView
debugTextView
;
private
final
Extractor
extractor
;
public
Mp4RendererBuilder
(
Uri
uri
,
TextView
debugTextView
)
{
public
ExtractorRendererBuilder
(
String
userAgent
,
Uri
uri
,
TextView
debugTextView
,
Extractor
extractor
)
{
this
.
userAgent
=
userAgent
;
this
.
uri
=
uri
;
this
.
debugTextView
=
debugTextView
;
this
.
extractor
=
extractor
;
}
@Override
public
void
buildRenderers
(
DemoPlayer
player
,
RendererBuilderCallback
callback
)
{
// Build the video and audio renderers.
DefaultSampleSource
sampleSource
=
new
DefaultSampleSource
(
new
Mp4SampleExtractor
(
new
UriDataSource
(
"exoplayer"
,
null
),
new
DataSpec
(
uri
)),
2
);
DataSource
dataSource
=
new
UriDataSource
(
userAgent
,
null
);
ExtractorSampleSource
sampleSource
=
new
ExtractorSampleSource
(
uri
,
dataSource
,
extractor
,
2
,
BUFFER_SIZE
);
MediaCodecVideoTrackRenderer
videoRenderer
=
new
MediaCodecVideoTrackRenderer
(
sampleSource
,
null
,
true
,
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
5000
,
null
,
player
.
getMainHandler
(),
player
,
50
);
...
...
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
View file @
587edf8e
...
...
@@ -22,11 +22,14 @@ import com.google.android.exoplayer.SampleHolder;
import
com.google.android.exoplayer.chunk.parser.Extractor
;
import
com.google.android.exoplayer.chunk.parser.SegmentIndex
;
import
com.google.android.exoplayer.drm.DrmInitData
;
import
com.google.android.exoplayer.mp4.Atom
;
import
com.google.android.exoplayer.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.mp4.Atom.LeafAtom
;
import
com.google.android.exoplayer.mp4.CommonMp4AtomParsers
;
import
com.google.android.exoplayer.mp4.Track
;
import
com.google.android.exoplayer.extractor.mp4.Atom
;
import
com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.extractor.mp4.Atom.LeafAtom
;
import
com.google.android.exoplayer.extractor.mp4.AtomParsers
;
import
com.google.android.exoplayer.extractor.mp4.DefaultSampleValues
;
import
com.google.android.exoplayer.extractor.mp4.Track
;
import
com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox
;
import
com.google.android.exoplayer.extractor.mp4.TrackFragment
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.H264Util
;
import
com.google.android.exoplayer.util.MimeTypes
;
...
...
@@ -157,7 +160,7 @@ public final class FragmentedMp4Extractor implements Extractor {
public
FragmentedMp4Extractor
(
int
workaroundFlags
)
{
this
.
workaroundFlags
=
workaroundFlags
;
parserState
=
STATE_READING_ATOM_HEADER
;
atomHeader
=
new
ParsableByteArray
(
Atom
.
ATOM_
HEADER_SIZE
);
atomHeader
=
new
ParsableByteArray
(
Atom
.
HEADER_SIZE
);
extendedTypeScratch
=
new
byte
[
16
];
containerAtoms
=
new
Stack
<
ContainerAtom
>();
fragmentRun
=
new
TrackFragment
();
...
...
@@ -259,14 +262,14 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private
int
readAtomHeader
(
NonBlockingInputStream
inputStream
)
{
int
remainingBytes
=
Atom
.
ATOM_
HEADER_SIZE
-
atomBytesRead
;
int
remainingBytes
=
Atom
.
HEADER_SIZE
-
atomBytesRead
;
int
bytesRead
=
inputStream
.
read
(
atomHeader
.
data
,
atomBytesRead
,
remainingBytes
);
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_STREAM
;
}
rootAtomBytesRead
+=
bytesRead
;
atomBytesRead
+=
bytesRead
;
if
(
atomBytesRead
!=
Atom
.
ATOM_
HEADER_SIZE
)
{
if
(
atomBytesRead
!=
Atom
.
HEADER_SIZE
)
{
return
RESULT_NEED_MORE_DATA
;
}
...
...
@@ -288,10 +291,10 @@ public final class FragmentedMp4Extractor implements Extractor {
if
(
CONTAINER_TYPES
.
contains
(
atomTypeInteger
))
{
enterState
(
STATE_READING_ATOM_HEADER
);
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
rootAtomBytesRead
+
atomSize
-
Atom
.
ATOM_
HEADER_SIZE
));
rootAtomBytesRead
+
atomSize
-
Atom
.
HEADER_SIZE
));
}
else
{
atomData
=
new
ParsableByteArray
(
atomSize
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
Atom
.
ATOM_
HEADER_SIZE
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
Atom
.
HEADER_SIZE
);
enterState
(
STATE_READING_ATOM_PAYLOAD
);
}
}
else
{
...
...
@@ -360,7 +363,7 @@ public final class FragmentedMp4Extractor implements Extractor {
LeafAtom
child
=
moovChildren
.
get
(
i
);
if
(
child
.
type
==
Atom
.
TYPE_pssh
)
{
ParsableByteArray
psshAtom
=
child
.
data
;
psshAtom
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
);
psshAtom
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
UUID
uuid
=
new
UUID
(
psshAtom
.
readLong
(),
psshAtom
.
readLong
());
int
dataSize
=
psshAtom
.
readInt
();
byte
[]
data
=
new
byte
[
dataSize
];
...
...
@@ -373,7 +376,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
ContainerAtom
mvex
=
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_mvex
);
extendsDefaults
=
parseTrex
(
mvex
.
getLeafAtomOfType
(
Atom
.
TYPE_trex
).
data
);
track
=
CommonMp4
AtomParsers
.
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
),
track
=
AtomParsers
.
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
),
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_mvhd
));
}
...
...
@@ -399,7 +402,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a trex atom (defined in 14496-12).
*/
private
static
DefaultSampleValues
parseTrex
(
ParsableByteArray
trex
)
{
trex
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
+
4
);
trex
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
+
4
);
int
defaultSampleDescriptionIndex
=
trex
.
readUnsignedIntToInt
()
-
1
;
int
defaultSampleDuration
=
trex
.
readUnsignedIntToInt
();
int
defaultSampleSize
=
trex
.
readUnsignedIntToInt
();
...
...
@@ -453,7 +456,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private
static
void
parseSaiz
(
TrackEncryptionBox
encryptionBox
,
ParsableByteArray
saiz
,
TrackFragment
out
)
{
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
saiz
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
saiz
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
saiz
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
if
((
flags
&
0x01
)
==
1
)
{
...
...
@@ -490,7 +493,7 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private
static
DefaultSampleValues
parseTfhd
(
DefaultSampleValues
extendsDefaults
,
ParsableByteArray
tfhd
)
{
tfhd
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
tfhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
tfhd
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
...
...
@@ -519,7 +522,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* media, expressed in the media's timescale.
*/
private
static
long
parseTfdt
(
ParsableByteArray
tfdt
)
{
tfdt
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
tfdt
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
tfdt
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
return
version
==
1
?
tfdt
.
readUnsignedLongToLong
()
:
tfdt
.
readUnsignedInt
();
...
...
@@ -536,7 +539,7 @@ public final class FragmentedMp4Extractor implements Extractor {
*/
private
static
void
parseTrun
(
Track
track
,
DefaultSampleValues
defaultSampleValues
,
long
decodeTime
,
int
workaroundFlags
,
ParsableByteArray
trun
,
TrackFragment
out
)
{
trun
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
trun
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
trun
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
...
...
@@ -596,7 +599,7 @@ public final class FragmentedMp4Extractor implements Extractor {
private
static
void
parseUuid
(
ParsableByteArray
uuid
,
TrackFragment
out
,
byte
[]
extendedTypeScratch
)
{
uuid
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
uuid
.
setPosition
(
Atom
.
HEADER_SIZE
);
uuid
.
readBytes
(
extendedTypeScratch
,
0
,
16
);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
...
...
@@ -615,7 +618,7 @@ public final class FragmentedMp4Extractor implements Extractor {
}
private
static
void
parseSenc
(
ParsableByteArray
senc
,
int
offset
,
TrackFragment
out
)
{
senc
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
+
offset
);
senc
.
setPosition
(
Atom
.
HEADER_SIZE
+
offset
);
int
fullAtom
=
senc
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
...
...
@@ -639,7 +642,7 @@ public final class FragmentedMp4Extractor implements Extractor {
* Parses a sidx atom (defined in 14496-12).
*/
private
static
SegmentIndex
parseSidx
(
ParsableByteArray
atom
)
{
atom
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
atom
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
atom
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
...
...
library/src/main/java/com/google/android/exoplayer/extractor/DefaultTrackOutput.java
View file @
587edf8e
...
...
@@ -144,6 +144,16 @@ public final class DefaultTrackOutput implements TrackOutput {
}
/**
* Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
*
* @param timeUs The seek time.
* @return True if the skip was successful. False otherwise.
*/
public
boolean
skipToKeyframeBefore
(
long
timeUs
)
{
return
rollingBuffer
.
skipToKeyframeBefore
(
timeUs
);
}
/**
* Attempts to configure a splice from this queue to the next.
*
* @param nextQueue The queue being spliced to.
...
...
library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java
View file @
587edf8e
...
...
@@ -25,14 +25,20 @@ import java.io.IOException;
public
interface
Extractor
{
/**
* Returned by {@link #read(ExtractorInput
)} if the {@link ExtractorInput} passed to the next
*
{@link #read(ExtractorInput)} is required to provide data continuing from the position in the
* stream reached by the returning call.
* Returned by {@link #read(ExtractorInput
, PositionHolder)} if the {@link ExtractorInput} passed
*
to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data
*
continuing from the position in the
stream reached by the returning call.
*/
public
static
final
int
RESULT_CONTINUE
=
0
;
/**
* Returned by {@link #read(ExtractorInput)} if the end of the {@link ExtractorInput} was reached.
* Equal to {@link C#RESULT_END_OF_INPUT}.
* Returned by {@link #read(ExtractorInput, PositionHolder)} if the {@link ExtractorInput} passed
* to the next {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting
* from a specified position in the stream.
*/
public
static
final
int
RESULT_SEEK
=
1
;
/**
* Returned by {@link #read(ExtractorInput, PositionHolder)} if the end of the
* {@link ExtractorInput} was reached. Equal to {@link C#RESULT_END_OF_INPUT}.
*/
public
static
final
int
RESULT_END_OF_INPUT
=
C
.
RESULT_END_OF_INPUT
;
...
...
@@ -47,21 +53,31 @@ public interface Extractor {
* Extracts data read from a provided {@link ExtractorInput}.
* <p>
* Each read will extract at most one sample from the stream before returning.
* <p>
* In the common case, {@link #RESULT_CONTINUE} is returned to indicate that
* {@link ExtractorInput} passed to the next read is required to provide data continuing from the
* position in the stream reached by the returning call. If the extractor requires data to be
* provided from a different position, then that position is set in {@code seekPosition} and
* {@link #RESULT_SEEK} is returned. If the extractor reached the end of the data provided by the
* {@link ExtractorInput}, then {@link #RESULT_END_OF_INPUT} is returned.
*
* @param input The {@link ExtractorInput} from which data should be read.
* @param seekPosition If {@link #RESULT_SEEK} is returned, this holder is updated to hold the
* position of the required data.
* @return One of the {@code RESULT_} values defined in this interface.
* @throws IOException If an error occurred reading from the input.
* @throws InterruptedException If the thread was interrupted.
*/
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
;
int
read
(
ExtractorInput
input
,
PositionHolder
seekPosition
)
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}.
* {@link #read(ExtractorInput
, PositionHolder)} 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/ExtractorSampleSource.java
0 → 100644
View file @
587edf8e
/*
* 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.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormatHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.TrackInfo
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.drm.DrmInitData
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.Loader
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.util.Assertions
;
import
android.net.Uri
;
import
android.os.SystemClock
;
import
android.util.SparseArray
;
import
java.io.IOException
;
/**
* A {@link SampleSource} that extracts sample data using an {@link Extractor}
*/
public
class
ExtractorSampleSource
implements
SampleSource
,
ExtractorOutput
,
Loader
.
Callback
{
/**
* The default minimum number of times to retry loading data prior to failing.
*/
public
static
final
int
DEFAULT_MIN_LOADABLE_RETRY_COUNT
=
3
;
private
static
final
int
BUFFER_LENGTH
=
256
*
1024
;
private
static
final
int
NO_RESET_PENDING
=
-
1
;
private
final
Extractor
extractor
;
private
final
BufferPool
bufferPool
;
private
final
int
requestedBufferSize
;
private
final
SparseArray
<
DefaultTrackOutput
>
sampleQueues
;
private
final
int
minLoadableRetryCount
;
private
final
boolean
frameAccurateSeeking
;
private
final
Uri
uri
;
private
final
DataSource
dataSource
;
private
volatile
boolean
tracksBuilt
;
private
volatile
SeekMap
seekMap
;
private
volatile
DrmInitData
drmInitData
;
private
boolean
prepared
;
private
int
enabledTrackCount
;
private
TrackInfo
[]
trackInfos
;
private
boolean
[]
pendingMediaFormat
;
private
boolean
[]
pendingDiscontinuities
;
private
boolean
[]
trackEnabledStates
;
private
int
remainingReleaseCount
;
private
long
downstreamPositionUs
;
private
long
lastSeekPositionUs
;
private
long
pendingResetPositionUs
;
private
Loader
loader
;
private
ExtractingLoadable
loadable
;
private
IOException
currentLoadableException
;
private
boolean
currentLoadableExceptionFatal
;
// TODO: Set this back to 0 in the correct place (some place indicative of making progress).
private
int
currentLoadableExceptionCount
;
private
long
currentLoadableExceptionTimestamp
;
private
boolean
loadingFinished
;
/**
* @param uri The {@link Uri} of the media stream.
* @param dataSource A data source to read the media stream.
* @param extractor An {@link Extractor} to extract the media stream.
* @param downstreamRendererCount Number of track renderers dependent on this sample source.
* @param requestedBufferSize The requested total buffer size for storing sample data, in bytes.
* The actual allocated size may exceed the value passed in if the implementation requires it.
*/
public
ExtractorSampleSource
(
Uri
uri
,
DataSource
dataSource
,
Extractor
extractor
,
int
downstreamRendererCount
,
int
requestedBufferSize
)
{
this
.
uri
=
uri
;
this
.
dataSource
=
dataSource
;
this
.
extractor
=
extractor
;
remainingReleaseCount
=
downstreamRendererCount
;
this
.
requestedBufferSize
=
requestedBufferSize
;
sampleQueues
=
new
SparseArray
<
DefaultTrackOutput
>();
bufferPool
=
new
BufferPool
(
BUFFER_LENGTH
);
minLoadableRetryCount
=
DEFAULT_MIN_LOADABLE_RETRY_COUNT
;
pendingResetPositionUs
=
NO_RESET_PENDING
;
frameAccurateSeeking
=
true
;
extractor
.
init
(
this
);
}
@Override
public
boolean
prepare
()
throws
IOException
{
if
(
prepared
)
{
return
true
;
}
if
(
loader
==
null
)
{
loader
=
new
Loader
(
"Loader:ExtractorSampleSource"
);
}
continueBufferingInternal
();
// TODO: Support non-seekable content? Or at least avoid getting stuck here if a seekMap doesn't
// arrive (we may end up filling the sample buffers whilst we're still not prepared, and then
// getting stuck).
if
(
seekMap
!=
null
&&
tracksBuilt
&&
haveFormatsForAllTracks
())
{
int
trackCount
=
sampleQueues
.
size
();
trackEnabledStates
=
new
boolean
[
trackCount
];
pendingDiscontinuities
=
new
boolean
[
trackCount
];
pendingMediaFormat
=
new
boolean
[
trackCount
];
trackInfos
=
new
TrackInfo
[
trackCount
];
for
(
int
i
=
0
;
i
<
trackCount
;
i
++)
{
MediaFormat
format
=
sampleQueues
.
valueAt
(
i
).
getFormat
();
trackInfos
[
i
]
=
new
TrackInfo
(
format
.
mimeType
,
format
.
durationUs
);
}
prepared
=
true
;
if
(
isPendingReset
())
{
restartFrom
(
pendingResetPositionUs
);
}
return
true
;
}
else
{
maybeThrowLoadableException
();
return
false
;
}
}
@Override
public
int
getTrackCount
()
{
return
sampleQueues
.
size
();
}
@Override
public
TrackInfo
getTrackInfo
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
return
trackInfos
[
track
];
}
@Override
public
void
enable
(
int
track
,
long
positionUs
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(!
trackEnabledStates
[
track
]);
enabledTrackCount
++;
trackEnabledStates
[
track
]
=
true
;
pendingMediaFormat
[
track
]
=
true
;
if
(
enabledTrackCount
==
1
)
{
seekToUs
(
positionUs
);
}
}
@Override
public
void
disable
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
trackEnabledStates
[
track
]);
enabledTrackCount
--;
trackEnabledStates
[
track
]
=
false
;
pendingDiscontinuities
[
track
]
=
false
;
if
(
enabledTrackCount
==
0
)
{
if
(
loader
.
isLoading
())
{
loader
.
cancelLoading
();
}
else
{
clearState
();
}
}
}
@Override
public
boolean
continueBuffering
(
long
playbackPositionUs
)
throws
IOException
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
enabledTrackCount
>
0
);
downstreamPositionUs
=
playbackPositionUs
;
discardSamplesForDisabledTracks
(
downstreamPositionUs
);
return
loadingFinished
||
continueBufferingInternal
();
}
@Override
public
int
readData
(
int
track
,
long
playbackPositionUs
,
MediaFormatHolder
formatHolder
,
SampleHolder
sampleHolder
,
boolean
onlyReadDiscontinuity
)
throws
IOException
{
downstreamPositionUs
=
playbackPositionUs
;
if
(
pendingDiscontinuities
[
track
])
{
pendingDiscontinuities
[
track
]
=
false
;
return
DISCONTINUITY_READ
;
}
if
(
onlyReadDiscontinuity
||
isPendingReset
())
{
maybeThrowLoadableException
();
return
NOTHING_READ
;
}
DefaultTrackOutput
sampleQueue
=
sampleQueues
.
valueAt
(
track
);
if
(
pendingMediaFormat
[
track
])
{
formatHolder
.
format
=
sampleQueue
.
getFormat
();
formatHolder
.
drmInitData
=
drmInitData
;
pendingMediaFormat
[
track
]
=
false
;
return
FORMAT_READ
;
}
if
(
sampleQueue
.
getSample
(
sampleHolder
))
{
boolean
decodeOnly
=
frameAccurateSeeking
&&
sampleHolder
.
timeUs
<
lastSeekPositionUs
;
sampleHolder
.
flags
|=
decodeOnly
?
C
.
SAMPLE_FLAG_DECODE_ONLY
:
0
;
return
SAMPLE_READ
;
}
if
(
loadingFinished
)
{
return
END_OF_STREAM
;
}
maybeThrowLoadableException
();
return
NOTHING_READ
;
}
@Override
public
void
seekToUs
(
long
positionUs
)
{
Assertions
.
checkState
(
prepared
);
Assertions
.
checkState
(
enabledTrackCount
>
0
);
lastSeekPositionUs
=
positionUs
;
if
((
isPendingReset
()
?
pendingResetPositionUs
:
downstreamPositionUs
)
==
positionUs
)
{
return
;
}
downstreamPositionUs
=
positionUs
;
// If we're not pending a reset, see if we can seek within the sample queues.
boolean
seekInsideBuffer
=
!
isPendingReset
();
for
(
int
i
=
0
;
seekInsideBuffer
&&
i
<
sampleQueues
.
size
();
i
++)
{
seekInsideBuffer
&=
sampleQueues
.
valueAt
(
i
).
skipToKeyframeBefore
(
positionUs
);
}
// If we failed to seek within the sample queues, we need to restart.
if
(!
seekInsideBuffer
)
{
restartFrom
(
positionUs
);
}
// Either way, we need to send discontinuities to the downstream components.
for
(
int
i
=
0
;
i
<
pendingDiscontinuities
.
length
;
i
++)
{
pendingDiscontinuities
[
i
]
=
true
;
}
}
@Override
public
long
getBufferedPositionUs
()
{
if
(
loadingFinished
)
{
return
TrackRenderer
.
END_OF_TRACK_US
;
}
else
if
(
isPendingReset
())
{
return
pendingResetPositionUs
;
}
else
{
long
largestParsedTimestampUs
=
Long
.
MIN_VALUE
;
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
largestParsedTimestampUs
=
Math
.
max
(
largestParsedTimestampUs
,
sampleQueues
.
valueAt
(
i
).
getLargestParsedTimestampUs
());
}
return
largestParsedTimestampUs
==
Long
.
MIN_VALUE
?
downstreamPositionUs
:
largestParsedTimestampUs
;
}
}
@Override
public
void
release
()
{
Assertions
.
checkState
(
remainingReleaseCount
>
0
);
if
(--
remainingReleaseCount
==
0
)
{
loader
.
release
();
loader
=
null
;
}
}
// Loader.Callback implementation.
@Override
public
void
onLoadCompleted
(
Loadable
loadable
)
{
loadingFinished
=
true
;
}
@Override
public
void
onLoadCanceled
(
Loadable
loadable
)
{
if
(
enabledTrackCount
>
0
)
{
restartFrom
(
pendingResetPositionUs
);
}
else
{
clearState
();
}
}
@Override
public
void
onLoadError
(
Loadable
loadable
,
IOException
e
)
{
currentLoadableException
=
e
;
currentLoadableExceptionCount
++;
currentLoadableExceptionTimestamp
=
SystemClock
.
elapsedRealtime
();
maybeStartLoading
();
}
// ExtractorOutput implementation.
@Override
public
TrackOutput
track
(
int
id
)
{
DefaultTrackOutput
sampleQueue
=
sampleQueues
.
get
(
id
);
if
(
sampleQueue
==
null
)
{
sampleQueue
=
new
DefaultTrackOutput
(
bufferPool
);
sampleQueues
.
put
(
id
,
sampleQueue
);
}
return
sampleQueue
;
}
@Override
public
void
endTracks
()
{
tracksBuilt
=
true
;
}
@Override
public
void
seekMap
(
SeekMap
seekMap
)
{
this
.
seekMap
=
seekMap
;
}
@Override
public
void
drmInitData
(
DrmInitData
drmInitData
)
{
this
.
drmInitData
=
drmInitData
;
}
// Internal stuff.
private
boolean
continueBufferingInternal
()
throws
IOException
{
maybeStartLoading
();
if
(
isPendingReset
())
{
return
false
;
}
boolean
haveSamples
=
prepared
&&
haveSampleForOneEnabledTrack
();
if
(!
haveSamples
)
{
maybeThrowLoadableException
();
}
return
haveSamples
;
}
private
void
restartFrom
(
long
positionUs
)
{
pendingResetPositionUs
=
positionUs
;
loadingFinished
=
false
;
if
(
loader
.
isLoading
())
{
loader
.
cancelLoading
();
}
else
{
clearState
();
maybeStartLoading
();
}
}
private
void
maybeStartLoading
()
{
if
(
currentLoadableExceptionFatal
||
loadingFinished
||
loader
.
isLoading
())
{
return
;
}
if
(
currentLoadableException
!=
null
)
{
Assertions
.
checkState
(
loadable
!=
null
);
long
elapsedMillis
=
SystemClock
.
elapsedRealtime
()
-
currentLoadableExceptionTimestamp
;
if
(
elapsedMillis
>=
getRetryDelayMillis
(
currentLoadableExceptionCount
))
{
currentLoadableException
=
null
;
loader
.
startLoading
(
loadable
,
this
);
}
return
;
}
if
(!
prepared
)
{
loadable
=
new
ExtractingLoadable
(
uri
,
dataSource
,
extractor
,
bufferPool
,
requestedBufferSize
,
0
);
}
else
{
Assertions
.
checkState
(
isPendingReset
());
loadable
=
new
ExtractingLoadable
(
uri
,
dataSource
,
extractor
,
bufferPool
,
requestedBufferSize
,
seekMap
.
getPosition
(
pendingResetPositionUs
));
pendingResetPositionUs
=
NO_RESET_PENDING
;
}
loader
.
startLoading
(
loadable
,
this
);
}
private
void
maybeThrowLoadableException
()
throws
IOException
{
if
(
currentLoadableException
!=
null
&&
(
currentLoadableExceptionFatal
||
currentLoadableExceptionCount
>
minLoadableRetryCount
))
{
throw
currentLoadableException
;
}
}
private
boolean
haveFormatsForAllTracks
()
{
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
if
(!
sampleQueues
.
valueAt
(
i
).
hasFormat
())
{
return
false
;
}
}
return
true
;
}
private
boolean
haveSampleForOneEnabledTrack
()
{
for
(
int
i
=
0
;
i
<
trackEnabledStates
.
length
;
i
++)
{
if
(
trackEnabledStates
[
i
]
&&
!
sampleQueues
.
valueAt
(
i
).
isEmpty
())
{
return
true
;
}
}
return
false
;
}
private
void
discardSamplesForDisabledTracks
(
long
timeUs
)
{
for
(
int
i
=
0
;
i
<
trackEnabledStates
.
length
;
i
++)
{
if
(!
trackEnabledStates
[
i
])
{
sampleQueues
.
valueAt
(
i
).
discardUntil
(
timeUs
);
}
}
}
private
void
clearState
()
{
for
(
int
i
=
0
;
i
<
sampleQueues
.
size
();
i
++)
{
sampleQueues
.
valueAt
(
i
).
clear
();
}
loadable
=
null
;
currentLoadableException
=
null
;
currentLoadableExceptionCount
=
0
;
currentLoadableExceptionFatal
=
false
;
}
private
boolean
isPendingReset
()
{
return
pendingResetPositionUs
!=
NO_RESET_PENDING
;
}
private
long
getRetryDelayMillis
(
long
errorCount
)
{
return
Math
.
min
((
errorCount
-
1
)
*
1000
,
5000
);
}
/**
* Loads the media stream and extracts sample data from it.
*/
private
static
class
ExtractingLoadable
implements
Loadable
{
private
final
Uri
uri
;
private
final
DataSource
dataSource
;
private
final
Extractor
extractor
;
private
final
BufferPool
bufferPool
;
private
final
int
bufferPoolSizeLimit
;
private
final
PositionHolder
positionHolder
;
private
volatile
boolean
loadCanceled
;
private
boolean
pendingExtractorSeek
;
public
ExtractingLoadable
(
Uri
uri
,
DataSource
dataSource
,
Extractor
extractor
,
BufferPool
bufferPool
,
int
bufferPoolSizeLimit
,
long
position
)
{
this
.
uri
=
Assertions
.
checkNotNull
(
uri
);
this
.
dataSource
=
Assertions
.
checkNotNull
(
dataSource
);
this
.
extractor
=
Assertions
.
checkNotNull
(
extractor
);
this
.
bufferPool
=
Assertions
.
checkNotNull
(
bufferPool
);
this
.
bufferPoolSizeLimit
=
bufferPoolSizeLimit
;
positionHolder
=
new
PositionHolder
();
positionHolder
.
position
=
position
;
pendingExtractorSeek
=
true
;
}
@Override
public
void
cancelLoad
()
{
loadCanceled
=
true
;
}
@Override
public
boolean
isLoadCanceled
()
{
return
loadCanceled
;
}
@Override
public
void
load
()
throws
IOException
,
InterruptedException
{
if
(
pendingExtractorSeek
)
{
extractor
.
seek
();
pendingExtractorSeek
=
false
;
}
int
result
=
Extractor
.
RESULT_CONTINUE
;
while
(
result
==
Extractor
.
RESULT_CONTINUE
&&
!
loadCanceled
)
{
ExtractorInput
input
=
null
;
try
{
long
position
=
positionHolder
.
position
;
long
length
=
dataSource
.
open
(
new
DataSpec
(
uri
,
position
,
C
.
LENGTH_UNBOUNDED
,
null
));
if
(
length
!=
C
.
LENGTH_UNBOUNDED
)
{
length
+=
position
;
}
input
=
new
DefaultExtractorInput
(
dataSource
,
position
,
length
);
while
(
result
==
Extractor
.
RESULT_CONTINUE
&&
!
loadCanceled
)
{
bufferPool
.
blockWhileAllocatedSizeExceeds
(
bufferPoolSizeLimit
);
result
=
extractor
.
read
(
input
,
positionHolder
);
// TODO: Implement throttling to stop us from buffering data too often.
}
}
finally
{
if
(
result
==
Extractor
.
RESULT_SEEK
)
{
result
=
Extractor
.
RESULT_CONTINUE
;
}
else
if
(
input
!=
null
)
{
positionHolder
.
position
=
input
.
getPosition
();
}
dataSource
.
close
();
}
}
}
}
}
library/src/main/java/com/google/android/exoplayer/extractor/PositionHolder.java
0 → 100644
View file @
587edf8e
/*
* 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
;
/**
* Holds a position in the stream.
*/
public
final
class
PositionHolder
{
/**
* The held position.
*/
public
long
position
;
}
library/src/main/java/com/google/android/exoplayer/extractor/RollingSampleBuffer.java
View file @
587edf8e
...
...
@@ -112,6 +112,21 @@ import java.util.concurrent.ConcurrentLinkedQueue;
}
/**
* Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
*
* @param timeUs The seek time.
* @return True if the skip was successful. False otherwise.
*/
public
boolean
skipToKeyframeBefore
(
long
timeUs
)
{
long
nextOffset
=
infoQueue
.
skipToKeyframeBefore
(
timeUs
);
if
(
nextOffset
==
-
1
)
{
return
false
;
}
dropDownstreamTo
(
nextOffset
);
return
true
;
}
/**
* Reads the current sample, advancing the read index to the next sample.
*
* @param sampleHolder The holder into which the current sample should be written.
...
...
@@ -471,6 +486,50 @@ import java.util.concurrent.ConcurrentLinkedQueue;
:
(
sizes
[
lastReadIndex
]
+
offsets
[
lastReadIndex
]);
}
/**
* Attempts to locate the keyframe before the specified time, if it's present in the buffer.
*
* @param timeUs The seek time.
* @return The offset of the keyframe's data if the keyframe was present. -1 otherwise.
*/
public
synchronized
long
skipToKeyframeBefore
(
long
timeUs
)
{
if
(
queueSize
==
0
||
timeUs
<
timesUs
[
relativeReadIndex
])
{
return
-
1
;
}
int
lastWriteIndex
=
(
relativeWriteIndex
==
0
?
capacity
:
relativeWriteIndex
)
-
1
;
long
lastTimeUs
=
timesUs
[
lastWriteIndex
];
if
(
timeUs
>
lastTimeUs
)
{
return
-
1
;
}
// TODO: This can be optimized further using binary search, although the fact that the array
// is cyclic means we'd need to implement the binary search ourselves.
int
sampleCount
=
0
;
int
sampleCountToKeyframe
=
-
1
;
int
searchIndex
=
relativeReadIndex
;
while
(
searchIndex
!=
relativeWriteIndex
)
{
if
(
timesUs
[
searchIndex
]
>
timeUs
)
{
// We've gone too far.
break
;
}
else
if
((
flags
[
searchIndex
]
&
C
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
// We've found a keyframe, and we're still before the seek position.
sampleCountToKeyframe
=
sampleCount
;
}
searchIndex
=
(
searchIndex
+
1
)
%
capacity
;
sampleCount
++;
}
if
(
sampleCountToKeyframe
==
-
1
)
{
return
-
1
;
}
queueSize
-=
sampleCountToKeyframe
;
relativeReadIndex
=
(
relativeReadIndex
+
sampleCountToKeyframe
)
%
capacity
;
absoluteReadIndex
+=
sampleCountToKeyframe
;
return
offsets
[
relativeReadIndex
];
}
// Called by the loading thread.
public
synchronized
void
commitSample
(
long
timeUs
,
int
sampleFlags
,
long
offset
,
int
size
,
...
...
library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java
View file @
587edf8e
...
...
@@ -21,6 +21,7 @@ import com.google.android.exoplayer.ParserException;
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.PositionHolder
;
import
com.google.android.exoplayer.extractor.SeekMap
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.util.MimeTypes
;
...
...
@@ -111,7 +112,8 @@ public final class Mp3Extractor implements Extractor {
}
@Override
public
int
read
(
ExtractorInput
extractorInput
)
throws
IOException
,
InterruptedException
{
public
int
read
(
ExtractorInput
extractorInput
,
PositionHolder
seekPosition
)
throws
IOException
,
InterruptedException
{
if
(
synchronizedHeaderData
==
0
&&
synchronizeCatchingEndOfInput
(
extractorInput
)
==
RESULT_END_OF_INPUT
)
{
return
RESULT_END_OF_INPUT
;
...
...
library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
→
library/src/main/java/com/google/android/exoplayer/
extractor/
mp4/Atom.java
View file @
587edf8e
...
...
@@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
extractor
.
mp4
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
...
...
@@ -25,69 +25,69 @@ import java.util.List;
public
abstract
class
Atom
{
/** Size of an atom header, in bytes. */
public
static
final
int
ATOM_HEADER_SIZE
=
8
;
/** Size of a long atom header, in bytes. */
public
static
final
int
LONG_ATOM_HEADER_SIZE
=
16
;
public
static
final
int
HEADER_SIZE
=
8
;
/** Size of a full atom header, in bytes. */
public
static
final
int
FULL_ATOM_HEADER_SIZE
=
12
;
public
static
final
int
FULL_HEADER_SIZE
=
12
;
/** Size of a long atom header, in bytes. */
public
static
final
int
LONG_HEADER_SIZE
=
16
;
/** Value for the first 32 bits of atomSize when the atom size is actually a long value. */
public
static
final
int
LONG_SIZE_PREFIX
=
1
;
public
static
final
int
TYPE_ftyp
=
getAtomTypeInteger
(
"ftyp"
);
public
static
final
int
TYPE_avc1
=
getAtomTypeInteger
(
"avc1"
);
public
static
final
int
TYPE_avc3
=
getAtomTypeInteger
(
"avc3"
);
public
static
final
int
TYPE_esds
=
getAtomTypeInteger
(
"esds"
);
public
static
final
int
TYPE_mdat
=
getAtomTypeInteger
(
"mdat"
);
public
static
final
int
TYPE_mp4a
=
getAtomTypeInteger
(
"mp4a"
);
public
static
final
int
TYPE_ac_3
=
getAtomTypeInteger
(
"ac-3"
);
public
static
final
int
TYPE_dac3
=
getAtomTypeInteger
(
"dac3"
);
public
static
final
int
TYPE_ec_3
=
getAtomTypeInteger
(
"ec-3"
);
public
static
final
int
TYPE_dec3
=
getAtomTypeInteger
(
"dec3"
);
public
static
final
int
TYPE_tfdt
=
getAtomTypeInteger
(
"tfdt"
);
public
static
final
int
TYPE_tfhd
=
getAtomTypeInteger
(
"tfhd"
);
public
static
final
int
TYPE_trex
=
getAtomTypeInteger
(
"trex"
);
public
static
final
int
TYPE_trun
=
getAtomTypeInteger
(
"trun"
);
public
static
final
int
TYPE_sidx
=
getAtomTypeInteger
(
"sidx"
);
public
static
final
int
TYPE_moov
=
getAtomTypeInteger
(
"moov"
);
public
static
final
int
TYPE_mvhd
=
getAtomTypeInteger
(
"mvhd"
);
public
static
final
int
TYPE_trak
=
getAtomTypeInteger
(
"trak"
);
public
static
final
int
TYPE_mdia
=
getAtomTypeInteger
(
"mdia"
);
public
static
final
int
TYPE_minf
=
getAtomTypeInteger
(
"minf"
);
public
static
final
int
TYPE_stbl
=
getAtomTypeInteger
(
"stbl"
);
public
static
final
int
TYPE_avcC
=
getAtomTypeInteger
(
"avcC"
);
public
static
final
int
TYPE_moof
=
getAtomTypeInteger
(
"moof"
);
public
static
final
int
TYPE_traf
=
getAtomTypeInteger
(
"traf"
);
public
static
final
int
TYPE_mvex
=
getAtomTypeInteger
(
"mvex"
);
public
static
final
int
TYPE_tkhd
=
getAtomTypeInteger
(
"tkhd"
);
public
static
final
int
TYPE_mdhd
=
getAtomTypeInteger
(
"mdhd"
);
public
static
final
int
TYPE_hdlr
=
getAtomTypeInteger
(
"hdlr"
);
public
static
final
int
TYPE_stsd
=
getAtomTypeInteger
(
"stsd"
);
public
static
final
int
TYPE_pssh
=
getAtomTypeInteger
(
"pssh"
);
public
static
final
int
TYPE_sinf
=
getAtomTypeInteger
(
"sinf"
);
public
static
final
int
TYPE_schm
=
getAtomTypeInteger
(
"schm"
);
public
static
final
int
TYPE_schi
=
getAtomTypeInteger
(
"schi"
);
public
static
final
int
TYPE_tenc
=
getAtomTypeInteger
(
"tenc"
);
public
static
final
int
TYPE_encv
=
getAtomTypeInteger
(
"encv"
);
public
static
final
int
TYPE_enca
=
getAtomTypeInteger
(
"enca"
);
public
static
final
int
TYPE_frma
=
getAtomTypeInteger
(
"frma"
);
public
static
final
int
TYPE_saiz
=
getAtomTypeInteger
(
"saiz"
);
public
static
final
int
TYPE_uuid
=
getAtomTypeInteger
(
"uuid"
);
public
static
final
int
TYPE_senc
=
getAtomTypeInteger
(
"senc"
);
public
static
final
int
TYPE_pasp
=
getAtomTypeInteger
(
"pasp"
);
public
static
final
int
TYPE_TTML
=
getAtomTypeInteger
(
"TTML"
);
public
static
final
int
TYPE_vmhd
=
getAtomTypeInteger
(
"vmhd"
);
public
static
final
int
TYPE_smhd
=
getAtomTypeInteger
(
"smhd"
);
public
static
final
int
TYPE_mp4v
=
getAtomTypeInteger
(
"mp4v"
);
public
static
final
int
TYPE_stts
=
getAtomTypeInteger
(
"stts"
);
public
static
final
int
TYPE_stss
=
getAtomTypeInteger
(
"stss"
);
public
static
final
int
TYPE_ctts
=
getAtomTypeInteger
(
"ctts"
);
public
static
final
int
TYPE_stsc
=
getAtomTypeInteger
(
"stsc"
);
public
static
final
int
TYPE_stsz
=
getAtomTypeInteger
(
"stsz"
);
public
static
final
int
TYPE_stco
=
getAtomTypeInteger
(
"stco"
);
public
static
final
int
TYPE_co64
=
getAtomTypeInteger
(
"co64"
);
public
static
final
int
TYPE_ftyp
=
Util
.
getIntegerCodeForString
(
"ftyp"
);
public
static
final
int
TYPE_avc1
=
Util
.
getIntegerCodeForString
(
"avc1"
);
public
static
final
int
TYPE_avc3
=
Util
.
getIntegerCodeForString
(
"avc3"
);
public
static
final
int
TYPE_esds
=
Util
.
getIntegerCodeForString
(
"esds"
);
public
static
final
int
TYPE_mdat
=
Util
.
getIntegerCodeForString
(
"mdat"
);
public
static
final
int
TYPE_mp4a
=
Util
.
getIntegerCodeForString
(
"mp4a"
);
public
static
final
int
TYPE_ac_3
=
Util
.
getIntegerCodeForString
(
"ac-3"
);
public
static
final
int
TYPE_dac3
=
Util
.
getIntegerCodeForString
(
"dac3"
);
public
static
final
int
TYPE_ec_3
=
Util
.
getIntegerCodeForString
(
"ec-3"
);
public
static
final
int
TYPE_dec3
=
Util
.
getIntegerCodeForString
(
"dec3"
);
public
static
final
int
TYPE_tfdt
=
Util
.
getIntegerCodeForString
(
"tfdt"
);
public
static
final
int
TYPE_tfhd
=
Util
.
getIntegerCodeForString
(
"tfhd"
);
public
static
final
int
TYPE_trex
=
Util
.
getIntegerCodeForString
(
"trex"
);
public
static
final
int
TYPE_trun
=
Util
.
getIntegerCodeForString
(
"trun"
);
public
static
final
int
TYPE_sidx
=
Util
.
getIntegerCodeForString
(
"sidx"
);
public
static
final
int
TYPE_moov
=
Util
.
getIntegerCodeForString
(
"moov"
);
public
static
final
int
TYPE_mvhd
=
Util
.
getIntegerCodeForString
(
"mvhd"
);
public
static
final
int
TYPE_trak
=
Util
.
getIntegerCodeForString
(
"trak"
);
public
static
final
int
TYPE_mdia
=
Util
.
getIntegerCodeForString
(
"mdia"
);
public
static
final
int
TYPE_minf
=
Util
.
getIntegerCodeForString
(
"minf"
);
public
static
final
int
TYPE_stbl
=
Util
.
getIntegerCodeForString
(
"stbl"
);
public
static
final
int
TYPE_avcC
=
Util
.
getIntegerCodeForString
(
"avcC"
);
public
static
final
int
TYPE_moof
=
Util
.
getIntegerCodeForString
(
"moof"
);
public
static
final
int
TYPE_traf
=
Util
.
getIntegerCodeForString
(
"traf"
);
public
static
final
int
TYPE_mvex
=
Util
.
getIntegerCodeForString
(
"mvex"
);
public
static
final
int
TYPE_tkhd
=
Util
.
getIntegerCodeForString
(
"tkhd"
);
public
static
final
int
TYPE_mdhd
=
Util
.
getIntegerCodeForString
(
"mdhd"
);
public
static
final
int
TYPE_hdlr
=
Util
.
getIntegerCodeForString
(
"hdlr"
);
public
static
final
int
TYPE_stsd
=
Util
.
getIntegerCodeForString
(
"stsd"
);
public
static
final
int
TYPE_pssh
=
Util
.
getIntegerCodeForString
(
"pssh"
);
public
static
final
int
TYPE_sinf
=
Util
.
getIntegerCodeForString
(
"sinf"
);
public
static
final
int
TYPE_schm
=
Util
.
getIntegerCodeForString
(
"schm"
);
public
static
final
int
TYPE_schi
=
Util
.
getIntegerCodeForString
(
"schi"
);
public
static
final
int
TYPE_tenc
=
Util
.
getIntegerCodeForString
(
"tenc"
);
public
static
final
int
TYPE_encv
=
Util
.
getIntegerCodeForString
(
"encv"
);
public
static
final
int
TYPE_enca
=
Util
.
getIntegerCodeForString
(
"enca"
);
public
static
final
int
TYPE_frma
=
Util
.
getIntegerCodeForString
(
"frma"
);
public
static
final
int
TYPE_saiz
=
Util
.
getIntegerCodeForString
(
"saiz"
);
public
static
final
int
TYPE_uuid
=
Util
.
getIntegerCodeForString
(
"uuid"
);
public
static
final
int
TYPE_senc
=
Util
.
getIntegerCodeForString
(
"senc"
);
public
static
final
int
TYPE_pasp
=
Util
.
getIntegerCodeForString
(
"pasp"
);
public
static
final
int
TYPE_TTML
=
Util
.
getIntegerCodeForString
(
"TTML"
);
public
static
final
int
TYPE_vmhd
=
Util
.
getIntegerCodeForString
(
"vmhd"
);
public
static
final
int
TYPE_smhd
=
Util
.
getIntegerCodeForString
(
"smhd"
);
public
static
final
int
TYPE_mp4v
=
Util
.
getIntegerCodeForString
(
"mp4v"
);
public
static
final
int
TYPE_stts
=
Util
.
getIntegerCodeForString
(
"stts"
);
public
static
final
int
TYPE_stss
=
Util
.
getIntegerCodeForString
(
"stss"
);
public
static
final
int
TYPE_ctts
=
Util
.
getIntegerCodeForString
(
"ctts"
);
public
static
final
int
TYPE_stsc
=
Util
.
getIntegerCodeForString
(
"stsc"
);
public
static
final
int
TYPE_stsz
=
Util
.
getIntegerCodeForString
(
"stsz"
);
public
static
final
int
TYPE_stco
=
Util
.
getIntegerCodeForString
(
"stco"
);
public
static
final
int
TYPE_co64
=
Util
.
getIntegerCodeForString
(
"co64"
);
public
final
int
type
;
...
...
@@ -187,14 +187,4 @@ public abstract class Atom {
+
(
char
)
(
type
&
0xFF
);
}
private
static
int
getAtomTypeInteger
(
String
typeName
)
{
Assertions
.
checkArgument
(
typeName
.
length
()
==
4
);
int
result
=
0
;
for
(
int
i
=
0
;
i
<
4
;
i
++)
{
result
<<=
8
;
result
|=
typeName
.
charAt
(
i
);
}
return
result
;
}
}
library/src/main/java/com/google/android/exoplayer/
mp4/CommonMp4
AtomParsers.java
→
library/src/main/java/com/google/android/exoplayer/
extractor/mp4/
AtomParsers.java
View file @
587edf8e
...
...
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
extractor
.
mp4
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.CodecSpecificDataUtil
;
import
com.google.android.exoplayer.util.H264Util
;
...
...
@@ -32,7 +31,7 @@ import java.util.Collections;
import
java.util.List
;
/** Utility methods for parsing MP4 format atom payloads according to ISO 14496-12. */
public
final
class
CommonMp4
AtomParsers
{
public
final
class
AtomParsers
{
/** Channel counts for AC-3 audio, indexed by acmod. (See ETSI TS 102 366.) */
private
static
final
int
[]
AC3_CHANNEL_COUNTS
=
new
int
[]
{
2
,
1
,
2
,
3
,
3
,
4
,
4
,
5
};
...
...
@@ -41,17 +40,19 @@ public final class CommonMp4AtomParsers {
192
,
224
,
256
,
320
,
384
,
448
,
512
,
576
,
640
};
/**
* Parses a trak atom (defined in 14496-12)
* Parses a trak atom (defined in 14496-12)
.
*
* @param trak Atom to parse.
* @param mvhd Movie header atom, used to get the timescale.
* @return A {@link Track} instance.
* @return A {@link Track} instance
, or {@code null} if the track's type isn't supported
.
*/
public
static
Track
parseTrak
(
Atom
.
ContainerAtom
trak
,
Atom
.
LeafAtom
mvhd
)
{
Atom
.
ContainerAtom
mdia
=
trak
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
);
int
trackType
=
parseHdlr
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_hdlr
).
data
);
Assertions
.
checkState
(
trackType
==
Track
.
TYPE_AUDIO
||
trackType
==
Track
.
TYPE_VIDEO
||
trackType
==
Track
.
TYPE_TEXT
||
trackType
==
Track
.
TYPE_TIME_CODE
);
if
(
trackType
!=
Track
.
TYPE_AUDIO
&&
trackType
!=
Track
.
TYPE_VIDEO
&&
trackType
!=
Track
.
TYPE_TEXT
&&
trackType
!=
Track
.
TYPE_TIME_CODE
)
{
return
null
;
}
Pair
<
Integer
,
Long
>
header
=
parseTkhd
(
trak
.
getLeafAtomOfType
(
Atom
.
TYPE_tkhd
).
data
);
int
id
=
header
.
first
;
...
...
@@ -80,7 +81,7 @@ public final class CommonMp4AtomParsers {
* @param stblAtom stbl (sample table) atom to parse.
* @return Sample table described by the stbl atom.
*/
public
static
Mp4
TrackSampleTable
parseStbl
(
Track
track
,
Atom
.
ContainerAtom
stblAtom
)
{
public
static
TrackSampleTable
parseStbl
(
Track
track
,
Atom
.
ContainerAtom
stblAtom
)
{
// Array of sample sizes.
ParsableByteArray
stsz
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stsz
).
data
;
...
...
@@ -103,7 +104,7 @@ public final class CommonMp4AtomParsers {
ParsableByteArray
ctts
=
cttsAtom
!=
null
?
cttsAtom
.
data
:
null
;
// Skip full atom.
stsz
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
);
stsz
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
int
fixedSampleSize
=
stsz
.
readUnsignedIntToInt
();
int
sampleCount
=
stsz
.
readUnsignedIntToInt
();
...
...
@@ -113,10 +114,10 @@ public final class CommonMp4AtomParsers {
int
[]
flags
=
new
int
[
sampleCount
];
// Prepare to read chunk offsets.
chunkOffsets
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
);
chunkOffsets
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
int
chunkCount
=
chunkOffsets
.
readUnsignedIntToInt
();
stsc
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
);
stsc
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
int
remainingSamplesPerChunkChanges
=
stsc
.
readUnsignedIntToInt
()
-
1
;
Assertions
.
checkState
(
stsc
.
readInt
()
==
1
,
"stsc first chunk must be 1"
);
int
samplesPerChunk
=
stsc
.
readUnsignedIntToInt
();
...
...
@@ -131,28 +132,31 @@ public final class CommonMp4AtomParsers {
int
remainingSamplesInChunk
=
samplesPerChunk
;
// Prepare to read sample timestamps.
stts
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
);
stts
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
int
remainingTimestampDeltaChanges
=
stts
.
readUnsignedIntToInt
()
-
1
;
int
remainingSamplesAtTimestampDelta
=
stts
.
readUnsignedIntToInt
();
int
timestampDeltaInTimeUnits
=
stts
.
readUnsignedIntToInt
();
// Prepare to read sample timestamp offsets, if ctts is present.
boolean
cttsHasSignedOffsets
=
false
;
int
remainingSamplesAtTimestampOffset
=
0
;
int
remainingTimestampOffsetChanges
=
0
;
int
timestampOffset
=
0
;
if
(
ctts
!=
null
)
{
ctts
.
setPosition
(
Atom
.
ATOM_HEADER_SIZE
);
cttsHasSignedOffsets
=
Atom
.
parseFullAtomVersion
(
ctts
.
readInt
())
==
1
;
ctts
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
remainingTimestampOffsetChanges
=
ctts
.
readUnsignedIntToInt
()
-
1
;
remainingSamplesAtTimestampOffset
=
ctts
.
readUnsignedIntToInt
();
timestampOffset
=
cttsHasSignedOffsets
?
ctts
.
readInt
()
:
ctts
.
readUnsignedIntToInt
();
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
// version 0 ctts boxes, however some streams violate the spec and use signed integers
// instead. It's safe to always parse sample offsets as signed integers here, because
// unsigned integers will still be parsed correctly (unless their top bit is set, which
// is never true in practice because sample offsets are always small).
timestampOffset
=
ctts
.
readInt
();
}
int
nextSynchronizationSampleIndex
=
-
1
;
int
remainingSynchronizationSamples
=
0
;
if
(
stss
!=
null
)
{
stss
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
);
stss
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
remainingSynchronizationSamples
=
stss
.
readUnsignedIntToInt
();
nextSynchronizationSampleIndex
=
stss
.
readUnsignedIntToInt
()
-
1
;
}
...
...
@@ -195,7 +199,8 @@ public final class CommonMp4AtomParsers {
remainingSamplesAtTimestampOffset
--;
if
(
remainingSamplesAtTimestampOffset
==
0
&&
remainingTimestampOffsetChanges
>
0
)
{
remainingSamplesAtTimestampOffset
=
ctts
.
readUnsignedIntToInt
();
timestampOffset
=
cttsHasSignedOffsets
?
ctts
.
readInt
()
:
ctts
.
readUnsignedIntToInt
();
// Read a signed offset even for version 0 ctts boxes (see comment above).
timestampOffset
=
ctts
.
readInt
();
remainingTimestampOffsetChanges
--;
}
}
...
...
@@ -240,7 +245,7 @@ public final class CommonMp4AtomParsers {
Assertions
.
checkArgument
(
remainingSamplesInChunk
==
0
);
Assertions
.
checkArgument
(
remainingTimestampDeltaChanges
==
0
);
Assertions
.
checkArgument
(
remainingTimestampOffsetChanges
==
0
);
return
new
Mp4
TrackSampleTable
(
offsets
,
sizes
,
timestamps
,
flags
);
return
new
TrackSampleTable
(
offsets
,
sizes
,
timestamps
,
flags
);
}
/**
...
...
@@ -250,7 +255,7 @@ public final class CommonMp4AtomParsers {
* @return Timescale for the movie.
*/
private
static
long
parseMvhd
(
ParsableByteArray
mvhd
)
{
mvhd
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
mvhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
mvhd
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
...
...
@@ -267,7 +272,7 @@ public final class CommonMp4AtomParsers {
* the movie header box). The duration is set to -1 if the duration is unspecified.
*/
private
static
Pair
<
Integer
,
Long
>
parseTkhd
(
ParsableByteArray
tkhd
)
{
tkhd
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
tkhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
tkhd
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
...
...
@@ -303,7 +308,7 @@ public final class CommonMp4AtomParsers {
* @return The track type.
*/
private
static
int
parseHdlr
(
ParsableByteArray
hdlr
)
{
hdlr
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
+
4
);
hdlr
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
+
4
);
return
hdlr
.
readInt
();
}
...
...
@@ -314,7 +319,7 @@ public final class CommonMp4AtomParsers {
* @return The media timescale, defined as the number of time units that pass in one second.
*/
private
static
long
parseMdhd
(
ParsableByteArray
mdhd
)
{
mdhd
.
setPosition
(
Atom
.
ATOM_
HEADER_SIZE
);
mdhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
mdhd
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
...
...
@@ -324,7 +329,7 @@ public final class CommonMp4AtomParsers {
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
[]>
parseStsd
(
ParsableByteArray
stsd
,
long
durationUs
)
{
stsd
.
setPosition
(
Atom
.
FULL_
ATOM_
HEADER_SIZE
);
stsd
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
int
numberOfEntries
=
stsd
.
readInt
();
MediaFormat
mediaFormat
=
null
;
TrackEncryptionBox
[]
trackEncryptionBoxes
=
new
TrackEncryptionBox
[
numberOfEntries
];
...
...
@@ -358,7 +363,7 @@ public final class CommonMp4AtomParsers {
/** Returns the media format for an avc1 box. */
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAvcFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
,
long
durationUs
)
{
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
);
parent
.
skip
(
24
);
int
width
=
parent
.
readUnsignedShort
();
...
...
@@ -395,7 +400,7 @@ public final class CommonMp4AtomParsers {
}
private
static
List
<
byte
[]>
parseAvcCFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
+
4
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
+
4
);
// Start of the AVCDecoderConfigurationRecord (defined in 14496-15)
int
nalUnitLength
=
(
parent
.
readUnsignedByte
()
&
0x3
)
+
1
;
if
(
nalUnitLength
!=
4
)
{
...
...
@@ -419,7 +424,7 @@ public final class CommonMp4AtomParsers {
private
static
TrackEncryptionBox
parseSinfFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
int
childPosition
=
position
+
Atom
.
ATOM_
HEADER_SIZE
;
int
childPosition
=
position
+
Atom
.
HEADER_SIZE
;
TrackEncryptionBox
trackEncryptionBox
=
null
;
while
(
childPosition
-
position
<
size
)
{
...
...
@@ -442,7 +447,7 @@ public final class CommonMp4AtomParsers {
}
private
static
float
parsePaspFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
);
int
hSpacing
=
parent
.
readUnsignedIntToInt
();
int
vSpacing
=
parent
.
readUnsignedIntToInt
();
return
(
float
)
hSpacing
/
vSpacing
;
...
...
@@ -450,7 +455,7 @@ public final class CommonMp4AtomParsers {
private
static
TrackEncryptionBox
parseSchiFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
)
{
int
childPosition
=
position
+
Atom
.
ATOM_
HEADER_SIZE
;
int
childPosition
=
position
+
Atom
.
HEADER_SIZE
;
while
(
childPosition
-
position
<
size
)
{
parent
.
setPosition
(
childPosition
);
int
childAtomSize
=
parent
.
readInt
();
...
...
@@ -472,7 +477,7 @@ public final class CommonMp4AtomParsers {
/** Returns the media format for an mp4v box. */
private
static
MediaFormat
parseMp4vFromParent
(
ParsableByteArray
parent
,
int
position
,
int
size
,
long
durationUs
)
{
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
);
parent
.
skip
(
24
);
int
width
=
parent
.
readUnsignedShort
();
...
...
@@ -499,7 +504,7 @@ public final class CommonMp4AtomParsers {
private
static
Pair
<
MediaFormat
,
TrackEncryptionBox
>
parseAudioSampleEntry
(
ParsableByteArray
parent
,
int
atomType
,
int
position
,
int
size
,
long
durationUs
)
{
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
);
parent
.
skip
(
16
);
int
channelCount
=
parent
.
readUnsignedShort
();
int
sampleSize
=
parent
.
readUnsignedShort
();
...
...
@@ -564,7 +569,7 @@ public final class CommonMp4AtomParsers {
/** Returns codec-specific initialization data contained in an esds box. */
private
static
byte
[]
parseEsdsFromParent
(
ParsableByteArray
parent
,
int
position
)
{
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
+
4
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
+
4
);
// Start of the ES_Descriptor (defined in 14496-1)
parent
.
skip
(
1
);
// ES_Descriptor tag
int
varIntByte
=
parent
.
readUnsignedByte
();
...
...
@@ -608,7 +613,7 @@ public final class CommonMp4AtomParsers {
private
static
Ac3Format
parseAc3SpecificBoxFromParent
(
ParsableByteArray
parent
,
int
position
)
{
// Start of the dac3 atom (defined in ETSI TS 102 366)
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
);
// fscod (sample rate code)
int
fscod
=
(
parent
.
readUnsignedByte
()
&
0xC0
)
>>
6
;
...
...
@@ -646,12 +651,12 @@ public final class CommonMp4AtomParsers {
private
static
int
parseEc3SpecificBoxFromParent
(
ParsableByteArray
parent
,
int
position
)
{
// Start of the dec3 atom (defined in ETSI TS 102 366)
parent
.
setPosition
(
position
+
Atom
.
ATOM_
HEADER_SIZE
);
parent
.
setPosition
(
position
+
Atom
.
HEADER_SIZE
);
// TODO: Implement parsing for enhanced AC-3 with multiple sub-streams.
return
0
;
}
private
CommonMp4
AtomParsers
()
{
private
AtomParsers
()
{
// Prevent instantiation.
}
...
...
library/src/main/java/com/google/android/exoplayer/
chunk/parse
r/mp4/DefaultSampleValues.java
→
library/src/main/java/com/google/android/exoplayer/
extracto
r/mp4/DefaultSampleValues.java
View file @
587edf8e
...
...
@@ -13,9 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
.
parse
r
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
extracto
r
.
mp4
;
/* package */
final
class
DefaultSampleValues
{
// TODO: Make package private.
public
final
class
DefaultSampleValues
{
public
final
int
sampleDescriptionIndex
;
public
final
int
duration
;
...
...
library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java
0 → 100644
View file @
587edf8e
/*
* 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
.
mp4
;
import
com.google.android.exoplayer.C
;
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.PositionHolder
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.extractor.mp4.Atom.LeafAtom
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.H264Util
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
import
java.io.IOException
;
import
java.util.Arrays
;
import
java.util.List
;
import
java.util.Stack
;
import
java.util.UUID
;
/**
* Facilitates the extraction of data from the fragmented mp4 container format.
* <p>
* This implementation only supports de-muxed (i.e. single track) streams.
*/
public
final
class
FragmentedMp4Extractor
implements
Extractor
{
/**
* Flag to work around an issue in some video streams where every frame is marked as a sync frame.
* The workaround overrides the sync frame flags in the stream, forcing them to false except for
* the first sample in each segment.
* <p>
* This flag does nothing if the stream is not a video stream.
*/
public
static
final
int
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
=
1
;
private
static
final
byte
[]
PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE
=
new
byte
[]
{-
94
,
57
,
79
,
82
,
90
,
-
101
,
79
,
20
,
-
94
,
68
,
108
,
66
,
124
,
100
,
-
115
,
-
12
};
// Parser states
private
static
final
int
STATE_READING_ATOM_HEADER
=
0
;
private
static
final
int
STATE_READING_ATOM_PAYLOAD
=
1
;
private
static
final
int
STATE_READING_ENCRYPTION_DATA
=
2
;
private
static
final
int
STATE_READING_SAMPLE_START
=
3
;
private
static
final
int
STATE_READING_SAMPLE_CONTINUE
=
4
;
private
final
int
workaroundFlags
;
// Temporary arrays.
private
final
ParsableByteArray
nalStartCode
;
private
final
ParsableByteArray
nalLength
;
private
final
ParsableByteArray
encryptionSignalByte
;
// Parser state
private
final
ParsableByteArray
atomHeader
;
private
final
byte
[]
extendedTypeScratch
;
private
final
Stack
<
ContainerAtom
>
containerAtoms
;
private
final
TrackFragment
fragmentRun
;
private
int
parserState
;
private
int
rootAtomBytesRead
;
private
int
atomType
;
private
int
atomSize
;
private
ParsableByteArray
atomData
;
private
int
sampleIndex
;
private
int
sampleSize
;
private
int
sampleBytesWritten
;
private
int
sampleCurrentNalBytesRemaining
;
// Data parsed from moov atom.
private
Track
track
;
private
DefaultSampleValues
extendsDefaults
;
// Extractor outputs.
private
ExtractorOutput
extractorOutput
;
private
TrackOutput
trackOutput
;
public
FragmentedMp4Extractor
()
{
this
(
0
);
}
/**
* @param workaroundFlags Flags to allow parsing of faulty streams.
* {@link #WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME} is currently the only flag defined.
*/
public
FragmentedMp4Extractor
(
int
workaroundFlags
)
{
this
.
workaroundFlags
=
workaroundFlags
;
atomHeader
=
new
ParsableByteArray
(
Atom
.
HEADER_SIZE
);
nalStartCode
=
new
ParsableByteArray
(
H264Util
.
NAL_START_CODE
);
nalLength
=
new
ParsableByteArray
(
4
);
encryptionSignalByte
=
new
ParsableByteArray
(
1
);
extendedTypeScratch
=
new
byte
[
16
];
containerAtoms
=
new
Stack
<
ContainerAtom
>();
fragmentRun
=
new
TrackFragment
();
parserState
=
STATE_READING_ATOM_HEADER
;
}
/**
* Sideloads track information into the extractor.
* <p>
* Should be called before {@link #read(ExtractorInput, PositionHolder)} in the case that the
* extractor will not receive a moov atom in the input data, from which track information would
* normally be parsed.
*
* @param track The track to sideload.
*/
public
void
setTrack
(
Track
track
)
{
this
.
extendsDefaults
=
new
DefaultSampleValues
(
0
,
0
,
0
,
0
);
this
.
track
=
track
;
}
@Override
public
void
init
(
ExtractorOutput
output
)
{
extractorOutput
=
output
;
trackOutput
=
output
.
track
(
0
);
extractorOutput
.
endTracks
();
}
@Override
public
void
seek
()
{
containerAtoms
.
clear
();
rootAtomBytesRead
=
0
;
parserState
=
STATE_READING_ATOM_HEADER
;
}
@Override
public
int
read
(
ExtractorInput
input
,
PositionHolder
seekPosition
)
throws
IOException
,
InterruptedException
{
while
(
true
)
{
switch
(
parserState
)
{
case
STATE_READING_ATOM_HEADER:
if
(!
readAtomHeader
(
input
))
{
return
Extractor
.
RESULT_END_OF_INPUT
;
}
break
;
case
STATE_READING_ATOM_PAYLOAD:
readAtomPayload
(
input
);
break
;
case
STATE_READING_ENCRYPTION_DATA:
readEncryptionData
(
input
);
break
;
default
:
if
(
readSample
(
input
))
{
return
RESULT_CONTINUE
;
}
}
}
}
private
boolean
readAtomHeader
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
if
(!
input
.
readFully
(
atomHeader
.
data
,
0
,
Atom
.
HEADER_SIZE
,
true
))
{
return
false
;
}
rootAtomBytesRead
+=
Atom
.
HEADER_SIZE
;
atomHeader
.
setPosition
(
0
);
atomSize
=
atomHeader
.
readInt
();
atomType
=
atomHeader
.
readInt
();
if
(
atomType
==
Atom
.
TYPE_mdat
)
{
if
(
fragmentRun
.
sampleEncryptionDataNeedsFill
)
{
parserState
=
STATE_READING_ENCRYPTION_DATA
;
}
else
{
parserState
=
STATE_READING_SAMPLE_START
;
}
return
true
;
}
if
(
shouldParseAtom
(
atomType
))
{
if
(
shouldParseContainerAtom
(
atomType
))
{
parserState
=
STATE_READING_ATOM_HEADER
;
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
rootAtomBytesRead
+
atomSize
-
Atom
.
HEADER_SIZE
));
}
else
{
atomData
=
new
ParsableByteArray
(
atomSize
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
Atom
.
HEADER_SIZE
);
parserState
=
STATE_READING_ATOM_PAYLOAD
;
}
}
else
{
atomData
=
null
;
parserState
=
STATE_READING_ATOM_PAYLOAD
;
}
return
true
;
}
private
void
readAtomPayload
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
int
payloadLength
=
atomSize
-
Atom
.
HEADER_SIZE
;
if
(
atomData
!=
null
)
{
input
.
readFully
(
atomData
.
data
,
Atom
.
HEADER_SIZE
,
payloadLength
);
rootAtomBytesRead
+=
payloadLength
;
onLeafAtomRead
(
new
LeafAtom
(
atomType
,
atomData
),
input
.
getPosition
());
}
else
{
input
.
skipFully
(
payloadLength
);
rootAtomBytesRead
+=
payloadLength
;
}
while
(!
containerAtoms
.
isEmpty
()
&&
containerAtoms
.
peek
().
endByteOffset
==
rootAtomBytesRead
)
{
onContainerAtomRead
(
containerAtoms
.
pop
());
}
if
(
containerAtoms
.
isEmpty
())
{
rootAtomBytesRead
=
0
;
}
parserState
=
STATE_READING_ATOM_HEADER
;
}
private
void
onLeafAtomRead
(
LeafAtom
leaf
,
long
inputPosition
)
{
if
(!
containerAtoms
.
isEmpty
())
{
containerAtoms
.
peek
().
add
(
leaf
);
}
else
if
(
leaf
.
type
==
Atom
.
TYPE_sidx
)
{
ChunkIndex
segmentIndex
=
parseSidx
(
leaf
.
data
,
inputPosition
);
extractorOutput
.
seekMap
(
segmentIndex
);
}
}
private
void
onContainerAtomRead
(
ContainerAtom
container
)
{
if
(
container
.
type
==
Atom
.
TYPE_moov
)
{
onMoovContainerAtomRead
(
container
);
}
else
if
(
container
.
type
==
Atom
.
TYPE_moof
)
{
onMoofContainerAtomRead
(
container
);
}
else
if
(!
containerAtoms
.
isEmpty
())
{
containerAtoms
.
peek
().
add
(
container
);
}
}
private
void
onMoovContainerAtomRead
(
ContainerAtom
moov
)
{
List
<
Atom
.
LeafAtom
>
moovChildren
=
moov
.
leafChildren
;
int
moovChildrenSize
=
moovChildren
.
size
();
DrmInitData
.
Mapped
drmInitData
=
null
;
for
(
int
i
=
0
;
i
<
moovChildrenSize
;
i
++)
{
LeafAtom
child
=
moovChildren
.
get
(
i
);
if
(
child
.
type
==
Atom
.
TYPE_pssh
)
{
ParsableByteArray
psshAtom
=
child
.
data
;
psshAtom
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
);
UUID
uuid
=
new
UUID
(
psshAtom
.
readLong
(),
psshAtom
.
readLong
());
int
dataSize
=
psshAtom
.
readInt
();
byte
[]
data
=
new
byte
[
dataSize
];
psshAtom
.
readBytes
(
data
,
0
,
dataSize
);
if
(
drmInitData
==
null
)
{
drmInitData
=
new
DrmInitData
.
Mapped
(
MimeTypes
.
VIDEO_MP4
);
}
drmInitData
.
put
(
uuid
,
data
);
}
}
if
(
drmInitData
!=
null
)
{
extractorOutput
.
drmInitData
(
drmInitData
);
}
ContainerAtom
mvex
=
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_mvex
);
extendsDefaults
=
parseTrex
(
mvex
.
getLeafAtomOfType
(
Atom
.
TYPE_trex
).
data
);
track
=
AtomParsers
.
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
),
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_mvhd
));
Assertions
.
checkState
(
track
!=
null
);
trackOutput
.
format
(
track
.
mediaFormat
);
}
private
void
onMoofContainerAtomRead
(
ContainerAtom
moof
)
{
fragmentRun
.
reset
();
parseMoof
(
track
,
extendsDefaults
,
moof
,
fragmentRun
,
workaroundFlags
,
extendedTypeScratch
);
sampleIndex
=
0
;
}
/**
* Parses a trex atom (defined in 14496-12).
*/
private
static
DefaultSampleValues
parseTrex
(
ParsableByteArray
trex
)
{
trex
.
setPosition
(
Atom
.
FULL_HEADER_SIZE
+
4
);
int
defaultSampleDescriptionIndex
=
trex
.
readUnsignedIntToInt
()
-
1
;
int
defaultSampleDuration
=
trex
.
readUnsignedIntToInt
();
int
defaultSampleSize
=
trex
.
readUnsignedIntToInt
();
int
defaultSampleFlags
=
trex
.
readInt
();
return
new
DefaultSampleValues
(
defaultSampleDescriptionIndex
,
defaultSampleDuration
,
defaultSampleSize
,
defaultSampleFlags
);
}
private
static
void
parseMoof
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
ContainerAtom
moof
,
TrackFragment
out
,
int
workaroundFlags
,
byte
[]
extendedTypeScratch
)
{
parseTraf
(
track
,
extendsDefaults
,
moof
.
getContainerAtomOfType
(
Atom
.
TYPE_traf
),
out
,
workaroundFlags
,
extendedTypeScratch
);
}
/**
* Parses a traf atom (defined in 14496-12).
*/
private
static
void
parseTraf
(
Track
track
,
DefaultSampleValues
extendsDefaults
,
ContainerAtom
traf
,
TrackFragment
out
,
int
workaroundFlags
,
byte
[]
extendedTypeScratch
)
{
LeafAtom
tfdtAtom
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfdt
);
long
decodeTime
=
tfdtAtom
==
null
?
0
:
parseTfdt
(
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfdt
).
data
);
LeafAtom
tfhd
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_tfhd
);
DefaultSampleValues
fragmentHeader
=
parseTfhd
(
extendsDefaults
,
tfhd
.
data
);
out
.
sampleDescriptionIndex
=
fragmentHeader
.
sampleDescriptionIndex
;
LeafAtom
trun
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_trun
);
parseTrun
(
track
,
fragmentHeader
,
decodeTime
,
workaroundFlags
,
trun
.
data
,
out
);
LeafAtom
saiz
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_saiz
);
if
(
saiz
!=
null
)
{
TrackEncryptionBox
trackEncryptionBox
=
track
.
sampleDescriptionEncryptionBoxes
[
fragmentHeader
.
sampleDescriptionIndex
];
parseSaiz
(
trackEncryptionBox
,
saiz
.
data
,
out
);
}
LeafAtom
senc
=
traf
.
getLeafAtomOfType
(
Atom
.
TYPE_senc
);
if
(
senc
!=
null
)
{
parseSenc
(
senc
.
data
,
out
);
}
int
childrenSize
=
traf
.
leafChildren
.
size
();
for
(
int
i
=
0
;
i
<
childrenSize
;
i
++)
{
LeafAtom
atom
=
traf
.
leafChildren
.
get
(
i
);
if
(
atom
.
type
==
Atom
.
TYPE_uuid
)
{
parseUuid
(
atom
.
data
,
out
,
extendedTypeScratch
);
}
}
}
private
static
void
parseSaiz
(
TrackEncryptionBox
encryptionBox
,
ParsableByteArray
saiz
,
TrackFragment
out
)
{
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
saiz
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
saiz
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
if
((
flags
&
0x01
)
==
1
)
{
saiz
.
skip
(
8
);
}
int
defaultSampleInfoSize
=
saiz
.
readUnsignedByte
();
int
sampleCount
=
saiz
.
readUnsignedIntToInt
();
if
(
sampleCount
!=
out
.
length
)
{
throw
new
IllegalStateException
(
"Length mismatch: "
+
sampleCount
+
", "
+
out
.
length
);
}
int
totalSize
=
0
;
if
(
defaultSampleInfoSize
==
0
)
{
boolean
[]
sampleHasSubsampleEncryptionTable
=
out
.
sampleHasSubsampleEncryptionTable
;
for
(
int
i
=
0
;
i
<
sampleCount
;
i
++)
{
int
sampleInfoSize
=
saiz
.
readUnsignedByte
();
totalSize
+=
sampleInfoSize
;
sampleHasSubsampleEncryptionTable
[
i
]
=
sampleInfoSize
>
vectorSize
;
}
}
else
{
boolean
subsampleEncryption
=
defaultSampleInfoSize
>
vectorSize
;
totalSize
+=
defaultSampleInfoSize
*
sampleCount
;
Arrays
.
fill
(
out
.
sampleHasSubsampleEncryptionTable
,
0
,
sampleCount
,
subsampleEncryption
);
}
out
.
initEncryptionData
(
totalSize
);
}
/**
* Parses a tfhd atom (defined in 14496-12).
*
* @param extendsDefaults Default sample values from the trex atom.
* @return The parsed default sample values.
*/
private
static
DefaultSampleValues
parseTfhd
(
DefaultSampleValues
extendsDefaults
,
ParsableByteArray
tfhd
)
{
tfhd
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
tfhd
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
tfhd
.
skip
(
4
);
// trackId
if
((
flags
&
0x01
/* base_data_offset_present */
)
!=
0
)
{
tfhd
.
skip
(
8
);
}
int
defaultSampleDescriptionIndex
=
((
flags
&
0x02
/* default_sample_description_index_present */
)
!=
0
)
?
tfhd
.
readUnsignedIntToInt
()
-
1
:
extendsDefaults
.
sampleDescriptionIndex
;
int
defaultSampleDuration
=
((
flags
&
0x08
/* default_sample_duration_present */
)
!=
0
)
?
tfhd
.
readUnsignedIntToInt
()
:
extendsDefaults
.
duration
;
int
defaultSampleSize
=
((
flags
&
0x10
/* default_sample_size_present */
)
!=
0
)
?
tfhd
.
readUnsignedIntToInt
()
:
extendsDefaults
.
size
;
int
defaultSampleFlags
=
((
flags
&
0x20
/* default_sample_flags_present */
)
!=
0
)
?
tfhd
.
readUnsignedIntToInt
()
:
extendsDefaults
.
flags
;
return
new
DefaultSampleValues
(
defaultSampleDescriptionIndex
,
defaultSampleDuration
,
defaultSampleSize
,
defaultSampleFlags
);
}
/**
* Parses a tfdt atom (defined in 14496-12).
*
* @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the
* media, expressed in the media's timescale.
*/
private
static
long
parseTfdt
(
ParsableByteArray
tfdt
)
{
tfdt
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
tfdt
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
return
version
==
1
?
tfdt
.
readUnsignedLongToLong
()
:
tfdt
.
readUnsignedInt
();
}
/**
* Parses a trun atom (defined in 14496-12).
*
* @param track The corresponding track.
* @param defaultSampleValues Default sample values.
* @param decodeTime The decode time.
* @param trun The trun atom to parse.
* @param out The {@TrackFragment} into which parsed data should be placed.
*/
private
static
void
parseTrun
(
Track
track
,
DefaultSampleValues
defaultSampleValues
,
long
decodeTime
,
int
workaroundFlags
,
ParsableByteArray
trun
,
TrackFragment
out
)
{
trun
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
trun
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
int
sampleCount
=
trun
.
readUnsignedIntToInt
();
if
((
flags
&
0x01
/* data_offset_present */
)
!=
0
)
{
trun
.
skip
(
4
);
}
boolean
firstSampleFlagsPresent
=
(
flags
&
0x04
/* first_sample_flags_present */
)
!=
0
;
int
firstSampleFlags
=
defaultSampleValues
.
flags
;
if
(
firstSampleFlagsPresent
)
{
firstSampleFlags
=
trun
.
readUnsignedIntToInt
();
}
boolean
sampleDurationsPresent
=
(
flags
&
0x100
/* sample_duration_present */
)
!=
0
;
boolean
sampleSizesPresent
=
(
flags
&
0x200
/* sample_size_present */
)
!=
0
;
boolean
sampleFlagsPresent
=
(
flags
&
0x400
/* sample_flags_present */
)
!=
0
;
boolean
sampleCompositionTimeOffsetsPresent
=
(
flags
&
0x800
/* sample_composition_time_offsets_present */
)
!=
0
;
out
.
initTables
(
sampleCount
);
int
[]
sampleSizeTable
=
out
.
sampleSizeTable
;
int
[]
sampleCompositionTimeOffsetTable
=
out
.
sampleCompositionTimeOffsetTable
;
long
[]
sampleDecodingTimeTable
=
out
.
sampleDecodingTimeTable
;
boolean
[]
sampleIsSyncFrameTable
=
out
.
sampleIsSyncFrameTable
;
long
timescale
=
track
.
timescale
;
long
cumulativeTime
=
decodeTime
;
boolean
workaroundEveryVideoFrameIsSyncFrame
=
track
.
type
==
Track
.
TYPE_VIDEO
&&
((
workaroundFlags
&
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
)
==
WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
);
for
(
int
i
=
0
;
i
<
sampleCount
;
i
++)
{
// Use trun values if present, otherwise tfhd, otherwise trex.
int
sampleDuration
=
sampleDurationsPresent
?
trun
.
readUnsignedIntToInt
()
:
defaultSampleValues
.
duration
;
int
sampleSize
=
sampleSizesPresent
?
trun
.
readUnsignedIntToInt
()
:
defaultSampleValues
.
size
;
int
sampleFlags
=
(
i
==
0
&&
firstSampleFlagsPresent
)
?
firstSampleFlags
:
sampleFlagsPresent
?
trun
.
readInt
()
:
defaultSampleValues
.
flags
;
if
(
sampleCompositionTimeOffsetsPresent
)
{
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
// version 0 trun boxes, however a significant number of streams violate the spec and use
// signed integers instead. It's safe to always parse sample offsets as signed integers
// here, because unsigned integers will still be parsed correctly (unless their top bit is
// set, which is never true in practice because sample offsets are always small).
int
sampleOffset
=
trun
.
readInt
();
sampleCompositionTimeOffsetTable
[
i
]
=
(
int
)
((
sampleOffset
*
1000
)
/
timescale
);
}
else
{
sampleCompositionTimeOffsetTable
[
i
]
=
0
;
}
sampleDecodingTimeTable
[
i
]
=
(
cumulativeTime
*
1000
)
/
timescale
;
sampleSizeTable
[
i
]
=
sampleSize
;
sampleIsSyncFrameTable
[
i
]
=
((
sampleFlags
>>
16
)
&
0x1
)
==
0
&&
(!
workaroundEveryVideoFrameIsSyncFrame
||
i
==
0
);
cumulativeTime
+=
sampleDuration
;
}
}
private
static
void
parseUuid
(
ParsableByteArray
uuid
,
TrackFragment
out
,
byte
[]
extendedTypeScratch
)
{
uuid
.
setPosition
(
Atom
.
HEADER_SIZE
);
uuid
.
readBytes
(
extendedTypeScratch
,
0
,
16
);
// Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
if
(!
Arrays
.
equals
(
extendedTypeScratch
,
PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE
))
{
return
;
}
// Except for the extended type, this box is identical to a SENC box. See "Portable encoding of
// audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al,
// Section 5.3.2.1."
parseSenc
(
uuid
,
16
,
out
);
}
private
static
void
parseSenc
(
ParsableByteArray
senc
,
TrackFragment
out
)
{
parseSenc
(
senc
,
0
,
out
);
}
private
static
void
parseSenc
(
ParsableByteArray
senc
,
int
offset
,
TrackFragment
out
)
{
senc
.
setPosition
(
Atom
.
HEADER_SIZE
+
offset
);
int
fullAtom
=
senc
.
readInt
();
int
flags
=
Atom
.
parseFullAtomFlags
(
fullAtom
);
if
((
flags
&
0x01
/* override_track_encryption_box_parameters */
)
!=
0
)
{
// TODO: Implement this.
throw
new
IllegalStateException
(
"Overriding TrackEncryptionBox parameters is unsupported"
);
}
boolean
subsampleEncryption
=
(
flags
&
0x02
/* use_subsample_encryption */
)
!=
0
;
int
sampleCount
=
senc
.
readUnsignedIntToInt
();
if
(
sampleCount
!=
out
.
length
)
{
throw
new
IllegalStateException
(
"Length mismatch: "
+
sampleCount
+
", "
+
out
.
length
);
}
Arrays
.
fill
(
out
.
sampleHasSubsampleEncryptionTable
,
0
,
sampleCount
,
subsampleEncryption
);
out
.
initEncryptionData
(
senc
.
bytesLeft
());
out
.
fillEncryptionData
(
senc
);
}
/**
* Parses a sidx atom (defined in 14496-12).
*/
private
static
ChunkIndex
parseSidx
(
ParsableByteArray
atom
,
long
inputPosition
)
{
atom
.
setPosition
(
Atom
.
HEADER_SIZE
);
int
fullAtom
=
atom
.
readInt
();
int
version
=
Atom
.
parseFullAtomVersion
(
fullAtom
);
atom
.
skip
(
4
);
long
timescale
=
atom
.
readUnsignedInt
();
long
earliestPresentationTime
;
long
offset
=
inputPosition
;
if
(
version
==
0
)
{
earliestPresentationTime
=
atom
.
readUnsignedInt
();
offset
+=
atom
.
readUnsignedInt
();
}
else
{
earliestPresentationTime
=
atom
.
readUnsignedLongToLong
();
offset
+=
atom
.
readUnsignedLongToLong
();
}
atom
.
skip
(
2
);
int
referenceCount
=
atom
.
readUnsignedShort
();
int
[]
sizes
=
new
int
[
referenceCount
];
long
[]
offsets
=
new
long
[
referenceCount
];
long
[]
durationsUs
=
new
long
[
referenceCount
];
long
[]
timesUs
=
new
long
[
referenceCount
];
long
time
=
earliestPresentationTime
;
long
timeUs
=
Util
.
scaleLargeTimestamp
(
time
,
C
.
MICROS_PER_SECOND
,
timescale
);
for
(
int
i
=
0
;
i
<
referenceCount
;
i
++)
{
int
firstInt
=
atom
.
readInt
();
int
type
=
0x80000000
&
firstInt
;
if
(
type
!=
0
)
{
throw
new
IllegalStateException
(
"Unhandled indirect reference"
);
}
long
referenceDuration
=
atom
.
readUnsignedInt
();
sizes
[
i
]
=
0x7fffffff
&
firstInt
;
offsets
[
i
]
=
offset
;
// Calculate time and duration values such that any rounding errors are consistent. i.e. That
// timesUs[i] + durationsUs[i] == timesUs[i + 1].
timesUs
[
i
]
=
timeUs
;
time
+=
referenceDuration
;
timeUs
=
Util
.
scaleLargeTimestamp
(
time
,
C
.
MICROS_PER_SECOND
,
timescale
);
durationsUs
[
i
]
=
timeUs
-
timesUs
[
i
];
atom
.
skip
(
4
);
offset
+=
sizes
[
i
];
}
return
new
ChunkIndex
(
sizes
,
offsets
,
durationsUs
,
timesUs
);
}
private
void
readEncryptionData
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
fragmentRun
.
fillEncryptionData
(
input
);
parserState
=
STATE_READING_SAMPLE_START
;
}
/**
* Attempts to extract the next sample in the current mdat atom.
* <p>
* If there are no more samples in the current mdat atom then the parser state is transitioned
* to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
* <p>
* It is possible for a sample to be extracted in part in the case that an exception is thrown. In
* this case the method can be called again to extract the remainder of the sample.
*
* @param input The {@link ExtractorInput} from which to read data.
* @return True if a sample was extracted. False otherwise.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
private
boolean
readSample
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
if
(
sampleIndex
>=
fragmentRun
.
length
)
{
// We've run out of samples in the current mdat atom.
parserState
=
STATE_READING_ATOM_HEADER
;
return
false
;
}
if
(
parserState
==
STATE_READING_SAMPLE_START
)
{
sampleSize
=
fragmentRun
.
sampleSizeTable
[
sampleIndex
];
if
(
fragmentRun
.
definesEncryptionData
)
{
sampleBytesWritten
=
appendSampleEncryptionData
(
fragmentRun
.
sampleEncryptionData
);
sampleSize
+=
sampleBytesWritten
;
}
else
{
sampleBytesWritten
=
0
;
}
sampleCurrentNalBytesRemaining
=
0
;
parserState
=
STATE_READING_SAMPLE_CONTINUE
;
}
if
(
track
.
type
==
Track
.
TYPE_VIDEO
)
{
while
(
sampleBytesWritten
<
sampleSize
)
{
// NAL units are length delimited, but the decoder requires start code delimited units.
if
(
sampleCurrentNalBytesRemaining
==
0
)
{
// Read the NAL length so that we know where we find the next NAL unit.
input
.
readFully
(
nalLength
.
data
,
0
,
4
);
nalLength
.
setPosition
(
0
);
sampleCurrentNalBytesRemaining
=
nalLength
.
readUnsignedIntToInt
();
// Write a start code for the current NAL unit.
nalStartCode
.
setPosition
(
0
);
trackOutput
.
sampleData
(
nalStartCode
,
4
);
sampleBytesWritten
+=
4
;
}
else
{
// Write the payload of the NAL unit.
int
writtenBytes
=
trackOutput
.
sampleData
(
input
,
sampleCurrentNalBytesRemaining
);
sampleBytesWritten
+=
writtenBytes
;
sampleCurrentNalBytesRemaining
-=
writtenBytes
;
}
}
}
else
{
while
(
sampleBytesWritten
<
sampleSize
)
{
int
writtenBytes
=
trackOutput
.
sampleData
(
input
,
sampleSize
-
sampleBytesWritten
);
sampleBytesWritten
+=
writtenBytes
;
}
}
long
sampleTimeUs
=
fragmentRun
.
getSamplePresentationTime
(
sampleIndex
)
*
1000L
;
int
sampleFlags
=
(
fragmentRun
.
definesEncryptionData
?
C
.
SAMPLE_FLAG_ENCRYPTED
:
0
)
|
(
fragmentRun
.
sampleIsSyncFrameTable
[
sampleIndex
]
?
C
.
SAMPLE_FLAG_SYNC
:
0
);
byte
[]
encryptionKey
=
fragmentRun
.
definesEncryptionData
?
track
.
sampleDescriptionEncryptionBoxes
[
fragmentRun
.
sampleDescriptionIndex
].
keyId
:
null
;
trackOutput
.
sampleMetadata
(
sampleTimeUs
,
sampleFlags
,
sampleSize
,
0
,
encryptionKey
);
sampleIndex
++;
parserState
=
STATE_READING_SAMPLE_START
;
return
true
;
}
private
int
appendSampleEncryptionData
(
ParsableByteArray
sampleEncryptionData
)
{
TrackEncryptionBox
encryptionBox
=
track
.
sampleDescriptionEncryptionBoxes
[
fragmentRun
.
sampleDescriptionIndex
];
int
vectorSize
=
encryptionBox
.
initializationVectorSize
;
boolean
subsampleEncryption
=
fragmentRun
.
sampleHasSubsampleEncryptionTable
[
sampleIndex
];
// Write the signal byte, containing the vector size and the subsample encryption flag.
encryptionSignalByte
.
data
[
0
]
=
(
byte
)
(
vectorSize
|
(
subsampleEncryption
?
0x80
:
0
));
encryptionSignalByte
.
setPosition
(
0
);
trackOutput
.
sampleData
(
encryptionSignalByte
,
1
);
// Write the vector.
trackOutput
.
sampleData
(
sampleEncryptionData
,
vectorSize
);
// If we don't have subsample encryption data, we're done.
if
(!
subsampleEncryption
)
{
return
1
+
vectorSize
;
}
// Write the subsample encryption data.
int
subsampleCount
=
sampleEncryptionData
.
readUnsignedShort
();
sampleEncryptionData
.
skip
(-
2
);
int
subsampleDataLength
=
2
+
6
*
subsampleCount
;
trackOutput
.
sampleData
(
sampleEncryptionData
,
subsampleDataLength
);
return
1
+
vectorSize
+
subsampleDataLength
;
}
/** Returns whether the extractor should parse an atom with type {@code atom}. */
private
static
boolean
shouldParseAtom
(
int
atom
)
{
return
atom
==
Atom
.
TYPE_avc1
||
atom
==
Atom
.
TYPE_avc3
||
atom
==
Atom
.
TYPE_esds
||
atom
==
Atom
.
TYPE_hdlr
||
atom
==
Atom
.
TYPE_mdat
||
atom
==
Atom
.
TYPE_mdhd
||
atom
==
Atom
.
TYPE_moof
||
atom
==
Atom
.
TYPE_moov
||
atom
==
Atom
.
TYPE_mp4a
||
atom
==
Atom
.
TYPE_mvhd
||
atom
==
Atom
.
TYPE_sidx
||
atom
==
Atom
.
TYPE_stsd
||
atom
==
Atom
.
TYPE_tfdt
||
atom
==
Atom
.
TYPE_tfhd
||
atom
==
Atom
.
TYPE_tkhd
||
atom
==
Atom
.
TYPE_traf
||
atom
==
Atom
.
TYPE_trak
||
atom
==
Atom
.
TYPE_trex
||
atom
==
Atom
.
TYPE_trun
||
atom
==
Atom
.
TYPE_mvex
||
atom
==
Atom
.
TYPE_mdia
||
atom
==
Atom
.
TYPE_minf
||
atom
==
Atom
.
TYPE_stbl
||
atom
==
Atom
.
TYPE_pssh
||
atom
==
Atom
.
TYPE_saiz
||
atom
==
Atom
.
TYPE_uuid
||
atom
==
Atom
.
TYPE_senc
||
atom
==
Atom
.
TYPE_pasp
;
}
/** Returns whether the extractor should parse a container atom with type {@code atom}. */
private
static
boolean
shouldParseContainerAtom
(
int
atom
)
{
return
atom
==
Atom
.
TYPE_moov
||
atom
==
Atom
.
TYPE_trak
||
atom
==
Atom
.
TYPE_mdia
||
atom
==
Atom
.
TYPE_minf
||
atom
==
Atom
.
TYPE_stbl
||
atom
==
Atom
.
TYPE_avcC
||
atom
==
Atom
.
TYPE_moof
||
atom
==
Atom
.
TYPE_traf
||
atom
==
Atom
.
TYPE_mvex
;
}
}
library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java
0 → 100644
View file @
587edf8e
/*
* 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
.
mp4
;
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.PositionHolder
;
import
com.google.android.exoplayer.extractor.SeekMap
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.extractor.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.H264Util
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Stack
;
/**
* Extracts data from an unfragmented MP4 file.
*/
public
final
class
Mp4Extractor
implements
Extractor
,
SeekMap
{
// Parser states.
private
static
final
int
STATE_READING_ATOM_HEADER
=
0
;
private
static
final
int
STATE_READING_ATOM_PAYLOAD
=
1
;
private
static
final
int
STATE_READING_SAMPLE
=
2
;
/**
* When seeking within the source, if the offset is greater than or equal to this value (or the
* offset is negative), the source will be reloaded.
*/
private
static
final
int
RELOAD_MINIMUM_SEEK_DISTANCE
=
256
*
1024
;
// Temporary arrays.
private
final
ParsableByteArray
nalStartCode
;
private
final
ParsableByteArray
nalLength
;
private
final
ParsableByteArray
atomHeader
;
private
final
Stack
<
ContainerAtom
>
containerAtoms
;
private
int
parserState
;
private
long
rootAtomBytesRead
;
private
int
atomType
;
private
long
atomSize
;
private
int
atomBytesRead
;
private
ParsableByteArray
atomData
;
private
int
sampleSize
;
private
int
sampleBytesWritten
;
private
int
sampleCurrentNalBytesRemaining
;
// Extractor outputs.
private
ExtractorOutput
extractorOutput
;
private
Mp4Track
[]
tracks
;
public
Mp4Extractor
()
{
atomHeader
=
new
ParsableByteArray
(
Atom
.
LONG_HEADER_SIZE
);
containerAtoms
=
new
Stack
<
Atom
.
ContainerAtom
>();
nalStartCode
=
new
ParsableByteArray
(
H264Util
.
NAL_START_CODE
);
nalLength
=
new
ParsableByteArray
(
4
);
parserState
=
STATE_READING_ATOM_HEADER
;
}
@Override
public
void
init
(
ExtractorOutput
output
)
{
extractorOutput
=
output
;
}
@Override
public
void
seek
()
{
rootAtomBytesRead
=
0
;
sampleBytesWritten
=
0
;
sampleCurrentNalBytesRemaining
=
0
;
}
@Override
public
int
read
(
ExtractorInput
input
,
PositionHolder
seekPosition
)
throws
IOException
,
InterruptedException
{
while
(
true
)
{
switch
(
parserState
)
{
case
STATE_READING_ATOM_HEADER:
if
(!
readAtomHeader
(
input
))
{
return
RESULT_END_OF_INPUT
;
}
break
;
case
STATE_READING_ATOM_PAYLOAD:
if
(
readAtomPayload
(
input
,
seekPosition
))
{
return
RESULT_SEEK
;
}
break
;
default
:
return
readSample
(
input
,
seekPosition
);
}
}
}
// SeekMap implementation.
@Override
public
long
getPosition
(
long
timeUs
)
{
long
earliestSamplePosition
=
Long
.
MAX_VALUE
;
for
(
int
trackIndex
=
0
;
trackIndex
<
tracks
.
length
;
trackIndex
++)
{
TrackSampleTable
sampleTable
=
tracks
[
trackIndex
].
sampleTable
;
int
sampleIndex
=
sampleTable
.
getIndexOfEarlierOrEqualSynchronizationSample
(
timeUs
);
if
(
sampleIndex
==
TrackSampleTable
.
NO_SAMPLE
)
{
sampleIndex
=
sampleTable
.
getIndexOfLaterOrEqualSynchronizationSample
(
timeUs
);
}
tracks
[
trackIndex
].
sampleIndex
=
sampleIndex
;
long
offset
=
sampleTable
.
offsets
[
tracks
[
trackIndex
].
sampleIndex
];
if
(
offset
<
earliestSamplePosition
)
{
earliestSamplePosition
=
offset
;
}
}
return
earliestSamplePosition
;
}
private
boolean
readAtomHeader
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
if
(!
input
.
readFully
(
atomHeader
.
data
,
0
,
Atom
.
HEADER_SIZE
,
true
))
{
return
false
;
}
atomHeader
.
setPosition
(
0
);
atomSize
=
atomHeader
.
readUnsignedInt
();
atomType
=
atomHeader
.
readInt
();
if
(
atomSize
==
Atom
.
LONG_SIZE_PREFIX
)
{
// The extended atom size is contained in the next 8 bytes, so try to read it now.
input
.
readFully
(
atomHeader
.
data
,
Atom
.
HEADER_SIZE
,
Atom
.
LONG_HEADER_SIZE
-
Atom
.
HEADER_SIZE
);
atomSize
=
atomHeader
.
readLong
();
rootAtomBytesRead
+=
Atom
.
LONG_HEADER_SIZE
;
atomBytesRead
=
Atom
.
LONG_HEADER_SIZE
;
}
else
{
rootAtomBytesRead
+=
Atom
.
HEADER_SIZE
;
atomBytesRead
=
Atom
.
HEADER_SIZE
;
}
if
(
shouldParseContainerAtom
(
atomType
))
{
if
(
atomSize
==
Atom
.
LONG_SIZE_PREFIX
)
{
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
rootAtomBytesRead
+
atomSize
-
atomBytesRead
));
}
else
{
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
rootAtomBytesRead
+
atomSize
-
atomBytesRead
));
}
parserState
=
STATE_READING_ATOM_HEADER
;
}
else
if
(
shouldParseLeafAtom
(
atomType
))
{
Assertions
.
checkState
(
atomSize
<
Integer
.
MAX_VALUE
);
atomData
=
new
ParsableByteArray
((
int
)
atomSize
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
Atom
.
HEADER_SIZE
);
parserState
=
STATE_READING_ATOM_PAYLOAD
;
}
else
{
atomData
=
null
;
parserState
=
STATE_READING_ATOM_PAYLOAD
;
}
return
true
;
}
/**
* Processes the atom payload. If {@link #atomData} is null and the size is at or above the
* threshold {@link #RELOAD_MINIMUM_SEEK_DISTANCE}, {@code true} is returned and the caller should
* restart loading at the position in {@code positionHolder}. Otherwise, the atom is read/skipped.
*/
private
boolean
readAtomPayload
(
ExtractorInput
input
,
PositionHolder
positionHolder
)
throws
IOException
,
InterruptedException
{
parserState
=
STATE_READING_ATOM_HEADER
;
rootAtomBytesRead
+=
atomSize
-
atomBytesRead
;
long
atomRemainingBytes
=
atomSize
-
atomBytesRead
;
boolean
seekRequired
=
atomData
==
null
&&
(
atomSize
>=
RELOAD_MINIMUM_SEEK_DISTANCE
||
atomSize
>
Integer
.
MAX_VALUE
);
if
(
seekRequired
)
{
positionHolder
.
position
=
rootAtomBytesRead
;
}
else
if
(
atomData
!=
null
)
{
input
.
readFully
(
atomData
.
data
,
atomBytesRead
,
(
int
)
atomRemainingBytes
);
if
(!
containerAtoms
.
isEmpty
())
{
containerAtoms
.
peek
().
add
(
new
Atom
.
LeafAtom
(
atomType
,
atomData
));
}
}
else
{
input
.
skipFully
((
int
)
atomRemainingBytes
);
}
while
(!
containerAtoms
.
isEmpty
()
&&
containerAtoms
.
peek
().
endByteOffset
==
rootAtomBytesRead
)
{
Atom
.
ContainerAtom
containerAtom
=
containerAtoms
.
pop
();
if
(
containerAtom
.
type
==
Atom
.
TYPE_moov
)
{
processMoovAtom
(
containerAtom
);
}
else
if
(!
containerAtoms
.
isEmpty
())
{
containerAtoms
.
peek
().
add
(
containerAtom
);
}
}
return
seekRequired
;
}
/** Updates the stored track metadata to reflect the contents of the specified moov atom. */
private
void
processMoovAtom
(
ContainerAtom
moov
)
{
List
<
Mp4Track
>
tracks
=
new
ArrayList
<
Mp4Track
>();
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
Atom
.
ContainerAtom
atom
=
moov
.
containerChildren
.
get
(
i
);
if
(
atom
.
type
!=
Atom
.
TYPE_trak
)
{
continue
;
}
Track
track
=
AtomParsers
.
parseTrak
(
atom
,
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_mvhd
));
if
(
track
==
null
||
(
track
.
type
!=
Track
.
TYPE_AUDIO
&&
track
.
type
!=
Track
.
TYPE_VIDEO
))
{
continue
;
}
Atom
.
ContainerAtom
stblAtom
=
atom
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
)
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
).
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
TrackSampleTable
trackSampleTable
=
AtomParsers
.
parseStbl
(
track
,
stblAtom
);
if
(
trackSampleTable
.
sampleCount
==
0
)
{
continue
;
}
Mp4Track
mp4Track
=
new
Mp4Track
(
track
,
trackSampleTable
,
extractorOutput
.
track
(
i
));
mp4Track
.
trackOutput
.
format
(
track
.
mediaFormat
);
tracks
.
add
(
mp4Track
);
long
firstSampleOffset
=
trackSampleTable
.
offsets
[
0
];
if
(
firstSampleOffset
<
earliestSampleOffset
)
{
earliestSampleOffset
=
firstSampleOffset
;
}
}
this
.
tracks
=
tracks
.
toArray
(
new
Mp4Track
[
0
]);
extractorOutput
.
endTracks
();
extractorOutput
.
seekMap
(
this
);
parserState
=
STATE_READING_SAMPLE
;
}
/**
* Attempts to extract the next sample in the current mdat atom for the specified track.
* <p>
* Returns {@link #RESULT_SEEK} if the source should be reloaded from the position in
* {@code positionHolder}.
* <p>
* Returns {@link #RESULT_END_OF_INPUT} if no samples are left. Otherwise, returns
* {@link #RESULT_CONTINUE}.
*
* @param input The {@link ExtractorInput} from which to read data.
* @param positionHolder If {@link #RESULT_SEEK} is returned, this holder is updated to hold the
* position of the required data.
* @return One of the {@code RESULT_*} flags in {@link Extractor}.
* @throws IOException If an error occurs reading from the input.
* @throws InterruptedException If the thread is interrupted.
*/
private
int
readSample
(
ExtractorInput
input
,
PositionHolder
positionHolder
)
throws
IOException
,
InterruptedException
{
int
trackIndex
=
getTrackIndexOfEarliestCurrentSample
();
if
(
trackIndex
==
TrackSampleTable
.
NO_SAMPLE
)
{
return
RESULT_END_OF_INPUT
;
}
Mp4Track
track
=
tracks
[
trackIndex
];
int
sampleIndex
=
track
.
sampleIndex
;
long
position
=
track
.
sampleTable
.
offsets
[
sampleIndex
];
long
skipAmount
=
position
-
input
.
getPosition
()
+
sampleBytesWritten
;
if
(
skipAmount
<
0
||
skipAmount
>=
RELOAD_MINIMUM_SEEK_DISTANCE
)
{
positionHolder
.
position
=
position
;
return
RESULT_SEEK
;
}
input
.
skipFully
((
int
)
skipAmount
);
sampleSize
=
track
.
sampleTable
.
sizes
[
sampleIndex
];
if
(
track
.
track
.
type
==
Track
.
TYPE_VIDEO
&&
MimeTypes
.
VIDEO_H264
.
equals
(
track
.
track
.
mediaFormat
.
mimeType
))
{
while
(
sampleBytesWritten
<
sampleSize
)
{
// NAL units are length delimited, but the decoder requires start code delimited units.
if
(
sampleCurrentNalBytesRemaining
==
0
)
{
// Read the NAL length so that we know where we find the next NAL unit.
input
.
readFully
(
nalLength
.
data
,
0
,
4
);
nalLength
.
setPosition
(
0
);
sampleCurrentNalBytesRemaining
=
nalLength
.
readUnsignedIntToInt
();
// Write a start code for the current NAL unit.
nalStartCode
.
setPosition
(
0
);
track
.
trackOutput
.
sampleData
(
nalStartCode
,
4
);
sampleBytesWritten
+=
4
;
}
else
{
// Write the payload of the NAL unit.
int
writtenBytes
=
track
.
trackOutput
.
sampleData
(
input
,
sampleCurrentNalBytesRemaining
);
sampleBytesWritten
+=
writtenBytes
;
sampleCurrentNalBytesRemaining
-=
writtenBytes
;
}
}
}
else
{
while
(
sampleBytesWritten
<
sampleSize
)
{
int
writtenBytes
=
track
.
trackOutput
.
sampleData
(
input
,
sampleSize
-
sampleBytesWritten
);
sampleBytesWritten
+=
writtenBytes
;
sampleCurrentNalBytesRemaining
-=
writtenBytes
;
}
}
track
.
trackOutput
.
sampleMetadata
(
track
.
sampleTable
.
timestampsUs
[
sampleIndex
],
track
.
sampleTable
.
flags
[
sampleIndex
],
sampleSize
,
0
,
null
);
track
.
sampleIndex
++;
sampleBytesWritten
=
0
;
sampleCurrentNalBytesRemaining
=
0
;
return
RESULT_CONTINUE
;
}
/**
* Returns the index of the track that contains the earliest current sample, or
* {@link TrackSampleTable#NO_SAMPLE} if no samples remain.
*/
private
int
getTrackIndexOfEarliestCurrentSample
()
{
int
earliestSampleTrackIndex
=
TrackSampleTable
.
NO_SAMPLE
;
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
for
(
int
trackIndex
=
0
;
trackIndex
<
tracks
.
length
;
trackIndex
++)
{
Mp4Track
track
=
tracks
[
trackIndex
];
int
sampleIndex
=
track
.
sampleIndex
;
if
(
sampleIndex
==
track
.
sampleTable
.
sampleCount
)
{
continue
;
}
long
trackSampleOffset
=
track
.
sampleTable
.
offsets
[
sampleIndex
];
if
(
trackSampleOffset
<
earliestSampleOffset
)
{
earliestSampleOffset
=
trackSampleOffset
;
earliestSampleTrackIndex
=
trackIndex
;
}
}
return
earliestSampleTrackIndex
;
}
/** Returns whether the extractor should parse a leaf atom with type {@code atom}. */
private
static
boolean
shouldParseLeafAtom
(
int
atom
)
{
return
atom
==
Atom
.
TYPE_mdhd
||
atom
==
Atom
.
TYPE_mvhd
||
atom
==
Atom
.
TYPE_hdlr
||
atom
==
Atom
.
TYPE_vmhd
||
atom
==
Atom
.
TYPE_smhd
||
atom
==
Atom
.
TYPE_stsd
||
atom
==
Atom
.
TYPE_avc1
||
atom
==
Atom
.
TYPE_avcC
||
atom
==
Atom
.
TYPE_mp4a
||
atom
==
Atom
.
TYPE_esds
||
atom
==
Atom
.
TYPE_stts
||
atom
==
Atom
.
TYPE_stss
||
atom
==
Atom
.
TYPE_ctts
||
atom
==
Atom
.
TYPE_stsc
||
atom
==
Atom
.
TYPE_stsz
||
atom
==
Atom
.
TYPE_stco
||
atom
==
Atom
.
TYPE_co64
||
atom
==
Atom
.
TYPE_tkhd
;
}
/** Returns whether the extractor should parse a container atom with type {@code atom}. */
private
static
boolean
shouldParseContainerAtom
(
int
atom
)
{
return
atom
==
Atom
.
TYPE_moov
||
atom
==
Atom
.
TYPE_trak
||
atom
==
Atom
.
TYPE_mdia
||
atom
==
Atom
.
TYPE_minf
||
atom
==
Atom
.
TYPE_stbl
;
}
private
static
final
class
Mp4Track
{
public
final
Track
track
;
public
final
TrackSampleTable
sampleTable
;
public
final
TrackOutput
trackOutput
;
public
int
sampleIndex
;
public
Mp4Track
(
Track
track
,
TrackSampleTable
sampleTable
,
TrackOutput
trackOutput
)
{
this
.
track
=
track
;
this
.
sampleTable
=
sampleTable
;
this
.
trackOutput
=
trackOutput
;
}
}
}
library/src/main/java/com/google/android/exoplayer/mp4/Track.java
→
library/src/main/java/com/google/android/exoplayer/
extractor/
mp4/Track.java
View file @
587edf8e
...
...
@@ -13,11 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
extractor
.
mp4
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox
;
/**
* Encapsulates information describing an MP4 track.
...
...
library/src/main/java/com/google/android/exoplayer/
chunk/parse
r/mp4/TrackEncryptionBox.java
→
library/src/main/java/com/google/android/exoplayer/
extracto
r/mp4/TrackEncryptionBox.java
View file @
587edf8e
...
...
@@ -13,11 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
.
parse
r
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
extracto
r
.
mp4
;
/**
* Encapsulates information parsed from a track encryption (tenc) box in an MP4 stream.
*/
// TODO: Make package private.
public
final
class
TrackEncryptionBox
{
/**
...
...
library/src/main/java/com/google/android/exoplayer/
chunk/parse
r/mp4/TrackFragment.java
→
library/src/main/java/com/google/android/exoplayer/
extracto
r/mp4/TrackFragment.java
View file @
587edf8e
...
...
@@ -13,15 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
chunk
.
parse
r
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
extracto
r
.
mp4
;
import
com.google.android.exoplayer.extractor.ExtractorInput
;
import
com.google.android.exoplayer.upstream.NonBlockingInputStream
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.io.IOException
;
/**
* A holder for information corresponding to a single fragment of an mp4 file.
*/
/* package */
final
class
TrackFragment
{
// TODO: Make package private.
public
final
class
TrackFragment
{
public
int
sampleDescriptionIndex
;
...
...
@@ -122,6 +126,17 @@ import com.google.android.exoplayer.util.ParsableByteArray;
}
/**
* Fills {@link #sampleEncryptionData} from the provided input.
*
* @param input An {@link ExtractorInput} from which to read the encryption data.
*/
public
void
fillEncryptionData
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
input
.
readFully
(
sampleEncryptionData
.
data
,
0
,
sampleEncryptionDataLength
);
sampleEncryptionData
.
setPosition
(
0
);
sampleEncryptionDataNeedsFill
=
false
;
}
/**
* Fills {@link #sampleEncryptionData} from the provided source.
*
* @param source A source from which to read the encryption data.
...
...
library/src/main/java/com/google/android/exoplayer/
mp4/Mp4
TrackSampleTable.java
→
library/src/main/java/com/google/android/exoplayer/
extractor/mp4/
TrackSampleTable.java
View file @
587edf8e
...
...
@@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
mp4
;
package
com
.
google
.
android
.
exoplayer
.
extractor
.
mp4
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Util
;
/** Sample table for a track in an MP4 file. */
public
final
class
Mp4
TrackSampleTable
{
public
final
class
TrackSampleTable
{
/** Sample index when no sample is available. */
public
static
final
int
NO_SAMPLE
=
-
1
;
/** Number of samples. */
public
final
int
sampleCount
;
/** Sample offsets in bytes. */
public
final
long
[]
offsets
;
/** Sample sizes in bytes. */
...
...
@@ -34,7 +36,7 @@ public final class Mp4TrackSampleTable {
/** Sample flags. */
public
final
int
[]
flags
;
Mp4
TrackSampleTable
(
TrackSampleTable
(
long
[]
offsets
,
int
[]
sizes
,
long
[]
timestampsUs
,
int
[]
flags
)
{
Assertions
.
checkArgument
(
sizes
.
length
==
timestampsUs
.
length
);
Assertions
.
checkArgument
(
offsets
.
length
==
timestampsUs
.
length
);
...
...
@@ -44,11 +46,7 @@ public final class Mp4TrackSampleTable {
this
.
sizes
=
sizes
;
this
.
timestampsUs
=
timestampsUs
;
this
.
flags
=
flags
;
}
/** Returns the number of samples in the table. */
public
int
getSampleCount
()
{
return
sizes
.
length
;
sampleCount
=
offsets
.
length
;
}
/**
...
...
@@ -65,7 +63,6 @@ public final class Mp4TrackSampleTable {
return
i
;
}
}
return
NO_SAMPLE
;
}
...
...
@@ -83,7 +80,6 @@ public final class Mp4TrackSampleTable {
return
i
;
}
}
return
NO_SAMPLE
;
}
...
...
library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java
View file @
587edf8e
...
...
@@ -18,6 +18,7 @@ package com.google.android.exoplayer.extractor.ts;
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.PositionHolder
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
java.io.IOException
;
...
...
@@ -55,7 +56,8 @@ public class AdtsExtractor implements Extractor {
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
public
int
read
(
ExtractorInput
input
,
PositionHolder
seekPosition
)
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 @
587edf8e
...
...
@@ -19,6 +19,7 @@ import com.google.android.exoplayer.C;
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.PositionHolder
;
import
com.google.android.exoplayer.util.ParsableBitArray
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
...
...
@@ -77,7 +78,8 @@ public final class TsExtractor implements Extractor {
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
public
int
read
(
ExtractorInput
input
,
PositionHolder
seekPosition
)
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/WebmExtractor.java
View file @
587edf8e
...
...
@@ -23,6 +23,7 @@ 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.PositionHolder
;
import
com.google.android.exoplayer.extractor.TrackOutput
;
import
com.google.android.exoplayer.util.LongArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
...
...
@@ -162,7 +163,8 @@ public final class WebmExtractor implements Extractor {
}
@Override
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
public
int
read
(
ExtractorInput
input
,
PositionHolder
seekPosition
)
throws
IOException
,
InterruptedException
{
sampleRead
=
false
;
boolean
inputHasData
=
true
;
while
(!
sampleRead
&&
inputHasData
)
{
...
...
library/src/main/java/com/google/android/exoplayer/hls/HlsExtractorWrapper.java
View file @
587edf8e
...
...
@@ -193,7 +193,8 @@ public final class HlsExtractorWrapper implements ExtractorOutput {
* @throws InterruptedException If the thread was interrupted.
*/
public
int
read
(
ExtractorInput
input
)
throws
IOException
,
InterruptedException
{
int
result
=
extractor
.
read
(
input
);
int
result
=
extractor
.
read
(
input
,
null
);
Assertions
.
checkState
(
result
!=
Extractor
.
RESULT_SEEK
);
return
result
;
}
...
...
library/src/main/java/com/google/android/exoplayer/smoothstreaming/SmoothStreamingChunkSource.java
View file @
587edf8e
...
...
@@ -29,9 +29,9 @@ import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
import
com.google.android.exoplayer.chunk.MediaChunk
;
import
com.google.android.exoplayer.chunk.parser.Extractor
;
import
com.google.android.exoplayer.chunk.parser.mp4.FragmentedMp4Extractor
;
import
com.google.android.exoplayer.chunk.parser.mp4.TrackEncryptionBox
;
import
com.google.android.exoplayer.drm.DrmInitData
;
import
com.google.android.exoplayer.mp4.Track
;
import
com.google.android.exoplayer.extractor.mp4.Track
;
import
com.google.android.exoplayer.extractor.mp4.TrackEncryptionBox
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.ProtectionElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.StreamElement
;
import
com.google.android.exoplayer.smoothstreaming.SmoothStreamingManifest.TrackElement
;
...
...
library/src/main/java/com/google/android/exoplayer/source/Mp4SampleExtractor.java
deleted
100644 → 0
View file @
f002e6a7
/*
* 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
.
source
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.drm.DrmInitData
;
import
com.google.android.exoplayer.mp4.Atom
;
import
com.google.android.exoplayer.mp4.Atom.ContainerAtom
;
import
com.google.android.exoplayer.mp4.CommonMp4AtomParsers
;
import
com.google.android.exoplayer.mp4.Mp4TrackSampleTable
;
import
com.google.android.exoplayer.mp4.Track
;
import
com.google.android.exoplayer.upstream.BufferPool
;
import
com.google.android.exoplayer.upstream.BufferedNonBlockingInputStream
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSourceStream
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.upstream.Loader
;
import
com.google.android.exoplayer.upstream.Loader.Loadable
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.H264Util
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
import
android.util.Log
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.Stack
;
/**
* Extracts data from a {@link DataSpec} in unfragmented MP4 format (ISO 14496-12).
*/
public
final
class
Mp4SampleExtractor
implements
SampleExtractor
,
Loader
.
Callback
{
private
static
final
String
TAG
=
"Mp4SampleExtractor"
;
private
static
final
String
LOADER_THREAD_NAME
=
"Mp4SampleExtractor"
;
private
static
final
int
NO_TRACK
=
-
1
;
// Reading results
private
static
final
int
RESULT_NEED_MORE_DATA
=
1
;
private
static
final
int
RESULT_END_OF_STREAM
=
2
;
// Parser states
private
static
final
int
STATE_READING_ATOM_HEADER
=
0
;
private
static
final
int
STATE_READING_ATOM_PAYLOAD
=
1
;
/** Set of atom types that contain data to be parsed. */
private
static
final
Set
<
Integer
>
LEAF_ATOM_TYPES
=
getAtomTypeSet
(
Atom
.
TYPE_mdhd
,
Atom
.
TYPE_mvhd
,
Atom
.
TYPE_hdlr
,
Atom
.
TYPE_vmhd
,
Atom
.
TYPE_smhd
,
Atom
.
TYPE_stsd
,
Atom
.
TYPE_avc1
,
Atom
.
TYPE_avcC
,
Atom
.
TYPE_mp4a
,
Atom
.
TYPE_esds
,
Atom
.
TYPE_stts
,
Atom
.
TYPE_stss
,
Atom
.
TYPE_ctts
,
Atom
.
TYPE_stsc
,
Atom
.
TYPE_stsz
,
Atom
.
TYPE_stco
,
Atom
.
TYPE_co64
,
Atom
.
TYPE_tkhd
);
/** Set of atom types that contain other atoms that need to be parsed. */
private
static
final
Set
<
Integer
>
CONTAINER_TYPES
=
getAtomTypeSet
(
Atom
.
TYPE_moov
,
Atom
.
TYPE_trak
,
Atom
.
TYPE_mdia
,
Atom
.
TYPE_minf
,
Atom
.
TYPE_stbl
);
/** Default number of times to retry loading data prior to failing. */
private
static
final
int
DEFAULT_LOADABLE_RETRY_COUNT
=
3
;
private
final
DataSource
dataSource
;
private
final
DataSpec
dataSpec
;
private
final
int
readAheadAllocationSize
;
private
final
int
reloadMinimumSeekDistance
;
private
final
int
maximumTrackSampleInterval
;
private
final
int
loadRetryCount
;
private
final
BufferPool
bufferPool
;
private
final
Loader
loader
;
private
final
ParsableByteArray
atomHeader
;
private
final
Stack
<
Atom
.
ContainerAtom
>
containerAtoms
;
private
DataSourceStream
dataSourceStream
;
private
BufferedNonBlockingInputStream
inputStream
;
private
long
inputStreamOffset
;
private
long
rootAtomBytesRead
;
private
boolean
loadCompleted
;
private
int
parserState
;
private
int
atomBytesRead
;
private
int
atomType
;
private
long
atomSize
;
private
ParsableByteArray
atomData
;
private
boolean
prepared
;
private
int
loadErrorCount
;
private
Mp4Track
[]
tracks
;
/** An exception from {@link #inputStream}'s callbacks, or {@code null} if there was no error. */
private
IOException
lastLoadError
;
private
long
loadErrorPosition
;
/** If handling a call to {@link #seekTo}, the new required stream offset, or -1 otherwise. */
private
long
pendingSeekPosition
;
/** If the input stream is being reopened at a new position, the new offset, or -1 otherwise. */
private
long
pendingLoadPosition
;
/**
* Creates a new sample extractor for reading {@code dataSource} and {@code dataSpec} as an
* unfragmented MP4 file with default settings.
*
* <p>The default settings read ahead by 5 MiB, handle maximum offsets between samples at the same
* timestamp in different tracks of 3 MiB and restart loading when seeking forward by >= 256 KiB.
*
* @param dataSource Data source used to read from {@code dataSpec}.
* @param dataSpec Data specification specifying what to read.
*/
public
Mp4SampleExtractor
(
DataSource
dataSource
,
DataSpec
dataSpec
)
{
this
(
dataSource
,
dataSpec
,
5
*
1024
*
1024
,
3
*
1024
*
1024
,
256
*
1024
,
DEFAULT_LOADABLE_RETRY_COUNT
);
}
/**
* Creates a new sample extractor for reading {@code dataSource} and {@code dataSpec} as an
* unfragmented MP4 file.
*
* @param dataSource Data source used to read from {@code dataSpec}.
* @param dataSpec Data specification specifying what to read.
* @param readAheadAllocationSize Size of the allocation that buffers the stream, in bytes. The
* value must exceed the maximum sample size, so that a sample can be read in its entirety.
* @param maximumTrackSampleInterval Size of the buffer that handles reading from any selected
* track. The value should be chosen so that the buffer is as big as the interval in bytes
* between the start of the earliest and the end of the latest sample required to render media
* from all selected tracks, at any timestamp in the data source.
* @param reloadMinimumSeekDistance Determines when {@code dataSource} is reopened while seeking:
* if the number of bytes between the current position and the new position is greater than or
* equal to this value, or the new position is before the current position, loading will
* restart. The value should be set to the number of bytes that can be loaded/consumed from an
* existing connection in the time it takes to start a new connection.
* @param loadableRetryCount The number of times to retry loading if an error occurs.
*/
public
Mp4SampleExtractor
(
DataSource
dataSource
,
DataSpec
dataSpec
,
int
readAheadAllocationSize
,
int
maximumTrackSampleInterval
,
int
reloadMinimumSeekDistance
,
int
loadableRetryCount
)
{
// TODO: Handle minimumTrackSampleInterval specified in time not bytes.
this
.
dataSource
=
Assertions
.
checkNotNull
(
dataSource
);
this
.
dataSpec
=
Assertions
.
checkNotNull
(
dataSpec
);
this
.
readAheadAllocationSize
=
readAheadAllocationSize
;
this
.
maximumTrackSampleInterval
=
maximumTrackSampleInterval
;
this
.
reloadMinimumSeekDistance
=
reloadMinimumSeekDistance
;
this
.
loadRetryCount
=
loadableRetryCount
;
// TODO: Implement Allocator here so it is possible to check there is only one buffer at a time.
bufferPool
=
new
BufferPool
(
readAheadAllocationSize
);
loader
=
new
Loader
(
LOADER_THREAD_NAME
);
atomHeader
=
new
ParsableByteArray
(
Atom
.
LONG_ATOM_HEADER_SIZE
);
containerAtoms
=
new
Stack
<
Atom
.
ContainerAtom
>();
parserState
=
STATE_READING_ATOM_HEADER
;
pendingLoadPosition
=
-
1
;
pendingSeekPosition
=
-
1
;
loadErrorPosition
=
-
1
;
}
@Override
public
boolean
prepare
()
throws
IOException
{
if
(
inputStream
==
null
)
{
loadFromOffset
(
0L
);
}
if
(!
prepared
)
{
if
(
readHeaders
()
&&
!
prepared
)
{
throw
new
IOException
(
"moov atom not found."
);
}
if
(!
prepared
)
{
maybeThrowLoadError
();
}
}
return
prepared
;
}
@Override
public
void
selectTrack
(
int
trackIndex
)
{
Assertions
.
checkState
(
prepared
);
if
(
tracks
[
trackIndex
].
selected
)
{
return
;
}
tracks
[
trackIndex
].
selected
=
true
;
// Get the timestamp of the earliest currently-selected sample.
int
earliestSampleTrackIndex
=
getTrackIndexOfEarliestCurrentSample
();
if
(
earliestSampleTrackIndex
==
NO_TRACK
)
{
tracks
[
trackIndex
].
sampleIndex
=
0
;
return
;
}
if
(
earliestSampleTrackIndex
==
Mp4TrackSampleTable
.
NO_SAMPLE
)
{
tracks
[
trackIndex
].
sampleIndex
=
Mp4TrackSampleTable
.
NO_SAMPLE
;
return
;
}
long
timestampUs
=
tracks
[
earliestSampleTrackIndex
].
sampleTable
.
timestampsUs
[
earliestSampleTrackIndex
];
// Find the latest sync sample in the new track that has an earlier or equal timestamp.
tracks
[
trackIndex
].
sampleIndex
=
tracks
[
trackIndex
].
sampleTable
.
getIndexOfEarlierOrEqualSynchronizationSample
(
timestampUs
);
}
@Override
public
void
deselectTrack
(
int
trackIndex
)
{
Assertions
.
checkState
(
prepared
);
tracks
[
trackIndex
].
selected
=
false
;
}
@Override
public
long
getBufferedPositionUs
()
{
Assertions
.
checkState
(
prepared
);
if
(
pendingLoadPosition
!=
-
1
)
{
return
TrackRenderer
.
UNKNOWN_TIME_US
;
}
if
(
loadCompleted
)
{
return
TrackRenderer
.
END_OF_TRACK_US
;
}
// Get the absolute position to which there is data buffered.
long
bufferedPosition
=
inputStreamOffset
+
inputStream
.
getReadPosition
()
+
inputStream
.
getAvailableByteCount
();
// Find the timestamp of the latest sample that does not exceed the buffered position.
long
latestTimestampBeforeEnd
=
Long
.
MIN_VALUE
;
for
(
int
trackIndex
=
0
;
trackIndex
<
tracks
.
length
;
trackIndex
++)
{
if
(!
tracks
[
trackIndex
].
selected
)
{
continue
;
}
Mp4TrackSampleTable
sampleTable
=
tracks
[
trackIndex
].
sampleTable
;
int
sampleIndex
=
Util
.
binarySearchFloor
(
sampleTable
.
offsets
,
bufferedPosition
,
false
,
true
);
if
(
sampleIndex
>
0
&&
sampleTable
.
offsets
[
sampleIndex
]
+
sampleTable
.
sizes
[
sampleIndex
]
>
bufferedPosition
)
{
sampleIndex
--;
}
// Update the latest timestamp if this is greater.
long
timestamp
=
sampleTable
.
timestampsUs
[
sampleIndex
];
if
(
timestamp
>
latestTimestampBeforeEnd
)
{
latestTimestampBeforeEnd
=
timestamp
;
}
}
return
latestTimestampBeforeEnd
<
0L
?
C
.
UNKNOWN_TIME_US
:
latestTimestampBeforeEnd
;
}
@Override
public
void
seekTo
(
long
positionUs
)
{
Assertions
.
checkState
(
prepared
);
long
earliestSamplePosition
=
Long
.
MAX_VALUE
;
for
(
int
trackIndex
=
0
;
trackIndex
<
tracks
.
length
;
trackIndex
++)
{
if
(!
tracks
[
trackIndex
].
selected
)
{
continue
;
}
Mp4TrackSampleTable
sampleTable
=
tracks
[
trackIndex
].
sampleTable
;
int
sampleIndex
=
sampleTable
.
getIndexOfEarlierOrEqualSynchronizationSample
(
positionUs
);
if
(
sampleIndex
==
Mp4TrackSampleTable
.
NO_SAMPLE
)
{
sampleIndex
=
sampleTable
.
getIndexOfLaterOrEqualSynchronizationSample
(
positionUs
);
}
tracks
[
trackIndex
].
sampleIndex
=
sampleIndex
;
long
offset
=
sampleTable
.
offsets
[
tracks
[
trackIndex
].
sampleIndex
];
if
(
offset
<
earliestSamplePosition
)
{
earliestSamplePosition
=
offset
;
}
}
pendingSeekPosition
=
earliestSamplePosition
;
if
(
pendingLoadPosition
!=
-
1
)
{
loadFromOffset
(
earliestSamplePosition
);
return
;
}
inputStream
.
returnToMark
();
long
earliestOffset
=
inputStreamOffset
+
inputStream
.
getReadPosition
();
long
latestOffset
=
earliestOffset
+
inputStream
.
getAvailableByteCount
();
if
(
earliestSamplePosition
<
earliestOffset
||
earliestSamplePosition
>=
latestOffset
+
reloadMinimumSeekDistance
)
{
loadFromOffset
(
earliestSamplePosition
);
}
}
@Override
public
int
getTrackCount
()
{
Assertions
.
checkState
(
prepared
);
return
tracks
.
length
;
}
@Override
public
MediaFormat
getMediaFormat
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
return
tracks
[
track
].
track
.
mediaFormat
;
}
@Override
public
DrmInitData
getDrmInitData
(
int
track
)
{
return
null
;
}
@Override
public
int
readSample
(
int
trackIndex
,
SampleHolder
sampleHolder
)
throws
IOException
{
Assertions
.
checkState
(
prepared
);
Mp4Track
track
=
tracks
[
trackIndex
];
Assertions
.
checkState
(
track
.
selected
);
int
sampleIndex
=
track
.
sampleIndex
;
// Check for the end of the stream.
if
(
sampleIndex
==
Mp4TrackSampleTable
.
NO_SAMPLE
)
{
// TODO: Should END_OF_STREAM be returned as soon as this track has no more samples, or as
// soon as no tracks have a sample (as implemented here)?
return
hasSampleInAnySelectedTrack
()
?
SampleSource
.
NOTHING_READ
:
SampleSource
.
END_OF_STREAM
;
}
// Return if the input stream will be reopened at the requested position.
if
(
pendingLoadPosition
!=
-
1
)
{
return
SampleSource
.
NOTHING_READ
;
}
// If there was a seek request, try to skip forwards to the requested position.
if
(
pendingSeekPosition
!=
-
1
)
{
int
bytesToSeekPosition
=
(
int
)
(
pendingSeekPosition
-
(
inputStreamOffset
+
inputStream
.
getReadPosition
()));
int
skippedByteCount
=
inputStream
.
skip
(
bytesToSeekPosition
);
if
(
skippedByteCount
==
-
1
)
{
throw
new
IOException
(
"Unexpected end-of-stream while seeking to sample."
);
}
bytesToSeekPosition
-=
skippedByteCount
;
inputStream
.
mark
();
if
(
bytesToSeekPosition
==
0
)
{
pendingSeekPosition
=
-
1
;
}
else
{
maybeThrowLoadError
();
return
SampleSource
.
NOTHING_READ
;
}
}
// Return if the sample offset hasn't been loaded yet.
inputStream
.
returnToMark
();
long
sampleOffset
=
track
.
sampleTable
.
offsets
[
sampleIndex
];
long
seekOffsetLong
=
(
sampleOffset
-
inputStreamOffset
)
-
inputStream
.
getReadPosition
();
Assertions
.
checkState
(
seekOffsetLong
<=
Integer
.
MAX_VALUE
);
int
seekOffset
=
(
int
)
seekOffsetLong
;
if
(
inputStream
.
skip
(
seekOffset
)
!=
seekOffset
)
{
maybeThrowLoadError
();
return
SampleSource
.
NOTHING_READ
;
}
// Return if the sample has been loaded.
int
sampleSize
=
track
.
sampleTable
.
sizes
[
sampleIndex
];
if
(
inputStream
.
getAvailableByteCount
()
<
sampleSize
)
{
maybeThrowLoadError
();
return
SampleSource
.
NOTHING_READ
;
}
if
(
sampleHolder
.
data
==
null
||
sampleHolder
.
data
.
capacity
()
<
sampleSize
)
{
sampleHolder
.
replaceBuffer
(
sampleSize
);
}
ByteBuffer
data
=
sampleHolder
.
data
;
if
(
data
==
null
)
{
inputStream
.
skip
(
sampleSize
);
sampleHolder
.
size
=
0
;
}
else
{
int
bytesRead
=
inputStream
.
read
(
data
,
sampleSize
);
Assertions
.
checkState
(
bytesRead
==
sampleSize
);
if
(
MimeTypes
.
VIDEO_H264
.
equals
(
tracks
[
trackIndex
].
track
.
mediaFormat
.
mimeType
))
{
// The mp4 file contains length-prefixed access units, but the decoder wants start code
// delimited content.
H264Util
.
replaceLengthPrefixesWithAvcStartCodes
(
sampleHolder
.
data
,
sampleSize
);
}
sampleHolder
.
size
=
sampleSize
;
}
// Move the input stream mark forwards if the earliest current sample was just read.
if
(
getTrackIndexOfEarliestCurrentSample
()
==
trackIndex
)
{
inputStream
.
mark
();
}
// TODO: Read encryption data.
sampleHolder
.
timeUs
=
track
.
sampleTable
.
timestampsUs
[
sampleIndex
];
sampleHolder
.
flags
=
track
.
sampleTable
.
flags
[
sampleIndex
];
// Advance to the next sample, checking if this was the last sample.
track
.
sampleIndex
=
sampleIndex
+
1
==
track
.
sampleTable
.
getSampleCount
()
?
Mp4TrackSampleTable
.
NO_SAMPLE
:
sampleIndex
+
1
;
// Reset the loading error counter if we read past the offset at which the error was thrown.
if
(
dataSourceStream
.
getReadPosition
()
>
loadErrorPosition
)
{
loadErrorCount
=
0
;
loadErrorPosition
=
-
1
;
}
return
SampleSource
.
SAMPLE_READ
;
}
@Override
public
void
release
()
{
pendingLoadPosition
=
-
1
;
loader
.
release
();
if
(
inputStream
!=
null
)
{
inputStream
.
close
();
}
}
@Override
public
void
onLoadError
(
Loadable
loadable
,
IOException
exception
)
{
lastLoadError
=
exception
;
loadErrorCount
++;
if
(
loadErrorPosition
==
-
1
)
{
loadErrorPosition
=
dataSourceStream
.
getLoadPosition
();
}
int
delayMs
=
getRetryDelayMs
(
loadErrorCount
);
Log
.
w
(
TAG
,
"Retry loading (delay "
+
delayMs
+
" ms)."
);
loader
.
startLoading
(
dataSourceStream
,
this
,
delayMs
);
}
@Override
public
void
onLoadCompleted
(
Loadable
loadable
)
{
loadCompleted
=
true
;
}
@Override
public
void
onLoadCanceled
(
Loadable
loadable
)
{
if
(
pendingLoadPosition
!=
-
1
)
{
loadFromOffset
(
pendingLoadPosition
);
pendingLoadPosition
=
-
1
;
}
}
private
void
loadFromOffset
(
long
offsetBytes
)
{
inputStreamOffset
=
offsetBytes
;
rootAtomBytesRead
=
offsetBytes
;
if
(
loader
.
isLoading
())
{
// Wait for loading to be canceled before proceeding.
pendingLoadPosition
=
offsetBytes
;
loader
.
cancelLoading
();
return
;
}
if
(
inputStream
!=
null
)
{
inputStream
.
close
();
}
DataSpec
dataSpec
=
new
DataSpec
(
this
.
dataSpec
.
uri
,
offsetBytes
,
C
.
LENGTH_UNBOUNDED
,
this
.
dataSpec
.
key
);
dataSourceStream
=
new
DataSourceStream
(
dataSource
,
dataSpec
,
bufferPool
,
readAheadAllocationSize
);
loader
.
startLoading
(
dataSourceStream
,
this
);
// Wrap the input stream with a buffering stream so that it is possible to read from any track.
inputStream
=
new
BufferedNonBlockingInputStream
(
dataSourceStream
,
maximumTrackSampleInterval
);
loadCompleted
=
false
;
loadErrorCount
=
0
;
loadErrorPosition
=
-
1
;
}
/**
* Returns the index of the track that contains the earliest current sample, or {@link #NO_TRACK}
* if no track is selected, or {@link Mp4TrackSampleTable#NO_SAMPLE} if no samples remain in
* selected tracks.
*/
private
int
getTrackIndexOfEarliestCurrentSample
()
{
int
earliestSampleTrackIndex
=
NO_TRACK
;
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
for
(
int
trackIndex
=
0
;
trackIndex
<
tracks
.
length
;
trackIndex
++)
{
Mp4Track
track
=
tracks
[
trackIndex
];
if
(!
track
.
selected
)
{
continue
;
}
int
sampleIndex
=
track
.
sampleIndex
;
if
(
sampleIndex
==
Mp4TrackSampleTable
.
NO_SAMPLE
)
{
if
(
earliestSampleTrackIndex
==
NO_TRACK
)
{
// A track is selected, but it has no more samples.
earliestSampleTrackIndex
=
Mp4TrackSampleTable
.
NO_SAMPLE
;
}
continue
;
}
long
trackSampleOffset
=
track
.
sampleTable
.
offsets
[
sampleIndex
];
if
(
trackSampleOffset
<
earliestSampleOffset
)
{
earliestSampleOffset
=
trackSampleOffset
;
earliestSampleTrackIndex
=
trackIndex
;
}
}
return
earliestSampleTrackIndex
;
}
private
boolean
hasSampleInAnySelectedTrack
()
{
boolean
hasSample
=
false
;
for
(
int
trackIndex
=
0
;
trackIndex
<
tracks
.
length
;
trackIndex
++)
{
if
(
tracks
[
trackIndex
].
selected
&&
tracks
[
trackIndex
].
sampleIndex
!=
Mp4TrackSampleTable
.
NO_SAMPLE
)
{
hasSample
=
true
;
break
;
}
}
return
hasSample
;
}
/** Reads headers, returning whether the end of the stream was reached. */
private
boolean
readHeaders
()
{
int
results
=
0
;
while
(!
prepared
&&
(
results
&
(
RESULT_NEED_MORE_DATA
|
RESULT_END_OF_STREAM
))
==
0
)
{
switch
(
parserState
)
{
case
STATE_READING_ATOM_HEADER:
results
|=
readAtomHeader
();
break
;
case
STATE_READING_ATOM_PAYLOAD:
results
|=
readAtomPayload
();
break
;
}
}
return
(
results
&
RESULT_END_OF_STREAM
)
!=
0
;
}
private
int
readAtomHeader
()
{
if
(
pendingLoadPosition
!=
-
1
)
{
return
RESULT_NEED_MORE_DATA
;
}
// The size value is either 4 or 8 bytes long (in which case atomSize = Mp4Util.LONG_ATOM_SIZE).
int
remainingBytes
;
if
(
atomSize
!=
Atom
.
LONG_SIZE_PREFIX
)
{
remainingBytes
=
Atom
.
ATOM_HEADER_SIZE
-
atomBytesRead
;
}
else
{
remainingBytes
=
Atom
.
LONG_ATOM_HEADER_SIZE
-
atomBytesRead
;
}
int
bytesRead
=
inputStream
.
read
(
atomHeader
.
data
,
atomBytesRead
,
remainingBytes
);
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_STREAM
;
}
rootAtomBytesRead
+=
bytesRead
;
atomBytesRead
+=
bytesRead
;
if
(
atomBytesRead
<
Atom
.
ATOM_HEADER_SIZE
||
(
atomSize
==
Atom
.
LONG_SIZE_PREFIX
&&
atomBytesRead
<
Atom
.
LONG_ATOM_HEADER_SIZE
))
{
return
RESULT_NEED_MORE_DATA
;
}
atomHeader
.
setPosition
(
0
);
atomSize
=
atomHeader
.
readUnsignedInt
();
atomType
=
atomHeader
.
readInt
();
if
(
atomSize
==
Atom
.
LONG_SIZE_PREFIX
)
{
// The extended atom size is contained in the next 8 bytes, so try to read it now.
if
(
atomBytesRead
<
Atom
.
LONG_ATOM_HEADER_SIZE
)
{
return
readAtomHeader
();
}
atomSize
=
atomHeader
.
readLong
();
}
Integer
atomTypeInteger
=
atomType
;
// Avoids boxing atomType twice.
if
(
CONTAINER_TYPES
.
contains
(
atomTypeInteger
))
{
if
(
atomSize
==
Atom
.
LONG_SIZE_PREFIX
)
{
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
rootAtomBytesRead
+
atomSize
-
Atom
.
LONG_ATOM_HEADER_SIZE
));
}
else
{
containerAtoms
.
add
(
new
ContainerAtom
(
atomType
,
rootAtomBytesRead
+
atomSize
-
Atom
.
ATOM_HEADER_SIZE
));
}
enterState
(
STATE_READING_ATOM_HEADER
);
}
else
if
(
LEAF_ATOM_TYPES
.
contains
(
atomTypeInteger
))
{
Assertions
.
checkState
(
atomSize
<=
Integer
.
MAX_VALUE
);
atomData
=
new
ParsableByteArray
((
int
)
atomSize
);
System
.
arraycopy
(
atomHeader
.
data
,
0
,
atomData
.
data
,
0
,
Atom
.
ATOM_HEADER_SIZE
);
enterState
(
STATE_READING_ATOM_PAYLOAD
);
}
else
{
atomData
=
null
;
enterState
(
STATE_READING_ATOM_PAYLOAD
);
}
return
0
;
}
private
int
readAtomPayload
()
{
int
bytesRead
;
if
(
atomData
!=
null
)
{
bytesRead
=
inputStream
.
read
(
atomData
.
data
,
atomBytesRead
,
(
int
)
atomSize
-
atomBytesRead
);
}
else
{
if
(
atomSize
>=
reloadMinimumSeekDistance
||
atomSize
>
Integer
.
MAX_VALUE
)
{
loadFromOffset
(
rootAtomBytesRead
+
atomSize
-
atomBytesRead
);
onContainerAtomRead
();
enterState
(
STATE_READING_ATOM_HEADER
);
return
0
;
}
else
{
bytesRead
=
inputStream
.
skip
((
int
)
atomSize
-
atomBytesRead
);
}
}
if
(
bytesRead
==
-
1
)
{
return
RESULT_END_OF_STREAM
;
}
rootAtomBytesRead
+=
bytesRead
;
atomBytesRead
+=
bytesRead
;
if
(
atomBytesRead
!=
atomSize
)
{
return
RESULT_NEED_MORE_DATA
;
}
if
(
atomData
!=
null
&&
!
containerAtoms
.
isEmpty
())
{
containerAtoms
.
peek
().
add
(
new
Atom
.
LeafAtom
(
atomType
,
atomData
));
}
onContainerAtomRead
();
enterState
(
STATE_READING_ATOM_HEADER
);
return
0
;
}
private
void
onContainerAtomRead
()
{
while
(!
containerAtoms
.
isEmpty
()
&&
containerAtoms
.
peek
().
endByteOffset
==
rootAtomBytesRead
)
{
Atom
.
ContainerAtom
containerAtom
=
containerAtoms
.
pop
();
if
(
containerAtom
.
type
==
Atom
.
TYPE_moov
)
{
processMoovAtom
(
containerAtom
);
}
else
if
(!
containerAtoms
.
isEmpty
())
{
containerAtoms
.
peek
().
add
(
containerAtom
);
}
}
}
private
void
enterState
(
int
state
)
{
switch
(
state
)
{
case
STATE_READING_ATOM_HEADER:
atomBytesRead
=
0
;
atomSize
=
0
;
break
;
}
parserState
=
state
;
inputStream
.
mark
();
}
/** Updates the stored track metadata to reflect the contents on the specified moov atom. */
private
void
processMoovAtom
(
Atom
.
ContainerAtom
moov
)
{
List
<
Mp4Track
>
tracks
=
new
ArrayList
<
Mp4Track
>();
long
earliestSampleOffset
=
Long
.
MAX_VALUE
;
for
(
int
i
=
0
;
i
<
moov
.
containerChildren
.
size
();
i
++)
{
Atom
.
ContainerAtom
atom
=
moov
.
containerChildren
.
get
(
i
);
if
(
atom
.
type
!=
Atom
.
TYPE_trak
)
{
continue
;
}
Track
track
=
CommonMp4AtomParsers
.
parseTrak
(
atom
,
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_mvhd
));
if
(
track
.
type
!=
Track
.
TYPE_AUDIO
&&
track
.
type
!=
Track
.
TYPE_VIDEO
)
{
continue
;
}
Atom
.
ContainerAtom
stblAtom
=
atom
.
getContainerAtomOfType
(
Atom
.
TYPE_mdia
)
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
).
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
Mp4TrackSampleTable
trackSampleTable
=
CommonMp4AtomParsers
.
parseStbl
(
track
,
stblAtom
);
if
(
trackSampleTable
.
getSampleCount
()
==
0
)
{
continue
;
}
tracks
.
add
(
new
Mp4Track
(
track
,
trackSampleTable
));
// Keep track of the byte offset of the earliest sample.
long
firstSampleOffset
=
trackSampleTable
.
offsets
[
0
];
if
(
firstSampleOffset
<
earliestSampleOffset
)
{
earliestSampleOffset
=
firstSampleOffset
;
}
}
this
.
tracks
=
tracks
.
toArray
(
new
Mp4Track
[
0
]);
if
(
earliestSampleOffset
<
inputStream
.
getReadPosition
())
{
loadFromOffset
(
earliestSampleOffset
);
}
prepared
=
true
;
}
/** Returns an unmodifiable set of atom types. */
private
static
Set
<
Integer
>
getAtomTypeSet
(
int
...
atomTypes
)
{
Set
<
Integer
>
atomTypeSet
=
new
HashSet
<
Integer
>();
for
(
int
atomType
:
atomTypes
)
{
atomTypeSet
.
add
(
atomType
);
}
return
Collections
.
unmodifiableSet
(
atomTypeSet
);
}
private
int
getRetryDelayMs
(
int
errorCount
)
{
return
Math
.
min
((
errorCount
-
1
)
*
1000
,
5000
);
}
private
void
maybeThrowLoadError
()
throws
IOException
{
if
(
loadErrorCount
>
loadRetryCount
)
{
throw
lastLoadError
;
}
}
private
static
final
class
Mp4Track
{
public
final
Track
track
;
public
final
Mp4TrackSampleTable
sampleTable
;
public
boolean
selected
;
public
int
sampleIndex
;
public
Mp4Track
(
Track
track
,
Mp4TrackSampleTable
sampleTable
)
{
this
.
track
=
track
;
this
.
sampleTable
=
sampleTable
;
}
}
}
library/src/main/java/com/google/android/exoplayer/upstream/BufferPool.java
View file @
587edf8e
...
...
@@ -129,6 +129,16 @@ public final class BufferPool implements Allocator {
}
/**
* Blocks execution until the allocated size is not greater than the threshold, or the thread is
* interrupted.
*/
public
synchronized
void
blockWhileAllocatedSizeExceeds
(
int
limit
)
throws
InterruptedException
{
while
(
getAllocatedSize
()
>
limit
)
{
wait
();
}
}
/**
* Returns the buffers belonging to an allocation to the pool.
*
* @param allocation The allocation to return.
...
...
library/src/test/java/com/google/android/exoplayer/extractor/ExtractorTest.java
View file @
587edf8e
...
...
@@ -29,6 +29,7 @@ public class ExtractorTest extends TestCase {
assertEquals
(
C
.
RESULT_END_OF_INPUT
,
Extractor
.
RESULT_END_OF_INPUT
);
// Sanity check that the other constant values don't overlap.
assertTrue
(
C
.
RESULT_END_OF_INPUT
!=
Extractor
.
RESULT_CONTINUE
);
assertTrue
(
C
.
RESULT_END_OF_INPUT
!=
Extractor
.
RESULT_SEEK
);
}
}
library/src/test/java/com/google/android/exoplayer/
source/Mp4Sample
ExtractorTest.java
→
library/src/test/java/com/google/android/exoplayer/
extractor/mp4/Mp4
ExtractorTest.java
View file @
587edf8e
...
...
@@ -13,23 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer
.
source
;
package
com
.
google
.
android
.
exoplayer
.
extractor
.
mp4
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.MediaFormatHolder
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.SampleSource
;
import
com.google.android.exoplayer.
mp4.Atom
;
import
com.google.android.exoplayer.
extractor.ExtractorSampleSource
;
import
com.google.android.exoplayer.upstream.ByteArrayDataSource
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.upstream.DataSpec
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
com.google.android.exoplayer.util.Util
;
import
android.annotation.SuppressLint
;
import
android.annotation.TargetApi
;
import
android.media.MediaExtractor
;
import
android.net.Uri
;
import
android.os.Handler
;
import
android.os.Looper
;
...
...
@@ -43,10 +42,10 @@ import java.util.List;
import
java.util.concurrent.CountDownLatch
;
/**
* Tests for {@link Mp4
Sample
Extractor}.
* Tests for {@link Mp4Extractor}.
*/
@TargetApi
(
16
)
public
class
Mp4
Sample
ExtractorTest
extends
TestCase
{
public
class
Mp4ExtractorTest
extends
TestCase
{
/** String of hexadecimal bytes containing the video stsd payload from an AVC video. */
private
static
final
byte
[]
VIDEO_STSD_PAYLOAD
=
getByteArray
(
...
...
@@ -97,7 +96,7 @@ public class Mp4SampleExtractorTest extends TestCase {
/** Indices of key-frames. */
private
static
final
int
[]
SYNCHRONIZATION_SAMPLE_INDICES
=
{
0
,
4
,
5
};
/** Indices of video frame chunk offsets. */
private
static
final
int
[]
CHUNK_OFFSETS
=
{
10
0
0
,
2000
,
3000
,
4000
};
private
static
final
int
[]
CHUNK_OFFSETS
=
{
10
8
0
,
2000
,
3000
,
4000
};
/** Numbers of video frames in each chunk. */
private
static
final
int
[]
SAMPLES_IN_CHUNK
=
{
2
,
2
,
1
,
1
};
/** The mdat box must be large enough to avoid reading chunk sample data out of bounds. */
...
...
@@ -194,7 +193,7 @@ public class Mp4SampleExtractorTest extends TestCase {
while
(
true
)
{
int
result
=
extractor
.
readSample
(
0
,
sampleHolder
);
if
(
result
==
SampleSource
.
SAMPLE_READ
)
{
assertTrue
(
(
sampleHolder
.
flags
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
);
assertTrue
(
sampleHolder
.
isSyncFrame
()
);
sampleHolder
.
clearData
();
sampleIndex
++;
}
else
if
(
result
==
SampleSource
.
END_OF_STREAM
)
{
...
...
@@ -343,10 +342,18 @@ public class Mp4SampleExtractorTest extends TestCase {
return
result
;
}
private
static
byte
[]
getMdat
()
{
// TODO: Put NAL length tags in at each sample position so the sample lengths don't have to
// be multiples of four.
return
new
byte
[
MDAT_SIZE
];
private
static
byte
[]
getMdat
(
int
mdatOffset
)
{
ByteBuffer
mdat
=
ByteBuffer
.
allocate
(
MDAT_SIZE
);
int
sampleIndex
=
0
;
for
(
int
chunk
=
0
;
chunk
<
CHUNK_OFFSETS
.
length
;
chunk
++)
{
int
sampleOffset
=
CHUNK_OFFSETS
[
chunk
];
for
(
int
sample
=
0
;
sample
<
SAMPLES_IN_CHUNK
[
chunk
];
sample
++)
{
int
sampleSize
=
SAMPLE_SIZES
[
sampleIndex
++];
mdat
.
putInt
(
sampleOffset
-
mdatOffset
,
sampleSize
);
sampleOffset
+=
sampleSize
;
}
}
return
mdat
.
array
();
}
private
static
final
DataSource
getFakeDataSource
(
boolean
includeStss
,
boolean
mp4vFormat
)
{
...
...
@@ -389,7 +396,7 @@ public class Mp4SampleExtractorTest extends TestCase {
atom
(
Atom
.
TYPE_stsc
,
getStsc
()),
atom
(
Atom
.
TYPE_stsz
,
getStsz
()),
atom
(
Atom
.
TYPE_stco
,
getStco
())))))),
atom
(
Atom
.
TYPE_mdat
,
getMdat
()));
atom
(
Atom
.
TYPE_mdat
,
getMdat
(
mp4vFormat
?
1048
:
1038
)));
}
/** Gets a valid MP4 file with audio/video tracks and without a synchronization table. */
...
...
@@ -425,7 +432,7 @@ public class Mp4SampleExtractorTest extends TestCase {
atom
(
Atom
.
TYPE_stsc
,
getStsc
()),
atom
(
Atom
.
TYPE_stsz
,
getStsz
()),
atom
(
Atom
.
TYPE_stco
,
getStco
())))))),
atom
(
Atom
.
TYPE_mdat
,
getMdat
()));
atom
(
Atom
.
TYPE_mdat
,
getMdat
(
mp4vFormat
?
992
:
982
)));
}
private
static
Mp4Atom
atom
(
int
type
,
Mp4Atom
...
containedMp4Atoms
)
{
...
...
@@ -506,9 +513,8 @@ public class Mp4SampleExtractorTest extends TestCase {
}
/**
* Creates a {@link Mp4SampleExtractor} on a separate thread with a looper, so that it can use a
* handler for loading, and provides blocking operations like {@link #seekTo} and
* {@link #readSample}.
* Creates a {@link Mp4Extractor} on a separate thread with a looper, so that it can use a handler
* for loading, and provides blocking operations like {@link #seekTo} and {@link #readSample}.
*/
private
static
final
class
Mp4ExtractorWrapper
extends
Thread
{
...
...
@@ -526,7 +532,7 @@ public class Mp4SampleExtractorTest extends TestCase {
private
volatile
CountDownLatch
pendingOperationLatch
;
public
Mp4ExtractorWrapper
(
DataSource
dataSource
)
{
super
(
"Mp4
Sample
ExtractorTest"
);
super
(
"Mp4ExtractorTest"
);
this
.
dataSource
=
Assertions
.
checkNotNull
(
dataSource
);
pendingOperationLatch
=
new
CountDownLatch
(
1
);
start
();
...
...
@@ -563,40 +569,45 @@ public class Mp4SampleExtractorTest extends TestCase {
@SuppressLint
(
"HandlerLeak"
)
@Override
public
void
run
()
{
final
Mp4SampleExtractor
mp4SampleExtractor
=
new
Mp4
SampleExtractor
(
dataSource
,
new
DataSpec
(
FAKE_URI
)
);
final
ExtractorSampleSource
source
=
new
ExtractorSampleSource
(
FAKE_URI
,
dataSource
,
new
Mp4
Extractor
(),
1
,
2
*
1024
*
1024
);
Looper
.
prepare
();
handler
=
new
Handler
()
{
@Override
public
void
handleMessage
(
Message
message
)
{
try
{
switch
(
message
.
what
)
{
case
MSG_PREPARE:
if
(!
mp4SampleExtractor
.
prepare
())
{
if
(!
source
.
prepare
())
{
sendEmptyMessage
(
MSG_PREPARE
);
}
else
{
// Select the video track and get its metadata.
mediaFormats
=
new
MediaFormat
[
mp4SampleExtractor
.
getTrackCount
()];
for
(
int
track
=
0
;
track
<
mp4SampleExtractor
.
getTrackCount
();
track
++)
{
MediaFormat
mediaFormat
=
mp4SampleExtractor
.
getMediaFormat
(
track
);
mediaFormats
=
new
MediaFormat
[
source
.
getTrackCount
()];
MediaFormatHolder
mediaFormatHolder
=
new
MediaFormatHolder
();
for
(
int
track
=
0
;
track
<
source
.
getTrackCount
();
track
++)
{
source
.
enable
(
track
,
0
);
source
.
readData
(
track
,
0
,
mediaFormatHolder
,
null
,
false
);
MediaFormat
mediaFormat
=
mediaFormatHolder
.
format
;
mediaFormats
[
track
]
=
mediaFormat
;
if
(
MimeTypes
.
isVideo
(
mediaFormat
.
mimeType
))
{
mp4SampleExtractor
.
selectTrack
(
track
);
selectedTrackMediaFormat
=
mediaFormat
;
}
else
{
source
.
disable
(
track
);
}
}
pendingOperationLatch
.
countDown
();
}
break
;
case
MSG_SEEK_TO:
long
timestampUs
=
(
l
ong
)
message
.
obj
;
mp4SampleExtractor
.
seekTo
(
timestampUs
);
long
timestampUs
=
(
L
ong
)
message
.
obj
;
source
.
seekToUs
(
timestampUs
);
break
;
case
MSG_READ_SAMPLE:
int
trackIndex
=
message
.
arg1
;
SampleHolder
sampleHolder
=
(
SampleHolder
)
message
.
obj
;
sampleHolder
.
clearData
();
readSampleResult
=
mp4SampleExtractor
.
readSample
(
trackIndex
,
sampleHolder
);
readSampleResult
=
source
.
readData
(
trackIndex
,
0
,
null
,
sampleHolder
,
false
);
if
(
readSampleResult
==
SampleSource
.
NOTHING_READ
)
{
Message
.
obtain
(
message
).
sendToTarget
();
return
;
...
...
library/src/test/java/com/google/android/exoplayer/extractor/webm/WebmExtractorTest.java
View file @
587edf8e
...
...
@@ -289,7 +289,7 @@ public class WebmExtractorTest extends InstrumentationTestCase {
ExtractorInput
input
=
createTestInput
(
data
);
int
readResult
=
Extractor
.
RESULT_CONTINUE
;
while
(
readResult
==
Extractor
.
RESULT_CONTINUE
)
{
readResult
=
extractor
.
read
(
input
);
readResult
=
extractor
.
read
(
input
,
null
);
}
assertEquals
(
Extractor
.
RESULT_END_OF_INPUT
,
readResult
);
}
...
...
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