diff --git a/RELEASENOTES.md b/RELEASENOTES.md index f4831003c2..8c33796606 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -17,6 +17,13 @@ count constraints as they only apply for playback. * Track Selection: * Extractors: + * MPEG-TS: Roll forward the change ensuring the last frame is rendered by + passing the last access unit of a stream to the sample queue + ([#7909](https://github.com/google/ExoPlayer/issues/7909)). + Incorporating fixes to resolve the issues that emerged in I-frame only + HLS streams([#1150](https://github.com/google/ExoPlayer/issues/1150)) + and H.262 HLS streams + ([#1126](https://github.com/google/ExoPlayer/issues/1126)). * Audio: * Video: * Text: diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/ElementaryStreamReader.java b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/ElementaryStreamReader.java index 6d59a73006..a6decc12a2 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/ElementaryStreamReader.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/ElementaryStreamReader.java @@ -31,7 +31,7 @@ import androidx.media3.extractor.TrackOutput; *
  • {@link #seek()} (optional, to reset the state) *
  • {@link #packetStarted(long, int)} (to signal the start of a new packet) *
  • {@link #consume(ParsableByteArray)} (zero or more times, to provide packet data) - *
  • {@link #packetFinished()} (to signal the end of the current packet) + *
  • {@link #packetFinished(boolean)} (to signal the end of the current packet) *
  • Repeat steps 3-5 for subsequent packets * */ diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H264Reader.java b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H264Reader.java index 8a7fbd609e..3990ae0486 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H264Reader.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H264Reader.java @@ -170,7 +170,6 @@ public final class H264Reader implements ElementaryStreamReader { public void packetFinished(boolean isEndOfInput) { assertTracksCreated(); if (isEndOfInput) { - sampleReader.getSampleIsKeyframe(); sampleReader.end(totalBytesWritten); } } @@ -495,16 +494,24 @@ public final class H264Reader implements ElementaryStreamReader { sampleIsKeyframe = false; readingSample = true; } - return getSampleIsKeyframe(); + setSampleIsKeyframe(); + return sampleIsKeyframe; } - public boolean getSampleIsKeyframe() { + public void end(long position) { + setSampleIsKeyframe(); + // Output a final sample with the NAL units currently held + nalUnitStartPosition = position; + outputSample(/* offset= */ 0); + readingSample = false; + } + + private void setSampleIsKeyframe() { boolean treatIFrameAsKeyframe = allowNonIdrKeyframes ? sliceHeader.isISlice() : randomAccessIndicator; sampleIsKeyframe |= nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_IDR || (treatIFrameAsKeyframe && nalUnitType == NalUnitUtil.NAL_UNIT_TYPE_NON_IDR); - return sampleIsKeyframe; } private void outputSample(int offset) { @@ -516,13 +523,6 @@ public final class H264Reader implements ElementaryStreamReader { output.sampleMetadata(sampleTimeUs, flags, size, offset, null); } - public void end(long position) { - // Output a final sample with the NAL units currently held - nalUnitStartPosition = position; - outputSample(/* offset= */ 0); - readingSample = false; - } - private static final class SliceHeaderData { private static final int SLICE_TYPE_I = 2; diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H265Reader.java b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H265Reader.java index c3ca6cacbe..3363ecc012 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H265Reader.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/H265Reader.java @@ -175,7 +175,6 @@ public final class H265Reader implements ElementaryStreamReader { public void packetFinished(boolean isEndOfInput) { assertTracksCreated(); if (isEndOfInput) { - sampleReader.getSampleIsKeyframe(); sampleReader.end(totalBytesWritten); } } @@ -378,9 +377,15 @@ public final class H265Reader implements ElementaryStreamReader { } } - public boolean getSampleIsKeyframe() { + public void end(long position) { sampleIsKeyframe = nalUnitHasKeyframeData; - return sampleIsKeyframe; + // Output a sample with the NAL units since the current nalUnitPosition + outputSample(/* offset= */ (int) (position - nalUnitPosition)); + // Output a final sample with the remaining NAL units up to the passed position + samplePosition = nalUnitPosition; + nalUnitPosition = position; + outputSample(/* offset= */ 0); + readingSample = false; } private void outputSample(int offset) { @@ -392,16 +397,6 @@ public final class H265Reader implements ElementaryStreamReader { output.sampleMetadata(sampleTimeUs, flags, size, offset, null); } - public void end(long position) { - // Output a sample with the NAL units since the current nalUnitPosition - outputSample(/* offset= */ (int) (position - nalUnitPosition)); - // Output a final sample with the remaining NAL units up to the passed position - samplePosition = nalUnitPosition; - nalUnitPosition = position; - outputSample(/* offset= */ 0); - readingSample = false; - } - /** Returns whether a NAL unit type is one that occurs before any VCL NAL units in a sample. */ private static boolean isPrefixNalUnit(int nalUnitType) { return (VPS_NUT <= nalUnitType && nalUnitType <= AUD_NUT) || nalUnitType == PREFIX_SEI_NUT; diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/PesReader.java b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/PesReader.java index 3c9862c69b..755672d214 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/PesReader.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/PesReader.java @@ -160,11 +160,12 @@ public final class PesReader implements TsPayloadReader { } } - private void setState(int state) { - this.state = state; - bytesRead = 0; - } - + /** + * Determines if the parser can consume a dummy end of input indication. + * + * @param isModeHls {@code True} if operating in HLS (HTTP Live Streaming) mode, {@code false} + * otherwise. + */ public boolean canConsumeDummyEndOfInput(boolean isModeHls) { // Pusi only payload to trigger end of sample data is only applicable if // pes does not have a length field and body is being read, another exclusion @@ -175,6 +176,11 @@ public final class PesReader implements TsPayloadReader { && !(isModeHls && reader instanceof H262Reader); } + private void setState(int state) { + this.state = state; + bytesRead = 0; + } + /** * Continues a read from the provided {@code source} into a given {@code target}. It's assumed * that the data should be written into {@code target} starting from an offset of zero. diff --git a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java index c29a968be7..160dc81e82 100644 --- a/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java +++ b/libraries/extractor/src/main/java/androidx/media3/extractor/ts/TsExtractor.java @@ -425,8 +425,9 @@ public final class TsExtractor implements Extractor { public @ReadResult int read(ExtractorInput input, PositionHolder seekPosition) throws IOException { long inputLength = input.getLength(); + boolean isModeHls = mode == MODE_HLS; if (tracksEnded) { - boolean canReadDuration = inputLength != C.LENGTH_UNSET && mode != MODE_HLS; + boolean canReadDuration = inputLength != C.LENGTH_UNSET && !isModeHls; if (canReadDuration && !durationReader.isDurationReadFinished()) { return durationReader.readDuration(input, seekPosition, pcrPid); } @@ -452,7 +453,6 @@ public final class TsExtractor implements Extractor { TsPayloadReader payloadReader = tsPayloadReaders.valueAt(i); if (payloadReader instanceof PesReader) { PesReader pesReader = (PesReader) payloadReader; - boolean isModeHls = (mode == MODE_HLS); if (pesReader.canConsumeDummyEndOfInput(isModeHls)) { pesReader.consume(new ParsableByteArray(), FLAG_PAYLOAD_UNIT_START_INDICATOR); }