Add support for processing 3 byte NAL start code in Mp4Muxer

Issue: androidx/media#725

Ideally the test to transmux a `ts` file (having 3 bytes NAL start code)
should go into `Mp4MuxerEndToEndTest`but `Mp4MuxerEndToEndTest` uses `MediaExtractor` and `MediaExtractor` is returning samples with `4 byte NAL start code` which will not exercise the newly added code path.
Hence the test is added in `TransformerWithInAppMuxerEndToEndTest` which
internally uses `media3 extractor` and feeds samples with `3 bytes NAL start code`
only.

PiperOrigin-RevId: 617985866
This commit is contained in:
sheenachhabra 2024-03-21 15:46:24 -07:00 committed by Copybara-Service
parent 6fc4f0263f
commit 16aac07bce
25 changed files with 723 additions and 228 deletions

View File

@ -32,7 +32,7 @@ public interface AnnexBToAvccConverter {
AnnexBToAvccConverter DEFAULT = AnnexBToAvccConverter DEFAULT =
(ByteBuffer inputBuffer) -> { (ByteBuffer inputBuffer) -> {
if (!inputBuffer.hasRemaining()) { if (!inputBuffer.hasRemaining()) {
return; return inputBuffer;
} }
checkArgument( checkArgument(
@ -40,25 +40,33 @@ public interface AnnexBToAvccConverter {
ImmutableList<ByteBuffer> nalUnitList = AnnexBUtils.findNalUnits(inputBuffer); ImmutableList<ByteBuffer> nalUnitList = AnnexBUtils.findNalUnits(inputBuffer);
int totalBytesNeeded = 0;
for (int i = 0; i < nalUnitList.size(); i++) { for (int i = 0; i < nalUnitList.size(); i++) {
int currentNalUnitLength = nalUnitList.get(i).remaining(); // 4 bytes to store NAL unit length.
totalBytesNeeded += 4 + 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);
} }
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}.
* *
* <p>Expects a {@link ByteBuffer} input with a zero offset. * <p>Expects a {@link ByteBuffer} input with a zero offset.
* *
* @param inputBuffer The buffer to be converted. * @param inputBuffer The buffer to be converted.
*/ */
void process(ByteBuffer inputBuffer); ByteBuffer process(ByteBuffer inputBuffer);
} }

View File

@ -15,47 +15,87 @@
*/ */
package androidx.media3.muxer; package androidx.media3.muxer;
import androidx.media3.common.C;
import androidx.media3.common.MimeTypes; import androidx.media3.common.MimeTypes;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** NAL unit utilities for start codes and emulation prevention. */ /** NAL unit utilities for start codes and emulation prevention. */
/* package */ final class AnnexBUtils { /* package */ final class AnnexBUtils {
private static final int THREE_BYTE_NAL_START_CODE_SIZE = 3;
private AnnexBUtils() {} 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).
* *
* <p>An empty list is returned if the input is not NAL units. * <p>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.
* *
* <p>The position of the input buffer is unchanged after calling this method. * <p>The input buffer must have position set to 0 and the position remains unchanged after
* calling this method.
*/ */
public static ImmutableList<ByteBuffer> findNalUnits(ByteBuffer input) { public static ImmutableList<ByteBuffer> findNalUnits(ByteBuffer input) {
if (input.remaining() < 4 || input.getInt(0) != 1) { if (input.remaining() == 0) {
return ImmutableList.of(); return ImmutableList.of();
} }
ImmutableList.Builder<ByteBuffer> nalUnits = new ImmutableList.Builder<>(); int nalStartIndex = C.INDEX_UNSET;
int inputLimit = input.limit();
boolean readingNalUnit = false;
int lastStart = 4; // The input must start with a NAL unit.
int zerosSeen = 0; for (int i = 0; i < inputLimit; i++) {
if (isThreeByteNalStartCode(input, i)) {
for (int i = 4; i < input.limit(); i++) { nalStartIndex = i + THREE_BYTE_NAL_START_CODE_SIZE;
if (input.get(i) == 1 && zerosSeen >= 3) { readingNalUnit = true;
// We're just looking at a start code. break;
nalUnits.add(getBytes(input, lastStart, i - 3 - lastStart)); } else if (input.get(i) == 0) {
lastStart = i + 1; // Skip the leading zeroes.
}
// 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++;
} else { } else {
zerosSeen = 0; throw new IllegalStateException("Sample does not start with a NAL unit");
}
}
ImmutableList.Builder<ByteBuffer> 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(); input.rewind();
@ -97,6 +137,20 @@ import java.nio.ByteBuffer;
|| sampleMimeType.equals(MimeTypes.VIDEO_H265); || 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) { private static ByteBuffer getBytes(ByteBuffer buf, int offset, int length) {
ByteBuffer result = buf.duplicate(); ByteBuffer result = buf.duplicate();
result.position(offset); result.position(offset);

View File

@ -264,6 +264,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
/** Writes out any pending samples to the file. */ /** Writes out any pending samples to the file. */
private void flushPending(Track track) throws IOException { private void flushPending(Track track) throws IOException {
checkState(track.pendingSamplesByteBuffer.size() == track.pendingSamplesBufferInfo.size());
if (track.pendingSamplesBufferInfo.isEmpty()) { if (track.pendingSamplesBufferInfo.isEmpty()) {
return; return;
} }
@ -278,13 +279,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
bytesNeededInMdat += sample.limit(); bytesNeededInMdat += sample.limit();
} }
// If the required number of bytes doesn't fit in the gap between the actual data and the moov extendMdatIfRequired(bytesNeededInMdat);
// 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);
}
track.writtenChunkOffsets.add(mdatDataEnd); track.writtenChunkOffsets.add(mdatDataEnd);
track.writtenChunkSampleCounts.add(track.pendingSamplesBufferInfo.size()); track.writtenChunkSampleCounts.add(track.pendingSamplesBufferInfo.size());
@ -293,22 +288,38 @@ import java.util.concurrent.atomic.AtomicBoolean;
BufferInfo currentSampleBufferInfo = track.pendingSamplesBufferInfo.removeFirst(); BufferInfo currentSampleBufferInfo = track.pendingSamplesBufferInfo.removeFirst();
ByteBuffer currentSampleByteBuffer = track.pendingSamplesByteBuffer.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 // Convert the H.264/H.265 samples from Annex-B format (output by MediaCodec) to
// Avcc format (required by MP4 container). // Avcc format (required by MP4 container).
if (doesSampleContainAnnexBNalUnits(checkNotNull(track.format.sampleMimeType))) { 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); mdatDataEnd += output.write(currentSampleByteBuffer, mdatDataEnd);
track.writtenSamples.add(currentSampleBufferInfo);
} while (!track.pendingSamplesBufferInfo.isEmpty()); } while (!track.pendingSamplesBufferInfo.isEmpty());
checkState(mdatDataEnd <= mdatEnd); 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 { private void updateMdatSize() throws IOException {
// Assuming that the mdat box has a 64-bit length, skip the box type (4 bytes) and // 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). // the 32-bit box length field (4 bytes).

View File

@ -277,23 +277,30 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private ProcessedTrackInfo processTrack(int trackId, Track track) { private ProcessedTrackInfo processTrack(int trackId, Track track) {
checkState(track.pendingSamplesByteBuffer.size() == track.pendingSamplesBufferInfo.size()); checkState(track.pendingSamplesByteBuffer.size() == track.pendingSamplesBufferInfo.size());
ImmutableList.Builder<ByteBuffer> pendingSamplesByteBuffer = new ImmutableList.Builder<>(); ImmutableList.Builder<ByteBuffer> pendingSamplesByteBuffer = new ImmutableList.Builder<>();
ImmutableList.Builder<BufferInfo> pendingSamplesBufferInfoBuilder =
new ImmutableList.Builder<>();
if (doesSampleContainAnnexBNalUnits(checkNotNull(track.format.sampleMimeType))) { if (doesSampleContainAnnexBNalUnits(checkNotNull(track.format.sampleMimeType))) {
while (!track.pendingSamplesByteBuffer.isEmpty()) { while (!track.pendingSamplesByteBuffer.isEmpty()) {
ByteBuffer currentSampleByteBuffer = track.pendingSamplesByteBuffer.removeFirst(); ByteBuffer currentSampleByteBuffer = track.pendingSamplesByteBuffer.removeFirst();
annexBToAvccConverter.process(currentSampleByteBuffer); currentSampleByteBuffer = annexBToAvccConverter.process(currentSampleByteBuffer);
pendingSamplesByteBuffer.add(currentSampleByteBuffer); pendingSamplesByteBuffer.add(currentSampleByteBuffer);
BufferInfo currentSampleBufferInfo = track.pendingSamplesBufferInfo.removeFirst();
currentSampleBufferInfo.set(
currentSampleByteBuffer.position(),
currentSampleByteBuffer.remaining(),
currentSampleBufferInfo.presentationTimeUs,
currentSampleBufferInfo.flags);
pendingSamplesBufferInfoBuilder.add(currentSampleBufferInfo);
} }
} else { } else {
pendingSamplesByteBuffer.addAll(track.pendingSamplesByteBuffer); pendingSamplesByteBuffer.addAll(track.pendingSamplesByteBuffer);
track.pendingSamplesByteBuffer.clear(); track.pendingSamplesByteBuffer.clear();
pendingSamplesBufferInfoBuilder.addAll(track.pendingSamplesBufferInfo);
track.pendingSamplesBufferInfo.clear();
} }
ImmutableList.Builder<BufferInfo> pendingSamplesBufferInfoBuilder =
new ImmutableList.Builder<>();
pendingSamplesBufferInfoBuilder.addAll(track.pendingSamplesBufferInfo);
track.pendingSamplesBufferInfo.clear();
ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build(); ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build();
List<Long> sampleDurations = List<Long> sampleDurations =
Boxes.convertPresentationTimestampsToDurationsVu( Boxes.convertPresentationTimestampsToDurationsVu(

View File

@ -17,6 +17,7 @@ package androidx.media3.muxer;
import static androidx.media3.common.util.Util.getBytesFromHexString; import static androidx.media3.common.util.Util.getBytesFromHexString;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -29,76 +30,109 @@ import org.junit.runner.RunWith;
public class AnnexBUtilsTest { public class AnnexBUtilsTest {
@Test @Test
public void findNalUnits_emptyBuffer_returnsEmptyList() { public void findNalUnits_emptyBuffer_returnsEmptyList() {
ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("")); ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString(""));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buf); ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
assertThat(components).isEmpty(); assertThat(components).isEmpty();
} }
@Test @Test
public void findNalUnits_noNalUnit_returnsEmptyList() { public void findNalUnits_noNalUnit_throws() {
ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("ABCDEFABC")); ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("ABCDEFABC"));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buf); assertThrows(IllegalStateException.class, () -> AnnexBUtils.findNalUnits(buffer));
assertThat(components).isEmpty();
} }
@Test @Test
public void findNalUnits_singleNalUnit_returnsSingleElement() { public void findNalUnits_singleNalUnitWithFourByteStartCode_returnsSingleElement() {
ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF")); ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF"));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buf); ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
assertThat(components).hasSize(1); assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF")));
assertThat(components.get(0)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF")));
} }
@Test @Test
public void findNalUnits_multipleNalUnits_allReturned() { public void findNalUnits_singleNalUnitWithThreeByteStartCode_returnsSingleElement() {
ByteBuffer buf = ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("000001ABCDEF"));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF")));
}
@Test
public void findNalUnits_multipleNalUnitsWithFourByteStartCode_allReturned() {
ByteBuffer buffer =
ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF00000001DDCC00000001BBAA")); ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF00000001DDCC00000001BBAA"));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buf); ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
assertThat(components).hasSize(3); assertThat(components)
assertThat(components.get(0)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF"))); .containsExactly(
assertThat(components.get(1)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("DDCC"))); ByteBuffer.wrap(getBytesFromHexString("ABCDEF")),
assertThat(components.get(2)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("BBAA"))); ByteBuffer.wrap(getBytesFromHexString("DDCC")),
ByteBuffer.wrap(getBytesFromHexString("BBAA")))
.inOrder();
} }
@Test @Test
public void findNalUnits_partialStartCodes_ignored() { public void findNalUnits_multipleNalUnitsWithThreeByteStartCode_allReturned() {
ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("000001ABCDEF000001DDCC000001BBAA"));
ImmutableList<ByteBuffer> 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. // The NAL unit has lots of zeros but no start code.
ByteBuffer buf = ByteBuffer buffer =
ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF0000AB0000CDEF00000000AB")); ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF0000AB0000CDEF00000000AB"));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buf); assertThrows(IllegalStateException.class, () -> AnnexBUtils.findNalUnits(buffer));
assertThat(components).hasSize(1);
assertThat(components.get(0))
.isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF0000AB0000CDEF00000000AB")));
} }
@Test @Test
public void findNalUnits_startCodeWithManyZeros_stillSplits() { public void findNalUnits_withTrailingZeroes_stripsTrailingZeroes() {
// The NAL unit has a start code that starts with more than 3 zeros (although too many zeros // The first NAL unit has some training zeroes.
// aren't allowed). ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB"));
ByteBuffer buf = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB"));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buf); ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
assertThat(components).hasSize(2); assertThat(components)
assertThat(components.get(0)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF0000"))); .containsExactly(
assertThat(components.get(1)).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("AB"))); ByteBuffer.wrap(getBytesFromHexString("ABCDEF")),
ByteBuffer.wrap(getBytesFromHexString("AB")))
.inOrder();
}
@Test
public void findNalUnits_withBothThreeBytesAndFourBytesNalStartCode_returnsAllNalUnits() {
ByteBuffer buffer = ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000001AB000001CDEF"));
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
assertThat(components)
.containsExactly(
ByteBuffer.wrap(getBytesFromHexString("ABCDEF")),
ByteBuffer.wrap(getBytesFromHexString("AB")),
ByteBuffer.wrap(getBytesFromHexString("CDEF")))
.inOrder();
} }
@Test @Test
public void stripEmulationPrevention_noEmulationPreventionBytes_copiesInput() { 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) assertThat(output)
.isEqualTo(ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB"))); .isEqualTo(ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB")));
@ -107,9 +141,9 @@ public class AnnexBUtilsTest {
@Test @Test
public void stripEmulationPrevention_emulationPreventionPresent_bytesStripped() { public void stripEmulationPrevention_emulationPreventionPresent_bytesStripped() {
// The NAL unit has a 00 00 03 * sequence. // 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) assertThat(output)
.isEqualTo(ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB"))); .isEqualTo(ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF000000000001AB")));
@ -118,9 +152,9 @@ public class AnnexBUtilsTest {
@Test @Test
public void stripEmulationPrevention_03WithoutEnoughZeros_notStripped() { public void stripEmulationPrevention_03WithoutEnoughZeros_notStripped() {
// The NAL unit has a 03 byte around, but not preceded by enough zeros. // 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) assertThat(output)
.isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEFABCD0003EFABCD03ABCD"))); .isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEFABCD0003EFABCD03ABCD")));
@ -129,9 +163,9 @@ public class AnnexBUtilsTest {
@Test @Test
public void stripEmulationPrevention_03AtEnd_stripped() { public void stripEmulationPrevention_03AtEnd_stripped() {
// The NAL unit has a 03 byte at the very end of the input. // 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"))); assertThat(output).isEqualTo(ByteBuffer.wrap(getBytesFromHexString("ABCDEF0000")));
} }

View File

@ -16,6 +16,7 @@
package androidx.media3.muxer; package androidx.media3.muxer;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -27,55 +28,49 @@ import org.junit.runner.RunWith;
public final class DefaultAnnexBToAvccConverterTest { public final class DefaultAnnexBToAvccConverterTest {
@Test @Test
public void convertAnnexBToAvcc_singleNalUnit() { public void convertAnnexBToAvcc_singleNalUnit() {
ByteBuffer in = generateFakeNalUnitData(1000); ByteBuffer input = generateFakeNalUnitData(1000);
// Add start code for the NAL unit. // Add start code for the NAL unit.
in.put(0, (byte) 0); input.put(0, (byte) 0);
in.put(1, (byte) 0); input.put(1, (byte) 0);
in.put(2, (byte) 0); input.put(2, (byte) 0);
in.put(3, (byte) 1); input.put(3, (byte) 1);
AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT; 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. // 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 @Test
public void convertAnnexBToAvcc_twoNalUnits() { public void convertAnnexBToAvcc_twoNalUnits() {
ByteBuffer in = generateFakeNalUnitData(1000); ByteBuffer input = generateFakeNalUnitData(1000);
// Add start code for the first NAL unit. // Add start code for the first NAL unit.
in.put(0, (byte) 0); input.put(0, (byte) 0);
in.put(1, (byte) 0); input.put(1, (byte) 0);
in.put(2, (byte) 0); input.put(2, (byte) 0);
in.put(3, (byte) 1); input.put(3, (byte) 1);
// Add start code for the second NAL unit. // Add start code for the second NAL unit.
in.put(600, (byte) 0); input.put(600, (byte) 0);
in.put(601, (byte) 0); input.put(601, (byte) 0);
in.put(602, (byte) 0); input.put(602, (byte) 0);
in.put(603, (byte) 1); input.put(603, (byte) 1);
AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT; AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT;
annexBToAvccConverter.process(in); ByteBuffer output = annexBToAvccConverter.process(input);
// Both the NAL units should have length headers. // Both the NAL units should have length headers.
assertThat(in.getInt(0)).isEqualTo(596); assertThat(output.getInt(0)).isEqualTo(596);
assertThat(in.getInt(600)).isEqualTo(396); assertThat(output.getInt(600)).isEqualTo(396);
} }
@Test @Test
public void convertAnnexBToAvcc_noNalUnit_outputSameAsInput() { public void convertAnnexBToAvcc_noNalUnit_throws() {
ByteBuffer input = generateFakeNalUnitData(1000); ByteBuffer input = generateFakeNalUnitData(1000);
ByteBuffer inputCopy = ByteBuffer.allocate(input.limit());
inputCopy.put(input);
input.rewind();
inputCopy.rewind();
AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT; AnnexBToAvccConverter annexBToAvccConverter = AnnexBToAvccConverter.DEFAULT;
annexBToAvccConverter.process(input); assertThrows(IllegalStateException.class, () -> annexBToAvccConverter.process(input));
assertThat(input).isEqualTo(inputCopy);
} }
/** Returns {@link ByteBuffer} filled with random NAL unit data without start code. */ /** Returns {@link ByteBuffer} filled with random NAL unit data without start code. */

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
colorInfo: colorInfo:
@ -27,5 +27,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
rotationDegrees = 180 rotationDegrees = 180
@ -28,5 +28,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
rotationDegrees = 270 rotationDegrees = 270
@ -28,5 +28,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
rotationDegrees = 90 rotationDegrees = 90
@ -28,5 +28,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -2,18 +2,18 @@ seekMap:
isSeekable = true isSeekable = true
duration = 200 duration = 200
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=99]] getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=100]]
getPosition(100) = [[timeUs=100, position=99]] getPosition(100) = [[timeUs=100, position=100]]
getPosition(200) = [[timeUs=100, position=99]] getPosition(200) = [[timeUs=100, position=100]]
numberOfTracks = 2 numberOfTracks = 2
track 0: track 0:
total output bytes = 110 total output bytes = 112
sample count = 2 sample count = 2
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
frameRate = 20000.0 frameRate = 20000.0
@ -28,19 +28,19 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
sample 1: sample 1:
time = 100 time = 100
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
track 1: track 1:
total output bytes = 110 total output bytes = 112
sample count = 2 sample count = 2
format 0: format 0:
id = 2 id = 2
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
frameRate = 10000.0 frameRate = 10000.0
@ -55,9 +55,9 @@ track 1:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
sample 1: sample 1:
time = 200 time = 200
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
colorInfo: colorInfo:
@ -27,5 +27,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
colorInfo: colorInfo:
@ -27,5 +27,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
colorInfo: colorInfo:
@ -27,5 +27,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
colorInfo: colorInfo:
@ -27,5 +27,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -2,18 +2,18 @@ seekMap:
isSeekable = true isSeekable = true
duration = 200 duration = 200
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=99]] getPosition(1) = [[timeUs=0, position=44], [timeUs=100, position=100]]
getPosition(100) = [[timeUs=100, position=99]] getPosition(100) = [[timeUs=100, position=100]]
getPosition(200) = [[timeUs=100, position=99]] getPosition(200) = [[timeUs=100, position=100]]
numberOfTracks = 2 numberOfTracks = 2
track 0: track 0:
total output bytes = 110 total output bytes = 112
sample count = 2 sample count = 2
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
frameRate = 20000.0 frameRate = 20000.0
@ -28,19 +28,19 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
sample 1: sample 1:
time = 100 time = 100
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
track 1: track 1:
total output bytes = 110 total output bytes = 112
sample count = 2 sample count = 2
format 0: format 0:
id = 2 id = 2
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
frameRate = 10000.0 frameRate = 10000.0
@ -55,9 +55,9 @@ track 1:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
sample 1: sample 1:
time = 200 time = 200
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -1,7 +1,7 @@
ftyp (28 bytes): ftyp (28 bytes):
Data = length 20, hash EF896440 Data = length 20, hash EF896440
mdat (71 bytes): mdat (72 bytes):
Data = length 55, hash 6B19F4A7 Data = length 56, hash DB5662FB
moov (873 bytes): moov (873 bytes):
mvhd (108 bytes): mvhd (108 bytes):
Data = length 100, hash 2613A5C Data = length 100, hash 2613A5C
@ -33,7 +33,7 @@ moov (873 bytes):
stts (24 bytes): stts (24 bytes):
Data = length 16, hash E4FC6483 Data = length 16, hash E4FC6483
stsz (24 bytes): stsz (24 bytes):
Data = length 16, hash 50B7F5B9 Data = length 16, hash 50B7F5BA
stsc (28 bytes): stsc (28 bytes):
Data = length 20, hash 8F6E8285 Data = length 20, hash 8F6E8285
co64 (24 bytes): co64 (24 bytes):

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(0) = [[timeUs=0, position=44]] getPosition(0) = [[timeUs=0, position=44]]
numberOfTracks = 1 numberOfTracks = 1
track 0: track 0:
total output bytes = 55 total output bytes = 56
sample count = 1 sample count = 1
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.F4000A codecs = avc1.F4000A
maxInputSize = 85 maxInputSize = 86
width = 12 width = 12
height = 10 height = 10
colorInfo: colorInfo:
@ -27,5 +27,5 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 536870913 flags = 536870913
data = length 55, hash A481CEF4 data = length 56, hash C4551A2E
tracksEnded = true tracksEnded = true

View File

@ -1,7 +1,7 @@
ftyp (28 bytes): ftyp (28 bytes):
Data = length 20, hash EF896440 Data = length 20, hash EF896440
mdat (71 bytes): mdat (72 bytes):
Data = length 55, hash 6B19F4A7 Data = length 56, hash DB5662FB
moov (658 bytes): moov (658 bytes):
mvhd (108 bytes): mvhd (108 bytes):
Data = length 100, hash 2613A5C Data = length 100, hash 2613A5C
@ -24,7 +24,7 @@ moov (658 bytes):
stts (24 bytes): stts (24 bytes):
Data = length 16, hash E4FC6483 Data = length 16, hash E4FC6483
stsz (24 bytes): stsz (24 bytes):
Data = length 16, hash 50B7F5B9 Data = length 16, hash 50B7F5BA
stsc (28 bytes): stsc (28 bytes):
Data = length 20, hash 8F6E8285 Data = length 20, hash 8F6E8285
co64 (24 bytes): co64 (24 bytes):

View File

@ -1,7 +1,7 @@
ftyp (28 bytes): ftyp (28 bytes):
Data = length 20, hash EF896440 Data = length 20, hash EF896440
mdat (126 bytes): mdat (128 bytes):
Data = length 110, hash 48173D41 Data = length 112, hash 1AAF8FF5
moov (674 bytes): moov (674 bytes):
mvhd (108 bytes): mvhd (108 bytes):
Data = length 100, hash 105FA889 Data = length 100, hash 105FA889
@ -24,7 +24,7 @@ moov (674 bytes):
stts (32 bytes): stts (32 bytes):
Data = length 24, hash 4A7D0E0E Data = length 24, hash 4A7D0E0E
stsz (28 bytes): stsz (28 bytes):
Data = length 20, hash 3828E071 Data = length 20, hash 3836F7F3
stsc (28 bytes): stsc (28 bytes):
Data = length 20, hash 8F7C9A06 Data = length 20, hash 8F7C9A06
co64 (24 bytes): co64 (24 bytes):

View File

@ -201,13 +201,13 @@ track 0:
flags = 536870913 flags = 536870913
data = length 6, hash 31B22286 data = length 6, hash 31B22286
track 1: track 1:
total output bytes = 301392 total output bytes = 301222
sample count = 30 sample count = 30
format 0: format 0:
id = 2 id = 2
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.640034 codecs = avc1.640034
maxInputSize = 22910 maxInputSize = 22904
width = 1080 width = 1080
height = 720 height = 720
frameRate = 31.004547 frameRate = 31.004547
@ -224,7 +224,7 @@ track 1:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 7744, hash DDF91733 data = length 7735, hash 87BD736D
sample 1: sample 1:
time = 33355 time = 33355
flags = 0 flags = 0
@ -232,7 +232,7 @@ track 1:
sample 2: sample 2:
time = 66722 time = 66722
flags = 0 flags = 0
data = length 960, hash D4AD9EF0 data = length 955, hash 9626CB10
sample 3: sample 3:
time = 100100 time = 100100
flags = 0 flags = 0
@ -248,7 +248,7 @@ track 1:
sample 6: sample 6:
time = 200200 time = 200200
flags = 0 flags = 0
data = length 3856, hash 3D7DCD46 data = length 3850, hash 6E1AB9C6
sample 7: sample 7:
time = 233555 time = 233555
flags = 0 flags = 0
@ -256,7 +256,7 @@ track 1:
sample 8: sample 8:
time = 266922 time = 266922
flags = 0 flags = 0
data = length 6144, hash 8FD9AD7D data = length 6137, hash AAA5D523
sample 9: sample 9:
time = 300300 time = 300300
flags = 0 flags = 0
@ -268,43 +268,43 @@ track 1:
sample 11: sample 11:
time = 367022 time = 367022
flags = 0 flags = 0
data = length 14496, hash E0F2E0BA data = length 14490, hash C5F0743A
sample 12: sample 12:
time = 400400 time = 400400
flags = 0 flags = 0
data = length 17664, hash 3E3189E data = length 17652, hash 63C959E
sample 13: sample 13:
time = 433755 time = 433755
flags = 0 flags = 0
data = length 5712, hash CA808ECF data = length 5703, hash 44AFBC51
sample 14: sample 14:
time = 467122 time = 467122
flags = 0 flags = 0
data = length 9776, hash C875D1AA data = length 9766, hash 77D5FE2A
sample 15: sample 15:
time = 500500 time = 500500
flags = 0 flags = 0
data = length 17712, hash 69AE17D4 data = length 17704, hash CF5D2BD4
sample 16: sample 16:
time = 533855 time = 533855
flags = 0 flags = 0
data = length 11440, hash 7370E78C data = length 11436, hash 9DCA8D8C
sample 17: sample 17:
time = 567222 time = 567222
flags = 0 flags = 0
data = length 8544, hash 5A581986 data = length 8540, hash BEBDCC86
sample 18: sample 18:
time = 600600 time = 600600
flags = 0 flags = 0
data = length 19904, hash 98AB5C44 data = length 19894, hash 6212144
sample 19: sample 19:
time = 633955 time = 633955
flags = 0 flags = 0
data = length 14352, hash 74B754E3 data = length 14342, hash EC6E84A3
sample 20: sample 20:
time = 667322 time = 667322
flags = 0 flags = 0
data = length 9568, hash 369746A6 data = length 9555, hash 601CE39A
sample 21: sample 21:
time = 700700 time = 700700
flags = 0 flags = 0
@ -312,33 +312,33 @@ track 1:
sample 22: sample 22:
time = 734055 time = 734055
flags = 0 flags = 0
data = length 22880, hash 75E833BA data = length 22874, hash 4620073A
sample 23: sample 23:
time = 767422 time = 767422
flags = 0 flags = 0
data = length 16832, hash E8BFCFE3 data = length 16821, hash 95A33FFD
sample 24: sample 24:
time = 800800 time = 800800
flags = 0 flags = 0
data = length 5120, hash E04AEF94 data = length 5114, hash F0CB2E94
sample 25: sample 25:
time = 834155 time = 834155
flags = 0 flags = 0
data = length 9888, hash 1166103E data = length 9884, hash 103DF3E
sample 26: sample 26:
time = 867522 time = 867522
flags = 0 flags = 0
data = length 17024, hash F9A96740 data = length 17011, hash 82DFD0C0
sample 27: sample 27:
time = 900900 time = 900900
flags = 0 flags = 0
data = length 20864, hash DF9E88B8 data = length 20860, hash 5DEFA4B8
sample 28: sample 28:
time = 934255 time = 934255
flags = 0 flags = 0
data = length 7216, hash BE22BE2F data = length 7209, hash AFAC48B1
sample 29: sample 29:
time = 967622 time = 967622
flags = 536870912 flags = 536870912
data = length 14240, hash E190BF31 data = length 14234, hash 101837F1
tracksEnded = true tracksEnded = true

