Construct codecs string for AAC in MP4/TS/FLV

PiperOrigin-RevId: 297578984
This commit is contained in:
andrewlewis 2020-02-27 14:09:21 +00:00 committed by kim-vde
parent c6a6e0d6f3
commit bd8ee155af
5 changed files with 87 additions and 40 deletions

View file

@ -23,11 +23,26 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Provides static utility methods for manipulating various types of codec specific data.
*/
/** Provides utilities for handling various types of codec-specific data. */
public final class CodecSpecificDataUtil {
/** Holds sample format information for AAC audio. */
public static final class AacConfig {
/** The sample rate in Hertz. */
public final int sampleRateHz;
/** The number of channels. */
public final int channelCount;
/** The RFC 6381 codecs string. */
public final String codecs;
private AacConfig(int sampleRateHz, int channelCount, String codecs) {
this.sampleRateHz = sampleRateHz;
this.channelCount = channelCount;
this.codecs = codecs;
}
}
private static final byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
private static final int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF;
@ -70,14 +85,20 @@ public final class CodecSpecificDataUtil {
AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID
};
/**
* Prefix for the RFC 6381 codecs string for AAC formats. To form a full codecs string, suffix the
* decimal AudioObjectType.
*/
private static final String AAC_CODECS_STRING_PREFIX = "mp4a.40.";
// Advanced Audio Coding Low-Complexity profile.
private static final int AUDIO_OBJECT_TYPE_AAC_LC = 2;
// Spectral Band Replication.
private static final int AUDIO_OBJECT_TYPE_SBR = 5;
private static final int AUDIO_OBJECT_TYPE_AAC_SBR = 5;
// Error Resilient Bit-Sliced Arithmetic Coding.
private static final int AUDIO_OBJECT_TYPE_ER_BSAC = 22;
private static final int AUDIO_OBJECT_TYPE_AAC_ER_BSAC = 22;
// Parametric Stereo.
private static final int AUDIO_OBJECT_TYPE_PS = 29;
private static final int AUDIO_OBJECT_TYPE_AAC_PS = 29;
// Escape code for extended audio object types.
private static final int AUDIO_OBJECT_TYPE_ESCAPE = 31;
@ -87,12 +108,13 @@ public final class CodecSpecificDataUtil {
* Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
*
* @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 The parsed configuration.
* @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
*/
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(byte[] audioSpecificConfig)
public static AacConfig parseAacAudioSpecificConfig(byte[] audioSpecificConfig)
throws ParserException {
return parseAacAudioSpecificConfig(new ParsableBitArray(audioSpecificConfig), false);
return parseAacAudioSpecificConfig(
new ParsableBitArray(audioSpecificConfig), /* forceReadToEnd= */ false);
}
/**
@ -102,23 +124,25 @@ public final class CodecSpecificDataUtil {
* position is advanced to the end of the AudioSpecificConfig.
* @param forceReadToEnd Whether the entire AudioSpecificConfig should be read. Required for
* knowing the length of the configuration payload.
* @return A pair consisting of the sample rate in Hz and the channel count.
* @return The parsed configuration.
* @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
*/
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(
public static AacConfig parseAacAudioSpecificConfig(
ParsableBitArray bitArray, boolean forceReadToEnd) throws ParserException {
int audioObjectType = getAacAudioObjectType(bitArray);
int sampleRate = getAacSamplingFrequency(bitArray);
int sampleRateHz = getAacSamplingFrequency(bitArray);
int channelConfiguration = bitArray.readBits(4);
if (audioObjectType == AUDIO_OBJECT_TYPE_SBR || audioObjectType == AUDIO_OBJECT_TYPE_PS) {
String codecs = AAC_CODECS_STRING_PREFIX + audioObjectType;
if (audioObjectType == AUDIO_OBJECT_TYPE_AAC_SBR
|| audioObjectType == AUDIO_OBJECT_TYPE_AAC_PS) {
// For an AAC bitstream using spectral band replication (SBR) or parametric stereo (PS) with
// explicit signaling, we return the extension sampling frequency as the sample rate of the
// content; this is identical to the sample rate of the decoded output but may differ from
// the sample rate set above.
// Use the extensionSamplingFrequencyIndex.
sampleRate = getAacSamplingFrequency(bitArray);
sampleRateHz = getAacSamplingFrequency(bitArray);
audioObjectType = getAacAudioObjectType(bitArray);
if (audioObjectType == AUDIO_OBJECT_TYPE_ER_BSAC) {
if (audioObjectType == AUDIO_OBJECT_TYPE_AAC_ER_BSAC) {
// Use the extensionChannelConfiguration.
channelConfiguration = bitArray.readBits(4);
}
@ -160,7 +184,7 @@ public final class CodecSpecificDataUtil {
// 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);
return new AacConfig(sampleRateHz, channelCount, codecs);
}
/**

View file

@ -15,12 +15,12 @@
*/
package com.google.android.exoplayer2.extractor.flv;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil.AacConfig;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.Collections;
@ -109,11 +109,16 @@ import java.util.Collections;
// Parse the sequence header.
byte[] audioSpecificConfig = new byte[data.bytesLeft()];
data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length);
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecificConfig);
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null,
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig), null, 0, null);
AacConfig aacConfig =
CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig);
Format format =
new Format.Builder()
.setSampleMimeType(MimeTypes.AUDIO_AAC)
.setCodecs(aacConfig.codecs)
.setChannelCount(aacConfig.channelCount)
.setSampleRate(aacConfig.sampleRateHz)
.setInitializationData(Collections.singletonList(audioSpecificConfig))
.build();
output.format(format);
hasOutputFormat = true;
return false;

View file

@ -29,6 +29,7 @@ import com.google.android.exoplayer2.extractor.GaplessInfoHolder;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil.AacConfig;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -1086,6 +1087,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
int channelCount;
int sampleRate;
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
@Nullable String codecs = null;
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
channelCount = parent.readUnsignedShort();
@ -1182,10 +1184,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
if (MimeTypes.AUDIO_AAC.equals(mimeType) && initializationData != null) {
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data,
// which is more reliable. See [Internal: b/10903778].
Pair<Integer, Integer> audioSpecificConfig =
AacConfig aacConfig =
CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData);
sampleRate = audioSpecificConfig.first;
channelCount = audioSpecificConfig.second;
sampleRate = aacConfig.sampleRateHz;
channelCount = aacConfig.channelCount;
codecs = aacConfig.codecs;
}
}
} else if (childAtomType == Atom.TYPE_dac3) {
@ -1237,10 +1240,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
if (out.format == null && mimeType != null) {
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
initializationData == null ? null : Collections.singletonList(initializationData),
drmInitData, 0, language);
out.format =
new Format.Builder()
.setId(Integer.toString(trackId))
.setSampleMimeType(mimeType)
.setCodecs(codecs)
.setChannelCount(channelCount)
.setSampleRate(sampleRate)
.setPcmEncoding(pcmEncoding)
.setInitializationData(
initializationData == null ? null : Collections.singletonList(initializationData))
.setDrmInitData(drmInitData)
.setLanguage(language)
.build();
}
}

