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
ccac9fad
authored
Feb 09, 2015
by
ojw28
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #287 from google/dev
dev -> dev-webm-vp9-opus
parents
876fa41b
b0a3c30a
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
731 additions
and
135 deletions
library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/SampleHolder.java
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
library/src/main/java/com/google/android/exoplayer/hls/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
library/src/main/java/com/google/android/exoplayer/mp4/Mp4TrackSampleTable.java
library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
library/src/main/java/com/google/android/exoplayer/source/SampleExtractor.java
library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java
library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java
library/src/main/java/com/google/android/exoplayer/util/Util.java
library/src/main/java/com/google/android/exoplayer/Ac3PassthroughAudioTrackRenderer.java
View file @
ccac9fad
...
...
@@ -25,7 +25,6 @@ import android.media.AudioFormat;
import
android.os.Handler
;
import
java.io.IOException
;
import
java.nio.ByteBuffer
;
/**
* Renders encoded AC-3/enhanced AC-3 data to an {@link AudioTrack} for decoding on the playback
...
...
@@ -105,8 +104,8 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer {
this
.
source
=
Assertions
.
checkNotNull
(
source
);
this
.
eventHandler
=
eventHandler
;
this
.
eventListener
=
eventListener
;
sampleHolder
=
new
SampleHolder
(
SampleHolder
.
BUFFER_REPLACEMENT_MODE_
NORMAL
);
sampleHolder
.
data
=
ByteBuffer
.
allocateDirect
(
DEFAULT_BUFFER_SIZE
);
sampleHolder
=
new
SampleHolder
(
SampleHolder
.
BUFFER_REPLACEMENT_MODE_
DIRECT
);
sampleHolder
.
replaceBuffer
(
DEFAULT_BUFFER_SIZE
);
formatHolder
=
new
MediaFormatHolder
();
audioTrack
=
new
AudioTrack
();
shouldReadInputBuffer
=
true
;
...
...
@@ -199,8 +198,7 @@ public final class Ac3PassthroughAudioTrackRenderer extends TrackRenderer {
// Get more data if we have run out.
if
(
shouldReadInputBuffer
)
{
sampleHolder
.
data
.
clear
();
sampleHolder
.
clearData
();
int
result
=
source
.
readData
(
trackIndex
,
currentPositionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
result
==
SampleSource
.
FORMAT_READ
)
{
...
...
library/src/main/java/com/google/android/exoplayer/SampleHolder.java
View file @
ccac9fad
...
...
@@ -96,4 +96,13 @@ public final class SampleHolder {
return
false
;
}
/**
* Clears {@link #data}. Does nothing if {@link #data} is null.
*/
public
void
clearData
()
{
if
(
data
!=
null
)
{
data
.
clear
();
}
}
}
library/src/main/java/com/google/android/exoplayer/chunk/parser/mp4/FragmentedMp4Extractor.java
View file @
ccac9fad
...
...
@@ -86,6 +86,7 @@ public final class FragmentedMp4Extractor implements Extractor {
parsedAtoms
.
add
(
Atom
.
TYPE_moof
);
parsedAtoms
.
add
(
Atom
.
TYPE_moov
);
parsedAtoms
.
add
(
Atom
.
TYPE_mp4a
);
parsedAtoms
.
add
(
Atom
.
TYPE_mvhd
);
parsedAtoms
.
add
(
Atom
.
TYPE_sidx
);
parsedAtoms
.
add
(
Atom
.
TYPE_stsd
);
parsedAtoms
.
add
(
Atom
.
TYPE_tfdt
);
...
...
@@ -379,7 +380,8 @@ public final class FragmentedMp4Extractor implements Extractor {
}
ContainerAtom
mvex
=
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_mvex
);
extendsDefaults
=
parseTrex
(
mvex
.
getLeafAtomOfType
(
Atom
.
TYPE_trex
).
data
);
track
=
CommonMp4AtomParsers
.
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
));
track
=
CommonMp4AtomParsers
.
parseTrak
(
moov
.
getContainerAtomOfType
(
Atom
.
TYPE_trak
),
moov
.
getLeafAtomOfType
(
Atom
.
TYPE_mvhd
));
}
private
void
onMoofContainerAtomRead
(
ContainerAtom
moof
)
{
...
...
library/src/main/java/com/google/android/exoplayer/hls/TsExtractor.java
View file @
ccac9fad
...
...
@@ -34,7 +34,9 @@ import android.util.SparseArray;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.List
;
import
java.util.TreeSet
;
import
java.util.concurrent.ConcurrentLinkedQueue
;
/**
...
...
@@ -541,7 +543,6 @@ public final class TsExtractor {
@SuppressWarnings
(
"hiding"
)
private
final
SamplePool
samplePool
;
private
final
ConcurrentLinkedQueue
<
Sample
>
internalQueue
;
// Accessed only by the consuming thread.
private
boolean
needKeyframe
;
...
...
@@ -553,7 +554,6 @@ public final class TsExtractor {
protected
SampleQueue
(
SamplePool
samplePool
)
{
this
.
samplePool
=
samplePool
;
internalQueue
=
new
ConcurrentLinkedQueue
<
Sample
>();
needKeyframe
=
true
;
lastReadTimeUs
=
Long
.
MIN_VALUE
;
spliceOutTimeUs
=
Long
.
MIN_VALUE
;
...
...
@@ -582,7 +582,7 @@ public final class TsExtractor {
public
Sample
poll
()
{
Sample
head
=
peek
();
if
(
head
!=
null
)
{
internal
Queue
.
remov
e
();
internal
PollSampl
e
();
needKeyframe
=
false
;
lastReadTimeUs
=
head
.
timeUs
;
}
...
...
@@ -595,13 +595,13 @@ public final class TsExtractor {
* @return The next sample from the queue, or null if a sample isn't available.
*/
public
Sample
peek
()
{
Sample
head
=
internal
Queue
.
peek
();
Sample
head
=
internal
PeekSample
();
if
(
needKeyframe
)
{
// Peeking discard of samples until we find a keyframe or run out of available samples.
while
(
head
!=
null
&&
!
head
.
isKeyframe
)
{
recycle
(
head
);
internal
Queue
.
remov
e
();
head
=
internal
Queue
.
peek
();
internal
PollSampl
e
();
head
=
internal
PeekSample
();
}
}
if
(
head
==
null
)
{
...
...
@@ -610,7 +610,7 @@ public final class TsExtractor {
if
(
spliceOutTimeUs
!=
Long
.
MIN_VALUE
&&
head
.
timeUs
>=
spliceOutTimeUs
)
{
// The sample is later than the time this queue is spliced out.
recycle
(
head
);
internal
Queue
.
remov
e
();
internal
PollSampl
e
();
return
null
;
}
return
head
;
...
...
@@ -625,8 +625,8 @@ public final class TsExtractor {
Sample
head
=
peek
();
while
(
head
!=
null
&&
head
.
timeUs
<
timeUs
)
{
recycle
(
head
);
internal
Queue
.
remov
e
();
head
=
internal
Queue
.
peek
();
internal
PollSampl
e
();
head
=
internal
PeekSample
();
// We're discarding at least one sample, so any subsequent read will need to start at
// a keyframe.
needKeyframe
=
true
;
...
...
@@ -638,10 +638,10 @@ public final class TsExtractor {
* Clears the queue.
*/
public
void
release
()
{
Sample
toRecycle
=
internal
Queue
.
poll
();
Sample
toRecycle
=
internal
PollSample
();
while
(
toRecycle
!=
null
)
{
recycle
(
toRecycle
);
toRecycle
=
internal
Queue
.
poll
();
toRecycle
=
internal
PollSample
();
}
}
...
...
@@ -666,20 +666,19 @@ public final class TsExtractor {
return
true
;
}
long
firstPossibleSpliceTime
;
Sample
nextSample
=
internal
Queue
.
peek
();
Sample
nextSample
=
internal
PeekSample
();
if
(
nextSample
!=
null
)
{
firstPossibleSpliceTime
=
nextSample
.
timeUs
;
}
else
{
firstPossibleSpliceTime
=
lastReadTimeUs
+
1
;
}
ConcurrentLinkedQueue
<
Sample
>
nextInternalQueue
=
nextQueue
.
internalQueue
;
Sample
nextQueueSample
=
nextInternalQueue
.
peek
();
Sample
nextQueueSample
=
nextQueue
.
internalPeekSample
();
while
(
nextQueueSample
!=
null
&&
(
nextQueueSample
.
timeUs
<
firstPossibleSpliceTime
||
!
nextQueueSample
.
isKeyframe
))
{
// Discard samples from the next queue for as long as they are before the earliest possible
// splice time, or not keyframes.
nextQueue
.
internal
Queue
.
remov
e
();
nextQueueSample
=
nextQueue
.
internal
Queue
.
peek
();
nextQueue
.
internal
PollSampl
e
();
nextQueueSample
=
nextQueue
.
internal
PeekSample
();
}
if
(
nextQueueSample
!=
null
)
{
// We've found a keyframe in the next queue that can serve as the splice point. Set the
...
...
@@ -720,7 +719,7 @@ public final class TsExtractor {
protected
void
addSample
(
Sample
sample
)
{
largestParsedTimestampUs
=
Math
.
max
(
largestParsedTimestampUs
,
sample
.
timeUs
);
internalQueue
.
add
(
sample
);
internalQueue
Sample
(
sample
);
}
protected
void
addToSample
(
Sample
sample
,
BitArray
buffer
,
int
size
)
{
...
...
@@ -731,15 +730,37 @@ public final class TsExtractor {
sample
.
size
+=
size
;
}
protected
abstract
Sample
internalPeekSample
();
protected
abstract
Sample
internalPollSample
();
protected
abstract
void
internalQueueSample
(
Sample
sample
);
}
/**
* Extracts individual samples from continuous byte stream.
* Extracts individual samples from continuous byte stream
, preserving original order
.
*/
private
abstract
class
PesPayloadReader
extends
SampleQueue
{
private
final
ConcurrentLinkedQueue
<
Sample
>
internalQueue
;
protected
PesPayloadReader
(
SamplePool
samplePool
)
{
super
(
samplePool
);
internalQueue
=
new
ConcurrentLinkedQueue
<
Sample
>();
}
@Override
protected
final
Sample
internalPeekSample
()
{
return
internalQueue
.
peek
();
}
@Override
protected
final
Sample
internalPollSample
()
{
return
internalQueue
.
poll
();
}
@Override
protected
final
void
internalQueueSample
(
Sample
sample
)
{
internalQueue
.
add
(
sample
);
}
public
abstract
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
);
...
...
@@ -992,18 +1013,23 @@ public final class TsExtractor {
/**
* Parses a SEI data from H.264 frames and extracts samples with closed captions data.
*
* TODO: Technically, we shouldn't allow a sample to be read from the queue until we're sure that
* a sample with an earlier timestamp won't be added to it.
*/
private
class
SeiReader
extends
SampleQueue
{
private
class
SeiReader
extends
SampleQueue
implements
Comparator
<
Sample
>
{
// SEI data, used for Closed Captions.
private
static
final
int
NAL_UNIT_TYPE_SEI
=
6
;
private
final
BitArray
seiBuffer
;
private
final
TreeSet
<
Sample
>
internalQueue
;
public
SeiReader
(
SamplePool
samplePool
)
{
super
(
samplePool
);
setMediaFormat
(
MediaFormat
.
createEia608Format
());
seiBuffer
=
new
BitArray
();
internalQueue
=
new
TreeSet
<
Sample
>(
this
);
}
@SuppressLint
(
"InlinedApi"
)
...
...
@@ -1022,6 +1048,27 @@ public final class TsExtractor {
}
}
@Override
public
int
compare
(
Sample
first
,
Sample
second
)
{
// Note - We don't expect samples to have identical timestamps.
return
first
.
timeUs
<=
second
.
timeUs
?
-
1
:
1
;
}
@Override
protected
synchronized
Sample
internalPeekSample
()
{
return
internalQueue
.
isEmpty
()
?
null
:
internalQueue
.
first
();
}
@Override
protected
synchronized
Sample
internalPollSample
()
{
return
internalQueue
.
pollFirst
();
}
@Override
protected
synchronized
void
internalQueueSample
(
Sample
sample
)
{
internalQueue
.
add
(
sample
);
}
}
/**
...
...
library/src/main/java/com/google/android/exoplayer/mp4/Atom.java
View file @
ccac9fad
...
...
@@ -39,6 +39,7 @@ public abstract class Atom {
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"
);
...
...
@@ -69,6 +70,7 @@ public abstract class Atom {
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"
);
...
...
library/src/main/java/com/google/android/exoplayer/mp4/CommonMp4AtomParsers.java
View file @
ccac9fad
...
...
@@ -24,6 +24,8 @@ import com.google.android.exoplayer.util.MimeTypes;
import
com.google.android.exoplayer.util.ParsableByteArray
;
import
com.google.android.exoplayer.util.Util
;
import
android.annotation.SuppressLint
;
import
android.media.MediaExtractor
;
import
android.util.Pair
;
import
java.util.ArrayList
;
...
...
@@ -40,11 +42,13 @@ 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.
*/
public
static
Track
parseTrak
(
Atom
.
ContainerAtom
trak
)
{
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
...
...
@@ -53,23 +57,212 @@ public final class CommonMp4AtomParsers {
Pair
<
Integer
,
Long
>
header
=
parseTkhd
(
trak
.
getLeafAtomOfType
(
Atom
.
TYPE_tkhd
).
data
);
int
id
=
header
.
first
;
long
duration
=
header
.
second
;
long
timescale
=
parseMdhd
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_mdhd
)
.
data
);
long
movieTimescale
=
parseMvhd
(
mvhd
.
data
);
long
durationUs
;
if
(
duration
==
-
1
)
{
durationUs
=
C
.
UNKNOWN_TIME_US
;
}
else
{
durationUs
=
Util
.
scaleLargeTimestamp
(
duration
,
C
.
MICROS_PER_SECOND
,
t
imescale
);
durationUs
=
Util
.
scaleLargeTimestamp
(
duration
,
C
.
MICROS_PER_SECOND
,
movieT
imescale
);
}
Atom
.
ContainerAtom
stbl
=
mdia
.
getContainerAtomOfType
(
Atom
.
TYPE_minf
)
.
getContainerAtomOfType
(
Atom
.
TYPE_stbl
);
long
mediaTimescale
=
parseMdhd
(
mdia
.
getLeafAtomOfType
(
Atom
.
TYPE_mdhd
).
data
);
Pair
<
MediaFormat
,
TrackEncryptionBox
[]>
sampleDescriptions
=
parseStsd
(
stbl
.
getLeafAtomOfType
(
Atom
.
TYPE_stsd
).
data
);
return
new
Track
(
id
,
trackType
,
t
imescale
,
durationUs
,
sampleDescriptions
.
first
,
return
new
Track
(
id
,
trackType
,
mediaT
imescale
,
durationUs
,
sampleDescriptions
.
first
,
sampleDescriptions
.
second
);
}
/**
* Parses an stbl atom (defined in 14496-12).
*
* @param track Track to which this sample table corresponds.
* @param stblAtom stbl (sample table) atom to parse.
* @return Sample table described by the stbl atom.
*/
@SuppressLint
(
"InlinedApi"
)
public
static
Mp4TrackSampleTable
parseStbl
(
Track
track
,
Atom
.
ContainerAtom
stblAtom
)
{
// Array of sample sizes.
ParsableByteArray
stsz
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stsz
).
data
;
// Entries are byte offsets of chunks.
ParsableByteArray
chunkOffsets
;
Atom
.
LeafAtom
chunkOffsetsAtom
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stco
);
if
(
chunkOffsetsAtom
==
null
)
{
chunkOffsetsAtom
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_co64
);
}
chunkOffsets
=
chunkOffsetsAtom
.
data
;
// Entries are (chunk number, number of samples per chunk, sample description index).
ParsableByteArray
stsc
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stsc
).
data
;
// Entries are (number of samples, timestamp delta between those samples).
ParsableByteArray
stts
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stts
).
data
;
// Entries are the indices of samples that are synchronization samples.
Atom
.
LeafAtom
stssAtom
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_stss
);
ParsableByteArray
stss
=
stssAtom
!=
null
?
stssAtom
.
data
:
null
;
// Entries are (number of samples, timestamp offset).
Atom
.
LeafAtom
cttsAtom
=
stblAtom
.
getLeafAtomOfType
(
Atom
.
TYPE_ctts
);
ParsableByteArray
ctts
=
cttsAtom
!=
null
?
cttsAtom
.
data
:
null
;
// Skip full atom.
stsz
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
);
int
fixedSampleSize
=
stsz
.
readUnsignedIntToInt
();
int
sampleCount
=
stsz
.
readUnsignedIntToInt
();
int
[]
sizes
=
new
int
[
sampleCount
];
long
[]
timestamps
=
new
long
[
sampleCount
];
long
[]
offsets
=
new
long
[
sampleCount
];
int
[]
flags
=
new
int
[
sampleCount
];
// Prepare to read chunk offsets.
chunkOffsets
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
);
int
chunkCount
=
chunkOffsets
.
readUnsignedIntToInt
();
stsc
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
);
int
remainingSamplesPerChunkChanges
=
stsc
.
readUnsignedIntToInt
()
-
1
;
Assertions
.
checkState
(
stsc
.
readInt
()
==
1
,
"stsc first chunk must be 1"
);
int
samplesPerChunk
=
stsc
.
readUnsignedIntToInt
();
stsc
.
skip
(
4
);
// Skip the sample description index.
int
nextSamplesPerChunkChangeChunkIndex
=
-
1
;
if
(
remainingSamplesPerChunkChanges
>
0
)
{
// Store the chunk index when the samples-per-chunk will next change.
nextSamplesPerChunkChangeChunkIndex
=
stsc
.
readUnsignedIntToInt
()
-
1
;
}
int
chunkIndex
=
0
;
int
remainingSamplesInChunk
=
samplesPerChunk
;
// Prepare to read sample timestamps.
stts
.
setPosition
(
Mp4Util
.
FULL_ATOM_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
(
Mp4Util
.
ATOM_HEADER_SIZE
);
cttsHasSignedOffsets
=
Mp4Util
.
parseFullAtomVersion
(
ctts
.
readInt
())
==
1
;
remainingTimestampOffsetChanges
=
ctts
.
readUnsignedIntToInt
()
-
1
;
remainingSamplesAtTimestampOffset
=
ctts
.
readUnsignedIntToInt
();
timestampOffset
=
cttsHasSignedOffsets
?
ctts
.
readInt
()
:
ctts
.
readUnsignedIntToInt
();
}
int
nextSynchronizationSampleIndex
=
-
1
;
int
remainingSynchronizationSamples
=
0
;
if
(
stss
!=
null
)
{
stss
.
setPosition
(
Mp4Util
.
FULL_ATOM_HEADER_SIZE
);
remainingSynchronizationSamples
=
stss
.
readUnsignedIntToInt
();
nextSynchronizationSampleIndex
=
stss
.
readUnsignedIntToInt
()
-
1
;
}
// Calculate the chunk offsets
long
offsetBytes
;
if
(
chunkOffsetsAtom
.
type
==
Atom
.
TYPE_stco
)
{
offsetBytes
=
chunkOffsets
.
readUnsignedInt
();
}
else
{
offsetBytes
=
chunkOffsets
.
readUnsignedLongToLong
();
}
long
timestampTimeUnits
=
0
;
for
(
int
i
=
0
;
i
<
sampleCount
;
i
++)
{
offsets
[
i
]
=
offsetBytes
;
sizes
[
i
]
=
fixedSampleSize
==
0
?
stsz
.
readUnsignedIntToInt
()
:
fixedSampleSize
;
timestamps
[
i
]
=
timestampTimeUnits
+
timestampOffset
;
// All samples are synchronization samples if the stss is not present.
flags
[
i
]
=
stss
==
null
?
MediaExtractor
.
SAMPLE_FLAG_SYNC
:
0
;
if
(
i
==
nextSynchronizationSampleIndex
)
{
flags
[
i
]
=
MediaExtractor
.
SAMPLE_FLAG_SYNC
;
remainingSynchronizationSamples
--;
if
(
remainingSynchronizationSamples
>
0
)
{
nextSynchronizationSampleIndex
=
stss
.
readUnsignedIntToInt
()
-
1
;
}
}
// Add on the duration of this sample.
timestampTimeUnits
+=
timestampDeltaInTimeUnits
;
remainingSamplesAtTimestampDelta
--;
if
(
remainingSamplesAtTimestampDelta
==
0
&&
remainingTimestampDeltaChanges
>
0
)
{
remainingSamplesAtTimestampDelta
=
stts
.
readUnsignedIntToInt
();
timestampDeltaInTimeUnits
=
stts
.
readUnsignedIntToInt
();
remainingTimestampDeltaChanges
--;
}
// Add on the timestamp offset if ctts is present.
if
(
ctts
!=
null
)
{
remainingSamplesAtTimestampOffset
--;
if
(
remainingSamplesAtTimestampOffset
==
0
&&
remainingTimestampOffsetChanges
>
0
)
{
remainingSamplesAtTimestampOffset
=
ctts
.
readUnsignedIntToInt
();
timestampOffset
=
cttsHasSignedOffsets
?
ctts
.
readInt
()
:
ctts
.
readUnsignedIntToInt
();
remainingTimestampOffsetChanges
--;
}
}
// If we're at the last sample in this chunk, move to the next chunk.
remainingSamplesInChunk
--;
if
(
remainingSamplesInChunk
==
0
)
{
chunkIndex
++;
if
(
chunkIndex
<
chunkCount
)
{
if
(
chunkOffsetsAtom
.
type
==
Atom
.
TYPE_stco
)
{
offsetBytes
=
chunkOffsets
.
readUnsignedInt
();
}
else
{
offsetBytes
=
chunkOffsets
.
readUnsignedLongToLong
();
}
}
// Change the samples-per-chunk if required.
if
(
chunkIndex
==
nextSamplesPerChunkChangeChunkIndex
)
{
samplesPerChunk
=
stsc
.
readUnsignedIntToInt
();
stsc
.
skip
(
4
);
// Skip the sample description index.
remainingSamplesPerChunkChanges
--;
if
(
remainingSamplesPerChunkChanges
>
0
)
{
nextSamplesPerChunkChangeChunkIndex
=
stsc
.
readUnsignedIntToInt
()
-
1
;
}
}
// Expect samplesPerChunk samples in the following chunk, if it's before the end.
if
(
chunkIndex
<
chunkCount
)
{
remainingSamplesInChunk
=
samplesPerChunk
;
}
}
else
{
// The next sample follows the current one.
offsetBytes
+=
sizes
[
i
];
}
}
Util
.
scaleLargeTimestampsInPlace
(
timestamps
,
1000000
,
track
.
timescale
);
// Check all the expected samples have been seen.
Assertions
.
checkArgument
(
remainingSynchronizationSamples
==
0
);
Assertions
.
checkArgument
(
remainingSamplesAtTimestampDelta
==
0
);
Assertions
.
checkArgument
(
remainingSamplesInChunk
==
0
);
Assertions
.
checkArgument
(
remainingTimestampDeltaChanges
==
0
);
Assertions
.
checkArgument
(
remainingTimestampOffsetChanges
==
0
);
return
new
Mp4TrackSampleTable
(
offsets
,
sizes
,
timestamps
,
flags
);
}
/**
* Parses a mvhd atom (defined in 14496-12), returning the timescale for the movie.
*
* @param mvhd Contents of the mvhd atom to be parsed.
* @return Timescale for the movie.
*/
private
static
long
parseMvhd
(
ParsableByteArray
mvhd
)
{
mvhd
.
setPosition
(
Mp4Util
.
ATOM_HEADER_SIZE
);
int
fullAtom
=
mvhd
.
readInt
();
int
version
=
Mp4Util
.
parseFullAtomVersion
(
fullAtom
);
mvhd
.
skip
(
version
==
0
?
8
:
16
);
return
mvhd
.
readUnsignedInt
();
}
/**
* Parses a tkhd atom (defined in 14496-12).
*
* @return A {@link Pair} consisting of the track id and duration (in the timescale indicated in
...
...
library/src/main/java/com/google/android/exoplayer/mp4/Mp4TrackSampleTable.java
0 → 100644
View file @
ccac9fad
/*
* 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
.
mp4
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Util
;
import
android.media.MediaExtractor
;
/** Sample table for a track in an MP4 file. */
public
final
class
Mp4TrackSampleTable
{
/** Sample offsets in bytes. */
public
final
long
[]
offsets
;
/** Sample sizes in bytes. */
public
final
int
[]
sizes
;
/** Sample timestamps in microseconds. */
public
final
long
[]
timestampsUs
;
/** Sample flags. */
public
final
int
[]
flags
;
Mp4TrackSampleTable
(
long
[]
offsets
,
int
[]
sizes
,
long
[]
timestampsUs
,
int
[]
flags
)
{
Assertions
.
checkArgument
(
sizes
.
length
==
timestampsUs
.
length
);
Assertions
.
checkArgument
(
offsets
.
length
==
timestampsUs
.
length
);
Assertions
.
checkArgument
(
flags
.
length
==
timestampsUs
.
length
);
this
.
offsets
=
offsets
;
this
.
sizes
=
sizes
;
this
.
timestampsUs
=
timestampsUs
;
this
.
flags
=
flags
;
}
/** Returns the number of samples in the table. */
public
int
getSampleCount
()
{
return
sizes
.
length
;
}
/**
* Returns the sample index of the closest synchronization sample at or before the given
* timestamp, if one is available.
*
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
* @return Index of the synchronization sample, or {@link Mp4Util#NO_SAMPLE} if none.
*/
public
int
getIndexOfEarlierOrEqualSynchronizationSample
(
long
timeUs
)
{
int
startIndex
=
Util
.
binarySearchFloor
(
timestampsUs
,
timeUs
,
true
,
false
);
for
(
int
i
=
startIndex
;
i
>=
0
;
i
--)
{
if
(
timestampsUs
[
i
]
<=
timeUs
&&
(
flags
[
i
]
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
return
i
;
}
}
return
Mp4Util
.
NO_SAMPLE
;
}
/**
* Returns the sample index of the closest synchronization sample at or after the given timestamp,
* if one is available.
*
* @param timeUs Timestamp adjacent to which to find a synchronization sample.
* @return index Index of the synchronization sample, or {@link Mp4Util#NO_SAMPLE} if none.
*/
public
int
getIndexOfLaterOrEqualSynchronizationSample
(
long
timeUs
)
{
int
startIndex
=
Util
.
binarySearchCeil
(
timestampsUs
,
timeUs
,
true
,
false
);
for
(
int
i
=
startIndex
;
i
<
timestampsUs
.
length
;
i
++)
{
if
(
timestampsUs
[
i
]
>=
timeUs
&&
(
flags
[
i
]
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
return
i
;
}
}
return
Mp4Util
.
NO_SAMPLE
;
}
}
library/src/main/java/com/google/android/exoplayer/mp4/Mp4Util.java
View file @
ccac9fad
...
...
@@ -34,6 +34,15 @@ public final class Mp4Util {
/** Size of a full atom header, in bytes. */
public
static
final
int
FULL_ATOM_HEADER_SIZE
=
12
;
/** Value for the first 32 bits of atomSize when the atom size is actually a long value. */
public
static
final
int
LONG_ATOM_SIZE
=
1
;
/** Sample index when no sample is available. */
public
static
final
int
NO_SAMPLE
=
-
1
;
/** Track index when no track is selected. */
public
static
final
int
NO_TRACK
=
-
1
;
/** Four initial bytes that must prefix H.264/AVC NAL units for decoding. */
private
static
final
byte
[]
NAL_START_CODE
=
new
byte
[]
{
0
,
0
,
0
,
1
};
...
...
library/src/main/java/com/google/android/exoplayer/source/SampleExtractor.java
View file @
ccac9fad
...
...
@@ -91,8 +91,9 @@ public interface SampleExtractor {
* {@link SampleSource#END_OF_STREAM} if the last samples in all tracks have been read, or
* {@link SampleSource#NOTHING_READ} if the sample cannot be read immediately as it is not
* loaded.
* @throws IOException Thrown if the source can't be read.
*/
int
readSample
(
int
track
,
SampleHolder
sampleHolder
);
int
readSample
(
int
track
,
SampleHolder
sampleHolder
)
throws
IOException
;
/** Releases resources associated with this extractor. */
void
release
();
...
...
library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java
View file @
ccac9fad
...
...
@@ -135,7 +135,6 @@ public class SubtitleParserHelper implements Handler.Callback {
if
(
sampleHolder
!=
holder
)
{
// A flush has occurred since this holder was posted. Do nothing.
}
else
{
holder
.
data
.
position
(
0
);
this
.
result
=
result
;
this
.
error
=
error
;
this
.
parsing
=
false
;
...
...
library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java
View file @
ccac9fad
...
...
@@ -177,8 +177,9 @@ public class TextTrackRenderer extends TrackRenderer implements Callback {
if
(!
inputStreamEnded
&&
subtitle
==
null
)
{
try
{
SampleHolder
sampleHolder
=
parserHelper
.
getSampleHolder
();
sampleHolder
.
clearData
();
int
result
=
source
.
readData
(
trackIndex
,
positionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
result
==
SampleSource
.
SAMPLE_READ
)
{
if
(
result
==
SampleSource
.
SAMPLE_READ
&&
!
sampleHolder
.
decodeOnly
)
{
parserHelper
.
startParseOperation
();
textRendererNeedsUpdate
=
false
;
}
else
if
(
result
==
SampleSource
.
END_OF_STREAM
)
{
...
...
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaption.java
View file @
ccac9fad
...
...
@@ -18,7 +18,7 @@ package com.google.android.exoplayer.text.eia608;
/**
* A Closed Caption that contains textual data associated with time indices.
*/
public
final
class
ClosedCaption
implements
Comparable
<
ClosedCaption
>
{
/* package */
abstract
class
ClosedCaption
implements
Comparable
<
ClosedCaption
>
{
/**
* Identifies closed captions with control characters.
...
...
@@ -30,23 +30,16 @@ public final class ClosedCaption implements Comparable<ClosedCaption> {
public
static
final
int
TYPE_TEXT
=
1
;
/**
* The type of the closed caption data. If equals to {@link #TYPE_TEXT} the {@link #text} field
* has the textual data, if equals to {@link #TYPE_CTRL} the {@link #text} field has two control
* characters (C1, C2).
* The type of the closed caption data.
*/
public
final
int
type
;
/**
* Contains text or two control characters.
*/
public
final
String
text
;
/**
* Timestamp associated with the closed caption.
*/
public
final
long
timeUs
;
p
ublic
ClosedCaption
(
int
type
,
String
text
,
long
timeUs
)
{
p
rotected
ClosedCaption
(
int
type
,
long
timeUs
)
{
this
.
type
=
type
;
this
.
text
=
text
;
this
.
timeUs
=
timeUs
;
}
...
...
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionCtrl.java
0 → 100644
View file @
ccac9fad
/*
* 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
.
text
.
eia608
;
/* package */
final
class
ClosedCaptionCtrl
extends
ClosedCaption
{
/**
* The receipt of the {@link #RESUME_CAPTION_LOADING} command initiates pop-on style captioning.
* Subsequent data should be loaded into a non-displayed memory and held there until the
* {@link #END_OF_CAPTION} command is received, at which point the non-displayed memory becomes
* the displayed memory (and vice versa).
*/
public
static
final
byte
RESUME_CAPTION_LOADING
=
0x20
;
/**
* The receipt of the {@link #ROLL_UP_CAPTIONS_2_ROWS} command initiates roll-up style
* captioning, with the maximum of 2 rows displayed simultaneously.
*/
public
static
final
byte
ROLL_UP_CAPTIONS_2_ROWS
=
0x25
;
/**
* The receipt of the {@link #ROLL_UP_CAPTIONS_3_ROWS} command initiates roll-up style
* captioning, with the maximum of 3 rows displayed simultaneously.
*/
public
static
final
byte
ROLL_UP_CAPTIONS_3_ROWS
=
0x26
;
/**
* The receipt of the {@link #ROLL_UP_CAPTIONS_4_ROWS} command initiates roll-up style
* captioning, with the maximum of 4 rows displayed simultaneously.
*/
public
static
final
byte
ROLL_UP_CAPTIONS_4_ROWS
=
0x27
;
/**
* The receipt of the {@link #RESUME_DIRECT_CAPTIONING} command initiates paint-on style
* captioning. Subsequent data should be addressed immediately to displayed memory without need
* for the {@link #RESUME_CAPTION_LOADING} command.
*/
public
static
final
byte
RESUME_DIRECT_CAPTIONING
=
0x29
;
/**
* The receipt of the {@link #END_OF_CAPTION} command indicates the end of pop-on style caption,
* at this point already loaded in non-displayed memory caption should become the displayed
* memory (and vice versa). If no {@link #RESUME_CAPTION_LOADING} command has been received,
* {@link #END_OF_CAPTION} command forces the receiver into pop-on style.
*/
public
static
final
byte
END_OF_CAPTION
=
0x2F
;
public
static
final
byte
ERASE_DISPLAYED_MEMORY
=
0x2C
;
public
static
final
byte
CARRIAGE_RETURN
=
0x2D
;
public
static
final
byte
ERASE_NON_DISPLAYED_MEMORY
=
0x2E
;
public
static
final
byte
MID_ROW_CHAN_1
=
0x11
;
public
static
final
byte
MID_ROW_CHAN_2
=
0x19
;
public
static
final
byte
MISC_CHAN_1
=
0x14
;
public
static
final
byte
MISC_CHAN_2
=
0x1C
;
public
static
final
byte
TAB_OFFSET_CHAN_1
=
0x17
;
public
static
final
byte
TAB_OFFSET_CHAN_2
=
0x1F
;
public
final
byte
cc1
;
public
final
byte
cc2
;
protected
ClosedCaptionCtrl
(
byte
cc1
,
byte
cc2
,
long
timeUs
)
{
super
(
ClosedCaption
.
TYPE_CTRL
,
timeUs
);
this
.
cc1
=
cc1
;
this
.
cc2
=
cc2
;
}
public
boolean
isMidRowCode
()
{
return
(
cc1
==
MID_ROW_CHAN_1
||
cc1
==
MID_ROW_CHAN_2
)
&&
(
cc2
>=
0x20
&&
cc2
<=
0x2F
);
}
public
boolean
isMiscCode
()
{
return
(
cc1
==
MISC_CHAN_1
||
cc1
==
MISC_CHAN_2
)
&&
(
cc2
>=
0x20
&&
cc2
<=
0x2F
);
}
public
boolean
isTabOffsetCode
()
{
return
(
cc1
==
TAB_OFFSET_CHAN_1
||
cc1
==
TAB_OFFSET_CHAN_2
)
&&
(
cc2
>=
0x21
&&
cc2
<=
0x23
);
}
public
boolean
isPreambleAddressCode
()
{
return
(
cc1
>=
0x10
&&
cc1
<=
0x1F
)
&&
(
cc2
>=
0x40
&&
cc2
<=
0x7F
);
}
}
library/src/main/java/com/google/android/exoplayer/text/eia608/ClosedCaptionText.java
0 → 100644
View file @
ccac9fad
/*
* 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
.
text
.
eia608
;
/* package */
final
class
ClosedCaptionText
extends
ClosedCaption
{
public
final
String
text
;
public
ClosedCaptionText
(
String
text
,
long
timeUs
)
{
super
(
ClosedCaption
.
TYPE_TEXT
,
timeUs
);
this
.
text
=
text
;
}
}
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608Parser.java
View file @
ccac9fad
...
...
@@ -18,8 +18,6 @@ package com.google.android.exoplayer.text.eia608;
import
com.google.android.exoplayer.util.BitArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
/**
...
...
@@ -82,22 +80,29 @@ public class Eia608Parser {
0xFB
// 3F: 251 'û' "Latin small letter U with circumflex"
};
public
boolean
canParse
(
String
mimeType
)
{
private
final
BitArray
seiBuffer
;
private
final
StringBuilder
stringBuilder
;
/* package */
Eia608Parser
()
{
seiBuffer
=
new
BitArray
();
stringBuilder
=
new
StringBuilder
();
}
/* package */
boolean
canParse
(
String
mimeType
)
{
return
mimeType
.
equals
(
MimeTypes
.
APPLICATION_EIA608
);
}
public
List
<
ClosedCaption
>
parse
(
byte
[]
data
,
int
size
,
long
timeUs
)
{
/* package */
void
parse
(
byte
[]
data
,
int
size
,
long
timeUs
,
List
<
ClosedCaption
>
out
)
{
if
(
size
<=
0
)
{
return
null
;
return
;
}
BitArray
seiBuffer
=
new
BitArray
(
data
,
size
);
stringBuilder
.
setLength
(
0
);
seiBuffer
.
reset
(
data
,
size
);
seiBuffer
.
skipBits
(
3
);
// reserved + process_cc_data_flag + zero_bit
int
ccCount
=
seiBuffer
.
readBits
(
5
);
seiBuffer
.
skipBytes
(
1
);
List
<
ClosedCaption
>
captions
=
new
ArrayList
<
ClosedCaption
>();
StringBuilder
stringBuilder
=
new
StringBuilder
();
for
(
int
i
=
0
;
i
<
ccCount
;
i
++)
{
seiBuffer
.
skipBits
(
5
);
// one_bit + reserved
boolean
ccValid
=
seiBuffer
.
readBit
();
...
...
@@ -129,12 +134,10 @@ public class Eia608Parser {
// Control character.
if
(
ccData1
<
0x20
)
{
if
(
stringBuilder
.
length
()
>
0
)
{
captions
.
add
(
new
ClosedCaption
(
ClosedCaption
.
TYPE_TEXT
,
stringBuilder
.
toString
(),
timeUs
));
out
.
add
(
new
ClosedCaptionText
(
stringBuilder
.
toString
(),
timeUs
));
stringBuilder
.
setLength
(
0
);
}
captions
.
add
(
new
ClosedCaption
(
ClosedCaption
.
TYPE_CTRL
,
new
String
(
new
char
[]
{(
char
)
ccData1
,
(
char
)
ccData2
}),
timeUs
));
out
.
add
(
new
ClosedCaptionCtrl
(
ccData1
,
ccData2
,
timeUs
));
continue
;
}
...
...
@@ -146,10 +149,8 @@ public class Eia608Parser {
}
if
(
stringBuilder
.
length
()
>
0
)
{
captions
.
add
(
new
ClosedCaption
(
ClosedCaption
.
TYPE_TEXT
,
stringBuilder
.
toString
(),
timeUs
));
out
.
add
(
new
ClosedCaptionText
(
stringBuilder
.
toString
(),
timeUs
));
}
return
Collections
.
unmodifiableList
(
captions
);
}
private
static
char
getChar
(
byte
ccData
)
{
...
...
library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java
View file @
ccac9fad
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer
.
text
.
eia608
;
import
com.google.android.exoplayer.C
;
import
com.google.android.exoplayer.ExoPlaybackException
;
import
com.google.android.exoplayer.MediaFormatHolder
;
import
com.google.android.exoplayer.SampleHolder
;
...
...
@@ -22,6 +23,7 @@ import com.google.android.exoplayer.SampleSource;
import
com.google.android.exoplayer.TrackRenderer
;
import
com.google.android.exoplayer.text.TextRenderer
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.Util
;
import
android.os.Handler
;
import
android.os.Handler.Callback
;
...
...
@@ -29,10 +31,8 @@ import android.os.Looper;
import
android.os.Message
;
import
java.io.IOException
;
import
java.util.Collections
;
import
java.util.LinkedList
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Queue
;
/**
* A {@link TrackRenderer} for EIA-608 closed captions in a media stream.
...
...
@@ -40,26 +40,32 @@ import java.util.Queue;
public
class
Eia608TrackRenderer
extends
TrackRenderer
implements
Callback
{
private
static
final
int
MSG_INVOKE_RENDERER
=
0
;
// The Number of closed captions text line to keep in memory.
private
static
final
int
ALLOWED_CAPTIONS_TEXT_LINES_COUNT
=
4
;
private
static
final
int
CC_MODE_UNKNOWN
=
0
;
private
static
final
int
CC_MODE_ROLL_UP
=
1
;
private
static
final
int
CC_MODE_POP_ON
=
2
;
private
static
final
int
CC_MODE_PAINT_ON
=
3
;
// The default number of rows to display in roll-up captions mode.
private
static
final
int
DEFAULT_CAPTIONS_ROW_COUNT
=
4
;
private
final
SampleSource
source
;
private
final
Eia608Parser
eia608Parser
;
private
final
TextRenderer
textRenderer
;
private
final
Handler
metadata
Handler
;
private
final
Handler
textRenderer
Handler
;
private
final
MediaFormatHolder
formatHolder
;
private
final
SampleHolder
sampleHolder
;
private
final
StringBuilder
closedCaptionStringBuilder
;
//Currently displayed captions.
private
final
List
<
ClosedCaption
>
currentCaptions
;
private
final
Queue
<
Integer
>
newLineIndexes
;
private
final
StringBuilder
captionStringBuilder
;
private
final
List
<
ClosedCaption
>
captionBuffer
;
private
int
trackIndex
;
private
long
currentPositionUs
;
private
boolean
inputStreamEnded
;
private
long
pendingCaptionsTimestamp
;
private
List
<
ClosedCaption
>
pendingCaptions
;
private
int
captionMode
;
private
int
captionRowCount
;
private
String
caption
;
private
String
lastRenderedCaption
;
/**
* @param source A source from which samples containing EIA-608 closed captions can be read.
...
...
@@ -74,14 +80,12 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
Looper
textRendererLooper
)
{
this
.
source
=
Assertions
.
checkNotNull
(
source
);
this
.
textRenderer
=
Assertions
.
checkNotNull
(
textRenderer
);
this
.
metadataHandler
=
textRendererLooper
==
null
?
null
:
new
Handler
(
textRendererLooper
,
this
);
textRendererHandler
=
textRendererLooper
==
null
?
null
:
new
Handler
(
textRendererLooper
,
this
);
eia608Parser
=
new
Eia608Parser
();
formatHolder
=
new
MediaFormatHolder
();
sampleHolder
=
new
SampleHolder
(
SampleHolder
.
BUFFER_REPLACEMENT_MODE_NORMAL
);
closedCaptionStringBuilder
=
new
StringBuilder
();
currentCaptions
=
new
LinkedList
<
ClosedCaption
>();
newLineIndexes
=
new
LinkedList
<
Integer
>();
captionStringBuilder
=
new
StringBuilder
();
captionBuffer
=
new
ArrayList
<
ClosedCaption
>();
}
@Override
...
...
@@ -117,10 +121,11 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
private
void
seekToInternal
(
long
positionUs
)
{
currentPositionUs
=
positionUs
;
pendingCaptions
=
null
;
inputStreamEnded
=
false
;
// Clear displayed captions.
currentCaptions
.
clear
();
clearPendingSample
();
captionRowCount
=
DEFAULT_CAPTIONS_ROW_COUNT
;
setCaptionMode
(
CC_MODE_UNKNOWN
);
invokeRenderer
(
null
);
}
@Override
...
...
@@ -133,15 +138,10 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
throw
new
ExoPlaybackException
(
e
);
}
if
(!
inputStreamEnded
&&
pendingCaptions
==
null
)
{
if
(!
inputStreamEnded
&&
!
isSamplePending
()
)
{
try
{
int
result
=
source
.
readData
(
trackIndex
,
positionUs
,
formatHolder
,
sampleHolder
,
false
);
if
(
result
==
SampleSource
.
SAMPLE_READ
)
{
pendingCaptionsTimestamp
=
sampleHolder
.
timeUs
;
pendingCaptions
=
eia608Parser
.
parse
(
sampleHolder
.
data
.
array
(),
sampleHolder
.
size
,
sampleHolder
.
timeUs
);
sampleHolder
.
data
.
clear
();
}
else
if
(
result
==
SampleSource
.
END_OF_STREAM
)
{
if
(
result
==
SampleSource
.
END_OF_STREAM
)
{
inputStreamEnded
=
true
;
}
}
catch
(
IOException
e
)
{
...
...
@@ -149,15 +149,22 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
}
}
if
(
pendingCaptions
!=
null
&&
pendingCaptionsTimestamp
<=
currentPositionUs
)
{
invokeRenderer
(
pendingCaptions
);
pendingCaptions
=
null
;
if
(
isSamplePending
()
&&
sampleHolder
.
timeUs
<=
currentPositionUs
)
{
// Parse the pending sample.
eia608Parser
.
parse
(
sampleHolder
.
data
.
array
(),
sampleHolder
.
size
,
sampleHolder
.
timeUs
,
captionBuffer
);
// Consume parsed captions.
consumeCaptionBuffer
();
// Update the renderer, unless the sample was marked for decoding only.
if
(!
sampleHolder
.
decodeOnly
)
{
invokeRenderer
(
caption
);
}
clearPendingSample
();
}
}
@Override
protected
void
onDisabled
()
{
pendingCaptions
=
null
;
source
.
disable
(
trackIndex
);
}
...
...
@@ -186,11 +193,16 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
return
true
;
}
private
void
invokeRenderer
(
List
<
ClosedCaption
>
metadata
)
{
if
(
metadataHandler
!=
null
)
{
metadataHandler
.
obtainMessage
(
MSG_INVOKE_RENDERER
,
metadata
).
sendToTarget
();
private
void
invokeRenderer
(
String
text
)
{
if
(
Util
.
areEqual
(
lastRenderedCaption
,
text
))
{
// No change.
return
;
}
this
.
lastRenderedCaption
=
text
;
if
(
textRendererHandler
!=
null
)
{
textRendererHandler
.
obtainMessage
(
MSG_INVOKE_RENDERER
,
text
).
sendToTarget
();
}
else
{
invokeRendererInternal
(
metadata
);
invokeRendererInternal
(
text
);
}
}
...
...
@@ -199,62 +211,155 @@ public class Eia608TrackRenderer extends TrackRenderer implements Callback {
public
boolean
handleMessage
(
Message
msg
)
{
switch
(
msg
.
what
)
{
case
MSG_INVOKE_RENDERER:
invokeRendererInternal
((
List
<
ClosedCaption
>
)
msg
.
obj
);
invokeRendererInternal
((
String
)
msg
.
obj
);
return
true
;
}
return
false
;
}
private
void
invokeRendererInternal
(
List
<
ClosedCaption
>
metadata
)
{
currentCaptions
.
addAll
(
metadata
);
// Sort captions by the timestamp.
Collections
.
sort
(
currentCaptions
);
closedCaptionStringBuilder
.
setLength
(
0
);
private
void
invokeRendererInternal
(
String
text
)
{
textRenderer
.
onText
(
text
);
}
// After processing keep only captions after cutIndex.
int
c
utIndex
=
0
;
newLineIndexes
.
clear
();
for
(
int
i
=
0
;
i
<
currentCaptions
.
size
();
i
++)
{
ClosedCaption
caption
=
currentCaptions
.
get
(
i
);
private
void
consumeCaptionBuffer
()
{
int
c
aptionBufferSize
=
captionBuffer
.
size
()
;
if
(
captionBufferSize
==
0
)
{
return
;
}
for
(
int
i
=
0
;
i
<
captionBufferSize
;
i
++)
{
ClosedCaption
caption
=
captionBuffer
.
get
(
i
);
if
(
caption
.
type
==
ClosedCaption
.
TYPE_CTRL
)
{
int
cc2
=
caption
.
text
.
codePointAt
(
1
);
switch
(
cc2
)
{
case
0x2C
:
// Erase Displayed Memory.
closedCaptionStringBuilder
.
setLength
(
0
);
cutIndex
=
i
;
newLineIndexes
.
clear
();
break
;
case
0x25
:
// Roll-Up.
case
0x26
:
case
0x27
:
default
:
if
(
cc2
>=
0x20
&&
cc2
<
0x40
)
{
break
;
}
if
(
closedCaptionStringBuilder
.
length
()
>
0
&&
closedCaptionStringBuilder
.
charAt
(
closedCaptionStringBuilder
.
length
()
-
1
)
!=
'\n'
)
{
closedCaptionStringBuilder
.
append
(
'\n'
);
newLineIndexes
.
add
(
i
);
if
(
newLineIndexes
.
size
()
>=
ALLOWED_CAPTIONS_TEXT_LINES_COUNT
)
{
cutIndex
=
newLineIndexes
.
poll
();
}
}
break
;
ClosedCaptionCtrl
captionCtrl
=
(
ClosedCaptionCtrl
)
caption
;
if
(
captionCtrl
.
isMiscCode
())
{
handleMiscCode
(
captionCtrl
);
}
else
if
(
captionCtrl
.
isPreambleAddressCode
())
{
handlePreambleAddressCode
();
}
}
else
{
closedCaptionStringBuilder
.
append
(
caption
.
text
);
handleText
((
ClosedCaptionText
)
caption
);
}
}
captionBuffer
.
clear
();
if
(
cutIndex
>
0
&&
cutIndex
<
currentCaptions
.
size
()
-
1
)
{
for
(
int
i
=
0
;
i
<=
cutIndex
;
i
++)
{
currentCaptions
.
remove
(
0
);
}
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
caption
=
getDisplayCaption
();
}
}
private
void
handleText
(
ClosedCaptionText
captionText
)
{
if
(
captionMode
!=
CC_MODE_UNKNOWN
)
{
captionStringBuilder
.
append
(
captionText
.
text
);
}
}
private
void
handleMiscCode
(
ClosedCaptionCtrl
captionCtrl
)
{
switch
(
captionCtrl
.
cc2
)
{
case
ClosedCaptionCtrl
.
ROLL_UP_CAPTIONS_2_ROWS
:
captionRowCount
=
2
;
setCaptionMode
(
CC_MODE_ROLL_UP
);
return
;
case
ClosedCaptionCtrl
.
ROLL_UP_CAPTIONS_3_ROWS
:
captionRowCount
=
3
;
setCaptionMode
(
CC_MODE_ROLL_UP
);
return
;
case
ClosedCaptionCtrl
.
ROLL_UP_CAPTIONS_4_ROWS
:
captionRowCount
=
4
;
setCaptionMode
(
CC_MODE_ROLL_UP
);
return
;
case
ClosedCaptionCtrl
.
RESUME_CAPTION_LOADING
:
setCaptionMode
(
CC_MODE_POP_ON
);
return
;
case
ClosedCaptionCtrl
.
RESUME_DIRECT_CAPTIONING
:
setCaptionMode
(
CC_MODE_PAINT_ON
);
return
;
}
if
(
captionMode
==
CC_MODE_UNKNOWN
)
{
return
;
}
switch
(
captionCtrl
.
cc2
)
{
case
ClosedCaptionCtrl
.
ERASE_DISPLAYED_MEMORY
:
caption
=
null
;
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
captionStringBuilder
.
setLength
(
0
);
}
return
;
case
ClosedCaptionCtrl
.
ERASE_NON_DISPLAYED_MEMORY
:
captionStringBuilder
.
setLength
(
0
);
return
;
case
ClosedCaptionCtrl
.
END_OF_CAPTION
:
caption
=
getDisplayCaption
();
captionStringBuilder
.
setLength
(
0
);
return
;
case
ClosedCaptionCtrl
.
CARRIAGE_RETURN
:
maybeAppendNewline
();
return
;
}
}
private
void
handlePreambleAddressCode
()
{
// TODO: Add better handling of this with specific positioning.
maybeAppendNewline
();
}
private
void
setCaptionMode
(
int
captionMode
)
{
if
(
this
.
captionMode
==
captionMode
)
{
return
;
}
this
.
captionMode
=
captionMode
;
// Clear the working memory.
captionStringBuilder
.
setLength
(
0
);
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_UNKNOWN
)
{
// When switching to roll-up or unknown, we also need to clear the caption.
caption
=
null
;
}
}
private
void
maybeAppendNewline
()
{
int
buildLength
=
captionStringBuilder
.
length
();
if
(
buildLength
>
0
&&
captionStringBuilder
.
charAt
(
buildLength
-
1
)
!=
'\n'
)
{
captionStringBuilder
.
append
(
'\n'
);
}
}
private
String
getDisplayCaption
()
{
int
buildLength
=
captionStringBuilder
.
length
();
if
(
buildLength
==
0
)
{
return
null
;
}
boolean
endsWithNewline
=
captionStringBuilder
.
charAt
(
buildLength
-
1
)
==
'\n'
;
if
(
buildLength
==
1
&&
endsWithNewline
)
{
return
null
;
}
int
endIndex
=
endsWithNewline
?
buildLength
-
1
:
buildLength
;
if
(
captionMode
!=
CC_MODE_ROLL_UP
)
{
return
captionStringBuilder
.
substring
(
0
,
endIndex
);
}
int
startIndex
=
0
;
int
searchBackwardFromIndex
=
endIndex
;
for
(
int
i
=
0
;
i
<
captionRowCount
&&
searchBackwardFromIndex
!=
-
1
;
i
++)
{
searchBackwardFromIndex
=
captionStringBuilder
.
lastIndexOf
(
"\n"
,
searchBackwardFromIndex
-
1
);
}
if
(
searchBackwardFromIndex
!=
-
1
)
{
startIndex
=
searchBackwardFromIndex
+
1
;
}
captionStringBuilder
.
delete
(
0
,
startIndex
);
return
captionStringBuilder
.
substring
(
0
,
endIndex
-
startIndex
);
}
private
void
clearPendingSample
()
{
sampleHolder
.
timeUs
=
C
.
UNKNOWN_TIME_US
;
sampleHolder
.
clearData
();
}
textRenderer
.
onText
(
closedCaptionStringBuilder
.
toString
());
private
boolean
isSamplePending
()
{
return
sampleHolder
.
timeUs
!=
C
.
UNKNOWN_TIME_US
;
}
}
library/src/main/java/com/google/android/exoplayer/util/Util.java
View file @
ccac9fad
...
...
@@ -402,6 +402,32 @@ public final class Util {
}
/**
* Applies {@link #scaleLargeTimestamp(long, long, long)} to an array of unscaled timestamps.
*
* @param timestamps The timestamps to scale.
* @param multiplier The multiplier.
* @param divisor The divisor.
*/
public
static
void
scaleLargeTimestampsInPlace
(
long
[]
timestamps
,
long
multiplier
,
long
divisor
)
{
if
(
divisor
>=
multiplier
&&
(
divisor
%
multiplier
)
==
0
)
{
long
divisionFactor
=
divisor
/
multiplier
;
for
(
int
i
=
0
;
i
<
timestamps
.
length
;
i
++)
{
timestamps
[
i
]
/=
divisionFactor
;
}
}
else
if
(
divisor
<
multiplier
&&
(
multiplier
%
divisor
)
==
0
)
{
long
multiplicationFactor
=
multiplier
/
divisor
;
for
(
int
i
=
0
;
i
<
timestamps
.
length
;
i
++)
{
timestamps
[
i
]
*=
multiplicationFactor
;
}
}
else
{
double
multiplicationFactor
=
(
double
)
multiplier
/
divisor
;
for
(
int
i
=
0
;
i
<
timestamps
.
length
;
i
++)
{
timestamps
[
i
]
=
(
long
)
(
timestamps
[
i
]
*
multiplicationFactor
);
}
}
}
/**
* Converts a list of integers to a primitive array.
*
* @param list A list of integers.
...
...
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