mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
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:
parent
b218d910ad
commit
04ce83691e
@ -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
|
||||
|
Binary file not shown.
Binary file not shown.
@ -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
|
@ -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
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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))
|
||||
|
Loading…
x
Reference in New Issue
Block a user