Java Code Examples for com.google.android.exoplayer2.util.ParsableByteArray#limit()

The following examples show how to use com.google.android.exoplayer2.util.ParsableByteArray#limit() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: VorbisReader.java    From MediaSDK with Apache License 2.0 6 votes vote down vote up
@VisibleForTesting
/* package */ VorbisSetup readSetupHeaders(ParsableByteArray scratch) throws IOException {

  if (vorbisIdHeader == null) {
    vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch);
    return null;
  }

  if (commentHeader == null) {
    commentHeader = VorbisUtil.readVorbisCommentHeader(scratch);
    return null;
  }

  // the third packet contains the setup header
  byte[] setupHeaderData = new byte[scratch.limit()];
  // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2
  System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit());
  // partially decode setup header to get the modes
  Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels);
  // we need the ilog of modes all the time when extracting, so we compute it once
  int iLogModes = VorbisUtil.iLog(modes.length - 1);

  return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes);
}
 
Example 2
Source File: TsDurationReader.java    From Telegram-FOSS with GNU General Public License v2.0 6 votes vote down vote up
private long readFirstPcrValueFromBuffer(ParsableByteArray packetBuffer, int pcrPid) {
  int searchStartPosition = packetBuffer.getPosition();
  int searchEndPosition = packetBuffer.limit();
  for (int searchPosition = searchStartPosition;
      searchPosition < searchEndPosition;
      searchPosition++) {
    if (packetBuffer.data[searchPosition] != TsExtractor.TS_SYNC_BYTE) {
      continue;
    }
    long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, searchPosition, pcrPid);
    if (pcrValue != C.TIME_UNSET) {
      return pcrValue;
    }
  }
  return C.TIME_UNSET;
}
 
Example 3
Source File: PsDurationReader.java    From Telegram-FOSS with GNU General Public License v2.0 6 votes vote down vote up
private long readFirstScrValueFromBuffer(ParsableByteArray packetBuffer) {
  int searchStartPosition = packetBuffer.getPosition();
  int searchEndPosition = packetBuffer.limit();
  for (int searchPosition = searchStartPosition;
      searchPosition < searchEndPosition - 3;
      searchPosition++) {
    int nextStartCode = peekIntAtPosition(packetBuffer.data, searchPosition);
    if (nextStartCode == PsExtractor.PACK_START_CODE) {
      packetBuffer.setPosition(searchPosition + 4);
      long scrValue = readScrValueFromPack(packetBuffer);
      if (scrValue != C.TIME_UNSET) {
        return scrValue;
      }
    }
  }
  return C.TIME_UNSET;
}
 
Example 4
Source File: TsDurationReader.java    From Telegram-FOSS with GNU General Public License v2.0 6 votes vote down vote up
private long readLastPcrValueFromBuffer(ParsableByteArray packetBuffer, int pcrPid) {
  int searchStartPosition = packetBuffer.getPosition();
  int searchEndPosition = packetBuffer.limit();
  for (int searchPosition = searchEndPosition - 1;
      searchPosition >= searchStartPosition;
      searchPosition--) {
    if (packetBuffer.data[searchPosition] != TsExtractor.TS_SYNC_BYTE) {
      continue;
    }
    long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, searchPosition, pcrPid);
    if (pcrValue != C.TIME_UNSET) {
      return pcrValue;
    }
  }
  return C.TIME_UNSET;
}
 
Example 5
Source File: CssParser.java    From no-player with Apache License 2.0 6 votes vote down vote up
private static boolean maybeSkipComment(ParsableByteArray input) {
  int position = input.getPosition();
  int limit = input.limit();
  byte[] data = input.data;
  if (position + 2 <= limit && data[position++] == '/' && data[position++] == '*') {
    while (position + 1 < limit) {
      char skippedChar = (char) data[position++];
      if (skippedChar == '*') {
        if (((char) data[position]) == '/') {
          position++;
          limit = position;
        }
      }
    }
    input.skipBytes(limit - input.getPosition());
    return true;
  }
  return false;
}
 
Example 6
Source File: VorbisReader.java    From K-Sonic with MIT License 6 votes vote down vote up
VorbisSetup readSetupHeaders(ParsableByteArray scratch) throws IOException {

    if (vorbisIdHeader == null) {
      vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch);
      return null;
    }

    if (commentHeader == null) {
      commentHeader = VorbisUtil.readVorbisCommentHeader(scratch);
      return null;
    }

    // the third packet contains the setup header
    byte[] setupHeaderData = new byte[scratch.limit()];
    // raw data of vorbis setup header has to be passed to decoder as CSD buffer #2
    System.arraycopy(scratch.data, 0, setupHeaderData, 0, scratch.limit());
    // partially decode setup header to get the modes
    Mode[] modes = VorbisUtil.readVorbisModes(scratch, vorbisIdHeader.channels);
    // we need the ilog of modes all the time when extracting, so we compute it once
    int iLogModes = VorbisUtil.iLog(modes.length - 1);

    return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes);
  }
 
Example 7
Source File: ProjectionDecoder.java    From MediaSDK with Apache License 2.0 6 votes vote down vote up
/** Parses MSHP data after the encoding_four_cc field. */
private static @Nullable ArrayList<Mesh> parseRawMshpData(ParsableByteArray input) {
  ArrayList<Mesh> meshes = new ArrayList<>();
  int position = input.getPosition();
  int limit = input.limit();
  while (position < limit) {
    int childEnd = position + input.readInt();
    if (childEnd <= position || childEnd > limit) {
      return null;
    }
    int childAtomType = input.readInt();
    if (childAtomType == TYPE_MESH) {
      Mesh mesh = parseMesh(input);
      if (mesh == null) {
        return null;
      }
      meshes.add(mesh);
    }
    position = childEnd;
    input.setPosition(position);
  }
  return meshes;
}
 
Example 8
Source File: MpegAudioReader.java    From Telegram with GNU General Public License v2.0 6 votes vote down vote up
/**
 * Attempts to locate the start of the next frame header.
 * <p>
 * If a frame header is located then the state is changed to {@link #STATE_READING_HEADER}, the
 * first two bytes of the header are written into {@link #headerScratch}, and the position of the
 * source is advanced to the byte that immediately follows these two bytes.
 * <p>
 * If a frame header is not located then the position of the source is advanced to the limit, and
 * the method should be called again with the next source to continue the search.
 *
 * @param source The source from which to read.
 */
private void findHeader(ParsableByteArray source) {
  byte[] data = source.data;
  int startOffset = source.getPosition();
  int endOffset = source.limit();
  for (int i = startOffset; i < endOffset; i++) {
    boolean byteIsFF = (data[i] & 0xFF) == 0xFF;
    boolean found = lastByteWasFF && (data[i] & 0xE0) == 0xE0;
    lastByteWasFF = byteIsFF;
    if (found) {
      source.setPosition(i + 1);
      // Reset lastByteWasFF for next time.
      lastByteWasFF = false;
      headerScratch.data[1] = data[i];
      frameBytesRead = 2;
      state = STATE_READING_HEADER;
      return;
    }
  }
  source.setPosition(endOffset);
}
 
Example 9
Source File: MpegAudioReader.java    From MediaSDK with Apache License 2.0 6 votes vote down vote up
/**
 * Attempts to locate the start of the next frame header.
 * <p>
 * If a frame header is located then the state is changed to {@link #STATE_READING_HEADER}, the
 * first two bytes of the header are written into {@link #headerScratch}, and the position of the
 * source is advanced to the byte that immediately follows these two bytes.
 * <p>
 * If a frame header is not located then the position of the source is advanced to the limit, and
 * the method should be called again with the next source to continue the search.
 *
 * @param source The source from which to read.
 */
private void findHeader(ParsableByteArray source) {
  byte[] data = source.data;
  int startOffset = source.getPosition();
  int endOffset = source.limit();
  for (int i = startOffset; i < endOffset; i++) {
    boolean byteIsFF = (data[i] & 0xFF) == 0xFF;
    boolean found = lastByteWasFF && (data[i] & 0xE0) == 0xE0;
    lastByteWasFF = byteIsFF;
    if (found) {
      source.setPosition(i + 1);
      // Reset lastByteWasFF for next time.
      lastByteWasFF = false;
      headerScratch.data[1] = data[i];
      frameBytesRead = 2;
      state = STATE_READING_HEADER;
      return;
    }
  }
  source.setPosition(endOffset);
}
 
Example 10
Source File: ProjectionDecoder.java    From Telegram with GNU General Public License v2.0 6 votes vote down vote up
/** Parses MSHP data after the encoding_four_cc field. */
private static @Nullable ArrayList<Mesh> parseRawMshpData(ParsableByteArray input) {
  ArrayList<Mesh> meshes = new ArrayList<>();
  int position = input.getPosition();
  int limit = input.limit();
  while (position < limit) {
    int childEnd = position + input.readInt();
    if (childEnd <= position || childEnd > limit) {
      return null;
    }
    int childAtomType = input.readInt();
    if (childAtomType == TYPE_MESH) {
      Mesh mesh = parseMesh(input);
      if (mesh == null) {
        return null;
      }
      meshes.add(mesh);
    }
    position = childEnd;
    input.setPosition(position);
  }
  return meshes;
}
 
