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
dc38e869
authored
Jan 15, 2018
by
ojw28
Committed by
GitHub
Jan 15, 2018
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #3673 from drhill/dev-v2_PGS
PGS subtitle decoding support
parents
96b7daa9
b610e114
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 @
dc38e869
...
...
@@ -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 @
dc38e869
/*
*
* 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 @
dc38e869
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 @
dc38e869
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