From 84d19c464b04e0dc54a133ed5f56fdfb04ed8266 Mon Sep 17 00:00:00 2001 From: aquilescanta Date: Thu, 27 Jul 2017 07:20:41 -0700 Subject: [PATCH] Add support for AAC-LATM in transport streams ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=163337073 --- .../exoplayer2/util/ParsableBitArrayTest.java | 78 +++-- .../ts/DefaultTsPayloadReaderFactory.java | 5 +- .../exoplayer2/extractor/ts/LatmReader.java | 306 ++++++++++++++++++ .../exoplayer2/extractor/ts/TsExtractor.java | 3 +- .../util/CodecSpecificDataUtil.java | 75 ++++- .../exoplayer2/util/ParsableBitArray.java | 37 +++ 6 files changed, 478 insertions(+), 26 deletions(-) create mode 100644 library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java index cfb9cd78be..fc8318d826 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/util/ParsableBitArrayTest.java @@ -16,7 +16,6 @@ package com.google.android.exoplayer2.util; import android.test.MoreAsserts; - import junit.framework.TestCase; /** @@ -27,8 +26,14 @@ public final class ParsableBitArrayTest extends TestCase { private static final byte[] TEST_DATA = new byte[] {0x3C, (byte) 0xD2, (byte) 0x5F, (byte) 0x01, (byte) 0xFF, (byte) 0x14, (byte) 0x60, (byte) 0x99}; + private ParsableBitArray testArray; + + @Override + public void setUp() { + testArray = new ParsableBitArray(TEST_DATA); + } + public void testReadAllBytes() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); byte[] bytesRead = new byte[TEST_DATA.length]; testArray.readBytes(bytesRead, 0, TEST_DATA.length); MoreAsserts.assertEquals(TEST_DATA, bytesRead); @@ -37,13 +42,12 @@ public final class ParsableBitArrayTest extends TestCase { } public void testReadBit() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); - assertReadBitsToEnd(0, testArray); + assertReadBitsToEnd(0); } public void testReadBits() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); assertEquals(getTestDataBits(0, 5), testArray.readBits(5)); + assertEquals(getTestDataBits(5, 0), testArray.readBits(0)); assertEquals(getTestDataBits(5, 3), testArray.readBits(3)); assertEquals(getTestDataBits(8, 16), testArray.readBits(16)); assertEquals(getTestDataBits(24, 3), testArray.readBits(3)); @@ -52,67 +56,97 @@ public final class ParsableBitArrayTest extends TestCase { assertEquals(getTestDataBits(50, 14), testArray.readBits(14)); } + public void testReadBitsToByteArray() { + byte[] result = new byte[TEST_DATA.length]; + // Test read within byte boundaries. + testArray.readBits(result, 0, 6); + assertEquals(TEST_DATA[0] & 0xFC, result[0]); + // Test read across byte boundaries. + testArray.readBits(result, 0, 8); + assertEquals(((TEST_DATA[0] & 0x03) << 6) | ((TEST_DATA[1] & 0xFC) >> 2), result[0]); + // Test reading across multiple bytes. + testArray.readBits(result, 1, 50); + for (int i = 1; i < 7; i++) { + assertEquals((byte) (((TEST_DATA[i] & 0x03) << 6) | ((TEST_DATA[i + 1] & 0xFC) >> 2)), + result[i]); + } + assertEquals((byte) (TEST_DATA[7] & 0x03) << 6, result[7]); + assertEquals(0, testArray.bitsLeft()); + // Test read last buffer byte across input data bytes. + testArray.setPosition(31); + result[3] = 0; + testArray.readBits(result, 3, 3); + assertEquals((byte) 0xE0, result[3]); + // Test read bits in the middle of a input data byte. + result[0] = 0; + assertEquals(34, testArray.getPosition()); + testArray.readBits(result, 0, 3); + assertEquals((byte) 0xE0, result[0]); + // Test read 0 bits. + testArray.setPosition(32); + result[1] = 0; + testArray.readBits(result, 1, 0); + assertEquals(0, result[1]); + // Test least significant bits are unmodified. + result[1] = (byte) 0xFF; + testArray.setPosition(16); + testArray.readBits(result, 0, 9); + assertEquals(0x5F, result[0]); + assertEquals(0x7F, result[1]); + } + public void testRead32BitsByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); assertEquals(getTestDataBits(0, 32), testArray.readBits(32)); assertEquals(getTestDataBits(32, 32), testArray.readBits(32)); } public void testRead32BitsNonByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); assertEquals(getTestDataBits(0, 5), testArray.readBits(5)); assertEquals(getTestDataBits(5, 32), testArray.readBits(32)); } public void testSkipBytes() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); testArray.skipBytes(2); - assertReadBitsToEnd(16, testArray); + assertReadBitsToEnd(16); } public void testSkipBitsByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); testArray.skipBits(16); - assertReadBitsToEnd(16, testArray); + assertReadBitsToEnd(16); } public void testSkipBitsNonByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); testArray.skipBits(5); - assertReadBitsToEnd(5, testArray); + assertReadBitsToEnd(5); } public void testSetPositionByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); testArray.setPosition(16); - assertReadBitsToEnd(16, testArray); + assertReadBitsToEnd(16); } public void testSetPositionNonByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); testArray.setPosition(5); - assertReadBitsToEnd(5, testArray); + assertReadBitsToEnd(5); } public void testByteAlignFromNonByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); testArray.setPosition(11); testArray.byteAlign(); assertEquals(2, testArray.getBytePosition()); assertEquals(16, testArray.getPosition()); - assertReadBitsToEnd(16, testArray); + assertReadBitsToEnd(16); } public void testByteAlignFromByteAligned() { - ParsableBitArray testArray = new ParsableBitArray(TEST_DATA); testArray.setPosition(16); testArray.byteAlign(); // Should be a no-op. assertEquals(2, testArray.getBytePosition()); assertEquals(16, testArray.getPosition()); - assertReadBitsToEnd(16, testArray); + assertReadBitsToEnd(16); } - private static void assertReadBitsToEnd(int expectedStartPosition, ParsableBitArray testArray) { + private void assertReadBitsToEnd(int expectedStartPosition) { int position = testArray.getPosition(); assertEquals(expectedStartPosition, position); for (int i = position; i < TEST_DATA.length * 8; i++) { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java index 40cfd7f8d9..bd013f96a3 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/DefaultTsPayloadReaderFactory.java @@ -94,9 +94,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact case TsExtractor.TS_STREAM_TYPE_MPA: case TsExtractor.TS_STREAM_TYPE_MPA_LSF: return new PesReader(new MpegAudioReader(esInfo.language)); - case TsExtractor.TS_STREAM_TYPE_AAC: + case TsExtractor.TS_STREAM_TYPE_AAC_ADTS: return isSet(FLAG_IGNORE_AAC_STREAM) ? null : new PesReader(new AdtsReader(false, esInfo.language)); + case TsExtractor.TS_STREAM_TYPE_AAC_LATM: + return isSet(FLAG_IGNORE_AAC_STREAM) + ? null : new PesReader(new LatmReader(esInfo.language)); case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3: return new PesReader(new Ac3Reader(esInfo.language)); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java new file mode 100644 index 0000000000..425dc43ea7 --- /dev/null +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2017 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.ts; + +import android.support.annotation.Nullable; +import android.util.Pair; +import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.Format; +import com.google.android.exoplayer2.extractor.ExtractorOutput; +import com.google.android.exoplayer2.extractor.TrackOutput; +import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator; +import com.google.android.exoplayer2.util.CodecSpecificDataUtil; +import com.google.android.exoplayer2.util.MimeTypes; +import com.google.android.exoplayer2.util.ParsableBitArray; +import com.google.android.exoplayer2.util.ParsableByteArray; +import java.util.Collections; + +/** + * Parses and extracts samples from an AAC/LATM elementary stream. + */ +public final class LatmReader implements ElementaryStreamReader { + + private static final int STATE_FINDING_SYNC_1 = 0; + private static final int STATE_FINDING_SYNC_2 = 1; + private static final int STATE_READING_HEADER = 2; + private static final int STATE_READING_SAMPLE = 3; + + private static final int INITIAL_BUFFER_SIZE = 1024; + private static final int SYNC_BYTE_FIRST = 0x56; + private static final int SYNC_BYTE_SECOND = 0xE0; + + private final String language; + private final ParsableByteArray sampleDataBuffer; + private final ParsableBitArray sampleBitArray; + + // Track output info. + private TrackOutput output; + private Format format; + private String formatId; + + // Parser state info. + private int state; + private int bytesRead; + private int sampleSize; + private int secondHeaderByte; + private long timeUs; + + // Container data. + private boolean streamMuxRead; + private int audioMuxVersion; + private int audioMuxVersionA; + private int numSubframes; + private int frameLengthType; + private boolean otherDataPresent; + private long otherDataLenBits; + private int sampleRateHz; + private long sampleDurationUs; + private int channelCount; + + /** + * @param language Track language. + */ + public LatmReader(@Nullable String language) { + this.language = language; + sampleDataBuffer = new ParsableByteArray(INITIAL_BUFFER_SIZE); + sampleBitArray = new ParsableBitArray(sampleDataBuffer.data); + } + + @Override + public void seek() { + state = STATE_FINDING_SYNC_1; + streamMuxRead = false; + } + + @Override + public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) { + idGenerator.generateNewId(); + output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO); + formatId = idGenerator.getFormatId(); + } + + @Override + public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) { + timeUs = pesTimeUs; + } + + @Override + public void consume(ParsableByteArray data) { + int bytesToRead; + while (data.bytesLeft() > 0) { + switch (state) { + case STATE_FINDING_SYNC_1: + if (data.readUnsignedByte() == SYNC_BYTE_FIRST) { + state = STATE_FINDING_SYNC_2; + } + break; + case STATE_FINDING_SYNC_2: + int secondByte = data.readUnsignedByte(); + if ((secondByte & SYNC_BYTE_SECOND) == SYNC_BYTE_SECOND) { + secondHeaderByte = secondByte; + state = STATE_READING_HEADER; + } else if (secondByte != SYNC_BYTE_FIRST) { + state = STATE_FINDING_SYNC_1; + } + break; + case STATE_READING_HEADER: + sampleSize = ((secondHeaderByte & ~SYNC_BYTE_SECOND) << 8) | data.readUnsignedByte(); + if (sampleSize > sampleDataBuffer.data.length) { + resetBufferForSize(sampleSize); + } + bytesRead = 0; + state = STATE_READING_SAMPLE; + break; + case STATE_READING_SAMPLE: + bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead); + data.readBytes(sampleBitArray.data, bytesRead, bytesToRead); + bytesRead += bytesToRead; + if (bytesRead == sampleSize) { + sampleBitArray.setPosition(0); + parseAudioMuxElement(sampleBitArray); + state = STATE_FINDING_SYNC_1; + } + break; + } + } + } + + @Override + public void packetFinished() { + // Do nothing. + } + + /** + * Parses an AudioMuxElement as defined in 14496-3:2009, Section 1.7.3.1, Table 1.41. + * + * @param data A {@link ParsableBitArray} containing the AudioMuxElement's bytes. + */ + private void parseAudioMuxElement(ParsableBitArray data) { + boolean useSameStreamMux = data.readBit(); + if (!useSameStreamMux) { + streamMuxRead = true; + parseStreamMuxConfig(data); + } else if (!streamMuxRead) { + return; // Parsing cannot continue without StreamMuxConfig information. + } + + if (audioMuxVersionA == 0) { + if (numSubframes != 0) { + throw new UnsupportedOperationException(); + } + int muxSlotLengthBytes = parsePayloadLengthInfo(data); + parsePayloadMux(data, muxSlotLengthBytes); + if (otherDataPresent) { + data.skipBits((int) otherDataLenBits); + } + } else { + throw new UnsupportedOperationException(); // Not defined by ISO/IEC 14496-3:2009. + } + } + + /** + * Parses a StreamMuxConfig as defined in ISO/IEC 14496-3:2009 Section 1.7.3.1, Table 1.42. + */ + private void parseStreamMuxConfig(ParsableBitArray data) { + audioMuxVersion = data.readBits(1); + audioMuxVersionA = audioMuxVersion == 1 ? data.readBits(1) : 0; + if (audioMuxVersionA == 0) { + if (audioMuxVersion == 1) { + latmGetValue(data); // Skip taraBufferFullness. + } + if (!data.readBit()) { + throw new UnsupportedOperationException(); + } + numSubframes = data.readBits(6); + int numProgram = data.readBits(4); + int numLayer = data.readBits(3); + if (numProgram != 0 || numLayer != 0) { + throw new UnsupportedOperationException(); + } + if (audioMuxVersion == 0) { + int startPosition = data.getPosition(); + int readBits = parseAudioSpecificConfig(data); + data.setPosition(startPosition); + byte[] initData = new byte[(readBits + 7) / 8]; + data.readBits(initData, 0, readBits); + Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null, + Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRateHz, + Collections.singletonList(initData), null, 0, language); + if (!format.equals(this.format)) { + this.format = format; + sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate; + output.format(format); + } + } else { + int ascLen = (int) latmGetValue(data); + int bitsRead = parseAudioSpecificConfig(data); + data.skipBits(ascLen - bitsRead); // fillBits. + } + parseFrameLength(data); + otherDataPresent = data.readBit(); + otherDataLenBits = 0; + if (otherDataPresent) { + if (audioMuxVersion == 1) { + otherDataLenBits = latmGetValue(data); + } else { + boolean otherDataLenEsc; + do { + otherDataLenEsc = data.readBit(); + otherDataLenBits = (otherDataLenBits << 8) + data.readBits(8); + } while (otherDataLenEsc); + } + } + boolean crcCheckPresent = data.readBit(); + if (crcCheckPresent) { + data.skipBits(8); // crcCheckSum. + } + } else { + throw new UnsupportedOperationException(); // This is not defined by ISO/IEC 14496-3:2009. + } + } + + private void parseFrameLength(ParsableBitArray data) { + frameLengthType = data.readBits(3); + switch (frameLengthType) { + case 0: + data.skipBits(8); // latmBufferFullness. + break; + case 1: + data.skipBits(9); // frameLength. + break; + case 3: + case 4: + case 5: + data.skipBits(6); // CELPframeLengthTableIndex. + break; + case 6: + case 7: + data.skipBits(1); // HVXCframeLengthTableIndex. + break; + } + } + + private int parseAudioSpecificConfig(ParsableBitArray data) { + int bitsLeft = data.bitsLeft(); + Pair config = CodecSpecificDataUtil.parseAacAudioSpecificConfig(data); + sampleRateHz = config.first; + channelCount = config.second; + return bitsLeft - data.bitsLeft(); + } + + private int parsePayloadLengthInfo(ParsableBitArray data) { + int muxSlotLengthBytes = 0; + // Assuming single program and single layer. + if (frameLengthType == 0) { + int tmp; + do { + tmp = data.readBits(8); + muxSlotLengthBytes += tmp; + } while (tmp == 255); + return muxSlotLengthBytes; + } else { + throw new UnsupportedOperationException(); + } + } + + private void parsePayloadMux(ParsableBitArray data, int muxLengthBytes) { + // The start of sample data in + int bitPosition = data.getPosition(); + if ((bitPosition & 0x07) == 0) { + // Sample data is byte-aligned. We can output it directly. + sampleDataBuffer.setPosition(bitPosition >> 3); + } else { + // Sample data is not byte-aligned and we need align it ourselves before outputting. + // Byte alignment is needed because LATM framing is not supported by MediaCodec. + data.readBits(sampleDataBuffer.data, 0, muxLengthBytes * 8); + sampleDataBuffer.setPosition(0); + } + output.sampleData(sampleDataBuffer, muxLengthBytes); + output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, muxLengthBytes, 0, null); + timeUs += sampleDurationUs; + } + + private void resetBufferForSize(int newSize) { + sampleDataBuffer.reset(newSize); + sampleBitArray.reset(sampleDataBuffer.data); + } + + private static long latmGetValue(ParsableBitArray data) { + int bytesForValue = data.readBits(2); + return data.readBits((bytesForValue + 1) * 8); + } + +} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 2929b8a076..90506ab2f6 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -84,7 +84,8 @@ public final class TsExtractor implements Extractor { public static final int TS_STREAM_TYPE_MPA = 0x03; public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; - public static final int TS_STREAM_TYPE_AAC = 0x0F; + public static final int TS_STREAM_TYPE_AAC_ADTS = 0x0F; + public static final int TS_STREAM_TYPE_AAC_LATM = 0x11; public static final int TS_STREAM_TYPE_AC3 = 0x81; public static final int TS_STREAM_TYPE_DTS = 0x8A; public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java b/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java index 0093c3b826..468e8dd666 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java @@ -83,11 +83,21 @@ public final class CodecSpecificDataUtil { /** * Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 * - * @param audioSpecificConfig The AudioSpecificConfig to parse. + * @param audioSpecificConfig A byte array containing the AudioSpecificConfig to parse. * @return A pair consisting of the sample rate in Hz and the channel count. */ public static Pair parseAacAudioSpecificConfig(byte[] audioSpecificConfig) { - ParsableBitArray bitArray = new ParsableBitArray(audioSpecificConfig); + return parseAacAudioSpecificConfig(new ParsableBitArray(audioSpecificConfig)); + } + + /** + * Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1 + * + * @param bitArray A {@link ParsableBitArray} containing the AudioSpecificConfig to parse. The + * position is advanced to the end of the AudioSpecificConfig. + * @return A pair consisting of the sample rate in Hz and the channel count. + */ + public static Pair parseAacAudioSpecificConfig(ParsableBitArray bitArray) { int audioObjectType = getAacAudioObjectType(bitArray); int sampleRate = getAacSamplingFrequency(bitArray); int channelConfiguration = bitArray.readBits(4); @@ -104,6 +114,39 @@ public final class CodecSpecificDataUtil { channelConfiguration = bitArray.readBits(4); } } + + switch (audioObjectType) { + case 1: + case 2: + case 3: + case 4: + case 6: + case 7: + case 17: + case 19: + case 20: + case 21: + case 22: + case 23: + parseGaSpecificConfig(bitArray, audioObjectType, channelConfiguration); + break; + default: + throw new UnsupportedOperationException(); + } + switch (audioObjectType) { + case 17: + case 19: + case 20: + case 21: + case 22: + case 23: + int epConfig = bitArray.readBits(2); + if (epConfig == 2 || epConfig == 3) { + throw new UnsupportedOperationException(); + } + break; + } + // For supported containers, bits_to_decode() is always 0. int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[channelConfiguration]; Assertions.checkArgument(channelCount != AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID); return Pair.create(sampleRate, channelCount); @@ -269,4 +312,32 @@ public final class CodecSpecificDataUtil { return samplingFrequency; } + private static void parseGaSpecificConfig(ParsableBitArray bitArray, int audioObjectType, + int channelConfiguration) { + bitArray.skipBits(1); // frameLengthFlag. + boolean dependsOnCoreDecoder = bitArray.readBit(); + if (dependsOnCoreDecoder) { + bitArray.skipBits(14); // coreCoderDelay. + } + boolean extensionFlag = bitArray.readBit(); + if (channelConfiguration == 0) { + throw new UnsupportedOperationException(); // TODO: Implement programConfigElement(); + } + if (audioObjectType == 6 || audioObjectType == 20) { + bitArray.skipBits(3); // layerNr. + } + if (extensionFlag) { + if (audioObjectType == 22) { + bitArray.skipBits(16); // numOfSubFrame (5), layer_length(11). + } + if (audioObjectType == 17 || audioObjectType == 19 || audioObjectType == 20 + || audioObjectType == 23) { + // aacSectionDataResilienceFlag, aacScalefactorDataResilienceFlag, + // aacSpectralDataResilienceFlag. + bitArray.skipBits(3); + } + bitArray.skipBits(1); // extensionFlag3. + } + } + } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java index 0456bcb879..e0238cb14d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/util/ParsableBitArray.java @@ -155,6 +155,9 @@ public final class ParsableBitArray { * @return An integer whose bottom n bits hold the read data. */ public int readBits(int numBits) { + if (numBits == 0) { + return 0; + } int returnValue = 0; bitOffset += numBits; while (bitOffset > 8) { @@ -171,6 +174,40 @@ public final class ParsableBitArray { return returnValue; } + /** + * Reads {@code numBits} bits into {@code buffer}. + * + * @param buffer The array into which the read data should be written. The trailing + * {@code numBits % 8} bits are written into the most significant bits of the last modified + * {@code buffer} byte. The remaining ones are unmodified. + * @param offset The offset in {@code buffer} at which the read data should be written. + * @param numBits The number of bits to read. + */ + public void readBits(byte[] buffer, int offset, int numBits) { + // Whole bytes. + int to = offset + (numBits >> 3) /* numBits / 8 */; + for (int i = offset; i < to; i++) { + buffer[i] = (byte) (data[byteOffset++] << bitOffset); + buffer[i] |= (data[byteOffset] & 0xFF) >> (8 - bitOffset); + } + // Trailing bits. + int bitsLeft = numBits & 7 /* numBits % 8 */; + buffer[to] &= 0xFF >> bitsLeft; // Set to 0 the bits that are going to be overwritten. + if (bitOffset + bitsLeft > 8) { + // We read the rest of data[byteOffset] and increase byteOffset. + buffer[to] |= (byte) ((data[byteOffset++] & 0xFF) << bitOffset); + bitOffset -= 8; + } + bitOffset += bitsLeft; + int lastDataByteTrailingBits = (data[byteOffset] & 0xFF) >> (8 - bitOffset); + buffer[to] |= (byte) (lastDataByteTrailingBits << (8 - bitsLeft)); + if (bitOffset == 8) { + bitOffset = 0; + byteOffset++; + } + assertValidOffset(); + } + /** * Aligns the position to the next byte boundary. Does nothing if the position is already aligned. */