summaryrefslogtreecommitdiffstats
path: root/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/FlacFrameReader.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/FlacFrameReader.java')
-rw-r--r--mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/FlacFrameReader.java336
1 files changed, 336 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/FlacFrameReader.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/FlacFrameReader.java
new file mode 100644
index 0000000000..e8d2b4928b
--- /dev/null
+++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/FlacFrameReader.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2019 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 org.mozilla.thirdparty.com.google.android.exoplayer2.extractor;
+
+import org.mozilla.thirdparty.com.google.android.exoplayer2.ParserException;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.FlacConstants;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.FlacStreamMetadata;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray;
+import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util;
+import java.io.IOException;
+
+/**
+ * Reads and peeks FLAC frame elements according to the <a
+ * href="https://xiph.org/flac/format.html">FLAC format specification</a>.
+ */
+public final class FlacFrameReader {
+
+ /** Holds a sample number. */
+ public static final class SampleNumberHolder {
+ /** The sample number. */
+ public long sampleNumber;
+ }
+
+ /**
+ * Checks whether the given FLAC frame header is valid and, if so, reads it and writes the frame
+ * first sample number in {@code sampleNumberHolder}.
+ *
+ * <p>If the header is valid, the position of {@code data} is moved to the byte following it.
+ * Otherwise, there is no guarantee on the position.
+ *
+ * @param data The array to read the data from, whose position must correspond to the frame
+ * header.
+ * @param flacStreamMetadata The stream metadata.
+ * @param frameStartMarker The frame start marker of the stream.
+ * @param sampleNumberHolder The holder used to contain the sample number.
+ * @return Whether the frame header is valid.
+ */
+ public static boolean checkAndReadFrameHeader(
+ ParsableByteArray data,
+ FlacStreamMetadata flacStreamMetadata,
+ int frameStartMarker,
+ SampleNumberHolder sampleNumberHolder) {
+ int frameStartPosition = data.getPosition();
+
+ long frameHeaderBytes = data.readUnsignedInt();
+ if (frameHeaderBytes >>> 16 != frameStartMarker) {
+ return false;
+ }
+
+ boolean isBlockSizeVariable = (frameHeaderBytes >>> 16 & 1) == 1;
+ int blockSizeKey = (int) (frameHeaderBytes >> 12 & 0xF);
+ int sampleRateKey = (int) (frameHeaderBytes >> 8 & 0xF);
+ int channelAssignmentKey = (int) (frameHeaderBytes >> 4 & 0xF);
+ int bitsPerSampleKey = (int) (frameHeaderBytes >> 1 & 0x7);
+ boolean reservedBit = (frameHeaderBytes & 1) == 1;
+ return checkChannelAssignment(channelAssignmentKey, flacStreamMetadata)
+ && checkBitsPerSample(bitsPerSampleKey, flacStreamMetadata)
+ && !reservedBit
+ && checkAndReadFirstSampleNumber(
+ data, flacStreamMetadata, isBlockSizeVariable, sampleNumberHolder)
+ && checkAndReadBlockSizeSamples(data, flacStreamMetadata, blockSizeKey)
+ && checkAndReadSampleRate(data, flacStreamMetadata, sampleRateKey)
+ && checkAndReadCrc(data, frameStartPosition);
+ }
+
+ /**
+ * Checks whether the given FLAC frame header is valid and, if so, writes the frame first sample
+ * number in {@code sampleNumberHolder}.
+ *
+ * <p>The {@code input} peek position is left unchanged.
+ *
+ * @param input The input to get the data from, whose peek position must correspond to the frame
+ * header.
+ * @param flacStreamMetadata The stream metadata.
+ * @param frameStartMarker The frame start marker of the stream.
+ * @param sampleNumberHolder The holder used to contain the sample number.
+ * @return Whether the frame header is valid.
+ */
+ public static boolean checkFrameHeaderFromPeek(
+ ExtractorInput input,
+ FlacStreamMetadata flacStreamMetadata,
+ int frameStartMarker,
+ SampleNumberHolder sampleNumberHolder)
+ throws IOException, InterruptedException {
+ long originalPeekPosition = input.getPeekPosition();
+
+ byte[] frameStartBytes = new byte[2];
+ input.peekFully(frameStartBytes, 0, 2);
+ int frameStart = (frameStartBytes[0] & 0xFF) << 8 | (frameStartBytes[1] & 0xFF);
+ if (frameStart != frameStartMarker) {
+ input.resetPeekPosition();
+ input.advancePeekPosition((int) (originalPeekPosition - input.getPosition()));
+ return false;
+ }
+
+ ParsableByteArray scratch = new ParsableByteArray(FlacConstants.MAX_FRAME_HEADER_SIZE);
+ System.arraycopy(
+ frameStartBytes, /* srcPos= */ 0, scratch.data, /* destPos= */ 0, /* length= */ 2);
+
+ int totalBytesPeeked =
+ ExtractorUtil.peekToLength(input, scratch.data, 2, FlacConstants.MAX_FRAME_HEADER_SIZE - 2);
+ scratch.setLimit(totalBytesPeeked);
+
+ input.resetPeekPosition();
+ input.advancePeekPosition((int) (originalPeekPosition - input.getPosition()));
+
+ return checkAndReadFrameHeader(
+ scratch, flacStreamMetadata, frameStartMarker, sampleNumberHolder);
+ }
+
+ /**
+ * Returns the number of the first sample in the given frame.
+ *
+ * <p>The read position of {@code input} is left unchanged.
+ *
+ * <p>If no exception is thrown, the peek position is aligned with the read position. Otherwise,
+ * there is no guarantee on the peek position.
+ *
+ * @param input Input stream to get the sample number from (starting from the read position).
+ * @return The frame first sample number.
+ * @throws ParserException If an error occurs parsing the sample number.
+ * @throws IOException If peeking from the input fails.
+ * @throws InterruptedException If interrupted while peeking from input.
+ */
+ public static long getFirstSampleNumber(
+ ExtractorInput input, FlacStreamMetadata flacStreamMetadata)
+ throws IOException, InterruptedException {
+ input.resetPeekPosition();
+ input.advancePeekPosition(1);
+ byte[] blockingStrategyByte = new byte[1];
+ input.peekFully(blockingStrategyByte, 0, 1);
+ boolean isBlockSizeVariable = (blockingStrategyByte[0] & 1) == 1;
+ input.advancePeekPosition(2);
+
+ int maxUtf8SampleNumberSize = isBlockSizeVariable ? 7 : 6;
+ ParsableByteArray scratch = new ParsableByteArray(maxUtf8SampleNumberSize);
+ int totalBytesPeeked =
+ ExtractorUtil.peekToLength(input, scratch.data, 0, maxUtf8SampleNumberSize);
+ scratch.setLimit(totalBytesPeeked);
+ input.resetPeekPosition();
+
+ SampleNumberHolder sampleNumberHolder = new SampleNumberHolder();
+ if (!checkAndReadFirstSampleNumber(
+ scratch, flacStreamMetadata, isBlockSizeVariable, sampleNumberHolder)) {
+ throw new ParserException();
+ }
+
+ return sampleNumberHolder.sampleNumber;
+ }
+
+ /**
+ * Reads the given block size.
+ *
+ * @param data The array to read the data from, whose position must correspond to the block size
+ * bits.
+ * @param blockSizeKey The key in the block size lookup table.
+ * @return The block size in samples, or -1 if the {@code blockSizeKey} is invalid.
+ */
+ public static int readFrameBlockSizeSamplesFromKey(ParsableByteArray data, int blockSizeKey) {
+ switch (blockSizeKey) {
+ case 1:
+ return 192;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ return 576 << (blockSizeKey - 2);
+ case 6:
+ return data.readUnsignedByte() + 1;
+ case 7:
+ return data.readUnsignedShort() + 1;
+ case 8:
+ case 9:
+ case 10:
+ case 11:
+ case 12:
+ case 13:
+ case 14:
+ case 15:
+ return 256 << (blockSizeKey - 8);
+ default:
+ return -1;
+ }
+ }
+
+ /**
+ * Checks whether the given channel assignment is valid.
+ *
+ * @param channelAssignmentKey The channel assignment lookup key.
+ * @param flacStreamMetadata The stream metadata.
+ * @return Whether the channel assignment is valid.
+ */
+ private static boolean checkChannelAssignment(
+ int channelAssignmentKey, FlacStreamMetadata flacStreamMetadata) {
+ if (channelAssignmentKey <= 7) {
+ return channelAssignmentKey == flacStreamMetadata.channels - 1;
+ } else if (channelAssignmentKey <= 10) {
+ return flacStreamMetadata.channels == 2;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks whether the given number of bits per sample is valid.
+ *
+ * @param bitsPerSampleKey The bits per sample lookup key.
+ * @param flacStreamMetadata The stream metadata.
+ * @return Whether the number of bits per sample is valid.
+ */
+ private static boolean checkBitsPerSample(
+ int bitsPerSampleKey, FlacStreamMetadata flacStreamMetadata) {
+ if (bitsPerSampleKey == 0) {
+ return true;
+ }
+ return bitsPerSampleKey == flacStreamMetadata.bitsPerSampleLookupKey;
+ }
+
+ /**
+ * Checks whether the given sample number is valid and, if so, reads it and writes it in {@code
+ * sampleNumberHolder}.
+ *
+ * <p>If the sample number is valid, the position of {@code data} is moved to the byte following
+ * it. Otherwise, there is no guarantee on the position.
+ *
+ * @param data The array to read the data from, whose position must correspond to the sample
+ * number data.
+ * @param flacStreamMetadata The stream metadata.
+ * @param isBlockSizeVariable Whether the stream blocking strategy is variable block size or fixed
+ * block size.
+ * @param sampleNumberHolder The holder used to contain the sample number.
+ * @return Whether the sample number is valid.
+ */
+ private static boolean checkAndReadFirstSampleNumber(
+ ParsableByteArray data,
+ FlacStreamMetadata flacStreamMetadata,
+ boolean isBlockSizeVariable,
+ SampleNumberHolder sampleNumberHolder) {
+ long utf8Value;
+ try {
+ utf8Value = data.readUtf8EncodedLong();
+ } catch (NumberFormatException e) {
+ return false;
+ }
+
+ sampleNumberHolder.sampleNumber =
+ isBlockSizeVariable ? utf8Value : utf8Value * flacStreamMetadata.maxBlockSizeSamples;
+ return true;
+ }
+
+ /**
+ * Checks whether the given frame block size key and block size bits are valid and, if so, reads
+ * the block size bits.
+ *
+ * <p>If the block size is valid, the position of {@code data} is moved to the byte following the
+ * block size bits. Otherwise, there is no guarantee on the position.
+ *
+ * @param data The array to read the data from, whose position must correspond to the block size
+ * bits.
+ * @param flacStreamMetadata The stream metadata.
+ * @param blockSizeKey The key in the block size lookup table.
+ * @return Whether the block size is valid.
+ */
+ private static boolean checkAndReadBlockSizeSamples(
+ ParsableByteArray data, FlacStreamMetadata flacStreamMetadata, int blockSizeKey) {
+ int blockSizeSamples = readFrameBlockSizeSamplesFromKey(data, blockSizeKey);
+ return blockSizeSamples != -1 && blockSizeSamples <= flacStreamMetadata.maxBlockSizeSamples;
+ }
+
+ /**
+ * Checks whether the given sample rate key and sample rate bits are valid and, if so, reads the
+ * sample rate bits.
+ *
+ * <p>If the sample rate is valid, the position of {@code data} is moved to the byte following the
+ * sample rate bits. Otherwise, there is no guarantee on the position.
+ *
+ * @param data The array to read the data from, whose position must indicate the sample rate bits.
+ * @param flacStreamMetadata The stream metadata.
+ * @param sampleRateKey The key in the sample rate lookup table.
+ * @return Whether the sample rate is valid.
+ */
+ private static boolean checkAndReadSampleRate(
+ ParsableByteArray data, FlacStreamMetadata flacStreamMetadata, int sampleRateKey) {
+ int expectedSampleRate = flacStreamMetadata.sampleRate;
+ if (sampleRateKey == 0) {
+ return true;
+ } else if (sampleRateKey <= 11) {
+ return sampleRateKey == flacStreamMetadata.sampleRateLookupKey;
+ } else if (sampleRateKey == 12) {
+ return data.readUnsignedByte() * 1000 == expectedSampleRate;
+ } else if (sampleRateKey <= 14) {
+ int sampleRate = data.readUnsignedShort();
+ if (sampleRateKey == 14) {
+ sampleRate *= 10;
+ }
+ return sampleRate == expectedSampleRate;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks whether the given CRC is valid and, if so, reads it.
+ *
+ * <p>If the CRC is valid, the position of {@code data} is moved to the byte following it.
+ * Otherwise, there is no guarantee on the position.
+ *
+ * <p>The {@code data} array must contain the whole frame header.
+ *
+ * @param data The array to read the data from, whose position must indicate the CRC.
+ * @param frameStartPosition The frame start offset in {@code data}.
+ * @return Whether the CRC is valid.
+ */
+ private static boolean checkAndReadCrc(ParsableByteArray data, int frameStartPosition) {
+ int crc = data.readUnsignedByte();
+ int frameEndPosition = data.getPosition();
+ int expectedCrc =
+ Util.crc8(data.data, frameStartPosition, frameEndPosition - 1, /* initialValue= */ 0);
+ return crc == expectedCrc;
+ }
+
+ private FlacFrameReader() {}
+}