Example 11
Source File: CssParser.java    From Telegram-FOSS with GNU General Public License v2.0 5 votes vote down vote up
/**
 * Reads the contents of ::cue() and returns it as a string.
 */
private static String readCueTarget(ParsableByteArray input) {
  int position = input.getPosition();
  int limit = input.limit();
  boolean cueTargetEndFound = false;
  while (position < limit && !cueTargetEndFound) {
    char c = (char) input.data[position++];
    cueTargetEndFound = c == ')';
  }
  return input.readString(--position - input.getPosition()).trim();
  // --offset to return ')' to the input.
}
 
Example 12
Source File: CssParser.java    From K-Sonic with MIT License 5 votes vote down vote up
/**
 * Reads the contents of ::cue() and returns it as a string.
 */
private static String readCueTarget(ParsableByteArray input) {
  int position = input.getPosition();
  int limit = input.limit();
  boolean cueTargetEndFound = false;
  while (position < limit && !cueTargetEndFound) {
    char c = (char) input.data[position++];
    cueTargetEndFound = c == ')';
  }
  return input.readString(--position - input.getPosition()).trim();
  // --offset to return ')' to the input.
}
 
Example 13
Source File: PsshAtomUtil.java    From MediaSDK with Apache License 2.0 5 votes vote down vote up
/**
 * Parses a PSSH atom. Version 0 and 1 PSSH atoms are supported.
 *
 * @param atom The atom to parse.
 * @return The parsed PSSH atom. Null if the input is not a valid PSSH atom, or if the PSSH atom
 *     has an unsupported version.
 */
// TODO: Support parsing of the key ids for version 1 PSSH atoms.
private static @Nullable PsshAtom parsePsshAtom(byte[] atom) {
  ParsableByteArray atomData = new ParsableByteArray(atom);
  if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) {
    // Data too short.
    return null;
  }
  atomData.setPosition(0);
  int atomSize = atomData.readInt();
  if (atomSize != atomData.bytesLeft() + 4) {
    // Not an atom, or incorrect atom size.
    return null;
  }
  int atomType = atomData.readInt();
  if (atomType != Atom.TYPE_pssh) {
    // Not an atom, or incorrect atom type.
    return null;
  }
  int atomVersion = Atom.parseFullAtomVersion(atomData.readInt());
  if (atomVersion > 1) {
    Log.w(TAG, "Unsupported pssh version: " + atomVersion);
    return null;
  }
  UUID uuid = new UUID(atomData.readLong(), atomData.readLong());
  if (atomVersion == 1) {
    int keyIdCount = atomData.readUnsignedIntToInt();
    atomData.skipBytes(16 * keyIdCount);
  }
  int dataSize = atomData.readUnsignedIntToInt();
  if (dataSize != atomData.bytesLeft()) {
    // Incorrect dataSize.
    return null;
  }
  byte[] data = new byte[dataSize];
  atomData.readBytes(data, 0, dataSize);
  return new PsshAtom(uuid, atomVersion, data);
}
 
Example 14
Source File: PgsDecoder.java    From MediaSDK with Apache License 2.0 5 votes vote down vote up
@Nullable
private static Cue readNextSection(ParsableByteArray buffer, CueBuilder cueBuilder) {
  int limit = buffer.limit();
  int sectionType = buffer.readUnsignedByte();
  int sectionLength = buffer.readUnsignedShort();

  int nextSectionPosition = buffer.getPosition() + sectionLength;
  if (nextSectionPosition > limit) {
    buffer.setPosition(limit);
    return null;
  }

  Cue cue = null;
  switch (sectionType) {
    case SECTION_TYPE_PALETTE:
      cueBuilder.parsePaletteSection(buffer, sectionLength);
      break;
    case SECTION_TYPE_BITMAP_PICTURE:
      cueBuilder.parseBitmapSection(buffer, sectionLength);
      break;
    case SECTION_TYPE_IDENTIFIER:
      cueBuilder.parseIdentifierSection(buffer, sectionLength);
      break;
    case SECTION_TYPE_END:
      cue = cueBuilder.build();
      cueBuilder.reset();
      break;
    default:
      break;
  }

  buffer.setPosition(nextSectionPosition);
  return cue;
}
 
Example 15
Source File: PsshAtomUtil.java    From K-Sonic with MIT License 5 votes vote down vote up
/**
 * Parses the UUID and scheme specific data from a PSSH atom. Version 0 and 1 PSSH atoms are
 * supported.
 *
 * @param atom The atom to parse.
 * @return A pair consisting of the parsed UUID and scheme specific data. Null if the input is
 *     not a valid PSSH atom, or if the PSSH atom has an unsupported version.
 */
