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:
parent
6fc4f0263f
commit
16aac07bce
@ -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<ByteBuffer> 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}.
|
||||
*
|
||||
* <p>Expects a {@link ByteBuffer} input with a zero offset.
|
||||
*
|
||||
* @param inputBuffer The buffer to be converted.
|
||||
*/
|
||||
void process(ByteBuffer inputBuffer);
|
||||
ByteBuffer process(ByteBuffer inputBuffer);
|
||||
}
|
||||
|
@ -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).
|
||||
*
|
||||
* <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) {
|
||||
if (input.remaining() < 4 || input.getInt(0) != 1) {
|
||||
if (input.remaining() == 0) {
|
||||
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;
|
||||
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<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();
|
||||
@ -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);
|
||||
|
@ -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).
|
||||
|
@ -277,22 +277,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
private ProcessedTrackInfo processTrack(int trackId, Track track) {
|
||||
checkState(track.pendingSamplesByteBuffer.size() == track.pendingSamplesBufferInfo.size());
|
||||
|
||||
ImmutableList.Builder<ByteBuffer> pendingSamplesByteBuffer = new ImmutableList.Builder<>();
|
||||
ImmutableList.Builder<BufferInfo> 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();
|
||||
}
|
||||
|
||||
ImmutableList.Builder<BufferInfo> pendingSamplesBufferInfoBuilder =
|
||||
new ImmutableList.Builder<>();
|
||||
pendingSamplesBufferInfoBuilder.addAll(track.pendingSamplesBufferInfo);
|
||||
track.pendingSamplesBufferInfo.clear();
|
||||
}
|
||||
|
||||
ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build();
|
||||
List<Long> sampleDurations =
|
||||
|
@ -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<ByteBuffer> components = AnnexBUtils.findNalUnits(buf);
|
||||
ImmutableList<ByteBuffer> 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<ByteBuffer> 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<ByteBuffer> components = AnnexBUtils.findNalUnits(buf);
|
||||
ImmutableList<ByteBuffer> 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<ByteBuffer> components = AnnexBUtils.findNalUnits(buffer);
|
||||
|
||||
assertThat(components).containsExactly(ByteBuffer.wrap(getBytesFromHexString("ABCDEF")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findNalUnits_multipleNalUnitsWithFourByteStartCode_allReturned() {
|
||||
ByteBuffer buffer =
|
||||
ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF00000001DDCC00000001BBAA"));
|
||||
|
||||
ImmutableList<ByteBuffer> components = AnnexBUtils.findNalUnits(buf);
|
||||
ImmutableList<ByteBuffer> 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<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.
|
||||
ByteBuffer buf =
|
||||
ByteBuffer buffer =
|
||||
ByteBuffer.wrap(getBytesFromHexString("00000001ABCDEF0000AB0000CDEF00000000AB"));
|
||||
|
||||
ImmutableList<ByteBuffer> 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<ByteBuffer> components = AnnexBUtils.findNalUnits(buf);
|
||||
ImmutableList<ByteBuffer> 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<ByteBuffer> 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")));
|
||||
}
|
||||
|
@ -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. */
|
||||
|
Binary file not shown.
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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 =
|
||||
|
Loading…
x
Reference in New Issue
Block a user