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
b610e114
authored
Jan 05, 2018
by
Drew Hill
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
PGS subtitle decoding support
parent
a1bac99f
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
317 additions
and
1 deletions
library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsBuilder.java
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsDecoder.java
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsSubtitle.java
library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java
View file @
b610e114
...
...
@@ -19,6 +19,7 @@ import com.google.android.exoplayer2.Format;
import
com.google.android.exoplayer2.text.cea.Cea608Decoder
;
import
com.google.android.exoplayer2.text.cea.Cea708Decoder
;
import
com.google.android.exoplayer2.text.dvb.DvbDecoder
;
import
com.google.android.exoplayer2.text.pgs.PgsDecoder
;
import
com.google.android.exoplayer2.text.ssa.SsaDecoder
;
import
com.google.android.exoplayer2.text.subrip.SubripDecoder
;
import
com.google.android.exoplayer2.text.ttml.TtmlDecoder
;
...
...
@@ -80,7 +81,8 @@ public interface SubtitleDecoderFactory {
||
MimeTypes
.
APPLICATION_CEA608
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_MP4CEA608
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_CEA708
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_DVBSUBS
.
equals
(
mimeType
);
||
MimeTypes
.
APPLICATION_DVBSUBS
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_PGS
.
equals
(
mimeType
);
}
@Override
...
...
@@ -105,6 +107,8 @@ public interface SubtitleDecoderFactory {
return
new
Cea708Decoder
(
format
.
accessibilityChannel
);
case
MimeTypes
.
APPLICATION_DVBSUBS
:
return
new
DvbDecoder
(
format
.
initializationData
);
case
MimeTypes
.
APPLICATION_PGS
:
return
new
PgsDecoder
();
default
:
throw
new
IllegalArgumentException
(
"Attempted to create decoder for unsupported format"
);
}
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsBuilder.java
0 → 100644
View file @
b610e114
/*
*
* Sources for this implementation PGS decoding can be founder below
*
* http://exar.ch/suprip/hddvd.php
* http://forum.doom9.org/showthread.php?t=124105
* http://www.equasys.de/colorconversion.html
*/
package
com
.
google
.
android
.
exoplayer2
.
text
.
pgs
;
import
android.graphics.Bitmap
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
java.nio.ByteBuffer
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.List
;
class
PgsBuilder
{
private
static
final
int
SECTION_PALETTE
=
0x14
;
private
static
final
int
SECTION_BITMAP_PICTURE
=
0x15
;
private
static
final
int
SECTION_IDENTIFIER
=
0x16
;
private
static
final
int
SECTION_END
=
0x80
;
private
List
<
Holder
>
list
=
new
ArrayList
<>();
private
Holder
holder
=
new
Holder
();
boolean
readNextSection
(
ParsableByteArray
buffer
)
{
if
(
buffer
.
bytesLeft
()
<
3
)
return
false
;
int
sectionId
=
buffer
.
readUnsignedByte
();
int
sectionLength
=
buffer
.
readUnsignedShort
();
switch
(
sectionId
)
{
case
SECTION_PALETTE:
holder
.
parsePaletteIndexes
(
buffer
,
sectionLength
);
break
;
case
SECTION_BITMAP_PICTURE:
holder
.
fetchBitmapData
(
buffer
,
sectionLength
);
break
;
case
SECTION_IDENTIFIER:
holder
.
fetchIdentifierData
(
buffer
,
sectionLength
);
break
;
case
SECTION_END:
list
.
add
(
holder
);
holder
=
new
Holder
();
break
;
default
:
buffer
.
skipBytes
(
Math
.
min
(
sectionLength
,
buffer
.
bytesLeft
()));
break
;
}
return
true
;
}
public
Subtitle
build
()
{
if
(
list
.
isEmpty
())
return
new
PgsSubtitle
();
Cue
[]
cues
=
new
Cue
[
list
.
size
()];
long
[]
cueStartTimes
=
new
long
[
list
.
size
()];
int
index
=
0
;
for
(
Holder
curr
:
list
)
{
cues
[
index
]
=
curr
.
build
();
cueStartTimes
[
index
++]
=
curr
.
start_time
;
}
return
new
PgsSubtitle
(
cues
,
cueStartTimes
);
}
private
class
Holder
{
private
int
[]
colors
=
null
;
private
ByteBuffer
rle
=
null
;
Bitmap
bitmap
=
null
;
int
plane_width
=
0
;
int
plane_height
=
0
;
int
bitmap_width
=
0
;
int
bitmap_height
=
0
;
public
int
x
=
0
;
public
int
y
=
0
;
long
start_time
=
0
;
public
Cue
build
()
{
if
(
rle
==
null
||
!
createBitmap
(
new
ParsableByteArray
(
rle
.
array
(),
rle
.
position
())))
return
null
;
float
left
=
(
float
)
x
/
plane_width
;
float
top
=
(
float
)
y
/
plane_height
;
return
new
Cue
(
bitmap
,
left
,
Cue
.
ANCHOR_TYPE_START
,
top
,
Cue
.
ANCHOR_TYPE_START
,
(
float
)
bitmap_width
/
plane_width
,
(
float
)
bitmap_height
/
plane_height
);
}
private
void
parsePaletteIndexes
(
ParsableByteArray
buffer
,
int
dataSize
)
{
// must be a multi of 5 for index, y, cb, cr, alpha
if
(
dataSize
==
0
||
(
dataSize
-
2
)
%
5
!=
0
)
return
;
// skip first two bytes
buffer
.
skipBytes
(
2
);
dataSize
-=
2
;
colors
=
new
int
[
256
];
while
(
dataSize
>
0
)
{
int
index
=
buffer
.
readUnsignedByte
();
int
color_y
=
buffer
.
readUnsignedByte
()
-
16
;
int
color_cr
=
buffer
.
readUnsignedByte
()
-
128
;
int
color_cb
=
buffer
.
readUnsignedByte
()
-
128
;
int
color_alpha
=
buffer
.
readUnsignedByte
();
dataSize
-=
5
;
if
(
index
>=
colors
.
length
)
continue
;
int
color_r
=
(
int
)
Math
.
min
(
Math
.
max
(
Math
.
round
(
1.1644
*
color_y
+
1.793
*
color_cr
),
0
),
255
);
int
color_g
=
(
int
)
Math
.
min
(
Math
.
max
(
Math
.
round
(
1.1644
*
color_y
+
(-
0.213
*
color_cr
)
+
(-
0.533
*
color_cb
)),
0
),
255
);
int
color_b
=
(
int
)
Math
.
min
(
Math
.
max
(
Math
.
round
(
1.1644
*
color_y
+
2.112
*
color_cb
),
0
),
255
);
//ARGB_8888
colors
[
index
]
=
(
color_alpha
<<
24
)
|
(
color_r
<<
16
)
|
(
color_g
<<
8
)
|
color_b
;
}
}
private
void
fetchBitmapData
(
ParsableByteArray
buffer
,
int
dataSize
)
{
if
(
dataSize
<=
4
)
{
buffer
.
skipBytes
(
dataSize
);
return
;
}
// skip id field (2 bytes)
// skip version field
buffer
.
skipBytes
(
3
);
dataSize
-=
3
;
// check to see if this section is an appended section of the base section with
// width and height values
dataSize
-=
1
;
// decrement first
if
((
0x80
&
buffer
.
readUnsignedByte
())
>
0
)
{
if
(
dataSize
<
3
)
{
buffer
.
skipBytes
(
dataSize
);
return
;
}
int
full_len
=
buffer
.
readUnsignedInt24
();
dataSize
-=
3
;
if
(
full_len
<=
4
)
{
buffer
.
skipBytes
(
dataSize
);
return
;
}
bitmap_width
=
buffer
.
readUnsignedShort
();
dataSize
-=
2
;
bitmap_height
=
buffer
.
readUnsignedShort
();
dataSize
-=
2
;
rle
=
ByteBuffer
.
allocate
(
full_len
-
4
);
// don't include width & height
buffer
.
readBytes
(
rle
,
Math
.
min
(
dataSize
,
rle
.
capacity
()));
}
else
if
(
rle
!=
null
)
{
int
postSkip
=
dataSize
>
rle
.
capacity
()
?
dataSize
-
rle
.
capacity
()
:
0
;
buffer
.
readBytes
(
rle
,
Math
.
min
(
dataSize
,
rle
.
capacity
()));
buffer
.
skipBytes
(
postSkip
);
}
}
private
void
fetchIdentifierData
(
ParsableByteArray
buffer
,
int
dataSize
)
{
if
(
dataSize
<
4
)
{
buffer
.
skipBytes
(
dataSize
);
return
;
}
plane_width
=
buffer
.
readUnsignedShort
();
plane_height
=
buffer
.
readUnsignedShort
();
dataSize
-=
4
;
if
(
dataSize
<
15
)
{
buffer
.
skipBytes
(
dataSize
);
return
;
}
// skip next 11 bytes
buffer
.
skipBytes
(
11
);
x
=
buffer
.
readUnsignedShort
();
y
=
buffer
.
readUnsignedShort
();
dataSize
-=
15
;
buffer
.
skipBytes
(
dataSize
);
}
private
boolean
createBitmap
(
ParsableByteArray
rle
)
{
if
(
bitmap_width
==
0
||
bitmap_height
==
0
||
rle
==
null
||
rle
.
bytesLeft
()
==
0
||
colors
==
null
||
colors
.
length
==
0
)
return
false
;
int
[]
argb
=
new
int
[
bitmap_width
*
bitmap_height
];
int
currPixel
=
0
;
int
nextbits
,
pixel_code
,
switchbits
;
int
number_of_pixels
;
int
line
=
0
;
while
(
rle
.
bytesLeft
()
>
0
&&
line
<
bitmap_height
)
{
boolean
end_of_line
=
false
;
do
{
nextbits
=
rle
.
readUnsignedByte
();
if
(
nextbits
!=
0
)
{
pixel_code
=
nextbits
;
number_of_pixels
=
1
;
}
else
{
switchbits
=
rle
.
readUnsignedByte
();
if
((
switchbits
&
0x80
)
==
0
)
{
pixel_code
=
0
;
if
((
switchbits
&
0x40
)
==
0
)
{
if
(
switchbits
>
0
)
{
number_of_pixels
=
switchbits
;
}
else
{
end_of_line
=
true
;
++
line
;
continue
;
}
}
else
{
number_of_pixels
=
((
switchbits
&
0x3f
)
<<
8
)
|
rle
.
readUnsignedByte
();
}
}
else
{
if
((
switchbits
&
0x40
)
==
0
)
{
number_of_pixels
=
switchbits
&
0x3f
;
pixel_code
=
rle
.
readUnsignedByte
();
}
else
{
number_of_pixels
=
((
switchbits
&
0x3f
)
<<
8
)
|
rle
.
readUnsignedByte
();
pixel_code
=
rle
.
readUnsignedByte
();
}
}
}
Arrays
.
fill
(
argb
,
currPixel
,
currPixel
+
number_of_pixels
,
colors
[
pixel_code
]);
currPixel
+=
number_of_pixels
;
}
while
(!
end_of_line
);
}
bitmap
=
Bitmap
.
createBitmap
(
argb
,
0
,
bitmap_width
,
bitmap_width
,
bitmap_height
,
Bitmap
.
Config
.
ARGB_8888
);
return
bitmap
!=
null
;
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsDecoder.java
0 → 100644
View file @
b610e114
package
com
.
google
.
android
.
exoplayer2
.
text
.
pgs
;
import
com.google.android.exoplayer2.text.SimpleSubtitleDecoder
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
@SuppressWarnings
(
"unused"
)
public
class
PgsDecoder
extends
SimpleSubtitleDecoder
{
@SuppressWarnings
(
"unused"
)
public
PgsDecoder
()
{
super
(
"PgsDecoder"
);
}
@Override
protected
Subtitle
decode
(
byte
[]
data
,
int
size
,
boolean
reset
)
throws
SubtitleDecoderException
{
ParsableByteArray
buffer
=
new
ParsableByteArray
(
data
,
size
);
PgsBuilder
builder
=
new
PgsBuilder
();
do
{
if
(!
builder
.
readNextSection
(
buffer
))
break
;
}
while
(
buffer
.
bytesLeft
()
>
0
);
return
builder
.
build
();
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsSubtitle.java
0 → 100644
View file @
b610e114
package
com
.
google
.
android
.
exoplayer2
.
text
.
pgs
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Collections
;
import
java.util.List
;
public
class
PgsSubtitle
implements
Subtitle
{
private
final
Cue
[]
cues
;
private
final
long
[]
cueTimesUs
;
PgsSubtitle
()
{
this
.
cues
=
null
;
this
.
cueTimesUs
=
new
long
[
0
];
}
PgsSubtitle
(
Cue
[]
cues
,
long
[]
cueTimesUs
)
{
this
.
cues
=
cues
;
this
.
cueTimesUs
=
cueTimesUs
;
}
@Override
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
int
index
=
Util
.
binarySearchCeil
(
cueTimesUs
,
timeUs
,
false
,
false
);
return
index
<
cueTimesUs
.
length
?
index
:
-
1
;
}
@Override
public
int
getEventTimeCount
()
{
return
cueTimesUs
.
length
;
}
@Override
public
long
getEventTime
(
int
index
)
{
Assertions
.
checkArgument
(
index
>=
0
);
Assertions
.
checkArgument
(
index
<
cueTimesUs
.
length
);
return
cueTimesUs
[
index
];
}
@Override
public
List
<
Cue
>
getCues
(
long
timeUs
)
{
int
index
=
Util
.
binarySearchFloor
(
cueTimesUs
,
timeUs
,
true
,
false
);
if
(
index
==
-
1
||
cues
==
null
||
cues
[
index
]
==
null
)
{
// timeUs is earlier than the start of the first cue, or we have an empty cue.
return
Collections
.
emptyList
();
}
else
return
Collections
.
singletonList
(
cues
[
index
]);
}
}
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