From dc05b1147ab8f853ce471c1928da4c627b89f74b Mon Sep 17 00:00:00 2001 From: Rex Jin Date: Thu, 2 Jul 2026 11:26:57 -0700 Subject: [PATCH 1/8] Add E-AC-3 and Dolby Atmos (E-AC-3 JOC) support in extractor and muxer Introduce FormatSpecificTransmuxingData, a typed Metadata.Entry that carries a raw MP4 box payload (the box body, without header) so it can be reproduced when transmuxing without re-encoding. Extractor: when parsing the MP4 dec3 (EC3SpecificBox), attach its payload to Format.metadata as a FormatSpecificTransmuxingData("dec3", ...) entry instead of Format.initializationData. This keeps E-AC-3's decode path unchanged (no csd-0 is injected) while preserving the bytes for muxing. Muxer: implement the dec3 box and ec-3 fourcc in Boxes.java, reading the payload from the FormatSpecificTransmuxingData metadata entry, and add AUDIO_E_AC3 / AUDIO_E_AC3_JOC to the supported audio MIME types in Mp4Muxer and FragmentedMp4Muxer. The default Transformer can now stream-copy (transmux) E-AC-3 / Dolby Atmos audio. Tests: - BoxesTest: dec3 box wrapping for AUDIO_E_AC3 and AUDIO_E_AC3_JOC, the ec-3 fourcc, and guards for missing / empty dec3 payload. - Mp4MuxerEndToEndTest and FragmentedMp4MuxerEndToEndTest: Mp4Extractor- driven transmux of sample_eac3.mp4 and sample_eac3joc.mp4 (reads via Mp4Extractor so Format.metadata is preserved). - Extractor golden dumps regenerated to carry the dec3 metadata entry. --- .../FormatSpecificTransmuxingData.java | 72 +++++ .../media3/extractor/mp4/BoxParser.java | 19 +- .../java/androidx/media3/muxer/Boxes.java | 44 +++ .../media3/muxer/FragmentedMp4Muxer.java | 6 +- .../java/androidx/media3/muxer/Mp4Muxer.java | 4 + .../java/androidx/media3/muxer/BoxesTest.java | 87 ++++++ .../muxer/FragmentedMp4MuxerEndToEndTest.java | 64 ++++ .../media3/muxer/Mp4MuxerEndToEndTest.java | 56 ++++ .../extractordumps/mp4/sample_eac3.mp4.0.dump | 2 +- .../extractordumps/mp4/sample_eac3.mp4.1.dump | 2 +- .../extractordumps/mp4/sample_eac3.mp4.2.dump | 2 +- .../extractordumps/mp4/sample_eac3.mp4.3.dump | 2 +- .../mp4/sample_eac3.mp4.unknown_length.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.0.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.1.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.2.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.3.dump | 2 +- ...le_eac3_fragmented.mp4.unknown_length.dump | 2 +- .../mp4/sample_eac3joc.mp4.0.dump | 2 +- .../mp4/sample_eac3joc.mp4.1.dump | 2 +- .../mp4/sample_eac3joc.mp4.2.dump | 2 +- .../mp4/sample_eac3joc.mp4.3.dump | 2 +- .../sample_eac3joc.mp4.unknown_length.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.0.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.1.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.2.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.3.dump | 2 +- ...eac3joc_fragmented.mp4.unknown_length.dump | 2 +- .../muxerdumps/mp4/sample_eac3.mp4.dump | 240 +++++++++++++++ .../mp4/sample_eac3.mp4_fragmented.dump | 234 +++++++++++++++ .../muxerdumps/mp4/sample_eac3joc.mp4.dump | 280 ++++++++++++++++++ .../mp4/sample_eac3joc.mp4_fragmented.dump | 274 +++++++++++++++++ 32 files changed, 1398 insertions(+), 22 deletions(-) create mode 100644 libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java create mode 100644 libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump create mode 100644 libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump create mode 100644 libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump create mode 100644 libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump diff --git a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java new file mode 100644 index 00000000000..abd1f66325f --- /dev/null +++ b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java @@ -0,0 +1,72 @@ +/* + * Copyright 2024 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.container; + +import androidx.annotation.Nullable; +import androidx.media3.common.Metadata; +import androidx.media3.common.util.UnstableApi; +import androidx.media3.common.util.Util; +import java.util.Arrays; + +/** + * Stores the raw payload of a format-specific box so it can be re-written during transmuxing (stream + * copy) without re-encoding. + * + *

Some codecs store codec configuration in a container box whose bytes are not otherwise exposed + * on {@link androidx.media3.common.Format} (for example the E-AC-3 {@code dec3} / EC3SpecificBox). + * The extractor preserves that box's payload here so a muxer can reproduce the box verbatim. + * + *

