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
15d3df6a
authored
Nov 18, 2014
by
Andrey Udovenko
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Add EIA-608 (CEA-608) Closed Captioning support for HLS #68
parent
c57484f9
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
425 additions
and
63 deletions
demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
library/src/main/java/com/google/android/exoplayer/MediaFormat.java
library/src/main/java/com/google/android/exoplayer/hls/TsExtractor.java
library/src/main/java/com/google/android/exoplayer/metadata/ClosedCaption.java
library/src/main/java/com/google/android/exoplayer/metadata/Eia608Parser.java
library/src/main/java/com/google/android/exoplayer/metadata/Id3Parser.java
library/src/main/java/com/google/android/exoplayer/metadata/MetadataParser.java
library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java
library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
demo/src/main/java/com/google/android/exoplayer/demo/full/FullPlayerActivity.java
View file @
15d3df6a
...
...
@@ -25,6 +25,7 @@ import com.google.android.exoplayer.demo.full.player.DemoPlayer;
import
com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.HlsRendererBuilder
;
import
com.google.android.exoplayer.demo.full.player.SmoothStreamingRendererBuilder
;
import
com.google.android.exoplayer.metadata.ClosedCaption
;
import
com.google.android.exoplayer.metadata.TxxxMetadata
;
import
com.google.android.exoplayer.text.CaptionStyleCompat
;
import
com.google.android.exoplayer.text.SubtitleView
;
...
...
@@ -56,13 +57,15 @@ import android.widget.PopupMenu;
import
android.widget.PopupMenu.OnMenuItemClickListener
;
import
android.widget.TextView
;
import
java.util.List
;
import
java.util.Map
;
/**
* An activity that plays media using {@link DemoPlayer}.
*/
public
class
FullPlayerActivity
extends
Activity
implements
SurfaceHolder
.
Callback
,
OnClickListener
,
DemoPlayer
.
Listener
,
DemoPlayer
.
TextListener
,
DemoPlayer
.
MetadataListener
{
DemoPlayer
.
Listener
,
DemoPlayer
.
TextListener
,
DemoPlayer
.
Id3MetadataListener
,
DemoPlayer
.
ClosedCaptionListener
{
private
static
final
String
TAG
=
"FullPlayerActivity"
;
...
...
@@ -196,6 +199,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
player
.
addListener
(
this
);
player
.
setTextListener
(
this
);
player
.
setMetadataListener
(
this
);
player
.
setClosedCaptionListener
(
this
);
player
.
seekTo
(
playerPosition
);
playerNeedsPrepare
=
true
;
mediaController
.
setMediaPlayer
(
player
.
getPlayerControl
());
...
...
@@ -415,7 +419,7 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
// DemoPlayer.MetadataListener implementation
@Override
public
void
onMetadata
(
Map
<
String
,
Object
>
metadata
)
{
public
void
on
Id3
Metadata
(
Map
<
String
,
Object
>
metadata
)
{
for
(
int
i
=
0
;
i
<
metadata
.
size
();
i
++)
{
if
(
metadata
.
containsKey
(
TxxxMetadata
.
TYPE
))
{
TxxxMetadata
txxxMetadata
=
(
TxxxMetadata
)
metadata
.
get
(
TxxxMetadata
.
TYPE
);
...
...
@@ -425,6 +429,31 @@ public class FullPlayerActivity extends Activity implements SurfaceHolder.Callba
}
}
//DemoPlayer.ClosedCaptioListener implementation
@Override
public
void
onClosedCaption
(
List
<
ClosedCaption
>
closedCaptions
)
{
StringBuilder
stringBuilder
=
new
StringBuilder
();
for
(
ClosedCaption
caption
:
closedCaptions
)
{
// Ignore control characters and just insert a new line in between words.
if
(
caption
.
type
==
ClosedCaption
.
TYPE_CTRL
)
{
if
(
stringBuilder
.
length
()
>
0
&&
stringBuilder
.
charAt
(
stringBuilder
.
length
()
-
1
)
!=
'\n'
)
{
stringBuilder
.
append
(
'\n'
);
}
}
else
if
(
caption
.
type
==
ClosedCaption
.
TYPE_TEXT
)
{
stringBuilder
.
append
(
caption
.
text
);
}
}
if
(
stringBuilder
.
length
()
>
0
&&
stringBuilder
.
charAt
(
stringBuilder
.
length
()
-
1
)
==
'\n'
)
{
stringBuilder
.
deleteCharAt
(
stringBuilder
.
length
()
-
1
);
}
if
(
stringBuilder
.
length
()
>
0
)
{
subtitleView
.
setVisibility
(
View
.
VISIBLE
);
subtitleView
.
setText
(
stringBuilder
.
toString
());
}
}
// SurfaceHolder.Callback implementation
@Override
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/DemoPlayer.java
View file @
15d3df6a
...
...
@@ -26,6 +26,7 @@ import com.google.android.exoplayer.TrackRenderer;
import
com.google.android.exoplayer.chunk.ChunkSampleSource
;
import
com.google.android.exoplayer.chunk.MultiTrackChunkSource
;
import
com.google.android.exoplayer.drm.StreamingDrmSessionManager
;
import
com.google.android.exoplayer.metadata.ClosedCaption
;
import
com.google.android.exoplayer.metadata.MetadataTrackRenderer
;
import
com.google.android.exoplayer.text.TextTrackRenderer
;
import
com.google.android.exoplayer.upstream.DefaultBandwidthMeter
;
...
...
@@ -37,6 +38,7 @@ import android.os.Looper;
import
android.view.Surface
;
import
java.io.IOException
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.CopyOnWriteArrayList
;
...
...
@@ -48,7 +50,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
public
class
DemoPlayer
implements
ExoPlayer
.
Listener
,
ChunkSampleSource
.
EventListener
,
DefaultBandwidthMeter
.
EventListener
,
MediaCodecVideoTrackRenderer
.
EventListener
,
MediaCodecAudioTrackRenderer
.
EventListener
,
TextTrackRenderer
.
TextRenderer
,
MetadataTrackRenderer
.
MetadataRenderer
,
StreamingDrmSessionManager
.
EventListener
{
StreamingDrmSessionManager
.
EventListener
{
/**
* Builds renderers for the player.
...
...
@@ -137,10 +139,17 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
}
/**
* A listener for receiving metadata parsed from the media stream.
* A listener for receiving
ID3
metadata parsed from the media stream.
*/
public
interface
MetadataListener
{
void
onMetadata
(
Map
<
String
,
Object
>
metadata
);
public
interface
Id3MetadataListener
{
void
onId3Metadata
(
Map
<
String
,
Object
>
metadata
);
}
/**
* A listener for receiving closed captions parsed from the media stream.
*/
public
interface
ClosedCaptionListener
{
void
onClosedCaption
(
List
<
ClosedCaption
>
closedCaptions
);
}
// Constants pulled into this class for convenience.
...
...
@@ -158,7 +167,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
public
static
final
int
TYPE_AUDIO
=
1
;
public
static
final
int
TYPE_TEXT
=
2
;
public
static
final
int
TYPE_TIMED_METADATA
=
3
;
public
static
final
int
TYPE_DEBUG
=
4
;
public
static
final
int
TYPE_CLOSED_CAPTIONS
=
4
;
public
static
final
int
TYPE_DEBUG
=
5
;
private
static
final
int
RENDERER_BUILDING_STATE_IDLE
=
1
;
private
static
final
int
RENDERER_BUILDING_STATE_BUILDING
=
2
;
...
...
@@ -183,7 +193,8 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
private
int
[]
selectedTracks
;
private
TextListener
textListener
;
private
MetadataListener
metadataListener
;
private
Id3MetadataListener
id3MetadataListener
;
private
ClosedCaptionListener
closedCaptionListener
;
private
InternalErrorListener
internalErrorListener
;
private
InfoListener
infoListener
;
...
...
@@ -225,8 +236,12 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
textListener
=
listener
;
}
public
void
setMetadataListener
(
MetadataListener
listener
)
{
metadataListener
=
listener
;
public
void
setMetadataListener
(
Id3MetadataListener
listener
)
{
id3MetadataListener
=
listener
;
}
public
void
setClosedCaptionListener
(
ClosedCaptionListener
listener
)
{
closedCaptionListener
=
listener
;
}
public
void
setSurface
(
Surface
surface
)
{
...
...
@@ -473,11 +488,32 @@ public class DemoPlayer implements ExoPlayer.Listener, ChunkSampleSource.EventLi
}
}
/* package */
MetadataTrackRenderer
.
MetadataRenderer
<
Map
<
String
,
Object
>>
getId3MetadataRenderer
()
{
return
new
MetadataTrackRenderer
.
MetadataRenderer
<
Map
<
String
,
Object
>>()
{
@Override
public
void
onMetadata
(
Map
<
String
,
Object
>
metadata
)
{
if
(
metadataListener
!=
null
)
{
metadataListener
.
onMetadata
(
metadata
);
if
(
id3MetadataListener
!=
null
)
{
id3MetadataListener
.
onId3Metadata
(
metadata
);
}
}
};
}
/* package */
MetadataTrackRenderer
.
MetadataRenderer
<
List
<
ClosedCaption
>>
getClosedCaptionMetadataRenderer
()
{
return
new
MetadataTrackRenderer
.
MetadataRenderer
<
List
<
ClosedCaption
>>()
{
@Override
public
void
onMetadata
(
List
<
ClosedCaption
>
metadata
)
{
if
(
closedCaptionListener
!=
null
)
{
closedCaptionListener
.
onClosedCaption
(
metadata
);
}
}
};
}
@Override
...
...
demo/src/main/java/com/google/android/exoplayer/demo/full/player/HlsRendererBuilder.java
View file @
15d3df6a
...
...
@@ -26,6 +26,8 @@ import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import
com.google.android.exoplayer.hls.HlsMasterPlaylistParser
;
import
com.google.android.exoplayer.hls.HlsSampleSource
;
import
com.google.android.exoplayer.hls.Variant
;
import
com.google.android.exoplayer.metadata.ClosedCaption
;
import
com.google.android.exoplayer.metadata.Eia608Parser
;
import
com.google.android.exoplayer.metadata.Id3Parser
;
import
com.google.android.exoplayer.metadata.MetadataTrackRenderer
;
import
com.google.android.exoplayer.upstream.DataSource
;
...
...
@@ -39,6 +41,8 @@ import android.net.Uri;
import
java.io.IOException
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Map
;
/**
* A {@link RendererBuilder} for HLS.
...
...
@@ -94,13 +98,19 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
MediaCodec
.
VIDEO_SCALING_MODE_SCALE_TO_FIT
,
0
,
player
.
getMainHandler
(),
player
,
50
);
MediaCodecAudioTrackRenderer
audioRenderer
=
new
MediaCodecAudioTrackRenderer
(
sampleSource
);
MetadataTrackRenderer
metadataRenderer
=
new
MetadataTrackRenderer
(
sampleSource
,
new
Id3Parser
(),
player
,
player
.
getMainHandler
().
getLooper
());
MetadataTrackRenderer
<
Map
<
String
,
Object
>>
id3Renderer
=
new
MetadataTrackRenderer
<
Map
<
String
,
Object
>>(
sampleSource
,
new
Id3Parser
(),
player
.
getId3MetadataRenderer
(),
player
.
getMainHandler
().
getLooper
());
MetadataTrackRenderer
<
List
<
ClosedCaption
>>
closedCaptionRenderer
=
new
MetadataTrackRenderer
<
List
<
ClosedCaption
>>(
sampleSource
,
new
Eia608Parser
(),
player
.
getClosedCaptionMetadataRenderer
(),
player
.
getMainHandler
().
getLooper
());
TrackRenderer
[]
renderers
=
new
TrackRenderer
[
DemoPlayer
.
RENDERER_COUNT
];
renderers
[
DemoPlayer
.
TYPE_VIDEO
]
=
videoRenderer
;
renderers
[
DemoPlayer
.
TYPE_AUDIO
]
=
audioRenderer
;
renderers
[
DemoPlayer
.
TYPE_TIMED_METADATA
]
=
metadataRenderer
;
renderers
[
DemoPlayer
.
TYPE_TIMED_METADATA
]
=
id3Renderer
;
renderers
[
DemoPlayer
.
TYPE_CLOSED_CAPTIONS
]
=
closedCaptionRenderer
;
callback
.
onRenderers
(
null
,
null
,
renderers
);
}
...
...
library/src/main/java/com/google/android/exoplayer/MediaFormat.java
View file @
15d3df6a
...
...
@@ -92,6 +92,11 @@ public class MediaFormat {
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
);
}
public
static
MediaFormat
createEia608Format
()
{
return
new
MediaFormat
(
MimeTypes
.
APPLICATION_EIA608
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
NO_VALUE
,
null
);
}
@TargetApi
(
16
)
private
MediaFormat
(
android
.
media
.
MediaFormat
format
)
{
this
.
frameworkMediaFormat
=
format
;
...
...
library/src/main/java/com/google/android/exoplayer/hls/TsExtractor.java
View file @
15d3df6a
...
...
@@ -17,6 +17,7 @@ package com.google.android.exoplayer.hls;
import
com.google.android.exoplayer.MediaFormat
;
import
com.google.android.exoplayer.SampleHolder
;
import
com.google.android.exoplayer.metadata.Eia608Parser
;
import
com.google.android.exoplayer.upstream.DataSource
;
import
com.google.android.exoplayer.util.Assertions
;
import
com.google.android.exoplayer.util.BitArray
;
...
...
@@ -33,7 +34,6 @@ import java.io.IOException;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.Queue
;
import
java.util.concurrent.ConcurrentLinkedQueue
;
/**
...
...
@@ -50,9 +50,10 @@ public final class TsExtractor {
private
static
final
int
TS_STREAM_TYPE_AAC
=
0x0F
;
private
static
final
int
TS_STREAM_TYPE_H264
=
0x1B
;
private
static
final
int
TS_STREAM_TYPE_ID3
=
0x15
;
private
static
final
int
TS_STREAM_TYPE_EIA608
=
0x100
;
// 0xFF + 1
private
final
BitArray
tsPacketBuffer
;
private
final
SparseArray
<
PesPayloadReader
>
pesPayloadReader
s
;
// Indexed by streamType
private
final
SparseArray
<
SampleQueue
>
sampleQueue
s
;
// Indexed by streamType
private
final
SparseArray
<
TsPayloadReader
>
tsPayloadReaders
;
// Indexed by pid
private
final
SamplePool
samplePool
;
/* package */
final
long
firstSampleTimestamp
;
...
...
@@ -69,7 +70,7 @@ public final class TsExtractor {
this
.
samplePool
=
samplePool
;
pendingFirstSampleTimestampAdjustment
=
true
;
tsPacketBuffer
=
new
BitArray
();
pesPayloadReaders
=
new
SparseArray
<
PesPayloadReader
>();
sampleQueues
=
new
SparseArray
<
SampleQueue
>();
tsPayloadReaders
=
new
SparseArray
<
TsPayloadReader
>();
tsPayloadReaders
.
put
(
TS_PAT_PID
,
new
PatReader
());
largestParsedTimestampUs
=
Long
.
MIN_VALUE
;
...
...
@@ -84,7 +85,7 @@ public final class TsExtractor {
*/
public
int
getTrackCount
()
{
Assertions
.
checkState
(
prepared
);
return
pesPayloadReader
s
.
size
();
return
sampleQueue
s
.
size
();
}
/**
...
...
@@ -97,7 +98,7 @@ public final class TsExtractor {
*/
public
MediaFormat
getFormat
(
int
track
)
{
Assertions
.
checkState
(
prepared
);
return
pesPayloadReader
s
.
valueAt
(
track
).
getMediaFormat
();
return
sampleQueue
s
.
valueAt
(
track
).
getMediaFormat
();
}
/**
...
...
@@ -113,13 +114,13 @@ public final class TsExtractor {
* Flushes any pending or incomplete samples, returning them to the sample pool.
*/
public
void
clear
()
{
for
(
int
i
=
0
;
i
<
pesPayloadReader
s
.
size
();
i
++)
{
pesPayloadReader
s
.
valueAt
(
i
).
clear
();
for
(
int
i
=
0
;
i
<
sampleQueue
s
.
size
();
i
++)
{
sampleQueue
s
.
valueAt
(
i
).
clear
();
}
}
/**
* For each track, discards samples from the next keyframe (inclusive).
* For each track, discards samples from the next key
frame (inclusive).
*/
public
void
discardFromNextKeyframes
()
{
discardFromNextKeyframes
=
true
;
...
...
@@ -143,8 +144,7 @@ public final class TsExtractor {
*/
public
boolean
getSample
(
int
track
,
SampleHolder
out
)
{
Assertions
.
checkState
(
prepared
);
Queue
<
Sample
>
queue
=
pesPayloadReaders
.
valueAt
(
track
).
sampleQueue
;
Sample
sample
=
queue
.
poll
();
Sample
sample
=
sampleQueues
.
valueAt
(
track
).
poll
();
if
(
sample
==
null
)
{
return
false
;
}
...
...
@@ -161,7 +161,7 @@ public final class TsExtractor {
* for any track. False otherwise.
*/
public
boolean
hasSamples
()
{
for
(
int
i
=
0
;
i
<
pesPayloadReader
s
.
size
();
i
++)
{
for
(
int
i
=
0
;
i
<
sampleQueue
s
.
size
();
i
++)
{
if
(
hasSamples
(
i
))
{
return
true
;
}
...
...
@@ -177,16 +177,16 @@ public final class TsExtractor {
* for the specified track. False otherwise.
*/
public
boolean
hasSamples
(
int
track
)
{
return
!
pesPayloadReaders
.
valueAt
(
track
).
sampleQueue
.
isEmpty
();
return
!
sampleQueues
.
valueAt
(
track
)
.
isEmpty
();
}
private
boolean
checkPrepared
()
{
int
pesPayloadReaderCount
=
pesPayloadReader
s
.
size
();
int
pesPayloadReaderCount
=
sampleQueue
s
.
size
();
if
(
pesPayloadReaderCount
==
0
)
{
return
false
;
}
for
(
int
i
=
0
;
i
<
pesPayloadReaderCount
;
i
++)
{
if
(!
pesPayloadReader
s
.
valueAt
(
i
).
hasMediaFormat
())
{
if
(!
sampleQueue
s
.
valueAt
(
i
).
hasMediaFormat
())
{
return
false
;
}
}
...
...
@@ -342,7 +342,7 @@ public final class TsExtractor {
tsBuffer
.
skipBytes
(
esInfoLength
);
entriesSize
-=
esInfoLength
+
5
;
if
(
pesPayloadReader
s
.
get
(
streamType
)
!=
null
)
{
if
(
sampleQueue
s
.
get
(
streamType
)
!=
null
)
{
continue
;
}
...
...
@@ -352,7 +352,9 @@ public final class TsExtractor {
pesPayloadReader
=
new
AdtsReader
();
break
;
case
TS_STREAM_TYPE_H264:
pesPayloadReader
=
new
H264Reader
();
SeiReader
seiReader
=
new
SeiReader
();
sampleQueues
.
put
(
TS_STREAM_TYPE_EIA608
,
seiReader
);
pesPayloadReader
=
new
H264Reader
(
seiReader
);
break
;
case
TS_STREAM_TYPE_ID3:
pesPayloadReader
=
new
Id3Reader
();
...
...
@@ -360,7 +362,7 @@ public final class TsExtractor {
}
if
(
pesPayloadReader
!=
null
)
{
pesPayloadReader
s
.
put
(
streamType
,
pesPayloadReader
);
sampleQueue
s
.
put
(
streamType
,
pesPayloadReader
);
tsPayloadReaders
.
put
(
elementaryPid
,
new
PesReader
(
pesPayloadReader
));
}
}
...
...
@@ -469,18 +471,18 @@ public final class TsExtractor {
}
/**
*
Extracts individual samples from continuous byte stream
.
*
A collection of extracted samples
.
*/
private
abstract
class
PesPayloadReader
{
private
abstract
class
SampleQueue
{
p
ublic
final
ConcurrentLinkedQueue
<
Sample
>
sampleQ
ueue
;
p
rivate
final
ConcurrentLinkedQueue
<
Sample
>
q
ueue
;
private
MediaFormat
mediaFormat
;
private
boolean
foundFirstKeyframe
;
private
boolean
foundLastKeyframe
;
protected
PesPayloadReader
()
{
this
.
sampleQ
ueue
=
new
ConcurrentLinkedQueue
<
Sample
>();
protected
SampleQueue
()
{
this
.
q
ueue
=
new
ConcurrentLinkedQueue
<
Sample
>();
}
public
boolean
hasMediaFormat
()
{
...
...
@@ -495,16 +497,22 @@ public final class TsExtractor {
this
.
mediaFormat
=
mediaFormat
;
}
public
abstract
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
);
public
void
clear
()
{
Sample
toRecycle
=
sampleQ
ueue
.
poll
();
Sample
toRecycle
=
q
ueue
.
poll
();
while
(
toRecycle
!=
null
)
{
samplePool
.
recycle
(
toRecycle
);
toRecycle
=
sampleQ
ueue
.
poll
();
toRecycle
=
q
ueue
.
poll
();
}
}
public
Sample
poll
()
{
return
queue
.
poll
();
}
public
boolean
isEmpty
()
{
return
queue
.
isEmpty
();
}
/**
* Creates a new Sample and adds it to the queue.
*
...
...
@@ -534,7 +542,7 @@ public final class TsExtractor {
adjustTimestamp
(
sample
);
if
(
foundFirstKeyframe
&&
!
foundLastKeyframe
)
{
largestParsedTimestampUs
=
Math
.
max
(
largestParsedTimestampUs
,
sample
.
timeUs
);
sampleQ
ueue
.
add
(
sample
);
q
ueue
.
add
(
sample
);
}
else
{
samplePool
.
recycle
(
sample
);
}
...
...
@@ -559,6 +567,15 @@ public final class TsExtractor {
}
/**
* Extracts individual samples from continuous byte stream.
*/
private
abstract
class
PesPayloadReader
extends
SampleQueue
{
public
abstract
void
read
(
BitArray
pesBuffer
,
int
pesPayloadSize
,
long
pesTimeUs
);
}
/**
* Parses a continuous H264 byte stream and extracts individual frames.
*/
private
class
H264Reader
extends
PesPayloadReader
{
...
...
@@ -567,9 +584,15 @@ public final class TsExtractor {
private
static
final
int
NAL_UNIT_TYPE_AUD
=
9
;
private
static
final
int
NAL_UNIT_TYPE_SPS
=
7
;
public
final
SeiReader
seiReader
;
// Used to store uncompleted sample data.
private
Sample
currentSample
;
public
H264Reader
(
SeiReader
seiReader
)
{
this
.
seiReader
=
seiReader
;
}
@Override
public
void
clear
()
{
super
.
clear
();
...
...
@@ -593,6 +616,7 @@ public final class TsExtractor {
if
(!
hasMediaFormat
()
&&
(
currentSample
.
flags
&
MediaExtractor
.
SAMPLE_FLAG_SYNC
)
!=
0
)
{
parseMediaFormat
(
currentSample
);
}
seiReader
.
read
(
currentSample
.
data
,
currentSample
.
size
,
pesTimeUs
);
addSample
(
currentSample
);
}
currentSample
=
samplePool
.
get
();
...
...
@@ -757,6 +781,39 @@ public final class TsExtractor {
}
/**
* Parses a SEI data from H.264 frames and extracts samples with closed captions data.
*/
private
class
SeiReader
extends
SampleQueue
{
// SEI data, used for Closed Captions.
private
static
final
int
NAL_UNIT_TYPE_SEI
=
6
;
private
final
BitArray
seiBuffer
;
public
SeiReader
()
{
setMediaFormat
(
MediaFormat
.
createEia608Format
());
seiBuffer
=
new
BitArray
();
}
@SuppressLint
(
"InlinedApi"
)
public
void
read
(
byte
[]
data
,
int
size
,
long
pesTimeUs
)
{
seiBuffer
.
reset
(
data
,
size
);
while
(
seiBuffer
.
bytesLeft
()
>
0
)
{
int
seiStart
=
seiBuffer
.
findNextNalUnit
(
NAL_UNIT_TYPE_SEI
,
0
);
if
(
seiStart
==
seiBuffer
.
bytesLeft
())
{
return
;
}
seiBuffer
.
skipBytes
(
seiStart
+
4
);
int
ccDataSize
=
Eia608Parser
.
parseHeader
(
seiBuffer
);
if
(
ccDataSize
>
0
)
{
addSample
(
seiBuffer
,
ccDataSize
,
pesTimeUs
,
MediaExtractor
.
SAMPLE_FLAG_SYNC
);
}
}
}
}
/**
* Parses a continuous ADTS byte stream and extracts individual frames.
*/
private
class
AdtsReader
extends
PesPayloadReader
{
...
...
library/src/main/java/com/google/android/exoplayer/metadata/ClosedCaption.java
0 → 100644
View file @
15d3df6a
/*
* 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
.
metadata
;
/**
* A Closed Caption that contains textual data associated with time indices.
*/
public
final
class
ClosedCaption
{
/**
* Identifies closed captions with control characters.
*/
public
static
final
int
TYPE_CTRL
=
0
;
/**
* Identifies closed captions with textual information.
*/
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).
*/
public
final
int
type
;
/**
* Contains text or two control characters.
*/
public
final
String
text
;
public
ClosedCaption
(
int
type
,
String
text
)
{
this
.
type
=
type
;
this
.
text
=
text
;
}
}
library/src/main/java/com/google/android/exoplayer/metadata/Eia608Parser.java
0 → 100644
View file @
15d3df6a
/*
* 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
.
metadata
;
import
com.google.android.exoplayer.util.BitArray
;
import
com.google.android.exoplayer.util.MimeTypes
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
/**
* Facilitates the extraction and parsing of EIA-608 (a.k.a. "line 21 captions" and "CEA-608")
* Closed Captions from the SEI data block from H.264.
*/
public
class
Eia608Parser
implements
MetadataParser
<
List
<
ClosedCaption
>>
{
private
static
final
int
PAYLOAD_TYPE_CC
=
4
;
private
static
final
int
COUNTRY_CODE
=
0xB5
;
private
static
final
int
PROVIDER_CODE
=
0x31
;
private
static
final
int
USER_ID
=
0x47413934
;
// "GA94"
private
static
final
int
USER_DATA_TYPE_CODE
=
0x3
;
private
static
final
int
[]
SPECIAL_CHARACTER_SET
=
new
int
[]
{
0xAE
,
// 30: 174 '®' "Registered Sign" - registered trademark symbol
0xB0
,
// 31: 176 '°' "Degree Sign"
0xBD
,
// 32: 189 '½' "Vulgar Fraction One Half" (1/2 symbol)
0xBF
,
// 33: 191 '¿' "Inverted Question Mark"
0x2122
,
// 34: "Trade Mark Sign" (tm superscript)
0xA2
,
// 35: 162 '¢' "Cent Sign"
0xA3
,
// 36: 163 '£' "Pound Sign" - pounds sterling
0x266A
,
// 37: "Eighth Note" - music note
0xE0
,
// 38: 224 'à' "Latin small letter A with grave"
0x20
,
// 39: TRANSPARENT SPACE - for now use ordinary space
0xE8
,
// 3A: 232 'è' "Latin small letter E with grave"
0xE2
,
// 3B: 226 'â' "Latin small letter A with circumflex"
0xEA
,
// 3C: 234 'ê' "Latin small letter E with circumflex"
0xEE
,
// 3D: 238 'î' "Latin small letter I with circumflex"
0xF4
,
// 3E: 244 'ô' "Latin small letter O with circumflex"
0xFB
// 3F: 251 'û' "Latin small letter U with circumflex"
};
@Override
public
boolean
canParse
(
String
mimeType
)
{
return
mimeType
.
equals
(
MimeTypes
.
APPLICATION_EIA608
);
}
@Override
public
List
<
ClosedCaption
>
parse
(
byte
[]
data
,
int
size
)
throws
IOException
{
if
(
size
<=
0
)
{
return
null
;
}
BitArray
seiBuffer
=
new
BitArray
(
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
();
if
(!
ccValid
)
{
seiBuffer
.
skipBits
(
18
);
continue
;
}
int
ccType
=
seiBuffer
.
readBits
(
2
);
if
(
ccType
!=
0
&&
ccType
!=
1
)
{
// Not EIA-608 captions.
seiBuffer
.
skipBits
(
16
);
continue
;
}
seiBuffer
.
skipBits
(
1
);
byte
ccData1
=
(
byte
)
seiBuffer
.
readBits
(
7
);
seiBuffer
.
skipBits
(
1
);
byte
ccData2
=
(
byte
)
seiBuffer
.
readBits
(
7
);
if
((
ccData1
==
0x11
)
&&
((
ccData2
&
0x70
)
==
0x30
))
{
ccData2
&=
0xF
;
stringBuilder
.
append
((
char
)
SPECIAL_CHARACTER_SET
[
ccData2
]);
continue
;
}
// Control character.
if
(
ccData1
<
0x20
)
{
if
(
stringBuilder
.
length
()
>
0
)
{
captions
.
add
(
new
ClosedCaption
(
ClosedCaption
.
TYPE_TEXT
,
stringBuilder
.
toString
()));
stringBuilder
.
setLength
(
0
);
}
captions
.
add
(
new
ClosedCaption
(
ClosedCaption
.
TYPE_CTRL
,
new
String
(
new
char
[]{(
char
)
ccData1
,
(
char
)
ccData2
})));
continue
;
}
stringBuilder
.
append
((
char
)
ccData1
);
if
(
ccData2
!=
0
)
{
stringBuilder
.
append
((
char
)
ccData2
);
}
}
if
(
stringBuilder
.
length
()
>
0
)
{
captions
.
add
(
new
ClosedCaption
(
ClosedCaption
.
TYPE_TEXT
,
stringBuilder
.
toString
()));
}
return
Collections
.
unmodifiableList
(
captions
);
}
/**
* Parses the beginning of SEI data and returns the size of underlying contains closed captions
* data following the header. Returns 0 if the SEI doesn't contain any closed captions data.
*
* @param seiBuffer The buffer to read from.
* @return The size of closed captions data.
*/
public
static
int
parseHeader
(
BitArray
seiBuffer
)
{
int
b
=
0
;
int
payloadType
=
0
;
do
{
b
=
seiBuffer
.
readUnsignedByte
();
payloadType
+=
b
;
}
while
(
b
==
0xFF
);
if
(
payloadType
!=
PAYLOAD_TYPE_CC
)
{
return
0
;
}
int
payloadSize
=
0
;
do
{
b
=
seiBuffer
.
readUnsignedByte
();
payloadSize
+=
b
;
}
while
(
b
==
0xFF
);
if
(
payloadSize
<=
0
)
{
return
0
;
}
int
countryCode
=
seiBuffer
.
readUnsignedByte
();
if
(
countryCode
!=
COUNTRY_CODE
)
{
return
0
;
}
int
providerCode
=
seiBuffer
.
readBits
(
16
);
if
(
providerCode
!=
PROVIDER_CODE
)
{
return
0
;
}
int
userIdentifier
=
seiBuffer
.
readBits
(
32
);
if
(
userIdentifier
!=
USER_ID
)
{
return
0
;
}
int
userDataTypeCode
=
seiBuffer
.
readUnsignedByte
();
if
(
userDataTypeCode
!=
USER_DATA_TYPE_CODE
)
{
return
0
;
}
return
payloadSize
;
}
}
library/src/main/java/com/google/android/exoplayer/metadata/Id3Parser.java
View file @
15d3df6a
...
...
@@ -27,7 +27,7 @@ import java.util.Map;
/**
* Extracts individual TXXX text frames from raw ID3 data.
*/
public
class
Id3Parser
implements
MetadataParser
{
public
class
Id3Parser
implements
MetadataParser
<
Map
<
String
,
Object
>>
{
@Override
public
boolean
canParse
(
String
mimeType
)
{
...
...
library/src/main/java/com/google/android/exoplayer/metadata/MetadataParser.java
View file @
15d3df6a
...
...
@@ -16,30 +16,30 @@
package
com
.
google
.
android
.
exoplayer
.
metadata
;
import
java.io.IOException
;
import
java.util.Map
;
/**
* Parses metadata objects from binary data.
* Parses objects of type <T> from binary data.
*
* @param <T> The type of the metadata.
*/
public
interface
MetadataParser
{
public
interface
MetadataParser
<
T
>
{
/**
* Checks whether the parser supports a given mime type.
*
* @param mimeType A
subtitle
mime type.
* @param mimeType A
metadata
mime type.
* @return Whether the mime type is supported.
*/
public
boolean
canParse
(
String
mimeType
);
/**
* Parses
a map of metadata type to metadata objects
from the provided binary data.
* Parses
metadata objects of type <T>
from the provided binary data.
*
* @param data The raw binary data from which to parse the metadata.
* @param size The size of the input data.
* @return
A parsed {@link Map} of metadata type to metadata objects
.
* @return
@return A parsed metadata object of type <T>
.
* @throws IOException If a problem occurred parsing the data.
*/
public
Map
<
String
,
Object
>
parse
(
byte
[]
data
,
int
size
)
throws
IOException
;
public
T
parse
(
byte
[]
data
,
int
size
)
throws
IOException
;
}
library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java
View file @
15d3df6a
...
...
@@ -28,32 +28,35 @@ import android.os.Looper;
import
android.os.Message
;
import
java.io.IOException
;
import
java.util.Map
;
/**
* A {@link TrackRenderer} for metadata embedded in a media stream.
*
* @param <T> The type of the metadata.
*/
public
class
MetadataTrackRenderer
extends
TrackRenderer
implements
Callback
{
public
class
MetadataTrackRenderer
<
T
>
extends
TrackRenderer
implements
Callback
{
/**
* An interface for components that process metadata.
*
* @param <T> The type of the metadata.
*/
public
interface
MetadataRenderer
{
public
interface
MetadataRenderer
<
T
>
{
/**
* Invoked each time there is a metadata associated with current playback time.
*
* @param metadata The metadata to process.
*/
void
onMetadata
(
Map
<
String
,
Object
>
metadata
);
void
onMetadata
(
T
metadata
);
}
private
static
final
int
MSG_INVOKE_RENDERER
=
0
;
private
final
SampleSource
source
;
private
final
MetadataParser
metadataParser
;
private
final
MetadataRenderer
metadataRenderer
;
private
final
MetadataParser
<
T
>
metadataParser
;
private
final
MetadataRenderer
<
T
>
metadataRenderer
;
private
final
Handler
metadataHandler
;
private
final
MediaFormatHolder
formatHolder
;
private
final
SampleHolder
sampleHolder
;
...
...
@@ -63,7 +66,7 @@ public class MetadataTrackRenderer extends TrackRenderer implements Callback {
private
boolean
inputStreamEnded
;
private
long
pendingMetadataTimestamp
;
private
Map
<
String
,
Object
>
pendingMetadata
;
private
T
pendingMetadata
;
/**
* @param source A source from which samples containing metadata can be read.
...
...
@@ -75,8 +78,8 @@ public class MetadataTrackRenderer extends TrackRenderer implements Callback {
* obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the
* renderer should be invoked directly on the player's internal rendering thread.
*/
public
MetadataTrackRenderer
(
SampleSource
source
,
MetadataParser
metadataParser
,
MetadataRenderer
metadataRenderer
,
Looper
metadataRendererLooper
)
{
public
MetadataTrackRenderer
(
SampleSource
source
,
MetadataParser
<
T
>
metadataParser
,
MetadataRenderer
<
T
>
metadataRenderer
,
Looper
metadataRendererLooper
)
{
this
.
source
=
Assertions
.
checkNotNull
(
source
);
this
.
metadataParser
=
Assertions
.
checkNotNull
(
metadataParser
);
this
.
metadataRenderer
=
Assertions
.
checkNotNull
(
metadataRenderer
);
...
...
@@ -185,7 +188,7 @@ public class MetadataTrackRenderer extends TrackRenderer implements Callback {
return
true
;
}
private
void
invokeRenderer
(
Map
<
String
,
Object
>
metadata
)
{
private
void
invokeRenderer
(
T
metadata
)
{
if
(
metadataHandler
!=
null
)
{
metadataHandler
.
obtainMessage
(
MSG_INVOKE_RENDERER
,
metadata
).
sendToTarget
();
}
else
{
...
...
@@ -198,13 +201,13 @@ public class MetadataTrackRenderer extends TrackRenderer implements Callback {
public
boolean
handleMessage
(
Message
msg
)
{
switch
(
msg
.
what
)
{
case
MSG_INVOKE_RENDERER:
invokeRendererInternal
((
Map
<
String
,
Object
>
)
msg
.
obj
);
invokeRendererInternal
((
T
)
msg
.
obj
);
return
true
;
}
return
false
;
}
private
void
invokeRendererInternal
(
Map
<
String
,
Object
>
metadata
)
{
private
void
invokeRendererInternal
(
T
metadata
)
{
metadataRenderer
.
onMetadata
(
metadata
);
}
...
...
library/src/main/java/com/google/android/exoplayer/util/MimeTypes.java
View file @
15d3df6a
...
...
@@ -38,6 +38,7 @@ public class MimeTypes {
public
static
final
String
TEXT_VTT
=
BASE_TYPE_TEXT
+
"/vtt"
;
public
static
final
String
APPLICATION_ID3
=
BASE_TYPE_APPLICATION
+
"/id3"
;
public
static
final
String
APPLICATION_EIA608
=
BASE_TYPE_APPLICATION
+
"/eia-608"
;
public
static
final
String
APPLICATION_TTML
=
BASE_TYPE_APPLICATION
+
"/ttml+xml"
;
private
MimeTypes
()
{}
...
...
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