diff --git a/library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsDecoder.java b/library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsDecoder.java index 1e45595144..091bda49f3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsDecoder.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/text/pgs/PgsDecoder.java @@ -25,7 +25,6 @@ import com.google.android.exoplayer2.util.Util; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.zip.DataFormatException; import java.util.zip.Inflater; /** A {@link SimpleSubtitleDecoder} for PGS subtitles. */ @@ -39,25 +38,22 @@ public final class PgsDecoder extends SimpleSubtitleDecoder { private static final byte INFLATE_HEADER = 0x78; private final ParsableByteArray buffer; + private final ParsableByteArray inflatedBuffer; private final CueBuilder cueBuilder; private Inflater inflater; - private byte[] inflatedData; - private int inflatedDataSize; public PgsDecoder() { super("PgsDecoder"); buffer = new ParsableByteArray(); + inflatedBuffer = new ParsableByteArray(); cueBuilder = new CueBuilder(); } @Override protected Subtitle decode(byte[] data, int size, boolean reset) throws SubtitleDecoderException { - if (maybeInflateData(data, size)) { - buffer.reset(inflatedData, inflatedDataSize); - } else { - buffer.reset(data, size); - } + buffer.reset(data, size); + maybeInflateData(buffer); cueBuilder.reset(); ArrayList cues = new ArrayList<>(); while (buffer.bytesLeft() >= 3) { @@ -69,31 +65,14 @@ public final class PgsDecoder extends SimpleSubtitleDecoder { return new PgsSubtitle(Collections.unmodifiableList(cues)); } - private boolean maybeInflateData(byte[] data, int size) { - if (size == 0 || data[0] != INFLATE_HEADER) { - return false; - } - if (inflater == null) { - inflater = new Inflater(); - inflatedData = new byte[size]; - } - inflatedDataSize = 0; - inflater.setInput(data, 0, size); - try { - while (!inflater.finished() && !inflater.needsDictionary() && !inflater.needsInput()) { - if (inflatedDataSize == inflatedData.length) { - inflatedData = Arrays.copyOf(inflatedData, inflatedData.length * 2); - } - inflatedDataSize += - inflater.inflate( - inflatedData, inflatedDataSize, inflatedData.length - inflatedDataSize); + private void maybeInflateData(ParsableByteArray buffer) { + if (buffer.bytesLeft() > 0 && buffer.peekUnsignedByte() == INFLATE_HEADER) { + if (inflater == null) { + inflater = new Inflater(); } - return inflater.finished(); - } catch (DataFormatException e) { - // Assume data is not compressed. - return false; - } finally { - inflater.reset(); + if (Util.inflate(buffer, inflatedBuffer, inflater)) { + buffer.reset(inflatedBuffer.data, inflatedBuffer.limit()); + } // else assume data is not compressed. } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java index 154cfc62ef..4b2944ce1a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -69,6 +69,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.PolyNull; @@ -1639,6 +1641,53 @@ public final class Util { return toUpperInvariant(Locale.getDefault().getCountry()); } + /** + * Uncompresses the data in {@code input}. + * + * @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} is null or it 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 + * length will be set to the length of the uncompressed data. + * @param inflater If not null, used to uncompressed the input. Otherwise a new {@link Inflater} + * is created. + * @return Whether the input is uncompressed successfully. + */ + public static boolean inflate( + ParsableByteArray input, ParsableByteArray output, @Nullable Inflater inflater) { + if (input.bytesLeft() <= 0) { + return false; + } + byte[] outputData = output.data; + if (outputData == null) { + outputData = new byte[input.bytesLeft()]; + } + if (inflater == null) { + inflater = new Inflater(); + } + inflater.setInput(input.data, input.getPosition(), input.bytesLeft()); + try { + int outputSize = 0; + while (true) { + outputSize += inflater.inflate(outputData, outputSize, outputData.length - outputSize); + if (inflater.finished()) { + output.reset(outputData, outputSize); + return true; + } + if (inflater.needsDictionary() || inflater.needsInput()) { + return false; + } + if (outputSize == outputData.length) { + outputData = Arrays.copyOf(outputData, outputData.length * 2); + } + } + } catch (DataFormatException e) { + return false; + } finally { + inflater.reset(); + } + } + /** * Gets the physical size of the default display, in pixels. * diff --git a/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java index cdd5d1a696..baf8aa7c40 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/util/UtilTest.java @@ -27,8 +27,10 @@ import static com.google.common.truth.Truth.assertThat; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.testutil.TestUtil; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Random; +import java.util.zip.Deflater; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -247,6 +249,23 @@ public class UtilTest { } } + @Test + public void testInflate() { + byte[] testData = TestUtil.buildTestData(/*arbitrary test data size*/ 256 * 1024); + byte[] compressedData = new byte[testData.length * 2]; + Deflater compresser = new Deflater(9); + compresser.setInput(testData); + compresser.finish(); + int compressedDataLength = compresser.deflate(compressedData); + compresser.end(); + + ParsableByteArray input = new ParsableByteArray(compressedData, compressedDataLength); + ParsableByteArray output = new ParsableByteArray(); + assertThat(Util.inflate(input, output, /* inflater= */ null)).isTrue(); + assertThat(output.limit()).isEqualTo(testData.length); + assertThat(Arrays.copyOf(output.data, output.limit())).isEqualTo(testData); + } + private static void assertEscapeUnescapeFileName(String fileName, String escapedFileName) { assertThat(escapeFileName(fileName)).isEqualTo(escapedFileName); assertThat(unescapeFileName(escapedFileName)).isEqualTo(fileName);