diff options
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.java | 336 |
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() {} +} |