mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Update Boxes to support writing negative timestamps to edit list
Previously when there were negative timestamps, the tkhd duration was incorrectly equal to the full track duration rather than the presentation duration of the edit list. From the [docs](https://developer.apple.com/documentation/quicktime-file-format/track_header_atom/duration) - "The value of this field is equal to the sum of the durations of all of the track’s edits". PiperOrigin-RevId: 752655137
This commit is contained in:
parent
48832cbbc4
commit
cfa13e9616
@ -162,7 +162,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
}
|
}
|
||||||
String languageCode = bcp47LanguageTagToIso3(format.language);
|
String languageCode = bcp47LanguageTagToIso3(format.language);
|
||||||
|
|
||||||
// Generate the sample durations to calculate the total duration for tkhd box.
|
// Generate the sample durations to calculate the total duration for tkhd, elst and mvhd
|
||||||
|
// boxes.
|
||||||
List<Integer> sampleDurationsVu =
|
List<Integer> sampleDurationsVu =
|
||||||
convertPresentationTimestampsToDurationsVu(
|
convertPresentationTimestampsToDurationsVu(
|
||||||
track.writtenSamples,
|
track.writtenSamples,
|
||||||
@ -178,6 +179,8 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
long firstInputPtsUs =
|
long firstInputPtsUs =
|
||||||
track.writtenSamples.isEmpty() ? 0 : track.writtenSamples.get(0).presentationTimeUs;
|
track.writtenSamples.isEmpty() ? 0 : track.writtenSamples.get(0).presentationTimeUs;
|
||||||
long trackDurationUs = usFromVu(trackDurationInTrackUnitsVu, track.videoUnitTimebase());
|
long trackDurationUs = usFromVu(trackDurationInTrackUnitsVu, track.videoUnitTimebase());
|
||||||
|
long presentationTrackDurationUs =
|
||||||
|
firstInputPtsUs < 0 ? trackDurationUs - abs(firstInputPtsUs) : trackDurationUs;
|
||||||
|
|
||||||
@C.TrackType int trackType = MimeTypes.getTrackType(format.sampleMimeType);
|
@C.TrackType int trackType = MimeTypes.getTrackType(format.sampleMimeType);
|
||||||
ByteBuffer stts = stts(sampleDurationsVu);
|
ByteBuffer stts = stts(sampleDurationsVu);
|
||||||
@ -232,7 +235,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
trak(
|
trak(
|
||||||
tkhd(
|
tkhd(
|
||||||
nextTrackId,
|
nextTrackId,
|
||||||
trackDurationUs,
|
presentationTrackDurationUs,
|
||||||
creationTimestampSeconds,
|
creationTimestampSeconds,
|
||||||
modificationTimestampSeconds,
|
modificationTimestampSeconds,
|
||||||
metadataCollector.orientationData.orientation,
|
metadataCollector.orientationData.orientation,
|
||||||
@ -240,7 +243,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
edts(
|
edts(
|
||||||
firstInputPtsUs,
|
firstInputPtsUs,
|
||||||
minInputPtsUs,
|
minInputPtsUs,
|
||||||
trackDurationUs,
|
presentationTrackDurationUs,
|
||||||
MVHD_TIMEBASE,
|
MVHD_TIMEBASE,
|
||||||
track.videoUnitTimebase()),
|
track.videoUnitTimebase()),
|
||||||
mdia(
|
mdia(
|
||||||
@ -254,7 +257,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
minf(mhdBox, dinf(dref(localUrl())), stblBox)));
|
minf(mhdBox, dinf(dref(localUrl())), stblBox)));
|
||||||
|
|
||||||
trakBoxes.add(trakBox);
|
trakBoxes.add(trakBox);
|
||||||
videoDurationUs = max(videoDurationUs, trackDurationUs);
|
videoDurationUs = max(videoDurationUs, presentationTrackDurationUs);
|
||||||
trexBoxes.add(trex(nextTrackId));
|
trexBoxes.add(trex(nextTrackId));
|
||||||
nextTrackId++;
|
nextTrackId++;
|
||||||
}
|
}
|
||||||
@ -853,8 +856,7 @@ import org.checkerframework.checker.nullness.qual.PolyNull;
|
|||||||
elstContent.putInt(1); // Entry count
|
elstContent.putInt(1); // Entry count
|
||||||
elstContent.put(
|
elstContent.put(
|
||||||
elstEntry(
|
elstEntry(
|
||||||
/* editDurationVu= */ vuFromUs(
|
/* editDurationVu= */ vuFromUs(trackDurationUs, mvhdTimescale),
|
||||||
trackDurationUs - abs(firstSamplePtsUs), mvhdTimescale),
|
|
||||||
/* mediaTimeVu= */ vuFromUs(abs(firstSamplePtsUs), trackTimescale),
|
/* mediaTimeVu= */ vuFromUs(abs(firstSamplePtsUs), trackTimescale),
|
||||||
/* mediaRateInt= */ 1,
|
/* mediaRateInt= */ 1,
|
||||||
/* mediaRateFraction= */ 0));
|
/* mediaRateFraction= */ 0));
|
||||||
|
@ -593,6 +593,40 @@ public class BoxesTest {
|
|||||||
assertThat(durationsVu).containsExactly(100, 100, 800, 100, 0);
|
assertThat(durationsVu).containsExactly(100, 100, 800, 100, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
convertPresentationTimestampsToDurationsVu_withNegativeSampleTimestampsAndZero_returnsExpectedDurations() {
|
||||||
|
List<BufferInfo> sampleBufferInfos =
|
||||||
|
createBufferInfoListWithSamplePresentationTimestamps(
|
||||||
|
-1_000L, 0L, 10_000L, 1_000L, 2_000L, 11_000L);
|
||||||
|
|
||||||
|
List<Integer> durationsVu =
|
||||||
|
Boxes.convertPresentationTimestampsToDurationsVu(
|
||||||
|
sampleBufferInfos,
|
||||||
|
VU_TIMEBASE,
|
||||||
|
LAST_SAMPLE_DURATION_BEHAVIOR_SET_TO_ZERO,
|
||||||
|
C.TIME_UNSET);
|
||||||
|
|
||||||
|
assertThat(durationsVu).containsExactly(100, 100, 100, 800, 100, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
convertPresentationTimestampsToDurationsVu_withNegativeSampleTimestamps_returnsExpectedDurations() {
|
||||||
|
List<BufferInfo> sampleBufferInfos =
|
||||||
|
createBufferInfoListWithSamplePresentationTimestamps(
|
||||||
|
-1_000L, 10_000L, 1_000L, 2_000L, 11_000L);
|
||||||
|
|
||||||
|
List<Integer> durationsVu =
|
||||||
|
Boxes.convertPresentationTimestampsToDurationsVu(
|
||||||
|
sampleBufferInfos,
|
||||||
|
VU_TIMEBASE,
|
||||||
|
LAST_SAMPLE_DURATION_BEHAVIOR_SET_TO_ZERO,
|
||||||
|
C.TIME_UNSET);
|
||||||
|
|
||||||
|
assertThat(durationsVu).containsExactly(200, 100, 800, 100, 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void
|
public void
|
||||||
convertPresentationTimestampsToDurationsVu_withLastSampleDurationBehaviorUsingEndOfStreamFlag_returnsExpectedDurations() {
|
convertPresentationTimestampsToDurationsVu_withLastSampleDurationBehaviorUsingEndOfStreamFlag_returnsExpectedDurations() {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
seekMap:
|
seekMap:
|
||||||
isSeekable = true
|
isSeekable = true
|
||||||
duration = 600
|
duration = 500
|
||||||
getPosition(0) = [[timeUs=-100, position=400052], [timeUs=100, position=400108]]
|
getPosition(0) = [[timeUs=-100, position=400052], [timeUs=100, position=400108]]
|
||||||
getPosition(1) = [[timeUs=-100, position=400052], [timeUs=100, position=400108]]
|
getPosition(1) = [[timeUs=-100, position=400052], [timeUs=100, position=400108]]
|
||||||
getPosition(300) = [[timeUs=300, position=400164]]
|
getPosition(250) = [[timeUs=100, position=400108], [timeUs=300, position=400164]]
|
||||||
getPosition(600) = [[timeUs=300, position=400164]]
|
getPosition(500) = [[timeUs=300, position=400164]]
|
||||||
numberOfTracks = 1
|
numberOfTracks = 1
|
||||||
track 0:
|
track 0:
|
||||||
total output bytes = 168
|
total output bytes = 168
|
||||||
sample count = 3
|
sample count = 3
|
||||||
track duration = 600
|
track duration = 500
|
||||||
format 0:
|
format 0:
|
||||||
averageBitrate = 2240000
|
averageBitrate = 2240000
|
||||||
id = 1
|
id = 1
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
edts (44 bytes):
|
edts (44 bytes):
|
||||||
elst (36 bytes):
|
elst (36 bytes):
|
||||||
Data = length 28, hash 68F2D7C9
|
Data = length 28, hash 75FF2BCC
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
seekMap:
|
seekMap:
|
||||||
isSeekable = true
|
isSeekable = true
|
||||||
duration = 3135000
|
duration = 2680000
|
||||||
getPosition(0) = [[timeUs=-455000, position=400052], [timeUs=611666, position=411095]]
|
getPosition(0) = [[timeUs=-455000, position=400052], [timeUs=611666, position=411095]]
|
||||||
getPosition(1) = [[timeUs=-455000, position=400052], [timeUs=611666, position=411095]]
|
getPosition(1) = [[timeUs=-455000, position=400052], [timeUs=611666, position=411095]]
|
||||||
getPosition(1567500) = [[timeUs=611666, position=411095], [timeUs=1680000, position=429829]]
|
getPosition(1340000) = [[timeUs=611666, position=411095], [timeUs=1680000, position=429829]]
|
||||||
getPosition(3135000) = [[timeUs=1680000, position=429829]]
|
getPosition(2680000) = [[timeUs=1680000, position=429829]]
|
||||||
numberOfTracks = 3
|
numberOfTracks = 3
|
||||||
track 0:
|
track 0:
|
||||||
total output bytes = 3208515
|
total output bytes = 3208515
|
||||||
sample count = 85
|
sample count = 85
|
||||||
track duration = 3135000
|
track duration = 2680000
|
||||||
format 0:
|
format 0:
|
||||||
averageBitrate = 8187598
|
averageBitrate = 8187598
|
||||||
id = 1
|
id = 1
|
||||||
@ -376,7 +376,7 @@ track 0:
|
|||||||
track 1:
|
track 1:
|
||||||
total output bytes = 3208515
|
total output bytes = 3208515
|
||||||
sample count = 85
|
sample count = 85
|
||||||
track duration = 3135000
|
track duration = 2680000
|
||||||
format 0:
|
format 0:
|
||||||
averageBitrate = 8187598
|
averageBitrate = 8187598
|
||||||
id = 2
|
id = 2
|
||||||
|
@ -2153,8 +2153,7 @@ public class TransformerEndToEndTest {
|
|||||||
Mp4Extractor mp4Extractor = new Mp4Extractor(new DefaultSubtitleParserFactory());
|
Mp4Extractor mp4Extractor = new Mp4Extractor(new DefaultSubtitleParserFactory());
|
||||||
FakeExtractorOutput fakeExtractorOutput =
|
FakeExtractorOutput fakeExtractorOutput =
|
||||||
TestUtil.extractAllSamplesFromFilePath(mp4Extractor, exportTestResult.filePath);
|
TestUtil.extractAllSamplesFromFilePath(mp4Extractor, exportTestResult.filePath);
|
||||||
// TODO: b/324903070 - The generated output file has incorrect duration.
|
assertThat(fakeExtractorOutput.seekMap.getDurationUs()).isEqualTo(1_562_800);
|
||||||
assertThat(fakeExtractorOutput.seekMap.getDurationUs()).isEqualTo(1_579_600);
|
|
||||||
assertThat(fakeExtractorOutput.numberOfTracks).isEqualTo(1);
|
assertThat(fakeExtractorOutput.numberOfTracks).isEqualTo(1);
|
||||||
FakeTrackOutput audioTrack = fakeExtractorOutput.trackOutputs.get(0);
|
FakeTrackOutput audioTrack = fakeExtractorOutput.trackOutputs.get(0);
|
||||||
int expectedSampleCount = 68;
|
int expectedSampleCount = 68;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user