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:
Googler 2024-05-22 23:04:46 -07:00 committed by Copybara-Service
parent 1ffeafecc3
commit 37c2d9957e
8 changed files with 592 additions and 32 deletions

View File

@ -27,7 +27,7 @@ import androidx.media3.test.utils.DumpableMp4Box;
import androidx.media3.test.utils.FakeExtractorOutput;
import androidx.media3.test.utils.TestUtil;
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.IOException;
import java.nio.ByteBuffer;
@ -38,14 +38,26 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
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}. */
@RunWith(AndroidJUnit4.class)
@RunWith(Parameterized.class)
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";
@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();
@Parameter public @MonotonicNonNull String inputFile;
private final Context context = ApplicationProvider.getApplicationContext();
private @MonotonicNonNull String outputPath;
private @MonotonicNonNull FileOutputStream outputStream;
@ -71,7 +83,7 @@ public class FragmentedMp4MuxerEndToEndAndroidTest {
new Mp4TimestampData(
/* creationTimestampSeconds= */ 100_000_000L,
/* modificationTimestampSeconds= */ 500_000_000L));
feedInputDataToMuxer(context, fragmentedMp4Muxer, H265_HDR10_MP4);
feedInputDataToMuxer(context, fragmentedMp4Muxer, checkNotNull(inputFile));
} finally {
if (fragmentedMp4Muxer != null) {
fragmentedMp4Muxer.close();
@ -84,7 +96,7 @@ public class FragmentedMp4MuxerEndToEndAndroidTest {
DumpFileAsserts.assertOutput(
context,
fakeExtractorOutput,
AndroidMuxerTestUtil.getExpectedDumpFilePath(H265_HDR10_MP4 + "_fragmented"));
AndroidMuxerTestUtil.getExpectedDumpFilePath(inputFile + "_fragmented"));
}
@Test

View File

@ -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
* 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.
* @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<Integer> compositionOffsets = new ArrayList<>(samplesInfo.size());
if (samplesInfo.isEmpty()) {
@ -998,8 +998,10 @@ import java.util.Locale;
}
/** Returns a track fragment run (trun) box. */
public static ByteBuffer trun(List<SampleMetadata> samplesMetadata, int dataOffset) {
ByteBuffer contents = ByteBuffer.allocate(getTrunBoxContentSize(samplesMetadata.size()));
public static ByteBuffer trun(
List<SampleMetadata> samplesMetadata, int dataOffset, boolean hasBFrame) {
ByteBuffer contents =
ByteBuffer.allocate(getTrunBoxContentSize(samplesMetadata.size(), hasBFrame));
// 0x000001 data-offset-present.
// 0x000100 sample-duration-present: indicates that each sample has its own duration, otherwise
@ -1008,8 +1010,13 @@ import java.util.Locale;
// default is used.
// 0x000400 sample-flags-present: indicates that each sample has its own flags, otherwise the
// default is used.
// Version is 0x0.
int versionAndFlags = 0x0 | 0x000001 | 0x000100 | 0x000200 | 0x000400;
// 0x000800 sample-composition-time-offsets-present: indicates that each sample has its own
// 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(samplesMetadata.size()); // An unsigned 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
? TRUN_BOX_SYNC_SAMPLE_FLAGS
: TRUN_BOX_NON_SYNC_SAMPLE_FLAGS);
if (hasBFrame) {
contents.putInt(currentSampleMetadata.compositionTimeOffsetVu);
}
}
contents.flip();
return BoxUtils.wrapIntoBox("trun", contents);
}
/** Returns the size required for {@link #trun(List, int)} box content. */
public static int getTrunBoxContentSize(int sampleCount) {
/** Returns the size required for {@link #trun(List, int, boolean)} box content. */
public static int getTrunBoxContentSize(int sampleCount, boolean hasBFrame) {
int trunBoxFixedSize = 3 * BYTES_PER_INTEGER;
// 3 int(32-bit) gets written for each sample.
return trunBoxFixedSize + 3 * sampleCount * BYTES_PER_INTEGER;
int intWrittenPerSample = hasBFrame ? 4 : 3;
return trunBoxFixedSize + intWrittenPerSample * sampleCount * BYTES_PER_INTEGER;
}
/** Returns a movie extends (mvex) box. */

View File

@ -29,7 +29,6 @@ import static java.lang.Math.min;
import android.media.MediaCodec;
import android.media.MediaCodec.BufferInfo;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.Util;
@ -53,11 +52,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
public final long durationVu;
public final int size;
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.size = size;
this.flags = flags;
this.compositionTimeOffsetVu = compositionTimeOffsetVu;
}
}
@ -74,7 +75,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private boolean headerCreated;
private long minInputPresentationTimeUs;
private long maxTrackDurationUs;
private long lastSamplePresentationTimeUs;
/**
* Creates an instance.
@ -102,7 +102,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.fragmentDurationUs = fragmentDurationMs * 1_000;
minInputPresentationTimeUs = Long.MAX_VALUE;
currentFragmentSequenceNumber = 1;
lastSamplePresentationTimeUs = C.TIME_UNSET;
}
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)
throws IOException {
checkArgument(token instanceof Track);
checkArgument(
bufferInfo.presentationTimeUs > lastSamplePresentationTimeUs,
"Out of order B-frames are not supported");
if (!headerCreated) {
createHeader();
headerCreated = true;
@ -130,7 +126,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
createFragment();
}
track.writeSampleData(byteBuffer, bufferInfo);
lastSamplePresentationTimeUs = bufferInfo.presentationTimeUs;
BufferInfo firstPendingSample = checkNotNull(track.pendingSamplesBufferInfo.peekFirst());
BufferInfo lastPendingSample = checkNotNull(track.pendingSamplesBufferInfo.peekLast());
minInputPresentationTimeUs =
@ -163,7 +158,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
trafBoxes.add(
Boxes.traf(
Boxes.tfhd(currentTrackInfo.trackId, /* baseDataOffset= */ moofBoxStartPosition),
Boxes.trun(currentTrackInfo.pendingSamplesMetadata, dataOffset)));
Boxes.trun(
currentTrackInfo.pendingSamplesMetadata,
dataOffset,
currentTrackInfo.hasBFrame)));
dataOffset += currentTrackInfo.totalSamplesSize;
}
return trafBoxes.build();
@ -189,7 +187,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
for (int i = 0; i < trackInfos.size(); i++) {
ProcessedTrackInfo trackInfo = trackInfos.get(i);
int trunBoxSize =
trunBoxHeaderFixedSize + getTrunBoxContentSize(trackInfo.pendingSamplesMetadata.size());
trunBoxHeaderFixedSize
+ getTrunBoxContentSize(trackInfo.pendingSamplesMetadata.size(), trackInfo.hasBFrame);
trafBoxesSize += trafBoxHeaderSize + tfhdBoxSize + trunBoxSize;
}
@ -319,6 +318,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
track.pendingSamplesBufferInfo.clear();
}
boolean hasBFrame = false;
ImmutableList<BufferInfo> pendingSamplesBufferInfo = pendingSamplesBufferInfoBuilder.build();
List<Long> sampleDurations =
Boxes.convertPresentationTimestampsToDurationsVu(
@ -329,6 +329,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
track.videoUnitTimebase(),
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<>();
int totalSamplesSize = 0;
for (int i = 0; i < pendingSamplesBufferInfo.size(); i++) {
@ -337,12 +344,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
new SampleMetadata(
sampleDurations.get(i),
pendingSamplesBufferInfo.get(i).size,
pendingSamplesBufferInfo.get(i).flags));
pendingSamplesBufferInfo.get(i).flags,
hasBFrame ? sampleCompositionTimeOffsets.get(i) : 0));
}
return new ProcessedTrackInfo(
trackId,
totalSamplesSize,
hasBFrame,
pendingSamplesByteBuffer.build(),
pendingSamplesMetadata.build());
}
@ -350,16 +359,19 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
private static class ProcessedTrackInfo {
public final int trackId;
public final int totalSamplesSize;
public final boolean hasBFrame;
public final ImmutableList<ByteBuffer> pendingSamplesByteBuffer;
public final ImmutableList<SampleMetadata> pendingSamplesMetadata;
public ProcessedTrackInfo(
int trackId,
int totalSamplesSize,
boolean hasBFrame,
ImmutableList<ByteBuffer> pendingSamplesByteBuffer,
ImmutableList<SampleMetadata> pendingSamplesMetadata) {
this.trackId = trackId;
this.totalSamplesSize = totalSamplesSize;
this.hasBFrame = hasBFrame;
this.pendingSamplesByteBuffer = pendingSamplesByteBuffer;
this.pendingSamplesMetadata = pendingSamplesMetadata;
}

View File

@ -681,16 +681,39 @@ public class BoxesTest {
new SampleMetadata(
/* durationsVu= */ 2_000L,
/* 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);
DumpFileAsserts.assertOutput(
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
public void createTrexBox_matchesExpected() throws IOException {
ByteBuffer trexBox = Boxes.trex(/* trackId= */ 2);

View File

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

View File

@ -65,12 +65,12 @@ moof (2876 bytes):
tfhd (24 bytes):
Data = length 16, hash D37153D4
trun (1100 bytes):
Data = length 1092, hash BA1962E9
Data = length 1092, hash 7B5FDF48
traf (1720 bytes):
tfhd (24 bytes):
Data = length 16, hash 67B5C2D5
trun (1688 bytes):
Data = length 1680, hash 2EDF9B97
Data = length 1680, hash 51194976
mdat (5712387 bytes):
Data = length 5712379, hash 86B2819D
moof (1244 bytes):
@ -80,11 +80,11 @@ moof (1244 bytes):
tfhd (24 bytes):
Data = length 16, hash D372A134
trun (464 bytes):
Data = length 456, hash E01BEFF7
Data = length 456, hash AB9FD4D6
traf (724 bytes):
tfhd (24 bytes):
Data = length 16, hash 67B71035
trun (692 bytes):
Data = length 684, hash 73BBFD29
Data = length 684, hash 121FB688
mdat (2364921 bytes):
Data = length 2364913, hash D363A845

View File

@ -1,2 +1,2 @@
trun (80 bytes):
Data = length 72, hash 516DBD9
Data = length 72, hash 876190B8

View File

@ -0,0 +1,2 @@
trun (100 bytes):
Data = length 92, hash 995185A4