From 29bda3bb5ce9ee86225ca8c7f44d24183265ee9c Mon Sep 17 00:00:00 2001 From: ybai001 Date: Fri, 8 Dec 2023 09:42:32 +0800 Subject: [PATCH 1/6] Add AC-4 Level-4 ISO base media file format support --- .../androidx/media3/common/util/Util.java | 16 + .../trackselection/DefaultTrackSelector.java | 24 +- .../androidx/media3/extractor/Ac4Util.java | 629 +++++++++++++++++- 3 files changed, 657 insertions(+), 12 deletions(-) diff --git a/libraries/common/src/main/java/androidx/media3/common/util/Util.java b/libraries/common/src/main/java/androidx/media3/common/util/Util.java index 12f93889829..106e43e09cc 100644 --- a/libraries/common/src/main/java/androidx/media3/common/util/Util.java +++ b/libraries/common/src/main/java/androidx/media3/common/util/Util.java @@ -2264,6 +2264,22 @@ public static int getAudioTrackChannelConfig(int channelCount) { } 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; } diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java index dc7c10f0c97..10099129417 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java @@ -4242,13 +4242,23 @@ public boolean isEnabled() { } 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) diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java index 3386f73b416..ad49d0efeb2 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java @@ -20,15 +20,22 @@ 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 @@ private SyncFrameInfo( /* [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 @@ private SyncFrameInfo( * @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 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 static void getAc4SampleHeader(int size, ParsableByteArray buffer) { 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) { From ac94afa4b125312653adbb2940b49dcf866a0e20 Mon Sep 17 00:00:00 2001 From: ybai001 Date: Fri, 8 Dec 2023 09:42:32 +0800 Subject: [PATCH 2/6] Add AC-4 Level-4 ISO base media file format support --- .../src/main/java/androidx/media3/extractor/Ac4Util.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java index ad49d0efeb2..099ac03b408 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java @@ -222,7 +222,7 @@ public static Format parseAc4AnnexEFormat( throw ParserException.createForUnsupportedContainerFeature( "Unsupported AC-4 DSI version: " + ac4DsiVersion); } - final int bitstreamVersion = dataBitArray.readBits(7); // bitstream_version + 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 From e41181d4c3c658e937ebfde710f58299ddb122d1 Mon Sep 17 00:00:00 2001 From: ybai001 Date: Fri, 8 Dec 2023 10:44:59 +0800 Subject: [PATCH 3/6] Merge branch 'dlb/ac4-level4/dev' of https://github.com/DolbyLaboratories/media into dlb/ac4-level4/dev --- .../androidx/media3/extractor/Ac4Util.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java index 099ac03b408..fa3c66d1631 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java @@ -244,7 +244,7 @@ public static Format parseAc4AnnexEFormat( } if (ac4DsiVersion == 1) { - if (!parseBitrateDsi(dataBitArray)) { + if (!parseDsiBitrate(dataBitArray)) { throw ParserException.createForUnsupportedContainerFeature( "Invalid AC-4 DSI bitrate."); } @@ -356,14 +356,14 @@ public static Format parseAc4AnnexEFormat( if (bSingleSubstreamGroup) { if (presentationVersion == 0) { - if (!parseSubstreamDSI( + if (!parseDsiSubstream( dataBitArray, ac4Presentation, presentationIndex, 0)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream DSI, presentation index = " + presentationIndex + ", single substream."); } } else { - if (!parseSubstreamGroupDSI( + if (!parseDsiSubstreamGroup( dataBitArray, ac4Presentation, presentationIndex, 0)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream group DSI, presentation index = " @@ -382,7 +382,7 @@ public static Format parseAc4AnnexEFormat( case 2: if (presentationVersion == 0) { for (int substreamID = 0; substreamID < 2; substreamID++) { - if (!parseSubstreamDSI( + if (!parseDsiSubstream( dataBitArray, ac4Presentation, presentationIndex, substreamID)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream DSI, presentation index = " @@ -391,7 +391,7 @@ public static Format parseAc4AnnexEFormat( } } else { for (int substreamGroupID = 0; substreamGroupID < 2; substreamGroupID++) { - if (!parseSubstreamGroupDSI( + if (!parseDsiSubstreamGroup( dataBitArray, ac4Presentation, presentationIndex, substreamGroupID)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream group DSI, presentation index = " @@ -404,7 +404,7 @@ public static Format parseAc4AnnexEFormat( case 4: if (presentationVersion == 0) { for (int substreamID = 0; substreamID < 3; substreamID++) { - if (!parseSubstreamDSI( + if (!parseDsiSubstream( dataBitArray, ac4Presentation, presentationIndex, substreamID)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream DSI, presentation index = " @@ -413,7 +413,7 @@ public static Format parseAc4AnnexEFormat( } } else { for (int substreamGroupID = 0; substreamGroupID < 3; substreamGroupID++) { - if (!parseSubstreamGroupDSI( + if (!parseDsiSubstreamGroup( dataBitArray, ac4Presentation, presentationIndex, substreamGroupID)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream group DSI, presentation index = " @@ -424,7 +424,7 @@ public static Format parseAc4AnnexEFormat( break; case 5: if (presentationVersion == 0) { - if (!parseSubstreamDSI( + if (!parseDsiSubstream( dataBitArray, ac4Presentation, presentationIndex, 0)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream DSI, presentation index = " @@ -434,7 +434,7 @@ public static Format parseAc4AnnexEFormat( int nSubstreamGroupsMinus2 = dataBitArray.readBits(3); for (int substreamGroupID = 0; substreamGroupID < nSubstreamGroupsMinus2 + 2; substreamGroupID++) { - if (!parseSubstreamGroupDSI( + if (!parseDsiSubstreamGroup( dataBitArray, ac4Presentation, presentationIndex, substreamGroupID)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse substream group DSI, presentation index = " @@ -467,7 +467,7 @@ public static Format parseAc4AnnexEFormat( } if (bPresentationBitrateInfo) { - if (!parseBitrateDsi(dataBitArray)) { + if (!parseDsiBitrate(dataBitArray)) { throw ParserException.createForUnsupportedContainerFeature( "Can't parse bitrate DSI."); } @@ -661,7 +661,7 @@ public static void getAc4SampleHeader(int size, ParsableByteArray buffer) { data[6] = (byte) (size & 0xFF); } - private static boolean parseBitrateDsi(ParsableBitArray dataBitArray) { + private static boolean parseDsiBitrate(ParsableBitArray dataBitArray) { if (dataBitArray.bitsLeft() < 2 + 32 + 32) { return false; } @@ -763,14 +763,14 @@ private static boolean parseLanguageTag(ParsableBitArray dataBitArray, } /** - * 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 + * Parse AC-4 DSI substream 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, + private static boolean parseDsiSubstream(ParsableBitArray dataBitArray, Ac4Presentation ac4Presentation, int presentationID, int substreamID) { int channelMode = dataBitArray.readBits(5); // channel_mode Log.d(TAG, presentationID + "." + substreamID + ": channel_mode = " @@ -808,14 +808,14 @@ private static boolean parseSubstreamDSI(ParsableBitArray dataBitArray, } /** - * Parse AC-4 substream group DSI according to ETSI TS 103 190-2 v1.1.1 section E.11 + * Parse AC-4 DSI substream group 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, + private static boolean parseDsiSubstreamGroup(ParsableBitArray dataBitArray, Ac4Presentation ac4Presentation, int presentationID, int groupID) { dataBitArray.skipBit(); // b_substreams_present dataBitArray.skipBit(); // b_hsf_ext From 64a006b66551e7fa6e174c8c7c18598a36462684 Mon Sep 17 00:00:00 2001 From: ybai001 Date: Wed, 17 Jan 2024 13:08:58 +0800 Subject: [PATCH 4/6] Merge branch 'dlb/ac4-level4/dev' of https://github.com/DolbyLaboratories/media into dlb/ac4-level4/dev --- .../extractor/mp4/Mp4ExtractorTest.java | 149 ++++++++++++++++++ .../mp4/sample_ac4_level4.mp4.0.dump | 100 ++++++++++++ .../mp4/sample_ac4_level4.mp4.1.dump | 100 ++++++++++++ .../mp4/sample_ac4_level4.mp4.2.dump | 60 +++++++ .../mp4/sample_ac4_level4.mp4.3.dump | 60 +++++++ .../sample_ac4_level4.mp4.unknown_length.dump | 100 ++++++++++++ .../assets/media/mp4/sample_ac4_level4.mp4 | Bin 0 -> 163225 bytes 7 files changed, 569 insertions(+) create mode 100644 libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.0.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.1.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.2.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.3.dump create mode 100644 libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.unknown_length.dump create mode 100644 libraries/test_data/src/test/assets/media/mp4/sample_ac4_level4.mp4 diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java new file mode 100644 index 00000000000..f4f0f1132b5 --- /dev/null +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.extractor.mp4; + +import androidx.media3.test.utils.ExtractorAsserts; +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.ParameterizedRobolectricTestRunner; +import org.robolectric.ParameterizedRobolectricTestRunner.Parameter; +import org.robolectric.ParameterizedRobolectricTestRunner.Parameters; + +/** Tests for {@link Mp4Extractor}. */ +@RunWith(ParameterizedRobolectricTestRunner.class) +public final class Mp4ExtractorTest { + + @Parameters(name = "{0}") + public static ImmutableList params() { + return ExtractorAsserts.configs(); + } + + @Parameter public ExtractorAsserts.SimulationConfig simulationConfig; + + @Test + public void mp4Sample() throws Exception { + ExtractorAsserts.assertBehavior(Mp4Extractor::new, "media/mp4/sample.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithSlowMotionMetadata() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_android_slow_motion.mp4", simulationConfig); + } + + /** + * Test case for https://github.com/google/ExoPlayer/issues/6774. The sample file contains an mdat + * atom whose size indicates that it extends 8 bytes beyond the end of the file. + */ + @Test + public void mp4SampleWithMdatTooLong() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_mdat_too_long.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithAc3Track() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_ac3.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithAc4Track() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_ac4.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithAc4Level4Track() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_ac4_level4.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithEac3Track() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_eac3.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithEac3jocTrack() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_eac3joc.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithOpusTrack() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_opus.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithMha1Track() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_mpegh_mha1.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithMhm1Track() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_mpegh_mhm1.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithColorInfo() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_with_color_info.mp4", simulationConfig); + } + + /** + * Test case for https://github.com/google/ExoPlayer/issues/9332. The file contains a colr box + * with size=18 and type=nclx. This is not valid according to the spec (size must be 19), but + * files like this exist in the wild. + */ + @Test + public void mp4Sample18ByteNclxColr() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_18byte_nclx_colr.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithDolbyTrueHDTrack() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig); + } + + @Test + public void mp4SampleWithColrMdcvAndClli() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_with_colr_mdcv_and_clli.mp4", simulationConfig); + } + + /** Test case for supporting original QuickTime specification [Internal: b/297137302]. */ + @Test + public void mp4SampleWithOriginalQuicktimeSpecification() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, + "media/mp4/sample_with_original_quicktime_specification.mov", + simulationConfig); + } + + @Test + public void mp4SampleWithAv1c() throws Exception { + ExtractorAsserts.assertBehavior( + Mp4Extractor::new, "media/mp4/sample_with_av1c.mp4", simulationConfig); + } +} diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.0.dump new file mode 100644 index 00000000000..90cd9d402cc --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.0.dump @@ -0,0 +1,100 @@ +seekMap: + isSeekable = true + duration = 853333 + getPosition(0) = [[timeUs=0, position=665]] + getPosition(1) = [[timeUs=1, position=665]] + getPosition(426666) = [[timeUs=426666, position=81945]] + getPosition(853333) = [[timeUs=853333, position=81945]] +numberOfTracks = 1 +track 0: + total output bytes = 162700 + sample count = 20 + format 0: + id = 1 + sampleMimeType = audio/ac4 + maxInputSize = 8158 + channelCount = 21 + sampleRate = 48000 + language = und + metadata = entries=[Mp4Timestamp: creation time=3785281997, modification time=3785281997, timescale=600] + sample 0: + time = 0 + flags = 1 + data = length 8135, hash B524F88E + sample 1: + time = 42666 + flags = 0 + data = length 8135, hash FB80C2FB + sample 2: + time = 85333 + flags = 0 + data = length 8135, hash 907C0C31 + sample 3: + time = 128000 + flags = 0 + data = length 8135, hash FDFBD32B + sample 4: + time = 170666 + flags = 0 + data = length 8135, hash 6CAF0549 + sample 5: + time = 213333 + flags = 0 + data = length 8135, hash F5CA1C9A + sample 6: + time = 256000 + flags = 0 + data = length 8135, hash B1B5160D + sample 7: + time = 298666 + flags = 0 + data = length 8135, hash 9E923B3F + sample 8: + time = 341333 + flags = 0 + data = length 8135, hash B1C0BB1F + sample 9: + time = 384000 + flags = 0 + data = length 8135, hash 56F65A03 + sample 10: + time = 426666 + flags = 1 + data = length 8135, hash D07FA9A1 + sample 11: + time = 469333 + flags = 0 + data = length 8135, hash EF26FDDE + sample 12: + time = 512000 + flags = 0 + data = length 8135, hash 8946EEEB + sample 13: + time = 554666 + flags = 0 + data = length 8135, hash AC2E4C99 + sample 14: + time = 597333 + flags = 0 + data = length 8135, hash B63A1D8 + sample 15: + time = 640000 + flags = 0 + data = length 8135, hash 23119F0F + sample 16: + time = 682666 + flags = 0 + data = length 8135, hash 507972CA + sample 17: + time = 725333 + flags = 0 + data = length 8135, hash E574BC00 + sample 18: + time = 768000 + flags = 0 + data = length 8135, hash 52F482FA + sample 19: + time = 810666 + flags = 536870912 + data = length 8135, hash C1A7B518 +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.1.dump new file mode 100644 index 00000000000..90cd9d402cc --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.1.dump @@ -0,0 +1,100 @@ +seekMap: + isSeekable = true + duration = 853333 + getPosition(0) = [[timeUs=0, position=665]] + getPosition(1) = [[timeUs=1, position=665]] + getPosition(426666) = [[timeUs=426666, position=81945]] + getPosition(853333) = [[timeUs=853333, position=81945]] +numberOfTracks = 1 +track 0: + total output bytes = 162700 + sample count = 20 + format 0: + id = 1 + sampleMimeType = audio/ac4 + maxInputSize = 8158 + channelCount = 21 + sampleRate = 48000 + language = und + metadata = entries=[Mp4Timestamp: creation time=3785281997, modification time=3785281997, timescale=600] + sample 0: + time = 0 + flags = 1 + data = length 8135, hash B524F88E + sample 1: + time = 42666 + flags = 0 + data = length 8135, hash FB80C2FB + sample 2: + time = 85333 + flags = 0 + data = length 8135, hash 907C0C31 + sample 3: + time = 128000 + flags = 0 + data = length 8135, hash FDFBD32B + sample 4: + time = 170666 + flags = 0 + data = length 8135, hash 6CAF0549 + sample 5: + time = 213333 + flags = 0 + data = length 8135, hash F5CA1C9A + sample 6: + time = 256000 + flags = 0 + data = length 8135, hash B1B5160D + sample 7: + time = 298666 + flags = 0 + data = length 8135, hash 9E923B3F + sample 8: + time = 341333 + flags = 0 + data = length 8135, hash B1C0BB1F + sample 9: + time = 384000 + flags = 0 + data = length 8135, hash 56F65A03 + sample 10: + time = 426666 + flags = 1 + data = length 8135, hash D07FA9A1 + sample 11: + time = 469333 + flags = 0 + data = length 8135, hash EF26FDDE + sample 12: + time = 512000 + flags = 0 + data = length 8135, hash 8946EEEB + sample 13: + time = 554666 + flags = 0 + data = length 8135, hash AC2E4C99 + sample 14: + time = 597333 + flags = 0 + data = length 8135, hash B63A1D8 + sample 15: + time = 640000 + flags = 0 + data = length 8135, hash 23119F0F + sample 16: + time = 682666 + flags = 0 + data = length 8135, hash 507972CA + sample 17: + time = 725333 + flags = 0 + data = length 8135, hash E574BC00 + sample 18: + time = 768000 + flags = 0 + data = length 8135, hash 52F482FA + sample 19: + time = 810666 + flags = 536870912 + data = length 8135, hash C1A7B518 +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.2.dump new file mode 100644 index 00000000000..5f9737eed8b --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.2.dump @@ -0,0 +1,60 @@ +seekMap: + isSeekable = true + duration = 853333 + getPosition(0) = [[timeUs=0, position=665]] + getPosition(1) = [[timeUs=1, position=665]] + getPosition(426666) = [[timeUs=426666, position=81945]] + getPosition(853333) = [[timeUs=853333, position=81945]] +numberOfTracks = 1 +track 0: + total output bytes = 81350 + sample count = 10 + format 0: + id = 1 + sampleMimeType = audio/ac4 + maxInputSize = 8158 + channelCount = 21 + sampleRate = 48000 + language = und + metadata = entries=[Mp4Timestamp: creation time=3785281997, modification time=3785281997, timescale=600] + sample 0: + time = 426666 + flags = 1 + data = length 8135, hash D07FA9A1 + sample 1: + time = 469333 + flags = 0 + data = length 8135, hash EF26FDDE + sample 2: + time = 512000 + flags = 0 + data = length 8135, hash 8946EEEB + sample 3: + time = 554666 + flags = 0 + data = length 8135, hash AC2E4C99 + sample 4: + time = 597333 + flags = 0 + data = length 8135, hash B63A1D8 + sample 5: + time = 640000 + flags = 0 + data = length 8135, hash 23119F0F + sample 6: + time = 682666 + flags = 0 + data = length 8135, hash 507972CA + sample 7: + time = 725333 + flags = 0 + data = length 8135, hash E574BC00 + sample 8: + time = 768000 + flags = 0 + data = length 8135, hash 52F482FA + sample 9: + time = 810666 + flags = 536870912 + data = length 8135, hash C1A7B518 +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.3.dump new file mode 100644 index 00000000000..5f9737eed8b --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.3.dump @@ -0,0 +1,60 @@ +seekMap: + isSeekable = true + duration = 853333 + getPosition(0) = [[timeUs=0, position=665]] + getPosition(1) = [[timeUs=1, position=665]] + getPosition(426666) = [[timeUs=426666, position=81945]] + getPosition(853333) = [[timeUs=853333, position=81945]] +numberOfTracks = 1 +track 0: + total output bytes = 81350 + sample count = 10 + format 0: + id = 1 + sampleMimeType = audio/ac4 + maxInputSize = 8158 + channelCount = 21 + sampleRate = 48000 + language = und + metadata = entries=[Mp4Timestamp: creation time=3785281997, modification time=3785281997, timescale=600] + sample 0: + time = 426666 + flags = 1 + data = length 8135, hash D07FA9A1 + sample 1: + time = 469333 + flags = 0 + data = length 8135, hash EF26FDDE + sample 2: + time = 512000 + flags = 0 + data = length 8135, hash 8946EEEB + sample 3: + time = 554666 + flags = 0 + data = length 8135, hash AC2E4C99 + sample 4: + time = 597333 + flags = 0 + data = length 8135, hash B63A1D8 + sample 5: + time = 640000 + flags = 0 + data = length 8135, hash 23119F0F + sample 6: + time = 682666 + flags = 0 + data = length 8135, hash 507972CA + sample 7: + time = 725333 + flags = 0 + data = length 8135, hash E574BC00 + sample 8: + time = 768000 + flags = 0 + data = length 8135, hash 52F482FA + sample 9: + time = 810666 + flags = 536870912 + data = length 8135, hash C1A7B518 +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.unknown_length.dump new file mode 100644 index 00000000000..90cd9d402cc --- /dev/null +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_ac4_level4.mp4.unknown_length.dump @@ -0,0 +1,100 @@ +seekMap: + isSeekable = true + duration = 853333 + getPosition(0) = [[timeUs=0, position=665]] + getPosition(1) = [[timeUs=1, position=665]] + getPosition(426666) = [[timeUs=426666, position=81945]] + getPosition(853333) = [[timeUs=853333, position=81945]] +numberOfTracks = 1 +track 0: + total output bytes = 162700 + sample count = 20 + format 0: + id = 1 + sampleMimeType = audio/ac4 + maxInputSize = 8158 + channelCount = 21 + sampleRate = 48000 + language = und + metadata = entries=[Mp4Timestamp: creation time=3785281997, modification time=3785281997, timescale=600] + sample 0: + time = 0 + flags = 1 + data = length 8135, hash B524F88E + sample 1: + time = 42666 + flags = 0 + data = length 8135, hash FB80C2FB + sample 2: + time = 85333 + flags = 0 + data = length 8135, hash 907C0C31 + sample 3: + time = 128000 + flags = 0 + data = length 8135, hash FDFBD32B + sample 4: + time = 170666 + flags = 0 + data = length 8135, hash 6CAF0549 + sample 5: + time = 213333 + flags = 0 + data = length 8135, hash F5CA1C9A + sample 6: + time = 256000 + flags = 0 + data = length 8135, hash B1B5160D + sample 7: + time = 298666 + flags = 0 + data = length 8135, hash 9E923B3F + sample 8: + time = 341333 + flags = 0 + data = length 8135, hash B1C0BB1F + sample 9: + time = 384000 + flags = 0 + data = length 8135, hash 56F65A03 + sample 10: + time = 426666 + flags = 1 + data = length 8135, hash D07FA9A1 + sample 11: + time = 469333 + flags = 0 + data = length 8135, hash EF26FDDE + sample 12: + time = 512000 + flags = 0 + data = length 8135, hash 8946EEEB + sample 13: + time = 554666 + flags = 0 + data = length 8135, hash AC2E4C99 + sample 14: + time = 597333 + flags = 0 + data = length 8135, hash B63A1D8 + sample 15: + time = 640000 + flags = 0 + data = length 8135, hash 23119F0F + sample 16: + time = 682666 + flags = 0 + data = length 8135, hash 507972CA + sample 17: + time = 725333 + flags = 0 + data = length 8135, hash E574BC00 + sample 18: + time = 768000 + flags = 0 + data = length 8135, hash 52F482FA + sample 19: + time = 810666 + flags = 536870912 + data = length 8135, hash C1A7B518 +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/media/mp4/sample_ac4_level4.mp4 b/libraries/test_data/src/test/assets/media/mp4/sample_ac4_level4.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..7e110d301e7b23a3ca0fcea8efdaca51d2d20ad1 GIT binary patch literal 163225 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 literal 0 HcmV?d00001 From 78964d0a90b06c5f7405664877a06ba523219a95 Mon Sep 17 00:00:00 2001 From: ybai001 Date: Thu, 11 Apr 2024 12:09:51 +0800 Subject: [PATCH 5/6] Move AC-4 Level 4 test case to new location --- .../mp4/Mp4ExtractorParameterizedTest.java | 8 + .../extractor/mp4/Mp4ExtractorTest.java | 149 ------------------ 2 files changed, 8 insertions(+), 149 deletions(-) delete mode 100644 libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorParameterizedTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorParameterizedTest.java index 367d2cb8102..7321b27c99d 100644 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorParameterizedTest.java +++ b/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorParameterizedTest.java @@ -92,6 +92,14 @@ public void mp4SampleWithAc4Track() throws Exception { simulationConfig); } + @Test + public void mp4SampleWithAc4Level4Track() throws Exception { + ExtractorAsserts.assertBehavior( + getExtractorFactory(subtitlesParsedDuringExtraction), + "media/mp4/sample_ac4_level4.mp4", + simulationConfig); + } + @Test public void mp4SampleWithEac3Track() throws Exception { ExtractorAsserts.assertBehavior( diff --git a/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java b/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java deleted file mode 100644 index f4f0f1132b5..00000000000 --- a/libraries/extractor/src/test/java/androidx/media3/extractor/mp4/Mp4ExtractorTest.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package androidx.media3.extractor.mp4; - -import androidx.media3.test.utils.ExtractorAsserts; -import com.google.common.collect.ImmutableList; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.ParameterizedRobolectricTestRunner; -import org.robolectric.ParameterizedRobolectricTestRunner.Parameter; -import org.robolectric.ParameterizedRobolectricTestRunner.Parameters; - -/** Tests for {@link Mp4Extractor}. */ -@RunWith(ParameterizedRobolectricTestRunner.class) -public final class Mp4ExtractorTest { - - @Parameters(name = "{0}") - public static ImmutableList params() { - return ExtractorAsserts.configs(); - } - - @Parameter public ExtractorAsserts.SimulationConfig simulationConfig; - - @Test - public void mp4Sample() throws Exception { - ExtractorAsserts.assertBehavior(Mp4Extractor::new, "media/mp4/sample.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithSlowMotionMetadata() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_android_slow_motion.mp4", simulationConfig); - } - - /** - * Test case for https://github.com/google/ExoPlayer/issues/6774. The sample file contains an mdat - * atom whose size indicates that it extends 8 bytes beyond the end of the file. - */ - @Test - public void mp4SampleWithMdatTooLong() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_mdat_too_long.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithAc3Track() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_ac3.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithAc4Track() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_ac4.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithAc4Level4Track() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_ac4_level4.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithEac3Track() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_eac3.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithEac3jocTrack() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_eac3joc.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithOpusTrack() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_opus.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithMha1Track() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_mpegh_mha1.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithMhm1Track() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_mpegh_mhm1.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithColorInfo() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_with_color_info.mp4", simulationConfig); - } - - /** - * Test case for https://github.com/google/ExoPlayer/issues/9332. The file contains a colr box - * with size=18 and type=nclx. This is not valid according to the spec (size must be 19), but - * files like this exist in the wild. - */ - @Test - public void mp4Sample18ByteNclxColr() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_18byte_nclx_colr.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithDolbyTrueHDTrack() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_dthd.mp4", simulationConfig); - } - - @Test - public void mp4SampleWithColrMdcvAndClli() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_with_colr_mdcv_and_clli.mp4", simulationConfig); - } - - /** Test case for supporting original QuickTime specification [Internal: b/297137302]. */ - @Test - public void mp4SampleWithOriginalQuicktimeSpecification() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, - "media/mp4/sample_with_original_quicktime_specification.mov", - simulationConfig); - } - - @Test - public void mp4SampleWithAv1c() throws Exception { - ExtractorAsserts.assertBehavior( - Mp4Extractor::new, "media/mp4/sample_with_av1c.mp4", simulationConfig); - } -} From 3b2f1acc6ddc764333c9a68f0b8fad4587b6418b Mon Sep 17 00:00:00 2001 From: ybai001 Date: Thu, 9 May 2024 16:21:00 +0800 Subject: [PATCH 6/6] Parse the first presentation only --- .../androidx/media3/extractor/Ac4Util.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java index fa3c66d1631..e0b0e80e2e5 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/Ac4Util.java @@ -214,7 +214,6 @@ public static Format parseAc4AnnexEFormat( throws ParserException { ParsableBitArray dataBitArray = new ParsableBitArray(); dataBitArray.reset(data); - Map ac4Presentations = new HashMap<>(); long dsiSize = dataBitArray.bitsLeft(); int ac4DsiVersion = dataBitArray.readBits(3); // ac4_dsi_version @@ -251,8 +250,10 @@ public static Format parseAc4AnnexEFormat( dataBitArray.byteAlign(); } - for (int presentationIndex = 0; presentationIndex < nPresentations; presentationIndex++) { - Ac4Presentation ac4Presentation = new Ac4Presentation(); + // Just parse the first presentation (default presentation) + int presentationIndex = 0; + Ac4Presentation ac4Presentation = new Ac4Presentation(); + do { ac4Presentation.programID = shortProgramId; // known as b_single_substream in ac4_dsi_version 0 boolean bSingleSubstreamGroup = false; @@ -273,7 +274,6 @@ public static Format parseAc4AnnexEFormat( } if (presentationVersion > 2) { dataBitArray.skipBits(presBytes * 8); - ac4Presentations.put(presentationIndex, ac4Presentation); continue; } // record a marker, less the size of the presentation_config @@ -420,7 +420,7 @@ public static Format parseAc4AnnexEFormat( + presentationIndex + ", substream group ID = " + substreamGroupID); } } - } + } break; case 5: if (presentationVersion == 0) { @@ -509,15 +509,9 @@ public static Format parseAc4AnnexEFormat( throw ParserException.createForUnsupportedContainerFeature( "Can't determine channel mode of presentation " + presentationIndex); } - - ac4Presentations.put(presentationIndex, ac4Presentation); - } + } while (false); 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);