Derive duration from sample table if missing from track header
Issue: #3926 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=188020756
This commit is contained in:
parent
110d6e4fdf
commit
b10f033229
@ -9,6 +9,8 @@
|
||||
`Timeline.Window.windowStartTimeMs` field
|
||||
([#3865](https://github.com/google/ExoPlayer/issues/3865),
|
||||
[#3888](https://github.com/google/ExoPlayer/issues/3888)).
|
||||
* Enable seeking in MP4 streams where duration is set incorrectly in the track
|
||||
header ([#3926](https://github.com/google/ExoPlayer/issues/3926)).
|
||||
|
||||
### 2.7.0 ###
|
||||
|
||||
|
@ -49,7 +49,6 @@ import java.util.List;
|
||||
private static final int TYPE_sbtl = Util.getIntegerCodeForString("sbtl");
|
||||
private static final int TYPE_subt = Util.getIntegerCodeForString("subt");
|
||||
private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp");
|
||||
private static final int TYPE_cenc = Util.getIntegerCodeForString("cenc");
|
||||
private static final int TYPE_meta = Util.getIntegerCodeForString("meta");
|
||||
|
||||
/**
|
||||
@ -128,7 +127,8 @@ import java.util.List;
|
||||
|
||||
int sampleCount = sampleSizeBox.getSampleCount();
|
||||
if (sampleCount == 0) {
|
||||
return new TrackSampleTable(new long[0], new int[0], 0, new long[0], new int[0]);
|
||||
return new TrackSampleTable(
|
||||
new long[0], new int[0], 0, new long[0], new int[0], C.TIME_UNSET);
|
||||
}
|
||||
|
||||
// Entries are byte offsets of chunks.
|
||||
@ -193,6 +193,7 @@ import java.util.List;
|
||||
long[] timestamps;
|
||||
int[] flags;
|
||||
long timestampTimeUnits = 0;
|
||||
long duration;
|
||||
|
||||
if (!isRechunkable) {
|
||||
offsets = new long[sampleCount];
|
||||
@ -260,6 +261,7 @@ import java.util.List;
|
||||
offset += sizes[i];
|
||||
remainingSamplesInChunk--;
|
||||
}
|
||||
duration = timestampTimeUnits + timestampOffset;
|
||||
|
||||
Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0);
|
||||
// Remove trailing ctts entries with 0-valued sample counts.
|
||||
@ -294,13 +296,15 @@ import java.util.List;
|
||||
maximumSize = rechunkedResults.maximumSize;
|
||||
timestamps = rechunkedResults.timestamps;
|
||||
flags = rechunkedResults.flags;
|
||||
duration = rechunkedResults.duration;
|
||||
}
|
||||
long durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, track.timescale);
|
||||
|
||||
if (track.editListDurations == null || gaplessInfoHolder.hasGaplessInfo()) {
|
||||
// There is no edit list, or we are ignoring it as we already have gapless metadata to apply.
|
||||
// This implementation does not support applying both gapless metadata and an edit list.
|
||||
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs);
|
||||
}
|
||||
|
||||
// See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a
|
||||
@ -317,10 +321,11 @@ import java.util.List;
|
||||
long editStartTime = track.editListMediaTimes[0];
|
||||
long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0],
|
||||
track.timescale, track.movieTimescale);
|
||||
long lastSampleEndTime = timestampTimeUnits;
|
||||
if (timestamps[0] <= editStartTime && editStartTime < timestamps[1]
|
||||
&& timestamps[timestamps.length - 1] < editEndTime && editEndTime <= lastSampleEndTime) {
|
||||
long paddingTimeUnits = lastSampleEndTime - editEndTime;
|
||||
if (timestamps[0] <= editStartTime
|
||||
&& editStartTime < timestamps[1]
|
||||
&& timestamps[timestamps.length - 1] < editEndTime
|
||||
&& editEndTime <= duration) {
|
||||
long paddingTimeUnits = duration - editEndTime;
|
||||
long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0],
|
||||
track.format.sampleRate, track.timescale);
|
||||
long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits,
|
||||
@ -330,7 +335,7 @@ import java.util.List;
|
||||
gaplessInfoHolder.encoderDelay = (int) encoderDelay;
|
||||
gaplessInfoHolder.encoderPadding = (int) encoderPadding;
|
||||
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -339,11 +344,15 @@ import java.util.List;
|
||||
// The current version of the spec leaves handling of an edit with zero segment_duration in
|
||||
// unfragmented files open to interpretation. We handle this as a special case and include all
|
||||
// samples in the edit.
|
||||
long editStartTime = track.editListMediaTimes[0];
|
||||
for (int i = 0; i < timestamps.length; i++) {
|
||||
timestamps[i] = Util.scaleLargeTimestamp(timestamps[i] - track.editListMediaTimes[0],
|
||||
C.MICROS_PER_SECOND, track.timescale);
|
||||
timestamps[i] =
|
||||
Util.scaleLargeTimestamp(
|
||||
timestamps[i] - editStartTime, C.MICROS_PER_SECOND, track.timescale);
|
||||
}
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
|
||||
durationUs =
|
||||
Util.scaleLargeTimestamp(duration - editStartTime, C.MICROS_PER_SECOND, track.timescale);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs);
|
||||
}
|
||||
|
||||
// Omit any sample at the end point of an edit for audio tracks.
|
||||
@ -354,13 +363,15 @@ import java.util.List;
|
||||
int nextSampleIndex = 0;
|
||||
boolean copyMetadata = false;
|
||||
for (int i = 0; i < track.editListDurations.length; i++) {
|
||||
long mediaTime = track.editListMediaTimes[i];
|
||||
if (mediaTime != -1) {
|
||||
long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale,
|
||||
track.movieTimescale);
|
||||
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
|
||||
int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, omitClippedSample,
|
||||
false);
|
||||
long editMediaTime = track.editListMediaTimes[i];
|
||||
if (editMediaTime != -1) {
|
||||
long editDuration =
|
||||
Util.scaleLargeTimestamp(
|
||||
track.editListDurations[i], track.timescale, track.movieTimescale);
|
||||
int startIndex = Util.binarySearchCeil(timestamps, editMediaTime, true, true);
|
||||
int endIndex =
|
||||
Util.binarySearchCeil(
|
||||
timestamps, editMediaTime + editDuration, omitClippedSample, false);
|
||||
editedSampleCount += endIndex - startIndex;
|
||||
copyMetadata |= nextSampleIndex != startIndex;
|
||||
nextSampleIndex = endIndex;
|
||||
@ -377,12 +388,13 @@ import java.util.List;
|
||||
long pts = 0;
|
||||
int sampleIndex = 0;
|
||||
for (int i = 0; i < track.editListDurations.length; i++) {
|
||||
long mediaTime = track.editListMediaTimes[i];
|
||||
long duration = track.editListDurations[i];
|
||||
if (mediaTime != -1) {
|
||||
long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale,
|
||||
track.movieTimescale);
|
||||
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
|
||||
long editMediaTime = track.editListMediaTimes[i];
|
||||
long editDuration = track.editListDurations[i];
|
||||
if (editMediaTime != -1) {
|
||||
long endMediaTime =
|
||||
editMediaTime
|
||||
+ Util.scaleLargeTimestamp(editDuration, track.timescale, track.movieTimescale);
|
||||
int startIndex = Util.binarySearchCeil(timestamps, editMediaTime, true, true);
|
||||
int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, omitClippedSample, false);
|
||||
if (copyMetadata) {
|
||||
int count = endIndex - startIndex;
|
||||
@ -392,8 +404,9 @@ import java.util.List;
|
||||
}
|
||||
for (int j = startIndex; j < endIndex; j++) {
|
||||
long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
|
||||
long timeInSegmentUs = Util.scaleLargeTimestamp(timestamps[j] - mediaTime,
|
||||
C.MICROS_PER_SECOND, track.timescale);
|
||||
long timeInSegmentUs =
|
||||
Util.scaleLargeTimestamp(
|
||||
timestamps[j] - editMediaTime, C.MICROS_PER_SECOND, track.timescale);
|
||||
editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs;
|
||||
if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) {
|
||||
editedMaximumSize = sizes[j];
|
||||
@ -401,8 +414,9 @@ import java.util.List;
|
||||
sampleIndex++;
|
||||
}
|
||||
}
|
||||
pts += duration;
|
||||
pts += editDuration;
|
||||
}
|
||||
long editedDurationUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.timescale);
|
||||
|
||||
boolean hasSyncSample = false;
|
||||
for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) {
|
||||
@ -413,11 +427,16 @@ import java.util.List;
|
||||
// Such edit lists are often (although not always) broken, so we ignore it and continue.
|
||||
Log.w(TAG, "Ignoring edit list: Edited sample sequence does not contain a sync sample.");
|
||||
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags);
|
||||
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs);
|
||||
}
|
||||
|
||||
return new TrackSampleTable(editedOffsets, editedSizes, editedMaximumSize, editedTimestamps,
|
||||
editedFlags);
|
||||
return new TrackSampleTable(
|
||||
editedOffsets,
|
||||
editedSizes,
|
||||
editedMaximumSize,
|
||||
editedTimestamps,
|
||||
editedFlags,
|
||||
editedDurationUs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,13 +33,21 @@ import com.google.android.exoplayer2.util.Util;
|
||||
public final int maximumSize;
|
||||
public final long[] timestamps;
|
||||
public final int[] flags;
|
||||
public final long duration;
|
||||
|
||||
private Results(long[] offsets, int[] sizes, int maximumSize, long[] timestamps, int[] flags) {
|
||||
private Results(
|
||||
long[] offsets,
|
||||
int[] sizes,
|
||||
int maximumSize,
|
||||
long[] timestamps,
|
||||
int[] flags,
|
||||
long duration) {
|
||||
this.offsets = offsets;
|
||||
this.sizes = sizes;
|
||||
this.maximumSize = maximumSize;
|
||||
this.timestamps = timestamps;
|
||||
this.flags = flags;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
}
|
||||
@ -95,8 +103,9 @@ import com.google.android.exoplayer2.util.Util;
|
||||
newSampleIndex++;
|
||||
}
|
||||
}
|
||||
long duration = timestampDeltaInTimeUnits * originalSampleIndex;
|
||||
|
||||
return new Results(offsets, sizes, maximumSize, timestamps, flags);
|
||||
return new Results(offsets, sizes, maximumSize, timestamps, flags, duration);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -427,7 +427,10 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
}
|
||||
mp4Track.trackOutput.format(format);
|
||||
|
||||
durationUs = Math.max(durationUs, track.durationUs);
|
||||
durationUs =
|
||||
Math.max(
|
||||
durationUs,
|
||||
track.durationUs != C.TIME_UNSET ? track.durationUs : trackSampleTable.durationUs);
|
||||
if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) {
|
||||
firstVideoTrackIndex = tracks.size();
|
||||
}
|
||||
|
@ -48,9 +48,19 @@ import com.google.android.exoplayer2.util.Util;
|
||||
* Sample flags.
|
||||
*/
|
||||
public final int[] flags;
|
||||
/**
|
||||
* The duration of the track sample table in microseconds, or {@link C#TIME_UNSET} if the sample
|
||||
* table is empty.
|
||||
*/
|
||||
public final long durationUs;
|
||||
|
||||
public TrackSampleTable(long[] offsets, int[] sizes, int maximumSize, long[] timestampsUs,
|
||||
int[] flags) {
|
||||
public TrackSampleTable(
|
||||
long[] offsets,
|
||||
int[] sizes,
|
||||
int maximumSize,
|
||||
long[] timestampsUs,
|
||||
int[] flags,
|
||||
long durationUs) {
|
||||
Assertions.checkArgument(sizes.length == timestampsUs.length);
|
||||
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
||||
Assertions.checkArgument(flags.length == timestampsUs.length);
|
||||
@ -60,6 +70,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||
this.maximumSize = maximumSize;
|
||||
this.timestampsUs = timestampsUs;
|
||||
this.flags = flags;
|
||||
this.durationUs = durationUs;
|
||||
sampleCount = offsets.length;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user