Fragmented Mp4Muxer: add support to B-frame Muxing
Add composition time offset parameter to TRUN box to support muxing of videos containing B-frames by FragmentedMp4Muxer. Update TRUN box version from 0 to 1 in order to manage signed composition time offset. PiperOrigin-RevId: 636426397
This commit is contained in:
parent
1ffeafecc3
commit
37c2d9957e
@ -27,7 +27,7 @@ import androidx.media3.test.utils.DumpableMp4Box;
|
|||||||
import androidx.media3.test.utils.FakeExtractorOutput;
|
import androidx.media3.test.utils.FakeExtractorOutput;
|
||||||
import androidx.media3.test.utils.TestUtil;
|
import androidx.media3.test.utils.TestUtil;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -38,14 +38,26 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
import org.junit.rules.TemporaryFolder;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameter;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
/** End to end instrumentation tests for {@link FragmentedMp4Muxer}. */
|
/** End to end instrumentation tests for {@link FragmentedMp4Muxer}. */
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(Parameterized.class)
|
||||||
public class FragmentedMp4MuxerEndToEndAndroidTest {
|
public class FragmentedMp4MuxerEndToEndAndroidTest {
|
||||||
|
private static final String H264_WITH_PYRAMID_B_FRAMES_MP4 =
|
||||||
|
"bbb_800x640_768kbps_30fps_avc_pyramid_3b.mp4";
|
||||||
private static final String H265_HDR10_MP4 = "hdr10-720p.mp4";
|
private static final String H265_HDR10_MP4 = "hdr10-720p.mp4";
|
||||||
|
|
||||||
|
@Parameters(name = "{0}")
|
||||||
|
public static ImmutableList<String> mediaSamples() {
|
||||||
|
return ImmutableList.of(H264_WITH_PYRAMID_B_FRAMES_MP4, H265_HDR10_MP4);
|
||||||
|
}
|
||||||
|
|
||||||
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
@Parameter public @MonotonicNonNull String inputFile;
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private @MonotonicNonNull String outputPath;
|
private @MonotonicNonNull String outputPath;
|
||||||
private @MonotonicNonNull FileOutputStream outputStream;
|
private @MonotonicNonNull FileOutputStream outputStream;
|
||||||
@ -71,7 +83,7 @@ public class FragmentedMp4MuxerEndToEndAndroidTest {
|
|||||||
new Mp4TimestampData(
|
new Mp4TimestampData(
|
||||||
/* creationTimestampSeconds= */ 100_000_000L,
|
/* creationTimestampSeconds= */ 100_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||||
feedInputDataToMuxer(context, fragmentedMp4Muxer, H265_HDR10_MP4);
|
feedInputDataToMuxer(context, fragmentedMp4Muxer, checkNotNull(inputFile));
|
||||||
} finally {
|
} finally {
|
||||||
if (fragmentedMp4Muxer != null) {
|
if (fragmentedMp4Muxer != null) {
|
||||||
fragmentedMp4Muxer.close();
|
fragmentedMp4Muxer.close();
|
||||||
@ -84,7 +96,7 @@ public class FragmentedMp4MuxerEndToEndAndroidTest {
|
|||||||
DumpFileAsserts.assertOutput(
|
DumpFileAsserts.assertOutput(
|
||||||
context,
|
context,
|
||||||
fakeExtractorOutput,
|
fakeExtractorOutput,
|
||||||
AndroidMuxerTestUtil.getExpectedDumpFilePath(H265_HDR10_MP4 + "_fragmented"));
|
AndroidMuxerTestUtil.getExpectedDumpFilePath(inputFile + "_fragmented"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -775,7 +775,7 @@ import java.util.Locale;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate sample composition time offsets (in timebase units).
|
* Calculates sample composition time offsets (in timebase units).
|
||||||
*
|
*
|
||||||
* <p>The sample composition time offset gives offset between composition time (CT) and decoding
|
* <p>The sample composition time offset gives offset between composition time (CT) and decoding
|
||||||
* time (DT), such that {@code CT(n) = DT(n) + sample_offset(n)}.
|
* time (DT), such that {@code CT(n) = DT(n) + sample_offset(n)}.
|
||||||
@ -785,7 +785,7 @@ import java.util.Locale;
|
|||||||
* @param videoUnitTimescale The timescale of the track.
|
* @param videoUnitTimescale The timescale of the track.
|
||||||
* @return A list of all the sample composition time offsets.
|
* @return A list of all the sample composition time offsets.
|
||||||
*/
|
*/
|
||||||
private static List<Integer> calculateSampleCompositionTimeOffsets(
|
public static List<Integer> calculateSampleCompositionTimeOffsets(
|
||||||
List<BufferInfo> samplesInfo, List<Long> durationVu, int videoUnitTimescale) {
|
List<BufferInfo> samplesInfo, List<Long> durationVu, int videoUnitTimescale) {
|
||||||
List<Integer> compositionOffsets = new ArrayList<>(samplesInfo.size());
|
List<Integer> compositionOffsets = new ArrayList<>(samplesInfo.size());
|
||||||
if (samplesInfo.isEmpty()) {
|
if (samplesInfo.isEmpty()) {
|
||||||
@ -998,8 +998,10 @@ import java.util.Locale;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a track fragment run (trun) box. */
|
/** Returns a track fragment run (trun) box. */
|
||||||
public static ByteBuffer trun(List<SampleMetadata> samplesMetadata, int dataOffset) {
|
public static ByteBuffer trun(
|
||||||
ByteBuffer contents = ByteBuffer.allocate(getTrunBoxContentSize(samplesMetadata.size()));
|
List<SampleMetadata> samplesMetadata, int dataOffset, boolean hasBFrame) {
|
||||||
|
ByteBuffer contents =
|
||||||
|
ByteBuffer.allocate(getTrunBoxContentSize(samplesMetadata.size(), hasBFrame));
|
||||||
|
|
||||||
// 0x000001 data-offset-present.
|
// 0x000001 data-offset-present.
|
||||||
// 0x000100 sample-duration-present: indicates that each sample has its own duration, otherwise
|
// 0x000100 sample-duration-present: indicates that each sample has its own duration, otherwise
|
||||||
@ -1008,8 +1010,13 @@ import java.util.Locale;
|
|||||||
// default is used.
|
// default is used.
|
||||||
// 0x000400 sample-flags-present: indicates that each sample has its own flags, otherwise the
|
// 0x000400 sample-flags-present: indicates that each sample has its own flags, otherwise the
|
||||||
// default is used.
|
// default is used.
|
||||||
// Version is 0x0.
|
// 0x000800 sample-composition-time-offsets-present: indicates that each sample has its own
|
||||||
int versionAndFlags = 0x0 | 0x000001 | 0x000100 | 0x000200 | 0x000400;
|
// composition time offset, otherwise default is used.
|
||||||
|
// Version (the most significant byte of versionAndFlags) is 0x1.
|
||||||
|
int versionAndFlags = 0x1 << 24 | 0x000001 | 0x000100 | 0x000200 | 0x000400;
|
||||||
|
if (hasBFrame) {
|
||||||
|
versionAndFlags |= 0x000800;
|
||||||
|
}
|
||||||
contents.putInt(versionAndFlags);
|
contents.putInt(versionAndFlags);
|
||||||
contents.putInt(samplesMetadata.size()); // An unsigned int(32)
|
contents.putInt(samplesMetadata.size()); // An unsigned int(32)
|
||||||
contents.putInt(dataOffset); // A signed int(32)
|
contents.putInt(dataOffset); // A signed int(32)
|
||||||
@ -1021,16 +1028,19 @@ import java.util.Locale;
|
|||||||
(currentSampleMetadata.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0
|
(currentSampleMetadata.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0
|
||||||
? TRUN_BOX_SYNC_SAMPLE_FLAGS
|
? TRUN_BOX_SYNC_SAMPLE_FLAGS
|
||||||
: TRUN_BOX_NON_SYNC_SAMPLE_FLAGS);
|
: TRUN_BOX_NON_SYNC_SAMPLE_FLAGS);
|
||||||
|
if (hasBFrame) {
|
||||||
|
contents.putInt(currentSampleMetadata.compositionTimeOffsetVu);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
contents.flip();
|
contents.flip();
|
||||||
return BoxUtils.wrapIntoBox("trun", contents);
|
return BoxUtils.wrapIntoBox("trun", contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the size required for {@link #trun(List, int)} box content. */
|
/** Returns the size required for {@link #trun(List, int, boolean)} box content. */
|
||||||
public static int getTrunBoxContentSize(int sampleCount) {
|
public static int getTrunBoxContentSize(int sampleCount, boolean hasBFrame) {
|
||||||
int trunBoxFixedSize = 3 * BYTES_PER_INTEGER;
|
int trunBoxFixedSize = 3 * BYTES_PER_INTEGER;
|
||||||
// 3 int(32-bit) gets written for each sample.
|
int intWrittenPerSample = hasBFrame ? 4 : 3;
|
||||||
return trunBoxFixedSize + 3 * sampleCount * BYTES_PER_INTEGER;
|
return trunBoxFixedSize + intWrittenPerSample * sampleCount * BYTES_PER_INTEGER;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a movie extends (mvex) box. */
|
/** Returns a movie extends (mvex) box. */
|
||||||
|
@ -29,7 +29,6 @@ import static java.lang.Math.min;
|
|||||||
|
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
import androidx.media3.common.C;
|
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
@ -53,11 +52,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public final long durationVu;
|
public final long durationVu;
|
||||||
public final int size;
|
public final int size;
|
||||||
public final int flags;
|
public final int flags;
|
||||||
|
public final int compositionTimeOffsetVu;
|
||||||
|
|
||||||
public SampleMetadata(long durationsVu, int size, int flags) {
|
public SampleMetadata(long durationsVu, int size, int flags, int compositionTimeOffsetVu) {
|
||||||
this.durationVu = durationsVu;
|
this.durationVu = durationsVu;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
|
this.compositionTimeOffsetVu = compositionTimeOffsetVu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +75,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private boolean headerCreated;
|
private boolean headerCreated;
|
||||||
private long minInputPresentationTimeUs;
|
private long minInputPresentationTimeUs;
|
||||||
private long maxTrackDurationUs;
|
private long maxTrackDurationUs;
|
||||||
private long lastSamplePresentationTimeUs;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance.
|
* Creates an instance.
|
||||||
@ -102,7 +102,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
this.fragmentDurationUs = fragmentDurationMs * 1_000;
|
this.fragmentDurationUs = fragmentDurationMs * 1_000;
|
||||||
minInputPresentationTimeUs = Long.MAX_VALUE;
|
minInputPresentationTimeUs = Long.MAX_VALUE;
|
||||||
currentFragmentSequenceNumber = 1;
|
currentFragmentSequenceNumber = 1;
|
||||||
lastSamplePresentationTimeUs = C.TIME_UNSET;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackToken addTrack(int sortKey, Format format) {
|
public TrackToken addTrack(int sortKey, Format format) {
|
||||||
@ -118,9 +117,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
TrackToken token, ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo)
|
TrackToken token, ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
checkArgument(token instanceof Track);
|
checkArgument(token instanceof Track);
|
||||||
checkArgument(
|
|
||||||
bufferInfo.presentationTimeUs > lastSamplePresentationTimeUs,
|
|
||||||
"Out of order B-frames are not supported");
|
|
||||||
if (!headerCreated) {
|
if (!headerCreated) {
|
||||||
createHeader();
|
createHeader();
|
||||||
headerCreated = true;
|
headerCreated = true;
|
||||||
@ -130,7 +126,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
createFragment();
|
createFragment();
|
||||||
}
|
}
|
||||||
track.writeSampleData(byteBuffer, bufferInfo);
|
track.writeSampleData(byteBuffer, bufferInfo);
|
||||||
lastSamplePresentationTimeUs = bufferInfo.presentationTimeUs;
|
|
||||||
BufferInfo firstPendingSample = checkNotNull(track.pendingSamplesBufferInfo.peekFirst());
|
BufferInfo firstPendingSample = checkNotNull(track.pendingSamplesBufferInfo.peekFirst());
|
||||||
BufferInfo lastPendingSample = checkNotNull(track.pendingSamplesBufferInfo.peekLast());
|
BufferInfo lastPendingSample = checkNotNull(track.pendingSamplesBufferInfo.peekLast());
|
||||||
minInputPresentationTimeUs =
|
minInputPresentationTimeUs =
|
||||||
@ -163,7 +158,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
trafBoxes.add(
|
trafBoxes.add(
|
||||||
Boxes.traf(
|
Boxes.traf(
|
||||||
Boxes.tfhd(currentTrackInfo.trackId, /* baseDataOffset= */ moofBoxStartPosition),
|
Boxes.tfhd(currentTrackInfo.trackId, /* baseDataOffset= */ moofBoxStartPosition),
|
||||||
Boxes.trun(currentTrackInfo.pendingSamplesMetadata, dataOffset)));
|
Boxes.trun(
|
||||||
|
currentTrackInfo.pendingSamplesMetadata,
|
||||||
|
dataOffset,
|
||||||
|
currentTrackInfo.hasBFrame)));
|
||||||
dataOffset += currentTrackInfo.totalSamplesSize;
|
dataOffset += currentTrackInfo.totalSamplesSize;
|
||||||
}
|
}
|
||||||
return trafBoxes.build();
|
return trafBoxes.build();
|
||||||
@ -189,7 +187,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
for (int i = 0; i < trackInfos.size(); i++) {
|
for (int i = 0; i < trackInfos.size(); i++) {
|
||||||
ProcessedTrackInfo trackInfo = trackInfos.get(i);
|
ProcessedTrackInfo trackInfo = trackInfos.get(i);
|
||||||
int trunBoxSize =
|
int trunBoxSize =
|
||||||
trunBoxHeaderFixedSize + getTrunBoxContentSize(trackInfo.pendingSamplesMetadata.size());
|
trunBoxHeaderFixedSize
|
||||||
|
+ getTrunBoxContentSize(trackInfo.pendingSamplesMetadata.size(), trackInfo.hasBFrame);
|
||||||
trafBoxesSize += trafBoxHeaderSize + tfhdBoxSize + trunBoxSize;
|
trafBoxesSize += trafBoxHeaderSize + tfhdBoxSize + trunBoxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,6 +318,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
track.pendingSamplesBufferInfo.clear();
|
track.pendingSamplesBufferInfo.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hasBFrame = false;
|
||||||
ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build();
|
ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build();
|
||||||
List<Long> sampleDurations =
|
List<Long> sampleDurations =
|
||||||
Boxes.convertPresentationTimestampsToDurationsVu(
|
Boxes.convertPresentationTimestampsToDurationsVu(
|
||||||
@ -329,6 +329,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
track.videoUnitTimebase(),
|
track.videoUnitTimebase(),
|
||||||
Mp4Muxer.LAST_FRAME_DURATION_BEHAVIOR_DUPLICATE_PREV_DURATION);
|
Mp4Muxer.LAST_FRAME_DURATION_BEHAVIOR_DUPLICATE_PREV_DURATION);
|
||||||
|
|
||||||
|
List<Integer> sampleCompositionTimeOffsets =
|
||||||
|
Boxes.calculateSampleCompositionTimeOffsets(
|
||||||
|
pendingSamplesBufferInfo, sampleDurations, track.videoUnitTimebase());
|
||||||
|
if (!sampleCompositionTimeOffsets.isEmpty()) {
|
||||||
|
hasBFrame = true;
|
||||||
|
}
|
||||||
|
|
||||||
ImmutableList.Builder<SampleMetadata> pendingSamplesMetadata = new ImmutableList.Builder<>();
|
ImmutableList.Builder<SampleMetadata> pendingSamplesMetadata = new ImmutableList.Builder<>();
|
||||||
int totalSamplesSize = 0;
|
int totalSamplesSize = 0;
|
||||||
for (int i = 0; i < pendingSamplesBufferInfo.size(); i++) {
|
for (int i = 0; i < pendingSamplesBufferInfo.size(); i++) {
|
||||||
@ -337,12 +344,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
new SampleMetadata(
|
new SampleMetadata(
|
||||||
sampleDurations.get(i),
|
sampleDurations.get(i),
|
||||||
pendingSamplesBufferInfo.get(i).size,
|
pendingSamplesBufferInfo.get(i).size,
|
||||||
pendingSamplesBufferInfo.get(i).flags));
|
pendingSamplesBufferInfo.get(i).flags,
|
||||||
|
hasBFrame ? sampleCompositionTimeOffsets.get(i) : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ProcessedTrackInfo(
|
return new ProcessedTrackInfo(
|
||||||
trackId,
|
trackId,
|
||||||
totalSamplesSize,
|
totalSamplesSize,
|
||||||
|
hasBFrame,
|
||||||
pendingSamplesByteBuffer.build(),
|
pendingSamplesByteBuffer.build(),
|
||||||
pendingSamplesMetadata.build());
|
pendingSamplesMetadata.build());
|
||||||
}
|
}
|
||||||
@ -350,16 +359,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private static class ProcessedTrackInfo {
|
private static class ProcessedTrackInfo {
|
||||||
public final int trackId;
|
public final int trackId;
|
||||||
public final int totalSamplesSize;
|
public final int totalSamplesSize;
|
||||||
|
public final boolean hasBFrame;
|
||||||
public final ImmutableList<ByteBuffer> pendingSamplesByteBuffer;
|
public final ImmutableList<ByteBuffer> pendingSamplesByteBuffer;
|
||||||
public final ImmutableList<SampleMetadata> pendingSamplesMetadata;
|
public final ImmutableList<SampleMetadata> pendingSamplesMetadata;
|
||||||
|
|
||||||
public ProcessedTrackInfo(
|
public ProcessedTrackInfo(
|
||||||
int trackId,
|
int trackId,
|
||||||
int totalSamplesSize,
|
int totalSamplesSize,
|
||||||
|
boolean hasBFrame,
|
||||||
ImmutableList<ByteBuffer> pendingSamplesByteBuffer,
|
ImmutableList<ByteBuffer> pendingSamplesByteBuffer,
|
||||||
ImmutableList<SampleMetadata> pendingSamplesMetadata) {
|
ImmutableList<SampleMetadata> pendingSamplesMetadata) {
|
||||||
this.trackId = trackId;
|
this.trackId = trackId;
|
||||||
this.totalSamplesSize = totalSamplesSize;
|
this.totalSamplesSize = totalSamplesSize;
|
||||||
|
this.hasBFrame = hasBFrame;
|
||||||
this.pendingSamplesByteBuffer = pendingSamplesByteBuffer;
|
this.pendingSamplesByteBuffer = pendingSamplesByteBuffer;
|
||||||
this.pendingSamplesMetadata = pendingSamplesMetadata;
|
this.pendingSamplesMetadata = pendingSamplesMetadata;
|
||||||
}
|
}
|
||||||
|
@ -681,16 +681,39 @@ public class BoxesTest {
|
|||||||
new SampleMetadata(
|
new SampleMetadata(
|
||||||
/* durationsVu= */ 2_000L,
|
/* durationsVu= */ 2_000L,
|
||||||
/* size= */ 5_000,
|
/* size= */ 5_000,
|
||||||
/* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0));
|
/* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0,
|
||||||
|
/* compositionTimeOffsetVu= */ 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
ByteBuffer trunBox = Boxes.trun(samplesMetadata, /* dataOffset= */ 1_000);
|
ByteBuffer trunBox =
|
||||||
|
Boxes.trun(samplesMetadata, /* dataOffset= */ 1_000, /* hasBFrame= */ false);
|
||||||
|
|
||||||
DumpableMp4Box dumpableBox = new DumpableMp4Box(trunBox);
|
DumpableMp4Box dumpableBox = new DumpableMp4Box(trunBox);
|
||||||
DumpFileAsserts.assertOutput(
|
DumpFileAsserts.assertOutput(
|
||||||
context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("trun_box"));
|
context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("trun_box"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createTrunBox_withBFrame_matchesExpected() throws IOException {
|
||||||
|
int sampleCount = 5;
|
||||||
|
List<SampleMetadata> samplesMetadata = new ArrayList<>(sampleCount);
|
||||||
|
for (int i = 0; i < sampleCount; i++) {
|
||||||
|
samplesMetadata.add(
|
||||||
|
new SampleMetadata(
|
||||||
|
/* durationsVu= */ 2_000L,
|
||||||
|
/* size= */ 5_000,
|
||||||
|
/* flags= */ i == 0 ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0,
|
||||||
|
/* compositionTimeOffsetVu= */ 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer trunBox =
|
||||||
|
Boxes.trun(samplesMetadata, /* dataOffset= */ 1_000, /* hasBFrame= */ true);
|
||||||
|
|
||||||
|
DumpableMp4Box dumpableBox = new DumpableMp4Box(trunBox);
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("trun_box_with_b_frame"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createTrexBox_matchesExpected() throws IOException {
|
public void createTrexBox_matchesExpected() throws IOException {
|
||||||
ByteBuffer trexBox = Boxes.trex(/* trackId= */ 2);
|
ByteBuffer trexBox = Boxes.trex(/* trackId= */ 2);
|
||||||
|
@ -0,0 +1,501 @@
|
|||||||
|
seekMap:
|
||||||
|
isSeekable = false
|
||||||
|
duration = UNSET TIME
|
||||||
|
getPosition(0) = [[timeUs=0, position=695]]
|
||||||
|
numberOfTracks = 1
|
||||||
|
track 0:
|
||||||
|
total output bytes = 406132
|
||||||
|
sample count = 120
|
||||||
|
format 0:
|
||||||
|
id = 1
|
||||||
|
sampleMimeType = video/avc
|
||||||
|
codecs = avc1.64001F
|
||||||
|
width = 800
|
||||||
|
height = 640
|
||||||
|
colorInfo:
|
||||||
|
lumaBitdepth = 8
|
||||||
|
chromaBitdepth = 8
|
||||||
|
initializationData:
|
||||||
|
data = length 33, hash A51E6AF9
|
||||||
|
data = length 9, hash FBAE9B2D
|
||||||
|
sample 0:
|
||||||
|
time = 0
|
||||||
|
flags = 1
|
||||||
|
data = length 74666, hash 9AF2BE5A
|
||||||
|
sample 1:
|
||||||
|
time = 133322
|
||||||
|
flags = 0
|
||||||
|
data = length 3689, hash 23BF8CA2
|
||||||
|
sample 2:
|
||||||
|
time = 66655
|
||||||
|
flags = 0
|
||||||
|
data = length 1319, hash 7E6541DB
|
||||||
|
sample 3:
|
||||||
|
time = 33322
|
||||||
|
flags = 0
|
||||||
|
data = length 466, hash ECD93C68
|
||||||
|
sample 4:
|
||||||
|
time = 100000
|
||||||
|
flags = 0
|
||||||
|
data = length 785, hash 3E824588
|
||||||
|
sample 5:
|
||||||
|
time = 266655
|
||||||
|
flags = 0
|
||||||
|
data = length 23472, hash 4A790E9B
|
||||||
|
sample 6:
|
||||||
|
time = 200000
|
||||||
|
flags = 0
|
||||||
|
data = length 169, hash B408CF0C
|
||||||
|
sample 7:
|
||||||
|
time = 166655
|
||||||
|
flags = 0
|
||||||
|
data = length 1008, hash 558016DF
|
||||||
|
sample 8:
|
||||||
|
time = 233322
|
||||||
|
flags = 0
|
||||||
|
data = length 60, hash A5220F0F
|
||||||
|
sample 9:
|
||||||
|
time = 400000
|
||||||
|
flags = 0
|
||||||
|
data = length 718, hash 56CA9233
|
||||||
|
sample 10:
|
||||||
|
time = 333322
|
||||||
|
flags = 0
|
||||||
|
data = length 277, hash F27B77B0
|
||||||
|
sample 11:
|
||||||
|
time = 300000
|
||||||
|
flags = 0
|
||||||
|
data = length 204, hash 451A7EF8
|
||||||
|
sample 12:
|
||||||
|
time = 366655
|
||||||
|
flags = 0
|
||||||
|
data = length 100, hash 72CCF451
|
||||||
|
sample 13:
|
||||||
|
time = 533322
|
||||||
|
flags = 0
|
||||||
|
data = length 2305, hash EE8EAD07
|
||||||
|
sample 14:
|
||||||
|
time = 466655
|
||||||
|
flags = 0
|
||||||
|
data = length 313, hash 23FD6C99
|
||||||
|
sample 15:
|
||||||
|
time = 433322
|
||||||
|
flags = 0
|
||||||
|
data = length 161, hash F9082751
|
||||||
|
sample 16:
|
||||||
|
time = 500000
|
||||||
|
flags = 0
|
||||||
|
data = length 512, hash F0317F7B
|
||||||
|
sample 17:
|
||||||
|
time = 666655
|
||||||
|
flags = 0
|
||||||
|
data = length 4386, hash 536E4530
|
||||||
|
sample 18:
|
||||||
|
time = 600000
|
||||||
|
flags = 0
|
||||||
|
data = length 2418, hash 3CA55886
|
||||||
|
sample 19:
|
||||||
|
time = 566655
|
||||||
|
flags = 0
|
||||||
|
data = length 818, hash CF732FE9
|
||||||
|
sample 20:
|
||||||
|
time = 633322
|
||||||
|
flags = 0
|
||||||
|
data = length 931, hash 9307BF9B
|
||||||
|
sample 21:
|
||||||
|
time = 800000
|
||||||
|
flags = 0
|
||||||
|
data = length 5881, hash 95D65FB
|
||||||
|
sample 22:
|
||||||
|
time = 733322
|
||||||
|
flags = 0
|
||||||
|
data = length 3241, hash C3722BD2
|
||||||
|
sample 23:
|
||||||
|
time = 700000
|
||||||
|
flags = 0
|
||||||
|
data = length 1957, hash 54FE72A0
|
||||||
|
sample 24:
|
||||||
|
time = 766655
|
||||||
|
flags = 0
|
||||||
|
data = length 943, hash 467A1DB1
|
||||||
|
sample 25:
|
||||||
|
time = 933322
|
||||||
|
flags = 0
|
||||||
|
data = length 7927, hash 20EB2632
|
||||||
|
sample 26:
|
||||||
|
time = 866655
|
||||||
|
flags = 0
|
||||||
|
data = length 3313, hash B854BF3E
|
||||||
|
sample 27:
|
||||||
|
time = 833322
|
||||||
|
flags = 0
|
||||||
|
data = length 2729, hash FC827FEE
|
||||||
|
sample 28:
|
||||||
|
time = 900000
|
||||||
|
flags = 0
|
||||||
|
data = length 847, hash 739EFD75
|
||||||
|
sample 29:
|
||||||
|
time = 966655
|
||||||
|
flags = 0
|
||||||
|
data = length 1780, hash 8C1AF0B1
|
||||||
|
sample 30:
|
||||||
|
time = 1000000
|
||||||
|
flags = 1
|
||||||
|
data = length 29827, hash B287A700
|
||||||
|
sample 31:
|
||||||
|
time = 1133322
|
||||||
|
flags = 0
|
||||||
|
data = length 2572, hash AB201EC8
|
||||||
|
sample 32:
|
||||||
|
time = 1066655
|
||||||
|
flags = 0
|
||||||
|
data = length 980, hash BD0ADFF1
|
||||||
|
sample 33:
|
||||||
|
time = 1033322
|
||||||
|
flags = 0
|
||||||
|
data = length 343, hash 645A3973
|
||||||
|
sample 34:
|
||||||
|
time = 1100000
|
||||||
|
flags = 0
|
||||||
|
data = length 476, hash 89BF903C
|
||||||
|
sample 35:
|
||||||
|
time = 1266655
|
||||||
|
flags = 0
|
||||||
|
data = length 9908, hash BDFEA8A4
|
||||||
|
sample 36:
|
||||||
|
time = 1200000
|
||||||
|
flags = 0
|
||||||
|
data = length 2422, hash 863FCD89
|
||||||
|
sample 37:
|
||||||
|
time = 1166655
|
||||||
|
flags = 0
|
||||||
|
data = length 718, hash E21DB793
|
||||||
|
sample 38:
|
||||||
|
time = 1233322
|
||||||
|
flags = 0
|
||||||
|
data = length 475, hash 6E2B14C6
|
||||||
|
sample 39:
|
||||||
|
time = 1400000
|
||||||
|
flags = 0
|
||||||
|
data = length 3084, hash 92862A1A
|
||||||
|
sample 40:
|
||||||
|
time = 1333322
|
||||||
|
flags = 0
|
||||||
|
data = length 825, hash 978654C8
|
||||||
|
sample 41:
|
||||||
|
time = 1300000
|
||||||
|
flags = 0
|
||||||
|
data = length 705, hash 28D04FCA
|
||||||
|
sample 42:
|
||||||
|
time = 1366655
|
||||||
|
flags = 0
|
||||||
|
data = length 335, hash D736EAC1
|
||||||
|
sample 43:
|
||||||
|
time = 1533322
|
||||||
|
flags = 0
|
||||||
|
data = length 3295, hash 2FCFA14A
|
||||||
|
sample 44:
|
||||||
|
time = 1466655
|
||||||
|
flags = 0
|
||||||
|
data = length 897, hash 6C76AFF1
|
||||||
|
sample 45:
|
||||||
|
time = 1433322
|
||||||
|
flags = 0
|
||||||
|
data = length 499, hash DDBE39E
|
||||||
|
sample 46:
|
||||||
|
time = 1500000
|
||||||
|
flags = 0
|
||||||
|
data = length 565, hash D269272C
|
||||||
|
sample 47:
|
||||||
|
time = 1666655
|
||||||
|
flags = 0
|
||||||
|
data = length 1958, hash 4A334D4E
|
||||||
|
sample 48:
|
||||||
|
time = 1600000
|
||||||
|
flags = 0
|
||||||
|
data = length 871, hash 21A0191A
|
||||||
|
sample 49:
|
||||||
|
time = 1566655
|
||||||
|
flags = 0
|
||||||
|
data = length 363, hash 376C3CBE
|
||||||
|
sample 50:
|
||||||
|
time = 1633322
|
||||||
|
flags = 0
|
||||||
|
data = length 398, hash BA958C19
|
||||||
|
sample 51:
|
||||||
|
time = 1800000
|
||||||
|
flags = 0
|
||||||
|
data = length 5413, hash 83EA9FBA
|
||||||
|
sample 52:
|
||||||
|
time = 1733322
|
||||||
|
flags = 0
|
||||||
|
data = length 1124, hash D7ADA732
|
||||||
|
sample 53:
|
||||||
|
time = 1700000
|
||||||
|
flags = 0
|
||||||
|
data = length 579, hash B6D6A34F
|
||||||
|
sample 54:
|
||||||
|
time = 1766655
|
||||||
|
flags = 0
|
||||||
|
data = length 1206, hash 1C5174A3
|
||||||
|
sample 55:
|
||||||
|
time = 1933322
|
||||||
|
flags = 0
|
||||||
|
data = length 5172, hash C4EBB198
|
||||||
|
sample 56:
|
||||||
|
time = 1866655
|
||||||
|
flags = 0
|
||||||
|
data = length 2045, hash 81261E50
|
||||||
|
sample 57:
|
||||||
|
time = 1833322
|
||||||
|
flags = 0
|
||||||
|
data = length 915, hash FFEDB29E
|
||||||
|
sample 58:
|
||||||
|
time = 1900000
|
||||||
|
flags = 0
|
||||||
|
data = length 1513, hash C03809BF
|
||||||
|
sample 59:
|
||||||
|
time = 1966655
|
||||||
|
flags = 0
|
||||||
|
data = length 4744, hash D0E55AF1
|
||||||
|
sample 60:
|
||||||
|
time = 2000000
|
||||||
|
flags = 1
|
||||||
|
data = length 30426, hash E5A29561
|
||||||
|
sample 61:
|
||||||
|
time = 2133322
|
||||||
|
flags = 0
|
||||||
|
data = length 7531, hash 756AE4D1
|
||||||
|
sample 62:
|
||||||
|
time = 2066655
|
||||||
|
flags = 0
|
||||||
|
data = length 2740, hash 669E4DF1
|
||||||
|
sample 63:
|
||||||
|
time = 2033322
|
||||||
|
flags = 0
|
||||||
|
data = length 1829, hash 66A7C400
|
||||||
|
sample 64:
|
||||||
|
time = 2100000
|
||||||
|
flags = 0
|
||||||
|
data = length 1423, hash DD6288B3
|
||||||
|
sample 65:
|
||||||
|
time = 2266655
|
||||||
|
flags = 0
|
||||||
|
data = length 12033, hash 244DD978
|
||||||
|
sample 66:
|
||||||
|
time = 2200000
|
||||||
|
flags = 0
|
||||||
|
data = length 2481, hash 9302A030
|
||||||
|
sample 67:
|
||||||
|
time = 2166655
|
||||||
|
flags = 0
|
||||||
|
data = length 1770, hash 86E54271
|
||||||
|
sample 68:
|
||||||
|
time = 2233322
|
||||||
|
flags = 0
|
||||||
|
data = length 636, hash B0BB7A9D
|
||||||
|
sample 69:
|
||||||
|
time = 2400000
|
||||||
|
flags = 0
|
||||||
|
data = length 5120, hash 8381F6ED
|
||||||
|
sample 70:
|
||||||
|
time = 2333322
|
||||||
|
flags = 0
|
||||||
|
data = length 1610, hash E9A0711D
|
||||||
|
sample 71:
|
||||||
|
time = 2300000
|
||||||
|
flags = 0
|
||||||
|
data = length 961, hash 494E3770
|
||||||
|
sample 72:
|
||||||
|
time = 2366655
|
||||||
|
flags = 0
|
||||||
|
data = length 1402, hash 3D40A6D1
|
||||||
|
sample 73:
|
||||||
|
time = 2533322
|
||||||
|
flags = 0
|
||||||
|
data = length 8006, hash A8C425BC
|
||||||
|
sample 74:
|
||||||
|
time = 2466655
|
||||||
|
flags = 0
|
||||||
|
data = length 4620, hash 18719317
|
||||||
|
sample 75:
|
||||||
|
time = 2433322
|
||||||
|
flags = 0
|
||||||
|
data = length 2507, hash 104771A0
|
||||||
|
sample 76:
|
||||||
|
time = 2500000
|
||||||
|
flags = 0
|
||||||
|
data = length 3970, hash B52DD9E
|
||||||
|
sample 77:
|
||||||
|
time = 2666655
|
||||||
|
flags = 0
|
||||||
|
data = length 6556, hash 173F0724
|
||||||
|
sample 78:
|
||||||
|
time = 2600000
|
||||||
|
flags = 0
|
||||||
|
data = length 1824, hash 8BB687AC
|
||||||
|
sample 79:
|
||||||
|
time = 2566655
|
||||||
|
flags = 0
|
||||||
|
data = length 1851, hash 29CD7E58
|
||||||
|
sample 80:
|
||||||
|
time = 2633322
|
||||||
|
flags = 0
|
||||||
|
data = length 731, hash 4E6BD69C
|
||||||
|
sample 81:
|
||||||
|
time = 2800000
|
||||||
|
flags = 0
|
||||||
|
data = length 3255, hash FCDDDFFC
|
||||||
|
sample 82:
|
||||||
|
time = 2733322
|
||||||
|
flags = 0
|
||||||
|
data = length 1118, hash 77E855CA
|
||||||
|
sample 83:
|
||||||
|
time = 2700000
|
||||||
|
flags = 0
|
||||||
|
data = length 413, hash E83C433F
|
||||||
|
sample 84:
|
||||||
|
time = 2766655
|
||||||
|
flags = 0
|
||||||
|
data = length 514, hash 9ED8EF4B
|
||||||
|
sample 85:
|
||||||
|
time = 2933322
|
||||||
|
flags = 0
|
||||||
|
data = length 2919, hash 93BC27A8
|
||||||
|
sample 86:
|
||||||
|
time = 2866655
|
||||||
|
flags = 0
|
||||||
|
data = length 833, hash 213B7EAB
|
||||||
|
sample 87:
|
||||||
|
time = 2833322
|
||||||
|
flags = 0
|
||||||
|
data = length 574, hash 554020E9
|
||||||
|
sample 88:
|
||||||
|
time = 2900000
|
||||||
|
flags = 0
|
||||||
|
data = length 431, hash 6C3E69F6
|
||||||
|
sample 89:
|
||||||
|
time = 2966655
|
||||||
|
flags = 0
|
||||||
|
data = length 2242, hash 8EC9C1D6
|
||||||
|
sample 90:
|
||||||
|
time = 2999988
|
||||||
|
flags = 1
|
||||||
|
data = length 33075, hash C0FCCB84
|
||||||
|
sample 91:
|
||||||
|
time = 3133311
|
||||||
|
flags = 0
|
||||||
|
data = length 3349, hash ADE2C1A1
|
||||||
|
sample 92:
|
||||||
|
time = 3066644
|
||||||
|
flags = 0
|
||||||
|
data = length 890, hash C384FE6A
|
||||||
|
sample 93:
|
||||||
|
time = 3033311
|
||||||
|
flags = 0
|
||||||
|
data = length 822, hash 2897A348
|
||||||
|
sample 94:
|
||||||
|
time = 3099988
|
||||||
|
flags = 0
|
||||||
|
data = length 347, hash 968D3ED1
|
||||||
|
sample 95:
|
||||||
|
time = 3266644
|
||||||
|
flags = 0
|
||||||
|
data = length 4885, hash 57E006DF
|
||||||
|
sample 96:
|
||||||
|
time = 3199988
|
||||||
|
flags = 0
|
||||||
|
data = length 1714, hash 10B49A30
|
||||||
|
sample 97:
|
||||||
|
time = 3166644
|
||||||
|
flags = 0
|
||||||
|
data = length 502, hash B0FDBCA
|
||||||
|
sample 98:
|
||||||
|
time = 3233311
|
||||||
|
flags = 0
|
||||||
|
data = length 378, hash AFCB47B5
|
||||||
|
sample 99:
|
||||||
|
time = 3399988
|
||||||
|
flags = 0
|
||||||
|
data = length 2216, hash 102B8AD4
|
||||||
|
sample 100:
|
||||||
|
time = 3333311
|
||||||
|
flags = 0
|
||||||
|
data = length 785, hash 10C1C847
|
||||||
|
sample 101:
|
||||||
|
time = 3299988
|
||||||
|
flags = 0
|
||||||
|
data = length 499, hash F11DD54
|
||||||
|
sample 102:
|
||||||
|
time = 3366644
|
||||||
|
flags = 0
|
||||||
|
data = length 352, hash 32BD14FA
|
||||||
|
sample 103:
|
||||||
|
time = 3533311
|
||||||
|
flags = 0
|
||||||
|
data = length 2525, hash F426C83B
|
||||||
|
sample 104:
|
||||||
|
time = 3466644
|
||||||
|
flags = 0
|
||||||
|
data = length 732, hash 6BD8DF40
|
||||||
|
sample 105:
|
||||||
|
time = 3433311
|
||||||
|
flags = 0
|
||||||
|
data = length 466, hash F2253523
|
||||||
|
sample 106:
|
||||||
|
time = 3499988
|
||||||
|
flags = 0
|
||||||
|
data = length 519, hash 457EB20F
|
||||||
|
sample 107:
|
||||||
|
time = 3666644
|
||||||
|
flags = 0
|
||||||
|
data = length 1205, hash A8894214
|
||||||
|
sample 108:
|
||||||
|
time = 3599988
|
||||||
|
flags = 0
|
||||||
|
data = length 483, hash 3C3E6CBA
|
||||||
|
sample 109:
|
||||||
|
time = 3566644
|
||||||
|
flags = 0
|
||||||
|
data = length 157, hash E3D5E025
|
||||||
|
sample 110:
|
||||||
|
time = 3633311
|
||||||
|
flags = 0
|
||||||
|
data = length 237, hash A7217EC5
|
||||||
|
sample 111:
|
||||||
|
time = 3799988
|
||||||
|
flags = 0
|
||||||
|
data = length 1330, hash 6CDE1190
|
||||||
|
sample 112:
|
||||||
|
time = 3733311
|
||||||
|
flags = 0
|
||||||
|
data = length 475, hash B9925752
|
||||||
|
sample 113:
|
||||||
|
time = 3699988
|
||||||
|
flags = 0
|
||||||
|
data = length 392, hash 2709B2E0
|
||||||
|
sample 114:
|
||||||
|
time = 3766644
|
||||||
|
flags = 0
|
||||||
|
data = length 258, hash 900651AF
|
||||||
|
sample 115:
|
||||||
|
time = 3933311
|
||||||
|
flags = 0
|
||||||
|
data = length 1130, hash 9C8E9289
|
||||||
|
sample 116:
|
||||||
|
time = 3866644
|
||||||
|
flags = 0
|
||||||
|
data = length 581, hash 79FAD374
|
||||||
|
sample 117:
|
||||||
|
time = 3833311
|
||||||
|
flags = 0
|
||||||
|
data = length 285, hash B460CF8A
|
||||||
|
sample 118:
|
||||||
|
time = 3899988
|
||||||
|
flags = 0
|
||||||
|
data = length 277, hash A40F948C
|
||||||
|
sample 119:
|
||||||
|
time = 3966644
|
||||||
|
flags = 0
|
||||||
|
data = length 1318, hash 9A6E5D81
|
||||||
|
tracksEnded = true
|
@ -65,12 +65,12 @@ moof (2876 bytes):
|
|||||||
tfhd (24 bytes):
|
tfhd (24 bytes):
|
||||||
Data = length 16, hash D37153D4
|
Data = length 16, hash D37153D4
|
||||||
trun (1100 bytes):
|
trun (1100 bytes):
|
||||||
Data = length 1092, hash BA1962E9
|
Data = length 1092, hash 7B5FDF48
|
||||||
traf (1720 bytes):
|
traf (1720 bytes):
|
||||||
tfhd (24 bytes):
|
tfhd (24 bytes):
|
||||||
Data = length 16, hash 67B5C2D5
|
Data = length 16, hash 67B5C2D5
|
||||||
trun (1688 bytes):
|
trun (1688 bytes):
|
||||||
Data = length 1680, hash 2EDF9B97
|
Data = length 1680, hash 51194976
|
||||||
mdat (5712387 bytes):
|
mdat (5712387 bytes):
|
||||||
Data = length 5712379, hash 86B2819D
|
Data = length 5712379, hash 86B2819D
|
||||||
moof (1244 bytes):
|
moof (1244 bytes):
|
||||||
@ -80,11 +80,11 @@ moof (1244 bytes):
|
|||||||
tfhd (24 bytes):
|
tfhd (24 bytes):
|
||||||
Data = length 16, hash D372A134
|
Data = length 16, hash D372A134
|
||||||
trun (464 bytes):
|
trun (464 bytes):
|
||||||
Data = length 456, hash E01BEFF7
|
Data = length 456, hash AB9FD4D6
|
||||||
traf (724 bytes):
|
traf (724 bytes):
|
||||||
tfhd (24 bytes):
|
tfhd (24 bytes):
|
||||||
Data = length 16, hash 67B71035
|
Data = length 16, hash 67B71035
|
||||||
trun (692 bytes):
|
trun (692 bytes):
|
||||||
Data = length 684, hash 73BBFD29
|
Data = length 684, hash 121FB688
|
||||||
mdat (2364921 bytes):
|
mdat (2364921 bytes):
|
||||||
Data = length 2364913, hash D363A845
|
Data = length 2364913, hash D363A845
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
trun (80 bytes):
|
trun (80 bytes):
|
||||||
Data = length 72, hash 516DBD9
|
Data = length 72, hash 876190B8
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
trun (100 bytes):
|
||||||
|
Data = length 92, hash 995185A4
|
Loading…
x
Reference in New Issue
Block a user