- remove 'NonNull' and add 'UnstableApi' annotation

- rename 'checkBitsAvailable()' to 'validateBitsAvailability()'
- rename 'ParseState' to 'State'
- rename State values to begin with 'STATE_'
- make use of 'Format.NO_VALUE' and 'C.INDEX_UNSET'
- adjust documentation
This commit is contained in:
rohks 2023-11-30 12:26:24 +01:00 committed by Rohit Singh
parent 3aff715824
commit 43cf32738d
2 changed files with 164 additions and 134 deletions

View file

@ -24,6 +24,8 @@ import static java.lang.annotation.ElementType.TYPE_USE;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.ParsableBitArray; import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
@ -33,13 +35,12 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.Arrays; import java.util.Arrays;
import org.checkerframework.checker.nullness.qual.NonNull;
/** Utility methods for parsing MPEG-H frames, which are access units in MPEG-H bitstreams. */ /** Utility methods for parsing MPEG-H frames, which are access units in MPEG-H bitstreams. */
@UnstableApi @UnstableApi
public final class MpeghUtil { public final class MpeghUtil {
/** See ISO_IEC_23003-3;2020, 6.1.1.1, Table 72 */ /** See ISO_IEC_23003-3;2020, 6.1.1.1, Table 72. */
private static final int[] SAMPLING_RATE_TABLE = private static final int[] SAMPLING_RATE_TABLE =
new int[]{ new int[]{
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350, 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350,
@ -47,24 +48,31 @@ public final class MpeghUtil {
9600, 0, 0, 0, 0 9600, 0, 0, 0, 0
}; };
/** See ISO_IEC_23003-3;2020, 6.1.1.1, Table 75 */ /** See ISO_IEC_23003-3;2020, 6.1.1.1, Table 75. */
private static final int[] OUTPUT_FRAMELENGTH_TABLE = private static final int[] OUTPUT_FRAMELENGTH_TABLE =
new int[]{ new int[]{
768, 1024, 2048, 2048, 4096, 0, 0, 0 768, 1024, 2048, 2048, 4096, 0, 0, 0
}; };
/** See ISO_IEC_23003-3;2020, 6.1.1.1, Table 75 */ /** See ISO_IEC_23003-3;2020, 6.1.1.1, Table 75. */
private static final int[] SBR_RATIO_INDEX_TABLE = private static final int[] SBR_RATIO_INDEX_TABLE =
new int[]{ new int[]{
0, 0, 2, 3, 1 0, 0, 2, 3, 1
}; };
/** See ISO_IEC_23003-8;2022, 14.4.4. */
private static final int MHAS_SYNCPACKET = 0xC001A5; private static final int MHAS_SYNCPACKET = 0xC001A5;
/** /**
* Enumeration of the MHAS packet types. * MHAS packet types. See ISO_IEC_23008-3;2022, 14.3.1, Table 226.
* See ISO_IEC_23008-3;2022, 14.3.1, Table 226 * One of {@link #PACTYP_FILLDATA}, {@link #PACTYP_MPEGH3DACFG}, {@link #PACTYP_MPEGH3DAFRAME},
* {@link #PACTYP_AUDIOSCENEINFO}, {@link #PACTYP_SYNC}, {@link #PACTYP_SYNCGAP},
* {@link #PACTYP_MARKER}, {@link #PACTYP_CRC16}, {@link #PACTYP_CRC32},
* {@link #PACTYP_DESCRIPTOR}, {@link #PACTYP_USERINTERACTION}, {@link #PACTYP_LOUDNESS_DRC},
* {@link #PACTYP_BUFFERINFO}, {@link #PACTYP_GLOBAL_CRC16}, {@link #PACTYP_GLOBAL_CRC32},
* {@link #PACTYP_AUDIOTRUNCATION}, {@link #PACTYP_GENDATA}, {@link #PACTYPE_EARCON},
* {@link #PACTYPE_PCMCONFIG}, {@link #PACTYPE_PCMDATA}, {@link #PACTYP_LOUDNESS}.
*/ */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -117,35 +125,48 @@ public final class MpeghUtil {
private static final int PACTYP_LOUDNESS = 22; private static final int PACTYP_LOUDNESS = 22;
/**
* Represents the parsing state. One of {@link #STATE_END_OUTPUT},
* {@link #STATE_PARSE_ERROR}, {@link #STATE_SUBSTREAM_UNSUPPORTED}.
*/
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE) @Target(TYPE_USE)
@IntDef({ @IntDef({
END_OUTPUT, STATE_END_OUTPUT,
PARSE_ERROR, STATE_PARSE_ERROR,
SUBSTREAM_UNSUPPORTED STATE_SUBSTREAM_UNSUPPORTED
}) })
private @interface ParseState {} private @interface State {}
private static final int END_OUTPUT = 0; private static final int STATE_END_OUTPUT = 0;
private static final int PARSE_ERROR = 1; private static final int STATE_PARSE_ERROR = 1;
private static final int SUBSTREAM_UNSUPPORTED = 2; private static final int STATE_SUBSTREAM_UNSUPPORTED = 2;
public static class FrameInfo { public static class FrameInfo {
public boolean containsConfig = false; public boolean containsConfig;
public boolean configChanged = false; public boolean configChanged;
public int standardFrameSamples = 0; public int standardFrameSamples;
public int samplingRate = 0; public int samplingRate;
public int frameSamples = 0; public int frameSamples;
public int frameBytes = 0; public int frameBytes;
public long mainStreamLabel = -1; public long mainStreamLabel;
public int mpegh3daProfileLevelIndication = -1; public int mpegh3daProfileLevelIndication;
public byte[] compatibleSetIndication = new byte[0]; public byte[] compatibleSetIndication;
public FrameInfo() { public FrameInfo() {
this.containsConfig = false;
this.configChanged = false;
this.standardFrameSamples = Format.NO_VALUE;
this.samplingRate = Format.NO_VALUE;
this.frameSamples = Format.NO_VALUE;
this.frameBytes = Format.NO_VALUE;
this.mainStreamLabel = Format.NO_VALUE;
this.mpegh3daProfileLevelIndication = Format.NO_VALUE;
this.compatibleSetIndication = new byte[0];
} }
public FrameInfo(boolean containsConfig, boolean configChanged, int standardFrameSamples, public FrameInfo(boolean containsConfig, boolean configChanged, int standardFrameSamples,
@ -168,24 +189,24 @@ public final class MpeghUtil {
public void reset() { public void reset() {
containsConfig = false; containsConfig = false;
configChanged = false; configChanged = false;
standardFrameSamples = 0; standardFrameSamples = Format.NO_VALUE;
samplingRate = 0; samplingRate = Format.NO_VALUE;
frameBytes = 0; frameBytes = Format.NO_VALUE;
frameSamples = 0; frameSamples = Format.NO_VALUE;
mainStreamLabel = -1; mainStreamLabel = Format.NO_VALUE;
mpegh3daProfileLevelIndication = -1; mpegh3daProfileLevelIndication = Format.NO_VALUE;
compatibleSetIndication = new byte[0]; compatibleSetIndication = new byte[0];
} }
} }
/** /**
* This function is used to check if the provided number of bits is available in the bit array. * Validates that the provided number of bits is available in the bit array.
* *
* @param data The byte array to parse. * @param data The byte array to parse.
* @param numBits The number of bits to check for. * @param numBits The number of bits to check for.
* @throws ParseException If not enough bits are available. * @throws {@link ParseException} if not enough bits are available.
*/ */
public static void checkBitsAvailable(ParsableBitArray data, int numBits) public static void validateBitsAvailability(ParsableBitArray data, int numBits)
throws ParseException { throws ParseException {
if (data.bitsLeft() < numBits) { if (data.bitsLeft() < numBits) {
throw ParseException.createForNotEnoughData(); throw ParseException.createForNotEnoughData();
@ -193,15 +214,15 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to find the start position of the MHAS sync packet in the provided data * Finds the start position of the MHAS sync packet in the provided data buffer.
* buffer. See ISO_IEC_23008-3;2022, 14.4.4 * See ISO_IEC_23008-3;2022, 14.4.4.
* *
* @param data The byte array to parse. * @param data The byte array to parse.
* @return byte position in data of the MHAS sync packet on success, negative value on failure. * @return Byte index in data of the MHAS sync packet on success, {@link C#INDEX_UNSET} on failure.
*/ */
public static int findSyncPacket(ParsableByteArray data) { public static int findSyncPacket(ParsableByteArray data) {
int startPos = data.getPosition(); int startPos = data.getPosition();
int syncPacketBytePos = -1; int syncPacketBytePos = C.INDEX_UNSET;
while (data.bytesLeft() >= 3) { while (data.bytesLeft() >= 3) {
int syncword = data.readUnsignedInt24(); int syncword = data.readUnsignedInt24();
if (syncword == MHAS_SYNCPACKET) { if (syncword == MHAS_SYNCPACKET) {
@ -216,8 +237,8 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to check if a complete MHAS frame could be parsed by calculating if * Checks if a complete MHAS frame could be parsed by calculating if enough data is available
* enough data is available in the provided ParsableBitArray. * in the provided ParsableBitArray.
* *
* @param data The bit array to parse. * @param data The bit array to parse.
* @return Whether a complete MHAS frame could be parsed. * @return Whether a complete MHAS frame could be parsed.
@ -262,12 +283,12 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to parse an MHAS packet header. * Parses an MHAS packet header.
* See ISO_IEC_23008-3;2022, 14.2.1, Table 222 * See ISO_IEC_23008-3;2022, 14.2.1, Table 222.
* *
* @param data The bit array to parse. * @param data The bit array to parse.
* @return MHASPacketHeader The MHAS packet header info. * @return MHASPacketHeader The MHAS packet header info.
* @throws ParseException If parsing failed, i.e. there is not enough data available. * @throws {@link ParseException} if parsing failed, i.e. there is not enough data available.
*/ */
private static MHASPacketHeader parseMhasPacketHeader(ParsableBitArray data) throws ParseException { private static MHASPacketHeader parseMhasPacketHeader(ParsableBitArray data) throws ParseException {
@MHASPacketType int packetType = (int) readEscapedValue(data, 3, 8, 8); @MHASPacketType int packetType = (int) readEscapedValue(data, 3, 8, 8);
@ -277,14 +298,14 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to parse one MPEG-H frame into the FrameInfo structure. * Parses the necessary info of an MPEG-H frame into the FrameInfo structure.
* *
* @param data The bit array to parse, positioned at the start of the MHAS frame. * @param data The bit array to parse, positioned at the start of the MHAS frame.
* @param prevFrameInfo A previously obtained FrameInfo. * @param prevFrameInfo A previously obtained FrameInfo.
* @return FrameInfo of the current frame. * @return FrameInfo of the current frame.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
public static FrameInfo parseFrame(ParsableBitArray data, @NonNull FrameInfo prevFrameInfo) public static FrameInfo parseFrame(ParsableBitArray data, FrameInfo prevFrameInfo)
throws ParseException { throws ParseException {
int nBitsIns; int nBitsIns;
int standardFrameSamples = prevFrameInfo.standardFrameSamples; int standardFrameSamples = prevFrameInfo.standardFrameSamples;
@ -293,8 +314,8 @@ public final class MpeghUtil {
boolean configFound = false; boolean configFound = false;
boolean configChanged = false; boolean configChanged = false;
int truncationSamples = 0; int truncationSamples = 0;
long mainStreamLabel = -1; long mainStreamLabel = Format.NO_VALUE;
int mpegh3daProfileLevelIndication = -1; int mpegh3daProfileLevelIndication = Format.NO_VALUE;
byte[] compatibleSetIndication = new byte[0]; byte[] compatibleSetIndication = new byte[0];
nBitsIns = data.bitsLeft(); nBitsIns = data.bitsLeft();
@ -315,7 +336,7 @@ public final class MpeghUtil {
} }
// check if the complete packet could be parsed // check if the complete packet could be parsed
checkBitsAvailable(data, packetHeader.packetLength * 8); validateBitsAvailability(data, packetHeader.packetLength * 8);
int dataPos = data.getPosition(); int dataPos = data.getPosition();
switch (packetHeader.packetType) { switch (packetHeader.packetType) {
@ -416,21 +437,21 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to obtain the sampling rate of the current MPEG-H frame. * Obtains the sampling rate of the current MPEG-H frame.
* *
* @param data The bit array holding the bits to be parsed. * @param data The bit array holding the bits to be parsed.
* @return The sampling frequency. * @return The sampling frequency.
* @throws ParseException If sampling frequency could not be obtained. * @throws {@link ParseException} if sampling frequency could not be obtained.
*/ */
public static int getSamplingFrequency(ParsableBitArray data) throws ParseException { public static int getSamplingFrequency(ParsableBitArray data) throws ParseException {
int sampleRate; int sampleRate;
int idx; int idx;
checkBitsAvailable(data, 5); validateBitsAvailability(data, 5);
idx = data.readBits(5); idx = data.readBits(5);
if (idx == 0x1F) { if (idx == 0x1F) {
checkBitsAvailable(data, 24); validateBitsAvailability(data, 24);
sampleRate = data.readBits(24); sampleRate = data.readBits(24);
} else { } else {
sampleRate = SAMPLING_RATE_TABLE[idx]; sampleRate = SAMPLING_RATE_TABLE[idx];
@ -439,31 +460,31 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to obtain an escaped value from an MPEG-H bit stream. * Obtains an escaped value from an MPEG-H bit stream.
* See ISO_IEC_23003-3;2020, 5.2, Table 19 * See ISO_IEC_23003-3;2020, 5.2, Table 19.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @param bits1 number of bits to be parsed. * @param bits1 number of bits to be parsed.
* @param bits2 number of bits to be parsed. * @param bits2 number of bits to be parsed.
* @param bits3 number of bits to be parsed. * @param bits3 number of bits to be parsed.
* @return The escaped value. * @return The escaped value.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
public static long readEscapedValue(ParsableBitArray data, int bits1, int bits2, int bits3) public static long readEscapedValue(ParsableBitArray data, int bits1, int bits2, int bits3)
throws ParseException { throws ParseException {
long value; long value;
long valueAdd; long valueAdd;
checkBitsAvailable(data, bits1); validateBitsAvailability(data, bits1);
value = data.readBitsToLong(bits1); value = data.readBitsToLong(bits1);
if (value == (1L << bits1) - 1) { if (value == (1L << bits1) - 1) {
checkBitsAvailable(data, bits2); validateBitsAvailability(data, bits2);
valueAdd = data.readBitsToLong(bits2); valueAdd = data.readBitsToLong(bits2);
value += valueAdd; value += valueAdd;
if (valueAdd == (1L << bits2) - 1) { if (valueAdd == (1L << bits2) - 1) {
checkBitsAvailable(data, bits3); validateBitsAvailability(data, bits3);
valueAdd = data.readBitsToLong(bits3); valueAdd = data.readBitsToLong(bits3);
value += valueAdd; value += valueAdd;
} }
@ -473,29 +494,37 @@ public final class MpeghUtil {
private static class Mpegh3daConfig { private static class Mpegh3daConfig {
int mpegh3daProfileLevelIndication = 0; int mpegh3daProfileLevelIndication;
int samplingFrequency = 0; int samplingFrequency;
int standardFrameSamples = 0; int standardFrameSamples;
@Nullable @Nullable
byte[] compatibleProfileLevelSet = null; byte[] compatibleProfileLevelSet;
private Mpegh3daConfig() {
this.mpegh3daProfileLevelIndication = Format.NO_VALUE;
this.samplingFrequency = Format.NO_VALUE;
this.standardFrameSamples = Format.NO_VALUE;
this.compatibleProfileLevelSet = null;
}
} }
/** /**
* This function is used to obtain the necessary info of the Mpegh3daConfig from an MPEG-H bit * Obtains the necessary info of the Mpegh3daConfig from an MPEG-H bit stream.
* stream. See ISO_IEC_23008-3;2022, 5.2.2.1, Table 15 * See ISO_IEC_23008-3;2022, 5.2.2.1, Table 15.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @return The Mpegh3daConfig. * @return The Mpegh3daConfig.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
private static Mpegh3daConfig parseMpegh3daConfig(ParsableBitArray data) throws ParseException { private static Mpegh3daConfig parseMpegh3daConfig(ParsableBitArray data) throws ParseException {
Mpegh3daConfig mpegh3daConfig = new Mpegh3daConfig(); Mpegh3daConfig mpegh3daConfig = new Mpegh3daConfig();
checkBitsAvailable(data, 8); validateBitsAvailability(data, 8);
mpegh3daConfig.mpegh3daProfileLevelIndication = data.readBits(8); mpegh3daConfig.mpegh3daProfileLevelIndication = data.readBits(8);
int usacSamplingFrequency = getSamplingFrequency(data); int usacSamplingFrequency = getSamplingFrequency(data);
checkBitsAvailable(data, 5); validateBitsAvailability(data, 5);
int coreSbrFrameLengthIndex = data.readBits(3); int coreSbrFrameLengthIndex = data.readBits(3);
data.skipBits(2); // cfg_reserved(1), receiverDelayCompensation(1) data.skipBits(2); // cfg_reserved(1), receiverDelayCompensation(1)
@ -506,7 +535,7 @@ public final class MpeghUtil {
int numSignals = parseSignals3d(data); // frameworkConfig3d int numSignals = parseSignals3d(data); // frameworkConfig3d
parseMpegh3daDecoderConfig(data, numSignals, sbrRatioIndex); // decoderConfig parseMpegh3daDecoderConfig(data, numSignals, sbrRatioIndex); // decoderConfig
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
if (data.readBit()) { // usacConfigExtensionPresent if (data.readBit()) { // usacConfigExtensionPresent
// Mpegh3daConfigExtension // Mpegh3daConfigExtension
int numConfigExtensions = (int) readEscapedValue(data, 2, 4, 8) + 1; int numConfigExtensions = (int) readEscapedValue(data, 2, 4, 8) + 1;
@ -515,16 +544,16 @@ public final class MpeghUtil {
int usacConfigExtLength = (int) readEscapedValue(data, 4, 8, 16); int usacConfigExtLength = (int) readEscapedValue(data, 4, 8, 16);
if (usacConfigExtType == 7 /*ID_CONFIG_EXT_COMPATIBLE_PROFILELVL_SET*/) { if (usacConfigExtType == 7 /*ID_CONFIG_EXT_COMPATIBLE_PROFILELVL_SET*/) {
checkBitsAvailable(data, 8); validateBitsAvailability(data, 8);
int numCompatibleSets = data.readBits(4) + 1; int numCompatibleSets = data.readBits(4) + 1;
data.skipBits(4); // reserved data.skipBits(4); // reserved
mpegh3daConfig.compatibleProfileLevelSet = new byte[numCompatibleSets]; mpegh3daConfig.compatibleProfileLevelSet = new byte[numCompatibleSets];
for (int idx = 0; idx < numCompatibleSets; idx++) { for (int idx = 0; idx < numCompatibleSets; idx++) {
checkBitsAvailable(data, 8); validateBitsAvailability(data, 8);
mpegh3daConfig.compatibleProfileLevelSet[idx] = (byte) data.readBits(8); mpegh3daConfig.compatibleProfileLevelSet[idx] = (byte) data.readBits(8);
} }
} else { } else {
checkBitsAvailable(data, 8 * usacConfigExtLength); validateBitsAvailability(data, 8 * usacConfigExtLength);
data.skipBits(8 * usacConfigExtLength); data.skipBits(8 * usacConfigExtLength);
} }
} }
@ -566,16 +595,16 @@ public final class MpeghUtil {
/** /**
* This function is used to obtain the number of truncated samples of the AudioTruncationInfo from * Obtains the number of truncated samples of the AudioTruncationInfo from an MPEG-H bit stream.
* an MPEG-H bit stream. See ISO_IEC_23008-3;2022, 14.2.2, Table 225 * See ISO_IEC_23008-3;2022, 14.2.2, Table 225.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @return The number of truncated samples. * @return The number of truncated samples.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
private static int parseAudioTruncationInfo(ParsableBitArray data) throws ParseException { private static int parseAudioTruncationInfo(ParsableBitArray data) throws ParseException {
int truncationSamples = 0; int truncationSamples = 0;
checkBitsAvailable(data, 16); validateBitsAvailability(data, 16);
boolean isActive = data.readBit(); boolean isActive = data.readBit();
data.skipBits(2); // reserved(1), truncFromBegin(1) data.skipBits(2); // reserved(1), truncFromBegin(1)
int trunc = data.readBits(13); int trunc = data.readBits(13);
@ -587,26 +616,26 @@ public final class MpeghUtil {
/** /**
* This function is used to parse the SpeakerConfig3d from an MPEG-H bit stream. * Parses the SpeakerConfig3d from an MPEG-H bit stream.
* See ISO_IEC_23008-3;2022, 5.2.2.2, Table 18 * See ISO_IEC_23008-3;2022, 5.2.2.2, Table 18.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
private static void parseSpeakerConfig3d(ParsableBitArray data) throws ParseException { private static void parseSpeakerConfig3d(ParsableBitArray data) throws ParseException {
checkBitsAvailable(data, 2); validateBitsAvailability(data, 2);
int speakerLayoutType = data.readBits(2); int speakerLayoutType = data.readBits(2);
if (speakerLayoutType == 0) { if (speakerLayoutType == 0) {
checkBitsAvailable(data, 6); validateBitsAvailability(data, 6);
data.skipBits(6); // cicpSpeakerLayoutIdx data.skipBits(6); // cicpSpeakerLayoutIdx
} else { } else {
int numSpeakers = (int) readEscapedValue(data, 5, 8, 16) + 1; int numSpeakers = (int) readEscapedValue(data, 5, 8, 16) + 1;
if (speakerLayoutType == 1) { if (speakerLayoutType == 1) {
checkBitsAvailable(data, 7 * numSpeakers); validateBitsAvailability(data, 7 * numSpeakers);
data.skipBits(7 * numSpeakers); // cicpSpeakerIdx per speaker data.skipBits(7 * numSpeakers); // cicpSpeakerIdx per speaker
} }
if (speakerLayoutType == 2) { if (speakerLayoutType == 2) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
boolean angularPrecision = data.readBit(); boolean angularPrecision = data.readBit();
int angularPrecisionDegrees = angularPrecision ? 1 : 5; int angularPrecisionDegrees = angularPrecision ? 1 : 5;
int elevationAngleBits = angularPrecision ? 7 : 5; int elevationAngleBits = angularPrecision ? 7 : 5;
@ -615,35 +644,35 @@ public final class MpeghUtil {
// Mpegh3daSpeakerDescription array // Mpegh3daSpeakerDescription array
for (int i = 0; i < numSpeakers; i++) { for (int i = 0; i < numSpeakers; i++) {
int azimuthAngle = 0; int azimuthAngle = 0;
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
if (data.readBit()) { // isCICPspeakerIdx if (data.readBit()) { // isCICPspeakerIdx
checkBitsAvailable(data, 7); validateBitsAvailability(data, 7);
data.skipBits(7); // cicpSpeakerIdx data.skipBits(7); // cicpSpeakerIdx
} else { } else {
checkBitsAvailable(data, 2); validateBitsAvailability(data, 2);
int elevationClass = data.readBits(2); int elevationClass = data.readBits(2);
if (elevationClass == 3) { if (elevationClass == 3) {
checkBitsAvailable(data, elevationAngleBits); validateBitsAvailability(data, elevationAngleBits);
int elevationAngleIdx = data.readBits(elevationAngleBits); int elevationAngleIdx = data.readBits(elevationAngleBits);
int elevationAngle = elevationAngleIdx * angularPrecisionDegrees; int elevationAngle = elevationAngleIdx * angularPrecisionDegrees;
if (elevationAngle != 0) { if (elevationAngle != 0) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // elevationDirection data.skipBit(); // elevationDirection
} }
} }
checkBitsAvailable(data, azimuthAngleBits); validateBitsAvailability(data, azimuthAngleBits);
int azimuthAngleIdx = data.readBits(azimuthAngleBits); int azimuthAngleIdx = data.readBits(azimuthAngleBits);
azimuthAngle = azimuthAngleIdx * angularPrecisionDegrees; azimuthAngle = azimuthAngleIdx * angularPrecisionDegrees;
if ((azimuthAngle != 0) && (azimuthAngle != 180)) { if ((azimuthAngle != 0) && (azimuthAngle != 180)) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // azimuthDirection data.skipBit(); // azimuthDirection
} }
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // isLFE data.skipBit(); // isLFE
} }
if ((azimuthAngle != 0) && (azimuthAngle != 180)) { if ((azimuthAngle != 0) && (azimuthAngle != 180)) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
if (data.readBit()) { // alsoAddSymmetricPair if (data.readBit()) { // alsoAddSymmetricPair
i++; i++;
} }
@ -654,28 +683,28 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to obtain the necessary info of Signals3d from an MPEG-H bit stream. * Obtains the necessary info of Signals3d from an MPEG-H bit stream.
* See ISO_IEC_23008-3;2022, 5.2.2.1, Table 17 * See ISO_IEC_23008-3;2022, 5.2.2.1, Table 17.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @return The number of overall signals in the bit stream. * @return The number of overall signals in the bit stream.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
private static int parseSignals3d(ParsableBitArray data) private static int parseSignals3d(ParsableBitArray data)
throws ParseException { throws ParseException {
int numSignals = 0; int numSignals = 0;
checkBitsAvailable(data, 5); validateBitsAvailability(data, 5);
int bsNumSignalGroups = data.readBits(5); int bsNumSignalGroups = data.readBits(5);
for (int grp = 0; grp < bsNumSignalGroups + 1; grp++) { for (int grp = 0; grp < bsNumSignalGroups + 1; grp++) {
checkBitsAvailable(data, 3); validateBitsAvailability(data, 3);
int signalGroupType = data.readBits(3); int signalGroupType = data.readBits(3);
int bsNumberOfSignals = (int) readEscapedValue(data, 5, 8, 16); int bsNumberOfSignals = (int) readEscapedValue(data, 5, 8, 16);
numSignals += bsNumberOfSignals + 1; numSignals += bsNumberOfSignals + 1;
if (signalGroupType == 0 /*SignalGroupTypeChannels*/ || if (signalGroupType == 0 /*SignalGroupTypeChannels*/ ||
signalGroupType == 2 /*SignalGroupTypeSAOC*/) { signalGroupType == 2 /*SignalGroupTypeSAOC*/) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
if (data.readBit()) { // differsFromReferenceLayout OR saocDmxLayoutPresent if (data.readBit()) { // differsFromReferenceLayout OR saocDmxLayoutPresent
parseSpeakerConfig3d(data); // audioChannelLayout[grp] OR saocDmxChannelLayout parseSpeakerConfig3d(data); // audioChannelLayout[grp] OR saocDmxChannelLayout
} }
@ -685,24 +714,24 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to parse the Mpegh3daDecoderConfig from an MPEG-H bit stream. * Parses the Mpegh3daDecoderConfig from an MPEG-H bit stream.
* See ISO_IEC_23008-3;2022, 5.2.2.3, Table 21 * See ISO_IEC_23008-3;2022, 5.2.2.3, Table 21.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @param numSignals The number of overall signals. * @param numSignals The number of overall signals.
* @param sbrRatioIndex The SBR ration index. * @param sbrRatioIndex The SBR ration index.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
private static void parseMpegh3daDecoderConfig(ParsableBitArray data, private static void parseMpegh3daDecoderConfig(ParsableBitArray data,
int numSignals, int sbrRatioIndex) int numSignals, int sbrRatioIndex)
throws ParseException { throws ParseException {
int numElements = (int) readEscapedValue(data, 4, 8, 16) + 1; int numElements = (int) readEscapedValue(data, 4, 8, 16) + 1;
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // elementLengthPresent data.skipBit(); // elementLengthPresent
for (int elemIdx = 0; elemIdx < numElements; elemIdx++) { for (int elemIdx = 0; elemIdx < numElements; elemIdx++) {
checkBitsAvailable(data, 2); validateBitsAvailability(data, 2);
int usacElementType = data.readBits(2); int usacElementType = data.readBits(2);
switch (usacElementType) { switch (usacElementType) {
@ -715,53 +744,53 @@ public final class MpeghUtil {
case 1 /*ID_USAC_CPE*/: case 1 /*ID_USAC_CPE*/:
boolean enhancedNoiseFilling = parseMpegh3daCoreConfig(data); // coreConfig boolean enhancedNoiseFilling = parseMpegh3daCoreConfig(data); // coreConfig
if (enhancedNoiseFilling) { if (enhancedNoiseFilling) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // igfIndependentTiling data.skipBit(); // igfIndependentTiling
} }
int stereoConfigIndex = 0; int stereoConfigIndex = 0;
if (sbrRatioIndex > 0) { if (sbrRatioIndex > 0) {
parseSbrConfig(data); // sbrConfig parseSbrConfig(data); // sbrConfig
checkBitsAvailable(data, 2); validateBitsAvailability(data, 2);
stereoConfigIndex = data.readBits(2); stereoConfigIndex = data.readBits(2);
} }
if (stereoConfigIndex > 0) { if (stereoConfigIndex > 0) {
// mps212Config // mps212Config
checkBitsAvailable(data, 13); validateBitsAvailability(data, 13);
data.skipBits(6); // bsFreqRes(3), bsFixedGainDMX(3), data.skipBits(6); // bsFreqRes(3), bsFixedGainDMX(3),
int bsTempShapeConfig = data.readBits(2); int bsTempShapeConfig = data.readBits(2);
data.skipBits(4);// bsDecorrConfig(2), bsHighRateMode(1), bsPhaseCoding(1) data.skipBits(4);// bsDecorrConfig(2), bsHighRateMode(1), bsPhaseCoding(1)
if (data.readBit()) { // bsOttBandsPhasePresent if (data.readBit()) { // bsOttBandsPhasePresent
checkBitsAvailable(data, 5); validateBitsAvailability(data, 5);
data.skipBits(5); // bsOttBandsPhase data.skipBits(5); // bsOttBandsPhase
} }
if (stereoConfigIndex == 2 || stereoConfigIndex == 3) { if (stereoConfigIndex == 2 || stereoConfigIndex == 3) {
checkBitsAvailable(data, 6); validateBitsAvailability(data, 6);
data.skipBits(6); // bsResidualBands(5), bsPseudoLr(1) data.skipBits(6); // bsResidualBands(5), bsPseudoLr(1)
} }
if (bsTempShapeConfig == 2) { if (bsTempShapeConfig == 2) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // bsEnvQuantMode data.skipBit(); // bsEnvQuantMode
} }
} }
int nBits = (int) Math.floor(Math.log(numSignals - 1) / Math.log(2.0)) + 1; int nBits = (int) Math.floor(Math.log(numSignals - 1) / Math.log(2.0)) + 1;
checkBitsAvailable(data, 2); validateBitsAvailability(data, 2);
int qceIndex = data.readBits(2); int qceIndex = data.readBits(2);
if (qceIndex > 0) { if (qceIndex > 0) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
if (data.readBit()) { // shiftIndex0 if (data.readBit()) { // shiftIndex0
checkBitsAvailable(data, nBits); validateBitsAvailability(data, nBits);
data.skipBits(nBits); // shiftChannel0 data.skipBits(nBits); // shiftChannel0
} }
} }
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
if (data.readBit()) { // shiftIndex1 if (data.readBit()) { // shiftIndex1
checkBitsAvailable(data, nBits); validateBitsAvailability(data, nBits);
data.skipBits(nBits); // shiftChannel1 data.skipBits(nBits); // shiftChannel1
} }
if (sbrRatioIndex == 0 && qceIndex == 0) { if (sbrRatioIndex == 0 && qceIndex == 0) {
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // lpdStereoIndex data.skipBit(); // lpdStereoIndex
} }
break; break;
@ -769,15 +798,15 @@ public final class MpeghUtil {
readEscapedValue(data, 4, 8, 16); // usacExtElementType readEscapedValue(data, 4, 8, 16); // usacExtElementType
int usacExtElementConfigLength = (int) readEscapedValue(data, 4, 8, 16); int usacExtElementConfigLength = (int) readEscapedValue(data, 4, 8, 16);
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
if (data.readBit()) { // usacExtElementDefaultLengthPresent if (data.readBit()) { // usacExtElementDefaultLengthPresent
readEscapedValue(data, 8, 16, 0)/*+1*/; // usacExtElementDefaultLength readEscapedValue(data, 8, 16, 0)/*+1*/; // usacExtElementDefaultLength
} }
checkBitsAvailable(data, 1); validateBitsAvailability(data, 1);
data.skipBit(); // usacExtElementPayloadFrag data.skipBit(); // usacExtElementPayloadFrag
if (usacExtElementConfigLength > 0) { if (usacExtElementConfigLength > 0) {
checkBitsAvailable(data, 8 * usacExtElementConfigLength); validateBitsAvailability(data, 8 * usacExtElementConfigLength);
data.skipBits(8 * usacExtElementConfigLength); data.skipBits(8 * usacExtElementConfigLength);
} }
break; break;
@ -788,46 +817,46 @@ public final class MpeghUtil {
} }
/** /**
* This function is used to obtain the necessary info of the Mpegh3daCoreConfig from an MPEG-H * Obtains the necessary info of the Mpegh3daCoreConfig from an MPEG-H bit stream.
* bit stream. See ISO_IEC_23008-3;2022, 5.2.2.3, Table 24 * See ISO_IEC_23008-3;2022, 5.2.2.3, Table 24.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @return The enhanced noise filling flag. * @return The enhanced noise filling flag.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
private static boolean parseMpegh3daCoreConfig(ParsableBitArray data) private static boolean parseMpegh3daCoreConfig(ParsableBitArray data)
throws ParseException { throws ParseException {
checkBitsAvailable(data, 4); validateBitsAvailability(data, 4);
data.skipBits(3); // tw_mdct(1), fullbandLpd(1), noiseFilling(1) data.skipBits(3); // tw_mdct(1), fullbandLpd(1), noiseFilling(1)
boolean enhancedNoiseFilling = data.readBit(); boolean enhancedNoiseFilling = data.readBit();
if (enhancedNoiseFilling) { if (enhancedNoiseFilling) {
checkBitsAvailable(data, 13); validateBitsAvailability(data, 13);
data.skipBits(13); // igfUseEnf(1), igfUseHighRes(1), igfUseWhitening(1), igfAfterTnsSynth(1), igfStartIndex(5), igfStopIndex(4) data.skipBits(13); // igfUseEnf(1), igfUseHighRes(1), igfUseWhitening(1), igfAfterTnsSynth(1), igfStartIndex(5), igfStopIndex(4)
} }
return enhancedNoiseFilling; return enhancedNoiseFilling;
} }
/** /**
* This function is used to parse the SbrConfig from an MPEG-H bit stream. * Parses the SbrConfig from an MPEG-H bit stream.
* See ISO_IEC_23003-3;2020, 5.2, Table 14 * See ISO_IEC_23003-3;2020, 5.2, Table 14.
* *
* @param data The bit array to be parsed. * @param data The bit array to be parsed.
* @throws ParseException If parsing failed. * @throws {@link ParseException} if parsing failed.
*/ */
private static void parseSbrConfig(ParsableBitArray data) throws ParseException { private static void parseSbrConfig(ParsableBitArray data) throws ParseException {
checkBitsAvailable(data, 3); validateBitsAvailability(data, 3);
data.skipBits(3); // harmonicSBR(1), bs_interTes(1), bs_pvc(1) data.skipBits(3); // harmonicSBR(1), bs_interTes(1), bs_pvc(1)
checkBitsAvailable(data, 10); validateBitsAvailability(data, 10);
data.skipBits(8); // dflt_start_freq(4), dflt_stop_freq(4) data.skipBits(8); // dflt_start_freq(4), dflt_stop_freq(4)
boolean dflt_header_extra1 = data.readBit(); boolean dflt_header_extra1 = data.readBit();
boolean dflt_header_extra2 = data.readBit(); boolean dflt_header_extra2 = data.readBit();
if (dflt_header_extra1) { if (dflt_header_extra1) {
checkBitsAvailable(data, 5); validateBitsAvailability(data, 5);
data.skipBits(5); // dflt_freq_scale(2), dflt_alter_scale(1), dflt_noise_bands(2) data.skipBits(5); // dflt_freq_scale(2), dflt_alter_scale(1), dflt_noise_bands(2)
} }
if (dflt_header_extra2) { if (dflt_header_extra2) {
checkBitsAvailable(data, 6); validateBitsAvailability(data, 6);
data.skipBits(6); // dflt_limiter_bands(2), dflt_limiter_gains(2), dflt_interpol_freq(1), dflt_smoothing_mode(1) data.skipBits(6); // dflt_limiter_bands(2), dflt_limiter_gains(2), dflt_interpol_freq(1), dflt_smoothing_mode(1)
} }
} }
@ -838,24 +867,24 @@ public final class MpeghUtil {
public static class ParseException extends IOException { public static class ParseException extends IOException {
public final @ParseState int parseState; public final @State int parseState;
public static ParseException createForNotEnoughData() { public static ParseException createForNotEnoughData() {
return new ParseException(null, null, END_OUTPUT); return new ParseException(null, null, STATE_END_OUTPUT);
} }
public static ParseException createForUnsupportedSubstream(@Nullable String message) { public static ParseException createForUnsupportedSubstream(@Nullable String message) {
return new ParseException(message, null, SUBSTREAM_UNSUPPORTED); return new ParseException(message, null, STATE_SUBSTREAM_UNSUPPORTED);
} }
public static ParseException createForParsingError(@Nullable String message) { public static ParseException createForParsingError(@Nullable String message) {
return new ParseException(message, null, PARSE_ERROR); return new ParseException(message, null, STATE_PARSE_ERROR);
} }
protected ParseException( protected ParseException(
@Nullable String message, @Nullable String message,
@Nullable Throwable cause, @Nullable Throwable cause,
@ParseState int parseState) { @State int parseState) {
super(message, cause); super(message, cause);
this.parseState = parseState; this.parseState = parseState;
} }

View file

@ -25,7 +25,6 @@ import static androidx.media3.extractor.ts.TsPayloadReader.FLAG_RANDOM_ACCESS_IN
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
@ -33,6 +32,7 @@ import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.ParsableBitArray; import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.ParsableByteArray; import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.extractor.ExtractorOutput; import androidx.media3.extractor.ExtractorOutput;
import androidx.media3.extractor.MpeghUtil; import androidx.media3.extractor.MpeghUtil;
import androidx.media3.extractor.TrackOutput; import androidx.media3.extractor.TrackOutput;
@ -44,6 +44,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Parses a continuous MPEG-H audio byte stream and extracts MPEG-H frames. * Parses a continuous MPEG-H audio byte stream and extracts MPEG-H frames.
*/ */
@UnstableApi
public final class MpeghReader implements ElementaryStreamReader { public final class MpeghReader implements ElementaryStreamReader {
private static final String TAG = MpeghReader.class.getSimpleName(); private static final String TAG = MpeghReader.class.getSimpleName();
@ -107,7 +108,7 @@ public final class MpeghReader implements ElementaryStreamReader {
} }
@Override @Override
public void consume(@NonNull ParsableByteArray data) { public void consume(ParsableByteArray data) {
// write the PES payload to a data buffer until the packet is complete // write the PES payload to a data buffer until the packet is complete
appendToDataBuffer(data); appendToDataBuffer(data);
} }