mirror of
https://github.com/samsonjs/media.git
synced 2026-04-10 12:05:47 +00:00
Simplify PGS captions + sync with internal tree
This commit is contained in:
parent
dc38e86945
commit
141f3aa836
9 changed files with 317 additions and 336 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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>"
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
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");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,232 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,26 +1,237 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,54 +1,51 @@
|
|||
/*
|
||||
* 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 {
|
||||
/** A representation of a PGS subtitle. */
|
||||
/* package */ final class PgsSubtitle implements Subtitle {
|
||||
|
||||
private final Cue[] cues;
|
||||
private final long[] cueTimesUs;
|
||||
private final List<Cue> cues;
|
||||
|
||||
PgsSubtitle() {
|
||||
this.cues = null;
|
||||
this.cueTimesUs = new long[0];
|
||||
}
|
||||
|
||||
PgsSubtitle(Cue[] cues, long[] cueTimesUs) {
|
||||
public PgsSubtitle(List<Cue> cues) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue