Add search bytes parameter to TsExtractor

Context: Issue: #7988
PiperOrigin-RevId: 335608610
This commit is contained in:
kimvde 2020-10-06 11:53:02 +01:00 committed by Oliver Woodman
parent 88abe26ec3
commit 1cca9ffd01
6 changed files with 85 additions and 24 deletions

View File

@ -15,9 +15,6 @@
([#7985](https://github.com/google/ExoPlayer/issues/7985)). ([#7985](https://github.com/google/ExoPlayer/issues/7985)).
* Fix NPE in `TextRenderer` when playing content with a single subtitle * Fix NPE in `TextRenderer` when playing content with a single subtitle
buffer ([#8017](https://github.com/google/ExoPlayer/issues/8017)). 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: * Audio:
* Fix the default audio sink position not advancing correctly when using * Fix the default audio sink position not advancing correctly when using
`AudioTrack`-based speed adjustment `AudioTrack`-based speed adjustment
@ -29,7 +26,12 @@
([#7949](https://github.com/google/ExoPlayer/issues/7949)). ([#7949](https://github.com/google/ExoPlayer/issues/7949)).
* Fix regression for Ogg files with packets that span multiple pages * Fix regression for Ogg files with packets that span multiple pages
([#7992](https://github.com/google/ExoPlayer/issues/7992)). ([#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 * 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 * Add the option to sort tracks by `Format` in `TrackSelectionView` and
`TrackSelectionDialogBuilder` `TrackSelectionDialogBuilder`
([#7709](https://github.com/google/ExoPlayer/issues/7709)). ([#7709](https://github.com/google/ExoPlayer/issues/7709)).

View File

@ -131,9 +131,11 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Mp3Extractor.Flags private int mp3Flags; @Mp3Extractor.Flags private int mp3Flags;
@TsExtractor.Mode private int tsMode; @TsExtractor.Mode private int tsMode;
@DefaultTsPayloadReaderFactory.Flags private int tsFlags; @DefaultTsPayloadReaderFactory.Flags private int tsFlags;
private int tsTimestampSearchBytes;
public DefaultExtractorsFactory() { public DefaultExtractorsFactory() {
tsMode = TsExtractor.MODE_SINGLE_PMT; tsMode = TsExtractor.MODE_SINGLE_PMT;
tsTimestampSearchBytes = TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES;
} }
/** /**
@ -246,7 +248,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
/** /**
* Sets the mode for {@link TsExtractor} instances created by the factory. * 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. * @param mode The mode to use.
* @return The factory, for convenience. * @return The factory, for convenience.
*/ */
@ -269,6 +271,20 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
return this; 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 @Override
public synchronized Extractor[] createExtractors() { public synchronized Extractor[] createExtractors() {
return createExtractors(Uri.EMPTY, new HashMap<>()); return createExtractors(Uri.EMPTY, new HashMap<>());
@ -361,7 +377,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
extractors.add(new PsExtractor()); extractors.add(new PsExtractor());
break; break;
case FileTypes.TS: case FileTypes.TS:
extractors.add(new TsExtractor(tsMode, tsFlags)); extractors.add(new TsExtractor(tsMode, tsFlags, tsTimestampSearchBytes));
break; break;
case FileTypes.WAV: case FileTypes.WAV:
extractors.add(new WavExtractor()); extractors.add(new WavExtractor());

View File

@ -37,13 +37,16 @@ import java.io.IOException;
private static final long SEEK_TOLERANCE_US = 100_000; private static final long SEEK_TOLERANCE_US = 100_000;
private static final int MINIMUM_SEARCH_RANGE_BYTES = 5 * TsExtractor.TS_PACKET_SIZE; private static final int MINIMUM_SEARCH_RANGE_BYTES = 5 * TsExtractor.TS_PACKET_SIZE;
private static final int TIMESTAMP_SEARCH_BYTES = 600 * TsExtractor.TS_PACKET_SIZE;
public TsBinarySearchSeeker( public TsBinarySearchSeeker(
TimestampAdjuster pcrTimestampAdjuster, long streamDurationUs, long inputLength, int pcrPid) { TimestampAdjuster pcrTimestampAdjuster,
long streamDurationUs,
long inputLength,
int pcrPid,
int timestampSearchBytes) {
super( super(
new DefaultSeekTimestampConverter(), new DefaultSeekTimestampConverter(),
new TsPcrSeeker(pcrPid, pcrTimestampAdjuster), new TsPcrSeeker(pcrPid, pcrTimestampAdjuster, timestampSearchBytes),
streamDurationUs, streamDurationUs,
/* floorTimePosition= */ 0, /* floorTimePosition= */ 0,
/* ceilingTimePosition= */ streamDurationUs + 1, /* ceilingTimePosition= */ streamDurationUs + 1,
@ -58,7 +61,7 @@ import java.io.IOException;
* position in a TS stream. * position in a TS stream.
* *
* <p>Given a PCR timestamp, and a position within a TS stream, this seeker will peek up to {@link * <p>Given 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 * PCR_PID, and then compare the PCR timestamps (if available) of these packets to the target
* timestamp. * timestamp.
*/ */
@ -67,10 +70,13 @@ import java.io.IOException;
private final TimestampAdjuster pcrTimestampAdjuster; private final TimestampAdjuster pcrTimestampAdjuster;
private final ParsableByteArray packetBuffer; private final ParsableByteArray packetBuffer;
private final int pcrPid; 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.pcrPid = pcrPid;
this.pcrTimestampAdjuster = pcrTimestampAdjuster; this.pcrTimestampAdjuster = pcrTimestampAdjuster;
this.timestampSearchBytes = timestampSearchBytes;
packetBuffer = new ParsableByteArray(); packetBuffer = new ParsableByteArray();
} }
@ -78,7 +84,7 @@ import java.io.IOException;
public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp) public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp)
throws IOException { throws IOException {
long inputPosition = input.getPosition(); 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); packetBuffer.reset(bytesToSearch);
input.peekFully(packetBuffer.getData(), /* offset= */ 0, bytesToSearch); input.peekFully(packetBuffer.getData(), /* offset= */ 0, bytesToSearch);

View File

@ -38,8 +38,7 @@ import java.io.IOException;
*/ */
/* package */ final class TsDurationReader { /* 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 TimestampAdjuster pcrTimestampAdjuster;
private final ParsableByteArray packetBuffer; private final ParsableByteArray packetBuffer;
@ -51,7 +50,8 @@ import java.io.IOException;
private long lastPcrValue; private long lastPcrValue;
private long durationUs; private long durationUs;
/* package */ TsDurationReader() { /* package */ TsDurationReader(int timestampSearchBytes) {
this.timestampSearchBytes = timestampSearchBytes;
pcrTimestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0); pcrTimestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0);
firstPcrValue = C.TIME_UNSET; firstPcrValue = C.TIME_UNSET;
lastPcrValue = C.TIME_UNSET; lastPcrValue = C.TIME_UNSET;
@ -125,7 +125,7 @@ import java.io.IOException;
private int readFirstPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) private int readFirstPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
throws IOException { throws IOException {
int bytesToSearch = (int) min(TIMESTAMP_SEARCH_BYTES, input.getLength()); int bytesToSearch = (int) min(timestampSearchBytes, input.getLength());
int searchStartPosition = 0; int searchStartPosition = 0;
if (input.getPosition() != searchStartPosition) { if (input.getPosition() != searchStartPosition) {
seekPositionHolder.position = searchStartPosition; seekPositionHolder.position = searchStartPosition;
@ -161,7 +161,7 @@ import java.io.IOException;
private int readLastPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid) private int readLastPcrValue(ExtractorInput input, PositionHolder seekPositionHolder, int pcrPid)
throws IOException { throws IOException {
long inputLength = input.getLength(); long inputLength = input.getLength();
int bytesToSearch = (int) min(TIMESTAMP_SEARCH_BYTES, inputLength); int bytesToSearch = (int) min(timestampSearchBytes, inputLength);
long searchStartPosition = inputLength - bytesToSearch; long searchStartPosition = inputLength - bytesToSearch;
if (input.getPosition() != searchStartPosition) { if (input.getPosition() != searchStartPosition) {
seekPositionHolder.position = searchStartPosition; seekPositionHolder.position = searchStartPosition;

View File

@ -80,6 +80,9 @@ public final class TsExtractor implements Extractor {
*/ */
public static final int MODE_HLS = 2; 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 = 0x03;
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04; public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
public static final int TS_STREAM_TYPE_AAC_ADTS = 0x0F; 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. // 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_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. public static final int TS_SYNC_BYTE = 0x47; // First byte of each TS packet.
private static final int TS_PAT_PID = 0; 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 static final int SNIFF_TS_PACKET_COUNT = 5;
private final @Mode int mode; private final @Mode int mode;
private final int timestampSearchBytes;
private final List<TimestampAdjuster> timestampAdjusters; private final List<TimestampAdjuster> timestampAdjusters;
private final ParsableByteArray tsPacketBuffer; private final ParsableByteArray tsPacketBuffer;
private final SparseIntArray continuityCounters; private final SparseIntArray continuityCounters;
@ -136,7 +139,7 @@ public final class TsExtractor implements Extractor {
private int pcrPid; private int pcrPid;
public TsExtractor() { public TsExtractor() {
this(0); this(/* defaultTsPayloadReaderFlags= */ 0);
} }
/** /**
@ -144,7 +147,7 @@ public final class TsExtractor implements Extractor {
* {@code FLAG_*} values that control the behavior of the payload readers. * {@code FLAG_*} values that control the behavior of the payload readers.
*/ */
public TsExtractor(@Flags int defaultTsPayloadReaderFlags) { public TsExtractor(@Flags int defaultTsPayloadReaderFlags) {
this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags); this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags, DEFAULT_TIMESTAMP_SEARCH_BYTES);
} }
/** /**
@ -152,12 +155,22 @@ public final class TsExtractor implements Extractor {
* and {@link #MODE_HLS}. * and {@link #MODE_HLS}.
* @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory} * @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory}
* {@code FLAG_*} values that control the behavior of the payload readers. * {@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( this(
mode, mode,
new TimestampAdjuster(0), new TimestampAdjuster(0),
new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags)); new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags),
timestampSearchBytes);
} }
/** /**
@ -170,7 +183,30 @@ public final class TsExtractor implements Extractor {
@Mode int mode, @Mode int mode,
TimestampAdjuster timestampAdjuster, TimestampAdjuster timestampAdjuster,
TsPayloadReader.Factory payloadReaderFactory) { 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.payloadReaderFactory = Assertions.checkNotNull(payloadReaderFactory);
this.timestampSearchBytes = timestampSearchBytes;
this.mode = mode; this.mode = mode;
if (mode == MODE_SINGLE_PMT || mode == MODE_HLS) { if (mode == MODE_SINGLE_PMT || mode == MODE_HLS) {
timestampAdjusters = Collections.singletonList(timestampAdjuster); timestampAdjusters = Collections.singletonList(timestampAdjuster);
@ -183,7 +219,7 @@ public final class TsExtractor implements Extractor {
trackPids = new SparseBooleanArray(); trackPids = new SparseBooleanArray();
tsPayloadReaders = new SparseArray<>(); tsPayloadReaders = new SparseArray<>();
continuityCounters = new SparseIntArray(); continuityCounters = new SparseIntArray();
durationReader = new TsDurationReader(); durationReader = new TsDurationReader(timestampSearchBytes);
pcrPid = -1; pcrPid = -1;
resetPayloadReaders(); resetPayloadReaders();
} }
@ -365,7 +401,8 @@ public final class TsExtractor implements Extractor {
durationReader.getPcrTimestampAdjuster(), durationReader.getPcrTimestampAdjuster(),
durationReader.getDurationUs(), durationReader.getDurationUs(),
inputLength, inputLength,
pcrPid); pcrPid,
timestampSearchBytes);
output.seekMap(tsBinarySearchSeeker.getSeekMap()); output.seekMap(tsBinarySearchSeeker.getSeekMap());
} else { } else {
output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs())); output.seekMap(new SeekMap.Unseekable(durationReader.getDurationUs()));

View File

@ -37,7 +37,7 @@ public final class TsDurationReaderTest {
@Before @Before
public void setUp() { public void setUp() {
tsDurationReader = new TsDurationReader(); tsDurationReader = new TsDurationReader(TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES);
seekPositionHolder = new PositionHolder(); seekPositionHolder = new PositionHolder();
} }