diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBToAvccConverter.java b/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBToAvccConverter.java index abfd0503bf..95255c1d4a 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBToAvccConverter.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBToAvccConverter.java @@ -32,7 +32,7 @@ public interface AnnexBToAvccConverter { AnnexBToAvccConverter DEFAULT = (ByteBuffer inputBuffer) -> { if (!inputBuffer.hasRemaining()) { - return; + return inputBuffer; } checkArgument( @@ -40,25 +40,33 @@ public interface AnnexBToAvccConverter { ImmutableList nalUnitList = AnnexBUtils.findNalUnits(inputBuffer); + int totalBytesNeeded = 0; + for (int i = 0; i < nalUnitList.size(); i++) { - int currentNalUnitLength = nalUnitList.get(i).remaining(); - - // Replace the start code with the NAL unit length. - inputBuffer.putInt(currentNalUnitLength); - - // Shift the input buffer's position to next start code. - int newPosition = inputBuffer.position() + currentNalUnitLength; - inputBuffer.position(newPosition); + // 4 bytes to store NAL unit length. + totalBytesNeeded += 4 + nalUnitList.get(i).remaining(); } - inputBuffer.rewind(); + + ByteBuffer outputBuffer = ByteBuffer.allocate(totalBytesNeeded); + + for (int i = 0; i < nalUnitList.size(); i++) { + ByteBuffer currentNalUnit = nalUnitList.get(i); + int currentNalUnitLength = currentNalUnit.remaining(); + + // Rewrite NAL units with NAL unit length in place of start code. + outputBuffer.putInt(currentNalUnitLength); + outputBuffer.put(currentNalUnit); + } + outputBuffer.rewind(); + return outputBuffer; }; /** - * Processes a buffer in-place. + * Returns the processed {@link ByteBuffer}. * *

Expects a {@link ByteBuffer} input with a zero offset. * * @param inputBuffer The buffer to be converted. */ - void process(ByteBuffer inputBuffer); + ByteBuffer process(ByteBuffer inputBuffer); } diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBUtils.java b/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBUtils.java index 1addc17732..66d5216530 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBUtils.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/AnnexBUtils.java @@ -15,47 +15,87 @@ */ package androidx.media3.muxer; +import androidx.media3.common.C; import androidx.media3.common.MimeTypes; import com.google.common.collect.ImmutableList; import java.nio.ByteBuffer; /** NAL unit utilities for start codes and emulation prevention. */ /* package */ final class AnnexBUtils { + private static final int THREE_BYTE_NAL_START_CODE_SIZE = 3; + private AnnexBUtils() {} /** - * Splits a {@link ByteBuffer} into individual NAL units (0x00000001 start code). + * Splits a {@link ByteBuffer} into individual NAL units (0x000001 or 0x00000001 start code). * - *

An empty list is returned if the input is not NAL units. + *

An {@link IllegalStateException} is thrown if the NAL units are invalid. The NAL units are + * identified as per ITU-T H264 spec:Annex B.2. * - *

The position of the input buffer is unchanged after calling this method. + *

The input buffer must have position set to 0 and the position remains unchanged after + * calling this method. */ public static ImmutableList findNalUnits(ByteBuffer input) { - if (input.remaining() < 4 || input.getInt(0) != 1) { + if (input.remaining() == 0) { return ImmutableList.of(); } - ImmutableList.Builder nalUnits = new ImmutableList.Builder<>(); + int nalStartIndex = C.INDEX_UNSET; + int inputLimit = input.limit(); + boolean readingNalUnit = false; - int lastStart = 4; - int zerosSeen = 0; - - for (int i = 4; i < input.limit(); i++) { - if (input.get(i) == 1 && zerosSeen >= 3) { - // We're just looking at a start code. - nalUnits.add(getBytes(input, lastStart, i - 3 - lastStart)); - lastStart = i + 1; - } - - // Handle the end of the stream. - if (i == input.limit() - 1) { - nalUnits.add(getBytes(input, lastStart, input.limit() - lastStart)); - } - - if (input.get(i) == 0) { - zerosSeen++; + // The input must start with a NAL unit. + for (int i = 0; i < inputLimit; i++) { + if (isThreeByteNalStartCode(input, i)) { + nalStartIndex = i + THREE_BYTE_NAL_START_CODE_SIZE; + readingNalUnit = true; + break; + } else if (input.get(i) == 0) { + // Skip the leading zeroes. } else { - zerosSeen = 0; + throw new IllegalStateException("Sample does not start with a NAL unit"); + } + } + + ImmutableList.Builder nalUnits = new ImmutableList.Builder<>(); + // Look for start code 0x000001. The logic will work for 0x00000001 start code as well because a + // NAL unit gets ended even when 0x000000 (which is a prefix of 0x00000001 start code) is found. + for (int i = nalStartIndex; i < inputLimit; ) { + if (readingNalUnit) { + // Found next start code 0x000001. + if (isThreeByteNalStartCode(input, i)) { + nalUnits.add(getBytes(input, nalStartIndex, i - nalStartIndex)); + i = i + THREE_BYTE_NAL_START_CODE_SIZE; + nalStartIndex = i; + continue; + } else if (isThreeBytesZeroSequence(input, i)) { + // Found code 0x000000; The previous NAL unit should be ended. + nalUnits.add(getBytes(input, nalStartIndex, i - nalStartIndex)); + // Stop reading NAL unit until next start code is found. + readingNalUnit = false; + i++; + } else { + // Continue reading NAL unit. + i++; + } + } else { + // Found new start code 0x000001. + if (isThreeByteNalStartCode(input, i)) { + i = i + THREE_BYTE_NAL_START_CODE_SIZE; + nalStartIndex = i; + readingNalUnit = true; + } else if (input.get(i) == 0x00) { + // Skip trailing zeroes. + i++; + } else { + // Found garbage data. + throw new IllegalStateException("Invalid NAL units"); + } + } + + // Add the last NAL unit. + if (i == inputLimit && readingNalUnit) { + nalUnits.add(getBytes(input, nalStartIndex, i - nalStartIndex)); } } input.rewind(); @@ -97,6 +137,20 @@ import java.nio.ByteBuffer; || sampleMimeType.equals(MimeTypes.VIDEO_H265); } + private static boolean isThreeByteNalStartCode(ByteBuffer input, int currentIndex) { + return (currentIndex < input.limit() - THREE_BYTE_NAL_START_CODE_SIZE + && input.get(currentIndex) == 0x00 + && input.get(currentIndex + 1) == 0x00 + && input.get(currentIndex + 2) == 0x01); + } + + private static boolean isThreeBytesZeroSequence(ByteBuffer input, int currentIndex) { + return (currentIndex < input.limit() - THREE_BYTE_NAL_START_CODE_SIZE + && input.get(currentIndex) == 0x00 + && input.get(currentIndex + 1) == 0x00 + && input.get(currentIndex + 2) == 0x00); + } + private static ByteBuffer getBytes(ByteBuffer buf, int offset, int length) { ByteBuffer result = buf.duplicate(); result.position(offset); diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/BasicMp4Writer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/BasicMp4Writer.java index 5ef82eb3ec..895934e9e4 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/BasicMp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/BasicMp4Writer.java @@ -264,6 +264,7 @@ import java.util.concurrent.atomic.AtomicBoolean; /** Writes out any pending samples to the file. */ private void flushPending(Track track) throws IOException { + checkState(track.pendingSamplesByteBuffer.size() == track.pendingSamplesBufferInfo.size()); if (track.pendingSamplesBufferInfo.isEmpty()) { return; } @@ -278,13 +279,7 @@ import java.util.concurrent.atomic.AtomicBoolean; bytesNeededInMdat += sample.limit(); } - // If the required number of bytes doesn't fit in the gap between the actual data and the moov - // box, extend the file and write out the moov box to the end again. - if (mdatDataEnd + bytesNeededInMdat >= mdatEnd) { - // Reserve some extra space than required, so that mdat box extension is less frequent. - rewriteMoovWithMdatEmptySpace( - /* bytesNeeded= */ getMdatExtensionAmount(mdatDataEnd) + bytesNeededInMdat); - } + extendMdatIfRequired(bytesNeededInMdat); track.writtenChunkOffsets.add(mdatDataEnd); track.writtenChunkSampleCounts.add(track.pendingSamplesBufferInfo.size()); @@ -293,22 +288,38 @@ import java.util.concurrent.atomic.AtomicBoolean; BufferInfo currentSampleBufferInfo = track.pendingSamplesBufferInfo.removeFirst(); ByteBuffer currentSampleByteBuffer = track.pendingSamplesByteBuffer.removeFirst(); - track.writtenSamples.add(currentSampleBufferInfo); - // Convert the H.264/H.265 samples from Annex-B format (output by MediaCodec) to // Avcc format (required by MP4 container). if (doesSampleContainAnnexBNalUnits(checkNotNull(track.format.sampleMimeType))) { - annexBToAvccConverter.process(currentSampleByteBuffer); + currentSampleByteBuffer = annexBToAvccConverter.process(currentSampleByteBuffer); + currentSampleBufferInfo.set( + currentSampleByteBuffer.position(), + currentSampleByteBuffer.remaining(), + currentSampleBufferInfo.presentationTimeUs, + currentSampleBufferInfo.flags); } - currentSampleByteBuffer.rewind(); + // If the original sample had 3 bytes NAL start code instead of 4 bytes, then after AnnexB to + // Avcc conversion it will have 1 additional byte. + extendMdatIfRequired(currentSampleByteBuffer.remaining()); mdatDataEnd += output.write(currentSampleByteBuffer, mdatDataEnd); + track.writtenSamples.add(currentSampleBufferInfo); } while (!track.pendingSamplesBufferInfo.isEmpty()); checkState(mdatDataEnd <= mdatEnd); } + private void extendMdatIfRequired(long additionalBytesNeeded) throws IOException { + // If the required number of bytes doesn't fit in the gap between the actual data and the moov + // box, extend the file and write out the moov box to the end again. + if (mdatDataEnd + additionalBytesNeeded >= mdatEnd) { + // Reserve some extra space than required, so that mdat box extension is less frequent. + rewriteMoovWithMdatEmptySpace( + /* bytesNeeded= */ getMdatExtensionAmount(mdatDataEnd) + additionalBytesNeeded); + } + } + private void updateMdatSize() throws IOException { // Assuming that the mdat box has a 64-bit length, skip the box type (4 bytes) and // the 32-bit box length field (4 bytes). diff --git a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java index c3087179ff..26de72528f 100644 --- a/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java +++ b/libraries/muxer/src/main/java/androidx/media3/muxer/FragmentedMp4Writer.java @@ -277,23 +277,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private ProcessedTrackInfo processTrack(int trackId, Track track) { checkState(track.pendingSamplesByteBuffer.size() == track.pendingSamplesBufferInfo.size()); + ImmutableList.Builder pendingSamplesByteBuffer = new ImmutableList.Builder<>(); + ImmutableList.Builder pendingSamplesBufferInfoBuilder = + new ImmutableList.Builder<>(); if (doesSampleContainAnnexBNalUnits(checkNotNull(track.format.sampleMimeType))) { while (!track.pendingSamplesByteBuffer.isEmpty()) { ByteBuffer currentSampleByteBuffer = track.pendingSamplesByteBuffer.removeFirst(); - annexBToAvccConverter.process(currentSampleByteBuffer); + currentSampleByteBuffer = annexBToAvccConverter.process(currentSampleByteBuffer); pendingSamplesByteBuffer.add(currentSampleByteBuffer); + BufferInfo currentSampleBufferInfo = track.pendingSamplesBufferInfo.removeFirst(); + currentSampleBufferInfo.set( + currentSampleByteBuffer.position(), + currentSampleByteBuffer.remaining(), + currentSampleBufferInfo.presentationTimeUs, + currentSampleBufferInfo.flags); + pendingSamplesBufferInfoBuilder.add(currentSampleBufferInfo); } } else { pendingSamplesByteBuffer.addAll(track.pendingSamplesByteBuffer); track.pendingSamplesByteBuffer.clear(); + pendingSamplesBufferInfoBuilder.addAll(track.pendingSamplesBufferInfo); + track.pendingSamplesBufferInfo.clear(); } - ImmutableList.Builder pendingSamplesBufferInfoBuilder = - new ImmutableList.Builder<>(); - pendingSamplesBufferInfoBuilder.addAll(track.pendingSamplesBufferInfo); - track.pendingSamplesBufferInfo.clear(); - ImmutableList pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build(); List sampleDurations = Boxes.convertPresentationTimestampsToDurationsVu( diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/AnnexBUtilsTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/AnnexBUtilsTest.java index 2a59163de6..392bdfd262 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/AnnexBUtilsTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/AnnexBUtilsTest.java @@ -17,6 +17,7 @@ package androidx.media3.muxer; import static androidx.media3.common.util.Util.getBytesFromHexString; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.common.collect.ImmutableList; @@ -29,76 +30,109 @@ import org.junit.runner.RunWith; public class AnnexBUtilsTest { @Test public void findNalUnits_emptyBuffer_returnsEmptyList() { - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("")); + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("")); - ImmutableList components = AnnexBUtils.findNalUnits(buf); + ImmutableList components = AnnexBUtils.findNalUnits(buffer); assertThat(components).isEmpty(); } @Test - public void findNalUnits_noNalUnit_returnsEmptyList() { - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("ABCDEFABC")); + public void findNalUnits_noNalUnit_throws() { + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("ABCDEFABC")); - ImmutableList components = AnnexBUtils.findNalUnits(buf); - - assertThat(components).isEmpty(); + assertThrows(IllegalStateException.class, () -> AnnexBUtils.findNalUnits(buffer)); } @Test - public void findNalUnits_singleNalUnit_returnsSingleElement() { - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF")); + public void findNalUnits_singleNalUnitWithFourByteStartCode_returnsSingleElement() { + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF")); - ImmutableList components = AnnexBUtils.findNalUnits(buf); + ImmutableList components = AnnexBUtils.findNalUnits(buffer); - assertThat(components).hasSize(1); - assertThat(components.get(0)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF"))); + assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF"))); } @Test - public void findNalUnits_multipleNalUnits_allReturned() { - ByteBuffer buf = + public void findNalUnits_singleNalUnitWithThreeByteStartCode_returnsSingleElement() { + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("000001ABCDEF")); + + ImmutableList components = AnnexBUtils.findNalUnits(buffer); + + assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF"))); + } + + @Test + public void findNalUnits_multipleNalUnitsWithFourByteStartCode_allReturned() { + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF00000001DDCC00000001BBAA")); - ImmutableList components = AnnexBUtils.findNalUnits(buf); + ImmutableList components = AnnexBUtils.findNalUnits(buffer); - assertThat(components).hasSize(3); - assertThat(components.get(0)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF"))); - assertThat(components.get(1)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("DDCC"))); - assertThat(components.get(2)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("BBAA"))); + assertThat(components) + .containsExactly( + ByteBuffer.wrap(getBytesFromHexString("ABCDEF")), + ByteBuffer.wrap(getBytesFromHexString("DDCC")), + ByteBuffer.wrap(getBytesFromHexString("BBAA"))) + .inOrder(); } @Test - public void findNalUnits_partialStartCodes_ignored() { + public void findNalUnits_multipleNalUnitsWithThreeByteStartCode_allReturned() { + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("000001ABCDEF000001DDCC000001BBAA")); + + ImmutableList components = AnnexBUtils.findNalUnits(buffer); + + assertThat(components) + .containsExactly( + ByteBuffer.wrap(getBytesFromHexString("ABCDEF")), + ByteBuffer.wrap(getBytesFromHexString("DDCC")), + ByteBuffer.wrap(getBytesFromHexString("BBAA"))) + .inOrder(); + } + + @Test + public void findNalUnits_withTrainingZeroesFollowedByGarbageData_throws() { // The NAL unit has lots of zeros but no start code. - ByteBuffer buf = + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF0000AB0000CDEF00000000AB")); - ImmutableList components = AnnexBUtils.findNalUnits(buf); - - assertThat(components).hasSize(1); - assertThat(components.get(0)) - .isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF0000AB0000CDEF00000000AB"))); + assertThrows(IllegalStateException.class, () -> AnnexBUtils.findNalUnits(buffer)); } @Test - public void findNalUnits_startCodeWithManyZeros_stillSplits() { - // The NAL unit has a start code that starts with more than 3 zeros (although too many zeros - // aren't allowed). - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB")); + public void findNalUnits_withTrailingZeroes_stripsTrailingZeroes() { + // The first NAL unit has some training zeroes. + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB")); - ImmutableList components = AnnexBUtils.findNalUnits(buf); + ImmutableList components = AnnexBUtils.findNalUnits(buffer); - assertThat(components).hasSize(2); - assertThat(components.get(0)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF0000"))); - assertThat(components.get(1)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("AB"))); + assertThat(components) + .containsExactly( + ByteBuffer.wrap(getBytesFromHexString("ABCDEF")), + ByteBuffer.wrap(getBytesFromHexString("AB"))) + .inOrder(); + } + + @Test + public void findNalUnits_withBothThreeBytesAndFourBytesNalStartCode_returnsAllNalUnits() { + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000001AB000001CDEF")); + + ImmutableList components = AnnexBUtils.findNalUnits(buffer); + + assertThat(components) + .containsExactly( + ByteBuffer.wrap(getBytesFromHexString("ABCDEF")), + ByteBuffer.wrap(getBytesFromHexString("AB")), + ByteBuffer.wrap(getBytesFromHexString("CDEF"))) + .inOrder(); } @Test public void stripEmulationPrevention_noEmulationPreventionBytes_copiesInput() { - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB")); + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB")); - ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buf); + ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buffer); assertThat(output) .isEqualTo(ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB"))); @@ -107,9 +141,9 @@ public class AnnexBUtilsTest { @Test public void stripEmulationPrevention_emulationPreventionPresent_bytesStripped() { // The NAL unit has a 00 00 03 * sequence. - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF00000300000001AB")); + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF00000300000001AB")); - ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buf); + ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buffer); assertThat(output) .isEqualTo(ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB"))); @@ -118,9 +152,9 @@ public class AnnexBUtilsTest { @Test public void stripEmulationPrevention_03WithoutEnoughZeros_notStripped() { // The NAL unit has a 03 byte around, but not preceded by enough zeros. - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("ABCDEFABCD0003EFABCD03ABCD")); + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("ABCDEFABCD0003EFABCD03ABCD")); - ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buf); + ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buffer); assertThat(output) .isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEFABCD0003EFABCD03ABCD"))); @@ -129,9 +163,9 @@ public class AnnexBUtilsTest { @Test public void stripEmulationPrevention_03AtEnd_stripped() { // The NAL unit has a 03 byte at the very end of the input. - ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("ABCDEF000003")); + ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("ABCDEF000003")); - ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buf); + ByteBuffer output = AnnexBUtils.stripEmulationPrevention(buffer); assertThat(output).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF0000"))); } diff --git a/libraries/muxer/src/test/java/androidx/media3/muxer/DefaultAnnexBToAvccConverterTest.java b/libraries/muxer/src/test/java/androidx/media3/muxer/DefaultAnnexBToAvccConverterTest.java index 89e7939d09..74ffdfa033 100644 --- a/libraries/muxer/src/test/java/androidx/media3/muxer/DefaultAnnexBToAvccConverterTest.java +++ b/libraries/muxer/src/test/java/androidx/media3/muxer/DefaultAnnexBToAvccConverterTest.java @@ -16,6 +16,7 @@ package androidx.media3.muxer; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import androidx.test.ext.junit.runners.AndroidJUnit4; import java.nio.ByteBuffer; @@ -27,55 +28,49 @@ import org.junit.runner.RunWith; public final class DefaultAnnexBToAvccConverterTest { @Test public void convertAnnexBToAvcc_singleNalUnit() { - ByteBuffer in = generateFakeNalUnitData(1000); + ByteBuffer input = generateFakeNalUnitData(1000); // Add start code for the NAL unit. - in.put(0, (byte) 0); - in.put(1, (byte) 0); - in.put(2, (byte) 0); - in.put(3, (byte) 1); + input.put(0, (byte) 0); + input.put(1, (byte) 0); + input.put(2, (byte) 0); + input.put(3, (byte) 1); AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT; - annexBToAvccConverter.process(in); + ByteBuffer output = annexBToAvccConverter.process(input); // The start code should get replaced with the length of the NAL unit. - assertThat(in.getInt(0)).isEqualTo(996); + assertThat(output.getInt(0)).isEqualTo(996); } @Test public void convertAnnexBToAvcc_twoNalUnits() { - ByteBuffer in = generateFakeNalUnitData(1000); + ByteBuffer input = generateFakeNalUnitData(1000); // Add start code for the first NAL unit. - in.put(0, (byte) 0); - in.put(1, (byte) 0); - in.put(2, (byte) 0); - in.put(3, (byte) 1); + input.put(0, (byte) 0); + input.put(1, (byte) 0); + input.put(2, (byte) 0); + input.put(3, (byte) 1); // Add start code for the second NAL unit. - in.put(600, (byte) 0); - in.put(601, (byte) 0); - in.put(602, (byte) 0); - in.put(603, (byte) 1); + input.put(600, (byte) 0); + input.put(601, (byte) 0); + input.put(602, (byte) 0); + input.put(603, (byte) 1); AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT; - annexBToAvccConverter.process(in); + ByteBuffer output = annexBToAvccConverter.process(input); // Both the NAL units should have length headers. - assertThat(in.getInt(0)).isEqualTo(596); - assertThat(in.getInt(600)).isEqualTo(396); + assertThat(output.getInt(0)).isEqualTo(596); + assertThat(output.getInt(600)).isEqualTo(396); } @Test - public void convertAnnexBToAvcc_noNalUnit_outputSameAsInput() { + public void convertAnnexBToAvcc_noNalUnit_throws() { ByteBuffer input = generateFakeNalUnitData(1000); - ByteBuffer inputCopy = ByteBuffer.allocate(input.limit()); - inputCopy.put(input); - input.rewind(); - inputCopy.rewind(); AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT; - annexBToAvccConverter.process(input); - - assertThat(input).isEqualTo(inputCopy); + assertThrows(IllegalStateException.class, () -> annexBToAvccConverter.process(input)); } /** Returns {@link ByteBuffer} filled with random NAL unit data without start code. */ diff --git a/libraries/test_data/src/test/assets/media/ts/sample_no_bframes.ts b/libraries/test_data/src/test/assets/media/ts/sample_no_bframes.ts new file mode 100644 index 0000000000..6514dd6772 Binary files /dev/null and b/libraries/test_data/src/test/assets/media/ts/sample_no_bframes.ts differ diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_0_orientation.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_0_orientation.mp4.dump index c253c89465..4cc51e76aa 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_0_orientation.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_0_orientation.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 colorInfo: @@ -27,5 +27,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_180_orientation.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_180_orientation.mp4.dump index e873336bb5..c300739c62 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_180_orientation.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_180_orientation.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 rotationDegrees = 180 @@ -28,5 +28,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_270_orientation.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_270_orientation.mp4.dump index ec6cecc4f0..0f65655e88 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_270_orientation.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_270_orientation.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 rotationDegrees = 270 @@ -28,5 +28,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_90_orientation.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_90_orientation.mp4.dump index 0b7b8ee4af..b512d6677d 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_90_orientation.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_90_orientation.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 rotationDegrees = 90 @@ -28,5 +28,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_different_tracks_offset.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_different_tracks_offset.mp4.dump index e9c10110cc..082a240186 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_different_tracks_offset.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_different_tracks_offset.mp4.dump @@ -2,18 +2,18 @@ seekMap: isSeekable = true duration = 200 getPosition(0) = [[timeUs=0, position=44]] - getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=99]] - getPosition(100) = [[timeUs=100, position=99]] - getPosition(200) = [[timeUs=100, position=99]] + getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=100]] + getPosition(100) = [[timeUs=100, position=100]] + getPosition(200) = [[timeUs=100, position=100]] numberOfTracks = 2 track 0: - total output bytes = 110 + total output bytes = 112 sample count = 2 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 frameRate = 20000.0 @@ -28,19 +28,19 @@ track 0: sample 0: time = 0 flags = 1 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E sample 1: time = 100 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E track 1: - total output bytes = 110 + total output bytes = 112 sample count = 2 format 0: id = 2 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 frameRate = 10000.0 @@ -55,9 +55,9 @@ track 1: sample 0: time = 0 flags = 1 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E sample 1: time = 200 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_float_metadata.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_float_metadata.mp4.dump index 97fb3af1ae..6d0ba01a7e 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_float_metadata.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_float_metadata.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 colorInfo: @@ -27,5 +27,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_frame_rate.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_frame_rate.mp4.dump index ff6af8caf2..6a6307bacf 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_frame_rate.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_frame_rate.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 colorInfo: @@ -27,5 +27,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_location.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_location.mp4.dump index 1464f93898..6edb9fe0cb 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_location.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_location.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 colorInfo: @@ -27,5 +27,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_null_location.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_null_location.mp4.dump index c253c89465..4cc51e76aa 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_null_location.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_null_location.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 colorInfo: @@ -27,5 +27,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_same_tracks_offset.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_same_tracks_offset.mp4.dump index e9c10110cc..082a240186 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_same_tracks_offset.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_same_tracks_offset.mp4.dump @@ -2,18 +2,18 @@ seekMap: isSeekable = true duration = 200 getPosition(0) = [[timeUs=0, position=44]] - getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=99]] - getPosition(100) = [[timeUs=100, position=99]] - getPosition(200) = [[timeUs=100, position=99]] + getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=100]] + getPosition(100) = [[timeUs=100, position=100]] + getPosition(200) = [[timeUs=100, position=100]] numberOfTracks = 2 track 0: - total output bytes = 110 + total output bytes = 112 sample count = 2 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 frameRate = 20000.0 @@ -28,19 +28,19 @@ track 0: sample 0: time = 0 flags = 1 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E sample 1: time = 100 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E track 1: - total output bytes = 110 + total output bytes = 112 sample count = 2 format 0: id = 2 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 frameRate = 10000.0 @@ -55,9 +55,9 @@ track 1: sample 0: time = 0 flags = 1 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E sample 1: time = 200 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_samples_and_metadata.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_samples_and_metadata.mp4.dump index 39988d902a..3dbc845250 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_samples_and_metadata.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_samples_and_metadata.mp4.dump @@ -1,7 +1,7 @@ ftyp (28 bytes): Data = length 20, hash EF896440 -mdat (71 bytes): - Data = length 55, hash 6B19F4A7 +mdat (72 bytes): + Data = length 56, hash DB5662FB moov (873 bytes): mvhd (108 bytes): Data = length 100, hash 2613A5C @@ -33,7 +33,7 @@ moov (873 bytes): stts (24 bytes): Data = length 16, hash E4FC6483 stsz (24 bytes): - Data = length 16, hash 50B7F5B9 + Data = length 16, hash 50B7F5BA stsc (28 bytes): Data = length 20, hash 8F6E8285 co64 (24 bytes): diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_string_metadata.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_string_metadata.mp4.dump index 413c2b18f9..146fe71a14 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_string_metadata.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_string_metadata.mp4.dump @@ -7,13 +7,13 @@ seekMap: getPosition(0) = [[timeUs=0, position=44]] numberOfTracks = 1 track 0: - total output bytes = 55 + total output bytes = 56 sample count = 1 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.F4000A - maxInputSize = 85 + maxInputSize = 86 width = 12 height = 10 colorInfo: @@ -27,5 +27,5 @@ track 0: sample 0: time = 0 flags = 536870913 - data = length 55, hash A481CEF4 + data = length 56, hash C4551A2E tracksEnded = true diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_xmp.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_xmp.mp4.dump index 749009974e..d34937de98 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_with_xmp.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_with_xmp.mp4.dump @@ -1,7 +1,7 @@ ftyp (28 bytes): Data = length 20, hash EF896440 -mdat (71 bytes): - Data = length 55, hash 6B19F4A7 +mdat (72 bytes): + Data = length 56, hash DB5662FB moov (658 bytes): mvhd (108 bytes): Data = length 100, hash 2613A5C @@ -24,7 +24,7 @@ moov (658 bytes): stts (24 bytes): Data = length 16, hash E4FC6483 stsz (24 bytes): - Data = length 16, hash 50B7F5B9 + Data = length 16, hash 50B7F5BA stsc (28 bytes): Data = length 20, hash 8F6E8285 co64 (24 bytes): diff --git a/libraries/test_data/src/test/assets/muxerdumps/mp4_without_empty_track.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/mp4_without_empty_track.mp4.dump index a08fe51c0f..29077ca72e 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/mp4_without_empty_track.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/mp4_without_empty_track.mp4.dump @@ -1,7 +1,7 @@ ftyp (28 bytes): Data = length 20, hash EF896440 -mdat (126 bytes): - Data = length 110, hash 48173D41 +mdat (128 bytes): + Data = length 112, hash 1AAF8FF5 moov (674 bytes): mvhd (108 bytes): Data = length 100, hash 105FA889 @@ -24,7 +24,7 @@ moov (674 bytes): stts (32 bytes): Data = length 24, hash 4A7D0E0E stsz (28 bytes): - Data = length 20, hash 3828E071 + Data = length 20, hash 3836F7F3 stsc (28 bytes): Data = length 20, hash 8F7C9A06 co64 (24 bytes): diff --git a/libraries/test_data/src/test/assets/muxerdumps/sample_no_bframes.mp4.dump b/libraries/test_data/src/test/assets/muxerdumps/sample_no_bframes.mp4.dump index 1a3b7e3047..f6eede36e2 100644 --- a/libraries/test_data/src/test/assets/muxerdumps/sample_no_bframes.mp4.dump +++ b/libraries/test_data/src/test/assets/muxerdumps/sample_no_bframes.mp4.dump @@ -201,13 +201,13 @@ track 0: flags = 536870913 data = length 6, hash 31B22286 track 1: - total output bytes = 301392 + total output bytes = 301222 sample count = 30 format 0: id = 2 sampleMimeType = video/avc codecs = avc1.640034 - maxInputSize = 22910 + maxInputSize = 22904 width = 1080 height = 720 frameRate = 31.004547 @@ -224,7 +224,7 @@ track 1: sample 0: time = 0 flags = 1 - data = length 7744, hash DDF91733 + data = length 7735, hash 87BD736D sample 1: time = 33355 flags = 0 @@ -232,7 +232,7 @@ track 1: sample 2: time = 66722 flags = 0 - data = length 960, hash D4AD9EF0 + data = length 955, hash 9626CB10 sample 3: time = 100100 flags = 0 @@ -248,7 +248,7 @@ track 1: sample 6: time = 200200 flags = 0 - data = length 3856, hash 3D7DCD46 + data = length 3850, hash 6E1AB9C6 sample 7: time = 233555 flags = 0 @@ -256,7 +256,7 @@ track 1: sample 8: time = 266922 flags = 0 - data = length 6144, hash 8FD9AD7D + data = length 6137, hash AAA5D523 sample 9: time = 300300 flags = 0 @@ -268,43 +268,43 @@ track 1: sample 11: time = 367022 flags = 0 - data = length 14496, hash E0F2E0BA + data = length 14490, hash C5F0743A sample 12: time = 400400 flags = 0 - data = length 17664, hash 3E3189E + data = length 17652, hash 63C959E sample 13: time = 433755 flags = 0 - data = length 5712, hash CA808ECF + data = length 5703, hash 44AFBC51 sample 14: time = 467122 flags = 0 - data = length 9776, hash C875D1AA + data = length 9766, hash 77D5FE2A sample 15: time = 500500 flags = 0 - data = length 17712, hash 69AE17D4 + data = length 17704, hash CF5D2BD4 sample 16: time = 533855 flags = 0 - data = length 11440, hash 7370E78C + data = length 11436, hash 9DCA8D8C sample 17: time = 567222 flags = 0 - data = length 8544, hash 5A581986 + data = length 8540, hash BEBDCC86 sample 18: time = 600600 flags = 0 - data = length 19904, hash 98AB5C44 + data = length 19894, hash 6212144 sample 19: time = 633955 flags = 0 - data = length 14352, hash 74B754E3 + data = length 14342, hash EC6E84A3 sample 20: time = 667322 flags = 0 - data = length 9568, hash 369746A6 + data = length 9555, hash 601CE39A sample 21: time = 700700 flags = 0 @@ -312,33 +312,33 @@ track 1: sample 22: time = 734055 flags = 0 - data = length 22880, hash 75E833BA + data = length 22874, hash 4620073A sample 23: time = 767422 flags = 0 - data = length 16832, hash E8BFCFE3 + data = length 16821, hash 95A33FFD sample 24: time = 800800 flags = 0 - data = length 5120, hash E04AEF94 + data = length 5114, hash F0CB2E94 sample 25: time = 834155 flags = 0 - data = length 9888, hash 1166103E + data = length 9884, hash 103DF3E sample 26: time = 867522 flags = 0 - data = length 17024, hash F9A96740 + data = length 17011, hash 82DFD0C0 sample 27: time = 900900 flags = 0 - data = length 20864, hash DF9E88B8 + data = length 20860, hash 5DEFA4B8 sample 28: time = 934255 flags = 0 - data = length 7216, hash BE22BE2F + data = length 7209, hash AFAC48B1 sample 29: time = 967622 flags = 536870912 - data = length 14240, hash E190BF31 + data = length 14234, hash 101837F1 tracksEnded = true diff --git a/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_no_bframes.mp4/transmuxed_with_inappmuxer.dump b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_no_bframes.mp4/transmuxed_with_inappmuxer.dump index d9678a62bf..b0535ed985 100644 --- a/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_no_bframes.mp4/transmuxed_with_inappmuxer.dump +++ b/libraries/test_data/src/test/assets/transformerdumps/mp4/sample_no_bframes.mp4/transmuxed_with_inappmuxer.dump @@ -7,13 +7,13 @@ seekMap: getPosition(1065600) = [[timeUs=0, position=44]] numberOfTracks = 2 track 0: - total output bytes = 301392 + total output bytes = 301222 sample count = 30 format 0: id = 1 sampleMimeType = video/avc codecs = avc1.640034 - maxInputSize = 22910 + maxInputSize = 22904 width = 1080 height = 720 frameRate = 31.004547 @@ -30,7 +30,7 @@ track 0: sample 0: time = 0 flags = 1 - data = length 7744, hash DDF91733 + data = length 7735, hash 87BD736D sample 1: time = 33355 flags = 0 @@ -38,7 +38,7 @@ track 0: sample 2: time = 66722 flags = 0 - data = length 960, hash D4AD9EF0 + data = length 955, hash 9626CB10 sample 3: time = 100100 flags = 0 @@ -54,7 +54,7 @@ track 0: sample 6: time = 200200 flags = 0 - data = length 3856, hash 3D7DCD46 + data = length 3850, hash 6E1AB9C6 sample 7: time = 233555 flags = 0 @@ -62,7 +62,7 @@ track 0: sample 8: time = 266922 flags = 0 - data = length 6144, hash 8FD9AD7D + data = length 6137, hash AAA5D523 sample 9: time = 300300 flags = 0 @@ -74,43 +74,43 @@ track 0: sample 11: time = 367022 flags = 0 - data = length 14496, hash E0F2E0BA + data = length 14490, hash C5F0743A sample 12: time = 400400 flags = 0 - data = length 17664, hash 3E3189E + data = length 17652, hash 63C959E sample 13: time = 433755 flags = 0 - data = length 5712, hash CA808ECF + data = length 5703, hash 44AFBC51 sample 14: time = 467122 flags = 0 - data = length 9776, hash C875D1AA + data = length 9766, hash 77D5FE2A sample 15: time = 500500 flags = 0 - data = length 17712, hash 69AE17D4 + data = length 17704, hash CF5D2BD4 sample 16: time = 533855 flags = 0 - data = length 11440, hash 7370E78C + data = length 11436, hash 9DCA8D8C sample 17: time = 567222 flags = 0 - data = length 8544, hash 5A581986 + data = length 8540, hash BEBDCC86 sample 18: time = 600600 flags = 0 - data = length 19904, hash 98AB5C44 + data = length 19894, hash 6212144 sample 19: time = 633955 flags = 0 - data = length 14352, hash 74B754E3 + data = length 14342, hash EC6E84A3 sample 20: time = 667322 flags = 0 - data = length 9568, hash 369746A6 + data = length 9555, hash 601CE39A sample 21: time = 700700 flags = 0 @@ -118,35 +118,35 @@ track 0: sample 22: time = 734055 flags = 0 - data = length 22880, hash 75E833BA + data = length 22874, hash 4620073A sample 23: time = 767422 flags = 0 - data = length 16832, hash E8BFCFE3 + data = length 16821, hash 95A33FFD sample 24: time = 800800 flags = 0 - data = length 5120, hash E04AEF94 + data = length 5114, hash F0CB2E94 sample 25: time = 834155 flags = 0 - data = length 9888, hash 1166103E + data = length 9884, hash 103DF3E sample 26: time = 867522 flags = 0 - data = length 17024, hash F9A96740 + data = length 17011, hash 82DFD0C0 sample 27: time = 900900 flags = 0 - data = length 20864, hash DF9E88B8 + data = length 20860, hash 5DEFA4B8 sample 28: time = 934255 flags = 0 - data = length 7216, hash BE22BE2F + data = length 7209, hash AFAC48B1 sample 29: time = 967622 flags = 536870912 - data = length 14240, hash E190BF31 + data = length 14234, hash 101837F1 track 1: total output bytes = 9529 sample count = 45 diff --git a/libraries/test_data/src/test/assets/transformerdumps/ts/sample_no_bframes.ts/transmuxed_with_inappmuxer.dump b/libraries/test_data/src/test/assets/transformerdumps/ts/sample_no_bframes.ts/transmuxed_with_inappmuxer.dump new file mode 100644 index 0000000000..ae148e8a07 --- /dev/null +++ b/libraries/test_data/src/test/assets/transformerdumps/ts/sample_no_bframes.ts/transmuxed_with_inappmuxer.dump @@ -0,0 +1,344 @@ +seekMap: + isSeekable = true + duration = 1065600 + getPosition(0) = [[timeUs=0, position=44]] + getPosition(1) = [[timeUs=0, position=44]] + getPosition(532800) = [[timeUs=0, position=44]] + getPosition(1065600) = [[timeUs=0, position=44]] +numberOfTracks = 2 +track 0: + total output bytes = 301430 + sample count = 30 + format 0: + id = 1 + sampleMimeType = video/avc + codecs = avc1.640034 + maxInputSize = 22910 + width = 1080 + height = 720 + frameRate = 31.004547 + colorInfo: + colorSpace = 1 + colorRange = 2 + colorTransfer = 3 + lumaBitdepth = 8 + chromaBitdepth = 8 + metadata = entries=[Mp4Timestamp: creation time=3000000000, modification time=4000000000, timescale=10000] + initializationData: + data = length 24, hash 489E4AD2 + data = length 9, hash FBAFBC0C + sample 0: + time = 0 + flags = 1 + data = length 7773, hash 97BEE8E7 + sample 1: + time = 33355 + flags = 0 + data = length 1175, hash 875A0AD8 + sample 2: + time = 66722 + flags = 0 + data = length 961, hash 39E26608 + sample 3: + time = 100100 + flags = 0 + data = length 979, hash 9843943C + sample 4: + time = 133455 + flags = 0 + data = length 1367, hash C795AD6F + sample 5: + time = 166822 + flags = 0 + data = length 2295, hash 90C61830 + sample 6: + time = 200200 + flags = 0 + data = length 3856, hash E5178FCE + sample 7: + time = 233555 + flags = 0 + data = length 4055, hash DC2E0B64 + sample 8: + time = 266922 + flags = 0 + data = length 6143, hash 6C8C0E1B + sample 9: + time = 300300 + flags = 0 + data = length 7639, hash 40ABA1B0 + sample 10: + time = 333655 + flags = 0 + data = length 9795, hash 824B4D8D + sample 11: + time = 367022 + flags = 0 + data = length 14496, hash D3EEBA42 + sample 12: + time = 400400 + flags = 0 + data = length 17658, hash 12A7A1A6 + sample 13: + time = 433755 + flags = 0 + data = length 5709, hash ACD2349 + sample 14: + time = 467122 + flags = 0 + data = length 9772, hash B00DF832 + sample 15: + time = 500500 + flags = 0 + data = length 17710, hash 366FC3DC + sample 16: + time = 533855 + flags = 0 + data = length 11442, hash 9B226194 + sample 17: + time = 567222 + flags = 0 + data = length 8546, hash D86DF08E + sample 18: + time = 600600 + flags = 0 + data = length 19900, hash C6748B4C + sample 19: + time = 633955 + flags = 0 + data = length 14348, hash D91E9EAB + sample 20: + time = 667322 + flags = 0 + data = length 9561, hash 819E9692 + sample 21: + time = 700700 + flags = 0 + data = length 12195, hash 895ADC1E + sample 22: + time = 734055 + flags = 0 + data = length 22880, hash A618D42 + sample 23: + time = 767422 + flags = 0 + data = length 16827, hash 4E6A74F5 + sample 24: + time = 800800 + flags = 0 + data = length 5120, hash 3EF0149C + sample 25: + time = 834155 + flags = 0 + data = length 9890, hash 32BAC346 + sample 26: + time = 867522 + flags = 0 + data = length 17017, hash 2D24A3B8 + sample 27: + time = 900900 + flags = 0 + data = length 20866, hash 219DA8C0 + sample 28: + time = 934255 + flags = 0 + data = length 7215, hash A853B1A9 + sample 29: + time = 967622 + flags = 536870912 + data = length 14240, hash 4EE77DF9 +track 1: + total output bytes = 9529 + sample count = 45 + format 0: + id = 2 + sampleMimeType = audio/mp4a-latm + codecs = mp4a.40.2 + maxInputSize = 294 + channelCount = 1 + sampleRate = 44100 + language = und + metadata = entries=[Mp4Timestamp: creation time=3000000000, modification time=4000000000, timescale=10000] + initializationData: + data = length 2, hash 5F7 + sample 0: + time = 0 + flags = 1 + data = length 23, hash 47DE9131 + sample 1: + time = 67187 + flags = 1 + data = length 6, hash 31EC5206 + sample 2: + time = 90416 + flags = 1 + data = length 148, hash 894A176B + sample 3: + time = 113625 + flags = 1 + data = length 189, hash CEF235A1 + sample 4: + time = 136854 + flags = 1 + data = length 205, hash BBF5F7B0 + sample 5: + time = 160062 + flags = 1 + data = length 210, hash F278B193 + sample 6: + time = 183291 + flags = 1 + data = length 210, hash 82DA1589 + sample 7: + time = 206520 + flags = 1 + data = length 207, hash 5BE231DF + sample 8: + time = 229729 + flags = 1 + data = length 225, hash 18819EE1 + sample 9: + time = 252958 + flags = 1 + data = length 215, hash CA7FA67B + sample 10: + time = 276166 + flags = 1 + data = length 211, hash 581A1C18 + sample 11: + time = 299395 + flags = 1 + data = length 216, hash ADB88187 + sample 12: + time = 322604 + flags = 1 + data = length 229, hash 2E8BA4DC + sample 13: + time = 345833 + flags = 1 + data = length 232, hash 22F0C510 + sample 14: + time = 369041 + flags = 1 + data = length 235, hash 867AD0DC + sample 15: + time = 392270 + flags = 1 + data = length 231, hash 84E823A8 + sample 16: + time = 415500 + flags = 1 + data = length 226, hash 1BEF3A95 + sample 17: + time = 438708 + flags = 1 + data = length 216, hash EAA345AE + sample 18: + time = 461937 + flags = 1 + data = length 229, hash 6957411F + sample 19: + time = 485145 + flags = 1 + data = length 219, hash 41275022 + sample 20: + time = 508375 + flags = 1 + data = length 241, hash 6495DF96 + sample 21: + time = 531583 + flags = 1 + data = length 228, hash 63D95906 + sample 22: + time = 554812 + flags = 1 + data = length 238, hash 34F676F9 + sample 23: + time = 578020 + flags = 1 + data = length 234, hash E5CBC045 + sample 24: + time = 601250 + flags = 1 + data = length 231, hash 5FC43661 + sample 25: + time = 624458 + flags = 1 + data = length 217, hash 682708ED + sample 26: + time = 647687 + flags = 1 + data = length 239, hash D43780FC + sample 27: + time = 670916 + flags = 1 + data = length 243, hash C5E17980 + sample 28: + time = 694145 + flags = 1 + data = length 231, hash AC5837BA + sample 29: + time = 717354 + flags = 1 + data = length 230, hash 169EE895 + sample 30: + time = 740583 + flags = 1 + data = length 238, hash C48FF3F1 + sample 31: + time = 763791 + flags = 1 + data = length 225, hash 531E4599 + sample 32: + time = 787020 + flags = 1 + data = length 232, hash CB3C6B8D + sample 33: + time = 810229 + flags = 1 + data = length 243, hash F8C94C7 + sample 34: + time = 833458 + flags = 1 + data = length 232, hash A646A7D0 + sample 35: + time = 856666 + flags = 1 + data = length 237, hash E8B787A5 + sample 36: + time = 879895 + flags = 1 + data = length 228, hash 3FA7A29F + sample 37: + time = 903104 + flags = 1 + data = length 235, hash B9B33B0A + sample 38: + time = 926333 + flags = 1 + data = length 264, hash 71A4869E + sample 39: + time = 949562 + flags = 1 + data = length 257, hash D049B54C + sample 40: + time = 972770 + flags = 1 + data = length 227, hash 66757231 + sample 41: + time = 996000 + flags = 1 + data = length 227, hash BD374F1B + sample 42: + time = 1019208 + flags = 1 + data = length 235, hash 999477F6 + sample 43: + time = 1042437 + flags = 1 + data = length 229, hash FFF98DF0 + sample 44: + time = 1065645 + flags = 536870913 + data = length 6, hash 31B22286 +tracksEnded = true diff --git a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java index 6b76d870a6..0f86a6323b 100644 --- a/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java +++ b/libraries/transformer/src/test/java/androidx/media3/transformer/TransformerWithInAppMuxerEndToEndTest.java @@ -16,6 +16,7 @@ package androidx.media3.transformer; import static androidx.media3.common.util.Assertions.checkState; +import static androidx.media3.test.utils.TestUtil.extractAllSamplesFromFilePath; import static androidx.media3.test.utils.TestUtil.retrieveTrackFormat; import static com.google.common.truth.Truth.assertThat; @@ -51,6 +52,7 @@ import org.junit.runner.RunWith; public class TransformerWithInAppMuxerEndToEndTest { private static final String MP4_FILE_PATH = "asset:///media/mp4/sample_no_bframes.mp4"; private static final String MP4_FILE_NAME = "mp4/sample_no_bframes.mp4"; + @Rule public final TemporaryFolder outputDir = new TemporaryFolder(); private final Context context = ApplicationProvider.getApplicationContext(); @@ -85,7 +87,7 @@ public class TransformerWithInAppMuxerEndToEndTest { TransformerTestRunner.runLooper(transformer); FakeExtractorOutput fakeExtractorOutput = - androidx.media3.test.utils.TestUtil.extractAllSamplesFromFilePath( + extractAllSamplesFromFilePath( new Mp4Extractor(new DefaultSubtitleParserFactory()), outputPath); DumpFileAsserts.assertOutput( context, @@ -95,6 +97,46 @@ public class TransformerWithInAppMuxerEndToEndTest { /* modifications...= */ "transmuxed_with_inappmuxer")); } + @Test + public void transmux_tsFileHavingThreeByteNalStartCode_outputMatchesExpected() throws Exception { + String tsFilePath = "asset:///media/ts/sample_no_bframes.ts"; + String tsFileName = "ts/sample_no_bframes.ts"; + Muxer.Factory inAppMuxerFactory = + new InAppMuxer.Factory.Builder() + .setMetadataProvider( + metadataEntries -> + // Add timestamp to make output file deterministic. + metadataEntries.add( + new Mp4TimestampData( + /* creationTimestampSeconds= */ 3_000_000_000L, + /* modificationTimestampSeconds= */ 4_000_000_000L))) + .build(); + + Transformer transformer = + new Transformer.Builder(context) + .setClock(new FakeClock(/* isAutoAdvancing= */ true)) + .setMuxerFactory(inAppMuxerFactory) + .build(); + MediaItem mediaItem = + new MediaItem.Builder() + .setUri(Uri.parse(tsFilePath)) + .setClippingConfiguration( + new MediaItem.ClippingConfiguration.Builder().setEndPositionMs(2_000).build()) + .build(); + transformer.start(mediaItem, outputPath); + TransformerTestRunner.runLooper(transformer); + + FakeExtractorOutput fakeExtractorOutput = + extractAllSamplesFromFilePath( + new Mp4Extractor(new DefaultSubtitleParserFactory()), outputPath); + DumpFileAsserts.assertOutput( + context, + fakeExtractorOutput, + TestUtil.getDumpFileName( + /* originalFileName= */ tsFileName, + /* modifications...= */ "transmuxed_with_inappmuxer")); + } + @Test public void transmux_withLocationMetadata_writesSameLocationMetadata() throws Exception { Mp4LocationData expectedLocationData =