blob: aefc69c0d5ce65d0a41169b714da5bf77207ec6a [file] [log] [blame]
/*
* 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 com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.flac.FlacConstants;
import com.google.android.exoplayer2.util.ParsableByteArray;
import 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 {
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.getData(), /* destPos= */ 0, /* length= */ 2);
int totalBytesPeeked =
ExtractorUtil.peekToLength(
input, scratch.getData(), 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.
*/
public static long getFirstSampleNumber(
ExtractorInput input, FlacStreamMetadata flacStreamMetadata) throws IOException {
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.getData(), 0, maxUtf8SampleNumberSize);
scratch.setLimit(totalBytesPeeked);
input.resetPeekPosition();
SampleNumberHolder sampleNumberHolder = new SampleNumberHolder();
if (!checkAndReadFirstSampleNumber(
scratch, flacStreamMetadata, isBlockSizeVariable, sampleNumberHolder)) {
throw ParserException.createForMalformedContainer(/* message= */ null, /* cause= */ null);
}
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.getData(), frameStartPosition, frameEndPosition - 1, /* initialValue= */ 0);
return crc == expectedCrc;
}
private FlacFrameReader() {}
}