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
141f3aa8
authored
Jan 15, 2018
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Simplify PGS captions + sync with internal tree
parent
dc38e869
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
317 additions
and
336 deletions
RELEASENOTES.md
demos/main/src/main/assets/media.exolist.json
extensions/vp9/README.md
extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh
library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java
library/core/src/main/java/com/google/android/exoplayer2/text/dvb/DvbDecoder.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
RELEASENOTES.md
View file @
141f3aa8
...
@@ -37,13 +37,14 @@
...
@@ -37,13 +37,14 @@
HLS source to finish preparation without downloading any chunks, which can
HLS source to finish preparation without downloading any chunks, which can
significantly reduce initial buffering time
significantly reduce initial buffering time
(
[
#3149
](
https://github.com/google/ExoPlayer/issues/3149
)
).
(
[
#3149
](
https://github.com/google/ExoPlayer/issues/3149
)
).
*
DefaultTrackSelector: Replace
`DefaultTrackSelector.Parameters`
copy methods
*
DefaultTrackSelector:
with a builder.
*
Replace
`DefaultTrackSelector.Parameters`
copy methods with a builder.
*
DefaultTrackSelector: Support disabling of individual text track selection
*
Support disabling of individual text track selection flags.
flags.
*
New Cast extension: Simplifies toggling between local and Cast playbacks.
*
New Cast extension: Simplifies toggling between local and Cast playbacks.
*
Audio: Support TrueHD passthrough for rechunked samples in Matroska files
*
Audio: Support TrueHD passthrough for rechunked samples in Matroska files
(
[
#2147
](
https://github.com/google/ExoPlayer/issues/2147
)
).
(
[
#2147
](
https://github.com/google/ExoPlayer/issues/2147
)
).
*
Captions: Initial support for PGS subtitles
(
[
#3008
](
https://github.com/google/ExoPlayer/issues/3008
)
).
*
CacheDataSource: Check periodically if it's possible to read from/write to
*
CacheDataSource: Check periodically if it's possible to read from/write to
cache after deciding to bypass cache.
cache after deciding to bypass cache.
*
IMA extension: Add support for playing non-Extractor content MediaSources in
*
IMA extension: Add support for playing non-Extractor content MediaSources in
...
...
demos/main/src/main/assets/media.exolist.json
View file @
141f3aa8
...
@@ -540,7 +540,7 @@
...
@@ -540,7 +540,7 @@
{
{
"name"
:
"VMAP pre-, mid- and post-rolls, single ads"
,
"name"
:
"VMAP pre-, mid- and post-rolls, single ads"
,
"uri"
:
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
,
"uri"
:
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
,
"ad_tag_uri"
:
"https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/
ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue
&correlator="
"ad_tag_uri"
:
"https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/
single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear
&correlator="
},
},
{
{
"name"
:
"VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad"
,
"name"
:
"VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad"
,
...
...
extensions/vp9/README.md
View file @
141f3aa8
...
@@ -29,6 +29,7 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main"
...
@@ -29,6 +29,7 @@ VP9_EXT_PATH="${EXOPLAYER_ROOT}/extensions/vp9/src/main"
```
```
*
Download the
[
Android NDK
][]
and set its location in an environment variable.
*
Download the
[
Android NDK
][]
and set its location in an environment variable.
```
```
NDK_PATH="<path to Android NDK>"
NDK_PATH="<path to Android NDK>"
```
```
...
...
extensions/vp9/src/main/jni/generate_libvpx_android_configs.sh
View file @
141f3aa8
...
@@ -102,7 +102,10 @@ for i in $(seq 0 ${limit}); do
...
@@ -102,7 +102,10 @@ for i in $(seq 0 ${limit}); do
# configure and make
# configure and make
echo
"build_android_configs: "
echo
"build_android_configs: "
echo
"configure
${
config
[
${
i
}
]
}
${
common_params
}
"
echo
"configure
${
config
[
${
i
}
]
}
${
common_params
}
"
../../libvpx/configure
${
config
[
${
i
}
]
}
${
common_params
}
--extra-cflags
=
"-isystem
$ndk
/sysroot/usr/include/arm-linux-androideabi -isystem
$ndk
/sysroot/usr/include"
../../libvpx/configure
${
config
[
${
i
}
]
}
${
common_params
}
--extra-cflags
=
"
\
-isystem
$ndk
/sysroot/usr/include/arm-linux-androideabi
\
-isystem
$ndk
/sysroot/usr/include
\
"
rm
-f
libvpx_srcs.txt
rm
-f
libvpx_srcs.txt
for
f
in
${
allowed_files
}
;
do
for
f
in
${
allowed_files
}
;
do
# the build system supports multiple different configurations. avoid
# the build system supports multiple different configurations. avoid
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java
View file @
141f3aa8
...
@@ -53,67 +53,69 @@ public interface SubtitleDecoderFactory {
...
@@ -53,67 +53,69 @@ public interface SubtitleDecoderFactory {
/**
/**
* Default {@link SubtitleDecoderFactory} implementation.
* Default {@link SubtitleDecoderFactory} implementation.
* <p>
*
* The formats supported by this factory are:
* <p>The formats supported by this factory are:
*
* <ul>
* <ul>
* <li>WebVTT ({@link WebvttDecoder})</li>
* <li>WebVTT ({@link WebvttDecoder})
* <li>WebVTT (MP4) ({@link Mp4WebvttDecoder})</li>
* <li>WebVTT (MP4) ({@link Mp4WebvttDecoder})
* <li>TTML ({@link TtmlDecoder})</li>
* <li>TTML ({@link TtmlDecoder})
* <li>SubRip ({@link SubripDecoder})</li>
* <li>SubRip ({@link SubripDecoder})
* <li>SSA/ASS ({@link SsaDecoder})</li>
* <li>SSA/ASS ({@link SsaDecoder})
* <li>TX3G ({@link Tx3gDecoder})</li>
* <li>TX3G ({@link Tx3gDecoder})
* <li>Cea608 ({@link Cea608Decoder})</li>
* <li>Cea608 ({@link Cea608Decoder})
* <li>Cea708 ({@link Cea708Decoder})</li>
* <li>Cea708 ({@link Cea708Decoder})
* <li>DVB ({@link DvbDecoder})</li>
* <li>DVB ({@link DvbDecoder})
* <li>PGS ({@link PgsDecoder})
* </ul>
* </ul>
*/
*/
SubtitleDecoderFactory
DEFAULT
=
new
SubtitleDecoderFactory
()
{
SubtitleDecoderFactory
DEFAULT
=
new
SubtitleDecoderFactory
()
{
@Override
public
boolean
supportsFormat
(
Format
format
)
{
String
mimeType
=
format
.
sampleMimeType
;
return
MimeTypes
.
TEXT_VTT
.
equals
(
mimeType
)
||
MimeTypes
.
TEXT_SSA
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_TTML
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_MP4VTT
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_SUBRIP
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_TX3G
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_CEA608
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_MP4CEA608
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_CEA708
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_DVBSUBS
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_PGS
.
equals
(
mimeType
);
}
@Override
public
SubtitleDecoder
createDecoder
(
Format
format
)
{
switch
(
format
.
sampleMimeType
)
{
case
MimeTypes
.
TEXT_VTT
:
return
new
WebvttDecoder
();
case
MimeTypes
.
TEXT_SSA
:
return
new
SsaDecoder
(
format
.
initializationData
);
case
MimeTypes
.
APPLICATION_MP4VTT
:
return
new
Mp4WebvttDecoder
();
case
MimeTypes
.
APPLICATION_TTML
:
return
new
TtmlDecoder
();
case
MimeTypes
.
APPLICATION_SUBRIP
:
return
new
SubripDecoder
();
case
MimeTypes
.
APPLICATION_TX3G
:
return
new
Tx3gDecoder
(
format
.
initializationData
);
case
MimeTypes
.
APPLICATION_CEA608
:
case
MimeTypes
.
APPLICATION_MP4CEA608
:
return
new
Cea608Decoder
(
format
.
sampleMimeType
,
format
.
accessibilityChannel
);
case
MimeTypes
.
APPLICATION_CEA708
:
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"
);
}
}
};
@Override
public
boolean
supportsFormat
(
Format
format
)
{
String
mimeType
=
format
.
sampleMimeType
;
return
MimeTypes
.
TEXT_VTT
.
equals
(
mimeType
)
||
MimeTypes
.
TEXT_SSA
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_TTML
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_MP4VTT
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_SUBRIP
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_TX3G
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_CEA608
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_MP4CEA608
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_CEA708
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_DVBSUBS
.
equals
(
mimeType
)
||
MimeTypes
.
APPLICATION_PGS
.
equals
(
mimeType
);
}
@Override
public
SubtitleDecoder
createDecoder
(
Format
format
)
{
switch
(
format
.
sampleMimeType
)
{
case
MimeTypes
.
TEXT_VTT
:
return
new
WebvttDecoder
();
case
MimeTypes
.
TEXT_SSA
:
return
new
SsaDecoder
(
format
.
initializationData
);
case
MimeTypes
.
APPLICATION_MP4VTT
:
return
new
Mp4WebvttDecoder
();
case
MimeTypes
.
APPLICATION_TTML
:
return
new
TtmlDecoder
();
case
MimeTypes
.
APPLICATION_SUBRIP
:
return
new
SubripDecoder
();
case
MimeTypes
.
APPLICATION_TX3G
:
return
new
Tx3gDecoder
(
format
.
initializationData
);
case
MimeTypes
.
APPLICATION_CEA608
:
case
MimeTypes
.
APPLICATION_MP4CEA608
:
return
new
Cea608Decoder
(
format
.
sampleMimeType
,
format
.
accessibilityChannel
);
case
MimeTypes
.
APPLICATION_CEA708
:
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/dvb/DvbDecoder.java
View file @
141f3aa8
...
@@ -19,9 +19,7 @@ import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
...
@@ -19,9 +19,7 @@ import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
java.util.List
;
import
java.util.List
;
/**
/** A {@link SimpleSubtitleDecoder} for DVB subtitles. */
* A {@link SimpleSubtitleDecoder} for DVB Subtitles.
*/
public
final
class
DvbDecoder
extends
SimpleSubtitleDecoder
{
public
final
class
DvbDecoder
extends
SimpleSubtitleDecoder
{
private
final
DvbParser
parser
;
private
final
DvbParser
parser
;
...
...
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsBuilder.java
deleted
100644 → 0
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
View file @
141f3aa8
/*
* Copyright (C) 2018 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
.
exoplayer2
.
text
.
pgs
;
package
com
.
google
.
android
.
exoplayer2
.
text
.
pgs
;
import
android.graphics.Bitmap
;
import
com.google.android.exoplayer2.text.Cue
;
import
com.google.android.exoplayer2.text.SimpleSubtitleDecoder
;
import
com.google.android.exoplayer2.text.SimpleSubtitleDecoder
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.Subtitle
;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.text.SubtitleDecoderException
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
@SuppressWarnings
(
"unused"
)
/** A {@link SimpleSubtitleDecoder} for PGS subtitles. */
public
class
PgsDecoder
extends
SimpleSubtitleDecoder
{
public
final
class
PgsDecoder
extends
SimpleSubtitleDecoder
{
private
static
final
int
SECTION_TYPE_PALETTE
=
0x14
;
private
static
final
int
SECTION_TYPE_BITMAP_PICTURE
=
0x15
;
private
static
final
int
SECTION_TYPE_IDENTIFIER
=
0x16
;
private
static
final
int
SECTION_TYPE_END
=
0x80
;
private
final
ParsableByteArray
buffer
;
private
final
CueBuilder
cueBuilder
;
@SuppressWarnings
(
"unused"
)
public
PgsDecoder
()
{
public
PgsDecoder
()
{
super
(
"PgsDecoder"
);
super
(
"PgsDecoder"
);
buffer
=
new
ParsableByteArray
();
cueBuilder
=
new
CueBuilder
();
}
}
@Override
@Override
protected
Subtitle
decode
(
byte
[]
data
,
int
size
,
boolean
reset
)
throws
SubtitleDecoderException
{
protected
Subtitle
decode
(
byte
[]
data
,
int
size
,
boolean
reset
)
throws
SubtitleDecoderException
{
ParsableByteArray
buffer
=
new
ParsableByteArray
(
data
,
size
);
buffer
.
reset
(
data
,
size
);
PgsBuilder
builder
=
new
PgsBuilder
();
cueBuilder
.
reset
();
do
{
ArrayList
<
Cue
>
cues
=
new
ArrayList
<>();
if
(!
builder
.
readNextSection
(
buffer
))
while
(
buffer
.
bytesLeft
()
>=
3
)
{
Cue
cue
=
readNextSection
(
buffer
,
cueBuilder
);
if
(
cue
!=
null
)
{
cues
.
add
(
cue
);
}
}
return
new
PgsSubtitle
(
Collections
.
unmodifiableList
(
cues
));
}
private
static
Cue
readNextSection
(
ParsableByteArray
buffer
,
CueBuilder
cueBuilder
)
{
int
limit
=
buffer
.
limit
();
int
sectionType
=
buffer
.
readUnsignedByte
();
int
sectionLength
=
buffer
.
readUnsignedShort
();
int
nextSectionPosition
=
buffer
.
getPosition
()
+
sectionLength
;
if
(
nextSectionPosition
>
limit
)
{
buffer
.
setPosition
(
limit
);
return
null
;
}
Cue
cue
=
null
;
switch
(
sectionType
)
{
case
SECTION_TYPE_PALETTE:
cueBuilder
.
parsePaletteSection
(
buffer
,
sectionLength
);
break
;
break
;
}
while
(
buffer
.
bytesLeft
()
>
0
);
case
SECTION_TYPE_BITMAP_PICTURE:
return
builder
.
build
();
cueBuilder
.
parseBitmapSection
(
buffer
,
sectionLength
);
break
;
case
SECTION_TYPE_IDENTIFIER:
cueBuilder
.
parseIdentifierSection
(
buffer
,
sectionLength
);
break
;
case
SECTION_TYPE_END:
cue
=
cueBuilder
.
build
();
cueBuilder
.
reset
();
break
;
default
:
break
;
}
buffer
.
setPosition
(
nextSectionPosition
);
return
cue
;
}
private
static
final
class
CueBuilder
{
private
final
ParsableByteArray
bitmapData
;
private
final
int
[]
colors
;
private
boolean
colorsSet
;
private
int
planeWidth
;
private
int
planeHeight
;
private
int
bitmapX
;
private
int
bitmapY
;
private
int
bitmapWidth
;
private
int
bitmapHeight
;
public
CueBuilder
()
{
bitmapData
=
new
ParsableByteArray
();
colors
=
new
int
[
256
];
}
private
void
parsePaletteSection
(
ParsableByteArray
buffer
,
int
sectionLength
)
{
if
((
sectionLength
%
5
)
!=
2
)
{
// Section must be two bytes followed by a whole number of (index, y, cb, cr, a) entries.
return
;
}
buffer
.
skipBytes
(
2
);
Arrays
.
fill
(
colors
,
0
);
int
entryCount
=
sectionLength
/
5
;
for
(
int
i
=
0
;
i
<
entryCount
;
i
++)
{
int
index
=
buffer
.
readUnsignedByte
();
int
y
=
buffer
.
readUnsignedByte
();
int
cr
=
buffer
.
readUnsignedByte
();
int
cb
=
buffer
.
readUnsignedByte
();
int
a
=
buffer
.
readUnsignedByte
();
int
r
=
(
int
)
(
y
+
(
1.40200
*
(
cr
-
128
)));
int
g
=
(
int
)
(
y
-
(
0.34414
*
(
cb
-
128
))
-
(
0.71414
*
(
cr
-
128
)));
int
b
=
(
int
)
(
y
+
(
1.77200
*
(
cb
-
128
)));
colors
[
index
]
=
(
a
<<
24
)
|
(
Util
.
constrainValue
(
r
,
0
,
255
)
<<
16
)
|
(
Util
.
constrainValue
(
g
,
0
,
255
)
<<
8
)
|
Util
.
constrainValue
(
b
,
0
,
255
);
}
colorsSet
=
true
;
}
private
void
parseBitmapSection
(
ParsableByteArray
buffer
,
int
sectionLength
)
{
if
(
sectionLength
<
4
)
{
return
;
}
buffer
.
skipBytes
(
3
);
// Id (2 bytes), version (1 byte).
boolean
isBaseSection
=
(
0x80
&
buffer
.
readUnsignedByte
())
!=
0
;
sectionLength
-=
4
;
if
(
isBaseSection
)
{
if
(
sectionLength
<
7
)
{
return
;
}
int
totalLength
=
buffer
.
readUnsignedInt24
()
-
4
;
if
(
totalLength
<
4
)
{
return
;
}
bitmapWidth
=
buffer
.
readUnsignedShort
();
bitmapHeight
=
buffer
.
readUnsignedShort
();
bitmapData
.
reset
(
totalLength
-
4
);
sectionLength
-=
7
;
}
int
position
=
bitmapData
.
getPosition
();
int
limit
=
bitmapData
.
limit
();
if
(
position
<
limit
&&
sectionLength
>
0
)
{
int
bytesToRead
=
Math
.
min
(
sectionLength
,
limit
-
position
);
buffer
.
readBytes
(
bitmapData
.
data
,
position
,
bytesToRead
);
bitmapData
.
setPosition
(
position
+
bytesToRead
);
}
}
private
void
parseIdentifierSection
(
ParsableByteArray
buffer
,
int
sectionLength
)
{
if
(
sectionLength
<
19
)
{
return
;
}
planeWidth
=
buffer
.
readUnsignedShort
();
planeHeight
=
buffer
.
readUnsignedShort
();
buffer
.
skipBytes
(
11
);
bitmapX
=
buffer
.
readUnsignedShort
();
bitmapY
=
buffer
.
readUnsignedShort
();
}
public
Cue
build
()
{
if
(
planeWidth
==
0
||
planeHeight
==
0
||
bitmapWidth
==
0
||
bitmapHeight
==
0
||
bitmapData
.
limit
()
==
0
||
bitmapData
.
getPosition
()
!=
bitmapData
.
limit
()
||
!
colorsSet
)
{
return
null
;
}
// Build the bitmapData.
bitmapData
.
setPosition
(
0
);
int
[]
argbBitmapData
=
new
int
[
bitmapWidth
*
bitmapHeight
];
int
argbBitmapDataIndex
=
0
;
while
(
argbBitmapDataIndex
<
argbBitmapData
.
length
)
{
int
colorIndex
=
bitmapData
.
readUnsignedByte
();
if
(
colorIndex
!=
0
)
{
argbBitmapData
[
argbBitmapDataIndex
++]
=
colors
[
colorIndex
];
}
else
{
int
switchBits
=
bitmapData
.
readUnsignedByte
();
if
(
switchBits
!=
0
)
{
int
runLength
=
(
switchBits
&
0x40
)
==
0
?
(
switchBits
&
0x3F
)
:
(((
switchBits
&
0x3F
)
<<
8
)
|
bitmapData
.
readUnsignedByte
());
int
color
=
(
switchBits
&
0x80
)
==
0
?
0
:
colors
[
bitmapData
.
readUnsignedByte
()];
Arrays
.
fill
(
argbBitmapData
,
argbBitmapDataIndex
,
argbBitmapDataIndex
+
runLength
,
color
);
argbBitmapDataIndex
+=
runLength
;
}
}
}
Bitmap
bitmap
=
Bitmap
.
createBitmap
(
argbBitmapData
,
bitmapWidth
,
bitmapHeight
,
Bitmap
.
Config
.
ARGB_8888
);
// Build the cue.
return
new
Cue
(
bitmap
,
(
float
)
bitmapX
/
planeWidth
,
Cue
.
ANCHOR_TYPE_START
,
(
float
)
bitmapY
/
planeHeight
,
Cue
.
ANCHOR_TYPE_START
,
(
float
)
bitmapWidth
/
planeWidth
,
(
float
)
bitmapHeight
/
planeHeight
);
}
public
void
reset
()
{
planeWidth
=
0
;
planeHeight
=
0
;
bitmapX
=
0
;
bitmapY
=
0
;
bitmapWidth
=
0
;
bitmapHeight
=
0
;
bitmapData
.
reset
(
0
);
colorsSet
=
false
;
}
}
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsSubtitle.java
View file @
141f3aa8
/*
* Copyright (C) 2018 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
.
exoplayer2
.
text
.
pgs
;
package
com
.
google
.
android
.
exoplayer2
.
text
.
pgs
;
import
com.google.android.exoplayer2.C
;
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
com.google.android.exoplayer2.util.Assertions
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.List
;
public
class
PgsSubtitle
implements
Subtitle
{
/** A representation of a PGS subtitle. */
/* package */
final
class
PgsSubtitle
implements
Subtitle
{
private
final
Cue
[]
cues
;
private
final
long
[]
cueTimesUs
;
PgsSubtitle
()
{
private
final
List
<
Cue
>
cues
;
this
.
cues
=
null
;
this
.
cueTimesUs
=
new
long
[
0
];
}
PgsSubtitle
(
Cue
[]
cues
,
long
[]
cueTimesU
s
)
{
public
PgsSubtitle
(
List
<
Cue
>
cue
s
)
{
this
.
cues
=
cues
;
this
.
cues
=
cues
;
this
.
cueTimesUs
=
cueTimesUs
;
}
}
@Override
@Override
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
public
int
getNextEventTimeIndex
(
long
timeUs
)
{
int
index
=
Util
.
binarySearchCeil
(
cueTimesUs
,
timeUs
,
false
,
false
);
return
C
.
INDEX_UNSET
;
return
index
<
cueTimesUs
.
length
?
index
:
-
1
;
}
}
@Override
@Override
public
int
getEventTimeCount
()
{
public
int
getEventTimeCount
()
{
return
cueTimesUs
.
length
;
return
1
;
}
}
@Override
@Override
public
long
getEventTime
(
int
index
)
{
public
long
getEventTime
(
int
index
)
{
Assertions
.
checkArgument
(
index
>=
0
);
return
0
;
Assertions
.
checkArgument
(
index
<
cueTimesUs
.
length
);
return
cueTimesUs
[
index
];
}
}
@Override
@Override
public
List
<
Cue
>
getCues
(
long
timeUs
)
{
public
List
<
Cue
>
getCues
(
long
timeUs
)
{
int
index
=
Util
.
binarySearchFloor
(
cueTimesUs
,
timeUs
,
true
,
false
);
return
cues
;
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