Skip to content

Commit

Permalink
Ignore all edit lists if one track's edits can't be applied
Browse files Browse the repository at this point in the history
Issue: #4348

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=204261718
  • Loading branch information
andrewlewis authored and ojw28 committed Jul 23, 2018
1 parent 2f6273c commit 0a46e74
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 56 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
* Add workaround for track index mismatches between tfhd and tkhd boxes in
fragmented MP4 files
([#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
track in DASH ([#4477](https://github.com/google/ExoPlayer/issues/4477)).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
*/
/* 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 int TYPE_vide = Util.getIntegerCodeForString("vide");
Expand Down Expand Up @@ -117,10 +120,12 @@ public static Track parseTrak(Atom.ContainerAtom trak, Atom.LeafAtom mvhd, long
* @param stblAtom stbl (sample table) atom to decode.
* @param gaplessInfoHolder Holder to populate with gapless playback information.
* @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,
GaplessInfoHolder gaplessInfoHolder) throws ParserException {
public static TrackSampleTable parseStbl(
Track track, Atom.ContainerAtom stblAtom, GaplessInfoHolder gaplessInfoHolder)
throws ParserException {
SampleSizeBox sampleSizeBox;
Atom.LeafAtom stszAtom = stblAtom.getLeafAtomOfType(Atom.TYPE_stsz);
if (stszAtom != null) {
Expand All @@ -136,7 +141,13 @@ public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAto
int sampleCount = sampleSizeBox.getSampleCount();
if (sampleCount == 0) {
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.
Expand Down Expand Up @@ -315,7 +326,8 @@ public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAto
// 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, 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
Expand All @@ -342,7 +354,8 @@ public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAto
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, durationUs);
return new TrackSampleTable(
track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
}
}
}
Expand All @@ -359,7 +372,8 @@ public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAto
}
durationUs =
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.
Expand Down Expand Up @@ -409,6 +423,11 @@ public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAto
System.arraycopy(sizes, startIndex, editedSizes, 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++) {
long ptsUs = Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, track.movieTimescale);
long timeInSegmentUs =
Expand All @@ -424,20 +443,8 @@ public static TrackSampleTable parseStbl(Track track, Atom.ContainerAtom stblAto
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++) {
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(
track,
editedOffsets,
editedSizes,
editedMaximumSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,25 +391,21 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
}
}

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),
C.TIME_UNSET, null, (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0, 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;
}
boolean ignoreEditLists = (flags & FLAG_WORKAROUND_IGNORE_EDIT_LISTS) != 0;
ArrayList<TrackSampleTable> trackSampleTables;
try {
trackSampleTables = getTrackSampleTables(moov, gaplessInfoHolder, ignoreEditLists);
} catch (AtomParsers.UnhandledEditListException e) {
// Discard gapless info as we aren't able to handle corresponding edits.
gaplessInfoHolder = new GaplessInfoHolder();
trackSampleTables =
getTrackSampleTables(moov, gaplessInfoHolder, /* ignoreEditLists= */ true);
}

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,
extractorOutput.track(i, track.type));
// Each sample has up to three bytes of overhead for the start code that replaces its length.
Expand Down Expand Up @@ -445,6 +441,39 @@ private void processMoovAtom(ContainerAtom moov) throws ParserException {
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.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,19 @@
*/
/* package */ final class TrackSampleTable {

/**
* Number of samples.
*/
/** The track corresponding to this sample table. */
public final Track track;
/** Number of samples. */
public final int sampleCount;
/**
* Sample offsets in bytes.
*/
/** Sample offsets in bytes. */
public final long[] offsets;
/**
* Sample sizes in bytes.
*/
/** Sample sizes in bytes. */
public final int[] sizes;
/**
* Maximum sample size in {@link #sizes}.
*/
/** Maximum sample size in {@link #sizes}. */
public final int maximumSize;
/**
* Sample timestamps in microseconds.
*/
/** Sample timestamps in microseconds. */
public final long[] timestampsUs;
/**
* Sample flags.
*/
/** Sample flags. */
public final int[] flags;
/**
* The duration of the track sample table in microseconds, or {@link C#TIME_UNSET} if the sample
Expand All @@ -55,6 +45,7 @@
public final long durationUs;

public TrackSampleTable(
Track track,
long[] offsets,
int[] sizes,
int maximumSize,
Expand All @@ -65,6 +56,7 @@ public TrackSampleTable(
Assertions.checkArgument(offsets.length == timestampsUs.length);
Assertions.checkArgument(flags.length == timestampsUs.length);

this.track = track;
this.offsets = offsets;
this.sizes = sizes;
this.maximumSize = maximumSize;
Expand Down

0 comments on commit 0a46e74

Please sign in to comment.