Commit 141f3aa8 by Oliver Woodman

Simplify PGS captions + sync with internal tree

parent dc38e869
...@@ -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
......
...@@ -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",
......
...@@ -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>"
``` ```
......
...@@ -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
......
...@@ -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");
}
}
};
} }
...@@ -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;
......
/*
*
* 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;
}
}
}
/*
* 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;
}
} }
} }
/*
* 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[] cueTimesUs) { public PgsSubtitle(List<Cue> cues) {
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]);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment