mirror of
https://github.com/samsonjs/media.git
synced 2026-03-30 10:15:48 +00:00
Construct codecs string for AAC in MP4/TS/FLV
PiperOrigin-RevId: 297578984
This commit is contained in:
parent
c6a6e0d6f3
commit
bd8ee155af
5 changed files with 87 additions and 40 deletions
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue