Add AC-4 Level-4 ISO base media file format support

This commit is contained in:
ybai001 2023-12-08 09:42:32 +08:00
parent b8c559ed32
commit 29bda3bb5c
3 changed files with 657 additions and 12 deletions

View file

@ -2264,6 +2264,22 @@ public final class Util {
}
case 12:
return AudioFormat.CHANNEL_OUT_7POINT1POINT4;
case 24:
return Util.SDK_INT >= 32
? AudioFormat.CHANNEL_OUT_7POINT1POINT4 |
AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER |
AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER |
AudioFormat.CHANNEL_OUT_BACK_CENTER |
AudioFormat.CHANNEL_OUT_TOP_CENTER |
AudioFormat.CHANNEL_OUT_TOP_FRONT_CENTER |
AudioFormat.CHANNEL_OUT_TOP_BACK_CENTER |
AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT |
AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT |
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT |
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT |
AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_CENTER |
AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2
: AudioFormat.CHANNEL_INVALID;
default:
return AudioFormat.CHANNEL_INVALID;
}

View file

@ -4242,13 +4242,23 @@ public class DefaultTrackSelector extends MappingTrackSelector
}
public boolean canBeSpatialized(AudioAttributes audioAttributes, Format format) {
// For E-AC3 JOC, the format is object based. When the channel count is 16, this maps to 12
// linear channels and the rest are used for objects. See
// https://github.com/google/ExoPlayer/pull/10322#discussion_r895265881
int linearChannelCount =
MimeTypes.AUDIO_E_AC3_JOC.equals(format.sampleMimeType) && format.channelCount == 16
? 12
: format.channelCount;
int linearChannelCount;
if (MimeTypes.AUDIO_E_AC3_JOC.equals(format.sampleMimeType)) {
// For E-AC3 JOC, the format is object based. When the channel count is 16, this maps to 12
// linear channels and the rest are used for objects. See
// https://github.com/google/ExoPlayer/pull/10322#discussion_r895265881
linearChannelCount = format.channelCount == 16 ? 12 : format.channelCount;
} else if (MimeTypes.AUDIO_AC4.equals(format.sampleMimeType)) {
// For AC-4 level 3 or level 4, the format may be object based. When the channel count is
// 18 (level 3 17.1 OBI) or 21 (level 4 20.1 OBI), it is mapped to 24 linear channels
// (There are some channels used for metadata transfer).
linearChannelCount = (format.channelCount == 18 || format.channelCount == 21)
? 24
: format.channelCount;
} else {
linearChannelCount = format.channelCount;
}
AudioFormat.Builder builder =
new AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)

View file

