blob: 5fbd0e4ae4ab6e8d07dfe21619ef0521f1fc265c [file] [log] [blame]
/*
* Copyright (C) 2016 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.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.Arrays;
/** Utility methods for parsing Vorbis streams. */
public final class VorbisUtil {
/** Vorbis comment header. */
public static final class CommentHeader {
public final String vendor;
public final String[] comments;
public final int length;
public CommentHeader(String vendor, String[] comments, int length) {
this.vendor = vendor;
this.comments = comments;
this.length = length;
}
}
/**
* Vorbis identification header.
*
* @see <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-630004.2.2">Vorbis
* spec/Identification header</a>
*/
public static final class VorbisIdHeader {
/** The {@code vorbis_version} field. */
public final int version;
/** The {@code audio_channels} field. */
public final int channels;
/** The {@code audio_sample_rate} field. */
public final int sampleRate;
/** The {@code bitrate_maximum} field, or {@link Format#NO_VALUE} if not greater than zero. */
public final int bitrateMaximum;
/** The {@code bitrate_nominal} field, or {@link Format#NO_VALUE} if not greater than zero. */
public final int bitrateNominal;
/** The {@code bitrate_minimum} field, or {@link Format#NO_VALUE} if not greater than zero. */
public final int bitrateMinimum;
/** The {@code blocksize_0} field. */
public final int blockSize0;
/** The {@code blocksize_1} field. */
public final int blockSize1;
/** The {@code framing_flag} field. */
public final boolean framingFlag;
/** The raw header data. */
public final byte[] data;
/**
* @param version See {@link #version}.
* @param channels See {@link #channels}.
* @param sampleRate See {@link #sampleRate}.
* @param bitrateMaximum See {@link #bitrateMaximum}.
* @param bitrateNominal See {@link #bitrateNominal}.
* @param bitrateMinimum See {@link #bitrateMinimum}.
* @param blockSize0 See {@link #version}.
* @param blockSize1 See {@link #blockSize1}.
* @param framingFlag See {@link #framingFlag}.
* @param data See {@link #data}.
*/
public VorbisIdHeader(
int version,
int channels,
int sampleRate,
int bitrateMaximum,
int bitrateNominal,
int bitrateMinimum,
int blockSize0,
int blockSize1,
boolean framingFlag,
byte[] data) {
this.version = version;
this.channels = channels;
this.sampleRate = sampleRate;
this.bitrateMaximum = bitrateMaximum;
this.bitrateNominal = bitrateNominal;
this.bitrateMinimum = bitrateMinimum;
this.blockSize0 = blockSize0;
this.blockSize1 = blockSize1;
this.framingFlag = framingFlag;
this.data = data;
}
}
/** Vorbis setup header modes. */
public static final class Mode {
public final boolean blockFlag;
public final int windowType;
public final int transformType;
public final int mapping;
public Mode(boolean blockFlag, int windowType, int transformType, int mapping) {
this.blockFlag = blockFlag;
this.windowType = windowType;
this.transformType = transformType;
this.mapping = mapping;
}
}
private static final String TAG = "VorbisUtil";
/**
* Returns ilog(x), which is the index of the highest set bit in {@code x}.
*
* @see <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-1190009.2.1">Vorbis
* spec</a>
* @param x the value of which the ilog should be calculated.
* @return ilog(x)
*/
public static int iLog(int x) {
int val = 0;
while (x > 0) {
val++;
x >>>= 1;
}
return val;
}
/**
* Reads a Vorbis identification header from {@code headerData}.
*
* @see <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-630004.2.2">Vorbis
* spec/Identification header</a>
* @param headerData a {@link ParsableByteArray} wrapping the header data.
* @return a {@link VorbisUtil.VorbisIdHeader} with meta data.
* @throws ParserException thrown if invalid capture pattern is detected.
*/
public static VorbisIdHeader readVorbisIdentificationHeader(ParsableByteArray headerData)
throws ParserException {
verifyVorbisHeaderCapturePattern(0x01, headerData, false);
int version = headerData.readLittleEndianUnsignedIntToInt();
int channels = headerData.readUnsignedByte();
int sampleRate = headerData.readLittleEndianUnsignedIntToInt();
int bitrateMaximum = headerData.readLittleEndianInt();
if (bitrateMaximum <= 0) {
bitrateMaximum = Format.NO_VALUE;
}
int bitrateNominal = headerData.readLittleEndianInt();
if (bitrateNominal <= 0) {
bitrateNominal = Format.NO_VALUE;
}
int bitrateMinimum = headerData.readLittleEndianInt();
if (bitrateMinimum <= 0) {
bitrateMinimum = Format.NO_VALUE;
}
int blockSize = headerData.readUnsignedByte();
int blockSize0 = (int) Math.pow(2, blockSize & 0x0F);
int blockSize1 = (int) Math.pow(2, (blockSize & 0xF0) >> 4);
boolean framingFlag = (headerData.readUnsignedByte() & 0x01) > 0;
// raw data of Vorbis setup header has to be passed to decoder as CSD buffer #1
byte[] data = Arrays.copyOf(headerData.getData(), headerData.limit());
return new VorbisIdHeader(
version,
channels,
sampleRate,
bitrateMaximum,
bitrateNominal,
bitrateMinimum,
blockSize0,
blockSize1,
framingFlag,
data);
}
/**
* Reads a Vorbis comment header.
*
* @see <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3">Vorbis
* spec/Comment header</a>
* @param headerData A {@link ParsableByteArray} wrapping the header data.
* @return A {@link VorbisUtil.CommentHeader} with all the comments.
* @throws ParserException If an error occurs parsing the comment header.
*/
public static CommentHeader readVorbisCommentHeader(ParsableByteArray headerData)
throws ParserException {
return readVorbisCommentHeader(
headerData, /* hasMetadataHeader= */ true, /* hasFramingBit= */ true);
}
/**
* Reads a Vorbis comment header.
*
* <p>The data provided may not contain the Vorbis metadata common header and the framing bit.
*
* @see <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-640004.2.3">Vorbis
* spec/Comment header</a>
* @param headerData A {@link ParsableByteArray} wrapping the header data.
* @param hasMetadataHeader Whether the {@code headerData} contains a Vorbis metadata common
* header preceding the comment header.
* @param hasFramingBit Whether the {@code headerData} contains a framing bit.
* @return A {@link VorbisUtil.CommentHeader} with all the comments.
* @throws ParserException If an error occurs parsing the comment header.
*/
public static CommentHeader readVorbisCommentHeader(
ParsableByteArray headerData, boolean hasMetadataHeader, boolean hasFramingBit)
throws ParserException {
if (hasMetadataHeader) {
verifyVorbisHeaderCapturePattern(/* headerType= */ 0x03, headerData, /* quiet= */ false);
}
int length = 7;
int len = (int) headerData.readLittleEndianUnsignedInt();
length += 4;
String vendor = headerData.readString(len);
length += vendor.length();
long commentListLen = headerData.readLittleEndianUnsignedInt();
String[] comments = new String[(int) commentListLen];
length += 4;
for (int i = 0; i < commentListLen; i++) {
len = (int) headerData.readLittleEndianUnsignedInt();
length += 4;
comments[i] = headerData.readString(len);
length += comments[i].length();
}
if (hasFramingBit && (headerData.readUnsignedByte() & 0x01) == 0) {
throw ParserException.createForMalformedContainer(
"framing bit expected to be set", /* cause= */ null);
}
length += 1;
return new CommentHeader(vendor, comments, length);
}
/**
* Verifies whether the next bytes in {@code header} are a Vorbis header of the given {@code
* headerType}.
*
* @param headerType the type of the header expected.
* @param header the alleged header bytes.
* @param quiet if {@code true} no exceptions are thrown. Instead {@code false} is returned.
* @return the number of bytes read.
* @throws ParserException thrown if header type or capture pattern is not as expected.
*/
public static boolean verifyVorbisHeaderCapturePattern(
int headerType, ParsableByteArray header, boolean quiet) throws ParserException {
if (header.bytesLeft() < 7) {
if (quiet) {
return false;
} else {
throw ParserException.createForMalformedContainer(
"too short header: " + header.bytesLeft(), /* cause= */ null);
}
}
if (header.readUnsignedByte() != headerType) {
if (quiet) {
return false;
} else {
throw ParserException.createForMalformedContainer(
"expected header type " + Integer.toHexString(headerType), /* cause= */ null);
}
}
if (!(header.readUnsignedByte() == 'v'
&& header.readUnsignedByte() == 'o'
&& header.readUnsignedByte() == 'r'
&& header.readUnsignedByte() == 'b'
&& header.readUnsignedByte() == 'i'
&& header.readUnsignedByte() == 's')) {
if (quiet) {
return false;
} else {
throw ParserException.createForMalformedContainer(
"expected characters 'vorbis'", /* cause= */ null);
}
}
return true;
}
/**
* This method reads the modes which are located at the very end of the Vorbis setup header.
* That's why we need to partially decode or at least read the entire setup header to know where
* to start reading the modes.
*
* @see <a href="https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-650004.2.4">Vorbis
* spec/Setup header</a>
* @param headerData a {@link ParsableByteArray} containing setup header data.
* @param channels the number of channels.
* @return an array of {@link Mode}s.
* @throws ParserException thrown if bit stream is invalid.
*/
public static Mode[] readVorbisModes(ParsableByteArray headerData, int channels)
throws ParserException {
verifyVorbisHeaderCapturePattern(0x05, headerData, false);
int numberOfBooks = headerData.readUnsignedByte() + 1;
VorbisBitArray bitArray = new VorbisBitArray(headerData.getData());
bitArray.skipBits(headerData.getPosition() * 8);
for (int i = 0; i < numberOfBooks; i++) {
readBook(bitArray);
}
int timeCount = bitArray.readBits(6) + 1;
for (int i = 0; i < timeCount; i++) {
if (bitArray.readBits(16) != 0x00) {
throw ParserException.createForMalformedContainer(
"placeholder of time domain transforms not zeroed out", /* cause= */ null);
}
}
readFloors(bitArray);
readResidues(bitArray);
readMappings(channels, bitArray);
Mode[] modes = readModes(bitArray);
if (!bitArray.readBit()) {
throw ParserException.createForMalformedContainer(
"framing bit after modes not set as expected", /* cause= */ null);
}
return modes;
}
private static Mode[] readModes(VorbisBitArray bitArray) {
int modeCount = bitArray.readBits(6) + 1;
Mode[] modes = new Mode[modeCount];
for (int i = 0; i < modeCount; i++) {
boolean blockFlag = bitArray.readBit();
int windowType = bitArray.readBits(16);
int transformType = bitArray.readBits(16);
int mapping = bitArray.readBits(8);
modes[i] = new Mode(blockFlag, windowType, transformType, mapping);
}
return modes;
}
private static void readMappings(int channels, VorbisBitArray bitArray) throws ParserException {
int mappingsCount = bitArray.readBits(6) + 1;
for (int i = 0; i < mappingsCount; i++) {
int mappingType = bitArray.readBits(16);
if (mappingType != 0) {
Log.e(TAG, "mapping type other than 0 not supported: " + mappingType);
continue;
}
int submaps;
if (bitArray.readBit()) {
submaps = bitArray.readBits(4) + 1;
} else {
submaps = 1;
}
int couplingSteps;
if (bitArray.readBit()) {
couplingSteps = bitArray.readBits(8) + 1;
for (int j = 0; j < couplingSteps; j++) {
bitArray.skipBits(iLog(channels - 1)); // magnitude
bitArray.skipBits(iLog(channels - 1)); // angle
}
} /*else {
couplingSteps = 0;
}*/
if (bitArray.readBits(2) != 0x00) {
throw ParserException.createForMalformedContainer(
"to reserved bits must be zero after mapping coupling steps", /* cause= */ null);
}
if (submaps > 1) {
for (int j = 0; j < channels; j++) {
bitArray.skipBits(4); // mappingMux
}
}
for (int j = 0; j < submaps; j++) {
bitArray.skipBits(8); // discard
bitArray.skipBits(8); // submapFloor
bitArray.skipBits(8); // submapResidue
}
}
}
private static void readResidues(VorbisBitArray bitArray) throws ParserException {
int residueCount = bitArray.readBits(6) + 1;
for (int i = 0; i < residueCount; i++) {
int residueType = bitArray.readBits(16);
if (residueType > 2) {
throw ParserException.createForMalformedContainer(
"residueType greater than 2 is not decodable", /* cause= */ null);
} else {
bitArray.skipBits(24); // begin
bitArray.skipBits(24); // end
bitArray.skipBits(24); // partitionSize (add one)
int classifications = bitArray.readBits(6) + 1;
bitArray.skipBits(8); // classbook
int[] cascade = new int[classifications];
for (int j = 0; j < classifications; j++) {
int highBits = 0;
int lowBits = bitArray.readBits(3);
if (bitArray.readBit()) {
highBits = bitArray.readBits(5);
}
cascade[j] = highBits * 8 + lowBits;
}
for (int j = 0; j < classifications; j++) {
for (int k = 0; k < 8; k++) {
if ((cascade[j] & (0x01 << k)) != 0) {
bitArray.skipBits(8); // discard
}
}
}
}
}
}
private static void readFloors(VorbisBitArray bitArray) throws ParserException {
int floorCount = bitArray.readBits(6) + 1;
for (int i = 0; i < floorCount; i++) {
int floorType = bitArray.readBits(16);
switch (floorType) {
case 0:
bitArray.skipBits(8); // order
bitArray.skipBits(16); // rate
bitArray.skipBits(16); // barkMapSize
bitArray.skipBits(6); // amplitudeBits
bitArray.skipBits(8); // amplitudeOffset
int floorNumberOfBooks = bitArray.readBits(4) + 1;
for (int j = 0; j < floorNumberOfBooks; j++) {
bitArray.skipBits(8);
}
break;
case 1:
int partitions = bitArray.readBits(5);
int maximumClass = -1;
int[] partitionClassList = new int[partitions];
for (int j = 0; j < partitions; j++) {
partitionClassList[j] = bitArray.readBits(4);
if (partitionClassList[j] > maximumClass) {
maximumClass = partitionClassList[j];
}
}
int[] classDimensions = new int[maximumClass + 1];
for (int j = 0; j < classDimensions.length; j++) {
classDimensions[j] = bitArray.readBits(3) + 1;
int classSubclasses = bitArray.readBits(2);
if (classSubclasses > 0) {
bitArray.skipBits(8); // classMasterbooks
}
for (int k = 0; k < (1 << classSubclasses); k++) {
bitArray.skipBits(8); // subclassBook (subtract 1)
}
}
bitArray.skipBits(2); // multiplier (add one)
int rangeBits = bitArray.readBits(4);
int count = 0;
for (int j = 0, k = 0; j < partitions; j++) {
int idx = partitionClassList[j];
count += classDimensions[idx];
for (; k < count; k++) {
bitArray.skipBits(rangeBits); // floorValue
}
}
break;
default:
throw ParserException.createForMalformedContainer(
"floor type greater than 1 not decodable: " + floorType, /* cause= */ null);
}
}
}
private static CodeBook readBook(VorbisBitArray bitArray) throws ParserException {
if (bitArray.readBits(24) != 0x564342) {
throw ParserException.createForMalformedContainer(
"expected code book to start with [0x56, 0x43, 0x42] at " + bitArray.getPosition(),
/* cause= */ null);
}
int dimensions = bitArray.readBits(16);
int entries = bitArray.readBits(24);
long[] lengthMap = new long[entries];
boolean isOrdered = bitArray.readBit();
if (!isOrdered) {
boolean isSparse = bitArray.readBit();
for (int i = 0; i < lengthMap.length; i++) {
if (isSparse) {
if (bitArray.readBit()) {
lengthMap[i] = (long) (bitArray.readBits(5) + 1);
} else { // entry unused
lengthMap[i] = 0;
}
} else { // not sparse
lengthMap[i] = (long) (bitArray.readBits(5) + 1);
}
}
} else {
int length = bitArray.readBits(5) + 1;
for (int i = 0; i < lengthMap.length; ) {
int num = bitArray.readBits(iLog(entries - i));
for (int j = 0; j < num && i < lengthMap.length; i++, j++) {
lengthMap[i] = length;
}
length++;
}
}
int lookupType = bitArray.readBits(4);
if (lookupType > 2) {
throw ParserException.createForMalformedContainer(
"lookup type greater than 2 not decodable: " + lookupType, /* cause= */ null);
} else if (lookupType == 1 || lookupType == 2) {
bitArray.skipBits(32); // minimumValue
bitArray.skipBits(32); // deltaValue
int valueBits = bitArray.readBits(4) + 1;
bitArray.skipBits(1); // sequenceP
long lookupValuesCount;
if (lookupType == 1) {
if (dimensions != 0) {
lookupValuesCount = mapType1QuantValues(entries, dimensions);
} else {
lookupValuesCount = 0;
}
} else {
lookupValuesCount = (long) entries * dimensions;
}
// discard (no decoding required yet)
bitArray.skipBits((int) (lookupValuesCount * valueBits));
}
return new CodeBook(dimensions, entries, lengthMap, lookupType, isOrdered);
}
/**
* @see <a href="http://svn.xiph.org/trunk/vorbis/lib/sharedbook.c">_book_maptype1_quantvals</a>
*/
private static long mapType1QuantValues(long entries, long dimension) {
return (long) Math.floor(Math.pow(entries, 1.d / dimension));
}
private VorbisUtil() {
// Prevent instantiation.
}
private static final class CodeBook {
public final int dimensions;
public final int entries;
public final long[] lengthMap;
public final int lookupType;
public final boolean isOrdered;
public CodeBook(
int dimensions, int entries, long[] lengthMap, int lookupType, boolean isOrdered) {
this.dimensions = dimensions;
this.entries = entries;
this.lengthMap = lengthMap;
this.lookupType = lookupType;
this.isOrdered = isOrdered;
}
}
}