diff options
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java')
-rw-r--r-- | mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java new file mode 100644 index 0000000000..acd08a2f12 --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/extractor/ts/PsBinarySearchSeeker.java @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ts; + +import org.mozilla.thirdparty.com.google.android.exoplayer2.C; +import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.BinarySearchSeeker; +import org.mozilla.thirdparty.com.google.android.exoplayer2.extractor.ExtractorInput; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.ParsableByteArray; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.TimestampAdjuster; +import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Util; +import java.io.IOException; + +/** + * A seeker that supports seeking within PS stream using binary search. + * + * <p>This seeker uses the first and last SCR values within the stream, as well as the stream + * duration to interpolate the SCR value of the seeking position. Then it performs binary search + * within the stream to find a packets whose SCR value is with in {@link #SEEK_TOLERANCE_US} from + * the target SCR. + */ +/* package */ final class PsBinarySearchSeeker extends BinarySearchSeeker { + + private static final long SEEK_TOLERANCE_US = 100_000; + private static final int MINIMUM_SEARCH_RANGE_BYTES = 1000; + private static final int TIMESTAMP_SEARCH_BYTES = 20000; + + public PsBinarySearchSeeker( + TimestampAdjuster scrTimestampAdjuster, long streamDurationUs, long inputLength) { + super( + new DefaultSeekTimestampConverter(), + new PsScrSeeker(scrTimestampAdjuster), + streamDurationUs, + /* floorTimePosition= */ 0, + /* ceilingTimePosition= */ streamDurationUs + 1, + /* floorBytePosition= */ 0, + /* ceilingBytePosition= */ inputLength, + /* approxBytesPerFrame= */ TsExtractor.TS_PACKET_SIZE, + MINIMUM_SEARCH_RANGE_BYTES); + } + + /** + * A seeker that looks for a given SCR timestamp at a given position in a PS stream. + * + * <p>Given a SCR timestamp, and a position within a PS stream, this seeker will peek up to {@link + * #TIMESTAMP_SEARCH_BYTES} bytes from that stream position, look for all packs in that range, and + * then compare the SCR timestamps (if available) of these packets to the target timestamp. + */ + private static final class PsScrSeeker implements TimestampSeeker { + + private final TimestampAdjuster scrTimestampAdjuster; + private final ParsableByteArray packetBuffer; + + private PsScrSeeker(TimestampAdjuster scrTimestampAdjuster) { + this.scrTimestampAdjuster = scrTimestampAdjuster; + packetBuffer = new ParsableByteArray(); + } + + @Override + public TimestampSearchResult searchForTimestamp(ExtractorInput input, long targetTimestamp) + throws IOException, InterruptedException { + long inputPosition = input.getPosition(); + int bytesToSearch = (int) Math.min(TIMESTAMP_SEARCH_BYTES, input.getLength() - inputPosition); + + packetBuffer.reset(bytesToSearch); + input.peekFully(packetBuffer.data, /* offset= */ 0, bytesToSearch); + + return searchForScrValueInBuffer(packetBuffer, targetTimestamp, inputPosition); + } + + @Override + public void onSeekFinished() { + packetBuffer.reset(Util.EMPTY_BYTE_ARRAY); + } + + private TimestampSearchResult searchForScrValueInBuffer( + ParsableByteArray packetBuffer, long targetScrTimeUs, long bufferStartOffset) { + int startOfLastPacketPosition = C.POSITION_UNSET; + int endOfLastPacketPosition = C.POSITION_UNSET; + long lastScrTimeUsInRange = C.TIME_UNSET; + + while (packetBuffer.bytesLeft() >= 4) { + int nextStartCode = peekIntAtPosition(packetBuffer.data, packetBuffer.getPosition()); + if (nextStartCode != PsExtractor.PACK_START_CODE) { + packetBuffer.skipBytes(1); + continue; + } else { + packetBuffer.skipBytes(4); + } + + // We found a pack. + long scrValue = PsDurationReader.readScrValueFromPack(packetBuffer); + if (scrValue != C.TIME_UNSET) { + long scrTimeUs = scrTimestampAdjuster.adjustTsTimestamp(scrValue); + if (scrTimeUs > targetScrTimeUs) { + if (lastScrTimeUsInRange == C.TIME_UNSET) { + // First SCR timestamp is already over target. + return TimestampSearchResult.overestimatedResult(scrTimeUs, bufferStartOffset); + } else { + // Last SCR timestamp < target timestamp < this timestamp. + return TimestampSearchResult.targetFoundResult( + bufferStartOffset + startOfLastPacketPosition); + } + } else if (scrTimeUs + SEEK_TOLERANCE_US > targetScrTimeUs) { + long startOfPacketInStream = bufferStartOffset + packetBuffer.getPosition(); + return TimestampSearchResult.targetFoundResult(startOfPacketInStream); + } + + lastScrTimeUsInRange = scrTimeUs; + startOfLastPacketPosition = packetBuffer.getPosition(); + } + skipToEndOfCurrentPack(packetBuffer); + endOfLastPacketPosition = packetBuffer.getPosition(); + } + + if (lastScrTimeUsInRange != C.TIME_UNSET) { + long endOfLastPacketPositionInStream = bufferStartOffset + endOfLastPacketPosition; + return TimestampSearchResult.underestimatedResult( + lastScrTimeUsInRange, endOfLastPacketPositionInStream); + } else { + return TimestampSearchResult.NO_TIMESTAMP_IN_RANGE_RESULT; + } + } + + /** + * Skips the buffer position to the position after the end of the current PS pack in the buffer, + * given the byte position right after the {@link PsExtractor#PACK_START_CODE} of the pack in + * the buffer. If the pack ends after the end of the buffer, skips to the end of the buffer. + */ + private static void skipToEndOfCurrentPack(ParsableByteArray packetBuffer) { + int limit = packetBuffer.limit(); + + if (packetBuffer.bytesLeft() < 10) { + // We require at least 9 bytes for pack header to read SCR value + 1 byte for pack_stuffing + // length. + packetBuffer.setPosition(limit); + return; + } + packetBuffer.skipBytes(9); + + int packStuffingLength = packetBuffer.readUnsignedByte() & 0x07; + if (packetBuffer.bytesLeft() < packStuffingLength) { + packetBuffer.setPosition(limit); + return; + } + packetBuffer.skipBytes(packStuffingLength); + + if (packetBuffer.bytesLeft() < 4) { + packetBuffer.setPosition(limit); + return; + } + + int nextStartCode = peekIntAtPosition(packetBuffer.data, packetBuffer.getPosition()); + if (nextStartCode == PsExtractor.SYSTEM_HEADER_START_CODE) { + packetBuffer.skipBytes(4); + int systemHeaderLength = packetBuffer.readUnsignedShort(); + if (packetBuffer.bytesLeft() < systemHeaderLength) { + packetBuffer.setPosition(limit); + return; + } + packetBuffer.skipBytes(systemHeaderLength); + } + + // Find the position of the next PACK_START_CODE or MPEG_PROGRAM_END_CODE, which is right + // after the end position of this pack. + // If we couldn't find these codes within the buffer, return the buffer limit, or return + // the first position which PES packets pattern does not match (some malformed packets). + while (packetBuffer.bytesLeft() >= 4) { + nextStartCode = peekIntAtPosition(packetBuffer.data, packetBuffer.getPosition()); + if (nextStartCode == PsExtractor.PACK_START_CODE + || nextStartCode == PsExtractor.MPEG_PROGRAM_END_CODE) { + break; + } + if (nextStartCode >>> 8 != PsExtractor.PACKET_START_CODE_PREFIX) { + break; + } + packetBuffer.skipBytes(4); + + if (packetBuffer.bytesLeft() < 2) { + // 2 bytes for PES_packet length. + packetBuffer.setPosition(limit); + return; + } + int pesPacketLength = packetBuffer.readUnsignedShort(); + packetBuffer.setPosition( + Math.min(packetBuffer.limit(), packetBuffer.getPosition() + pesPacketLength)); + } + } + } + + private static int peekIntAtPosition(byte[] data, int position) { + return (data[position] & 0xFF) << 24 + | (data[position + 1] & 0xFF) << 16 + | (data[position + 2] & 0xFF) << 8 + | (data[position + 3] & 0xFF); + } +} |