private static Pair<UUID, byte[]> parsePsshAtom(byte[] atom) {
  ParsableByteArray atomData = new ParsableByteArray(atom);
  if (atomData.limit() < Atom.FULL_HEADER_SIZE + 16 /* UUID */ + 4 /* DataSize */) {
    // Data too short.
    return null;
  }
  atomData.setPosition(0);
  int atomSize = atomData.readInt();
  if (atomSize != atomData.bytesLeft() + 4) {
    // Not an atom, or incorrect atom size.
    return null;
  }
  int atomType = atomData.readInt();
  if (atomType != Atom.TYPE_pssh) {
    // Not an atom, or incorrect atom type.
    return null;
  }
  int atomVersion = Atom.parseFullAtomVersion(atomData.readInt());
  if (atomVersion > 1) {
    Log.w(TAG, "Unsupported pssh version: " + atomVersion);
    return null;
  }
  UUID uuid = new UUID(atomData.readLong(), atomData.readLong());
  if (atomVersion == 1) {
    int keyIdCount = atomData.readUnsignedIntToInt();
    atomData.skipBytes(16 * keyIdCount);
  }
  int dataSize = atomData.readUnsignedIntToInt();
  if (dataSize != atomData.bytesLeft()) {
    // Incorrect dataSize.
    return null;
  }
  byte[] data = new byte[dataSize];
  atomData.readBytes(data, 0, dataSize);
  return Pair.create(uuid, data);
}
 
Example 16
Source File: PgsDecoder.java    From Telegram-FOSS with GNU General Public License v2.0 5 votes vote down vote up
@Nullable
private static Cue readNextSection(ParsableByteArray buffer, CueBuilder cueBuilder) {
  int limit = buffer.limit();
  int sectionType = buffer.readUnsignedByte();
  int sectionLength = buffer.readUnsignedShort();

  int nextSectionPosition = buffer.getPosition() + sectionLength;
  if (nextSectionPosition > limit) {
    buffer.setPosition(limit);
    return null;
  }

  Cue cue = null;
  switch (sectionType) {
    case SECTION_TYPE_PALETTE:
      cueBuilder.parsePaletteSection(buffer, sectionLength);
      break;
    case SECTION_TYPE_BITMAP_PICTURE:
      cueBuilder.parseBitmapSection(buffer, sectionLength);
      break;
    case SECTION_TYPE_IDENTIFIER:
      cueBuilder.parseIdentifierSection(buffer, sectionLength);
      break;
    case SECTION_TYPE_END:
      cue = cueBuilder.build();
      cueBuilder.reset();
      break;
    default:
      break;
  }

  buffer.setPosition(nextSectionPosition);
  return cue;
}
 
Example 17
Source File: H264Reader.java    From TelePlus-Android with GNU General Public License v2.0 4 votes vote down vote up
@Override
public void consume(ParsableByteArray data) {
  int offset = data.getPosition();
  int limit = data.limit();
  byte[] dataArray = data.data;

  // Append the data to the buffer.
  totalBytesWritten += data.bytesLeft();
  output.sampleData(data, data.bytesLeft());

  // Scan the appended data, processing NAL units as they are encountered
  while (true) {
    int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);

    if (nalUnitOffset == limit) {
      // We've scanned to the end of the data without finding the start of another NAL unit.
      nalUnitData(dataArray, offset, limit);
      return;
    }

    // We've seen the start of a NAL unit of the following type.
    int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset);

    // This is the number of bytes from the current offset to the start of the next NAL unit.
    // It may be negative if the NAL unit started in the previously consumed data.
    int lengthToNalUnit = nalUnitOffset - offset;
    if (lengthToNalUnit > 0) {
      nalUnitData(dataArray, offset, nalUnitOffset);
    }
    int bytesWrittenPastPosition = limit - nalUnitOffset;
    long absolutePosition = totalBytesWritten - bytesWrittenPastPosition;
    // Indicate the end of the previous NAL unit. If the length to the start of the next unit
    // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes
    // when notifying that the unit has ended.
    endNalUnit(absolutePosition, bytesWrittenPastPosition,
        lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs);
    // Indicate the start of the next NAL unit.
    startNalUnit(absolutePosition, nalUnitType, pesTimeUs);
    // Continue scanning the data.
    offset = nalUnitOffset + 3;
  }
}
 