@ -20,15 +20,22 @@ import androidx.media3.common.C;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.ParsableBitArray;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/** Utility methods for parsing AC-4 frames, which are access units in AC-4 bitstreams. */
@UnstableApi
public final class Ac4Util {
private static final String TAG = "Ac4Util";
/** Holds sample format information as presented by a syncframe header. */
public static final class SyncFrameInfo {
@ -102,9 +109,99 @@ public final class Ac4Util {
/* [13] 23.438 fps */ 2048
};
/**
* channel_mode is defined in ETSI TS 103 190-2 V1.2.1 (2018-02), section 6.3.2.7.2 and Table 78.
*/
private static final String[] CHANNEL_MODES =
new String[] {
"Mono",
"Stereo",
"3.0",
"5.0",
"5.1",
"7.0 (3/4/0)",
"7.1 (3/4/0.1)",
"7.0 (5/2/0)",
"7.1 (5/2/0.1)",
"7.0 (3/2/2)",
"7.1 (3/2/2.1)",
"7.0.4",
"7.1.4",
"9.0.4",
"9.1.4",
"22.2"
};
/** Holds AC-4 presentation information. */
public static final class Ac4Presentation {
// TS 103 190-1 v1.2.1 4.3.3.8.1: content_classifiers
public static final int K_COMPLETE_MAIN = 0;
public static final int K_MUSIC_AND_EFFECTS = 1;
public static final int K_VISUALLY_IMPAIRED = 2;
public static final int K_HEARING_IMPAIRED = 3;
public static final int K_DIALOG = 4;
public static final int K_COMMENTARY = 5;
public static final int K_EMERGENCY = 6;
public static final int K_VOICEOVER = 7;
public int contentClassifier = K_COMPLETE_MAIN;
// ETSI TS 103 190-2 V1.1.1 (2015-09) Table 79: channel_mode
public static final int K_CHANNEL_MODE_MONO = 0;
public static final int K_CHANNEL_MODE_STEREO = 1;
public static final int K_CHANNEL_MODE_3_0 = 2;
public static final int K_CHANNEL_MODE_5_0 = 3;
public static final int K_CHANNEL_MODE_5_1 = 4;
public static final int K_CHANNEL_MODE_7_0_34 = 5;
public static final int K_CHANNEL_MODE_7_1_34 = 6;
public static final int K_CHANNEL_MODE_7_0_52 = 7;
public static final int K_CHANNEL_MODE_7_1_52 = 8;
public static final int K_CHANNEL_MODE_7_0_322 = 9;
public static final int K_CHANNEL_MODE_7_1_322 = 10;
public static final int K_CHANNEL_MODE_7_0_4 = 11;
public static final int K_CHANNEL_MODE_7_1_4 = 12;
public static final int K_CHANNEL_MODE_9_0_4 = 13;
public static final int K_CHANNEL_MODE_9_1_4 = 14;
public static final int K_CHANNEL_MODE_22_2 = 15;
public static final int K_CHANNEL_MODE_RESERVED = 16;
public boolean channelCoded;
public int channelMode;
public int numOfUmxObjects;
public boolean backChannelsPresent;
public int topChannelPairs;
public int programID;
public int groupIndex;
public boolean hasDialogEnhancements;
public boolean preVirtualized;
public int version;
public int level;
@Nullable
public ByteBuffer language;
@Nullable
public ByteBuffer description;
private Ac4Presentation() {
this.channelCoded = true;
this.channelMode = -1;
this.numOfUmxObjects = -1;
this.backChannelsPresent = true;
this.topChannelPairs = 2;
this.programID = -1;
this.groupIndex = -1;
this.hasDialogEnhancements = false;
this.preVirtualized = false;
this.version = 0;
this.level = 0;
this.language = null;
this.description = null;
}
}
/**
* Returns the AC-4 format given {@code data} containing the AC4SpecificBox according to ETSI TS
* 103 190-1 Annex E. The reading position of {@code data} will be modified.
* 103 190-1 Annex E.4 (ac4_dsi) and TS 103 190-2 section E.6 (ac4_dsi_v1). The reading position
* of {@code data} will be modified.
*
* @param data The AC4SpecificBox to parse.
* @param trackId The track identifier to set on the format.
@ -113,13 +210,335 @@ public final class Ac4Util {
* @return The AC-4 format parsed from data in the header.
*/
public static Format parseAc4AnnexEFormat(
ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData) {
data.skipBytes(1); // ac4_dsi_version, bitstream_version[0:5]
int sampleRate = ((data.readUnsignedByte() & 0x20) >> 5 == 1) ? 48000 : 44100;
ParsableByteArray data, String trackId, String language, @Nullable DrmInitData drmInitData)
throws ParserException {
ParsableBitArray dataBitArray = new ParsableBitArray();
dataBitArray.reset(data);
Map<Integer, Ac4Presentation> ac4Presentations = new HashMap<>();
long dsiSize = dataBitArray.bitsLeft();
int ac4DsiVersion = dataBitArray.readBits(3); // ac4_dsi_version
if (ac4DsiVersion > 1) {
throw ParserException.createForUnsupportedContainerFeature(
"Unsupported AC-4 DSI version: " + ac4DsiVersion);
}
final int bitstreamVersion = dataBitArray.readBits(7); // bitstream_version
int sampleRate = dataBitArray.readBit() ? 48000 : 44100; // fs_index
dataBitArray.skipBits(4); // frame_rate_index
int nPresentations = dataBitArray.readBits(9); // n_presentations
int shortProgramId = -1;
if (bitstreamVersion > 1) {
if (ac4DsiVersion == 0) {
throw ParserException.createForUnsupportedContainerFeature(
"Invalid AC-4 DSI version: " + ac4DsiVersion);
}
boolean bProgramId = dataBitArray.readBit(); // b_program_id
if (bProgramId) {
shortProgramId = dataBitArray.readBits(16);
boolean bUuid = dataBitArray.readBit(); // b_uuid
if (bUuid) {
dataBitArray.skipBits(16 * 8); // program_uuid
}
}
}
if (ac4DsiVersion == 1) {
if (!parseBitrateDsi(dataBitArray)) {
throw ParserException.createForUnsupportedContainerFeature(
"Invalid AC-4 DSI bitrate.");
}
dataBitArray.byteAlign();
}
for (int presentationIndex = 0; presentationIndex < nPresentations; presentationIndex++) {
Ac4Presentation ac4Presentation = new Ac4Presentation();
ac4Presentation.programID = shortProgramId;
// known as b_single_substream in ac4_dsi_version 0
boolean bSingleSubstreamGroup = false;
int presentationConfig = 0;
int presentationVersion = 0;
int presBytes = 0;
long start = 0;
if (ac4DsiVersion == 0) {
bSingleSubstreamGroup = dataBitArray.readBit(); // b_single_substream_group
presentationConfig = dataBitArray.readBits(5); // presentation_config
presentationVersion = dataBitArray.readBits(5); // presentation_version
} else if (ac4DsiVersion == 1) {
presentationVersion = dataBitArray.readBits(8); // presentation_version
presBytes = dataBitArray.readBits(8); // pres_bytes
if (presBytes == 0xff) {
presBytes += dataBitArray.readBits(16); // pres_bytes
}
if (presentationVersion > 2) {
dataBitArray.skipBits(presBytes * 8);
ac4Presentations.put(presentationIndex, ac4Presentation);
continue;
}
// record a marker, less the size of the presentation_config
start = (dsiSize - dataBitArray.bitsLeft()) / 8;
// ac4_presentation_v0_dsi(), ac4_presentation_v1_dsi() and ac4_presentation_v2_dsi()
// all start with a presentation_config of 5 bits
presentationConfig = dataBitArray.readBits(5); // presentation_config
bSingleSubstreamGroup = (presentationConfig == 0x1f);
}
boolean bAddEmdfSubstreams;
if (!bSingleSubstreamGroup && presentationConfig == 6) {
bAddEmdfSubstreams = true;
} else {
int mdcompat = dataBitArray.readBits(3); // mdcompat
ac4Presentation.version = presentationVersion;
ac4Presentation.level = mdcompat;
boolean bPresentationGroupIndex = dataBitArray.readBit(); // b_presentation_group_index
if (bPresentationGroupIndex) {
ac4Presentation.groupIndex = dataBitArray.readBits(5); // group_index
}
dataBitArray.skipBits(2); // dsi_frame_rate_multiply_info
if (ac4DsiVersion == 1 && (presentationVersion == 1 || presentationVersion == 2)) {
dataBitArray.skipBits(2); // dsi_frame_rate_fraction_info
}
dataBitArray.skipBits(5); // presentation_emdf_version
dataBitArray.skipBits(10); // presentation_key_id
if (ac4DsiVersion == 1) {
boolean bPresentationChannelCoded; // b_presentation_channel_coded
if (presentationVersion == 0) {
bPresentationChannelCoded = true;
} else {
bPresentationChannelCoded = dataBitArray.readBit(); // b_presentation_channel_coded
}
ac4Presentation.channelCoded = bPresentationChannelCoded;
if (bPresentationChannelCoded) {
if (presentationVersion == 1 || presentationVersion == 2) {
int dsiPresentationChMode =
dataBitArray.readBits(5); // dsi_presentation_ch_mode
ac4Presentation.channelMode = dsiPresentationChMode;
if (dsiPresentationChMode >= 11 && dsiPresentationChMode <= 14) {
ac4Presentation.backChannelsPresent =
dataBitArray.readBit(); // pres_b_4_back_channels_present
ac4Presentation.topChannelPairs =
dataBitArray.readBits(2); // pres_top_channel_pairs
}
}
// presentation_channel_mask in ac4_presentation_v0_dsi()
dataBitArray.skipBits(24); // presentation_channel_mask_v1
}
if (presentationVersion == 1 || presentationVersion == 2) {
boolean bPresentationCoreDiffers =
dataBitArray.readBit(); // b_presentation_core_differs
if (bPresentationCoreDiffers) {
boolean bPresentationCoreChannelCoded =
dataBitArray.readBit(); // b_presentation_core_channel_coded
if (bPresentationCoreChannelCoded) {
dataBitArray.skipBits(2); // dsi_presentation_channel_mode_core
}
}
boolean bPresentationFilter = dataBitArray.readBit(); // b_presentation_filter
if (bPresentationFilter) {
// Ignore b_enable_presentation field since this flag occurs in AC-4 elementary stream
// TOC and AC-4 decoder doesn't handle it either.
dataBitArray.skipBit(); // b_enable_presentation
int nFilterBytes = dataBitArray.readBits(8); // n_filter_bytes
for (int i = 0; i < nFilterBytes; i++) {
dataBitArray.skipBits(8); // filter_data
}
}
}
}
if (bSingleSubstreamGroup) {
if (presentationVersion == 0) {
if (!parseSubstreamDSI(
dataBitArray, ac4Presentation, presentationIndex, 0)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream DSI, presentation index = "
+ presentationIndex + ", single substream.");
}
} else {
if (!parseSubstreamGroupDSI(
dataBitArray, ac4Presentation, presentationIndex, 0)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream group DSI, presentation index = "
+ presentationIndex + "single substream group.");
}
}
} else {
if (ac4DsiVersion == 1) {
dataBitArray.skipBit(); // b_multi_pid
} else {
dataBitArray.skipBit(); // b_hsf_ext
}
switch (presentationConfig) {
case 0:
case 1:
case 2:
if (presentationVersion == 0) {
for (int substreamID = 0; substreamID < 2; substreamID++) {
if (!parseSubstreamDSI(
dataBitArray, ac4Presentation, presentationIndex, substreamID)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream DSI, presentation index = "
+ presentationIndex + ", substream ID = " + substreamID);
}
}
} else {
for (int substreamGroupID = 0; substreamGroupID < 2; substreamGroupID++) {
if (!parseSubstreamGroupDSI(
dataBitArray, ac4Presentation, presentationIndex, substreamGroupID)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream group DSI, presentation index = "
+ presentationIndex + ", substream group ID = " + substreamGroupID);
}
}
}
break;
case 3:
case 4:
if (presentationVersion == 0) {
for (int substreamID = 0; substreamID < 3; substreamID++) {
if (!parseSubstreamDSI(
dataBitArray, ac4Presentation, presentationIndex, substreamID)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream DSI, presentation index = "
+ presentationIndex + ", substream ID = " + substreamID);
}
}
} else {
for (int substreamGroupID = 0; substreamGroupID < 3; substreamGroupID++) {
if (!parseSubstreamGroupDSI(
dataBitArray, ac4Presentation, presentationIndex, substreamGroupID)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream group DSI, presentation index = "
+ presentationIndex + ", substream group ID = " + substreamGroupID);
}
}
}
break;
case 5:
if (presentationVersion == 0) {
if (!parseSubstreamDSI(
dataBitArray, ac4Presentation, presentationIndex, 0)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream DSI, presentation index = "
+ presentationIndex + "single substream.");
}
} else {
int nSubstreamGroupsMinus2 = dataBitArray.readBits(3);
for (int substreamGroupID = 0; substreamGroupID < nSubstreamGroupsMinus2 + 2;
substreamGroupID++) {
if (!parseSubstreamGroupDSI(
dataBitArray, ac4Presentation, presentationIndex, substreamGroupID)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse substream group DSI, presentation index = "
+ presentationIndex + ", substream group ID = " + substreamGroupID);
}
}
}
break;
default:
int nSkipBytes = dataBitArray.readBits(7); // n_skip_bytes
for (int j = 0; j < nSkipBytes; j++) {
dataBitArray.skipBits(8);
}
break;
}
}
ac4Presentation.preVirtualized = dataBitArray.readBit(); // b_pre_virtualized
bAddEmdfSubstreams = dataBitArray.readBit(); // b_add_emdf_substreams
}
if (bAddEmdfSubstreams) {
int nAddEmdfSubstreams = dataBitArray.readBits(7); // n_add_emdf_substreams
for (int j = 0; j < nAddEmdfSubstreams; j++) {
dataBitArray.skipBits(5 + 10); // substream_emdf_version and substream_key_id
}
}
boolean bPresentationBitrateInfo = false;
if (presentationVersion > 0) {
bPresentationBitrateInfo = dataBitArray.readBit(); // b_presentation_bitrate_info
}
if (bPresentationBitrateInfo) {
if (!parseBitrateDsi(dataBitArray)) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't parse bitrate DSI.");
}
}
if (presentationVersion > 0) {
boolean bAlternative = dataBitArray.readBit(); // b_alternative
if (bAlternative) {
dataBitArray.byteAlign();
int nameLen = dataBitArray.readBits(16); // name_len
byte[] presentationName = new byte[nameLen];
dataBitArray.readBytes(presentationName, 0, nameLen);
ac4Presentation.description = ByteBuffer.wrap(presentationName);
int nTargets = dataBitArray.readBits(5); // n_targets
for (int i = 0; i < nTargets; i++) {
dataBitArray.skipBits(3); // target_md_compat
dataBitArray.skipBits(8); // target_device_category
}
}
}
dataBitArray.byteAlign();
if (ac4DsiVersion == 1) {
long end = (dsiSize - dataBitArray.bitsLeft()) / 8;
long presentationBytes = end - start;
if (presBytes < presentationBytes) {
throw ParserException.createForUnsupportedContainerFeature(
"pres_bytes is smaller than presentation_bytes.");
}
long skipBytes = presBytes - presentationBytes;
dataBitArray.skipBits((int)skipBytes * 8);
}
// We should know this or something is probably wrong
// with the bitstream (or we don't support it)
if (ac4Presentation.channelCoded && ac4Presentation.channelMode == -1) {
throw ParserException.createForUnsupportedContainerFeature(
"Can't determine channel mode of presentation " + presentationIndex);
}
ac4Presentations.put(presentationIndex, ac4Presentation);
}
int channelCount = -1;
// Using first presentation (default presentation) channel count
int presentationIndex = 0;
Ac4Presentation ac4Presentation =
Objects.requireNonNull(ac4Presentations.get(presentationIndex));
if (ac4Presentation.channelCoded) {
channelCount = convertAc4ChannelModeToChannelCount(ac4Presentation.channelMode,
ac4Presentation.backChannelsPresent, ac4Presentation.topChannelPairs);
} else {
channelCount = ac4Presentation.numOfUmxObjects;
// TODO: There is a bug in ETSI TS 103 190-2 V1.2.1 (2018-02), E.11.11
// For AC-4 level 4 stream, the intention is to set 19 to n_umx_objects_minus1 but it is
// equal to 15 based on current specification. Dolby has filed a bug report to ETSI.
// The following sentence should be deleted after ETSI specification error is fixed.
if (ac4Presentation.level == 4) {
channelCount = channelCount == 16 ? 21 : channelCount;
}
}
if (channelCount <= 0) throw ParserException.createForUnsupportedContainerFeature(
"Can't determine channel count of presentation.");
return new Format.Builder()
.setId(trackId)
.setSampleMimeType(MimeTypes.AUDIO_AC4)
.setChannelCount(CHANNEL_COUNT_2)
.setChannelCount(channelCount)
.setSampleRate(sampleRate)
.setDrmInitData(drmInitData)
.setLanguage(language)
@ -242,6 +661,206 @@ public final class Ac4Util {
data[6] = (byte) (size & 0xFF);
}
private static boolean parseBitrateDsi(ParsableBitArray dataBitArray) {
if (dataBitArray.bitsLeft() < 2 + 32 + 32) {
return false;
}
dataBitArray.skipBits(2); // bit_rate_mode
dataBitArray.skipBits(32); // bit_rate
dataBitArray.skipBits(32); // bit_rate_precision
return true;
}
private static int convertAc4ChannelModeToChannelCount(
int mode, boolean backChannelsPresent, int topChannelPairs) {
int channelCount = -1;
switch (mode) {
case Ac4Presentation.K_CHANNEL_MODE_MONO:
channelCount = 1;
break;
case Ac4Presentation.K_CHANNEL_MODE_STEREO:
channelCount = 2;
break;
case Ac4Presentation.K_CHANNEL_MODE_3_0:
channelCount = 3;
break;
case Ac4Presentation.K_CHANNEL_MODE_5_0:
channelCount = 5;
break;
case Ac4Presentation.K_CHANNEL_MODE_5_1:
channelCount = 6;
break;
case Ac4Presentation.K_CHANNEL_MODE_7_0_34:
case Ac4Presentation.K_CHANNEL_MODE_7_0_52:
case Ac4Presentation.K_CHANNEL_MODE_7_0_322:
channelCount = 7;
break;
case Ac4Presentation.K_CHANNEL_MODE_7_1_34:
case Ac4Presentation.K_CHANNEL_MODE_7_1_52:
case Ac4Presentation.K_CHANNEL_MODE_7_1_322:
channelCount = 8;
break;
case Ac4Presentation.K_CHANNEL_MODE_7_0_4:
channelCount = 11;
break;
case Ac4Presentation.K_CHANNEL_MODE_7_1_4:
channelCount = 12;
break;
case Ac4Presentation.K_CHANNEL_MODE_9_0_4:
channelCount = 13;
break;
case Ac4Presentation.K_CHANNEL_MODE_9_1_4:
channelCount = 14;
break;
case Ac4Presentation.K_CHANNEL_MODE_22_2:
channelCount = 24;
break;
default:
Log.w(TAG, "Invalid channel mode in AC-4 presentation.");
return channelCount;
}
switch (mode) {
case Ac4Presentation.K_CHANNEL_MODE_7_0_4:
case Ac4Presentation.K_CHANNEL_MODE_7_1_4:
case Ac4Presentation.K_CHANNEL_MODE_9_0_4:
case Ac4Presentation.K_CHANNEL_MODE_9_1_4:
if (!backChannelsPresent) {
channelCount -= 2;
}
if (topChannelPairs == 0) {
channelCount -= 4;
} else if (topChannelPairs == 1) {
channelCount -= 2;
} else if (topChannelPairs == 2) {
;
} else {
Log.w(TAG, "Invalid topChannelPairs in AC-4 presentation.");
}
break;
default:
break;
}
return channelCount;
}
private static boolean parseLanguageTag(ParsableBitArray dataBitArray,
Ac4Presentation ac4Presentation, int presentationID, int substreamID) {
int nLanguageTagBytes = dataBitArray.readBits(6);
if (nLanguageTagBytes < 2 || nLanguageTagBytes >= 42) {
return false;
}
byte[] languageTagBytes = new byte[nLanguageTagBytes]; // TS 103 190 part 1 4.3.3.8.7
// Can't use readBytes() since it is not byte-aligned here.
dataBitArray.readBits(languageTagBytes, 0, nLanguageTagBytes * 8);
ac4Presentation.language = ByteBuffer.wrap(languageTagBytes);
Log.d(TAG, presentationID + "." + substreamID + ": language_tag = "
+ ac4Presentation.language);
return true;
}
/**
* Parse AC-4 substream DSI according to TS 103 190-1 v1.2.1 E.5 and TS 103 190-2 v1.1.1 E.9
* @param dataBitArray A {@link ParsableBitArray} containing the AC-4 DSI to parse.
* @param ac4Presentation A structure to store AC-4 presentation info.
* @param presentationID The AC-4 presentation index.
* @param substreamID The AC-4 presentation substream ID.
* @return Whether there is an error during substream paring.
*/
private static boolean parseSubstreamDSI(ParsableBitArray dataBitArray,
Ac4Presentation ac4Presentation, int presentationID, int substreamID) {
int channelMode = dataBitArray.readBits(5); // channel_mode
Log.d(TAG, presentationID + "." + substreamID + ": channel_mode = "
+ (channelMode < CHANNEL_MODES.length ? CHANNEL_MODES[channelMode] : "Reserved"));
dataBitArray.skipBits(2); // dsi_sf_multiplier
boolean bSubstreamBitrateIndicator = dataBitArray.readBit();
if (bSubstreamBitrateIndicator) {
dataBitArray.skipBits(5); // substream_bitrate_indicator
}
if (channelMode >= 7 && channelMode <= 10) {
dataBitArray.skipBit(); // add_ch_base
}
boolean bContentType = dataBitArray.readBit(); // b_content_type
if (bContentType) {
int contentClassifier = dataBitArray.readBits(3);
// For streams based on TS 103 190 part 1 the presentation level channel_mode doesn't
// exist and so we use the channel_mode from either the CM or M&E substream
// (they are mutually exclusive)
if (ac4Presentation.channelMode == -1 && (contentClassifier == 0 || contentClassifier == 1)) {
ac4Presentation.channelMode = channelMode;
}
ac4Presentation.contentClassifier = contentClassifier;
boolean bLanguageIndicator = dataBitArray.readBit(); // b_language_indicator
if (bLanguageIndicator) {
if (!parseLanguageTag(dataBitArray, ac4Presentation, presentationID, substreamID)) {
return false;
}
}
}
return true;
}
/**
* Parse AC-4 substream group DSI according to ETSI TS 103 190-2 v1.1.1 section E.11
* @param dataBitArray A {@link ParsableBitArray} containing the AC-4 DSI to parse.
* @param ac4Presentation A structure to store AC-4 presentation info.
* @param presentationID The AC-4 presentation index.
* @param groupID The AC-4 presentation substream group ID.
* @return Whether there is an error during substream group paring.
*/
private static boolean parseSubstreamGroupDSI(ParsableBitArray dataBitArray,
Ac4Presentation ac4Presentation, int presentationID, int groupID) {
dataBitArray.skipBit(); // b_substreams_present
dataBitArray.skipBit(); // b_hsf_ext
boolean bChannelCoded = dataBitArray.readBit(); // b_channel_coded
int nSubstreams = dataBitArray.readBits(8); // n_substreams
for (int i = 0; i < nSubstreams; i++) {
dataBitArray.skipBits(2); // dsi_sf_multiplier
boolean bSubstreamBitrateIndicator = dataBitArray.readBit(); // b_substream_bitrate_indicator
if (bSubstreamBitrateIndicator) {
dataBitArray.skipBits(5); // substream_bitrate_indicator
}
if (bChannelCoded) {
dataBitArray.skipBits(24); // dsi_substream_channel_mask
} else {
boolean bAjoc = dataBitArray.readBit(); // b_ajoc
if (bAjoc) {
boolean bStaticDmx = dataBitArray.readBit(); // b_static_dmx
if (!bStaticDmx) {
dataBitArray.skipBits(4); // n_dmx_objects_minus1
}
int nUmxObjectsMinus1 = dataBitArray.readBits(6); // n_umx_objects_minus1
ac4Presentation.numOfUmxObjects = nUmxObjectsMinus1 + 1;
}
dataBitArray.skipBits(4); // objects_assignment_mask
}
}
boolean bContentType = dataBitArray.readBit(); // b_content_type
if (bContentType) {
ac4Presentation.contentClassifier = dataBitArray.readBits(3); // content_classifier
boolean bLanguageIndicator = dataBitArray.readBit(); // b_language_indicator
if (bLanguageIndicator) {
if (!parseLanguageTag(dataBitArray, ac4Presentation, presentationID, groupID)) {
return false;
}
}
}
return true;
}
private static int readVariableBits(ParsableBitArray data, int bitsPerRead) {
int value = 0;
while (true) {