diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java b/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java index 55907e4137..1695b4bfc2 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/CodecSpecificDataUtil.java @@ -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 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 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); } /** diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java index 4a904844ee..4801c121cf 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/flv/AudioTagPayloadReader.java @@ -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 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; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index ccbe42d4b5..67af623082 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -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 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(); } } diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java index 7be3ddc5f8..37b0dc83ed 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsReader.java @@ -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 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; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java index 4cb1dc505a..4e692dd975 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/LatmReader.java @@ -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 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(); }