View File

@ -7,13 +7,13 @@ seekMap:
getPosition(1065600) = [[timeUs=0, position=44]] getPosition(1065600) = [[timeUs=0, position=44]]
numberOfTracks = 2 numberOfTracks = 2
track 0: track 0:
total output bytes = 301392 total output bytes = 301222
sample count = 30 sample count = 30
format 0: format 0:
id = 1 id = 1
sampleMimeType = video/avc sampleMimeType = video/avc
codecs = avc1.640034 codecs = avc1.640034
maxInputSize = 22910 maxInputSize = 22904
width = 1080 width = 1080
height = 720 height = 720
frameRate = 31.004547 frameRate = 31.004547
@ -30,7 +30,7 @@ track 0:
sample 0: sample 0:
time = 0 time = 0
flags = 1 flags = 1
data = length 7744, hash DDF91733 data = length 7735, hash 87BD736D
sample 1: sample 1:
time = 33355 time = 33355
flags = 0 flags = 0
@ -38,7 +38,7 @@ track 0:
sample 2: sample 2:
time = 66722 time = 66722
flags = 0 flags = 0
data = length 960, hash D4AD9EF0 data = length 955, hash 9626CB10
sample 3: sample 3:
time = 100100 time = 100100
flags = 0 flags = 0
@ -54,7 +54,7 @@ track 0:
sample 6: sample 6:
time = 200200 time = 200200
flags = 0 flags = 0
data = length 3856, hash 3D7DCD46 data = length 3850, hash 6E1AB9C6
sample 7: sample 7:
time = 233555 time = 233555
flags = 0 flags = 0
@ -62,7 +62,7 @@ track 0:
sample 8: sample 8:
time = 266922 time = 266922
flags = 0 flags = 0
data = length 6144, hash 8FD9AD7D data = length 6137, hash AAA5D523
sample 9: sample 9:
time = 300300 time = 300300
flags = 0 flags = 0
@ -74,43 +74,43 @@ track 0:
sample 11: sample 11:
time = 367022 time = 367022
flags = 0 flags = 0
data = length 14496, hash E0F2E0BA data = length 14490, hash C5F0743A
sample 12: sample 12:
time = 400400 time = 400400
flags = 0 flags = 0
data = length 17664, hash 3E3189E data = length 17652, hash 63C959E
sample 13: sample 13:
time = 433755 time = 433755
flags = 0 flags = 0
data = length 5712, hash CA808ECF data = length 5703, hash 44AFBC51
sample 14: sample 14:
time = 467122 time = 467122
flags = 0 flags = 0
data = length 9776, hash C875D1AA data = length 9766, hash 77D5FE2A
sample 15: sample 15:
time = 500500 time = 500500
flags = 0 flags = 0
data = length 17712, hash 69AE17D4 data = length 17704, hash CF5D2BD4
sample 16: sample 16:
time = 533855 time = 533855
flags = 0 flags = 0
data = length 11440, hash 7370E78C data = length 11436, hash 9DCA8D8C
sample 17: sample 17:
time = 567222 time = 567222
flags = 0 flags = 0
data = length 8544, hash 5A581986 data = length 8540, hash BEBDCC86
sample 18: sample 18:
time = 600600 time = 600600
flags = 0 flags = 0
data = length 19904, hash 98AB5C44 data = length 19894, hash 6212144
sample 19: sample 19:
time = 633955 time = 633955
flags = 0 flags = 0
data = length 14352, hash 74B754E3 data = length 14342, hash EC6E84A3
sample 20: sample 20:
time = 667322 time = 667322
flags = 0 flags = 0
data = length 9568, hash 369746A6 data = length 9555, hash 601CE39A
sample 21: sample 21:
time = 700700 time = 700700
flags = 0 flags = 0
@ -118,35 +118,35 @@ track 0:
sample 22: sample 22:
time = 734055 time = 734055
flags = 0 flags = 0
data = length 22880, hash 75E833BA data = length 22874, hash 4620073A
sample 23: sample 23:
time = 767422 time = 767422
flags = 0 flags = 0
data = length 16832, hash E8BFCFE3 data = length 16821, hash 95A33FFD
sample 24: sample 24:
time = 800800 time = 800800
flags = 0 flags = 0
data = length 5120, hash E04AEF94 data = length 5114, hash F0CB2E94
sample 25: sample 25:
time = 834155 time = 834155
flags = 0 flags = 0
data = length 9888, hash 1166103E data = length 9884, hash 103DF3E
sample 26: sample 26:
time = 867522 time = 867522
flags = 0 flags = 0
data = length 17024, hash F9A96740 data = length 17011, hash 82DFD0C0
sample 27: sample 27:
time = 900900 time = 900900
flags = 0 flags = 0
data = length 20864, hash DF9E88B8 data = length 20860, hash 5DEFA4B8
sample 28: sample 28:
time = 934255 time = 934255
flags = 0 flags = 0
data = length 7216, hash BE22BE2F data = length 7209, hash AFAC48B1
sample 29: sample 29:
time = 967622 time = 967622
flags = 536870912 flags = 536870912
data = length 14240, hash E190BF31 data = length 14234, hash 101837F1
track 1: track 1:
total output bytes = 9529 total output bytes = 9529
sample count = 45 sample count = 45

