Add support for AAC-LATM in transport streams
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=163337073
This commit is contained in:
parent
5278a63768
commit
84d19c464b
@ -16,7 +16,6 @@
|
|||||||
package com.google.android.exoplayer2.util;
|
package com.google.android.exoplayer2.util;
|
||||||
|
|
||||||
import android.test.MoreAsserts;
|
import android.test.MoreAsserts;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
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,
|
private static final byte[] TEST_DATA = new byte[] {0x3C, (byte) 0xD2, (byte) 0x5F, (byte) 0x01,
|
||||||
(byte) 0xFF, (byte) 0x14, (byte) 0x60, (byte) 0x99};
|
(byte) 0xFF, (byte) 0x14, (byte) 0x60, (byte) 0x99};
|
||||||
|
|
||||||
|
private ParsableBitArray testArray;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUp() {
|
||||||
|
testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
public void testReadAllBytes() {
|
public void testReadAllBytes() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
byte[] bytesRead = new byte[TEST_DATA.length];
|
byte[] bytesRead = new byte[TEST_DATA.length];
|
||||||
testArray.readBytes(bytesRead, 0, TEST_DATA.length);
|
testArray.readBytes(bytesRead, 0, TEST_DATA.length);
|
||||||
MoreAsserts.assertEquals(TEST_DATA, bytesRead);
|
MoreAsserts.assertEquals(TEST_DATA, bytesRead);
|
||||||
@ -37,13 +42,12 @@ public final class ParsableBitArrayTest extends TestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testReadBit() {
|
public void testReadBit() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
assertReadBitsToEnd(0);
|
||||||
assertReadBitsToEnd(0, testArray);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadBits() {
|
public void testReadBits() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
|
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
|
||||||
|
assertEquals(getTestDataBits(5, 0), testArray.readBits(0));
|
||||||
assertEquals(getTestDataBits(5, 3), testArray.readBits(3));
|
assertEquals(getTestDataBits(5, 3), testArray.readBits(3));
|
||||||
assertEquals(getTestDataBits(8, 16), testArray.readBits(16));
|
assertEquals(getTestDataBits(8, 16), testArray.readBits(16));
|
||||||
assertEquals(getTestDataBits(24, 3), testArray.readBits(3));
|
assertEquals(getTestDataBits(24, 3), testArray.readBits(3));
|
||||||
@ -52,67 +56,97 @@ public final class ParsableBitArrayTest extends TestCase {
|
|||||||
assertEquals(getTestDataBits(50, 14), testArray.readBits(14));
|
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() {
|
public void testRead32BitsByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
assertEquals(getTestDataBits(0, 32), testArray.readBits(32));
|
assertEquals(getTestDataBits(0, 32), testArray.readBits(32));
|
||||||
assertEquals(getTestDataBits(32, 32), testArray.readBits(32));
|
assertEquals(getTestDataBits(32, 32), testArray.readBits(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testRead32BitsNonByteAligned() {
|
public void testRead32BitsNonByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
|
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
|
||||||
assertEquals(getTestDataBits(5, 32), testArray.readBits(32));
|
assertEquals(getTestDataBits(5, 32), testArray.readBits(32));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipBytes() {
|
public void testSkipBytes() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
testArray.skipBytes(2);
|
testArray.skipBytes(2);
|
||||||
assertReadBitsToEnd(16, testArray);
|
assertReadBitsToEnd(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipBitsByteAligned() {
|
public void testSkipBitsByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
testArray.skipBits(16);
|
testArray.skipBits(16);
|
||||||
assertReadBitsToEnd(16, testArray);
|
assertReadBitsToEnd(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipBitsNonByteAligned() {
|
public void testSkipBitsNonByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
testArray.skipBits(5);
|
testArray.skipBits(5);
|
||||||
assertReadBitsToEnd(5, testArray);
|
assertReadBitsToEnd(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSetPositionByteAligned() {
|
public void testSetPositionByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
testArray.setPosition(16);
|
testArray.setPosition(16);
|
||||||
assertReadBitsToEnd(16, testArray);
|
assertReadBitsToEnd(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSetPositionNonByteAligned() {
|
public void testSetPositionNonByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
testArray.setPosition(5);
|
testArray.setPosition(5);
|
||||||
assertReadBitsToEnd(5, testArray);
|
assertReadBitsToEnd(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testByteAlignFromNonByteAligned() {
|
public void testByteAlignFromNonByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
testArray.setPosition(11);
|
testArray.setPosition(11);
|
||||||
testArray.byteAlign();
|
testArray.byteAlign();
|
||||||
assertEquals(2, testArray.getBytePosition());
|
assertEquals(2, testArray.getBytePosition());
|
||||||
assertEquals(16, testArray.getPosition());
|
assertEquals(16, testArray.getPosition());
|
||||||
assertReadBitsToEnd(16, testArray);
|
assertReadBitsToEnd(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testByteAlignFromByteAligned() {
|
public void testByteAlignFromByteAligned() {
|
||||||
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
|
||||||
testArray.setPosition(16);
|
testArray.setPosition(16);
|
||||||
testArray.byteAlign(); // Should be a no-op.
|
testArray.byteAlign(); // Should be a no-op.
|
||||||
assertEquals(2, testArray.getBytePosition());
|
assertEquals(2, testArray.getBytePosition());
|
||||||
assertEquals(16, testArray.getPosition());
|
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();
|
int position = testArray.getPosition();
|
||||||
assertEquals(expectedStartPosition, position);
|
assertEquals(expectedStartPosition, position);
|
||||||
for (int i = position; i < TEST_DATA.length * 8; i++) {
|
for (int i = position; i < TEST_DATA.length * 8; i++) {
|
||||||
|
@ -94,9 +94,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
|
|||||||
case TsExtractor.TS_STREAM_TYPE_MPA:
|
case TsExtractor.TS_STREAM_TYPE_MPA:
|
||||||
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
|
||||||
return new PesReader(new MpegAudioReader(esInfo.language));
|
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)
|
return isSet(FLAG_IGNORE_AAC_STREAM)
|
||||||
? null : new PesReader(new AdtsReader(false, esInfo.language));
|
? 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_AC3:
|
||||||
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
case TsExtractor.TS_STREAM_TYPE_E_AC3:
|
||||||
return new PesReader(new Ac3Reader(esInfo.language));
|
return new PesReader(new Ac3Reader(esInfo.language));
|
||||||
|
@ -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<Integer, Integer> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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 = 0x03;
|
||||||
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
|
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_AC3 = 0x81;
|
||||||
public static final int TS_STREAM_TYPE_DTS = 0x8A;
|
public static final int TS_STREAM_TYPE_DTS = 0x8A;
|
||||||
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;
|
||||||
|
@ -83,11 +83,21 @@ public final class CodecSpecificDataUtil {
|
|||||||
/**
|
/**
|
||||||
* Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
|
* 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.
|
* @return A pair consisting of the sample rate in Hz and the channel count.
|
||||||
*/
|
*/
|
||||||
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(byte[] audioSpecificConfig) {
|
public static Pair<Integer, Integer> 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<Integer, Integer> parseAacAudioSpecificConfig(ParsableBitArray bitArray) {
|
||||||
int audioObjectType = getAacAudioObjectType(bitArray);
|
int audioObjectType = getAacAudioObjectType(bitArray);
|
||||||
int sampleRate = getAacSamplingFrequency(bitArray);
|
int sampleRate = getAacSamplingFrequency(bitArray);
|
||||||
int channelConfiguration = bitArray.readBits(4);
|
int channelConfiguration = bitArray.readBits(4);
|
||||||
@ -104,6 +114,39 @@ public final class CodecSpecificDataUtil {
|
|||||||
channelConfiguration = bitArray.readBits(4);
|
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];
|
int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[channelConfiguration];
|
||||||
Assertions.checkArgument(channelCount != AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID);
|
Assertions.checkArgument(channelCount != AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID);
|
||||||
return Pair.create(sampleRate, channelCount);
|
return Pair.create(sampleRate, channelCount);
|
||||||
@ -269,4 +312,32 @@ public final class CodecSpecificDataUtil {
|
|||||||
return samplingFrequency;
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,9 @@ public final class ParsableBitArray {
|
|||||||
* @return An integer whose bottom n bits hold the read data.
|
* @return An integer whose bottom n bits hold the read data.
|
||||||
*/
|
*/
|
||||||
public int readBits(int numBits) {
|
public int readBits(int numBits) {
|
||||||
|
if (numBits == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
int returnValue = 0;
|
int returnValue = 0;
|
||||||
bitOffset += numBits;
|
bitOffset += numBits;
|
||||||
while (bitOffset > 8) {
|
while (bitOffset > 8) {
|
||||||
@ -171,6 +174,40 @@ public final class ParsableBitArray {
|
|||||||
return returnValue;
|
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.
|
* Aligns the position to the next byte boundary. Does nothing if the position is already aligned.
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user