Fix failure to write negative PTS sample

Fixes an issue caused by no support for negative audio PTS and edit lists
in FrameworkMuxer, Android versions before 11

PiperOrigin-RevId: 607690507
(cherry picked from commit e43f96687c3c6d13907da1a5c53e90ffa57d624c)
This commit is contained in:
Googler 2024-02-16 08:00:23 -08:00 committed by SheenaChhabra
parent b218d910ad
commit 04ce83691e
9 changed files with 737 additions and 1 deletions

View File

@ -4,6 +4,9 @@
* Effect:
* Improved PQ to SDR tone-mapping by converting color spaces.
* Transformer:
* Add workaround for exception thrown due to `MediaMuxer` not supporting
negative presentation timestamps before API 30.
* UI:
* Fallback to include audio track language name if `Locale` cannot
identify a display name

View File

@ -0,0 +1,95 @@
format video:
id = 1
sampleMimeType = video/avc
codecs = avc1.F40016
maxInputSize = 7838
width = 704
height = 576
frameRate = 1.04
colorInfo:
lumaBitdepth = 8
chromaBitdepth = 8
metadata = entries=[TSSE: description=null: values=[Lavf60.3.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000]
initializationData:
data = length 30, hash 9DFD8D5
data = length 9, hash FBADD682
container metadata = entries=[TSSE: description=null: values=[Lavf60.3.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000]
sample:
trackType = video
dataHashCode = 1491581480
size = 7804
isKeyFrame = true
presentationTimeUs = 0
sample:
trackType = video
dataHashCode = -1689048121
size = 7808
isKeyFrame = true
presentationTimeUs = 2500000
sample:
trackType = video
dataHashCode = 1018268785
size = 1301
isKeyFrame = false
presentationTimeUs = 1500000
sample:
trackType = video
dataHashCode = -1625273408
size = 1114
isKeyFrame = false
presentationTimeUs = 500000
sample:
trackType = video
dataHashCode = -374381878
size = 7730
isKeyFrame = true
presentationTimeUs = 5500000
sample:
trackType = video
dataHashCode = -2004918573
size = 1247
isKeyFrame = false
presentationTimeUs = 4500000
sample:
trackType = video
dataHashCode = 1940057858
size = 1110
isKeyFrame = false
presentationTimeUs = 3500000
sample:
trackType = video
dataHashCode = 472148756
size = 7595
isKeyFrame = true
presentationTimeUs = 8500000
sample:
trackType = video
dataHashCode = 911200371
size = 1273
isKeyFrame = false
presentationTimeUs = 7500000
sample:
trackType = video
dataHashCode = -954114383
size = 1130
isKeyFrame = false
presentationTimeUs = 6500000
sample:
trackType = video
dataHashCode = 77841273
size = 6734
isKeyFrame = true
presentationTimeUs = 11500000
sample:
trackType = video
dataHashCode = 1932832421
size = 1437
isKeyFrame = false
presentationTimeUs = 10500000
sample:
trackType = video
dataHashCode = -2133964046
size = 1186
isKeyFrame = false
presentationTimeUs = 9500000
released = true

View File

@ -0,0 +1,423 @@
format audio:
averageBitrate = 140021
peakBitrate = 140781
id = 1
sampleMimeType = audio/mp4a-latm
codecs = mp4a.40.2
maxInputSize = 476
channelCount = 2
sampleRate = 44100
language = und
metadata = entries=[TSSE: description=null: values=[Lavf60.3.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000]
initializationData:
data = length 2, hash 5FF
container metadata = entries=[TSSE: description=null: values=[Lavf60.3.100], Mp4Timestamp: creation time=0, modification time=0, timescale=1000]
sample:
trackType = audio
dataHashCode = -620111888
size = 423
isKeyFrame = true
presentationTimeUs = -16826
sample:
trackType = audio
dataHashCode = -1530182437
size = 411
isKeyFrame = true
presentationTimeUs = 6394
sample:
trackType = audio
dataHashCode = 1230616627
size = 404
isKeyFrame = true
presentationTimeUs = 29614
sample:
trackType = audio
dataHashCode = -1245126751
size = 403
isKeyFrame = true
presentationTimeUs = 52834
sample:
trackType = audio
dataHashCode = -1410779761
size = 422
isKeyFrame = true
presentationTimeUs = 76054
sample:
trackType = audio
dataHashCode = -1047441584
size = 418
isKeyFrame = true
presentationTimeUs = 99274
sample:
trackType = audio
dataHashCode = 1146337558
size = 421
isKeyFrame = true
presentationTimeUs = 122494
sample:
trackType = audio
dataHashCode = 1933912787
size = 400
isKeyFrame = true
presentationTimeUs = 145714
sample:
trackType = audio
dataHashCode = 252268411
size = 400
isKeyFrame = true
presentationTimeUs = 168934
sample:
trackType = audio
dataHashCode = -329438270
size = 410
isKeyFrame = true
presentationTimeUs = 192154
sample:
trackType = audio
dataHashCode = 165440818
size = 391
isKeyFrame = true
presentationTimeUs = 215374
sample:
trackType = audio
dataHashCode = 1712500598
size = 361
isKeyFrame = true
presentationTimeUs = 238594
sample:
trackType = audio
dataHashCode = 1264185277
size = 391
isKeyFrame = true
presentationTimeUs = 261814
sample:
trackType = audio
dataHashCode = -1934387857
size = 390
isKeyFrame = true
presentationTimeUs = 285034
sample:
trackType = audio
dataHashCode = -105475655
size = 388
isKeyFrame = true
presentationTimeUs = 308253
sample:
trackType = audio
dataHashCode = 671697059
size = 399
isKeyFrame = true
presentationTimeUs = 331473
sample:
trackType = audio
dataHashCode = -1835652942
size = 390
isKeyFrame = true
presentationTimeUs = 354693
sample:
trackType = audio
dataHashCode = 1445535733
size = 387
isKeyFrame = true
presentationTimeUs = 377913
sample:
trackType = audio
dataHashCode = 713025022
size = 446
isKeyFrame = true
presentationTimeUs = 401133
sample:
trackType = audio
dataHashCode = 226962058
size = 436
isKeyFrame = true
presentationTimeUs = 424353
sample:
trackType = audio
dataHashCode = -361162400
size = 394
isKeyFrame = true
presentationTimeUs = 447573
sample:
trackType = audio
dataHashCode = -1091817776
size = 417
isKeyFrame = true
presentationTimeUs = 470793
sample:
trackType = audio
dataHashCode = -33570145
size = 442
isKeyFrame = true
presentationTimeUs = 494013
sample:
trackType = audio
dataHashCode = 791597935
size = 416
isKeyFrame = true
presentationTimeUs = 517233
sample:
trackType = audio
dataHashCode = 486177154
size = 396
isKeyFrame = true
presentationTimeUs = 540453
sample:
trackType = audio
dataHashCode = 697876210
size = 395
isKeyFrame = true
presentationTimeUs = 563673
sample:
trackType = audio
dataHashCode = -1416713338
size = 389
isKeyFrame = true
presentationTimeUs = 586893
sample:
trackType = audio
dataHashCode = 180955111
size = 404
isKeyFrame = true
presentationTimeUs = 610113
sample:
trackType = audio
dataHashCode = 1614220208
size = 418
isKeyFrame = true
presentationTimeUs = 633333
sample:
trackType = audio
dataHashCode = 1619215866
size = 393
isKeyFrame = true
presentationTimeUs = 656553
sample:
trackType = audio
dataHashCode = -730291234
size = 402
isKeyFrame = true
presentationTimeUs = 679773
sample:
trackType = audio
dataHashCode = -1734250280
size = 404
isKeyFrame = true
presentationTimeUs = 702993
sample:
trackType = audio
dataHashCode = -24161892
size = 397
isKeyFrame = true
presentationTimeUs = 726213
sample:
trackType = audio
dataHashCode = 1339131070
size = 386
isKeyFrame = true
presentationTimeUs = 749433
sample:
trackType = audio
dataHashCode = 1996219279
size = 377
isKeyFrame = true
presentationTimeUs = 772653
sample:
trackType = audio
dataHashCode = -1832486999
size = 409
isKeyFrame = true
presentationTimeUs = 795873
sample:
trackType = audio
dataHashCode = 1120922082
size = 402
isKeyFrame = true
presentationTimeUs = 819092
sample:
trackType = audio
dataHashCode = -1514208718
size = 390
isKeyFrame = true
presentationTimeUs = 842312
sample:
trackType = audio
dataHashCode = 1442267846
size = 388
isKeyFrame = true
presentationTimeUs = 865532
sample:
trackType = audio
dataHashCode = -2064065315
size = 377
isKeyFrame = true
presentationTimeUs = 888752
sample:
trackType = audio
dataHashCode = -502416917
size = 391
isKeyFrame = true
presentationTimeUs = 911972
sample:
trackType = audio
dataHashCode = 730491655
size = 398
isKeyFrame = true
presentationTimeUs = 935192
sample:
trackType = audio
dataHashCode = -1910342128
size = 381
isKeyFrame = true
presentationTimeUs = 958412
sample:
trackType = audio
dataHashCode = 475981018
size = 393
isKeyFrame = true
presentationTimeUs = 981632
sample:
trackType = audio
dataHashCode = -746607724
size = 393
isKeyFrame = true
presentationTimeUs = 1004852
sample:
trackType = audio
dataHashCode = 1641658609
size = 380
isKeyFrame = true
presentationTimeUs = 1028072
sample:
trackType = audio
dataHashCode = -1148163632
size = 395
isKeyFrame = true
presentationTimeUs = 1051292
sample:
trackType = audio
dataHashCode = 665110699
size = 379
isKeyFrame = true
presentationTimeUs = 1074512
sample:
trackType = audio
dataHashCode = 798207150
size = 403
isKeyFrame = true
presentationTimeUs = 1097732
sample:
trackType = audio
dataHashCode = 1359581525
size = 415
isKeyFrame = true
presentationTimeUs = 1120952
sample:
trackType = audio
dataHashCode = -335439207
size = 400
isKeyFrame = true
presentationTimeUs = 1144172
sample:
trackType = audio
dataHashCode = -198311225
size = 401
isKeyFrame = true
presentationTimeUs = 1167392
sample:
trackType = audio
dataHashCode = -924672246
size = 400
isKeyFrame = true
presentationTimeUs = 1190612
sample:
trackType = audio
dataHashCode = -1282928372
size = 408
isKeyFrame = true
presentationTimeUs = 1213832
sample:
trackType = audio
dataHashCode = -50597927
size = 406
isKeyFrame = true
presentationTimeUs = 1237052
sample:
trackType = audio
dataHashCode = -1671375815
size = 411
isKeyFrame = true
presentationTimeUs = 1260272
sample:
trackType = audio
dataHashCode = -1897083943
size = 414
isKeyFrame = true
presentationTimeUs = 1283492
sample:
trackType = audio
dataHashCode = -1693247556
size = 393
isKeyFrame = true
presentationTimeUs = 1306712
sample:
trackType = audio
dataHashCode = 1287376831
size = 405
isKeyFrame = true
presentationTimeUs = 1329931
sample:
trackType = audio
dataHashCode = -1463566839
size = 412
isKeyFrame = true
presentationTimeUs = 1353151
sample:
trackType = audio
dataHashCode = -841250803
size = 409
isKeyFrame = true
presentationTimeUs = 1376371
sample:
trackType = audio
dataHashCode = 167279197
size = 423
isKeyFrame = true
presentationTimeUs = 1399591
sample:
trackType = audio
dataHashCode = 1819296695
size = 399
isKeyFrame = true
presentationTimeUs = 1422811
sample:
trackType = audio
dataHashCode = 696153948
size = 400
isKeyFrame = true
presentationTimeUs = 1446031
sample:
trackType = audio
dataHashCode = 1462953378
size = 397
isKeyFrame = true
presentationTimeUs = 1469251
sample:
trackType = audio
dataHashCode = 310189811
size = 398
isKeyFrame = true
presentationTimeUs = 1492471
sample:
trackType = audio
dataHashCode = -1082736404
size = 424
isKeyFrame = true
presentationTimeUs = 1515691
sample:
trackType = audio
dataHashCode = 1980862648
size = 417
isKeyFrame = true
presentationTimeUs = 1538911
released = true

View File

@ -68,6 +68,11 @@ import androidx.media3.effect.RgbFilter;
import androidx.media3.effect.ScaleAndRotateTransformation;
import androidx.media3.effect.TimestampWrapper;
import androidx.media3.exoplayer.audio.TeeAudioProcessor;
import androidx.media3.extractor.mp4.Mp4Extractor;
import androidx.media3.extractor.text.DefaultSubtitleParserFactory;
import androidx.media3.test.utils.FakeExtractorOutput;
import androidx.media3.test.utils.FakeTrackOutput;
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;
@ -923,6 +928,118 @@ public class TransformerEndToEndTest {
assertThat(result.exportResult.channelCount).isEqualTo(2);
}
@Test
public void transmux_audioWithEditList_preservesDuration() throws Exception {
String testId = "transmux_audioWithEditList_preservesDuration";
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer = new Transformer.Builder(context).build();
MediaItem mediaItem =
MediaItem.fromUri(Uri.parse("asset:///media/mp4/long_edit_list_audioonly.mp4"));
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Mp4Extractor mp4Extractor = new Mp4Extractor(new DefaultSubtitleParserFactory());
FakeExtractorOutput fakeExtractorOutput =
TestUtil.extractAllSamplesFromFilePath(mp4Extractor, exportTestResult.filePath);
// TODO: b/324842222 - Mp4Extractor reports incorrect duration, without considering edit lists.
assertThat(fakeExtractorOutput.seekMap.getDurationUs()).isEqualTo(1_579_000);
assertThat(fakeExtractorOutput.numberOfTracks).isEqualTo(1);
FakeTrackOutput audioTrack = fakeExtractorOutput.trackOutputs.get(0);
int expectedSampleCount = 68;
audioTrack.assertSampleCount(expectedSampleCount);
if (Util.SDK_INT >= 30) {
// TODO: b/324842222 - Mp4Extractor doesn't interpret Transformer's generated output as
// "gapless" audio. The generated file should have encoderDelay = 742 and first
// sample PTS of 0.
assertThat(audioTrack.lastFormat.encoderDelay).isEqualTo(0);
assertThat(audioTrack.getSampleTimeUs(/* index= */ 0)).isEqualTo(-16_826);
assertThat(audioTrack.getSampleTimeUs(/* index= */ expectedSampleCount - 1))
.isEqualTo(1_538_911);
} else {
// Edit lists are not supported b/142580952 : sample times start from zero,
// and output duration will be longer than input duration by encoder delay.
assertThat(audioTrack.lastFormat.encoderDelay).isEqualTo(0);
assertThat(audioTrack.getSampleTimeUs(/* index= */ 0)).isEqualTo(0);
assertThat(audioTrack.getSampleTimeUs(/* index= */ expectedSampleCount - 1))
.isEqualTo(1_555_736);
}
}
@Test
public void transmux_audioWithEditListUsingInAppMuxer_preservesDuration() throws Exception {
String testId = "transmux_AudioWithEditListUsingInAppMuxer_preservesDuration";
Context context = ApplicationProvider.getApplicationContext();
Transformer transformer =
new Transformer.Builder(context)
.setMuxerFactory(new InAppMuxer.Factory.Builder().build())
.build();
MediaItem mediaItem =
MediaItem.fromUri(Uri.parse("asset:///media/mp4/long_edit_list_audioonly.mp4"));
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Mp4Extractor mp4Extractor = new Mp4Extractor(new DefaultSubtitleParserFactory());
FakeExtractorOutput fakeExtractorOutput =
TestUtil.extractAllSamplesFromFilePath(mp4Extractor, exportTestResult.filePath);
// TODO: b/324903070 - The generated output file has incorrect duration.
assertThat(fakeExtractorOutput.seekMap.getDurationUs()).isEqualTo(1_555_700);
assertThat(fakeExtractorOutput.numberOfTracks).isEqualTo(1);
FakeTrackOutput audioTrack = fakeExtractorOutput.trackOutputs.get(0);
int expectedSampleCount = 68;
audioTrack.assertSampleCount(expectedSampleCount);
// TODO: b/324903070 - InAppMuxer doesn't write edit lists to support gapless audio muxing.
// Output incorrectly starts at encoderDelay 0, PTS 0
assertThat(audioTrack.lastFormat.encoderDelay).isEqualTo(0);
assertThat(audioTrack.getSampleTimeUs(/* index= */ 0)).isEqualTo(0);
// TODO: b/270583563 - InAppMuxer always uses 1 / 48_000 timebase for audio.
// The audio file in this test is 44_100 Hz, with timebase for audio of 1 / 44_100 and
// each sample duration is exactly 1024 / 44_100, with no rounding errors.
// Since InAppMuxer uses a different timebase for audio, some rounding errors are introduced
// and MP4 sample durations are off.
// TODO: b/324903070 - expectedLastSampleTimeUs & expectedDurationUs are incorrect.
// Last sample time cannot be greater than total duration.
assertThat(audioTrack.getSampleTimeUs(/* index= */ expectedSampleCount - 1))
.isEqualTo(1_555_708);
}
@Test
public void transmux_videoWithEditList_trimsFirstIDRFrameDuration() throws Exception {
String testId = "transmux_videoWithEditList_trimsFirstIDRFrameDuration";
Context context = ApplicationProvider.getApplicationContext();
assumeTrue(
"MediaMuxer doesn't support B frames reliably on older SDK versions", Util.SDK_INT >= 29);
Transformer transformer = new Transformer.Builder(context).build();
MediaItem mediaItem =
MediaItem.fromUri(Uri.parse("asset:///media/mp4/iibbibb_editlist_videoonly.mp4"));
ExportTestResult exportTestResult =
new TransformerAndroidTestRunner.Builder(context, transformer)
.build()
.run(testId, mediaItem);
Mp4Extractor mp4Extractor = new Mp4Extractor(new DefaultSubtitleParserFactory());
FakeExtractorOutput fakeExtractorOutput =
TestUtil.extractAllSamplesFromFilePath(mp4Extractor, exportTestResult.filePath);
assertThat(fakeExtractorOutput.numberOfTracks).isEqualTo(1);
// TODO: b/324842222 - Duration isn't written correctly when transmuxing, and differs
// between SDK versions. Do not assert for duration yet.
FakeTrackOutput videoTrack = fakeExtractorOutput.trackOutputs.get(0);
int expectedSampleCount = 13;
videoTrack.assertSampleCount(expectedSampleCount);
assertThat(videoTrack.getSampleTimeUs(/* index= */ 0)).isEqualTo(0);
int sampleIndexWithLargestSampleTime = 10;
assertThat(videoTrack.getSampleTimeUs(sampleIndexWithLargestSampleTime)).isEqualTo(11_500_000);
assertThat(videoTrack.getSampleTimeUs(/* index= */ expectedSampleCount - 1))
.isEqualTo(9_500_000);
}
private static AudioProcessor createSonic(float pitch) {
SonicAudioProcessor sonic = new SonicAudioProcessor();
sonic.setPitch(pitch);

View File

@ -91,6 +91,7 @@ import java.nio.ByteBuffer;
private final long videoDurationUs;
private final MediaCodec.BufferInfo bufferInfo;
private final SparseLongArray trackIndexToLastPresentationTimeUs;
private final SparseLongArray trackIndexToPresentationTimeOffsetUs;
private int videoTrackIndex;
@ -103,6 +104,7 @@ import java.nio.ByteBuffer;
this.videoDurationUs = Util.msToUs(videoDurationMs);
bufferInfo = new MediaCodec.BufferInfo();
trackIndexToLastPresentationTimeUs = new SparseLongArray();
trackIndexToPresentationTimeOffsetUs = new SparseLongArray();
videoTrackIndex = C.INDEX_UNSET;
}
@ -153,6 +155,9 @@ import java.nio.ByteBuffer;
if (!isStarted) {
isStarted = true;
if (Util.SDK_INT < 30 && presentationTimeUs < 0) {
trackIndexToPresentationTimeOffsetUs.put(trackIndex, -presentationTimeUs);
}
try {
mediaMuxer.start();
} catch (RuntimeException e) {
@ -163,6 +168,9 @@ import java.nio.ByteBuffer;
int offset = data.position();
int size = data.limit() - offset;
long presentationTimeOffsetUs = trackIndexToPresentationTimeOffsetUs.get(trackIndex);
presentationTimeUs += presentationTimeOffsetUs;
bufferInfo.set(offset, size, presentationTimeUs, TransformerUtil.getMediaCodecFlags(flags));
long lastSamplePresentationTimeUs = trackIndexToLastPresentationTimeUs.get(trackIndex);
// writeSampleData blocks on old API versions, so check here to avoid calling the method.
@ -174,6 +182,15 @@ import java.nio.ByteBuffer;
+ lastSamplePresentationTimeUs
+ ") unsupported on this API version");
trackIndexToLastPresentationTimeUs.put(trackIndex, presentationTimeUs);
checkState(
presentationTimeOffsetUs == 0 || presentationTimeUs >= lastSamplePresentationTimeUs,
"Samples not in presentation order ("
+ presentationTimeUs
+ " < "
+ lastSamplePresentationTimeUs
+ ") unsupported when using negative PTS workaround");
try {
mediaMuxer.writeSampleData(trackIndex, data, bufferInfo);
} catch (RuntimeException e) {

View File

@ -19,15 +19,18 @@ package androidx.media3.transformer;
import static androidx.media3.test.utils.robolectric.RobolectricUtil.runLooperUntil;
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_DECODED;
import static androidx.media3.transformer.AssetLoader.SUPPORTED_OUTPUT_TYPE_ENCODED;
import static androidx.media3.transformer.DefaultMuxer.Factory.DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS;
import static androidx.media3.transformer.ExportResult.OPTIMIZATION_ABANDONED_KEYFRAME_PLACEMENT_OPTIMAL_FOR_TRIM;
import static androidx.media3.transformer.ExportResult.OPTIMIZATION_FAILED_EXTRACTION_FAILED;
import static androidx.media3.transformer.TestUtil.ASSET_URI_PREFIX;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_AMR_NB;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_AMR_WB;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_ELST_SKIP_500MS;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_RAW;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO;
import static androidx.media3.transformer.TestUtil.FILE_AUDIO_VIDEO_INCREASING_TIMESTAMPS_15S;
import static androidx.media3.transformer.TestUtil.FILE_UNKNOWN_DURATION;
import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ELST_TRIM_IDR_DURATION;
import static androidx.media3.transformer.TestUtil.FILE_VIDEO_ONLY;
import static androidx.media3.transformer.TestUtil.FILE_WITH_SEF_SLOW_MOTION;
import static androidx.media3.transformer.TestUtil.FILE_WITH_SUBTITLES;
@ -66,6 +69,7 @@ import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.effect.Contrast;
import androidx.media3.effect.Presentation;
import androidx.media3.effect.ScaleAndRotateTransformation;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
@ -101,6 +105,7 @@ import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowMediaCodec;
/**
@ -1381,6 +1386,79 @@ public final class MediaItemExportTest {
assertThat(illegalStateException.get()).isNotNull();
}
@Test
@Config(minSdk = 30)
// This test requires Android SDK >= 30 for MediaMuxer negative PTS support.
public void transmux_audioWithEditList_api30_correctDuration() throws Exception {
Transformer transformer =
createTransformerBuilder(muxerFactory, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_ELST_SKIP_500MS);
transformer.start(mediaItem, outputDir.newFile().getPath());
ExportResult result = TransformerTestRunner.runLooper(transformer);
// TODO: b/324245196 - Update this test when bugs are fixed.
// Duration is actually 68267 / 44100 = 1548ms.
// Last frame PTS is 67866 / 44100 = 1.53891 which rounds down to 1538ms.
assertThat(result.durationMs).isEqualTo(1538);
// TODO: b/325020444 - Update this test when bugs are fixed.
// Dump incorrectly includes the last clipped audio sample from input file.
DumpFileAsserts.assertOutput(
context,
muxerFactory.getCreatedMuxer(),
getDumpFileName(
/* originalFileName= */ FILE_AUDIO_ELST_SKIP_500MS,
/* modifications...= */ "transmuxed"));
}
@Test
@Config(minSdk = 21, maxSdk = 29)
// This test requires Android SDK < 30 with no MediaMuxer negative PTS support.
public void transmux_audioWithEditList_api29_frameworkMuxerDoesNotThrow() throws Exception {
// Do not use CapturingMuxer.Factory(), as this test checks for a workaround in
// FrameworkMuxer.
Transformer transformer =
createTransformerBuilder(
new FrameworkMuxer.Factory(DEFAULT_MAX_DELAY_BETWEEN_SAMPLES_MS, C.TIME_UNSET),
/* enableFallback= */ false)
.build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_AUDIO_ELST_SKIP_500MS);
transformer.start(mediaItem, outputDir.newFile().getPath());
ExportResult result = TransformerTestRunner.runLooper(transformer);
// TODO: b/324842222 - Update this test when bugs are fixed.
// The result.durationMs is incorrect in this test because
// FrameworkMuxer workaround doesn't propagate changed timestamps to MuxerWrapper.
assertThat(result.durationMs).isEqualTo(1538);
assertThat(result.exportException).isNull();
}
@Test
@Config(minSdk = 25)
// This test requires Android SDK < 30 for lack of MediaMuxer negative PTS support
// and SDK >= 25 for B-frame support.
public void transmux_trimsFirstIDRDuration() throws Exception {
Transformer transformer =
createTransformerBuilder(muxerFactory, /* enableFallback= */ false).build();
MediaItem mediaItem = MediaItem.fromUri(ASSET_URI_PREFIX + FILE_VIDEO_ELST_TRIM_IDR_DURATION);
transformer.start(mediaItem, outputDir.newFile().getPath());
ExportResult result = TransformerTestRunner.runLooper(transformer);
// TODO: b/324245196 - Update this test when bugs are fixed.
// Duration is actually 12_500. Last frame PTS is 11_500.
assertThat(result.durationMs).isEqualTo(11_500);
int inputFrameCount = 13;
assertThat(result.videoFrameCount).isEqualTo(inputFrameCount);
DumpFileAsserts.assertOutput(
context,
muxerFactory.getCreatedMuxer(),
getDumpFileName(
/* originalFileName= */ FILE_VIDEO_ELST_TRIM_IDR_DURATION,
/* modifications...= */ "transmuxed"));
}
private static final class SlowExtractorsFactory implements ExtractorsFactory {
private final long delayBetweenReadsMs;

View File

@ -53,6 +53,9 @@ public final class TestUtil {
public static final String FILE_AUDIO_AMR_NB = "amr/sample_nb.amr";
public static final String FILE_AUDIO_AC3_UNSUPPORTED_BY_MUXER = "mp4/sample_ac3.mp4";
public static final String FILE_UNKNOWN_DURATION = "mp4/sample_fragmented.mp4";
public static final String FILE_AUDIO_ELST_SKIP_500MS = "mp4/long_edit_list_audioonly.mp4";
public static final String FILE_VIDEO_ELST_TRIM_IDR_DURATION =
"mp4/iibbibb_editlist_videoonly.mp4";
private static final String DUMP_FILE_OUTPUT_DIRECTORY = "transformerdumps";
private static final String DUMP_FILE_EXTENSION = "dump";
@ -60,7 +63,7 @@ public final class TestUtil {
private TestUtil() {}
public static Transformer.Builder createTransformerBuilder(
CapturingMuxer.Factory muxerFactory, boolean enableFallback) {
Muxer.Factory muxerFactory, boolean enableFallback) {
Context context = ApplicationProvider.getApplicationContext();
return new Transformer.Builder(context)
.setClock(new FakeClock(/* isAutoAdvancing= */ true))