View File

@ -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

View File

@ -16,6 +16,7 @@
package androidx.media3.transformer; package androidx.media3.transformer;
import static androidx.media3.common.util.Assertions.checkState; 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 androidx.media3.test.utils.TestUtil.retrieveTrackFormat;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
@ -51,6 +52,7 @@ import org.junit.runner.RunWith;
public class TransformerWithInAppMuxerEndToEndTest { public class TransformerWithInAppMuxerEndToEndTest {
private static final String MP4_FILE_PATH = "asset:///media/mp4/sample_no_bframes.mp4"; 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"; private static final String MP4_FILE_NAME = "mp4/sample_no_bframes.mp4";
@Rule public final TemporaryFolder outputDir = new TemporaryFolder(); @Rule public final TemporaryFolder outputDir = new TemporaryFolder();
private final Context context = ApplicationProvider.getApplicationContext(); private final Context context = ApplicationProvider.getApplicationContext();
@ -85,7 +87,7 @@ public class TransformerWithInAppMuxerEndToEndTest {
TransformerTestRunner.runLooper(transformer); TransformerTestRunner.runLooper(transformer);
FakeExtractorOutput fakeExtractorOutput = FakeExtractorOutput fakeExtractorOutput =
androidx.media3.test.utils.TestUtil.extractAllSamplesFromFilePath( extractAllSamplesFromFilePath(
new Mp4Extractor(new DefaultSubtitleParserFactory()), outputPath); new Mp4Extractor(new DefaultSubtitleParserFactory()), outputPath);
DumpFileAsserts.assertOutput( DumpFileAsserts.assertOutput(
context, context,
@ -95,6 +97,46 @@ public class TransformerWithInAppMuxerEndToEndTest {
/* modifications...= */ "transmuxed_with_inappmuxer")); /* 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 @Test
public void transmux_withLocationMetadata_writesSameLocationMetadata() throws Exception { public void transmux_withLocationMetadata_writesSameLocationMetadata() throws Exception {
Mp4LocationData expectedLocationData = Mp4LocationData expectedLocationData =