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
|
* 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)).
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user