diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 54537ce844..b717fd0830 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -43,6 +43,8 @@ ((#3845)[https://github.com/google/ExoPlayer/issues/3845]). * Handle non-empty end-of-stream buffers, to fix gapless playback of streams with encoder padding when the decoder returns a non-empty final buffer. + * Allow trimming more than one sample when applying an elst audio edit via + gapless playback info. * Caching: * Add release method to Cache interface. * Prevent multiple instances of SimpleCache in the same folder. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java index 30358ff7c7..a6e2524f0b 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/extractor/mp4/AtomParsers.java @@ -53,6 +53,12 @@ import java.util.List; private static final int TYPE_clcp = Util.getIntegerCodeForString("clcp"); private static final int TYPE_meta = Util.getIntegerCodeForString("meta"); + /** + * The threshold number of samples to trim from the start/end of an audio track when applying an + * edit below which gapless info can be used (rather than removing samples from the sample table). + */ + private static final int MAX_GAPLESS_TRIM_SIZE_SAMPLES = 3; + /** * Parses a trak atom (defined in 14496-12). * @@ -311,22 +317,18 @@ import java.util.List; // See the BMFF spec (ISO 14496-12) subsection 8.6.6. Edit lists that require prerolling from a // sync sample after reordering are not supported. Partial audio sample truncation is only - // supported in edit lists with one edit that removes less than one sample from the start/end of - // the track, for gapless audio playback. This implementation handles simple discarding/delaying - // of samples. The extractor may place further restrictions on what edited streams are playable. + // supported in edit lists with one edit that removes less than MAX_GAPLESS_TRIM_SIZE_SAMPLES + // samples from the start/end of the track. This implementation handles simple + // discarding/delaying of samples. The extractor may place further restrictions on what edited + // streams are playable. - if (track.editListDurations.length == 1 && track.type == C.TRACK_TYPE_AUDIO + if (track.editListDurations.length == 1 + && track.type == C.TRACK_TYPE_AUDIO && timestamps.length >= 2) { - // Handle the edit by setting gapless playback metadata, if possible. This implementation - // assumes that only one "roll" sample is needed, which is the case for AAC, so the start/end - // points of the edit must lie within the first/last samples respectively. long editStartTime = track.editListMediaTimes[0]; long editEndTime = editStartTime + Util.scaleLargeTimestamp(track.editListDurations[0], track.timescale, track.movieTimescale); - if (timestamps[0] <= editStartTime - && editStartTime < timestamps[1] - && timestamps[timestamps.length - 1] < editEndTime - && editEndTime <= duration) { + if (canApplyEditWithGaplessInfo(timestamps, duration, editStartTime, editEndTime)) { long paddingTimeUnits = duration - editEndTime; long encoderDelay = Util.scaleLargeTimestamp(editStartTime - timestamps[0], track.format.sampleRate, track.timescale); @@ -1180,6 +1182,19 @@ import java.util.List; return size; } + /** Returns whether it's possible to apply the specified edit using gapless playback info. */ + private static boolean canApplyEditWithGaplessInfo( + long[] timestamps, long duration, long editStartTime, long editEndTime) { + int lastIndex = timestamps.length - 1; + int latestDelayIndex = Util.constrainValue(MAX_GAPLESS_TRIM_SIZE_SAMPLES, 0, lastIndex); + int earliestPaddingIndex = + Util.constrainValue(timestamps.length - MAX_GAPLESS_TRIM_SIZE_SAMPLES, 0, lastIndex); + return timestamps[0] <= editStartTime + && editStartTime < timestamps[latestDelayIndex] + && timestamps[earliestPaddingIndex] < editEndTime + && editEndTime <= duration; + } + private AtomParsers() { // Prevent instantiation. }