The {@link #data} holds the box body only, i.e. excluding the 4-byte size and 4-byte type + * header, and {@link #boxType} identifies which box the payload belongs to (for example {@code + * dec3}). + */ +@UnstableApi +public final class FormatSpecificTransmuxingData implements Metadata.Entry { + + /** The four-character type of the box the {@link #data} belongs to, for example {@code dec3}. */ + public final String boxType; + + /** The raw box payload (the box body, excluding its 4-byte size and 4-byte type header). */ + public final byte[] data; + + /** Creates an instance. */ + public FormatSpecificTransmuxingData(String boxType, byte[] data) { + this.boxType = boxType; + this.data = data; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + FormatSpecificTransmuxingData other = (FormatSpecificTransmuxingData) obj; + return boxType.equals(other.boxType) && Arrays.equals(data, other.data); + } + + @Override + public int hashCode() { + return 31 * boxType.hashCode() + Arrays.hashCode(data); + } + + @Override + public String toString() { + return "FormatSpecificTransmuxingData: boxType=" + boxType; + } +} diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java index c994e35a441..f20e79928a7 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java @@ -41,6 +41,7 @@ import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.container.DolbyVisionConfig; +import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.Mp4AlternateGroupData; import androidx.media3.container.Mp4Box; import androidx.media3.container.Mp4Box.LeafBox; @@ -2251,9 +2252,25 @@ private static void parseAudioSampleEntry( out.format = Ac3Util.parseAc3AnnexFFormat(parent, Integer.toString(trackId), language, drmInitData); } else if (childAtomType == Mp4Box.TYPE_dec3) { + // Preserve the raw EC3SpecificBox body as format-specific transmuxing metadata so that + // downstream components (e.g. the muxer for stream-copy) can rewrite the dec3 box. + int childAtomBodySize = childAtomSize - Mp4Box.HEADER_SIZE; + byte[] ec3SpecificBoxPayload = new byte[childAtomBodySize]; parent.setPosition(Mp4Box.HEADER_SIZE + childPosition); - out.format = + parent.readBytes(ec3SpecificBoxPayload, /* offset= */ 0, childAtomBodySize); + parent.setPosition(Mp4Box.HEADER_SIZE + childPosition); + Format eac3Format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language, drmInitData); + Metadata.Entry dec3TransmuxingData = + new FormatSpecificTransmuxingData("dec3", ec3SpecificBoxPayload); + out.format = + eac3Format + .buildUpon() + .setMetadata( + eac3Format.metadata != null + ? eac3Format.metadata.copyWithAppendedEntries(dec3TransmuxingData) + : new Metadata(dec3TransmuxingData)) + .build(); } else if (childAtomType == Mp4Box.TYPE_dac4) { parent.setPosition(Mp4Box.HEADER_SIZE + childPosition); out.format = diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java index c60ad3a19f0..4f659b005bd 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -30,10 +30,12 @@ import androidx.media3.common.C; import androidx.media3.common.ColorInfo; import androidx.media3.common.Format; +import androidx.media3.common.Metadata; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.CodecSpecificDataUtil; import androidx.media3.common.util.Log; import androidx.media3.common.util.Util; +import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.Mp4LocationData; import androidx.media3.container.NalUnitUtil; @@ -781,6 +783,9 @@ public static ByteBuffer codecSpecificBox(Format format) { return dOpsBox(format); case MimeTypes.AUDIO_IAMF: return iacbBox(format); + case MimeTypes.AUDIO_E_AC3: + case MimeTypes.AUDIO_E_AC3_JOC: + return dec3Box(format); case MimeTypes.AUDIO_RAW: return ByteBuffer.allocate(0); // No codec specific box for raw audio. case MimeTypes.VIDEO_H263: @@ -1643,6 +1648,42 @@ private static ByteBuffer av1CBox(Format format) { return BoxUtils.wrapIntoBox("av1C", ByteBuffer.wrap(csd0)); } + /** + * Returns the dec3 box (EC3SpecificBox) for E-AC-3 / E-AC-3 JOC (Dolby Atmos) audio. + * + *

The dec3 box body is carried verbatim in a {@link FormatSpecificTransmuxingData} entry (with + * {@linkplain FormatSpecificTransmuxingData#boxType boxType} {@code "dec3"}) on {@link + * Format#metadata}. Stream-copy callers must populate it with the source track's raw dec3 payload; + * the MP4 extractor populates this from the source {@code dec3} box. + */ + private static ByteBuffer dec3Box(Format format) { + byte[] dec3Payload = getTransmuxingBoxPayload(format, "dec3"); + checkArgument( + dec3Payload != null && dec3Payload.length > 0, "dec3 payload not found for dec3 box."); + return BoxUtils.wrapIntoBox("dec3", ByteBuffer.wrap(dec3Payload)); + } + + /** + * Returns the raw payload of the {@link FormatSpecificTransmuxingData} entry on {@code + * format.metadata} whose {@linkplain FormatSpecificTransmuxingData#boxType boxType} matches {@code + * boxType}, or {@code null} if there is no such entry. + */ + @Nullable + private static byte[] getTransmuxingBoxPayload(Format format, String boxType) { + @Nullable Metadata metadata = format.metadata; + if (metadata == null) { + return null; + } + for (int i = 0; i < metadata.length(); i++) { + Metadata.Entry entry = metadata.get(i); + if (entry instanceof FormatSpecificTransmuxingData + && ((FormatSpecificTransmuxingData) entry).boxType.equals(boxType)) { + return ((FormatSpecificTransmuxingData) entry).data; + } + } + return null; + } + /** Returns a dvcC/dvwC/dvvC vision box which will be included in dolby vision box. */ private static ByteBuffer doviBox(int profile, byte[] csd) { checkArgument(csd.length > 0, "csd is empty for dovi box."); @@ -1847,6 +1888,9 @@ private static String codecSpecificFourcc(Format format) { return "Opus"; case MimeTypes.AUDIO_IAMF: return "iamf"; + case MimeTypes.AUDIO_E_AC3: + case MimeTypes.AUDIO_E_AC3_JOC: + return "ec-3"; case MimeTypes.AUDIO_RAW: if (format.pcmEncoding == C.ENCODING_PCM_16BIT) { return "sowt"; diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java index f64e1f3da53..cca077012ee 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Muxer.java @@ -62,6 +62,8 @@ *

  • Opus *
  • Vorbis *
  • Raw Audio + *
  • E-AC-3 (Dolby Digital Plus) + *
  • E-AC-3 JOC (Dolby Atmos) * *
  • Metadata * @@ -180,7 +182,9 @@ public FragmentedMp4Muxer build() { MimeTypes.AUDIO_IAMF, MimeTypes.AUDIO_OPUS, MimeTypes.AUDIO_VORBIS, - MimeTypes.AUDIO_RAW); + MimeTypes.AUDIO_RAW, + MimeTypes.AUDIO_E_AC3, + MimeTypes.AUDIO_E_AC3_JOC); // LINT.ThenChange(Boxes.java:codec_specific_boxes) diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java index ee0421739e8..15062d108e8 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Mp4Muxer.java @@ -84,6 +84,8 @@ *
  • Opus *
  • Vorbis *
  • Raw Audio + *
  • E-AC-3 (Dolby Digital Plus) + *
  • E-AC-3 JOC (Dolby Atmos) * *
  • Metadata * @@ -414,6 +416,8 @@ public Mp4Muxer build() { MimeTypes.AUDIO_OPUS, MimeTypes.AUDIO_VORBIS, MimeTypes.AUDIO_RAW, + MimeTypes.AUDIO_E_AC3, + MimeTypes.AUDIO_E_AC3_JOC, MimeTypes.AUDIO_IAMF); // LINT.ThenChange(Boxes.java:codec_specific_boxes) diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java index 1f4e414f19f..a936963a8c2 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java @@ -29,8 +29,10 @@ import androidx.media3.common.C; import androidx.media3.common.ColorInfo; import androidx.media3.common.Format; +import androidx.media3.common.Metadata; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Util; +import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.Mp4LocationData; import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata; @@ -403,6 +405,91 @@ public void createAudioSampleEntryBox_forVorbis_matchesExpected() throws Excepti MuxerTestUtil.getExpectedMp4DumpFilePath("audio_sample_entry_box_vorbis")); } + @Test + public void createCodecSpecificBox_forEAc3_wrapsDec3PayloadVerbatim() { + byte[] payload = new byte[] {0x0F, (byte) 0x80, 0x00}; + Format format = + FAKE_AUDIO_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_E_AC3) + .setMetadata(new Metadata(new FormatSpecificTransmuxingData("dec3", payload))) + .build(); + + ByteBuffer box = Boxes.codecSpecificBox(format); + + // Box layout: 4 bytes size + 4 bytes "dec3" type + payload bytes. + assertThat(box.remaining()).isEqualTo(8 + payload.length); + byte[] typeBytes = new byte[4]; + box.position(4); + box.get(typeBytes); + assertThat(Util.fromUtf8Bytes(typeBytes)).isEqualTo("dec3"); + byte[] actual = new byte[payload.length]; + box.get(actual); + assertThat(actual).isEqualTo(payload); + } + + @Test + public void createCodecSpecificBox_forEAc3Joc_wrapsDec3PayloadVerbatim() { + byte[] payload = new byte[] {0x0F, (byte) 0x80, 0x00}; + Format format = + FAKE_AUDIO_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_E_AC3_JOC) + .setMetadata(new Metadata(new FormatSpecificTransmuxingData("dec3", payload))) + .build(); + + ByteBuffer box = Boxes.codecSpecificBox(format); + + assertThat(box.remaining()).isEqualTo(8 + payload.length); + byte[] typeBytes = new byte[4]; + box.position(4); + box.get(typeBytes); + assertThat(Util.fromUtf8Bytes(typeBytes)).isEqualTo("dec3"); + byte[] actual = new byte[payload.length]; + box.get(actual); + assertThat(actual).isEqualTo(payload); + } + + @Test + public void createCodecSpecificBox_forEAc3WithNoTransmuxingData_throws() { + // No FormatSpecificTransmuxingData entry on the format's metadata. + Format format = FAKE_AUDIO_FORMAT.buildUpon().setSampleMimeType(MimeTypes.AUDIO_E_AC3).build(); + + assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format)); + } + + @Test + public void createCodecSpecificBox_forEAc3WithEmptyPayload_throws() { + Format format = + FAKE_AUDIO_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_E_AC3) + .setMetadata(new Metadata(new FormatSpecificTransmuxingData("dec3", new byte[0]))) + .build(); + + assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format)); + } + + @Test + public void createAudioSampleEntryBox_forEAc3_usesEc3Fourcc() { + Format format = + FAKE_AUDIO_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_E_AC3) + .setMetadata( + new Metadata( + new FormatSpecificTransmuxingData("dec3", new byte[] {0x0F, (byte) 0x80, 0x00}))) + .build(); + + ByteBuffer box = Boxes.audioSampleEntry(format); + + // Sample entry box layout: 4 bytes size + 4 bytes fourcc + ... + byte[] fourccBytes = new byte[4]; + box.position(4); + box.get(fourccBytes); + assertThat(Util.fromUtf8Bytes(fourccBytes)).isEqualTo("ec-3"); + } + @Test public void createAudioSampleEntryBox_withUnknownAudioFormat_throws() { // The audio format contains an unknown MIME type. diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java index bbac80f42f0..bde4fca3f62 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java @@ -19,17 +19,21 @@ import static com.google.common.base.Preconditions.checkNotNull; import android.content.Context; +import androidx.media3.common.C; import androidx.media3.common.Format; import androidx.media3.common.MimeTypes; import androidx.media3.container.Mp4TimestampData; import androidx.media3.extractor.mp4.FragmentedMp4Extractor; +import androidx.media3.extractor.mp4.Mp4Extractor; import androidx.media3.extractor.text.DefaultSubtitleParserFactory; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpableMp4Box; import androidx.media3.test.utils.FakeExtractorOutput; +import androidx.media3.test.utils.FakeTrackOutput; import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.google.common.collect.Iterables; import java.io.FileOutputStream; import java.nio.ByteBuffer; import org.junit.Rule; @@ -243,4 +247,64 @@ public void createFragmentedMp4File_withSomeUnknownTrack_writesAsTextMetadataTra dumpableMp4Box, MuxerTestUtil.getExpectedMp4DumpFilePath("fragmented_mp4_with_unknown_track.mp4")); } + + @Test + public void writeFragmentedMp4File_fromEAc3Input_matchesExpected() throws Exception { + transmuxToFragmentedAndAssert(/* inputFileName= */ "mp4/sample_eac3.mp4"); + } + + @Test + public void writeFragmentedMp4File_fromEAc3JocInput_matchesExpected() throws Exception { + transmuxToFragmentedAndAssert(/* inputFileName= */ "mp4/sample_eac3joc.mp4"); + } + + /** + * Stream-copies an MP4 audio track through {@link FragmentedMp4Muxer} and asserts the re-extracted + * output against the expected dump. + * + *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so that + * {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code + * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. + */ + private void transmuxToFragmentedAndAssert(String inputFileName) throws Exception { + FakeExtractorOutput inputExtractorOutput = + TestUtil.extractAllSamplesFromFile(new Mp4Extractor(), context, "media/" + inputFileName); + FakeTrackOutput audioTrackOutput = + Iterables.getOnlyElement(inputExtractorOutput.getTrackOutputsForType(C.TRACK_TYPE_AUDIO)); + Format audioFormat = checkNotNull(audioTrackOutput.lastFormat); + String outputFilePath = temporaryFolder.newFile().getPath(); + + try (FragmentedMp4Muxer muxer = + new FragmentedMp4Muxer.Builder(new FileOutputStream(outputFilePath).getChannel()).build()) { + // Use fixed timestamps so the output (and its dump) is deterministic. + muxer.addMetadataEntry( + new Mp4TimestampData( + /* creationTimestampSeconds= */ 100_000_000L, + /* modificationTimestampSeconds= */ 500_000_000L)); + int trackId = muxer.addTrack(audioFormat); + for (int i = 0; i < audioTrackOutput.getSampleCount(); i++) { + byte[] sampleData = audioTrackOutput.getSampleData(i); + ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleData.length); + sampleBuffer.put(sampleData); + sampleBuffer.rewind(); + muxer.writeSampleData( + trackId, + sampleBuffer, + new BufferInfo( + audioTrackOutput.getSampleTimeUs(i), + sampleData.length, + audioTrackOutput.getSampleFlags(i))); + } + } + + FakeExtractorOutput outputExtractorOutput = + TestUtil.extractAllSamplesFromFilePath( + new FragmentedMp4Extractor(new DefaultSubtitleParserFactory()), + checkNotNull(outputFilePath)); + DumpFileAsserts.assertOutput( + context, + outputExtractorOutput, + MuxerTestUtil.getExpectedDumpFilePath( + MuxerTestUtil.getSubstitutedPath(inputFileName, MuxerTestUtil.MP4) + "_fragmented")); + } } diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java index 6a84a18d973..b48be60c1d7 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java @@ -43,6 +43,7 @@ import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpableMp4Box; import androidx.media3.test.utils.FakeExtractorOutput; +import androidx.media3.test.utils.FakeTrackOutput; import androidx.media3.test.utils.TestUtil; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -105,6 +106,61 @@ public void writeMp4File_withSampleAndMetadata_matchedExpectedBoxStructure() thr MuxerTestUtil.getExpectedMp4DumpFilePath("mp4_with_samples_and_metadata.mp4")); } + @Test + public void writeMp4File_fromEAc3Input_matchesExpected() throws Exception { + transmuxAndAssert(/* inputFileName= */ "sample_eac3.mp4"); + } + + @Test + public void writeMp4File_fromEAc3JocInput_matchesExpected() throws Exception { + transmuxAndAssert(/* inputFileName= */ "sample_eac3joc.mp4"); + } + + /** + * Stream-copies an MP4 audio track through {@link Mp4Muxer} and asserts the re-extracted output + * against the expected dump. + * + *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so that + * {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code + * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. + */ + private void transmuxAndAssert(String inputFileName) throws Exception { + FakeExtractorOutput inputExtractorOutput = + TestUtil.extractAllSamplesFromFile( + new Mp4Extractor(), context, "media/mp4/" + inputFileName); + FakeTrackOutput audioTrackOutput = + Iterables.getOnlyElement(inputExtractorOutput.getTrackOutputsForType(C.TRACK_TYPE_AUDIO)); + Format audioFormat = checkNotNull(audioTrackOutput.lastFormat); + String outputFilePath = temporaryFolder.newFile().getPath(); + + try (Mp4Muxer muxer = new Mp4Muxer.Builder(SeekableMuxerOutput.of(outputFilePath)).build()) { + // Use fixed timestamps so the output (and its dump) is deterministic. + muxer.addMetadataEntry( + new Mp4TimestampData( + /* creationTimestampSeconds= */ 100_000_000L, + /* modificationTimestampSeconds= */ 500_000_000L)); + int trackId = muxer.addTrack(audioFormat); + for (int i = 0; i < audioTrackOutput.getSampleCount(); i++) { + byte[] sampleData = audioTrackOutput.getSampleData(i); + ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleData.length); + sampleBuffer.put(sampleData); + sampleBuffer.rewind(); + muxer.writeSampleData( + trackId, + sampleBuffer, + new BufferInfo( + audioTrackOutput.getSampleTimeUs(i), + sampleData.length, + audioTrackOutput.getSampleFlags(i))); + } + } + + FakeExtractorOutput outputExtractorOutput = + TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), outputFilePath); + DumpFileAsserts.assertOutput( + context, outputExtractorOutput, MuxerTestUtil.getExpectedMp4DumpFilePath(inputFileName)); + } + @Test public void createMp4File_addTrackAndMetadataButNoSamples_createsEmptyFile() throws Exception { String outputFilePath = temporaryFolder.newFile().getPath(); diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump index 18be5b4841c..06e031c84f5 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump index 5553d6aacc5..36641422acb 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 576000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump index 70065cd5b70..6baa2a18e81 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 1152000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump index debb18e351b..43ab7b846d3 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 1696000 flags = 536870913 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump index 18be5b4841c..06e031c84f5 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump index d667a5b6bd3..b928c6b22d1 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump index b2fe4e15817..b81b87b36f5 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 576000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump index 9b79dddd878..a68a51bd1ec 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 1152000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump index e05add6a366..029b35752cc 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 1696000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump index d667a5b6bd3..b928c6b22d1 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump index 722eacf3946..3d07a8f8b0f 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump index 0c4e9324c26..a823a6612f3 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 672000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump index 8391f766571..5027d42ea81 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 1344000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump index adac41cf3e0..96322d89825 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 2016000 flags = 536870913 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump index 722eacf3946..3d07a8f8b0f 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump index 37ca0ee67ae..4e4c63626d3 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump index a95b4b0033a..f7505977d74 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 672000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump index 3f5ff4f7381..f95bb5ccffd 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 1344000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump index e4372fd8373..93c7963349b 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 2016000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump index 37ca0ee67ae..4e4c63626d3 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump new file mode 100644 index 00000000000..b42bd69b386 --- /dev/null +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump @@ -0,0 +1,240 @@ +seekMap: + isSeekable = true + duration = 1728000 + getPosition(0) = [[timeUs=0, position=400052]] + getPosition(1) = [[timeUs=1, position=400052]] + getPosition(864000) = [[timeUs=864000, position=508052]] + getPosition(1728000) = [[timeUs=1728000, position=612052]] +numberOfTracks = 1 +track 0: + total output bytes = 216000 + sample count = 54 + track duration = 1728000 + format 0: + averageBitrate = 1000000 + peakBitrate = 1000000 + id = 1 + containerMimeType = audio/mp4 + sampleMimeType = audio/eac3 + maxInputSize = 4030 + channelCount = 6 + sampleRate = 48000 + language = und + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + sample 0: + time = 0 + flags = 1 + data = length 4000, hash BAEAFB2A + sample 1: + time = 32000 + flags = 1 + data = length 4000, hash E3C5EBF0 + sample 2: + time = 64000 + flags = 1 + data = length 4000, hash 32E0F957 + sample 3: + time = 96000 + flags = 1 + data = length 4000, hash 5354CC5D + sample 4: + time = 128000 + flags = 1 + data = length 4000, hash FF834906 + sample 5: + time = 160000 + flags = 1 + data = length 4000, hash 6F571E61 + sample 6: + time = 192000 + flags = 1 + data = length 4000, hash 5C931F6B + sample 7: + time = 224000 + flags = 1 + data = length 4000, hash B1FB2E57 + sample 8: + time = 256000 + flags = 1 + data = length 4000, hash C71240EB + sample 9: + time = 288000 + flags = 1 + data = length 4000, hash C3E302EE + sample 10: + time = 320000 + flags = 1 + data = length 4000, hash 7994C27B + sample 11: + time = 352000 + flags = 1 + data = length 4000, hash 1ED4E6F3 + sample 12: + time = 384000 + flags = 1 + data = length 4000, hash 1D5E6AAC + sample 13: + time = 416000 + flags = 1 + data = length 4000, hash 30058F51 + sample 14: + time = 448000 + flags = 1 + data = length 4000, hash 15DD0E4A + sample 15: + time = 480000 + flags = 1 + data = length 4000, hash 37BE7C15 + sample 16: + time = 512000 + flags = 1 + data = length 4000, hash 7CFDD34B + sample 17: + time = 544000 + flags = 1 + data = length 4000, hash 27F20D29 + sample 18: + time = 576000 + flags = 1 + data = length 4000, hash 6F565894 + sample 19: + time = 608000 + flags = 1 + data = length 4000, hash A6F07C4A + sample 20: + time = 640000 + flags = 1 + data = length 4000, hash 3A0CA15C + sample 21: + time = 672000 + flags = 1 + data = length 4000, hash DB365414 + sample 22: + time = 704000 + flags = 1 + data = length 4000, hash 31E08469 + sample 23: + time = 736000 + flags = 1 + data = length 4000, hash 315F5C28 + sample 24: + time = 768000 + flags = 1 + data = length 4000, hash CC65DF80 + sample 25: + time = 800000 + flags = 1 + data = length 4000, hash 503FB64C + sample 26: + time = 832000 + flags = 1 + data = length 4000, hash 817CF735 + sample 27: + time = 864000 + flags = 1 + data = length 4000, hash 37391ADA + sample 28: + time = 896000 + flags = 1 + data = length 4000, hash 37391ADA + sample 29: + time = 928000 + flags = 1 + data = length 4000, hash 64DBF751 + sample 30: + time = 960000 + flags = 1 + data = length 4000, hash 81AE828E + sample 31: + time = 992000 + flags = 1 + data = length 4000, hash 767D6C98 + sample 32: + time = 1024000 + flags = 1 + data = length 4000, hash A5F6D4E + sample 33: + time = 1056000 + flags = 1 + data = length 4000, hash EABC6B0D + sample 34: + time = 1088000 + flags = 1 + data = length 4000, hash F47EF742 + sample 35: + time = 1120000 + flags = 1 + data = length 4000, hash 9B2549DA + sample 36: + time = 1152000 + flags = 1 + data = length 4000, hash A12733C9 + sample 37: + time = 1184000 + flags = 1 + data = length 4000, hash 95F62E99 + sample 38: + time = 1216000 + flags = 1 + data = length 4000, hash A4D858 + sample 39: + time = 1248000 + flags = 1 + data = length 4000, hash A4D858 + sample 40: + time = 1280000 + flags = 1 + data = length 4000, hash 22C1A129 + sample 41: + time = 1312000 + flags = 1 + data = length 4000, hash 2C51E4A1 + sample 42: + time = 1344000 + flags = 1 + data = length 4000, hash 3782E8BB + sample 43: + time = 1376000 + flags = 1 + data = length 4000, hash 2C51E4A1 + sample 44: + time = 1408000 + flags = 1 + data = length 4000, hash BDB3D129 + sample 45: + time = 1440000 + flags = 1 + data = length 4000, hash F642A55 + sample 46: + time = 1472000 + flags = 1 + data = length 4000, hash 32F259F4 + sample 47: + time = 1504000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 48: + time = 1536000 + flags = 1 + data = length 4000, hash 57C98E1C + sample 49: + time = 1568000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 50: + time = 1600000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 51: + time = 1632000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 52: + time = 1664000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 53: + time = 1696000 + flags = 536870913 + data = length 4000, hash 4C987B7C +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump new file mode 100644 index 00000000000..66ee04d96f2 --- /dev/null +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump @@ -0,0 +1,234 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = [[timeUs=0, position=569]] +numberOfTracks = 1 +track 0: + total output bytes = 216000 + sample count = 54 + format 0: + peakBitrate = 1000000 + id = 1 + containerMimeType = audio/mp4 + sampleMimeType = audio/eac3 + channelCount = 6 + sampleRate = 48000 + language = und + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + sample 0: + time = 0 + flags = 1 + data = length 4000, hash BAEAFB2A + sample 1: + time = 32000 + flags = 1 + data = length 4000, hash E3C5EBF0 + sample 2: + time = 64000 + flags = 1 + data = length 4000, hash 32E0F957 + sample 3: + time = 96000 + flags = 1 + data = length 4000, hash 5354CC5D + sample 4: + time = 128000 + flags = 1 + data = length 4000, hash FF834906 + sample 5: + time = 160000 + flags = 1 + data = length 4000, hash 6F571E61 + sample 6: + time = 192000 + flags = 1 + data = length 4000, hash 5C931F6B + sample 7: + time = 224000 + flags = 1 + data = length 4000, hash B1FB2E57 + sample 8: + time = 256000 + flags = 1 + data = length 4000, hash C71240EB + sample 9: + time = 288000 + flags = 1 + data = length 4000, hash C3E302EE + sample 10: + time = 320000 + flags = 1 + data = length 4000, hash 7994C27B + sample 11: + time = 352000 + flags = 1 + data = length 4000, hash 1ED4E6F3 + sample 12: + time = 384000 + flags = 1 + data = length 4000, hash 1D5E6AAC + sample 13: + time = 416000 + flags = 1 + data = length 4000, hash 30058F51 + sample 14: + time = 448000 + flags = 1 + data = length 4000, hash 15DD0E4A + sample 15: + time = 480000 + flags = 1 + data = length 4000, hash 37BE7C15 + sample 16: + time = 512000 + flags = 1 + data = length 4000, hash 7CFDD34B + sample 17: + time = 544000 + flags = 1 + data = length 4000, hash 27F20D29 + sample 18: + time = 576000 + flags = 1 + data = length 4000, hash 6F565894 + sample 19: + time = 608000 + flags = 1 + data = length 4000, hash A6F07C4A + sample 20: + time = 640000 + flags = 1 + data = length 4000, hash 3A0CA15C + sample 21: + time = 672000 + flags = 1 + data = length 4000, hash DB365414 + sample 22: + time = 704000 + flags = 1 + data = length 4000, hash 31E08469 + sample 23: + time = 736000 + flags = 1 + data = length 4000, hash 315F5C28 + sample 24: + time = 768000 + flags = 1 + data = length 4000, hash CC65DF80 + sample 25: + time = 800000 + flags = 1 + data = length 4000, hash 503FB64C + sample 26: + time = 832000 + flags = 1 + data = length 4000, hash 817CF735 + sample 27: + time = 864000 + flags = 1 + data = length 4000, hash 37391ADA + sample 28: + time = 896000 + flags = 1 + data = length 4000, hash 37391ADA + sample 29: + time = 928000 + flags = 1 + data = length 4000, hash 64DBF751 + sample 30: + time = 960000 + flags = 1 + data = length 4000, hash 81AE828E + sample 31: + time = 992000 + flags = 1 + data = length 4000, hash 767D6C98 + sample 32: + time = 1024000 + flags = 1 + data = length 4000, hash A5F6D4E + sample 33: + time = 1056000 + flags = 1 + data = length 4000, hash EABC6B0D + sample 34: + time = 1088000 + flags = 1 + data = length 4000, hash F47EF742 + sample 35: + time = 1120000 + flags = 1 + data = length 4000, hash 9B2549DA + sample 36: + time = 1152000 + flags = 1 + data = length 4000, hash A12733C9 + sample 37: + time = 1184000 + flags = 1 + data = length 4000, hash 95F62E99 + sample 38: + time = 1216000 + flags = 1 + data = length 4000, hash A4D858 + sample 39: + time = 1248000 + flags = 1 + data = length 4000, hash A4D858 + sample 40: + time = 1280000 + flags = 1 + data = length 4000, hash 22C1A129 + sample 41: + time = 1312000 + flags = 1 + data = length 4000, hash 2C51E4A1 + sample 42: + time = 1344000 + flags = 1 + data = length 4000, hash 3782E8BB + sample 43: + time = 1376000 + flags = 1 + data = length 4000, hash 2C51E4A1 + sample 44: + time = 1408000 + flags = 1 + data = length 4000, hash BDB3D129 + sample 45: + time = 1440000 + flags = 1 + data = length 4000, hash F642A55 + sample 46: + time = 1472000 + flags = 1 + data = length 4000, hash 32F259F4 + sample 47: + time = 1504000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 48: + time = 1536000 + flags = 1 + data = length 4000, hash 57C98E1C + sample 49: + time = 1568000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 50: + time = 1600000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 51: + time = 1632000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 52: + time = 1664000 + flags = 1 + data = length 4000, hash 4C987B7C + sample 53: + time = 1696000 + flags = 1 + data = length 4000, hash 4C987B7C +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump new file mode 100644 index 00000000000..55db1ad6067 --- /dev/null +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump @@ -0,0 +1,280 @@ +seekMap: + isSeekable = true + duration = 2048000 + getPosition(0) = [[timeUs=0, position=400052]] + getPosition(1) = [[timeUs=1, position=400052]] + getPosition(1024000) = [[timeUs=1024000, position=481972]] + getPosition(2048000) = [[timeUs=2048000, position=561332]] +numberOfTracks = 1 +track 0: + total output bytes = 163840 + sample count = 64 + track duration = 2048000 + format 0: + averageBitrate = 640000 + peakBitrate = 640000 + id = 1 + containerMimeType = audio/mp4 + sampleMimeType = audio/eac3-joc + maxInputSize = 2590 + channelCount = 6 + sampleRate = 48000 + language = und + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + sample 0: + time = 0 + flags = 1 + data = length 2560, hash 882594AD + sample 1: + time = 32000 + flags = 1 + data = length 2560, hash 41EC8B22 + sample 2: + time = 64000 + flags = 1 + data = length 2560, hash 67E6EFD4 + sample 3: + time = 96000 + flags = 1 + data = length 2560, hash A7E66AFD + sample 4: + time = 128000 + flags = 1 + data = length 2560, hash 3924116 + sample 5: + time = 160000 + flags = 1 + data = length 2560, hash 64DCE40B + sample 6: + time = 192000 + flags = 1 + data = length 2560, hash F2E0DA64 + sample 7: + time = 224000 + flags = 1 + data = length 2560, hash C156258B + sample 8: + time = 256000 + flags = 1 + data = length 2560, hash D8DBDCDE + sample 9: + time = 288000 + flags = 1 + data = length 2560, hash C11B2F25 + sample 10: + time = 320000 + flags = 1 + data = length 2560, hash B3C5612 + sample 11: + time = 352000 + flags = 1 + data = length 2560, hash A94B15D0 + sample 12: + time = 384000 + flags = 1 + data = length 2560, hash 12E4E306 + sample 13: + time = 416000 + flags = 1 + data = length 2560, hash 11CB959F + sample 14: + time = 448000 + flags = 1 + data = length 2560, hash B6433844 + sample 15: + time = 480000 + flags = 1 + data = length 2560, hash EA6DEB89 + sample 16: + time = 512000 + flags = 1 + data = length 2560, hash 6D65CBD9 + sample 17: + time = 544000 + flags = 1 + data = length 2560, hash A5D635C5 + sample 18: + time = 576000 + flags = 1 + data = length 2560, hash 992E36AB + sample 19: + time = 608000 + flags = 1 + data = length 2560, hash 1EC4E5AF + sample 20: + time = 640000 + flags = 1 + data = length 2560, hash DCFEB7D2 + sample 21: + time = 672000 + flags = 1 + data = length 2560, hash 45EFC639 + sample 22: + time = 704000 + flags = 1 + data = length 2560, hash F598673 + sample 23: + time = 736000 + flags = 1 + data = length 2560, hash 89E4E5EC + sample 24: + time = 768000 + flags = 1 + data = length 2560, hash FBE2532B + sample 25: + time = 800000 + flags = 1 + data = length 2560, hash 9CE5F83B + sample 26: + time = 832000 + flags = 1 + data = length 2560, hash 6ED49E2C + sample 27: + time = 864000 + flags = 1 + data = length 2560, hash BC52F8F3 + sample 28: + time = 896000 + flags = 1 + data = length 2560, hash 759203E2 + sample 29: + time = 928000 + flags = 1 + data = length 2560, hash D5D31AE9 + sample 30: + time = 960000 + flags = 1 + data = length 2560, hash 640A24ED + sample 31: + time = 992000 + flags = 1 + data = length 2560, hash 19B52B8B + sample 32: + time = 1024000 + flags = 1 + data = length 2560, hash 5DA977C3 + sample 33: + time = 1056000 + flags = 1 + data = length 2560, hash 982879DD + sample 34: + time = 1088000 + flags = 1 + data = length 2560, hash A7656B9C + sample 35: + time = 1120000 + flags = 1 + data = length 2560, hash 445CCC67 + sample 36: + time = 1152000 + flags = 1 + data = length 2560, hash ACD5CB5C + sample 37: + time = 1184000 + flags = 1 + data = length 2560, hash 175BBF26 + sample 38: + time = 1216000 + flags = 1 + data = length 2560, hash DBCBEB0 + sample 39: + time = 1248000 + flags = 1 + data = length 2560, hash DA39D991 + sample 40: + time = 1280000 + flags = 1 + data = length 2560, hash F08CC8E2 + sample 41: + time = 1312000 + flags = 1 + data = length 2560, hash 6B0842D7 + sample 42: + time = 1344000 + flags = 1 + data = length 2560, hash 9FE87594 + sample 43: + time = 1376000 + flags = 1 + data = length 2560, hash 8E62CE19 + sample 44: + time = 1408000 + flags = 1 + data = length 2560, hash 5FDC4084 + sample 45: + time = 1440000 + flags = 1 + data = length 2560, hash C32DAEE1 + sample 46: + time = 1472000 + flags = 1 + data = length 2560, hash BBEFB568 + sample 47: + time = 1504000 + flags = 1 + data = length 2560, hash 20504279 + sample 48: + time = 1536000 + flags = 1 + data = length 2560, hash 3B8192D2 + sample 49: + time = 1568000 + flags = 1 + data = length 2560, hash 4206B48 + sample 50: + time = 1600000 + flags = 1 + data = length 2560, hash B195AB53 + sample 51: + time = 1632000 + flags = 1 + data = length 2560, hash 3AA8E25F + sample 52: + time = 1664000 + flags = 1 + data = length 2560, hash BC227D7B + sample 53: + time = 1696000 + flags = 1 + data = length 2560, hash 6A34F7EA + sample 54: + time = 1728000 + flags = 1 + data = length 2560, hash F1E731C4 + sample 55: + time = 1760000 + flags = 1 + data = length 2560, hash 9CC406 + sample 56: + time = 1792000 + flags = 1 + data = length 2560, hash A1532233 + sample 57: + time = 1824000 + flags = 1 + data = length 2560, hash 98E49039 + sample 58: + time = 1856000 + flags = 1 + data = length 2560, hash 3F8B6DC0 + sample 59: + time = 1888000 + flags = 1 + data = length 2560, hash 4E7BF79F + sample 60: + time = 1920000 + flags = 1 + data = length 2560, hash 6DD6F2D7 + sample 61: + time = 1952000 + flags = 1 + data = length 2560, hash A05C0EC2 + sample 62: + time = 1984000 + flags = 1 + data = length 2560, hash 10C62F30 + sample 63: + time = 2016000 + flags = 536870913 + data = length 2560, hash EE4F848A +tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump new file mode 100644 index 00000000000..5b0867221d7 --- /dev/null +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump @@ -0,0 +1,274 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = [[timeUs=0, position=571]] +numberOfTracks = 1 +track 0: + total output bytes = 163840 + sample count = 64 + format 0: + peakBitrate = 640000 + id = 1 + containerMimeType = audio/mp4 + sampleMimeType = audio/eac3-joc + channelCount = 6 + sampleRate = 48000 + language = und + metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + sample 0: + time = 0 + flags = 1 + data = length 2560, hash 882594AD + sample 1: + time = 32000 + flags = 1 + data = length 2560, hash 41EC8B22 + sample 2: + time = 64000 + flags = 1 + data = length 2560, hash 67E6EFD4 + sample 3: + time = 96000 + flags = 1 + data = length 2560, hash A7E66AFD + sample 4: + time = 128000 + flags = 1 + data = length 2560, hash 3924116 + sample 5: + time = 160000 + flags = 1 + data = length 2560, hash 64DCE40B + sample 6: + time = 192000 + flags = 1 + data = length 2560, hash F2E0DA64 + sample 7: + time = 224000 + flags = 1 + data = length 2560, hash C156258B + sample 8: + time = 256000 + flags = 1 + data = length 2560, hash D8DBDCDE + sample 9: + time = 288000 + flags = 1 + data = length 2560, hash C11B2F25 + sample 10: + time = 320000 + flags = 1 + data = length 2560, hash B3C5612 + sample 11: + time = 352000 + flags = 1 + data = length 2560, hash A94B15D0 + sample 12: + time = 384000 + flags = 1 + data = length 2560, hash 12E4E306 + sample 13: + time = 416000 + flags = 1 + data = length 2560, hash 11CB959F + sample 14: + time = 448000 + flags = 1 + data = length 2560, hash B6433844 + sample 15: + time = 480000 + flags = 1 + data = length 2560, hash EA6DEB89 + sample 16: + time = 512000 + flags = 1 + data = length 2560, hash 6D65CBD9 + sample 17: + time = 544000 + flags = 1 + data = length 2560, hash A5D635C5 + sample 18: + time = 576000 + flags = 1 + data = length 2560, hash 992E36AB + sample 19: + time = 608000 + flags = 1 + data = length 2560, hash 1EC4E5AF + sample 20: + time = 640000 + flags = 1 + data = length 2560, hash DCFEB7D2 + sample 21: + time = 672000 + flags = 1 + data = length 2560, hash 45EFC639 + sample 22: + time = 704000 + flags = 1 + data = length 2560, hash F598673 + sample 23: + time = 736000 + flags = 1 + data = length 2560, hash 89E4E5EC + sample 24: + time = 768000 + flags = 1 + data = length 2560, hash FBE2532B + sample 25: + time = 800000 + flags = 1 + data = length 2560, hash 9CE5F83B + sample 26: + time = 832000 + flags = 1 + data = length 2560, hash 6ED49E2C + sample 27: + time = 864000 + flags = 1 + data = length 2560, hash BC52F8F3 + sample 28: + time = 896000 + flags = 1 + data = length 2560, hash 759203E2 + sample 29: + time = 928000 + flags = 1 + data = length 2560, hash D5D31AE9 + sample 30: + time = 960000 + flags = 1 + data = length 2560, hash 640A24ED + sample 31: + time = 992000 + flags = 1 + data = length 2560, hash 19B52B8B + sample 32: + time = 1024000 + flags = 1 + data = length 2560, hash 5DA977C3 + sample 33: + time = 1056000 + flags = 1 + data = length 2560, hash 982879DD + sample 34: + time = 1088000 + flags = 1 + data = length 2560, hash A7656B9C + sample 35: + time = 1120000 + flags = 1 + data = length 2560, hash 445CCC67 + sample 36: + time = 1152000 + flags = 1 + data = length 2560, hash ACD5CB5C + sample 37: + time = 1184000 + flags = 1 + data = length 2560, hash 175BBF26 + sample 38: + time = 1216000 + flags = 1 + data = length 2560, hash DBCBEB0 + sample 39: + time = 1248000 + flags = 1 + data = length 2560, hash DA39D991 + sample 40: + time = 1280000 + flags = 1 + data = length 2560, hash F08CC8E2 + sample 41: + time = 1312000 + flags = 1 + data = length 2560, hash 6B0842D7 + sample 42: + time = 1344000 + flags = 1 + data = length 2560, hash 9FE87594 + sample 43: + time = 1376000 + flags = 1 + data = length 2560, hash 8E62CE19 + sample 44: + time = 1408000 + flags = 1 + data = length 2560, hash 5FDC4084 + sample 45: + time = 1440000 + flags = 1 + data = length 2560, hash C32DAEE1 + sample 46: + time = 1472000 + flags = 1 + data = length 2560, hash BBEFB568 + sample 47: + time = 1504000 + flags = 1 + data = length 2560, hash 20504279 + sample 48: + time = 1536000 + flags = 1 + data = length 2560, hash 3B8192D2 + sample 49: + time = 1568000 + flags = 1 + data = length 2560, hash 4206B48 + sample 50: + time = 1600000 + flags = 1 + data = length 2560, hash B195AB53 + sample 51: + time = 1632000 + flags = 1 + data = length 2560, hash 3AA8E25F + sample 52: + time = 1664000 + flags = 1 + data = length 2560, hash BC227D7B + sample 53: + time = 1696000 + flags = 1 + data = length 2560, hash 6A34F7EA + sample 54: + time = 1728000 + flags = 1 + data = length 2560, hash F1E731C4 + sample 55: + time = 1760000 + flags = 1 + data = length 2560, hash 9CC406 + sample 56: + time = 1792000 + flags = 1 + data = length 2560, hash A1532233 + sample 57: + time = 1824000 + flags = 1 + data = length 2560, hash 98E49039 + sample 58: + time = 1856000 + flags = 1 + data = length 2560, hash 3F8B6DC0 + sample 59: + time = 1888000 + flags = 1 + data = length 2560, hash 4E7BF79F + sample 60: + time = 1920000 + flags = 1 + data = length 2560, hash 6DD6F2D7 + sample 61: + time = 1952000 + flags = 1 + data = length 2560, hash A05C0EC2 + sample 62: + time = 1984000 + flags = 1 + data = length 2560, hash 10C62F30 + sample 63: + time = 2016000 + flags = 1 + data = length 2560, hash EE4F848A +tracksEnded = true From 3cd5a6ecee3f537a4696952f874d51448f044ac2 Mon Sep 17 00:00:00 2001 From: Lion Six Date: Thu, 9 Jul 2026 10:44:13 +0000 Subject: [PATCH 2/8] Format with google-java-format --- .../media3/container/FormatSpecificTransmuxingData.java | 5 ++--- .../muxer/src/main/java/androidx/media3/muxer/Boxes.java | 8 ++++---- .../src/test/java/androidx/media3/muxer/BoxesTest.java | 3 ++- .../media3/muxer/FragmentedMp4MuxerEndToEndTest.java | 8 ++++---- .../java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java index abd1f66325f..f67727c05ba 100644 --- a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java +++ b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java @@ -18,12 +18,11 @@ import androidx.annotation.Nullable; import androidx.media3.common.Metadata; import androidx.media3.common.util.UnstableApi; -import androidx.media3.common.util.Util; import java.util.Arrays; /** - * Stores the raw payload of a format-specific box so it can be re-written during transmuxing (stream - * copy) without re-encoding. + * Stores the raw payload of a format-specific box so it can be re-written during transmuxing + * (stream copy) without re-encoding. * *

    Some codecs store codec configuration in a container box whose bytes are not otherwise exposed * on {@link androidx.media3.common.Format} (for example the E-AC-3 {@code dec3} / EC3SpecificBox). diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java index 4f659b005bd..125dd2b5c50 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -1653,8 +1653,8 @@ private static ByteBuffer av1CBox(Format format) { * *

    The dec3 box body is carried verbatim in a {@link FormatSpecificTransmuxingData} entry (with * {@linkplain FormatSpecificTransmuxingData#boxType boxType} {@code "dec3"}) on {@link - * Format#metadata}. Stream-copy callers must populate it with the source track's raw dec3 payload; - * the MP4 extractor populates this from the source {@code dec3} box. + * Format#metadata}. Stream-copy callers must populate it with the source track's raw dec3 + * payload; the MP4 extractor populates this from the source {@code dec3} box. */ private static ByteBuffer dec3Box(Format format) { byte[] dec3Payload = getTransmuxingBoxPayload(format, "dec3"); @@ -1665,8 +1665,8 @@ private static ByteBuffer dec3Box(Format format) { /** * Returns the raw payload of the {@link FormatSpecificTransmuxingData} entry on {@code - * format.metadata} whose {@linkplain FormatSpecificTransmuxingData#boxType boxType} matches {@code - * boxType}, or {@code null} if there is no such entry. + * format.metadata} whose {@linkplain FormatSpecificTransmuxingData#boxType boxType} matches + * {@code boxType}, or {@code null} if there is no such entry. */ @Nullable private static byte[] getTransmuxingBoxPayload(Format format, String boxType) { diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java index a936963a8c2..e28720b734f 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java @@ -478,7 +478,8 @@ public void createAudioSampleEntryBox_forEAc3_usesEc3Fourcc() { .setSampleMimeType(MimeTypes.AUDIO_E_AC3) .setMetadata( new Metadata( - new FormatSpecificTransmuxingData("dec3", new byte[] {0x0F, (byte) 0x80, 0x00}))) + new FormatSpecificTransmuxingData( + "dec3", new byte[] {0x0F, (byte) 0x80, 0x00}))) .build(); ByteBuffer box = Boxes.audioSampleEntry(format); diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java index bde4fca3f62..cea21a556fd 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java @@ -259,11 +259,11 @@ public void writeFragmentedMp4File_fromEAc3JocInput_matchesExpected() throws Exc } /** - * Stream-copies an MP4 audio track through {@link FragmentedMp4Muxer} and asserts the re-extracted - * output against the expected dump. + * Stream-copies an MP4 audio track through {@link FragmentedMp4Muxer} and asserts the + * re-extracted output against the expected dump. * - *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so that - * {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code + *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so + * that {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. */ private void transmuxToFragmentedAndAssert(String inputFileName) throws Exception { diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java index b48be60c1d7..daac657608e 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java @@ -120,8 +120,8 @@ public void writeMp4File_fromEAc3JocInput_matchesExpected() throws Exception { * Stream-copies an MP4 audio track through {@link Mp4Muxer} and asserts the re-extracted output * against the expected dump. * - *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so that - * {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code + *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so + * that {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. */ private void transmuxAndAssert(String inputFileName) throws Exception { From 8974fb2d1a3bc8c290442eecf7092474a603e1f9 Mon Sep 17 00:00:00 2001 From: Lion Six Date: Thu, 9 Jul 2026 14:42:58 +0000 Subject: [PATCH 3/8] Updated Releasenotes --- RELEASENOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 9dce6ce067e..5e3c0edcb73 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,6 +24,9 @@ Custom `AssetLoader` implementations must implement these new methods. * Track Selection: * Extractors: + * MP4: Preserve E-AC-3 and E-AC-3 JOC (Dolby Atmos) format metadata during + demuxing to enable lossless stream-copying + ([#3317](https://github.com/androidx/media/pull/3317)). * Inspector: * Inspector Frame: * Audio: @@ -40,6 +43,9 @@ * Muxers: * Fix crash in `Mp4Writer` when writing an EOS sample before any other samples are written. + * MP4: Add support for writing E-AC-3 and E-AC-3 JOC (Dolby Atmos) audio + tracks in `Mp4Muxer` and `FragmentedMp4Muxer` + ([#3317](https://github.com/androidx/media/pull/3317)). * IMA extension: * Session: * UI: From e0723439e1df78f499f38029fbff5c3590d2b5d9 Mon Sep 17 00:00:00 2001 From: Lion Six Date: Mon, 13 Jul 2026 13:11:59 +0000 Subject: [PATCH 4/8] Address review feedback: update copyright year, add mutation tests, and clean up test organization --- .../FormatSpecificTransmuxingData.java | 8 +- .../java/androidx/media3/muxer/BoxesTest.java | 63 +++++++++++++ .../muxer/FragmentedMp4MuxerEndToEndTest.java | 5 +- .../media3/muxer/Mp4MuxerEndToEndTest.java | 93 ++++++++++--------- 4 files changed, 120 insertions(+), 49 deletions(-) diff --git a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java index f67727c05ba..670a4cfc2d5 100644 --- a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java +++ b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Android Open Source Project + * Copyright 2026 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. @@ -15,6 +15,8 @@ */ package androidx.media3.container; +import static com.google.common.base.Preconditions.checkNotNull; + import androidx.annotation.Nullable; import androidx.media3.common.Metadata; import androidx.media3.common.util.UnstableApi; @@ -43,8 +45,8 @@ public final class FormatSpecificTransmuxingData implements Metadata.Entry { /** Creates an instance. */ public FormatSpecificTransmuxingData(String boxType, byte[] data) { - this.boxType = boxType; - this.data = data; + this.boxType = checkNotNull(boxType); + this.data = checkNotNull(data).clone(); } @Override diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java index e28720b734f..a3ff72b4841 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java @@ -35,6 +35,7 @@ import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.Mp4LocationData; +import androidx.media3.container.Mp4TimestampData; import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpableMp4Box; @@ -470,6 +471,47 @@ public void createCodecSpecificBox_forEAc3WithEmptyPayload_throws() { assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format)); } + @Test + public void createCodecSpecificBox_forEAc3WithWrongBoxType_throws() { + Format format = + FAKE_AUDIO_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_E_AC3) + .setMetadata( + new Metadata( + new FormatSpecificTransmuxingData( + "wrong", new byte[] {0x0F, (byte) 0x80, 0x00}))) + .build(); + + assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format)); + } + + @Test + public void createCodecSpecificBox_forEAc3WithMultipleMetadataEntries_findsCorrectPayload() { + byte[] payload = new byte[] {0x0F, (byte) 0x80, 0x00}; + Format format = + FAKE_AUDIO_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_E_AC3) + .setMetadata( + new Metadata( + new FormatSpecificTransmuxingData("wrong", new byte[] {1, 2, 3}), + new Mp4TimestampData(100_000_000L, 500_000_000L), + new FormatSpecificTransmuxingData("dec3", payload))) + .build(); + + ByteBuffer box = Boxes.codecSpecificBox(format); + + assertThat(box.remaining()).isEqualTo(8 + payload.length); + byte[] typeBytes = new byte[4]; + box.position(4); + box.get(typeBytes); + assertThat(Util.fromUtf8Bytes(typeBytes)).isEqualTo("dec3"); + byte[] actual = new byte[payload.length]; + box.get(actual); + assertThat(actual).isEqualTo(payload); + } + @Test public void createAudioSampleEntryBox_forEAc3_usesEc3Fourcc() { Format format = @@ -491,6 +533,27 @@ public void createAudioSampleEntryBox_forEAc3_usesEc3Fourcc() { assertThat(Util.fromUtf8Bytes(fourccBytes)).isEqualTo("ec-3"); } + @Test + public void createAudioSampleEntryBox_forEAc3Joc_usesEc3Fourcc() { + Format format = + FAKE_AUDIO_FORMAT + .buildUpon() + .setSampleMimeType(MimeTypes.AUDIO_E_AC3_JOC) + .setMetadata( + new Metadata( + new FormatSpecificTransmuxingData( + "dec3", new byte[] {0x0F, (byte) 0x80, 0x00}))) + .build(); + + ByteBuffer box = Boxes.audioSampleEntry(format); + + // Sample entry box layout: 4 bytes size + 4 bytes fourcc + ... + byte[] fourccBytes = new byte[4]; + box.position(4); + box.get(fourccBytes); + assertThat(Util.fromUtf8Bytes(fourccBytes)).isEqualTo("ec-3"); + } + @Test public void createAudioSampleEntryBox_withUnknownAudioFormat_throws() { // The audio format contains an unknown MIME type. diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java index cea21a556fd..be0ea943d78 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java @@ -268,7 +268,10 @@ public void writeFragmentedMp4File_fromEAc3JocInput_matchesExpected() throws Exc */ private void transmuxToFragmentedAndAssert(String inputFileName) throws Exception { FakeExtractorOutput inputExtractorOutput = - TestUtil.extractAllSamplesFromFile(new Mp4Extractor(), context, "media/" + inputFileName); + TestUtil.extractAllSamplesFromFile( + new Mp4Extractor(new DefaultSubtitleParserFactory()), + context, + "media/" + inputFileName); FakeTrackOutput audioTrackOutput = Iterables.getOnlyElement(inputExtractorOutput.getTrackOutputsForType(C.TRACK_TYPE_AUDIO)); Format audioFormat = checkNotNull(audioTrackOutput.lastFormat); diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java index daac657608e..4ae63b511b6 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java @@ -116,51 +116,6 @@ public void writeMp4File_fromEAc3JocInput_matchesExpected() throws Exception { transmuxAndAssert(/* inputFileName= */ "sample_eac3joc.mp4"); } - /** - * Stream-copies an MP4 audio track through {@link Mp4Muxer} and asserts the re-extracted output - * against the expected dump. - * - *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so - * that {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code - * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. - */ - private void transmuxAndAssert(String inputFileName) throws Exception { - FakeExtractorOutput inputExtractorOutput = - TestUtil.extractAllSamplesFromFile( - new Mp4Extractor(), context, "media/mp4/" + inputFileName); - FakeTrackOutput audioTrackOutput = - Iterables.getOnlyElement(inputExtractorOutput.getTrackOutputsForType(C.TRACK_TYPE_AUDIO)); - Format audioFormat = checkNotNull(audioTrackOutput.lastFormat); - String outputFilePath = temporaryFolder.newFile().getPath(); - - try (Mp4Muxer muxer = new Mp4Muxer.Builder(SeekableMuxerOutput.of(outputFilePath)).build()) { - // Use fixed timestamps so the output (and its dump) is deterministic. - muxer.addMetadataEntry( - new Mp4TimestampData( - /* creationTimestampSeconds= */ 100_000_000L, - /* modificationTimestampSeconds= */ 500_000_000L)); - int trackId = muxer.addTrack(audioFormat); - for (int i = 0; i < audioTrackOutput.getSampleCount(); i++) { - byte[] sampleData = audioTrackOutput.getSampleData(i); - ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleData.length); - sampleBuffer.put(sampleData); - sampleBuffer.rewind(); - muxer.writeSampleData( - trackId, - sampleBuffer, - new BufferInfo( - audioTrackOutput.getSampleTimeUs(i), - sampleData.length, - audioTrackOutput.getSampleFlags(i))); - } - } - - FakeExtractorOutput outputExtractorOutput = - TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), outputFilePath); - DumpFileAsserts.assertOutput( - context, outputExtractorOutput, MuxerTestUtil.getExpectedMp4DumpFilePath(inputFileName)); - } - @Test public void createMp4File_addTrackAndMetadataButNoSamples_createsEmptyFile() throws Exception { String outputFilePath = temporaryFolder.newFile().getPath(); @@ -1272,4 +1227,52 @@ public void writeMp4File_onlyEndOfStreamSample_doesNotCrash() throws Exception { byte[] outputFileBytes = TestUtil.getByteArrayFromFilePath(outputFilePath); assertThat(outputFileBytes).isEmpty(); } + + /** + * Stream-copies an MP4 audio track through {@link Mp4Muxer} and asserts the re-extracted output + * against the expected dump. + * + *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so + * that {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code + * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. + */ + private void transmuxAndAssert(String inputFileName) throws Exception { + FakeExtractorOutput inputExtractorOutput = + TestUtil.extractAllSamplesFromFile( + new Mp4Extractor(new DefaultSubtitleParserFactory()), + context, + "media/mp4/" + inputFileName); + FakeTrackOutput audioTrackOutput = + Iterables.getOnlyElement(inputExtractorOutput.getTrackOutputsForType(C.TRACK_TYPE_AUDIO)); + Format audioFormat = checkNotNull(audioTrackOutput.lastFormat); + String outputFilePath = temporaryFolder.newFile().getPath(); + + try (Mp4Muxer muxer = new Mp4Muxer.Builder(SeekableMuxerOutput.of(outputFilePath)).build()) { + // Use fixed timestamps so the output (and its dump) is deterministic. + muxer.addMetadataEntry( + new Mp4TimestampData( + /* creationTimestampSeconds= */ 100_000_000L, + /* modificationTimestampSeconds= */ 500_000_000L)); + int trackId = muxer.addTrack(audioFormat); + for (int i = 0; i < audioTrackOutput.getSampleCount(); i++) { + byte[] sampleData = audioTrackOutput.getSampleData(i); + ByteBuffer sampleBuffer = ByteBuffer.allocateDirect(sampleData.length); + sampleBuffer.put(sampleData); + sampleBuffer.rewind(); + muxer.writeSampleData( + trackId, + sampleBuffer, + new BufferInfo( + audioTrackOutput.getSampleTimeUs(i), + sampleData.length, + audioTrackOutput.getSampleFlags(i))); + } + } + + FakeExtractorOutput outputExtractorOutput = + TestUtil.extractAllSamplesFromFilePath( + new Mp4Extractor(new DefaultSubtitleParserFactory()), outputFilePath); + DumpFileAsserts.assertOutput( + context, outputExtractorOutput, MuxerTestUtil.getExpectedMp4DumpFilePath(inputFileName)); + } } From e4fd5cef2eac388f3fef4dfb94fe11f664320f71 Mon Sep 17 00:00:00 2001 From: Lion Six Date: Mon, 13 Jul 2026 19:33:34 +0000 Subject: [PATCH 5/8] Address review feedback: inline dec3Box, update docstring, and revert release notes --- RELEASENOTES.md | 6 ----- .../FormatSpecificTransmuxingData.java | 1 - .../java/androidx/media3/muxer/Boxes.java | 27 +++++-------------- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5e3c0edcb73..9dce6ce067e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -24,9 +24,6 @@ Custom `AssetLoader` implementations must implement these new methods. * Track Selection: * Extractors: - * MP4: Preserve E-AC-3 and E-AC-3 JOC (Dolby Atmos) format metadata during - demuxing to enable lossless stream-copying - ([#3317](https://github.com/androidx/media/pull/3317)). * Inspector: * Inspector Frame: * Audio: @@ -43,9 +40,6 @@ * Muxers: * Fix crash in `Mp4Writer` when writing an EOS sample before any other samples are written. - * MP4: Add support for writing E-AC-3 and E-AC-3 JOC (Dolby Atmos) audio - tracks in `Mp4Muxer` and `FragmentedMp4Muxer` - ([#3317](https://github.com/androidx/media/pull/3317)). * IMA extension: * Session: * UI: diff --git a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java index 670a4cfc2d5..1168a80ebf3 100644 --- a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java +++ b/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java @@ -28,7 +28,6 @@ * *

    Some codecs store codec configuration in a container box whose bytes are not otherwise exposed * on {@link androidx.media3.common.Format} (for example the E-AC-3 {@code dec3} / EC3SpecificBox). - * The extractor preserves that box's payload here so a muxer can reproduce the box verbatim. * *

    The {@link #data} holds the box body only, i.e. excluding the 4-byte size and 4-byte type * header, and {@link #boxType} identifies which box the payload belongs to (for example {@code diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java index 125dd2b5c50..7eb7a0976dd 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -785,7 +785,10 @@ public static ByteBuffer codecSpecificBox(Format format) { return iacbBox(format); case MimeTypes.AUDIO_E_AC3: case MimeTypes.AUDIO_E_AC3_JOC: - return dec3Box(format); + byte[] dec3Payload = getTransmuxingBoxPayload(format, "dec3"); + checkArgument( + dec3Payload != null && dec3Payload.length > 0, "dec3 payload not found for dec3 box."); + return BoxUtils.wrapIntoBox("dec3", ByteBuffer.wrap(dec3Payload)); case MimeTypes.AUDIO_RAW: return ByteBuffer.allocate(0); // No codec specific box for raw audio. case MimeTypes.VIDEO_H263: @@ -1648,21 +1651,6 @@ private static ByteBuffer av1CBox(Format format) { return BoxUtils.wrapIntoBox("av1C", ByteBuffer.wrap(csd0)); } - /** - * Returns the dec3 box (EC3SpecificBox) for E-AC-3 / E-AC-3 JOC (Dolby Atmos) audio. - * - *

    The dec3 box body is carried verbatim in a {@link FormatSpecificTransmuxingData} entry (with - * {@linkplain FormatSpecificTransmuxingData#boxType boxType} {@code "dec3"}) on {@link - * Format#metadata}. Stream-copy callers must populate it with the source track's raw dec3 - * payload; the MP4 extractor populates this from the source {@code dec3} box. - */ - private static ByteBuffer dec3Box(Format format) { - byte[] dec3Payload = getTransmuxingBoxPayload(format, "dec3"); - checkArgument( - dec3Payload != null && dec3Payload.length > 0, "dec3 payload not found for dec3 box."); - return BoxUtils.wrapIntoBox("dec3", ByteBuffer.wrap(dec3Payload)); - } - /** * Returns the raw payload of the {@link FormatSpecificTransmuxingData} entry on {@code * format.metadata} whose {@linkplain FormatSpecificTransmuxingData#boxType boxType} matches @@ -1670,12 +1658,11 @@ private static ByteBuffer dec3Box(Format format) { */ @Nullable private static byte[] getTransmuxingBoxPayload(Format format, String boxType) { - @Nullable Metadata metadata = format.metadata; - if (metadata == null) { + if (format.metadata == null) { return null; } - for (int i = 0; i < metadata.length(); i++) { - Metadata.Entry entry = metadata.get(i); + for (int i = 0; i < format.metadata.length(); i++) { + Metadata.Entry entry = format.metadata.get(i); if (entry instanceof FormatSpecificTransmuxingData && ((FormatSpecificTransmuxingData) entry).boxType.equals(boxType)) { return ((FormatSpecificTransmuxingData) entry).data; From aa841440d03b8391c31c6a17ea46cd5998dd7748 Mon Sep 17 00:00:00 2001 From: Lion Six Date: Tue, 14 Jul 2026 11:01:15 +0000 Subject: [PATCH 6/8] Use Metadata.getFirstEntryOfType in Boxes.java per review feedback --- .../java/androidx/media3/muxer/Boxes.java | 13 +++------ .../java/androidx/media3/muxer/BoxesTest.java | 27 ------------------- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java index 7eb7a0976dd..a360677e6af 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -30,7 +30,6 @@ import androidx.media3.common.C; import androidx.media3.common.ColorInfo; import androidx.media3.common.Format; -import androidx.media3.common.Metadata; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.CodecSpecificDataUtil; import androidx.media3.common.util.Log; @@ -1661,14 +1660,10 @@ private static byte[] getTransmuxingBoxPayload(Format format, String boxType) { if (format.metadata == null) { return null; } - for (int i = 0; i < format.metadata.length(); i++) { - Metadata.Entry entry = format.metadata.get(i); - if (entry instanceof FormatSpecificTransmuxingData - && ((FormatSpecificTransmuxingData) entry).boxType.equals(boxType)) { - return ((FormatSpecificTransmuxingData) entry).data; - } - } - return null; + @Nullable + FormatSpecificTransmuxingData entry = + format.metadata.getFirstEntryOfType(FormatSpecificTransmuxingData.class); + return entry != null && entry.boxType.equals(boxType) ? entry.data : null; } /** Returns a dvcC/dvwC/dvvC vision box which will be included in dolby vision box. */ diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java index a3ff72b4841..7437bd53acf 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java @@ -35,7 +35,6 @@ import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.MdtaMetadataEntry; import androidx.media3.container.Mp4LocationData; -import androidx.media3.container.Mp4TimestampData; import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata; import androidx.media3.test.utils.DumpFileAsserts; import androidx.media3.test.utils.DumpableMp4Box; @@ -486,32 +485,6 @@ public void createCodecSpecificBox_forEAc3WithWrongBoxType_throws() { assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format)); } - @Test - public void createCodecSpecificBox_forEAc3WithMultipleMetadataEntries_findsCorrectPayload() { - byte[] payload = new byte[] {0x0F, (byte) 0x80, 0x00}; - Format format = - FAKE_AUDIO_FORMAT - .buildUpon() - .setSampleMimeType(MimeTypes.AUDIO_E_AC3) - .setMetadata( - new Metadata( - new FormatSpecificTransmuxingData("wrong", new byte[] {1, 2, 3}), - new Mp4TimestampData(100_000_000L, 500_000_000L), - new FormatSpecificTransmuxingData("dec3", payload))) - .build(); - - ByteBuffer box = Boxes.codecSpecificBox(format); - - assertThat(box.remaining()).isEqualTo(8 + payload.length); - byte[] typeBytes = new byte[4]; - box.position(4); - box.get(typeBytes); - assertThat(Util.fromUtf8Bytes(typeBytes)).isEqualTo("dec3"); - byte[] actual = new byte[payload.length]; - box.get(actual); - assertThat(actual).isEqualTo(payload); - } - @Test public void createAudioSampleEntryBox_forEAc3_usesEc3Fourcc() { Format format = From f8273dcf88a1df9602070057fd8344125b3d9dc7 Mon Sep 17 00:00:00 2001 From: Lion Six Date: Tue, 14 Jul 2026 12:55:06 +0000 Subject: [PATCH 7/8] Rename FormatSpecificTransmuxingData to Mp4FormatSpecificMetadataEntry --- ....java => Mp4FormatSpecificMetadataEntry.java} | 8 ++++---- .../androidx/media3/extractor/mp4/BoxParser.java | 4 ++-- .../main/java/androidx/media3/muxer/Boxes.java | 10 +++++----- .../java/androidx/media3/muxer/BoxesTest.java | 16 ++++++++-------- .../muxer/FragmentedMp4MuxerEndToEndTest.java | 2 +- .../media3/muxer/Mp4MuxerEndToEndTest.java | 2 +- .../extractordumps/mp4/sample_eac3.mp4.0.dump | 2 +- .../extractordumps/mp4/sample_eac3.mp4.1.dump | 2 +- .../extractordumps/mp4/sample_eac3.mp4.2.dump | 2 +- .../extractordumps/mp4/sample_eac3.mp4.3.dump | 2 +- .../mp4/sample_eac3.mp4.unknown_length.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.0.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.1.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.2.dump | 2 +- .../mp4/sample_eac3_fragmented.mp4.3.dump | 2 +- ...ample_eac3_fragmented.mp4.unknown_length.dump | 2 +- .../extractordumps/mp4/sample_eac3joc.mp4.0.dump | 2 +- .../extractordumps/mp4/sample_eac3joc.mp4.1.dump | 2 +- .../extractordumps/mp4/sample_eac3joc.mp4.2.dump | 2 +- .../extractordumps/mp4/sample_eac3joc.mp4.3.dump | 2 +- .../mp4/sample_eac3joc.mp4.unknown_length.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.0.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.1.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.2.dump | 2 +- .../mp4/sample_eac3joc_fragmented.mp4.3.dump | 2 +- ...le_eac3joc_fragmented.mp4.unknown_length.dump | 2 +- .../assets/muxerdumps/mp4/sample_eac3.mp4.dump | 2 +- .../mp4/sample_eac3.mp4_fragmented.dump | 2 +- .../muxerdumps/mp4/sample_eac3joc.mp4.dump | 2 +- .../mp4/sample_eac3joc.mp4_fragmented.dump | 2 +- 30 files changed, 45 insertions(+), 45 deletions(-) rename libraries/container/src/main/java/androidx/media3/container/{FormatSpecificTransmuxingData.java => Mp4FormatSpecificMetadataEntry.java} (88%) diff --git a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java b/libraries/container/src/main/java/androidx/media3/container/Mp4FormatSpecificMetadataEntry.java similarity index 88% rename from libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java rename to libraries/container/src/main/java/androidx/media3/container/Mp4FormatSpecificMetadataEntry.java index 1168a80ebf3..63ffb4898f0 100644 --- a/libraries/container/src/main/java/androidx/media3/container/FormatSpecificTransmuxingData.java +++ b/libraries/container/src/main/java/androidx/media3/container/Mp4FormatSpecificMetadataEntry.java @@ -34,7 +34,7 @@ * dec3}). */ @UnstableApi -public final class FormatSpecificTransmuxingData implements Metadata.Entry { +public final class Mp4FormatSpecificMetadataEntry implements Metadata.Entry { /** The four-character type of the box the {@link #data} belongs to, for example {@code dec3}. */ public final String boxType; @@ -43,7 +43,7 @@ public final class FormatSpecificTransmuxingData implements Metadata.Entry { public final byte[] data; /** Creates an instance. */ - public FormatSpecificTransmuxingData(String boxType, byte[] data) { + public Mp4FormatSpecificMetadataEntry(String boxType, byte[] data) { this.boxType = checkNotNull(boxType); this.data = checkNotNull(data).clone(); } @@ -56,7 +56,7 @@ public boolean equals(@Nullable Object obj) { if (obj == null || getClass() != obj.getClass()) { return false; } - FormatSpecificTransmuxingData other = (FormatSpecificTransmuxingData) obj; + Mp4FormatSpecificMetadataEntry other = (Mp4FormatSpecificMetadataEntry) obj; return boxType.equals(other.boxType) && Arrays.equals(data, other.data); } @@ -67,6 +67,6 @@ public int hashCode() { @Override public String toString() { - return "FormatSpecificTransmuxingData: boxType=" + boxType; + return "Mp4FormatSpecificMetadataEntry: boxType=" + boxType; } } diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java index f20e79928a7..6e837010afc 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/mp4/BoxParser.java @@ -41,10 +41,10 @@ import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.container.DolbyVisionConfig; -import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.Mp4AlternateGroupData; import androidx.media3.container.Mp4Box; import androidx.media3.container.Mp4Box.LeafBox; +import androidx.media3.container.Mp4FormatSpecificMetadataEntry; import androidx.media3.container.Mp4LocationData; import androidx.media3.container.Mp4TimestampData; import androidx.media3.container.NalUnitUtil; @@ -2262,7 +2262,7 @@ private static void parseAudioSampleEntry( Format eac3Format = Ac3Util.parseEAc3AnnexFFormat(parent, Integer.toString(trackId), language, drmInitData); Metadata.Entry dec3TransmuxingData = - new FormatSpecificTransmuxingData("dec3", ec3SpecificBoxPayload); + new Mp4FormatSpecificMetadataEntry("dec3", ec3SpecificBoxPayload); out.format = eac3Format .buildUpon() diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java index a360677e6af..0d6bfa9d276 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -34,8 +34,8 @@ import androidx.media3.common.util.CodecSpecificDataUtil; import androidx.media3.common.util.Log; import androidx.media3.common.util.Util; -import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.MdtaMetadataEntry; +import androidx.media3.container.Mp4FormatSpecificMetadataEntry; import androidx.media3.container.Mp4LocationData; import androidx.media3.container.NalUnitUtil; import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata; @@ -1651,8 +1651,8 @@ private static ByteBuffer av1CBox(Format format) { } /** - * Returns the raw payload of the {@link FormatSpecificTransmuxingData} entry on {@code - * format.metadata} whose {@linkplain FormatSpecificTransmuxingData#boxType boxType} matches + * Returns the raw payload of the {@link Mp4FormatSpecificMetadataEntry} entry on {@code + * format.metadata} whose {@linkplain Mp4FormatSpecificMetadataEntry#boxType boxType} matches * {@code boxType}, or {@code null} if there is no such entry. */ @Nullable @@ -1661,8 +1661,8 @@ private static byte[] getTransmuxingBoxPayload(Format format, String boxType) { return null; } @Nullable - FormatSpecificTransmuxingData entry = - format.metadata.getFirstEntryOfType(FormatSpecificTransmuxingData.class); + Mp4FormatSpecificMetadataEntry entry = + format.metadata.getFirstEntryOfType(Mp4FormatSpecificMetadataEntry.class); return entry != null && entry.boxType.equals(boxType) ? entry.data : null; } diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java index 7437bd53acf..307087084d7 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java @@ -32,8 +32,8 @@ import androidx.media3.common.Metadata; import androidx.media3.common.MimeTypes; import androidx.media3.common.util.Util; -import androidx.media3.container.FormatSpecificTransmuxingData; import androidx.media3.container.MdtaMetadataEntry; +import androidx.media3.container.Mp4FormatSpecificMetadataEntry; import androidx.media3.container.Mp4LocationData; import androidx.media3.muxer.FragmentedMp4Writer.SampleMetadata; import androidx.media3.test.utils.DumpFileAsserts; @@ -412,7 +412,7 @@ public void createCodecSpecificBox_forEAc3_wrapsDec3PayloadVerbatim() { FAKE_AUDIO_FORMAT .buildUpon() .setSampleMimeType(MimeTypes.AUDIO_E_AC3) - .setMetadata(new Metadata(new FormatSpecificTransmuxingData("dec3", payload))) + .setMetadata(new Metadata(new Mp4FormatSpecificMetadataEntry("dec3", payload))) .build(); ByteBuffer box = Boxes.codecSpecificBox(format); @@ -435,7 +435,7 @@ public void createCodecSpecificBox_forEAc3Joc_wrapsDec3PayloadVerbatim() { FAKE_AUDIO_FORMAT .buildUpon() .setSampleMimeType(MimeTypes.AUDIO_E_AC3_JOC) - .setMetadata(new Metadata(new FormatSpecificTransmuxingData("dec3", payload))) + .setMetadata(new Metadata(new Mp4FormatSpecificMetadataEntry("dec3", payload))) .build(); ByteBuffer box = Boxes.codecSpecificBox(format); @@ -452,7 +452,7 @@ public void createCodecSpecificBox_forEAc3Joc_wrapsDec3PayloadVerbatim() { @Test public void createCodecSpecificBox_forEAc3WithNoTransmuxingData_throws() { - // No FormatSpecificTransmuxingData entry on the format's metadata. + // No Mp4FormatSpecificMetadataEntry entry on the format's metadata. Format format = FAKE_AUDIO_FORMAT.buildUpon().setSampleMimeType(MimeTypes.AUDIO_E_AC3).build(); assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format)); @@ -464,7 +464,7 @@ public void createCodecSpecificBox_forEAc3WithEmptyPayload_throws() { FAKE_AUDIO_FORMAT .buildUpon() .setSampleMimeType(MimeTypes.AUDIO_E_AC3) - .setMetadata(new Metadata(new FormatSpecificTransmuxingData("dec3", new byte[0]))) + .setMetadata(new Metadata(new Mp4FormatSpecificMetadataEntry("dec3", new byte[0]))) .build(); assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format)); @@ -478,7 +478,7 @@ public void createCodecSpecificBox_forEAc3WithWrongBoxType_throws() { .setSampleMimeType(MimeTypes.AUDIO_E_AC3) .setMetadata( new Metadata( - new FormatSpecificTransmuxingData( + new Mp4FormatSpecificMetadataEntry( "wrong", new byte[] {0x0F, (byte) 0x80, 0x00}))) .build(); @@ -493,7 +493,7 @@ public void createAudioSampleEntryBox_forEAc3_usesEc3Fourcc() { .setSampleMimeType(MimeTypes.AUDIO_E_AC3) .setMetadata( new Metadata( - new FormatSpecificTransmuxingData( + new Mp4FormatSpecificMetadataEntry( "dec3", new byte[] {0x0F, (byte) 0x80, 0x00}))) .build(); @@ -514,7 +514,7 @@ public void createAudioSampleEntryBox_forEAc3Joc_usesEc3Fourcc() { .setSampleMimeType(MimeTypes.AUDIO_E_AC3_JOC) .setMetadata( new Metadata( - new FormatSpecificTransmuxingData( + new Mp4FormatSpecificMetadataEntry( "dec3", new byte[] {0x0F, (byte) 0x80, 0x00}))) .build(); diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java index be0ea943d78..0af22bd49bd 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/FragmentedMp4MuxerEndToEndTest.java @@ -264,7 +264,7 @@ public void writeFragmentedMp4File_fromEAc3JocInput_matchesExpected() throws Exc * *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so * that {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code - * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. + * Mp4FormatSpecificMetadataEntry} entry — is preserved and passed to the muxer. */ private void transmuxToFragmentedAndAssert(String inputFileName) throws Exception { FakeExtractorOutput inputExtractorOutput = diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java index 4ae63b511b6..50040c9d25b 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/Mp4MuxerEndToEndTest.java @@ -1234,7 +1234,7 @@ public void writeMp4File_onlyEndOfStreamSample_doesNotCrash() throws Exception { * *

    The input is read with {@link Mp4Extractor} (rather than {@code MediaExtractorCompat}) so * that {@link Format#metadata} — which carries the E-AC-3 {@code dec3} payload as a {@code - * FormatSpecificTransmuxingData} entry — is preserved and passed to the muxer. + * Mp4FormatSpecificMetadataEntry} entry — is preserved and passed to the muxer. */ private void transmuxAndAssert(String inputFileName) throws Exception { FakeExtractorOutput inputExtractorOutput = diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump index 06e031c84f5..b8eb83e33ba 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.0.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump index 36641422acb..776584f8a75 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.1.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 576000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump index 6baa2a18e81..65988879a1e 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.2.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 1152000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump index 43ab7b846d3..9f7116b62db 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.3.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 1696000 flags = 536870913 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump index 06e031c84f5..b8eb83e33ba 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086526, modification time=3662086526, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump index b928c6b22d1..52851feed94 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.0.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump index b81b87b36f5..3f6131b53ff 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.1.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 576000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump index a68a51bd1ec..4765533379e 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.2.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 1152000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump index 029b35752cc..bbf8921e9a1 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.3.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 1696000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump index b928c6b22d1..52851feed94 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3_fragmented.mp4.unknown_length.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086719, modification time=3662086719, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump index 3d07a8f8b0f..c31cd25979a 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.0.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump index a823a6612f3..e8f90cad169 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.1.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 672000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump index 5027d42ea81..20859db2407 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.2.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 1344000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump index 96322d89825..35a1c05c2b7 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.3.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 2016000 flags = 536870913 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump index 3d07a8f8b0f..c31cd25979a 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc.mp4.unknown_length.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086940, modification time=3662086940, timescale=48000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump index 4e4c63626d3..7879b20aaf8 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.0.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump index f7505977d74..26e599855dc 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.1.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 672000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump index f95bb5ccffd..6c88dd93d92 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.2.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 1344000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump index 93c7963349b..85da890c251 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.3.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 2016000 flags = 1 diff --git a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump index 4e4c63626d3..7879b20aaf8 100644 --- a/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump +++ b/libraries/test_data/src/test/assets/extractordumps/mp4/sample_eac3joc_fragmented.mp4.unknown_length.dump @@ -18,7 +18,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=3662086977, modification time=3662086977, timescale=1000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump index b42bd69b386..9f837ef0756 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump index 66ee04d96f2..24ca45810e4 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3.mp4_fragmented.dump @@ -14,7 +14,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump index 55db1ad6067..8e3f2ed5641 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4.dump @@ -20,7 +20,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] sample 0: time = 0 flags = 1 diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump index 5b0867221d7..475faf162a8 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4/sample_eac3joc.mp4_fragmented.dump @@ -14,7 +14,7 @@ track 0: channelCount = 6 sampleRate = 48000 language = und - metadata = entries=[FormatSpecificTransmuxingData: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] + metadata = entries=[Mp4FormatSpecificMetadataEntry: boxType=dec3, Mp4Timestamp: creation time=100000000, modification time=500000000, timescale=10000] sample 0: time = 0 flags = 1 From e133a7e2f1dea976271f03567e04c85cc5a86596 Mon Sep 17 00:00:00 2001 From: Lion Six Date: Wed, 15 Jul 2026 08:28:35 +0000 Subject: [PATCH 8/8] Address review comments for Mp4FormatSpecificMetadataEntry and Boxes --- .../Mp4FormatSpecificMetadataEntry.java | 10 ++++---- .../java/androidx/media3/muxer/Boxes.java | 24 ++++++------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/libraries/container/src/main/java/androidx/media3/container/Mp4FormatSpecificMetadataEntry.java b/libraries/container/src/main/java/androidx/media3/container/Mp4FormatSpecificMetadataEntry.java index 63ffb4898f0..f13b3ba2e33 100644 --- a/libraries/container/src/main/java/androidx/media3/container/Mp4FormatSpecificMetadataEntry.java +++ b/libraries/container/src/main/java/androidx/media3/container/Mp4FormatSpecificMetadataEntry.java @@ -15,9 +15,8 @@ */ package androidx.media3.container; -import static com.google.common.base.Preconditions.checkNotNull; - import androidx.annotation.Nullable; +import androidx.media3.common.Format; import androidx.media3.common.Metadata; import androidx.media3.common.util.UnstableApi; import java.util.Arrays; @@ -27,7 +26,8 @@ * (stream copy) without re-encoding. * *

    Some codecs store codec configuration in a container box whose bytes are not otherwise exposed - * on {@link androidx.media3.common.Format} (for example the E-AC-3 {@code dec3} / EC3SpecificBox). + * on {@link Format} or {@link Format#initializationData} (for example the E-AC-3 {@code dec3} / + * EC3SpecificBox). * *

    The {@link #data} holds the box body only, i.e. excluding the 4-byte size and 4-byte type * header, and {@link #boxType} identifies which box the payload belongs to (for example {@code @@ -44,8 +44,8 @@ public final class Mp4FormatSpecificMetadataEntry implements Metadata.Entry { /** Creates an instance. */ public Mp4FormatSpecificMetadataEntry(String boxType, byte[] data) { - this.boxType = checkNotNull(boxType); - this.data = checkNotNull(data).clone(); + this.boxType = boxType; + this.data = data.clone(); } @Override diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java index 0d6bfa9d276..97926c354a8 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java @@ -784,7 +784,13 @@ public static ByteBuffer codecSpecificBox(Format format) { return iacbBox(format); case MimeTypes.AUDIO_E_AC3: case MimeTypes.AUDIO_E_AC3_JOC: - byte[] dec3Payload = getTransmuxingBoxPayload(format, "dec3"); + @Nullable + Mp4FormatSpecificMetadataEntry dec3Entry = + format.metadata != null + ? format.metadata.getFirstEntryOfType(Mp4FormatSpecificMetadataEntry.class) + : null; + byte[] dec3Payload = + dec3Entry != null && dec3Entry.boxType.equals("dec3") ? dec3Entry.data : null; checkArgument( dec3Payload != null && dec3Payload.length > 0, "dec3 payload not found for dec3 box."); return BoxUtils.wrapIntoBox("dec3", ByteBuffer.wrap(dec3Payload)); @@ -1650,22 +1656,6 @@ private static ByteBuffer av1CBox(Format format) { return BoxUtils.wrapIntoBox("av1C", ByteBuffer.wrap(csd0)); } - /** - * Returns the raw payload of the {@link Mp4FormatSpecificMetadataEntry} entry on {@code - * format.metadata} whose {@linkplain Mp4FormatSpecificMetadataEntry#boxType boxType} matches - * {@code boxType}, or {@code null} if there is no such entry. - */ - @Nullable - private static byte[] getTransmuxingBoxPayload(Format format, String boxType) { - if (format.metadata == null) { - return null; - } - @Nullable - Mp4FormatSpecificMetadataEntry entry = - format.metadata.getFirstEntryOfType(Mp4FormatSpecificMetadataEntry.class); - return entry != null && entry.boxType.equals(boxType) ? entry.data : null; - } - /** Returns a dvcC/dvwC/dvvC vision box which will be included in dolby vision box. */ private static ByteBuffer doviBox(int profile, byte[] csd) { checkArgument(csd.length > 0, "csd is empty for dovi box.");