mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/** Provides utilities for handling various types of codec-specific data. */
|
||||||
* Provides static utility methods for manipulating various types of codec specific data.
|
|
||||||
*/
|
|
||||||
public final class CodecSpecificDataUtil {
|
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 byte[] NAL_START_CODE = new byte[] {0, 0, 0, 1};
|
||||||
|
|
||||||
private static final int AUDIO_SPECIFIC_CONFIG_FREQUENCY_INDEX_ARBITRARY = 0xF;
|
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
|
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.
|
// Advanced Audio Coding Low-Complexity profile.
|
||||||
private static final int AUDIO_OBJECT_TYPE_AAC_LC = 2;
|
private static final int AUDIO_OBJECT_TYPE_AAC_LC = 2;
|
||||||
// Spectral Band Replication.
|
// 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.
|
// 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.
|
// 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.
|
// Escape code for extended audio object types.
|
||||||
private static final int AUDIO_OBJECT_TYPE_ESCAPE = 31;
|
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
|
* Parses an AAC AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
|
||||||
*
|
*
|
||||||
* @param audioSpecificConfig A byte array containing 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 The parsed configuration.
|
||||||
* @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
|
* @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 {
|
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.
|
* position is advanced to the end of the AudioSpecificConfig.
|
||||||
* @param forceReadToEnd Whether the entire AudioSpecificConfig should be read. Required for
|
* @param forceReadToEnd Whether the entire AudioSpecificConfig should be read. Required for
|
||||||
* knowing the length of the configuration payload.
|
* 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.
|
* @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 {
|
ParsableBitArray bitArray, boolean forceReadToEnd) throws ParserException {
|
||||||
int audioObjectType = getAacAudioObjectType(bitArray);
|
int audioObjectType = getAacAudioObjectType(bitArray);
|
||||||
int sampleRate = getAacSamplingFrequency(bitArray);
|
int sampleRateHz = getAacSamplingFrequency(bitArray);
|
||||||
int channelConfiguration = bitArray.readBits(4);
|
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
|
// 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
|
// 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
|
// content; this is identical to the sample rate of the decoded output but may differ from
|
||||||
// the sample rate set above.
|
// the sample rate set above.
|
||||||
// Use the extensionSamplingFrequencyIndex.
|
// Use the extensionSamplingFrequencyIndex.
|
||||||
sampleRate = getAacSamplingFrequency(bitArray);
|
sampleRateHz = getAacSamplingFrequency(bitArray);
|
||||||
audioObjectType = getAacAudioObjectType(bitArray);
|
audioObjectType = getAacAudioObjectType(bitArray);
|
||||||
if (audioObjectType == AUDIO_OBJECT_TYPE_ER_BSAC) {
|
if (audioObjectType == AUDIO_OBJECT_TYPE_AAC_ER_BSAC) {
|
||||||
// Use the extensionChannelConfiguration.
|
// Use the extensionChannelConfiguration.
|
||||||
channelConfiguration = bitArray.readBits(4);
|
channelConfiguration = bitArray.readBits(4);
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +184,7 @@ public final class CodecSpecificDataUtil {
|
||||||
// For supported containers, bits_to_decode() is always 0.
|
// 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 new AacConfig(sampleRateHz, channelCount, codecs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,12 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.flv;
|
package com.google.android.exoplayer2.extractor.flv;
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
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.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -109,11 +109,16 @@ import java.util.Collections;
|
||||||
// Parse the sequence header.
|
// Parse the sequence header.
|
||||||
byte[] audioSpecificConfig = new byte[data.bytesLeft()];
|
byte[] audioSpecificConfig = new byte[data.bytesLeft()];
|
||||||
data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length);
|
data.readBytes(audioSpecificConfig, 0, audioSpecificConfig.length);
|
||||||
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
AacConfig aacConfig =
|
||||||
audioSpecificConfig);
|
CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig);
|
||||||
Format format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_AAC, null,
|
Format format =
|
||||||
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
|
new Format.Builder()
|
||||||
Collections.singletonList(audioSpecificConfig), null, 0, null);
|
.setSampleMimeType(MimeTypes.AUDIO_AAC)
|
||||||
|
.setCodecs(aacConfig.codecs)
|
||||||
|
.setChannelCount(aacConfig.channelCount)
|
||||||
|
.setSampleRate(aacConfig.sampleRateHz)
|
||||||
|
.setInitializationData(Collections.singletonList(audioSpecificConfig))
|
||||||
|
.build();
|
||||||
output.format(format);
|
output.format(format);
|
||||||
hasOutputFormat = true;
|
hasOutputFormat = true;
|
||||||
return false;
|
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.metadata.Metadata;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
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.Log;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
@ -1086,6 +1087,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
int channelCount;
|
int channelCount;
|
||||||
int sampleRate;
|
int sampleRate;
|
||||||
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
|
@C.PcmEncoding int pcmEncoding = Format.NO_VALUE;
|
||||||
|
@Nullable String codecs = null;
|
||||||
|
|
||||||
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
|
if (quickTimeSoundDescriptionVersion == 0 || quickTimeSoundDescriptionVersion == 1) {
|
||||||
channelCount = parent.readUnsignedShort();
|
channelCount = parent.readUnsignedShort();
|
||||||
|
|
@ -1182,10 +1184,11 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
if (MimeTypes.AUDIO_AAC.equals(mimeType) && initializationData != null) {
|
if (MimeTypes.AUDIO_AAC.equals(mimeType) && initializationData != null) {
|
||||||
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data,
|
// Update sampleRate and channelCount from the AudioSpecificConfig initialization data,
|
||||||
// which is more reliable. See [Internal: b/10903778].
|
// which is more reliable. See [Internal: b/10903778].
|
||||||
Pair<Integer, Integer> audioSpecificConfig =
|
AacConfig aacConfig =
|
||||||
CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData);
|
CodecSpecificDataUtil.parseAacAudioSpecificConfig(initializationData);
|
||||||
sampleRate = audioSpecificConfig.first;
|
sampleRate = aacConfig.sampleRateHz;
|
||||||
channelCount = audioSpecificConfig.second;
|
channelCount = aacConfig.channelCount;
|
||||||
|
codecs = aacConfig.codecs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (childAtomType == Atom.TYPE_dac3) {
|
} else if (childAtomType == Atom.TYPE_dac3) {
|
||||||
|
|
@ -1237,10 +1240,19 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (out.format == null && mimeType != null) {
|
if (out.format == null && mimeType != null) {
|
||||||
out.format = Format.createAudioSampleFormat(Integer.toString(trackId), mimeType, null,
|
out.format =
|
||||||
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRate, pcmEncoding,
|
new Format.Builder()
|
||||||
initializationData == null ? null : Collections.singletonList(initializationData),
|
.setId(Integer.toString(trackId))
|
||||||
drmInitData, 0, language);
|
.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;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
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.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
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.Log;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
|
|
@ -469,12 +469,17 @@ public final class AdtsReader implements ElementaryStreamReader {
|
||||||
byte[] audioSpecificConfig =
|
byte[] audioSpecificConfig =
|
||||||
CodecSpecificDataUtil.buildAacAudioSpecificConfig(
|
CodecSpecificDataUtil.buildAacAudioSpecificConfig(
|
||||||
audioObjectType, firstFrameSampleRateIndex, channelConfig);
|
audioObjectType, firstFrameSampleRateIndex, channelConfig);
|
||||||
Pair<Integer, Integer> audioParams = CodecSpecificDataUtil.parseAacAudioSpecificConfig(
|
AacConfig aacConfig = CodecSpecificDataUtil.parseAacAudioSpecificConfig(audioSpecificConfig);
|
||||||
audioSpecificConfig);
|
Format format =
|
||||||
|
new Format.Builder()
|
||||||
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
|
.setId(formatId)
|
||||||
Format.NO_VALUE, Format.NO_VALUE, audioParams.second, audioParams.first,
|
.setSampleMimeType(MimeTypes.AUDIO_AAC)
|
||||||
Collections.singletonList(audioSpecificConfig), null, 0, language);
|
.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
|
// In this class a sample is an access unit, but the MediaFormat sample rate specifies the
|
||||||
// number of PCM audio samples per second.
|
// number of PCM audio samples per second.
|
||||||
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
|
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.extractor.ts;
|
package com.google.android.exoplayer2.extractor.ts;
|
||||||
|
|
||||||
import android.util.Pair;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
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.extractor.ts.TsPayloadReader.TrackIdGenerator;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
|
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.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.ParsableBitArray;
|
import com.google.android.exoplayer2.util.ParsableBitArray;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
|
|
@ -273,9 +273,10 @@ public final class LatmReader implements ElementaryStreamReader {
|
||||||
|
|
||||||
private int parseAudioSpecificConfig(ParsableBitArray data) throws ParserException {
|
private int parseAudioSpecificConfig(ParsableBitArray data) throws ParserException {
|
||||||
int bitsLeft = data.bitsLeft();
|
int bitsLeft = data.bitsLeft();
|
||||||
Pair<Integer, Integer> config = CodecSpecificDataUtil.parseAacAudioSpecificConfig(data, true);
|
AacConfig config =
|
||||||
sampleRateHz = config.first;
|
CodecSpecificDataUtil.parseAacAudioSpecificConfig(data, /* forceReadToEnd= */ true);
|
||||||
channelCount = config.second;
|
sampleRateHz = config.sampleRateHz;
|
||||||
|
channelCount = config.channelCount;
|
||||||
return bitsLeft - data.bitsLeft();
|
return bitsLeft - data.bitsLeft();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue