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:
olly 2018-03-06 07:37:46 -08:00 committed by Oliver Woodman
parent 110d6e4fdf
commit b10f033229
5 changed files with 79 additions and 35 deletions

View File

@ -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 ###

View File

@ -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);
}
/**

View File

@ -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);
}
}

View File

@ -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();
}

View File

@ -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;
}