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
|
`Timeline.Window.windowStartTimeMs` field
|
||||||
([#3865](https://github.com/google/ExoPlayer/issues/3865),
|
([#3865](https://github.com/google/ExoPlayer/issues/3865),
|
||||||
[#3888](https://github.com/google/ExoPlayer/issues/3888)).
|
[#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 ###
|
### 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_sbtl = Util.getIntegerCodeForString("sbtl");
|
||||||
private static final int TYPE_subt = Util.getIntegerCodeForString("subt");
|
private static final int TYPE_subt = Util.getIntegerCodeForString("subt");
|
||||||
private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp");
|
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");
|
private static final int TYPE_meta = Util.getIntegerCodeForString("meta");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,7 +127,8 @@ import java.util.List;
|
|||||||
|
|
||||||
int sampleCount = sampleSizeBox.getSampleCount();
|
int sampleCount = sampleSizeBox.getSampleCount();
|
||||||
if (sampleCount == 0) {
|
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.
|
// Entries are byte offsets of chunks.
|
||||||
@ -193,6 +193,7 @@ import java.util.List;
|
|||||||
long[] timestamps;
|
long[] timestamps;
|
||||||
int[] flags;
|
int[] flags;
|
||||||
long timestampTimeUnits = 0;
|
long timestampTimeUnits = 0;
|
||||||
|
long duration;
|
||||||
|
|
||||||
if (!isRechunkable) {
|
if (!isRechunkable) {
|
||||||
offsets = new long[sampleCount];
|
offsets = new long[sampleCount];
|
||||||
@ -260,6 +261,7 @@ import java.util.List;
|
|||||||
offset += sizes[i];
|
offset += sizes[i];
|
||||||
remainingSamplesInChunk--;
|
remainingSamplesInChunk--;
|
||||||
}
|
}
|
||||||
|
duration = timestampTimeUnits + timestampOffset;
|
||||||
|
|
||||||
Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0);
|
Assertions.checkArgument(remainingSamplesAtTimestampOffset == 0);
|
||||||
// Remove trailing ctts entries with 0-valued sample counts.
|
// Remove trailing ctts entries with 0-valued sample counts.
|
||||||
@ -294,13 +296,15 @@ import java.util.List;
|
|||||||
maximumSize = rechunkedResults.maximumSize;
|
maximumSize = rechunkedResults.maximumSize;
|
||||||
timestamps = rechunkedResults.timestamps;
|
timestamps = rechunkedResults.timestamps;
|
||||||
flags = rechunkedResults.flags;
|
flags = rechunkedResults.flags;
|
||||||
|
duration = rechunkedResults.duration;
|
||||||
}
|
}
|
||||||
|
long durationUs = Util.scaleLargeTimestamp(duration, C.MICROS_PER_SECOND, track.timescale);
|
||||||
|
|
||||||
if (track.editListDurations == null || gaplessInfoHolder.hasGaplessInfo()) {
|
if (track.editListDurations == null || gaplessInfoHolder.hasGaplessInfo()) {
|
||||||
// There is no edit list, or we are ignoring it as we already have gapless metadata to apply.
|
// 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.
|
// This implementation does not support applying both gapless metadata and an edit list.
|
||||||
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
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
|
// 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 editStartTime = track.editListMediaTimes[0];
|
||||||
long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0],
|
long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0],
|
||||||
track.timescale, track.movieTimescale);
|
track.timescale, track.movieTimescale);
|
||||||
long lastSampleEndTime = timestampTimeUnits;
|
if (timestamps[0] <= editStartTime
|
||||||
if (timestamps[0] <= editStartTime && editStartTime < timestamps[1]
|
&& editStartTime < timestamps[1]
|
||||||
&& timestamps[timestamps.length - 1] < editEndTime && editEndTime <= lastSampleEndTime) {
|
&& timestamps[timestamps.length - 1] < editEndTime
|
||||||
long paddingTimeUnits = lastSampleEndTime - editEndTime;
|
&& editEndTime <= duration) {
|
||||||
|
long paddingTimeUnits = duration - editEndTime;
|
||||||
long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0],
|
long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0],
|
||||||
track.format.sampleRate, track.timescale);
|
track.format.sampleRate, track.timescale);
|
||||||
long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits,
|
long encoderPadding = Util.scaleLargeTimestamp(paddingTimeUnits,
|
||||||
@ -330,7 +335,7 @@ import java.util.List;
|
|||||||
gaplessInfoHolder.encoderDelay = (int) encoderDelay;
|
gaplessInfoHolder.encoderDelay = (int) encoderDelay;
|
||||||
gaplessInfoHolder.encoderPadding = (int) encoderPadding;
|
gaplessInfoHolder.encoderPadding = (int) encoderPadding;
|
||||||
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
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
|
// 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
|
// unfragmented files open to interpretation. We handle this as a special case and include all
|
||||||
// samples in the edit.
|
// samples in the edit.
|
||||||
|
long editStartTime = track.editListMediaTimes[0];
|
||||||
for (int i = 0; i < timestamps.length; i++) {
|
for (int i = 0; i < timestamps.length; i++) {
|
||||||
timestamps[i] = Util.scaleLargeTimestamp(timestamps[i] - track.editListMediaTimes[0],
|
timestamps[i] =
|
||||||
C.MICROS_PER_SECOND, track.timescale);
|
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.
|
// Omit any sample at the end point of an edit for audio tracks.
|
||||||
@ -354,13 +363,15 @@ import java.util.List;
|
|||||||
int nextSampleIndex = 0;
|
int nextSampleIndex = 0;
|
||||||
boolean copyMetadata = false;
|
boolean copyMetadata = false;
|
||||||
for (int i = 0; i < track.editListDurations.length; i++) {
|
for (int i = 0; i < track.editListDurations.length; i++) {
|
||||||
long mediaTime = track.editListMediaTimes[i];
|
long editMediaTime = track.editListMediaTimes[i];
|
||||||
if (mediaTime != -1) {
|
if (editMediaTime != -1) {
|
||||||
long duration = Util.scaleLargeTimestamp(track.editListDurations[i], track.timescale,
|
long editDuration =
|
||||||
track.movieTimescale);
|
Util.scaleLargeTimestamp(
|
||||||
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
|
track.editListDurations[i], track.timescale, track.movieTimescale);
|
||||||
int endIndex = Util.binarySearchCeil(timestamps, mediaTime + duration, omitClippedSample,
|
int startIndex = Util.binarySearchCeil(timestamps, editMediaTime, true, true);
|
||||||
false);
|
int endIndex =
|
||||||
|
Util.binarySearchCeil(
|
||||||
|
timestamps, editMediaTime + editDuration, omitClippedSample, false);
|
||||||
editedSampleCount += endIndex - startIndex;
|
editedSampleCount += endIndex - startIndex;
|
||||||
copyMetadata |= nextSampleIndex != startIndex;
|
copyMetadata |= nextSampleIndex != startIndex;
|
||||||
nextSampleIndex = endIndex;
|
nextSampleIndex = endIndex;
|
||||||
@ -377,12 +388,13 @@ import java.util.List;
|
|||||||
long pts = 0;
|
long pts = 0;
|
||||||
int sampleIndex = 0;
|
int sampleIndex = 0;
|
||||||
for (int i = 0; i < track.editListDurations.length; i++) {
|
for (int i = 0; i < track.editListDurations.length; i++) {
|
||||||
long mediaTime = track.editListMediaTimes[i];
|
long editMediaTime = track.editListMediaTimes[i];
|
||||||
long duration = track.editListDurations[i];
|
long editDuration = track.editListDurations[i];
|
||||||
if (mediaTime != -1) {
|
if (editMediaTime != -1) {
|
||||||
long endMediaTime = mediaTime + Util.scaleLargeTimestamp(duration, track.timescale,
|
long endMediaTime =
|
||||||
track.movieTimescale);
|
editMediaTime
|
||||||
int startIndex = Util.binarySearchCeil(timestamps, mediaTime, true, true);
|
+ Util.scaleLargeTimestamp(editDuration, track.timescale, track.movieTimescale);
|
||||||
|
int startIndex = Util.binarySearchCeil(timestamps, editMediaTime, true, true);
|
||||||
int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, omitClippedSample, false);
|
int endIndex = Util.binarySearchCeil(timestamps, endMediaTime, omitClippedSample, false);
|
||||||
if (copyMetadata) {
|
if (copyMetadata) {
|
||||||
int count = endIndex - startIndex;
|
int count = endIndex - startIndex;
|
||||||
@ -392,8 +404,9 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
for (int j = startIndex; j < endIndex; j++) {
|
for (int j = startIndex; j < endIndex; j++) {
|
||||||
long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
|
long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
|
||||||
long timeInSegmentUs = Util.scaleLargeTimestamp(timestamps[j] - mediaTime,
|
long timeInSegmentUs =
|
||||||
C.MICROS_PER_SECOND, track.timescale);
|
Util.scaleLargeTimestamp(
|
||||||
|
timestamps[j] - editMediaTime, C.MICROS_PER_SECOND, track.timescale);
|
||||||
editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs;
|
editedTimestamps[sampleIndex] = ptsUs + timeInSegmentUs;
|
||||||
if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) {
|
if (copyMetadata && editedSizes[sampleIndex] > editedMaximumSize) {
|
||||||
editedMaximumSize = sizes[j];
|
editedMaximumSize = sizes[j];
|
||||||
@ -401,8 +414,9 @@ import java.util.List;
|
|||||||
sampleIndex++;
|
sampleIndex++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pts += duration;
|
pts += editDuration;
|
||||||
}
|
}
|
||||||
|
long editedDurationUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.timescale);
|
||||||
|
|
||||||
boolean hasSyncSample = false;
|
boolean hasSyncSample = false;
|
||||||
for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) {
|
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.
|
// 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.");
|
Log.w(TAG, "Ignoring edit list: Edited sample sequence does not contain a sync sample.");
|
||||||
Util.scaleLargeTimestampsInPlace(timestamps, C.MICROS_PER_SECOND, track.timescale);
|
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,
|
return new TrackSampleTable(
|
||||||
editedFlags);
|
editedOffsets,
|
||||||
|
editedSizes,
|
||||||
|
editedMaximumSize,
|
||||||
|
editedTimestamps,
|
||||||
|
editedFlags,
|
||||||
|
editedDurationUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,13 +33,21 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
public final int maximumSize;
|
public final int maximumSize;
|
||||||
public final long[] timestamps;
|
public final long[] timestamps;
|
||||||
public final int[] flags;
|
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.offsets = offsets;
|
||||||
this.sizes = sizes;
|
this.sizes = sizes;
|
||||||
this.maximumSize = maximumSize;
|
this.maximumSize = maximumSize;
|
||||||
this.timestamps = timestamps;
|
this.timestamps = timestamps;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
|
this.duration = duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -95,8 +103,9 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
newSampleIndex++;
|
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);
|
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) {
|
if (track.type == C.TRACK_TYPE_VIDEO && firstVideoTrackIndex == C.INDEX_UNSET) {
|
||||||
firstVideoTrackIndex = tracks.size();
|
firstVideoTrackIndex = tracks.size();
|
||||||
}
|
}
|
||||||
|
@ -48,9 +48,19 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
* Sample flags.
|
* Sample flags.
|
||||||
*/
|
*/
|
||||||
public final int[] 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,
|
public TrackSampleTable(
|
||||||
int[] flags) {
|
long[] offsets,
|
||||||
|
int[] sizes,
|
||||||
|
int maximumSize,
|
||||||
|
long[] timestampsUs,
|
||||||
|
int[] flags,
|
||||||
|
long durationUs) {
|
||||||
Assertions.checkArgument(sizes.length == timestampsUs.length);
|
Assertions.checkArgument(sizes.length == timestampsUs.length);
|
||||||
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
||||||
Assertions.checkArgument(flags.length == timestampsUs.length);
|
Assertions.checkArgument(flags.length == timestampsUs.length);
|
||||||
@ -60,6 +70,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
this.maximumSize = maximumSize;
|
this.maximumSize = maximumSize;
|
||||||
this.timestampsUs = timestampsUs;
|
this.timestampsUs = timestampsUs;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
|
this.durationUs = durationUs;
|
||||||
sampleCount = offsets.length;
|
sampleCount = offsets.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user