Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions libraries/muxer/src/main/java/androidx/media3/muxer/Boxes.java
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,9 @@ public static ByteBuffer codecSpecificBox(Format format) {
return damrBox(/* mode= */ (short) 0x83FF); // mode set: all enabled for AMR-WB
case MimeTypes.AUDIO_OPUS:
return dOpsBox(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:
Expand Down Expand Up @@ -1590,6 +1593,19 @@ 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.
*
* <p>The dec3 box body is the codec-specific configuration, carried verbatim in csd-0. Stream-copy
* callers must populate {@code format.initializationData} with the source track's raw dec3 payload.
*/
private static ByteBuffer dec3Box(Format format) {
checkArgument(
!format.initializationData.isEmpty(), "csd-0 (dec3 payload) not found for dec3 box.");
byte[] csd0 = format.initializationData.get(0);
return BoxUtils.wrapIntoBox("dec3", ByteBuffer.wrap(csd0));
}

/** 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.");
Expand Down Expand Up @@ -1792,6 +1808,9 @@ private static String codecSpecificFourcc(Format format) {
return "s263";
case MimeTypes.AUDIO_OPUS:
return "Opus";
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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ public FragmentedMp4Muxer build() {
MimeTypes.AUDIO_AMR_WB,
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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,9 @@ public Mp4Muxer build() {
MimeTypes.AUDIO_AMR_WB,
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)

Expand Down
57 changes: 57 additions & 0 deletions libraries/muxer/src/test/java/androidx/media3/muxer/BoxesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,63 @@ 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)
.setInitializationData(ImmutableList.of(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(new String(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)
.setInitializationData(ImmutableList.of(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(new String(typeBytes)).isEqualTo("dec3");
byte[] actual = new byte[payload.length];
box.get(actual);
assertThat(actual).isEqualTo(payload);
}

@Test
public void createCodecSpecificBox_forEAc3WithNoCsd_throws() {
Format format =
FAKE_AUDIO_FORMAT
.buildUpon()
.setSampleMimeType(MimeTypes.AUDIO_E_AC3)
.setInitializationData(ImmutableList.of())
.build();

assertThrows(IllegalArgumentException.class, () -> Boxes.codecSpecificBox(format));
}

@Test
public void createAudioSampleEntryBox_withUnknownAudioFormat_throws() {
// The audio format contains an unknown MIME type.
Expand Down