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
54b4df70
authored
Dec 01, 2016
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge branch 'RikHeijdens-eia-608-improvements' into dev-v2
parents
7a18738d
a6ccedf7
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
372 additions
and
114 deletions
library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
library/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java
library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
library/src/main/java/com/google/android/exoplayer2/text/cea/Cea608Decoder.java
View file @
54b4df70
...
@@ -15,7 +15,18 @@
...
@@ -15,7 +15,18 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
text
.
cea
;
package
com
.
google
.
android
.
exoplayer2
.
text
.
cea
;
import
android.text.TextUtils
;
import
static
com
.
google
.
android
.
exoplayer2
.
text
.
Cue
.
TYPE_UNSET
;
import
android.graphics.Color
;
import
android.graphics.Typeface
;
import
android.text.Layout.Alignment
;
import
android.text.SpannableString
;
import
android.text.SpannableStringBuilder
;
import
android.text.Spanned
;
import
android.text.style.CharacterStyle
;
import
android.text.style.ForegroundColorSpan
;
import
android.text.style.StyleSpan
;
import
android.text.style.UnderlineSpan
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.Subtitle
;
...
@@ -23,14 +34,15 @@ import com.google.android.exoplayer2.text.SubtitleDecoder;
...
@@ -23,14 +34,15 @@ import com.google.android.exoplayer2.text.SubtitleDecoder;
import
com.google.android.exoplayer2.text.SubtitleInputBuffer
;
import
com.google.android.exoplayer2.text.SubtitleInputBuffer
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
java.util.ArrayList
;
import
java.util.LinkedList
;
import
java.util.List
;
/**
/**
* A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608").
* A {@link SubtitleDecoder} for CEA-608 (also known as "line 21 captions" and "EIA-608").
*/
*/
public
final
class
Cea608Decoder
extends
CeaDecoder
{
public
final
class
Cea608Decoder
extends
CeaDecoder
{
private
static
final
String
TAG
=
"Cea608Decoder"
;
private
static
final
int
CC_VALID_FLAG
=
0x04
;
private
static
final
int
CC_VALID_FLAG
=
0x04
;
private
static
final
int
CC_TYPE_FLAG
=
0x02
;
private
static
final
int
CC_TYPE_FLAG
=
0x02
;
private
static
final
int
CC_FIELD_FLAG
=
0x01
;
private
static
final
int
CC_FIELD_FLAG
=
0x01
;
...
@@ -50,6 +62,18 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -50,6 +62,18 @@ public final class Cea608Decoder extends CeaDecoder {
private
static
final
int
CC_MODE_POP_ON
=
2
;
private
static
final
int
CC_MODE_POP_ON
=
2
;
private
static
final
int
CC_MODE_PAINT_ON
=
3
;
private
static
final
int
CC_MODE_PAINT_ON
=
3
;
private
static
final
int
[]
ROW_INDICES
=
new
int
[]
{
11
,
1
,
3
,
12
,
14
,
5
,
7
,
9
};
private
static
final
int
[]
COLUMN_INDICES
=
new
int
[]
{
0
,
4
,
8
,
12
,
16
,
20
,
24
,
28
};
private
static
final
int
[]
COLORS
=
new
int
[]
{
Color
.
WHITE
,
Color
.
GREEN
,
Color
.
BLUE
,
Color
.
CYAN
,
Color
.
RED
,
Color
.
YELLOW
,
Color
.
MAGENTA
,
};
// The default number of rows to display in roll-up captions mode.
// The default number of rows to display in roll-up captions mode.
private
static
final
int
DEFAULT_CAPTIONS_ROW_COUNT
=
4
;
private
static
final
int
DEFAULT_CAPTIONS_ROW_COUNT
=
4
;
...
@@ -94,12 +118,10 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -94,12 +118,10 @@ public final class Cea608Decoder extends CeaDecoder {
private
static
final
byte
CTRL_ERASE_DISPLAYED_MEMORY
=
0x2C
;
private
static
final
byte
CTRL_ERASE_DISPLAYED_MEMORY
=
0x2C
;
private
static
final
byte
CTRL_CARRIAGE_RETURN
=
0x2D
;
private
static
final
byte
CTRL_CARRIAGE_RETURN
=
0x2D
;
private
static
final
byte
CTRL_ERASE_NON_DISPLAYED_MEMORY
=
0x2E
;
private
static
final
byte
CTRL_ERASE_NON_DISPLAYED_MEMORY
=
0x2E
;
private
static
final
byte
CTRL_DELETE_TO_END_OF_ROW
=
0x24
;
private
static
final
byte
CTRL_BACKSPACE
=
0x21
;
private
static
final
byte
CTRL_BACKSPACE
=
0x21
;
private
static
final
byte
CTRL_MISC_CHAN_1
=
0x14
;
private
static
final
byte
CTRL_MISC_CHAN_2
=
0x1C
;
// Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20).
// Basic North American 608 CC char set, mostly ASCII. Indexed by (char-0x20).
private
static
final
int
[]
BASIC_CHARACTER_SET
=
new
int
[]
{
private
static
final
int
[]
BASIC_CHARACTER_SET
=
new
int
[]
{
0x20
,
0x21
,
0x22
,
0x23
,
0x24
,
0x25
,
0x26
,
0x27
,
// ! " # $ % & '
0x20
,
0x21
,
0x22
,
0x23
,
0x24
,
0x25
,
0x26
,
0x27
,
// ! " # $ % & '
...
@@ -169,15 +191,16 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -169,15 +191,16 @@ public final class Cea608Decoder extends CeaDecoder {
};
};
private
final
ParsableByteArray
ccData
;
private
final
ParsableByteArray
ccData
;
private
final
StringBuilder
captionStringBuilder
;
private
final
int
packetLength
;
private
final
int
packetLength
;
private
final
int
selectedField
;
private
final
int
selectedField
;
private
final
LinkedList
<
CueBuilder
>
cueBuilders
;
private
CueBuilder
currentCueBuilder
;
private
List
<
Cue
>
cues
;
private
List
<
Cue
>
lastCues
;
private
int
captionMode
;
private
int
captionMode
;
private
int
captionRowCount
;
private
int
captionRowCount
;
private
String
captionString
;
private
String
lastCaptionString
;
private
boolean
repeatableControlSet
;
private
boolean
repeatableControlSet
;
private
byte
repeatableControlCc1
;
private
byte
repeatableControlCc1
;
...
@@ -185,8 +208,8 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -185,8 +208,8 @@ public final class Cea608Decoder extends CeaDecoder {
public
Cea608Decoder
(
String
mimeType
,
int
accessibilityChannel
)
{
public
Cea608Decoder
(
String
mimeType
,
int
accessibilityChannel
)
{
ccData
=
new
ParsableByteArray
();
ccData
=
new
ParsableByteArray
();
c
aptionStringBuilder
=
new
StringBuilder
();
c
ueBuilders
=
new
LinkedList
<>
();
currentCueBuilder
=
new
CueBuilder
(
CC_MODE_UNKNOWN
,
DEFAULT_CAPTIONS_ROW_COUNT
);
packetLength
=
MimeTypes
.
APPLICATION_MP4CEA608
.
equals
(
mimeType
)
?
2
:
3
;
packetLength
=
MimeTypes
.
APPLICATION_MP4CEA608
.
equals
(
mimeType
)
?
2
:
3
;
switch
(
accessibilityChannel
)
{
switch
(
accessibilityChannel
)
{
case
3
:
case
3
:
...
@@ -201,7 +224,7 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -201,7 +224,7 @@ public final class Cea608Decoder extends CeaDecoder {
}
}
setCaptionMode
(
CC_MODE_UNKNOWN
);
setCaptionMode
(
CC_MODE_UNKNOWN
);
captionRowCount
=
DEFAULT_CAPTIONS_ROW_COUNT
;
resetCueBuilders
()
;
}
}
@Override
@Override
...
@@ -212,11 +235,11 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -212,11 +235,11 @@ public final class Cea608Decoder extends CeaDecoder {
@Override
@Override
public
void
flush
()
{
public
void
flush
()
{
super
.
flush
();
super
.
flush
();
cues
=
null
;
lastCues
=
null
;
setCaptionMode
(
CC_MODE_UNKNOWN
);
setCaptionMode
(
CC_MODE_UNKNOWN
);
resetCueBuilders
();
captionRowCount
=
DEFAULT_CAPTIONS_ROW_COUNT
;
captionRowCount
=
DEFAULT_CAPTIONS_ROW_COUNT
;
captionStringBuilder
.
setLength
(
0
);
captionString
=
null
;
lastCaptionString
=
null
;
repeatableControlSet
=
false
;
repeatableControlSet
=
false
;
repeatableControlCc1
=
0
;
repeatableControlCc1
=
0
;
repeatableControlCc2
=
0
;
repeatableControlCc2
=
0
;
...
@@ -229,13 +252,13 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -229,13 +252,13 @@ public final class Cea608Decoder extends CeaDecoder {
@Override
@Override
protected
boolean
isNewSubtitleDataAvailable
()
{
protected
boolean
isNewSubtitleDataAvailable
()
{
return
!
TextUtils
.
equals
(
captionString
,
lastCaptionString
)
;
return
cues
!=
lastCues
;
}
}
@Override
@Override
protected
Subtitle
createSubtitle
()
{
protected
Subtitle
createSubtitle
()
{
lastC
aptionString
=
captionString
;
lastC
ues
=
cues
;
return
new
CeaSubtitle
(
new
Cue
(
captionString
)
);
return
new
CeaSubtitle
(
cues
);
}
}
@Override
@Override
...
@@ -246,10 +269,13 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -246,10 +269,13 @@ public final class Cea608Decoder extends CeaDecoder {
while
(
ccData
.
bytesLeft
()
>=
packetLength
)
{
while
(
ccData
.
bytesLeft
()
>=
packetLength
)
{
byte
ccDataHeader
=
packetLength
==
2
?
CC_IMPLICIT_DATA_HEADER
byte
ccDataHeader
=
packetLength
==
2
?
CC_IMPLICIT_DATA_HEADER
:
(
byte
)
ccData
.
readUnsignedByte
();
:
(
byte
)
ccData
.
readUnsignedByte
();
byte
ccData1
=
(
byte
)
(
ccData
.
readUnsignedByte
()
&
0x7F
);
byte
ccData1
=
(
byte
)
(
ccData
.
readUnsignedByte
()
&
0x7F
);
// strip the parity bit
byte
ccData2
=
(
byte
)
(
ccData
.
readUnsignedByte
()
&
0x7F
);
byte
ccData2
=
(
byte
)
(
ccData
.
readUnsignedByte
()
&
0x7F
);
// strip the parity bit
// Only examine valid CEA-608 packets
// Only examine valid CEA-608 packets
// TODO: We're currently ignoring the top 5 marker bits, which should all be 1s according
// to the CEA-608 specification. We need to determine if the data should be handled
// differently when that is not the case.
if
((
ccDataHeader
&
(
CC_VALID_FLAG
|
CC_TYPE_FLAG
))
!=
CC_VALID_608_ID
)
{
if
((
ccDataHeader
&
(
CC_VALID_FLAG
|
CC_TYPE_FLAG
))
!=
CC_VALID_608_ID
)
{
continue
;
continue
;
}
}
...
@@ -264,49 +290,47 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -264,49 +290,47 @@ public final class Cea608Decoder extends CeaDecoder {
if
(
ccData1
==
0
&&
ccData2
==
0
)
{
if
(
ccData1
==
0
&&
ccData2
==
0
)
{
continue
;
continue
;
}
}
// If we've reached this point then there is data to process; flag that work has been done.
// If we've reached this point then there is data to process; flag that work has been done.
captionDataProcessed
=
true
;
captionDataProcessed
=
true
;
// Special North American character set.
// Special North American character set.
// ccData1 -
P
|0|0|1|C|0|0|1
// ccData1 -
0
|0|0|1|C|0|0|1
// ccData2 -
P
|0|1|1|X|X|X|X
// ccData2 -
0
|0|1|1|X|X|X|X
if
((
ccData1
==
0x11
||
ccData1
==
0x19
)
&&
((
ccData2
&
0x7
0
)
==
0x30
))
{
if
((
(
ccData1
&
0xF7
)
==
0x11
)
&&
((
ccData2
&
0xF
0
)
==
0x30
))
{
// TODO: Make use of the channel
bit
// TODO: Make use of the channel
toggle
c
aptionString
Builder
.
append
(
getSpecialChar
(
ccData2
));
c
urrentCue
Builder
.
append
(
getSpecialChar
(
ccData2
));
continue
;
continue
;
}
}
// Extended Western European character set.
// Extended Western European character set.
// ccData1 - P|0|0|1|C|0|1|S
// ccData1 - 0|0|0|1|C|0|1|S
// ccData2 - P|0|1|X|X|X|X|X
// ccData2 - 0|0|1|X|X|X|X|X
if
((
ccData2
&
0x60
)
==
0x20
)
{
if
(((
ccData1
&
0xF6
)
==
0x12
)
&&
(
ccData2
&
0xE0
)
==
0x20
)
{
// TODO: Make use of the channel toggle
// Remove standard equivalent of the special extended char before appending new one
currentCueBuilder
.
backspace
();
if
((
ccData1
&
0x01
)
==
0x00
)
{
// Extended Spanish/Miscellaneous and French character set (S = 0).
// Extended Spanish/Miscellaneous and French character set (S = 0).
if
(
ccData1
==
0x12
||
ccData1
==
0x1A
)
{
currentCueBuilder
.
append
(
getExtendedEsFrChar
(
ccData2
));
// TODO: Make use of the channel bit
}
else
{
backspace
();
// Remove standard equivalent of the special extended char.
captionStringBuilder
.
append
(
getExtendedEsFrChar
(
ccData2
));
continue
;
}
// Extended Portuguese and German/Danish character set (S = 1).
// Extended Portuguese and German/Danish character set (S = 1).
if
(
ccData1
==
0x13
||
ccData1
==
0x1B
)
{
currentCueBuilder
.
append
(
getExtendedPtDeChar
(
ccData2
));
// TODO: Make use of the channel bit
backspace
();
// Remove standard equivalent of the special extended char.
captionStringBuilder
.
append
(
getExtendedPtDeChar
(
ccData2
));
continue
;
}
}
continue
;
}
}
// Control character.
// Control character.
if
(
ccData1
<
0x20
)
{
// ccData1 - 0|0|0|X|X|X|X|X
if
((
ccData1
&
0xE0
)
==
0x00
)
{
isRepeatableControl
=
handleCtrl
(
ccData1
,
ccData2
);
isRepeatableControl
=
handleCtrl
(
ccData1
,
ccData2
);
continue
;
continue
;
}
}
// Basic North American character set.
// Basic North American character set.
c
aptionString
Builder
.
append
(
getChar
(
ccData1
));
c
urrentCue
Builder
.
append
(
getChar
(
ccData1
));
if
(
ccData2
>=
0x2
0
)
{
if
(
(
ccData2
&
0xE0
)
!=
0x0
0
)
{
c
aptionString
Builder
.
append
(
getChar
(
ccData2
));
c
urrentCue
Builder
.
append
(
getChar
(
ccData2
));
}
}
}
}
...
@@ -315,34 +339,102 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -315,34 +339,102 @@ public final class Cea608Decoder extends CeaDecoder {
repeatableControlSet
=
false
;
repeatableControlSet
=
false
;
}
}
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
c
aptionString
=
getDisplayCaption
();
c
ues
=
getDisplayCues
();
}
}
}
}
}
}
private
boolean
handleCtrl
(
byte
cc1
,
byte
cc2
)
{
private
boolean
handleCtrl
(
byte
cc1
,
byte
cc2
)
{
boolean
isRepeatableControl
=
isRepeatable
(
cc1
);
boolean
isRepeatableControl
=
isRepeatable
(
cc1
);
// Most control commands are sent twice in succession to ensure they are received properly.
// We don't want to process duplicate commands, so if we see the same repeatable command twice
// in a row, ignore the second one.
if
(
isRepeatableControl
)
{
if
(
isRepeatableControl
)
{
if
(
repeatableControlSet
if
(
repeatableControlSet
&&
repeatableControlCc1
==
cc1
&&
repeatableControlCc1
==
cc1
&&
repeatableControlCc2
==
cc2
)
{
&&
repeatableControlCc2
==
cc2
)
{
// This is a duplicate. Clear the repeatable control flag and return.
repeatableControlSet
=
false
;
repeatableControlSet
=
false
;
return
true
;
return
true
;
}
else
{
}
else
{
// This is a repeatable command, but we haven't see it yet, so set the repeabable control
// flag (to ensure we ignore the next one should it be a duplicate) and continue processing
// the command.
repeatableControlSet
=
true
;
repeatableControlSet
=
true
;
repeatableControlCc1
=
cc1
;
repeatableControlCc1
=
cc1
;
repeatableControlCc2
=
cc2
;
repeatableControlCc2
=
cc2
;
}
}
}
}
if
(
isMiscCode
(
cc1
,
cc2
))
{
handleMiscCode
(
cc2
);
if
(
isMidrowCtrlCode
(
cc1
,
cc2
))
{
handleMidrowCtrl
(
cc2
);
}
else
if
(
isPreambleAddressCode
(
cc1
,
cc2
))
{
}
else
if
(
isPreambleAddressCode
(
cc1
,
cc2
))
{
// TODO: Add better handling of this with specific positioning.
handlePreambleAddressCode
(
cc1
,
cc2
);
maybeAppendNewline
();
}
else
if
(
isTabCtrlCode
(
cc1
,
cc2
))
{
currentCueBuilder
.
tab
(
cc2
-
0x20
);
}
else
if
(
isMiscCode
(
cc1
,
cc2
))
{
handleMiscCode
(
cc2
);
}
}
return
isRepeatableControl
;
return
isRepeatableControl
;
}
}
private
void
handleMidrowCtrl
(
byte
cc2
)
{
// TODO: support the extended styles (i.e. backgrounds and transparencies)
// cc2 - 0|0|1|0|ATRBT|U
// ATRBT is the 3-byte encoded attribute, and U is the underline toggle
boolean
isUnderlined
=
(
cc2
&
0x01
)
==
0x01
;
currentCueBuilder
.
setUnderline
(
isUnderlined
);
int
attribute
=
(
cc2
>>
1
)
&
0x0F
;
if
(
attribute
==
0x07
)
{
currentCueBuilder
.
setMidrowStyle
(
new
StyleSpan
(
Typeface
.
ITALIC
),
2
);
currentCueBuilder
.
setMidrowStyle
(
new
ForegroundColorSpan
(
Color
.
WHITE
),
1
);
}
else
{
currentCueBuilder
.
setMidrowStyle
(
new
ForegroundColorSpan
(
COLORS
[
attribute
]),
1
);
}
}
private
void
handlePreambleAddressCode
(
byte
cc1
,
byte
cc2
)
{
// cc1 - 0|0|0|1|C|E|ROW
// C is the channel toggle, E is the extended flag, and ROW is the encoded row
int
row
=
ROW_INDICES
[
cc1
&
0x07
];
// TODO: Make use of the channel toggle
// TODO: support the extended address and style
// cc2 - 0|1|N|ATTRBTE|U
// N is the next row down toggle, ATTRBTE is the 4-byte encoded attribute, and U is the
// underline toggle
boolean
nextRowDown
=
(
cc2
&
0x20
)
!=
0
;
if
(
row
!=
currentCueBuilder
.
getRow
()
||
nextRowDown
)
{
if
(!
currentCueBuilder
.
isEmpty
())
{
currentCueBuilder
=
new
CueBuilder
(
captionMode
,
captionRowCount
);
cueBuilders
.
add
(
currentCueBuilder
);
}
currentCueBuilder
.
setRow
(
nextRowDown
?
++
row
:
row
);
}
if
((
cc2
&
0x01
)
==
0x01
)
{
currentCueBuilder
.
setPreambleStyle
(
new
UnderlineSpan
());
}
// cc2 - 0|1|N|0|STYLE|U
// cc2 - 0|1|N|1|CURSR|U
int
attribute
=
cc2
>>
1
&
0x0F
;
if
(
attribute
<=
0x07
)
{
if
(
attribute
==
0x07
)
{
currentCueBuilder
.
setPreambleStyle
(
new
StyleSpan
(
Typeface
.
ITALIC
));
currentCueBuilder
.
setPreambleStyle
(
new
ForegroundColorSpan
(
Color
.
WHITE
));
}
else
{
currentCueBuilder
.
setPreambleStyle
(
new
ForegroundColorSpan
(
COLORS
[
attribute
]));
}
}
else
{
currentCueBuilder
.
setIndent
(
COLUMN_INDICES
[
attribute
&
0x07
]);
}
}
private
void
handleMiscCode
(
byte
cc2
)
{
private
void
handleMiscCode
(
byte
cc2
)
{
switch
(
cc2
)
{
switch
(
cc2
)
{
case
CTRL_ROLL_UP_CAPTIONS_2_ROWS:
case
CTRL_ROLL_UP_CAPTIONS_2_ROWS:
...
@@ -371,68 +463,40 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -371,68 +463,40 @@ public final class Cea608Decoder extends CeaDecoder {
switch
(
cc2
)
{
switch
(
cc2
)
{
case
CTRL_ERASE_DISPLAYED_MEMORY:
case
CTRL_ERASE_DISPLAYED_MEMORY:
c
aptionString
=
null
;
c
ues
=
null
;
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_PAINT_ON
)
{
captionStringBuilder
.
setLength
(
0
);
resetCueBuilders
(
);
}
}
break
;
break
;
case
CTRL_ERASE_NON_DISPLAYED_MEMORY:
case
CTRL_ERASE_NON_DISPLAYED_MEMORY:
captionStringBuilder
.
setLength
(
0
);
resetCueBuilders
(
);
break
;
break
;
case
CTRL_END_OF_CAPTION:
case
CTRL_END_OF_CAPTION:
c
aptionString
=
getDisplayCaption
();
c
ues
=
getDisplayCues
();
captionStringBuilder
.
setLength
(
0
);
resetCueBuilders
(
);
break
;
break
;
case
CTRL_CARRIAGE_RETURN:
case
CTRL_CARRIAGE_RETURN:
maybeAppendNewline
();
// carriage returns only apply to rollup captions; don't bother if we don't have anything
// to add a carriage return to
if
(
captionMode
==
CC_MODE_ROLL_UP
&&
!
currentCueBuilder
.
isEmpty
())
{
currentCueBuilder
.
rollUp
();
}
break
;
break
;
case
CTRL_BACKSPACE:
case
CTRL_BACKSPACE:
if
(
captionStringBuilder
.
length
()
>
0
)
{
currentCueBuilder
.
backspace
();
captionStringBuilder
.
setLength
(
captionStringBuilder
.
length
()
-
1
);
break
;
}
case
CTRL_DELETE_TO_END_OF_ROW:
// TODO: implement
break
;
break
;
}
}
}
}
private
void
backspace
()
{
private
List
<
Cue
>
getDisplayCues
()
{
if
(
captionStringBuilder
.
length
()
>
0
)
{
List
<
Cue
>
displayCues
=
new
ArrayList
<>();
captionStringBuilder
.
setLength
(
captionStringBuilder
.
length
()
-
1
);
for
(
int
i
=
0
;
i
<
cueBuilders
.
size
();
i
++)
{
}
displayCues
.
add
(
cueBuilders
.
get
(
i
).
build
());
}
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
)
{
return
displayCues
;
startIndex
=
searchBackwardFromIndex
+
1
;
}
captionStringBuilder
.
delete
(
0
,
startIndex
);
return
captionStringBuilder
.
substring
(
0
,
endIndex
-
startIndex
);
}
}
private
void
setCaptionMode
(
int
captionMode
)
{
private
void
setCaptionMode
(
int
captionMode
)
{
...
@@ -442,20 +506,26 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -442,20 +506,26 @@ public final class Cea608Decoder extends CeaDecoder {
this
.
captionMode
=
captionMode
;
this
.
captionMode
=
captionMode
;
// Clear the working memory.
// Clear the working memory.
captionStringBuilder
.
setLength
(
0
);
resetCueBuilders
(
);
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_UNKNOWN
)
{
if
(
captionMode
==
CC_MODE_ROLL_UP
||
captionMode
==
CC_MODE_UNKNOWN
)
{
// When switching to roll-up or unknown, we also need to clear the caption.
// When switching to roll-up or unknown, we also need to clear the caption.
c
aptionString
=
null
;
c
ues
=
null
;
}
}
}
}
private
void
resetCueBuilders
()
{
currentCueBuilder
.
reset
(
captionMode
,
captionRowCount
);
cueBuilders
.
clear
();
cueBuilders
.
add
(
currentCueBuilder
);
}
private
static
char
getChar
(
byte
ccData
)
{
private
static
char
getChar
(
byte
ccData
)
{
int
index
=
(
ccData
&
0x7F
)
-
0x20
;
int
index
=
(
ccData
&
0x7F
)
-
0x20
;
return
(
char
)
BASIC_CHARACTER_SET
[
index
];
return
(
char
)
BASIC_CHARACTER_SET
[
index
];
}
}
private
static
char
getSpecialChar
(
byte
ccData
)
{
private
static
char
getSpecialChar
(
byte
ccData
)
{
int
index
=
ccData
&
0xF
;
int
index
=
ccData
&
0x
0
F
;
return
(
char
)
SPECIAL_CHARACTER_SET
[
index
];
return
(
char
)
SPECIAL_CHARACTER_SET
[
index
];
}
}
...
@@ -469,17 +539,33 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -469,17 +539,33 @@ public final class Cea608Decoder extends CeaDecoder {
return
(
char
)
SPECIAL_PT_DE_CHARACTER_SET
[
index
];
return
(
char
)
SPECIAL_PT_DE_CHARACTER_SET
[
index
];
}
}
private
static
boolean
isMiscCode
(
byte
cc1
,
byte
cc2
)
{
private
static
boolean
isMidrowCtrlCode
(
byte
cc1
,
byte
cc2
)
{
return
(
cc1
==
CTRL_MISC_CHAN_1
||
cc1
==
CTRL_MISC_CHAN_2
)
// cc1 - 0|0|0|1|C|0|0|1
&&
(
cc2
>=
0x20
&&
cc2
<=
0x2F
);
// cc2 - 0|0|1|0|X|X|X|X
return
((
cc1
&
0xF7
)
==
0x11
)
&&
((
cc2
&
0xF0
)
==
0x20
);
}
}
private
static
boolean
isPreambleAddressCode
(
byte
cc1
,
byte
cc2
)
{
private
static
boolean
isPreambleAddressCode
(
byte
cc1
,
byte
cc2
)
{
return
(
cc1
>=
0x10
&&
cc1
<=
0x1F
)
&&
(
cc2
>=
0x40
&&
cc2
<=
0x7F
);
// cc1 - 0|0|0|1|C|X|X|X
// cc2 - 0|1|X|X|X|X|X|X
return
((
cc1
&
0xF0
)
==
0x10
)
&&
((
cc2
&
0xC0
)
==
0x80
);
}
private
static
boolean
isTabCtrlCode
(
byte
cc1
,
byte
cc2
)
{
// cc1 - 0|0|0|1|C|1|1|1
// cc2 - 0|0|1|0|0|0|0|1 to 0|0|1|0|0|0|1|1
return
((
cc1
&
0xF7
)
==
0x17
)
&&
(
cc2
>=
0x21
&&
cc2
<=
0x23
);
}
private
static
boolean
isMiscCode
(
byte
cc1
,
byte
cc2
)
{
// cc1 - 0|0|0|1|C|1|0|0
// cc2 - 0|0|1|0|X|X|X|X
return
((
cc1
&
0xF7
)
==
0x14
)
&&
((
cc2
&
0xF0
)
==
0x20
);
}
}
private
static
boolean
isRepeatable
(
byte
cc1
)
{
private
static
boolean
isRepeatable
(
byte
cc1
)
{
return
cc1
>=
0x10
&&
cc1
<=
0x1F
;
// cc1 - 0|0|0|1|X|X|X|X
return
(
cc1
&
0xF0
)
==
0x10
;
}
}
/**
/**
...
@@ -507,4 +593,182 @@ public final class Cea608Decoder extends CeaDecoder {
...
@@ -507,4 +593,182 @@ public final class Cea608Decoder extends CeaDecoder {
&&
userIdentifier
==
USER_ID
&&
userDataTypeCode
==
USER_DATA_TYPE_CODE
;
&&
userIdentifier
==
USER_ID
&&
userDataTypeCode
==
USER_DATA_TYPE_CODE
;
}
}
private
static
class
CueBuilder
{
private
static
final
int
POSITION_UNSET
=
-
1
;
// 608 captions define a 15 row by 32 column screen grid. These constants convert from 608
// positions to normalized screen position.
private
static
final
int
SCREEN_CHARWIDTH
=
32
;
private
static
final
int
BASE_ROW
=
15
;
private
final
List
<
CharacterStyle
>
preambleStyles
;
private
final
List
<
CueStyle
>
midrowStyles
;
private
final
List
<
SpannableString
>
rolledUpCaptions
;
private
final
SpannableStringBuilder
captionStringBuilder
;
private
int
row
;
private
int
indent
;
private
int
tabOffset
;
private
int
captionMode
;
private
int
captionRowCount
;
private
int
underlineStartPosition
;
public
CueBuilder
(
int
captionMode
,
int
captionRowCount
)
{
preambleStyles
=
new
ArrayList
<>();
midrowStyles
=
new
ArrayList
<>();
rolledUpCaptions
=
new
LinkedList
<>();
captionStringBuilder
=
new
SpannableStringBuilder
();
reset
(
captionMode
,
captionRowCount
);
}
public
void
reset
(
int
captionMode
,
int
captionRowCount
)
{
preambleStyles
.
clear
();
midrowStyles
.
clear
();
rolledUpCaptions
.
clear
();
captionStringBuilder
.
clear
();
row
=
BASE_ROW
;
indent
=
0
;
tabOffset
=
0
;
this
.
captionMode
=
captionMode
;
this
.
captionRowCount
=
captionRowCount
;
underlineStartPosition
=
POSITION_UNSET
;
}
public
boolean
isEmpty
()
{
return
preambleStyles
.
isEmpty
()
&&
midrowStyles
.
isEmpty
()
&&
rolledUpCaptions
.
isEmpty
()
&&
captionStringBuilder
.
length
()
==
0
;
}
public
void
backspace
()
{
int
length
=
captionStringBuilder
.
length
();
if
(
length
>
0
)
{
captionStringBuilder
.
delete
(
length
-
1
,
length
);
}
}
public
int
getRow
()
{
return
row
;
}
public
void
setRow
(
int
row
)
{
this
.
row
=
row
;
}
public
void
rollUp
()
{
rolledUpCaptions
.
add
(
buildSpannableString
());
captionStringBuilder
.
clear
();
preambleStyles
.
clear
();
midrowStyles
.
clear
();
underlineStartPosition
=
POSITION_UNSET
;
int
numRows
=
Math
.
min
(
captionRowCount
,
row
);
while
(
rolledUpCaptions
.
size
()
>=
numRows
)
{
rolledUpCaptions
.
remove
(
0
);
}
}
public
void
setIndent
(
int
indent
)
{
this
.
indent
=
indent
;
}
public
void
tab
(
int
tabs
)
{
tabOffset
+=
tabs
;
}
public
void
setPreambleStyle
(
CharacterStyle
style
)
{
preambleStyles
.
add
(
style
);
}
public
void
setMidrowStyle
(
CharacterStyle
style
,
int
nextStyleIncrement
)
{
midrowStyles
.
add
(
new
CueStyle
(
style
,
captionStringBuilder
.
length
(),
nextStyleIncrement
));
}
public
void
setUnderline
(
boolean
enabled
)
{
if
(
enabled
)
{
underlineStartPosition
=
captionStringBuilder
.
length
();
}
else
if
(
underlineStartPosition
!=
POSITION_UNSET
)
{
// underline spans won't overlap, so it's safe to modify the builder directly with them
captionStringBuilder
.
setSpan
(
new
UnderlineSpan
(),
underlineStartPosition
,
captionStringBuilder
.
length
(),
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
underlineStartPosition
=
POSITION_UNSET
;
}
}
public
void
append
(
char
text
)
{
captionStringBuilder
.
append
(
text
);
}
public
SpannableString
buildSpannableString
()
{
int
length
=
captionStringBuilder
.
length
();
// preamble styles apply to the entire cue
for
(
int
i
=
0
;
i
<
preambleStyles
.
size
();
i
++)
{
captionStringBuilder
.
setSpan
(
preambleStyles
.
get
(
i
),
0
,
length
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
// midrow styles only apply to part of the cue, and after preamble styles
for
(
int
i
=
0
;
i
<
midrowStyles
.
size
();
i
++)
{
CueStyle
cueStyle
=
midrowStyles
.
get
(
i
);
int
end
=
(
i
<
midrowStyles
.
size
()
-
cueStyle
.
nextStyleIncrement
)
?
midrowStyles
.
get
(
i
+
cueStyle
.
nextStyleIncrement
).
start
:
length
;
captionStringBuilder
.
setSpan
(
cueStyle
.
style
,
cueStyle
.
start
,
end
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
// special case for midrow underlines that went to the end of the cue
if
(
underlineStartPosition
!=
POSITION_UNSET
)
{
captionStringBuilder
.
setSpan
(
new
UnderlineSpan
(),
underlineStartPosition
,
length
,
Spanned
.
SPAN_EXCLUSIVE_EXCLUSIVE
);
}
return
new
SpannableString
(
captionStringBuilder
);
}
public
Cue
build
()
{
SpannableStringBuilder
cueString
=
new
SpannableStringBuilder
();
// add any rolled up captions, separated by new lines
for
(
int
i
=
0
;
i
<
rolledUpCaptions
.
size
();
i
++)
{
cueString
.
append
(
rolledUpCaptions
.
get
(
i
));
cueString
.
append
(
'\n'
);
}
// add the current line
cueString
.
append
(
buildSpannableString
());
float
position
=
(
float
)
(
indent
+
tabOffset
)
/
SCREEN_CHARWIDTH
;
float
line
;
int
lineType
;
if
(
captionMode
==
CC_MODE_ROLL_UP
)
{
line
=
(
row
-
1
)
-
BASE_ROW
;
lineType
=
Cue
.
LINE_TYPE_NUMBER
;
}
else
{
line
=
(
float
)
(
row
-
1
)
/
BASE_ROW
;
lineType
=
Cue
.
LINE_TYPE_FRACTION
;
}
return
new
Cue
(
cueString
,
Alignment
.
ALIGN_NORMAL
,
line
,
lineType
,
TYPE_UNSET
,
position
,
TYPE_UNSET
,
0.8f
);
}
private
static
class
CueStyle
{
public
final
CharacterStyle
style
;
public
final
int
start
;
public
final
int
nextStyleIncrement
;
public
CueStyle
(
CharacterStyle
style
,
int
start
,
int
nextStyleIncrement
)
{
this
.
style
=
style
;
this
.
start
=
start
;
this
.
nextStyleIncrement
=
nextStyleIncrement
;
}
}
}
}
}
library/src/main/java/com/google/android/exoplayer2/text/cea/CeaSubtitle.java
View file @
54b4df70
...
@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.text.cea;
...
@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.text.cea;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.List
;
/**
/**
...
@@ -28,14 +27,10 @@ import java.util.List;
...
@@ -28,14 +27,10 @@ import java.util.List;
private
final
List
<
Cue
>
cues
;
private
final
List
<
Cue
>
cues
;
/**
/**
* @param cue
The subtitle cue
.
* @param cue
s The subtitle cues
.
*/
*/
public
CeaSubtitle
(
Cue
cue
)
{
public
CeaSubtitle
(
List
<
Cue
>
cues
)
{
if
(
cue
==
null
)
{
this
.
cues
=
cues
;
cues
=
Collections
.
emptyList
();
}
else
{
cues
=
Collections
.
singletonList
(
cue
);
}
}
}
@Override
@Override
...
@@ -56,7 +51,6 @@ import java.util.List;
...
@@ -56,7 +51,6 @@ import java.util.List;
@Override
@Override
public
List
<
Cue
>
getCues
(
long
timeUs
)
{
public
List
<
Cue
>
getCues
(
long
timeUs
)
{
return
cues
;
return
cues
;
}
}
}
}
library/src/main/java/com/google/android/exoplayer2/ui/SubtitlePainter.java
View file @
54b4df70
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