mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Add E2E test for Mp4Muxer to verify overall box structure
There is no super test which covers whole MP4 structure. In all the E2E test, it verified against `ExtractorOutput` which would remain same if there are changes in the box structure. PiperOrigin-RevId: 608310006
This commit is contained in:
parent
2cd0cb30cd
commit
d097fe11d0
4 changed files with 100 additions and 20 deletions
|
|
@ -16,9 +16,12 @@
|
||||||
package androidx.media3.muxer;
|
package androidx.media3.muxer;
|
||||||
|
|
||||||
import static androidx.media3.muxer.MuxerTestUtil.FAKE_VIDEO_FORMAT;
|
import static androidx.media3.muxer.MuxerTestUtil.FAKE_VIDEO_FORMAT;
|
||||||
|
import static androidx.media3.muxer.MuxerTestUtil.XMP_SAMPLE_DATA;
|
||||||
|
import static androidx.media3.muxer.MuxerTestUtil.getFakeSampleAndSampleInfo;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.media3.container.Mp4TimestampData;
|
import androidx.media3.container.Mp4TimestampData;
|
||||||
|
|
@ -43,6 +46,8 @@ import org.junit.runner.RunWith;
|
||||||
public class Mp4MuxerEndToEndTest {
|
public class Mp4MuxerEndToEndTest {
|
||||||
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createMp4File_addTrackAndMetadataButNoSamples_createsEmptyFile() throws IOException {
|
public void createMp4File_addTrackAndMetadataButNoSamples_createsEmptyFile() throws IOException {
|
||||||
String outputFilePath = temporaryFolder.newFile().getPath();
|
String outputFilePath = temporaryFolder.newFile().getPath();
|
||||||
|
|
@ -69,13 +74,13 @@ public class Mp4MuxerEndToEndTest {
|
||||||
/* creationTimestampSeconds= */ 100_000_000L,
|
/* creationTimestampSeconds= */ 100_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 200L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 200L);
|
||||||
Pair<ByteBuffer, BufferInfo> track2Sample1 =
|
Pair<ByteBuffer, BufferInfo> track2Sample1 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
||||||
Pair<ByteBuffer, BufferInfo> track2Sample2 =
|
Pair<ByteBuffer, BufferInfo> track2Sample2 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 300L);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||||
|
|
@ -98,7 +103,7 @@ public class Mp4MuxerEndToEndTest {
|
||||||
FakeExtractorOutput fakeExtractorOutput =
|
FakeExtractorOutput fakeExtractorOutput =
|
||||||
TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), outputFilePath);
|
TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), outputFilePath);
|
||||||
DumpFileAsserts.assertOutput(
|
DumpFileAsserts.assertOutput(
|
||||||
ApplicationProvider.getApplicationContext(),
|
context,
|
||||||
fakeExtractorOutput,
|
fakeExtractorOutput,
|
||||||
MuxerTestUtil.getExpectedDumpFilePath("mp4_with_same_tracks_offset.mp4"));
|
MuxerTestUtil.getExpectedDumpFilePath("mp4_with_same_tracks_offset.mp4"));
|
||||||
}
|
}
|
||||||
|
|
@ -112,13 +117,13 @@ public class Mp4MuxerEndToEndTest {
|
||||||
/* creationTimestampSeconds= */ 100_000_000L,
|
/* creationTimestampSeconds= */ 100_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
||||||
Pair<ByteBuffer, BufferInfo> track2Sample1 =
|
Pair<ByteBuffer, BufferInfo> track2Sample1 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
||||||
Pair<ByteBuffer, BufferInfo> track2Sample2 =
|
Pair<ByteBuffer, BufferInfo> track2Sample2 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 200L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 200L);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||||
|
|
@ -137,7 +142,7 @@ public class Mp4MuxerEndToEndTest {
|
||||||
FakeExtractorOutput fakeExtractorOutput =
|
FakeExtractorOutput fakeExtractorOutput =
|
||||||
TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), outputFilePath);
|
TestUtil.extractAllSamplesFromFilePath(new Mp4Extractor(), outputFilePath);
|
||||||
DumpFileAsserts.assertOutput(
|
DumpFileAsserts.assertOutput(
|
||||||
ApplicationProvider.getApplicationContext(),
|
context,
|
||||||
fakeExtractorOutput,
|
fakeExtractorOutput,
|
||||||
MuxerTestUtil.getExpectedDumpFilePath("mp4_with_different_tracks_offset.mp4"));
|
MuxerTestUtil.getExpectedDumpFilePath("mp4_with_different_tracks_offset.mp4"));
|
||||||
}
|
}
|
||||||
|
|
@ -147,11 +152,11 @@ public class Mp4MuxerEndToEndTest {
|
||||||
String outputFilePath = temporaryFolder.newFile().getPath();
|
String outputFilePath = temporaryFolder.newFile().getPath();
|
||||||
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
Mp4Muxer mp4Muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 2000L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 2000L);
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample3 =
|
Pair<ByteBuffer, BufferInfo> track1Sample3 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 1000L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 1000L);
|
||||||
try {
|
try {
|
||||||
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||||
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
|
mp4Muxer.writeSampleData(track1, track1Sample1.first, track1Sample1.second);
|
||||||
|
|
@ -174,9 +179,9 @@ public class Mp4MuxerEndToEndTest {
|
||||||
/* creationTimestampSeconds= */ 100_000_000L,
|
/* creationTimestampSeconds= */ 100_000_000L,
|
||||||
/* modificationTimestampSeconds= */ 500_000_000L));
|
/* modificationTimestampSeconds= */ 500_000_000L));
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
Pair<ByteBuffer, BufferInfo> track1Sample1 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
||||||
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
Pair<ByteBuffer, BufferInfo> track1Sample2 =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 100L);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
TrackToken track1 = mp4Muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||||
|
|
@ -194,8 +199,39 @@ public class Mp4MuxerEndToEndTest {
|
||||||
new DumpableMp4Box(ByteBuffer.wrap(TestUtil.getByteArrayFromFilePath(outputFilePath)));
|
new DumpableMp4Box(ByteBuffer.wrap(TestUtil.getByteArrayFromFilePath(outputFilePath)));
|
||||||
// Output contains only one trak box.
|
// Output contains only one trak box.
|
||||||
DumpFileAsserts.assertOutput(
|
DumpFileAsserts.assertOutput(
|
||||||
ApplicationProvider.getApplicationContext(),
|
context, dumpableBox, MuxerTestUtil.getExpectedDumpFilePath("mp4_without_empty_track.mp4"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeMp4File_withSampleAndMetadata_matchedExpectedBoxStructure() throws Exception {
|
||||||
|
String outputFilePath = temporaryFolder.newFile().getPath();
|
||||||
|
Mp4Muxer muxer = new Mp4Muxer.Builder(new FileOutputStream(outputFilePath)).build();
|
||||||
|
Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
|
||||||
|
getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
||||||
|
byte[] xmpBytes = TestUtil.getByteArray(context, XMP_SAMPLE_DATA);
|
||||||
|
ByteBuffer xmp = ByteBuffer.wrap(xmpBytes);
|
||||||
|
|
||||||
|
try {
|
||||||
|
muxer.setOrientation(90);
|
||||||
|
muxer.setLocation(/* latitude= */ 33.0f, /* longitude= */ -120f);
|
||||||
|
muxer.setCaptureFps(120.0f);
|
||||||
|
muxer.setTimestampData(
|
||||||
|
new Mp4TimestampData(
|
||||||
|
/* creationTimestampSeconds= */ 1_000_000L,
|
||||||
|
/* modificationTimestampSeconds= */ 5_000_000L));
|
||||||
|
muxer.addMetadata("StringKey1", "StringValue");
|
||||||
|
muxer.addXmp(xmp);
|
||||||
|
TrackToken token = muxer.addTrack(/* sortKey= */ 0, FAKE_VIDEO_FORMAT);
|
||||||
|
muxer.writeSampleData(token, sampleAndSampleInfo.first, sampleAndSampleInfo.second);
|
||||||
|
} finally {
|
||||||
|
muxer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
DumpableMp4Box dumpableBox =
|
||||||
|
new DumpableMp4Box(ByteBuffer.wrap(TestUtil.getByteArrayFromFilePath(outputFilePath)));
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
context,
|
||||||
dumpableBox,
|
dumpableBox,
|
||||||
MuxerTestUtil.getExpectedDumpFilePath("mp4_without_empty_track.mp4"));
|
MuxerTestUtil.getExpectedDumpFilePath("mp4_with_samples_and_metadata.mp4"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
package androidx.media3.muxer;
|
package androidx.media3.muxer;
|
||||||
|
|
||||||
import static androidx.media3.muxer.MuxerTestUtil.FAKE_VIDEO_FORMAT;
|
import static androidx.media3.muxer.MuxerTestUtil.FAKE_VIDEO_FORMAT;
|
||||||
|
import static androidx.media3.muxer.MuxerTestUtil.XMP_SAMPLE_DATA;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.MediaCodec.BufferInfo;
|
import android.media.MediaCodec.BufferInfo;
|
||||||
|
|
@ -41,9 +42,6 @@ import org.junit.runner.RunWith;
|
||||||
public class Mp4MuxerMetadataTest {
|
public class Mp4MuxerMetadataTest {
|
||||||
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
|
||||||
// Input files.
|
|
||||||
private static final String XMP_SAMPLE_DATA = "media/xmp/sample_datetime_xmp.xmp";
|
|
||||||
|
|
||||||
private final Context context = ApplicationProvider.getApplicationContext();
|
private final Context context = ApplicationProvider.getApplicationContext();
|
||||||
private final Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
|
private final Pair<ByteBuffer, BufferInfo> sampleAndSampleInfo =
|
||||||
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
MuxerTestUtil.getFakeSampleAndSampleInfo(/* presentationTimeUs= */ 0L);
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@ import java.nio.ByteBuffer;
|
||||||
.setInitializationData(ImmutableList.of(FAKE_CSD_0, FAKE_CSD_1))
|
.setInitializationData(ImmutableList.of(FAKE_CSD_0, FAKE_CSD_1))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
public static final String XMP_SAMPLE_DATA = "media/xmp/sample_datetime_xmp.xmp";
|
||||||
|
|
||||||
private static final byte[] FAKE_H264_SAMPLE =
|
private static final byte[] FAKE_H264_SAMPLE =
|
||||||
BaseEncoding.base16()
|
BaseEncoding.base16()
|
||||||
.decode(
|
.decode(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
ftyp (28 bytes):
|
||||||
|
Data = length 20, hash EF896440
|
||||||
|
mdat (71 bytes):
|
||||||
|
Data = length 55, hash 6B19F4A7
|
||||||
|
moov (873 bytes):
|
||||||
|
mvhd (108 bytes):
|
||||||
|
Data = length 100, hash 2613A5C
|
||||||
|
udta (38 bytes):
|
||||||
|
Data = length 30, hash 25372BB9
|
||||||
|
meta (177 bytes):
|
||||||
|
hdlr (33 bytes):
|
||||||
|
Data = length 25, hash C39D0F5B
|
||||||
|
keys (65 bytes):
|
||||||
|
Data = length 57, hash E97C2148
|
||||||
|
ilst (71 bytes):
|
||||||
|
Data = length 63, hash D81D174
|
||||||
|
trak (542 bytes):
|
||||||
|
tkhd (92 bytes):
|
||||||
|
Data = length 84, hash 4ACEE52E
|
||||||
|
mdia (442 bytes):
|
||||||
|
mdhd (32 bytes):
|
||||||
|
Data = length 24, hash 41542D81
|
||||||
|
hdlr (44 bytes):
|
||||||
|
Data = length 36, hash A0852FF2
|
||||||
|
minf (358 bytes):
|
||||||
|
vmhd (20 bytes):
|
||||||
|
Data = length 12, hash EE830681
|
||||||
|
dinf (36 bytes):
|
||||||
|
Data = length 28, hash D535436B
|
||||||
|
stbl (294 bytes):
|
||||||
|
stsd (166 bytes):
|
||||||
|
Data = length 158, hash 11532063
|
||||||
|
stts (24 bytes):
|
||||||
|
Data = length 16, hash E4FC6483
|
||||||
|
stsz (24 bytes):
|
||||||
|
Data = length 16, hash 50B7F5B9
|
||||||
|
stsc (28 bytes):
|
||||||
|
Data = length 20, hash 8F6E8285
|
||||||
|
co64 (24 bytes):
|
||||||
|
Data = length 16, hash E4EE4D2E
|
||||||
|
stss (20 bytes):
|
||||||
|
Data = length 12, hash EE911E03
|
||||||
|
uuid (2853 bytes):
|
||||||
|
Data = length 2845, hash 52AF0F9D
|
||||||
Loading…
Reference in a new issue