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:
parent
78d7754f29
commit
44c253058e
@ -85,6 +85,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)).
|
||||
|
||||
|
@ -43,6 +43,9 @@ import java.util.List;
|
||||
*/
|
||||
/* 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");
|
||||
@ -117,10 +120,12 @@ import java.util.List;
|
||||
* @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) {
|
||||
@ -136,7 +141,13 @@ 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], 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.
|
||||
@ -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.
|
||||
// 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
|
||||
@ -342,7 +354,8 @@ 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, durationUs);
|
||||
return new TrackSampleTable(
|
||||
track, offsets, sizes, maximumSize, timestamps, flags, durationUs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -359,7 +372,8 @@ import java.util.List;
|
||||
}
|
||||
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.
|
||||
@ -409,6 +423,11 @@ import java.util.List;
|
||||
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 =
|
||||
@ -424,20 +443,8 @@ import java.util.List;
|
||||
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,
|
||||
|
@ -391,25 +391,21 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@ -445,6 +441,39 @@ public final class Mp4Extractor implements Extractor, SeekMap {
|
||||
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>
|
||||
|
@ -24,29 +24,19 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*/
|
||||
/* 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
|
||||
@ -55,6 +45,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||
public final long durationUs;
|
||||
|
||||
public TrackSampleTable(
|
||||
Track track,
|
||||
long[] offsets,
|
||||
int[] sizes,
|
||||
int maximumSize,
|
||||
@ -65,6 +56,7 @@ import com.google.android.exoplayer2.util.Util;
|
||||
Assertions.checkArgument(offsets.length == timestampsUs.length);
|
||||
Assertions.checkArgument(flags.length == timestampsUs.length);
|
||||
|
||||
this.track = track;
|
||||
this.offsets = offsets;
|
||||
this.sizes = sizes;
|
||||
this.maximumSize = maximumSize;
|
||||
|
Loading…
x
Reference in New Issue
Block a user