From 1cca9ffd01c4b217eea77d5e7239a5921b72f92f Mon Sep 17 00:00:00 2001 From: kimvde Date: Tue, 6 Oct 2020 11:53:02 +0100 Subject: [PATCH] Add search bytes parameter to TsExtractor Context: Issue: #7988 PiperOrigin-RevId: 335608610 --- RELEASENOTES.md | 8 +-- .../extractor/DefaultExtractorsFactory.java | 20 +++++++- .../extractor/ts/TsBinarySearchSeeker.java | 18 ++++--- .../extractor/ts/TsDurationReader.java | 10 ++-- .../exoplayer2/extractor/ts/TsExtractor.java | 51 ++++++++++++++++--- .../extractor/ts/TsDurationReaderTest.java | 2 +- 6 files changed, 85 insertions(+), 24 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3b2aa2e1589..bffb735a47a 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -15,9 +15,6 @@ ([#7985](https://github.com/google/ExoPlayer/issues/7985)). * Fix NPE in `TextRenderer` when playing content with a single subtitle buffer ([#8017](https://github.com/google/ExoPlayer/issues/8017)). -* UI: - * Do not require subtitleButton in custom layouts of StyledPlayerView - ([#7962](https://github.com/google/ExoPlayer/issues/7962)). * Audio: * Fix the default audio sink position not advancing correctly when using `AudioTrack`-based speed adjustment @@ -29,7 +26,12 @@ ([#7949](https://github.com/google/ExoPlayer/issues/7949)). * Fix regression for Ogg files with packets that span multiple pages ([#7992](https://github.com/google/ExoPlayer/issues/7992)). + * Add TS extractor parameter to configure the number of bytes in which + to search for a timestamp to determine the duration and to seek. + ([#7988](https://github.com/google/ExoPlayer/issues/7988)). * UI + * Do not require subtitleButton in custom layouts of StyledPlayerView + ([#7962](https://github.com/google/ExoPlayer/issues/7962)). * Add the option to sort tracks by `Format` in `TrackSelectionView` and `TrackSelectionDialogBuilder` ([#7709](https://github.com/google/ExoPlayer/issues/7709)). diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java index 2eba1b1cca0..2068853d9ea 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/DefaultExtractorsFactory.java @@ -131,9 +131,11 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { @Mp3Extractor.Flags private int mp3Flags; @TsExtractor.Mode private int tsMode; @DefaultTsPayloadReaderFactory.Flags private int tsFlags; + private int tsTimestampSearchBytes; public DefaultExtractorsFactory() { tsMode = TsExtractor.MODE_SINGLE_PMT; + tsTimestampSearchBytes = TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES; } /** @@ -246,7 +248,7 @@ public synchronized DefaultExtractorsFactory setMp3ExtractorFlags(@Mp3Extractor. /** * Sets the mode for {@link TsExtractor} instances created by the factory. * - * @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory) + * @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int) * @param mode The mode to use. * @return The factory, for convenience. */ @@ -269,6 +271,20 @@ public synchronized DefaultExtractorsFactory setTsExtractorFlags( return this; } + /** + * Sets the number of bytes searched to find a timestamp for {@link TsExtractor} instances created + * by the factory. + * + * @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory, int) + * @param timestampSearchBytes The number of search bytes to use. + * @return The factory, for convenience. + */ + public synchronized DefaultExtractorsFactory setTsExtractorTimestampSearchBytes( + int timestampSearchBytes) { + tsTimestampSearchBytes = timestampSearchBytes; + return this; + } + @Override public synchronized Extractor[] createExtractors() { return createExtractors(Uri.EMPTY, new HashMap<>()); @@ -361,7 +377,7 @@ private void addExtractorsForFileType(@FileTypes.Type int fileType, ListGiven a PCR timestamp, and a position within a TS stream, this seeker will peek up to {@link - * #TIMESTAMP_SEARCH_BYTES} from that stream position, look for all packets with PID equal to + * #timestampSearchBytes} from that stream position, look for all packets with PID equal to * PCR_PID, and then compare the PCR timestamps (if available) of these packets to the target * timestamp. */ @@ -67,10 +70,13 @@ private static final class TsPcrSeeker implements TimestampSeeker { private final TimestampAdjuster pcrTimestampAdjuster; private final ParsableByteArray packetBuffer; private final int pcrPid; + private final int timestampSearchBytes; - public TsPcrSeeker(int pcrPid, TimestampAdjuster pcrTimestampAdjuster) { + public TsPcrSeeker( + int pcrPid, TimestampAdjuster pcrTimestampAdjuster, int timestampSearchBytes) { this.pcrPid = pcrPid; this.pcrTimestampAdjuster = pcrTimestampAdjuster; + this.timestampSearchBytes = timestampSearchBytes; packetBuffer = new ParsableByteArray(); } @@ -78,7 +84,7 @@ public TsPcrSeeker(int pcrPid, TimestampAdjuster pcrTimestampAdjuster) { public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp) throws IOException { long inputPosition = input.getPosition(); - int bytesToSearch = (int) min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); + int bytesToSearch = (int) min(timestampSearchBytes, input.getLength() - inputPosition); packetBuffer.reset(bytesToSearch); input.peekFully(packetBuffer.getData(), /* offset= */ 0, bytesToSearch); diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java index 5020f4c76da..504b84d575c 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsDurationReader.java @@ -38,8 +38,7 @@ */ /* package */ final class TsDurationReader { - private static final int TIMESTAMP_SEARCH_BYTES = 600 * TsExtractor.TS_PACKET_SIZE; - + private final int timestampSearchBytes; private final TimestampAdjuster pcrTimestampAdjuster; private final ParsableByteArray packetBuffer; @@ -51,7 +50,8 @@ private long lastPcrValue; private long durationUs; - /* package */ TsDurationReader() { + /* package */ TsDurationReader(int timestampSearchBytes) { + this.timestampSearchBytes = timestampSearchBytes; pcrTimestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0); firstPcrValue = C.TIME_UNSET; lastPcrValue = C.TIME_UNSET; @@ -125,7 +125,7 @@ private int finishReadDuration(ExtractorInput input) { private int readFirstPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) throws IOException { - int bytesToSearch = (int) min(TIMESTAMP_SEARCH_BYTES, input.getLength()); + int bytesToSearch = (int) min(timestampSearchBytes, input.getLength()); int searchStartPosition = 0; if (input.getPosition() != searchStartPosition) { seekPositionHolder.position = searchStartPosition; @@ -161,7 +161,7 @@ private long readFirstPcrValueFromBuffer(ParsableByteArray packetBuffer, int pcr private int readLastPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) throws IOException { long inputLength = input.getLength(); - int bytesToSearch = (int) min(TIMESTAMP_SEARCH_BYTES, inputLength); + int bytesToSearch = (int) min(timestampSearchBytes, inputLength); long searchStartPosition = inputLength - bytesToSearch; if (input.getPosition() != searchStartPosition) { seekPositionHolder.position = searchStartPosition; diff --git a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 2fcfd422a01..2a9613f7f40 100644 --- a/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/extractor/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -80,6 +80,9 @@ public final class TsExtractor implements Extractor { */ public static final int MODE_HLS = 2; + public static final int TS_PACKET_SIZE = 188; + public static final int DEFAULT_TIMESTAMP_SEARCH_BYTES = 600 * TS_PACKET_SIZE; + public static final int TS_STREAM_TYPE_MPA = 0x03; public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; public static final int TS_STREAM_TYPE_AAC_ADTS = 0x0F; @@ -100,7 +103,6 @@ public final class TsExtractor implements Extractor { // Stream types that aren't defined by the MPEG-2 TS specification. public static final int TS_STREAM_TYPE_AIT = 0x101; - public static final int TS_PACKET_SIZE = 188; public static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet. private static final int TS_PAT_PID = 0; @@ -115,6 +117,7 @@ public final class TsExtractor implements Extractor { private static final int SNIFF_TS_PACKET_COUNT = 5; private final @Mode int mode; + private final int timestampSearchBytes; private final List timestampAdjusters; private final ParsableByteArray tsPacketBuffer; private final SparseIntArray continuityCounters; @@ -136,7 +139,7 @@ public final class TsExtractor implements Extractor { private int pcrPid; public TsExtractor() { - this(0); + this(/* defaultTsPayloadReaderFlags= */ 0); } /** @@ -144,7 +147,7 @@ public TsExtractor() { * {@code FLAG_*} values that control the behavior of the payload readers. */ public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { - this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags); + this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags, DEFAULT_TIMESTAMP_SEARCH_BYTES); } /** @@ -152,12 +155,22 @@ public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { * and {@link #MODE_HLS}. * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} * {@code FLAG_*} values that control the behavior of the payload readers. + * @param timestampSearchBytes The number of bytes searched from a given position in the stream to + * find a PCR timestamp. If this value is too small, the duration might be unknown and seeking + * might not be supported for high bitrate progressive streams. Setting a large value for this + * field might be inefficient though because the extractor stores a buffer of {@code + * timestampSearchBytes} bytes when determining the duration or when performing a seek + * operation. The default value is {@link #DEFAULT_TIMESTAMP_SEARCH_BYTES}. If the number of + * bytes left in the stream from the current position is less than {@code + * timestampSearchBytes}, the search is performed on the bytes left. */ - public TsExtractor(@Mode int mode, @Flags int defaultTsPayloadReaderFlags) { + public TsExtractor( + @Mode int mode, @Flags int defaultTsPayloadReaderFlags, int timestampSearchBytes) { this( mode, new TimestampAdjuster(0), - new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); + new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags), + timestampSearchBytes); } /** @@ -170,7 +183,30 @@ public TsExtractor( @Mode int mode, TimestampAdjuster timestampAdjuster, TsPayloadReader.Factory payloadReaderFactory) { + this(mode, timestampAdjuster, payloadReaderFactory, DEFAULT_TIMESTAMP_SEARCH_BYTES); + } + + /** + * @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT} + * and {@link #MODE_HLS}. + * @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps. + * @param payloadReaderFactory Factory for injecting a custom set of payload readers. + * @param timestampSearchBytes The number of bytes searched from a given position in the stream to + * find a PCR timestamp. If this value is too small, the duration might be unknown and seeking + * might not be supported for high bitrate progressive streams. Setting a large value for this + * field might be inefficient though because the extractor stores a buffer of {@code + * timestampSearchBytes} bytes when determining the duration or when performing a seek + * operation. The default value is {@link #DEFAULT_TIMESTAMP_SEARCH_BYTES}. If the number of + * bytes left in the stream from the current position is less than {@code + * timestampSearchBytes}, the search is performed on the bytes left. + */ + public TsExtractor( + @Mode int mode, + TimestampAdjuster timestampAdjuster, + TsPayloadReader.Factory payloadReaderFactory, + int timestampSearchBytes) { this.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory); + this.timestampSearchBytes = timestampSearchBytes; this.mode = mode; if (mode == MODE_SINGLE_PMT || mode == MODE_HLS) { timestampAdjusters = Collections.singletonList(timestampAdjuster); @@ -183,7 +219,7 @@ public TsExtractor( trackPids = new SparseBooleanArray(); tsPayloadReaders = new SparseArray<>(); continuityCounters = new SparseIntArray(); - durationReader = new TsDurationReader(); + durationReader = new TsDurationReader(timestampSearchBytes); pcrPid = -1; resetPayloadReaders(); } @@ -365,7 +401,8 @@ private void maybeOutputSeekMap(long inputLength) { durationReader.getPcrTimestampAdjuster(), durationReader.getDurationUs(), inputLength, - pcrPid); + pcrPid, + timestampSearchBytes); output.seekMap(tsBinarySearchSeeker.getSeekMap()); } else { output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs())); diff --git a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java index 8f744e855d7..0e55d292b8c 100644 --- a/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java +++ b/library/extractor/src/test/java/com/google/android/exoplayer2/extractor/ts/TsDurationReaderTest.java @@ -37,7 +37,7 @@ public final class TsDurationReaderTest { @Before public void setUp() { - tsDurationReader = new TsDurationReader(); + tsDurationReader = new TsDurationReader(TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES); seekPositionHolder = new PositionHolder(); }