Merge pull request #1979 from wischnow:main

PiperOrigin-RevId: 714911017
This commit is contained in:
Copybara-Service 2025-01-13 04:09:58 -08:00
commit d18ad57e30
10 changed files with 994 additions and 15 deletions

View File

@ -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:

View File

@ -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.
*

View File

@ -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;

View File

@ -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:

View File

@ -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();

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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