blob: 3465d89318c4cfea58583cff9165eac8ea65c503 [file] [log] [blame]
/*
* 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 androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.audio.AacUtil;
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.Assertions;
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;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* 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;
@Nullable private final String language;
private final ParsableByteArray sampleDataBuffer;
private final ParsableBitArray sampleBitArray;
// Track output info.
private @MonotonicNonNull TrackOutput output;
private @MonotonicNonNull String formatId;
private @MonotonicNonNull Format format;
// 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 audioMuxVersionA;
private int numSubframes;
private int frameLengthType;
private boolean otherDataPresent;
private long otherDataLenBits;
private int sampleRateHz;
private long sampleDurationUs;
private int channelCount;
@Nullable private String codecs;
/**
* @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, @TsPayloadReader.Flags int flags) {
timeUs = pesTimeUs;
}
@Override
public void consume(ParsableByteArray data) throws ParserException {
Assertions.checkStateNotNull(output); // Asserts that createTracks has been called.
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;
default:
throw new IllegalStateException();
}
}
}
@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.
*/
@RequiresNonNull("output")
private void parseAudioMuxElement(ParsableBitArray data) throws ParserException {
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 ParserException();
}
int muxSlotLengthBytes = parsePayloadLengthInfo(data);
parsePayloadMux(data, muxSlotLengthBytes);
if (otherDataPresent) {
data.skipBits((int) otherDataLenBits);
}
} else {
throw new ParserException(); // 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. */
@RequiresNonNull("output")
private void parseStreamMuxConfig(ParsableBitArray data) throws ParserException {
int 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 ParserException();
}
numSubframes = data.readBits(6);
int numProgram = data.readBits(4);
int numLayer = data.readBits(3);
if (numProgram != 0 || numLayer != 0) {
throw new ParserException();
}
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 =
new Format.Builder()
.setId(formatId)
.setSampleMimeType(MimeTypes.AUDIO_AAC)
.setCodecs(codecs)
.setChannelCount(channelCount)
.setSampleRate(sampleRateHz)
.setInitializationData(Collections.singletonList(initData))
.setLanguage(language)
.build();
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 ParserException(); // 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;
default:
throw new IllegalStateException();
}
}
private int parseAudioSpecificConfig(ParsableBitArray data) throws ParserException {
int bitsLeft = data.bitsLeft();
AacUtil.Config config = AacUtil.parseAudioSpecificConfig(data, /* forceReadToEnd= */ true);
codecs = config.codecs;
sampleRateHz = config.sampleRateHz;
channelCount = config.channelCount;
return bitsLeft - data.bitsLeft();
}
private int parsePayloadLengthInfo(ParsableBitArray data) throws ParserException {
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 ParserException();
}
}
@RequiresNonNull("output")
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);
}
}