Example 18
Source File: H264Reader.java    From K-Sonic with MIT License 4 votes vote down vote up
@Override
public void consume(ParsableByteArray data) {
  int offset = data.getPosition();
  int limit = data.limit();
  byte[] dataArray = data.data;

  // Append the data to the buffer.
  totalBytesWritten += data.bytesLeft();
  output.sampleData(data, data.bytesLeft());

  // Scan the appended data, processing NAL units as they are encountered
  while (true) {
    int nalUnitOffset = NalUnitUtil.findNalUnit(dataArray, offset, limit, prefixFlags);

    if (nalUnitOffset == limit) {
      // We've scanned to the end of the data without finding the start of another NAL unit.
      nalUnitData(dataArray, offset, limit);
      return;
    }

    // We've seen the start of a NAL unit of the following type.
    int nalUnitType = NalUnitUtil.getNalUnitType(dataArray, nalUnitOffset);

    // This is the number of bytes from the current offset to the start of the next NAL unit.
    // It may be negative if the NAL unit started in the previously consumed data.
    int lengthToNalUnit = nalUnitOffset - offset;
    if (lengthToNalUnit > 0) {
      nalUnitData(dataArray, offset, nalUnitOffset);
    }
    int bytesWrittenPastPosition = limit - nalUnitOffset;
    long absolutePosition = totalBytesWritten - bytesWrittenPastPosition;
    // Indicate the end of the previous NAL unit. If the length to the start of the next unit
    // is negative then we wrote too many bytes to the NAL buffers. Discard the excess bytes
    // when notifying that the unit has ended.
    endNalUnit(absolutePosition, bytesWrittenPastPosition,
        lengthToNalUnit < 0 ? -lengthToNalUnit : 0, pesTimeUs);
    // Indicate the start of the next NAL unit.
    startNalUnit(absolutePosition, nalUnitType, pesTimeUs);
    // Continue scanning the data.
    offset = nalUnitOffset + 3;
  }
}
 
Example 19
Source File: PsBinarySearchSeeker.java    From Telegram with GNU General Public License v2.0 4 votes vote down vote up
/**
 * 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));
  }
}
 
Example 20
Source File: TsBinarySearchSeeker.java    From Telegram-FOSS with GNU General Public License v2.0 4 votes vote down vote up
private TimestampSearchResult searchForPcrValueInBuffer(
    ParsableByteArray packetBuffer, long targetPcrTimeUs, long bufferStartOffset) {
  int limit = packetBuffer.limit();

  long startOfLastPacketPosition = C.POSITION_UNSET;
  long endOfLastPacketPosition = C.POSITION_UNSET;
  long lastPcrTimeUsInRange = C.TIME_UNSET;

  while (packetBuffer.bytesLeft() >= TsExtractor.TS_PACKET_SIZE) {
    int startOfPacket =
        TsUtil.findSyncBytePosition(packetBuffer.data, packetBuffer.getPosition(), limit);
    int endOfPacket = startOfPacket + TsExtractor.TS_PACKET_SIZE;
    if (endOfPacket > limit) {
      break;
    }
    long pcrValue = TsUtil.readPcrFromPacket(packetBuffer, startOfPacket, pcrPid);
    if (pcrValue != C.TIME_UNSET) {
      long pcrTimeUs = pcrTimestampAdjuster.adjustTsTimestamp(pcrValue);
      if (pcrTimeUs > targetPcrTimeUs) {
        if (lastPcrTimeUsInRange == C.TIME_UNSET) {
          // First PCR timestamp is already over target.
          return TimestampSearchResult.overestimatedResult(pcrTimeUs, bufferStartOffset);
        } else {
          // Last PCR timestamp < target timestamp < this timestamp.
          return TimestampSearchResult.targetFoundResult(
              bufferStartOffset + startOfLastPacketPosition);
        }
      } else if (pcrTimeUs + SEEK_TOLERANCE_US > targetPcrTimeUs) {
        long startOfPacketInStream = bufferStartOffset + startOfPacket;
        return TimestampSearchResult.targetFoundResult(startOfPacketInStream);
      }

      lastPcrTimeUsInRange = pcrTimeUs;
      startOfLastPacketPosition = startOfPacket;
    }
    packetBuffer.setPosition(endOfPacket);
    endOfLastPacketPosition = endOfPacket;
  }

  if (lastPcrTimeUsInRange != C.TIME_UNSET) {
    long endOfLastPacketPositionInStream = bufferStartOffset + endOfLastPacketPosition;
    return TimestampSearchResult.underestimatedResult(
        lastPcrTimeUsInRange, endOfLastPacketPositionInStream);
  } else {
    return TimestampSearchResult.NO_TIMESTAMP_IN_RANGE_RESULT;
  }
}