View file

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@ -26,6 +25,7 @@ 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.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil.AacConfig;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
@ -469,12 +469,17 @@ public final class AdtsReader implements ElementaryStreamReader {
byte[] audioSpecificConfig =
CodecSpecificDataUtil.buildAacAudioSpecificConfig(
audioObjectType, firstFrameSampleRateIndex, channelConfig);
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
audioSpecificConfig);
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
Collections.singletonList(audioSpecificConfig), null, 0, language);
AacConfig aacConfig = CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig);
Format format =
new Format.Builder()
.setId(formatId)
.setSampleMimeType(MimeTypes.AUDIO_AAC)
.setCodecs(aacConfig.codecs)
.setChannelCount(aacConfig.channelCount)
.setSampleRate(aacConfig.sampleRateHz)
.setInitializationData(Collections.singletonList(audioSpecificConfig))
.setLanguage(language)
.build();
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
// number of PCM audio samples per second.
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;

View file

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import android.util.Pair;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
@ -25,6 +24,7 @@ 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.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil.AacConfig;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -273,9 +273,10 @@ public final class LatmReader implements ElementaryStreamReader {
private int parseAudioSpecificConfig(ParsableBitArray data) throws ParserException {
int bitsLeft = data.bitsLeft();
Pair<Integer, Integer> config = CodecSpecificDataUtil.parseAacAudioSpecificConfig(data, true);
sampleRateHz = config.first;
channelCount = config.second;
AacConfig config =
CodecSpecificDataUtil.parseAacAudioSpecificConfig(data, /* forceReadToEnd= */ true);
sampleRateHz = config.sampleRateHz;
channelCount = config.channelCount;
return bitsLeft - data.bitsLeft();
}