Ignore all edit lists if one track's edits can't be applied

Issue: #4348

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=204261718
This commit is contained in:
andrewlewis 2018-07-12 01:47:22 -07:00 committed by Oliver Woodman
parent 78d7754f29
commit 44c253058e
4 changed files with 86 additions and 56 deletions

View File

@ -85,6 +85,8 @@
* Add workaround for track index mismatches between tfhd and tkhd boxes in * Add workaround for track index mismatches between tfhd and tkhd boxes in
fragmented MP4 files fragmented MP4 files
([#4083](https://github.com/google/ExoPlayer/issues/4083)). ([#4083](https://github.com/google/ExoPlayer/issues/4083)).
* Ignore all MP4 edit lists if one edit list couldn't be handled
([#4348](https://github.com/google/ExoPlayer/issues/4348)).
* Fix issue when switching track selection from an embedded track to a primary * Fix issue when switching track selection from an embedded track to a primary
track in DASH ([#4477](https://github.com/google/ExoPlayer/issues/4477)). track in DASH ([#4477](https://github.com/google/ExoPlayer/issues/4477)).

View File

@ -43,6 +43,9 @@ import java.util.List;
*/ */
/* package */ final class AtomParsers { /* package */ final class AtomParsers {
/** Thrown if an edit list couldn't be applied. */
public static final class UnhandledEditListException extends ParserException {}
private static final String TAG = "AtomParsers"; private static final String TAG = "AtomParsers";
private static final int TYPE_vide = Util.getIntegerCodeForString("vide"); private static final int TYPE_vide = Util.getIntegerCodeForString("vide");
@ -117,10 +120,12 @@ import java.util.List;
* @param stblAtom stbl (sample table) atom to decode. * @param stblAtom stbl (sample table) atom to decode.
* @param gaplessInfoHolder Holder to populate with gapless playback information. * @param gaplessInfoHolder Holder to populate with gapless playback information.
* @return Sample table described by the stbl atom. * @return Sample table described by the stbl atom.
* @throws ParserException If the resulting sample sequence does not contain a sync sample. * @throws UnhandledEditListException Thrown if the edit list can't be applied.
* @throws ParserException Thrown if the stbl atom can't be parsed.
*/ */
public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAtom, public static TrackSampleTable parseStbl(
GaplessInfoHolder gaplessInfoHolder) throws ParserException { Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder)
throws ParserException {
SampleSizeBox sampleSizeBox; SampleSizeBox sampleSizeBox;
Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz); Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz);
if (stszAtom != null) { if (stszAtom != null) {
@ -136,7 +141,13 @@ import java.util.List;
int sampleCount = sampleSizeBox.getSampleCount(); int sampleCount = sampleSizeBox.getSampleCount();
if (sampleCount == 0) { if (sampleCount == 0) {
return new TrackSampleTable( return new TrackSampleTable(
new long[0], new int[0], 0, new long[0], new int[0], C.TIME_UNSET); track,
/* offsets= */ new long[0],
/* sizes= */ new int[0],
/* maximumSize= */ 0,
/* timestampsUs= */ new long[0],
/* flags= */ new int[0],
/* durationUs= */ C.TIME_UNSET);
} }
// Entries are byte offsets of chunks. // Entries are byte offsets of chunks.
@ -315,7 +326,8 @@ import java.util.List;
// 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, durationUs); return new TrackSampleTable(
track, 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
@ -342,7 +354,8 @@ 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, durationUs); return new TrackSampleTable(
track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
} }
} }
} }
@ -359,7 +372,8 @@ import java.util.List;
} }
durationUs = durationUs =
Util.scaleLargeTimestamp(duration - editStartTime, C.MICROS_PER_SECOND, track.timescale); Util.scaleLargeTimestamp(duration - editStartTime, C.MICROS_PER_SECOND, track.timescale);
return new TrackSampleTable(offsets, sizes, maximumSize, timestamps, flags, durationUs); return new TrackSampleTable(
track, 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.
@ -409,6 +423,11 @@ import java.util.List;
System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count); System.arraycopy(sizes, startIndex, editedSizes, sampleIndex, count);
System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count); System.arraycopy(flags, startIndex, editedFlags, sampleIndex, count);
} }
if (startIndex < endIndex && (editedFlags[sampleIndex] & C.BUFFER_FLAG_KEY_FRAME) == 0) {
// Applying the edit list would require prerolling from a sync sample.
Log.w(TAG, "Ignoring edit list: edit does not start with a sync sample.");
throw new UnhandledEditListException();
}
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 = long timeInSegmentUs =
@ -424,20 +443,8 @@ import java.util.List;
pts += editDuration; pts += editDuration;
} }
long editedDurationUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.timescale); long editedDurationUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.timescale);
boolean hasSyncSample = false;
for (int i = 0; i < editedFlags.length && !hasSyncSample; i++) {
hasSyncSample |= (editedFlags[i] & C.BUFFER_FLAG_KEY_FRAME) != 0;
}
if (!hasSyncSample) {
// We don't support edit lists where the edited sample sequence doesn't contain a sync sample.
// 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, durationUs);
}
return new TrackSampleTable( return new TrackSampleTable(
track,
editedOffsets, editedOffsets,
editedSizes, editedSizes,
editedMaximumSize, editedMaximumSize,

View File

@ -391,25 +391,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
} }
} }
for (int i = 0; i < moov.containerChildren.size(); i++) { boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0;
Atom.ContainerAtom atom = moov.containerChildren.get(i); ArrayList<TrackSampleTable> trackSampleTables;
if (atom.type != Atom.TYPE_trak) { try {
continue; trackSampleTables = getTrackSampleTables(moov, gaplessInfoHolder, ignoreEditLists);
} } catch (AtomParsers.UnhandledEditListException e) {
// Discard gapless info as we aren't able to handle corresponding edits.
Track track = AtomParsers.parseTrak(atom, moov.getLeafAtomOfType(Atom.TYPE_mvhd), gaplessInfoHolder = new GaplessInfoHolder();
C.TIME_UNSET, null, (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, isQuickTime); trackSampleTables =
if (track == null) { getTrackSampleTables(moov, gaplessInfoHolder, /* ignoreEditLists= */ true);
continue; }
}
Atom.ContainerAtom stblAtom = atom.getContainerAtomOfType(Atom.TYPE_mdia)
.getContainerAtomOfType(Atom.TYPE_minf).getContainerAtomOfType(Atom.TYPE_stbl);
TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder);
if (trackSampleTable.sampleCount == 0) {
continue;
}
int trackCount = trackSampleTables.size();
for (int i = 0; i < trackCount; i++) {
TrackSampleTable trackSampleTable = trackSampleTables.get(i);
Track track = trackSampleTable.track;
Mp4Track mp4Track = new Mp4Track(track, trackSampleTable, Mp4Track mp4Track = new Mp4Track(track, trackSampleTable,
extractorOutput.track(i, track.type)); extractorOutput.track(i, track.type));
// Each sample has up to three bytes of overhead for the start code that replaces its length. // Each sample has up to three bytes of overhead for the start code that replaces its length.
@ -445,6 +441,39 @@ public final class Mp4Extractor implements Extractor, SeekMap {
extractorOutput.seekMap(this); extractorOutput.seekMap(this);
} }
private ArrayList<TrackSampleTable> getTrackSampleTables(
ContainerAtom moov, GaplessInfoHolder gaplessInfoHolder, boolean ignoreEditLists)
throws ParserException {
ArrayList<TrackSampleTable> trackSampleTables = new ArrayList<>();
for (int i = 0; i < moov.containerChildren.size(); i++) {
Atom.ContainerAtom atom = moov.containerChildren.get(i);
if (atom.type != Atom.TYPE_trak) {
continue;
}
Track track =
AtomParsers.parseTrak(
atom,
moov.getLeafAtomOfType(Atom.TYPE_mvhd),
/* duration= */ C.TIME_UNSET,
/* drmInitData= */ null,
ignoreEditLists,
isQuickTime);
if (track == null) {
continue;
}
Atom.ContainerAtom stblAtom =
atom.getContainerAtomOfType(Atom.TYPE_mdia)
.getContainerAtomOfType(Atom.TYPE_minf)
.getContainerAtomOfType(Atom.TYPE_stbl);
TrackSampleTable trackSampleTable = AtomParsers.parseStbl(track, stblAtom, gaplessInfoHolder);
if (trackSampleTable.sampleCount == 0) {
continue;
}
trackSampleTables.add(trackSampleTable);
}
return trackSampleTables;
}
/** /**
* Attempts to extract the next sample in the current mdat atom for the specified track. * Attempts to extract the next sample in the current mdat atom for the specified track.
* <p> * <p>

View File

@ -24,29 +24,19 @@ import com.google.android.exoplayer2.util.Util;
*/ */
/* package */ final class TrackSampleTable { /* package */ final class TrackSampleTable {
/** /** The track corresponding to this sample table. */
* Number of samples. public final Track track;
*/ /** Number of samples. */
public final int sampleCount; public final int sampleCount;
/** /** Sample offsets in bytes. */
* Sample offsets in bytes.
*/
public final long[] offsets; public final long[] offsets;
/** /** Sample sizes in bytes. */
* Sample sizes in bytes.
*/
public final int[] sizes; public final int[] sizes;
/** /** Maximum sample size in {@link #sizes}. */
* Maximum sample size in {@link #sizes}.
*/
public final int maximumSize; public final int maximumSize;
/** /** Sample timestamps in microseconds. */
* Sample timestamps in microseconds.
*/
public final long[] timestampsUs; public final long[] timestampsUs;
/** /** 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 * The duration of the track sample table in microseconds, or {@link C#TIME_UNSET} if the sample
@ -55,6 +45,7 @@ import com.google.android.exoplayer2.util.Util;
public final long durationUs; public final long durationUs;
public TrackSampleTable( public TrackSampleTable(
Track track,
long[] offsets, long[] offsets,
int[] sizes, int[] sizes,
int maximumSize, int maximumSize,
@ -65,6 +56,7 @@ import com.google.android.exoplayer2.util.Util;
Assertions.checkArgument(offsets.length == timestampsUs.length); Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length); Assertions.checkArgument(flags.length == timestampsUs.length);
this.track = track;
this.offsets = offsets; this.offsets = offsets;
this.sizes = sizes; this.sizes = sizes;
this.maximumSize = maximumSize; this.maximumSize = maximumSize;