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 @@
HLS source to finish preparation without downloading any chunks, which can
significantly reduce initial buffering time
(
[
#3149
](
https://github.com/google/ExoPlayer/issues/3149
)
).
*
DefaultTrackSelector: Replace
`DefaultTrackSelector.Parameters`
copy methods
with a builder.
*
DefaultTrackSelector: Support disabling of individual text track selection
flags.
*
DefaultTrackSelector:
*
Replace
`DefaultTrackSelector.Parameters`
copy methods with a builder.
*
Support disabling of individual text track selection flags.
*
New Cast extension: Simplifies toggling between local and Cast playbacks.
*
Audio: Support TrueHD passthrough for rechunked samples in Matroska files
(
[
#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
cache after deciding to bypass cache.
*
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 @@
{
"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"
,
"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"
,
...
...
extensions/vp9/README.md
View file @
141f3aa8
...
...
@@ -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.
```
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
# configure and make
echo
"build_android_configs: "
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
for
f
in
${
allowed_files
}
;
do
# 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 {
/**
* Default {@link SubtitleDecoderFactory} implementation.
* <p>
* The formats supported by this factory are:
*
* <p>The formats supported by this factory are:
*
* <ul>
* <li>WebVTT ({@link WebvttDecoder})</li>
* <li>WebVTT (MP4) ({@link Mp4WebvttDecoder})</li>
* <li>TTML ({@link TtmlDecoder})</li>
* <li>SubRip ({@link SubripDecoder})</li>
* <li>SSA/ASS ({@link SsaDecoder})</li>
* <li>TX3G ({@link Tx3gDecoder})</li>
* <li>Cea608 ({@link Cea608Decoder})</li>
* <li>Cea708 ({@link Cea708Decoder})</li>
* <li>DVB ({@link DvbDecoder})</li>
* <li>WebVTT ({@link WebvttDecoder})
* <li>WebVTT (MP4) ({@link Mp4WebvttDecoder})
* <li>TTML ({@link TtmlDecoder})
* <li>SubRip ({@link SubripDecoder})
* <li>SSA/ASS ({@link SsaDecoder})
* <li>TX3G ({@link Tx3gDecoder})
* <li>Cea608 ({@link Cea608Decoder})
* <li>Cea708 ({@link Cea708Decoder})
* <li>DVB ({@link DvbDecoder})
* <li>PGS ({@link PgsDecoder})
* </ul>
*/
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"
);
}
}
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"
);
}
}
};
}
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;
import
com.google.android.exoplayer2.util.ParsableByteArray
;
import
java.util.List
;
/**
* A {@link SimpleSubtitleDecoder} for DVB Subtitles.
*/
/** A {@link SimpleSubtitleDecoder} for DVB subtitles. */
public
final
class
DvbDecoder
extends
SimpleSubtitleDecoder
{
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
;
import
android.graphics.Bitmap
;
import
com.google.android.exoplayer2.text.Cue
;
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
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
@SuppressWarnings
(
"unused"
)
public
class
PgsDecoder
extends
SimpleSubtitleDecoder
{
/** A {@link SimpleSubtitleDecoder} for PGS subtitles. */
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
()
{
super
(
"PgsDecoder"
);
buffer
=
new
ParsableByteArray
();
cueBuilder
=
new
CueBuilder
();
}
@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
))
buffer
.
reset
(
data
,
size
);
cueBuilder
.
reset
();
ArrayList
<
Cue
>
cues
=
new
ArrayList
<>();
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
;
}
while
(
buffer
.
bytesLeft
()
>
0
);
return
builder
.
build
();
case
SECTION_TYPE_BITMAP_PICTURE:
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
;
import
com.google.android.exoplayer2.C
;
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
;
/** A representation of a PGS subtitle. */
/* package */
final
class
PgsSubtitle
implements
Subtitle
{
PgsSubtitle
()
{
this
.
cues
=
null
;
this
.
cueTimesUs
=
new
long
[
0
];
}
private
final
List
<
Cue
>
cues
;
PgsSubtitle
(
Cue
[]
cues
,
long
[]
cueTimesU
s
)
{
public
PgsSubtitle
(
List
<
Cue
>
cue
s
)
{
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
;
return
C
.
INDEX_UNSET
;
}
@Override
public
int
getEventTimeCount
()
{
return
cueTimesUs
.
length
;
}
return
1
;
}
@Override
public
long
getEventTime
(
int
index
)
{
Assertions
.
checkArgument
(
index
>=
0
);
Assertions
.
checkArgument
(
index
<
cueTimesUs
.
length
);
return
cueTimesUs
[
index
];
return
0
;
}
@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
]);
return
cues
;
}
}
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