// @VisibleForTesting
  /* package */ VorbisSetup readSetupHeaders(ExtractorInput input, ParsableByteArray scratch)
      throws IOException, InterruptedException {

    if (vorbisIdHeader == null) {
      oggReader.readPacket(input, scratch);
      vorbisIdHeader = VorbisUtil.readVorbisIdentificationHeader(scratch);
      scratch.reset();
    }

    if (commentHeader == null) {
      oggReader.readPacket(input, scratch);
      commentHeader = VorbisUtil.readVorbisCommentHeader(scratch);
      scratch.reset();
    }

    oggReader.readPacket(input, scratch);
    // 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);
    scratch.reset();

    return new VorbisSetup(vorbisIdHeader, commentHeader, setupHeaderData, modes, iLogModes);
  }
 @Override
 public void seek() {
   oggReader.reset();
   previousPacketBlockSize = -1;
   elapsedSamples = 0;
   seenFirstAudioPacket = false;
   scratch.reset();
 }
  @Override
  public int read(ExtractorInput input, PositionHolder seekPosition)
      throws IOException, InterruptedException {

    if (vorbisSetup == null) {
      vorbisSetup = readSetupHeaders(input, scratch);
      ArrayList<byte[]> codecInitialisationData = new ArrayList<>();
      codecInitialisationData.clear();
      codecInitialisationData.add(vorbisSetup.idHeader.data);
      codecInitialisationData.add(vorbisSetup.setupHeaderData);

      long duration =
          input.getLength() == C.LENGTH_UNBOUNDED
              ? C.UNKNOWN_TIME_US
              : input.getLength() * 8000000 / vorbisSetup.idHeader.getApproximateBitrate();
      trackOutput.format(
          MediaFormat.createAudioFormat(
              null,
              MimeTypes.AUDIO_VORBIS,
              this.vorbisSetup.idHeader.bitrateNominal,
              OGG_MAX_SEGMENT_SIZE * 255,
              duration,
              this.vorbisSetup.idHeader.channels,
              (int) this.vorbisSetup.idHeader.sampleRate,
              codecInitialisationData,
              null));
    }
    if (oggReader.readPacket(input, scratch)) {
      // if this is an audio packet...
      if ((scratch.data[0] & 0x01) != 1) {
        // ... we need to decode the block size
        int packetBlockSize = decodeBlockSize(scratch.data[0], vorbisSetup);
        // a packet contains samples produced from overlapping the previous and current frame data
        // (https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-350001.3.2)
        int samplesInPacket =
            seenFirstAudioPacket ? (packetBlockSize + previousPacketBlockSize) / 4 : 0;
        // codec expects the number of samples appended to audio data
        appendNumberOfSamples(scratch, samplesInPacket);

        // calculate time and send audio data to codec
        long timeUs = elapsedSamples * C.MICROS_PER_SECOND / vorbisSetup.idHeader.sampleRate;
        trackOutput.sampleData(scratch, scratch.limit());
        trackOutput.sampleMetadata(timeUs, C.SAMPLE_FLAG_SYNC, scratch.limit(), 0, null);

        // update state in members for next iteration
        seenFirstAudioPacket = true;
        elapsedSamples += samplesInPacket;
        previousPacketBlockSize = packetBlockSize;
      }
      scratch.reset();
      return RESULT_CONTINUE;
    }
    return RESULT_END_OF_INPUT;
  }
 @Override
 public boolean sniff(ExtractorInput input) throws IOException, InterruptedException {
   try {
     OggReader.PageHeader header = new OggReader.PageHeader();
     OggReader.populatePageHeader(input, header, scratch, true);
     if ((header.type & 0x02) != 0x02) {
       throw new ParserException("expected page to be first page of a logical stream");
     }
     input.resetPeekPosition();
   } catch (ParserException e) {
     Log.e(TAG, e.getMessage());
     return false;
   }
   return true;
 }