mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Merge pull request #1979 from wischnow:main
PiperOrigin-RevId: 714911017
This commit is contained in:
commit
d18ad57e30
@ -44,6 +44,8 @@
|
||||
Previously we incorrectly parsed any number of decimal places but always
|
||||
assumed the value was in milliseconds, leading to incorrect timestamps
|
||||
([#1997](https://github.com/androidx/media/issues/1997)).
|
||||
* Add support for VobSub subtitles
|
||||
([#8260](https://github.com/google/ExoPlayer/issues/8260)).
|
||||
* Metadata:
|
||||
* Image:
|
||||
* DataSource:
|
||||
|
@ -212,6 +212,8 @@ public final class Util {
|
||||
private static final String ISM_HLS_FORMAT_EXTENSION = "format=m3u8-aapl";
|
||||
private static final String ISM_DASH_FORMAT_EXTENSION = "format=mpd-time-csf";
|
||||
|
||||
private static final int ZLIB_INFLATE_HEADER = 0x78;
|
||||
|
||||
// Replacement map of ISO language codes used for normalization.
|
||||
@Nullable private static HashMap<String, String> languageTagReplacementMap;
|
||||
|
||||
@ -3102,6 +3104,26 @@ public final class Util {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncompresses the data in {@code input} if it starts with the zlib marker {@code 0x78}.
|
||||
*
|
||||
* @param input Wraps the compressed input data.
|
||||
* @param output Wraps an output buffer to be used to store the uncompressed data. If {@code
|
||||
* output.data} isn't big enough to hold the uncompressed data, a new array is created. If
|
||||
* {@code true} is returned then the output's position will be set to 0 and its limit will be
|
||||
* set to the length of the uncompressed data.
|
||||
* @param inflater If not null, used to uncompress the input. Otherwise a new {@link Inflater} is
|
||||
* created.
|
||||
* @return Whether the input is uncompressed successfully.
|
||||
*/
|
||||
@UnstableApi
|
||||
public static boolean maybeInflate(
|
||||
ParsableByteArray input, ParsableByteArray output, @Nullable Inflater inflater) {
|
||||
return input.bytesLeft() > 0
|
||||
&& input.peekUnsignedByte() == ZLIB_INFLATE_HEADER
|
||||
&& inflate(input, output, inflater);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the app is running on a TV device.
|
||||
*
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package androidx.media3.exoplayer.e2etest;
|
||||
|
||||
import static org.robolectric.annotation.GraphicsMode.Mode.NATIVE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.view.Surface;
|
||||
@ -35,9 +37,11 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner;
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
|
||||
import org.robolectric.annotation.GraphicsMode;
|
||||
|
||||
/** End-to-end tests using MKV samples. */
|
||||
@RunWith(ParameterizedRobolectricTestRunner.class)
|
||||
@GraphicsMode(NATIVE)
|
||||
public final class MkvPlaybackTest {
|
||||
@Parameters(name = "{0}")
|
||||
public static ImmutableList<String> mediaSamples() {
|
||||
@ -51,7 +55,8 @@ public final class MkvPlaybackTest {
|
||||
"sample_with_null_terminated_srt.mkv",
|
||||
"sample_with_overlapping_srt.mkv",
|
||||
"sample_with_vtt_subtitles.mkv",
|
||||
"sample_with_null_terminated_vtt_subtitles.mkv");
|
||||
"sample_with_null_terminated_vtt_subtitles.mkv",
|
||||
"sample_with_vobsub.mkv");
|
||||
}
|
||||
|
||||
@ParameterizedRobolectricTestRunner.Parameter public String inputFile;
|
||||
|
@ -26,6 +26,7 @@ import androidx.media3.extractor.text.ssa.SsaParser;
|
||||
import androidx.media3.extractor.text.subrip.SubripParser;
|
||||
import androidx.media3.extractor.text.ttml.TtmlParser;
|
||||
import androidx.media3.extractor.text.tx3g.Tx3gParser;
|
||||
import androidx.media3.extractor.text.vobsub.VobsubParser;
|
||||
import androidx.media3.extractor.text.webvtt.Mp4WebvttParser;
|
||||
import androidx.media3.extractor.text.webvtt.WebvttParser;
|
||||
import java.util.Objects;
|
||||
@ -58,6 +59,7 @@ public final class DefaultSubtitleParserFactory implements SubtitleParser.Factor
|
||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_SUBRIP)
|
||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TX3G)
|
||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_PGS)
|
||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_VOBSUB)
|
||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_DVBSUBS)
|
||||
|| Objects.equals(mimeType, MimeTypes.APPLICATION_TTML);
|
||||
}
|
||||
@ -79,6 +81,8 @@ public final class DefaultSubtitleParserFactory implements SubtitleParser.Factor
|
||||
return Tx3gParser.CUE_REPLACEMENT_BEHAVIOR;
|
||||
case MimeTypes.APPLICATION_PGS:
|
||||
return PgsParser.CUE_REPLACEMENT_BEHAVIOR;
|
||||
case MimeTypes.APPLICATION_VOBSUB:
|
||||
return VobsubParser.CUE_REPLACEMENT_BEHAVIOR;
|
||||
case MimeTypes.APPLICATION_DVBSUBS:
|
||||
return DvbParser.CUE_REPLACEMENT_BEHAVIOR;
|
||||
case MimeTypes.APPLICATION_TTML:
|
||||
@ -107,6 +111,8 @@ public final class DefaultSubtitleParserFactory implements SubtitleParser.Factor
|
||||
return new Tx3gParser(format.initializationData);
|
||||
case MimeTypes.APPLICATION_PGS:
|
||||
return new PgsParser();
|
||||
case MimeTypes.APPLICATION_VOBSUB:
|
||||
return new VobsubParser(format.initializationData);
|
||||
case MimeTypes.APPLICATION_DVBSUBS:
|
||||
return new DvbParser(format.initializationData);
|
||||
case MimeTypes.APPLICATION_TTML:
|
||||
|
@ -49,8 +49,6 @@ public final class PgsParser implements SubtitleParser {
|
||||
private static final int SECTION_TYPE_IDENTIFIER = 0x16;
|
||||
private static final int SECTION_TYPE_END = 0x80;
|
||||
|
||||
private static final byte INFLATE_HEADER = 0x78;
|
||||
|
||||
private final ParsableByteArray buffer;
|
||||
private final ParsableByteArray inflatedBuffer;
|
||||
private final CueBuilder cueBuilder;
|
||||
@ -76,7 +74,12 @@ public final class PgsParser implements SubtitleParser {
|
||||
Consumer<CuesWithTiming> output) {
|
||||
buffer.reset(data, /* limit= */ offset + length);
|
||||
buffer.setPosition(offset);
|
||||
maybeInflateData(buffer);
|
||||
if (inflater == null) {
|
||||
inflater = new Inflater();
|
||||
}
|
||||
if (Util.maybeInflate(buffer, inflatedBuffer, inflater)) {
|
||||
buffer.reset(inflatedBuffer.getData(), inflatedBuffer.limit());
|
||||
}
|
||||
cueBuilder.reset();
|
||||
ArrayList<Cue> cues = new ArrayList<>();
|
||||
while (buffer.bytesLeft() >= 3) {
|
||||
@ -89,17 +92,6 @@ public final class PgsParser implements SubtitleParser {
|
||||
new CuesWithTiming(cues, /* startTimeUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET));
|
||||
}
|
||||
|
||||
private void maybeInflateData(ParsableByteArray buffer) {
|
||||
if (buffer.bytesLeft() > 0 && buffer.peekUnsignedByte() == INFLATE_HEADER) {
|
||||
if (inflater == null) {
|
||||
inflater = new Inflater();
|
||||
}
|
||||
if (Util.inflate(buffer, inflatedBuffer, inflater)) {
|
||||
buffer.reset(inflatedBuffer.getData(), inflatedBuffer.limit());
|
||||
} // else assume data is not compressed.
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Cue readNextSection(ParsableByteArray buffer, CueBuilder cueBuilder) {
|
||||
int limit = buffer.limit();
|
||||
|
@ -0,0 +1,391 @@
|
||||
/*
|
||||
* Copyright 2025 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
|
||||
*
|
||||
* https://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 androidx.media3.extractor.text.vobsub;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Rect;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.Format.CueReplacementBehavior;
|
||||
import androidx.media3.common.text.Cue;
|
||||
import androidx.media3.common.util.Consumer;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.ParsableBitArray;
|
||||
import androidx.media3.common.util.ParsableByteArray;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.extractor.text.CuesWithTiming;
|
||||
import androidx.media3.extractor.text.SubtitleParser;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.zip.Inflater;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/** A {@link SubtitleParser} for Vobsub subtitles. */
|
||||
@UnstableApi
|
||||
public final class VobsubParser implements SubtitleParser {
|
||||
|
||||
/**
|
||||
* The {@link CueReplacementBehavior} for consecutive {@link CuesWithTiming} emitted by this
|
||||
* implementation.
|
||||
*/
|
||||
public static final @CueReplacementBehavior int CUE_REPLACEMENT_BEHAVIOR =
|
||||
Format.CUE_REPLACEMENT_BEHAVIOR_REPLACE;
|
||||
|
||||
private static final String TAG = "VobsubParser";
|
||||
private static final int DEFAULT_DURATION_US = 5_000_000;
|
||||
|
||||
private final ParsableByteArray scratch;
|
||||
private final ParsableByteArray inflatedScratch;
|
||||
private final CueBuilder cueBuilder;
|
||||
@Nullable private Inflater inflater;
|
||||
|
||||
public VobsubParser(List<byte[]> initializationData) {
|
||||
scratch = new ParsableByteArray();
|
||||
inflatedScratch = new ParsableByteArray();
|
||||
cueBuilder = new CueBuilder();
|
||||
cueBuilder.parseIdx(new String(initializationData.get(0), UTF_8));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @CueReplacementBehavior int getCueReplacementBehavior() {
|
||||
return CUE_REPLACEMENT_BEHAVIOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parse(
|
||||
byte[] data,
|
||||
int offset,
|
||||
int length,
|
||||
OutputOptions outputOptions,
|
||||
Consumer<CuesWithTiming> output) {
|
||||
scratch.reset(data, offset + length);
|
||||
scratch.setPosition(offset);
|
||||
@Nullable Cue cue = parse();
|
||||
output.accept(
|
||||
new CuesWithTiming(
|
||||
cue != null ? ImmutableList.of(cue) : ImmutableList.of(),
|
||||
/* startTimeUs= */ C.TIME_UNSET,
|
||||
/* durationUs= */ DEFAULT_DURATION_US));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Cue parse() {
|
||||
if (inflater == null) {
|
||||
inflater = new Inflater();
|
||||
}
|
||||
if (Util.maybeInflate(scratch, inflatedScratch, inflater)) {
|
||||
scratch.reset(inflatedScratch.getData(), inflatedScratch.limit());
|
||||
}
|
||||
cueBuilder.reset();
|
||||
int bytesLeft = scratch.bytesLeft();
|
||||
if (bytesLeft < 2 || scratch.readUnsignedShort() != bytesLeft) {
|
||||
return null;
|
||||
}
|
||||
cueBuilder.parseSpu(scratch);
|
||||
return cueBuilder.build(scratch);
|
||||
}
|
||||
|
||||
private static final class CueBuilder {
|
||||
|
||||
private static final int CMD_COLORS = 3;
|
||||
private static final int CMD_ALPHA = 4;
|
||||
private static final int CMD_AREA = 5;
|
||||
private static final int CMD_OFFSETS = 6;
|
||||
private static final int CMD_END = 255;
|
||||
|
||||
private final int[] colors;
|
||||
|
||||
private boolean hasPlane;
|
||||
private boolean hasColors;
|
||||
private int @MonotonicNonNull [] palette;
|
||||
private int planeWidth;
|
||||
private int planeHeight;
|
||||
@Nullable private Rect boundingBox;
|
||||
private int dataOffset0;
|
||||
private int dataOffset1;
|
||||
|
||||
public CueBuilder() {
|
||||
colors = new int[4];
|
||||
dataOffset0 = C.INDEX_UNSET;
|
||||
dataOffset1 = C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
public void parseIdx(String idx) {
|
||||
for (String line : Util.split(idx.trim(), "\\r?\\n")) {
|
||||
if (line.startsWith("palette: ")) {
|
||||
String[] values = Util.split(line.substring("palette: ".length()), ",");
|
||||
palette = new int[values.length];
|
||||
|
||||
for (int i = 0; i < values.length; i++) {
|
||||
palette[i] = parseColor(values[i].trim());
|
||||
}
|
||||
} else if (line.startsWith("size: ")) {
|
||||
// We need this line to calculate the relative positions and size required when building
|
||||
// the Cue below.
|
||||
String[] sizes = Util.split(line.substring("size: ".length()).trim(), "x");
|
||||
|
||||
if (sizes.length == 2) {
|
||||
try {
|
||||
planeWidth = Integer.parseInt(sizes[0]);
|
||||
planeHeight = Integer.parseInt(sizes[1]);
|
||||
hasPlane = true;
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "Parsing IDX failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int parseColor(String value) {
|
||||
try {
|
||||
return Integer.parseInt(value, 16);
|
||||
} catch (RuntimeException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void parseSpu(ParsableByteArray buffer) {
|
||||
if (palette == null || !hasPlane) {
|
||||
// Give up if we don't have the color palette or the video size.
|
||||
return;
|
||||
}
|
||||
int[] palette = this.palette;
|
||||
buffer.skipBytes(buffer.readUnsignedShort() - 2);
|
||||
int end = buffer.readUnsignedShort();
|
||||
parseControl(palette, buffer, end);
|
||||
}
|
||||
|
||||
private void parseControl(int[] palette, ParsableByteArray buffer, int end) {
|
||||
while (buffer.getPosition() < end && buffer.bytesLeft() > 0) {
|
||||
switch (buffer.readUnsignedByte()) {
|
||||
case CMD_COLORS:
|
||||
if (!parseControlColors(palette, buffer)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CMD_ALPHA:
|
||||
if (!parseControlAlpha(buffer)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CMD_AREA:
|
||||
if (!parseControlArea(buffer)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CMD_OFFSETS:
|
||||
if (!parseControlOffsets(buffer)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case CMD_END:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean parseControlColors(int[] palette, ParsableByteArray buffer) {
|
||||
if (buffer.bytesLeft() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int byte0 = buffer.readUnsignedByte();
|
||||
int byte1 = buffer.readUnsignedByte();
|
||||
|
||||
colors[3] = getColor(palette, byte0 >> 4);
|
||||
colors[2] = getColor(palette, byte0 & 0xf);
|
||||
colors[1] = getColor(palette, byte1 >> 4);
|
||||
colors[0] = getColor(palette, byte1 & 0xf);
|
||||
hasColors = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int getColor(int[] palette, int index) {
|
||||
return index >= 0 && index < palette.length ? palette[index] : palette[0];
|
||||
}
|
||||
|
||||
private boolean parseControlAlpha(ParsableByteArray buffer) {
|
||||
|
||||
if (buffer.bytesLeft() < 2 || !hasColors) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int byte0 = buffer.readUnsignedByte();
|
||||
int byte1 = buffer.readUnsignedByte();
|
||||
|
||||
colors[3] = setAlpha(colors[3], (byte0 >> 4));
|
||||
colors[2] = setAlpha(colors[2], (byte0 & 0xf));
|
||||
colors[1] = setAlpha(colors[1], (byte1 >> 4));
|
||||
colors[0] = setAlpha(colors[0], (byte1 & 0xf));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int setAlpha(int color, int alpha) {
|
||||
return ((color & 0x00ffffff) | ((alpha * 17) << 24));
|
||||
}
|
||||
|
||||
private boolean parseControlArea(ParsableByteArray buffer) {
|
||||
if (buffer.bytesLeft() < 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int byte0 = buffer.readUnsignedByte();
|
||||
int byte1 = buffer.readUnsignedByte();
|
||||
int byte2 = buffer.readUnsignedByte();
|
||||
|
||||
int left = (byte0 << 4) | (byte1 >> 4);
|
||||
int right = ((byte1 & 0xf) << 8) | byte2;
|
||||
|
||||
int byte3 = buffer.readUnsignedByte();
|
||||
int byte4 = buffer.readUnsignedByte();
|
||||
int byte5 = buffer.readUnsignedByte();
|
||||
|
||||
int top = (byte3 << 4) | (byte4 >> 4);
|
||||
int bottom = ((byte4 & 0xf) << 8) | byte5;
|
||||
|
||||
boundingBox = new Rect(left, top, right + 1, bottom + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean parseControlOffsets(ParsableByteArray buffer) {
|
||||
if (buffer.bytesLeft() < 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dataOffset0 = buffer.readUnsignedShort();
|
||||
dataOffset1 = buffer.readUnsignedShort();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Cue build(ParsableByteArray buffer) {
|
||||
if (palette == null
|
||||
|| !hasPlane
|
||||
|| !hasColors
|
||||
|| boundingBox == null
|
||||
|| dataOffset0 == C.INDEX_UNSET
|
||||
|| dataOffset1 == C.INDEX_UNSET
|
||||
|| boundingBox.width() < 2
|
||||
|| boundingBox.height() < 2) {
|
||||
return null;
|
||||
}
|
||||
Rect boundingBox = this.boundingBox;
|
||||
int[] bitmapData = new int[boundingBox.width() * boundingBox.height()];
|
||||
ParsableBitArray bitBuffer = new ParsableBitArray();
|
||||
|
||||
buffer.setPosition(dataOffset0);
|
||||
bitBuffer.reset(buffer);
|
||||
parseRleData(bitBuffer, /* evenInterlace= */ true, boundingBox, bitmapData);
|
||||
buffer.setPosition(dataOffset1);
|
||||
bitBuffer.reset(buffer);
|
||||
parseRleData(bitBuffer, /* evenInterlace= */ false, boundingBox, bitmapData);
|
||||
|
||||
Bitmap bitmap =
|
||||
Bitmap.createBitmap(
|
||||
bitmapData, boundingBox.width(), boundingBox.height(), Bitmap.Config.ARGB_8888);
|
||||
|
||||
return new Cue.Builder()
|
||||
.setBitmap(bitmap)
|
||||
.setPosition((float) boundingBox.left / planeWidth)
|
||||
.setPositionAnchor(Cue.ANCHOR_TYPE_START)
|
||||
.setLine((float) boundingBox.top / planeHeight, Cue.LINE_TYPE_FRACTION)
|
||||
.setLineAnchor(Cue.ANCHOR_TYPE_START)
|
||||
.setSize((float) boundingBox.width() / planeWidth)
|
||||
.setBitmapHeight((float) boundingBox.height() / planeHeight)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse run-length encoded data into the {@code bitmapData} array. The subtitle bitmap is
|
||||
* encoded in two blocks of interlaced lines, {@code y} gives the index of the starting line (0
|
||||
* or 1).
|
||||
*
|
||||
* @param bitBuffer The RLE encoded data.
|
||||
* @param evenInterlace Whether to decode the even or odd interlaced lines.
|
||||
* @param bitmapData Output array.
|
||||
*/
|
||||
private void parseRleData(
|
||||
ParsableBitArray bitBuffer, boolean evenInterlace, Rect boundingBox, int[] bitmapData) {
|
||||
int width = boundingBox.width();
|
||||
int height = boundingBox.height();
|
||||
int x = 0;
|
||||
int y = evenInterlace ? 0 : 1;
|
||||
int outIndex = y * width;
|
||||
Run run = new Run();
|
||||
|
||||
while (true) {
|
||||
parseRun(bitBuffer, width, run);
|
||||
|
||||
int length = min(run.length, width - x);
|
||||
if (length > 0) {
|
||||
Arrays.fill(bitmapData, outIndex, outIndex + length, colors[run.colorIndex]);
|
||||
outIndex += length;
|
||||
x += length;
|
||||
}
|
||||
if (x >= width) {
|
||||
y += 2;
|
||||
if (y >= height) {
|
||||
break;
|
||||
}
|
||||
x = 0;
|
||||
outIndex = y * width;
|
||||
bitBuffer.byteAlign();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void parseRun(ParsableBitArray bitBuffer, int width, Run output) {
|
||||
int value = 0;
|
||||
int test = 1;
|
||||
|
||||
while (value < test && test <= 0x40) {
|
||||
if (bitBuffer.bitsLeft() < 4) {
|
||||
output.colorIndex = C.INDEX_UNSET;
|
||||
output.length = 0;
|
||||
return;
|
||||
}
|
||||
value = (value << 4) | bitBuffer.readBits(4);
|
||||
test <<= 2;
|
||||
}
|
||||
output.colorIndex = value & 3;
|
||||
output.length = value < 4 ? width : (value >> 2);
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
hasColors = false;
|
||||
boundingBox = null;
|
||||
dataOffset0 = C.INDEX_UNSET;
|
||||
dataOffset1 = C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
private static final class Run {
|
||||
public int colorIndex;
|
||||
public int length;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2025 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.
|
||||
*/
|
||||
@NonNullApi
|
||||
package androidx.media3.extractor.text.vobsub;
|
||||
|
||||
import androidx.media3.common.util.NonNullApi;
|
@ -50,6 +50,9 @@ public class DefaultSubtitleParserFactoryTest {
|
||||
if (fieldValue.equals(MimeTypes.APPLICATION_DVBSUBS)) {
|
||||
formatBuilder.setInitializationData(ImmutableList.of(new byte[] {1, 2, 3, 4}));
|
||||
}
|
||||
if (fieldValue.equals(MimeTypes.APPLICATION_VOBSUB)) {
|
||||
formatBuilder.setInitializationData(ImmutableList.of(new byte[] {1, 2, 3, 4}));
|
||||
}
|
||||
Format format = formatBuilder.build();
|
||||
if (factory.supportsFormat(format)) {
|
||||
try {
|
||||
|
Binary file not shown.
@ -0,0 +1,539 @@
|
||||
MediaCodecAdapter (exotest.audio.ac3):
|
||||
inputBuffers:
|
||||
count = 30
|
||||
input buffer #0:
|
||||
timeUs = 1000000000000
|
||||
contents = length 416, hash 211F2286
|
||||
input buffer #1:
|
||||
timeUs = 1000000034000
|
||||
contents = length 418, hash 77425A86
|
||||
input buffer #2:
|
||||
timeUs = 1000000069000
|
||||
contents = length 418, hash A0FE5CA1
|
||||
input buffer #3:
|
||||
timeUs = 1000000104000
|
||||
contents = length 418, hash 2309B066
|
||||
input buffer #4:
|
||||
timeUs = 1000000139000
|
||||
contents = length 418, hash 928A653B
|
||||
input buffer #5:
|
||||
timeUs = 1000000173000
|
||||
contents = length 418, hash 3422F0CB
|
||||
input buffer #6:
|
||||
timeUs = 1000000208000
|
||||
contents = length 418, hash EFF43D5B
|
||||
input buffer #7:
|
||||
timeUs = 1000000243000
|
||||
contents = length 418, hash FC8093C7
|
||||
input buffer #8:
|
||||
timeUs = 1000000279000
|
||||
contents = length 418, hash CCC08A16
|
||||
input buffer #9:
|
||||
timeUs = 1000000313000
|
||||
contents = length 418, hash 2A6EE863
|
||||
input buffer #10:
|
||||
timeUs = 1000000348000
|
||||
contents = length 418, hash D69A9251
|
||||
input buffer #11:
|
||||
timeUs = 1000000383000
|
||||
contents = length 418, hash BCFB758D
|
||||
input buffer #12:
|
||||
timeUs = 1000000418000
|
||||
contents = length 418, hash 11B66799
|
||||
input buffer #13:
|
||||
timeUs = 1000000452000
|
||||
contents = length 418, hash C824D392
|
||||
input buffer #14:
|
||||
timeUs = 1000000487000
|
||||
contents = length 418, hash C167D872
|
||||
input buffer #15:
|
||||
timeUs = 1000000522000
|
||||
contents = length 418, hash 4221C855
|
||||
input buffer #16:
|
||||
timeUs = 1000000557000
|
||||
contents = length 418, hash 4D4FF934
|
||||
input buffer #17:
|
||||
timeUs = 1000000591000
|
||||
contents = length 418, hash 984AA025
|
||||
input buffer #18:
|
||||
timeUs = 1000000626000
|
||||
contents = length 418, hash BB788B46
|
||||
input buffer #19:
|
||||
timeUs = 1000000661000
|
||||
contents = length 418, hash 9EFBFD97
|
||||
input buffer #20:
|
||||
timeUs = 1000000696000
|
||||
contents = length 418, hash DF1A460C
|
||||
input buffer #21:
|
||||
timeUs = 1000000730000
|
||||
contents = length 418, hash 2BDB56A
|
||||
input buffer #22:
|
||||
timeUs = 1000000765000
|
||||
contents = length 418, hash CA230060
|
||||
input buffer #23:
|
||||
timeUs = 1000000800000
|
||||
contents = length 418, hash D2F19F41
|
||||
input buffer #24:
|
||||
timeUs = 1000000836000
|
||||
contents = length 418, hash AF392D79
|
||||
input buffer #25:
|
||||
timeUs = 1000000870000
|
||||
contents = length 418, hash C5D7F2A3
|
||||
input buffer #26:
|
||||
timeUs = 1000000905000
|
||||
contents = length 418, hash 733A35AE
|
||||
input buffer #27:
|
||||
timeUs = 1000000940000
|
||||
contents = length 418, hash DE46E5D3
|
||||
input buffer #28:
|
||||
timeUs = 1000000975000
|
||||
contents = length 418, hash 56AB8D37
|
||||
input buffer #29:
|
||||
timeUs = 0
|
||||
flags = 4
|
||||
contents = length 0, hash 1
|
||||
outputBuffers:
|
||||
count = 29
|
||||
output buffer #0:
|
||||
timeUs = 1000000000000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #1:
|
||||
timeUs = 1000000034000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #2:
|
||||
timeUs = 1000000069000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #3:
|
||||
timeUs = 1000000104000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #4:
|
||||
timeUs = 1000000139000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #5:
|
||||
timeUs = 1000000173000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #6:
|
||||
timeUs = 1000000208000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #7:
|
||||
timeUs = 1000000243000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #8:
|
||||
timeUs = 1000000279000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #9:
|
||||
timeUs = 1000000313000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #10:
|
||||
timeUs = 1000000348000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #11:
|
||||
timeUs = 1000000383000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #12:
|
||||
timeUs = 1000000418000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #13:
|
||||
timeUs = 1000000452000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #14:
|
||||
timeUs = 1000000487000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #15:
|
||||
timeUs = 1000000522000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #16:
|
||||
timeUs = 1000000557000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #17:
|
||||
timeUs = 1000000591000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #18:
|
||||
timeUs = 1000000626000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #19:
|
||||
timeUs = 1000000661000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #20:
|
||||
timeUs = 1000000696000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #21:
|
||||
timeUs = 1000000730000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #22:
|
||||
timeUs = 1000000765000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #23:
|
||||
timeUs = 1000000800000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #24:
|
||||
timeUs = 1000000836000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #25:
|
||||
timeUs = 1000000870000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #26:
|
||||
timeUs = 1000000905000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #27:
|
||||
timeUs = 1000000940000
|
||||
size = 0
|
||||
rendered = false
|
||||
output buffer #28:
|
||||
timeUs = 1000000975000
|
||||
size = 0
|
||||
rendered = false
|
||||
MediaCodecAdapter (exotest.video.avc):
|
||||
inputBuffers:
|
||||
count = 31
|
||||
input buffer #0:
|
||||
timeUs = 1000000000000
|
||||
contents = length 36517, hash B334DF25
|
||||
input buffer #1:
|
||||
timeUs = 1000000003000
|
||||
contents = length 5341, hash 40B85E2
|
||||
input buffer #2:
|
||||
timeUs = 1000000002000
|
||||
contents = length 596, hash 357B4D92
|
||||
input buffer #3:
|
||||
timeUs = 1000000010000
|
||||
contents = length 7704, hash A39EDA06
|
||||
input buffer #4:
|
||||
timeUs = 1000000007000
|
||||
contents = length 989, hash 2813C72D
|
||||
input buffer #5:
|
||||
timeUs = 1000000005000
|
||||
contents = length 721, hash C50D1C73
|
||||
input buffer #6:
|
||||
timeUs = 1000000008000
|
||||
contents = length 519, hash 65FE1911
|
||||
input buffer #7:
|
||||
timeUs = 1000000017000
|
||||
contents = length 6160, hash E1CAC0EC
|
||||
input buffer #8:
|
||||
timeUs = 1000000013000
|
||||
contents = length 953, hash 7160C661
|
||||
input buffer #9:
|
||||
timeUs = 1000000012000
|
||||
contents = length 620, hash 7A7AE07C
|
||||
input buffer #10:
|
||||
timeUs = 1000000015000
|
||||
contents = length 405, hash 5CC7F4E7
|
||||
input buffer #11:
|
||||
timeUs = 1000000022000
|
||||
contents = length 4852, hash 9DB6979D
|
||||
input buffer #12:
|
||||
timeUs = 1000000020000
|
||||
contents = length 547, hash E31A6979
|
||||
input buffer #13:
|
||||
timeUs = 1000000018000
|
||||
contents = length 570, hash FEC40D00
|
||||
input buffer #14:
|
||||
timeUs = 1000000028000
|
||||
contents = length 5525, hash 7C478F7E
|
||||
input buffer #15:
|
||||
timeUs = 1000000025000
|
||||
contents = length 1082, hash DA07059A
|
||||
input buffer #16:
|
||||
timeUs = 1000000023000
|
||||
contents = length 807, hash 93478E6B
|
||||
input buffer #17:
|
||||
timeUs = 1000000027000
|
||||
contents = length 744, hash 9A8E6026
|
||||
input buffer #18:
|
||||
timeUs = 1000000035000
|
||||
contents = length 4732, hash C73B23C0
|
||||
input buffer #19:
|
||||
timeUs = 1000000032000
|
||||
contents = length 1004, hash 8A19A228
|
||||
input buffer #20:
|
||||
timeUs = 1000000030000
|
||||
contents = length 794, hash 8126022C
|
||||
input buffer #21:
|
||||
timeUs = 1000000033000
|
||||
contents = length 645, hash F08300E5
|
||||
input buffer #22:
|
||||
timeUs = 1000000042000
|
||||
contents = length 2684, hash 727FE378
|
||||
input buffer #23:
|
||||
timeUs = 1000000038000
|
||||
contents = length 787, hash 419A7821
|
||||
input buffer #24:
|
||||
timeUs = 1000000037000
|
||||
contents = length 649, hash 5C159346
|
||||
input buffer #25:
|
||||
timeUs = 1000000040000
|
||||
contents = length 509, hash F912D655
|
||||
input buffer #26:
|
||||
timeUs = 1000000048000
|
||||
contents = length 1226, hash 29815C21
|
||||
input buffer #27:
|
||||
timeUs = 1000000045000
|
||||
contents = length 898, hash D997AD0A
|
||||
input buffer #28:
|
||||
timeUs = 1000000043000
|
||||
contents = length 476, hash A0423645
|
||||
input buffer #29:
|
||||
timeUs = 1000000047000
|
||||
contents = length 486, hash DDF32CBB
|
||||
input buffer #30:
|
||||
timeUs = 0
|
||||
flags = 4
|
||||
contents = length 0, hash 1
|
||||
outputBuffers:
|
||||
count = 30
|
||||
output buffer #0:
|
||||
timeUs = 1000000000000
|
||||
size = 36517
|
||||
rendered = true
|
||||
output buffer #1:
|
||||
timeUs = 1000000003000
|
||||
size = 5341
|
||||
rendered = true
|
||||
output buffer #2:
|
||||
timeUs = 1000000002000
|
||||
size = 596
|
||||
rendered = true
|
||||
output buffer #3:
|
||||
timeUs = 1000000010000
|
||||
size = 7704
|
||||
rendered = true
|
||||
output buffer #4:
|
||||
timeUs = 1000000007000
|
||||
size = 989
|
||||
rendered = true
|
||||
output buffer #5:
|
||||
timeUs = 1000000005000
|
||||
size = 721
|
||||
rendered = true
|
||||
output buffer #6:
|
||||
timeUs = 1000000008000
|
||||
size = 519
|
||||
rendered = true
|
||||
output buffer #7:
|
||||
timeUs = 1000000017000
|
||||
size = 6160
|
||||
rendered = true
|
||||
output buffer #8:
|
||||
timeUs = 1000000013000
|
||||
size = 953
|
||||
rendered = true
|
||||
output buffer #9:
|
||||
timeUs = 1000000012000
|
||||
size = 620
|
||||
rendered = true
|
||||
output buffer #10:
|
||||
timeUs = 1000000015000
|
||||
size = 405
|
||||
rendered = true
|
||||
output buffer #11:
|
||||
timeUs = 1000000022000
|
||||
size = 4852
|
||||
rendered = true
|
||||
output buffer #12:
|
||||
timeUs = 1000000020000
|
||||
size = 547
|
||||
rendered = true
|
||||
output buffer #13:
|
||||
timeUs = 1000000018000
|
||||
size = 570
|
||||
rendered = true
|
||||
output buffer #14:
|
||||
timeUs = 1000000028000
|
||||
size = 5525
|
||||
rendered = true
|
||||
output buffer #15:
|
||||
timeUs = 1000000025000
|
||||
size = 1082
|
||||
rendered = true
|
||||
output buffer #16:
|
||||
timeUs = 1000000023000
|
||||
size = 807
|
||||
rendered = true
|
||||
output buffer #17:
|
||||
timeUs = 1000000027000
|
||||
size = 744
|
||||
rendered = true
|
||||
output buffer #18:
|
||||
timeUs = 1000000035000
|
||||
size = 4732
|
||||
rendered = true
|
||||
output buffer #19:
|
||||
timeUs = 1000000032000
|
||||
size = 1004
|
||||
rendered = true
|
||||
output buffer #20:
|
||||
timeUs = 1000000030000
|
||||
size = 794
|
||||
rendered = true
|
||||
output buffer #21:
|
||||
timeUs = 1000000033000
|
||||
size = 645
|
||||
rendered = true
|
||||
output buffer #22:
|
||||
timeUs = 1000000042000
|
||||
size = 2684
|
||||
rendered = true
|
||||
output buffer #23:
|
||||
timeUs = 1000000038000
|
||||
size = 787
|
||||
rendered = true
|
||||
output buffer #24:
|
||||
timeUs = 1000000037000
|
||||
size = 649
|
||||
rendered = true
|
||||
output buffer #25:
|
||||
timeUs = 1000000040000
|
||||
size = 509
|
||||
rendered = true
|
||||
output buffer #26:
|
||||
timeUs = 1000000048000
|
||||
size = 1226
|
||||
rendered = true
|
||||
output buffer #27:
|
||||
timeUs = 1000000045000
|
||||
size = 898
|
||||
rendered = true
|
||||
output buffer #28:
|
||||
timeUs = 1000000043000
|
||||
size = 476
|
||||
rendered = true
|
||||
output buffer #29:
|
||||
timeUs = 1000000047000
|
||||
size = 486
|
||||
rendered = true
|
||||
AudioSink:
|
||||
buffer count = 29
|
||||
config:
|
||||
pcmEncoding = 2
|
||||
channelCount = 1
|
||||
sampleRate = 44100
|
||||
buffer #0:
|
||||
time = 1000000000000
|
||||
data = 1
|
||||
buffer #1:
|
||||
time = 1000000034000
|
||||
data = 1
|
||||
buffer #2:
|
||||
time = 1000000069000
|
||||
data = 1
|
||||
buffer #3:
|
||||
time = 1000000104000
|
||||
data = 1
|
||||
buffer #4:
|
||||
time = 1000000139000
|
||||
data = 1
|
||||
buffer #5:
|
||||
time = 1000000173000
|
||||
data = 1
|
||||
buffer #6:
|
||||
time = 1000000208000
|
||||
data = 1
|
||||
buffer #7:
|
||||
time = 1000000243000
|
||||
data = 1
|
||||
buffer #8:
|
||||
time = 1000000279000
|
||||
data = 1
|
||||
buffer #9:
|
||||
time = 1000000313000
|
||||
data = 1
|
||||
buffer #10:
|
||||
time = 1000000348000
|
||||
data = 1
|
||||
buffer #11:
|
||||
time = 1000000383000
|
||||
data = 1
|
||||
buffer #12:
|
||||
time = 1000000418000
|
||||
data = 1
|
||||
buffer #13:
|
||||
time = 1000000452000
|
||||
data = 1
|
||||
buffer #14:
|
||||
time = 1000000487000
|
||||
data = 1
|
||||
buffer #15:
|
||||
time = 1000000522000
|
||||
data = 1
|
||||
buffer #16:
|
||||
time = 1000000557000
|
||||
data = 1
|
||||
buffer #17:
|
||||
time = 1000000591000
|
||||
data = 1
|
||||
buffer #18:
|
||||
time = 1000000626000
|
||||
data = 1
|
||||
buffer #19:
|
||||
time = 1000000661000
|
||||
data = 1
|
||||
buffer #20:
|
||||
time = 1000000696000
|
||||
data = 1
|
||||
buffer #21:
|
||||
time = 1000000730000
|
||||
data = 1
|
||||
buffer #22:
|
||||
time = 1000000765000
|
||||
data = 1
|
||||
buffer #23:
|
||||
time = 1000000800000
|
||||
data = 1
|
||||
buffer #24:
|
||||
time = 1000000836000
|
||||
data = 1
|
||||
buffer #25:
|
||||
time = 1000000870000
|
||||
data = 1
|
||||
buffer #26:
|
||||
time = 1000000905000
|
||||
data = 1
|
||||
buffer #27:
|
||||
time = 1000000940000
|
||||
data = 1
|
||||
buffer #28:
|
||||
time = 1000000975000
|
||||
data = 1
|
||||
TextOutput:
|
||||
Subtitle[0]:
|
||||
presentationTimeUs = 0
|
||||
Cues = []
|
||||
Subtitle[1]:
|
||||
presentationTimeUs = 0
|
||||
Cue[0]:
|
||||
bitmap = length 296960, hash 18F91C99
|
||||
line = 0.88611114
|
||||
lineType = 0
|
||||
lineAnchor = 0
|
||||
position = 0.0
|
||||
positionAnchor = 0
|
||||
size = 1.0
|
||||
bitmapHeight = 0.08055556
|
Loading…
x
Reference in New Issue
Block a user