@Override
    public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
      // Skip pointer.
      if (payloadUnitStartIndicator) {
        int pointerField = data.readUnsignedByte();
        data.skip(pointerField);
      }

      data.readBytes(patScratch, 3);
      patScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
      int sectionLength = patScratch.readBits(12);
      // transport_stream_id (16), reserved (2), version_number (5), current_next_indicator (1),
      // section_number (8), last_section_number (8)
      data.skip(5);

      int programCount = (sectionLength - 9) / 4;
      for (int i = 0; i < programCount; i++) {
        data.readBytes(patScratch, 4);
        patScratch.skipBits(19); // program_number (16), reserved (3)
        int pid = patScratch.readBits(13);
        tsPayloadReaders.put(pid, new PmtReader());
      }

      // Skip CRC_32.
    }
  // @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 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;
  }
  // @VisibleForTesting
  /* package */ static void appendNumberOfSamples(
      ParsableByteArray buffer, long packetSampleCount) {

    buffer.setLimit(buffer.limit() + 4);
    // The vorbis decoder expects the number of samples in the packet
    // to be appended to the audio data as an int32
    buffer.data[buffer.limit() - 4] = (byte) ((packetSampleCount) & 0xFF);
    buffer.data[buffer.limit() - 3] = (byte) ((packetSampleCount >>> 8) & 0xFF);
    buffer.data[buffer.limit() - 2] = (byte) ((packetSampleCount >>> 16) & 0xFF);
    buffer.data[buffer.limit() - 1] = (byte) ((packetSampleCount >>> 24) & 0xFF);
  }
 /**
  * Continues a read from the provided {@code source} into a given {@code target}. It's assumed
  * that the data should be written into {@code target} starting from an offset of zero.
  *
  * @param source The source from which to read.
  * @param target The target into which data is to be read, or {@code null} to skip.
  * @param targetLength The target length of the read.
  * @return Whether the target length has been reached.
  */
 private boolean continueRead(ParsableByteArray source, byte[] target, int targetLength) {
   int bytesToRead = Math.min(source.bytesLeft(), targetLength - bytesRead);
   if (bytesToRead <= 0) {
     return true;
   } else if (target == null) {
     source.skip(bytesToRead);
   } else {
     source.readBytes(target, bytesRead, bytesToRead);
   }
   bytesRead += bytesToRead;
   return bytesRead == targetLength;
 }
 /** Parses a trex atom (defined in 14496-12). */
 private static DefaultSampleValues parseTrex(ParsableByteArray trex) {
   trex.setPosition(Atom.FULL_HEADER_SIZE + 4);
   int defaultSampleDescriptionIndex = trex.readUnsignedIntToInt() - 1;
   int defaultSampleDuration = trex.readUnsignedIntToInt();
   int defaultSampleSize = trex.readUnsignedIntToInt();
   int defaultSampleFlags = trex.readInt();
   return new DefaultSampleValues(
       defaultSampleDescriptionIndex,
       defaultSampleDuration,
       defaultSampleSize,
       defaultSampleFlags);
 }
  private static void parseUuid(
      ParsableByteArray uuid, TrackFragment out, byte[] extendedTypeScratch)
      throws ParserException {
    uuid.setPosition(Atom.HEADER_SIZE);
    uuid.readBytes(extendedTypeScratch, 0, 16);

    // Currently this parser only supports Microsoft's PIFF SampleEncryptionBox.
    if (!Arrays.equals(extendedTypeScratch, PIFF_SAMPLE_ENCRYPTION_BOX_EXTENDED_TYPE)) {
      return;
    }

    // Except for the extended type, this box is identical to a SENC box. See "Portable encoding of
    // audio-video objects: The Protected Interoperable File Format (PIFF), John A. Bocharov et al,
    // Section 5.3.2.1."
    parseSenc(uuid, 16, out);
  }
 @Override
 public void seek() {
   oggReader.reset();
   previousPacketBlockSize = -1;
   elapsedSamples = 0;
   seenFirstAudioPacket = false;
   scratch.reset();
 }
  private static void parseSenc(ParsableByteArray senc, int offset, TrackFragment out)
      throws ParserException {
    senc.setPosition(Atom.HEADER_SIZE + offset);
    int fullAtom = senc.readInt();
    int flags = Atom.parseFullAtomFlags(fullAtom);

    if ((flags & 0x01 /* override_track_encryption_box_parameters */) != 0) {
      // TODO: Implement this.
      throw new ParserException("Overriding TrackEncryptionBox parameters is unsupported.");
    }

    boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
    int sampleCount = senc.readUnsignedIntToInt();
    if (sampleCount != out.length) {
      throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length);
    }

    Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
    out.initEncryptionData(senc.bytesLeft());
    out.fillEncryptionData(senc);
  }
Beispiel #10
0
 @Override
 public void consume(ParsableByteArray data) {
   if (!writingSample) {
     return;
   }
   int bytesAvailable = data.bytesLeft();
   if (sampleBytesRead < ID3_HEADER_SIZE) {
     // We're still reading the ID3 header.
     int headerBytesAvailable = Math.min(bytesAvailable, ID3_HEADER_SIZE - sampleBytesRead);
     System.arraycopy(
         data.data, data.getPosition(), id3Header.data, sampleBytesRead, headerBytesAvailable);
     if (sampleBytesRead + headerBytesAvailable == ID3_HEADER_SIZE) {
       // We've finished reading the ID3 header. Extract the sample size.
       id3Header.setPosition(6); // 'ID3' (3) + version (2) + flags (1)
       sampleSize = ID3_HEADER_SIZE + id3Header.readSynchSafeInt();
     }
   }
   // Write data to the output.
   output.sampleData(data, bytesAvailable);
   sampleBytesRead += bytesAvailable;
 }
  private static void parseSaiz(
      TrackEncryptionBox encryptionBox, ParsableByteArray saiz, TrackFragment out)
      throws ParserException {
    int vectorSize = encryptionBox.initializationVectorSize;
    saiz.setPosition(Atom.HEADER_SIZE);
    int fullAtom = saiz.readInt();
    int flags = Atom.parseFullAtomFlags(fullAtom);
    if ((flags & 0x01) == 1) {
      saiz.skipBytes(8);
    }
    int defaultSampleInfoSize = saiz.readUnsignedByte();

    int sampleCount = saiz.readUnsignedIntToInt();
    if (sampleCount != out.length) {
      throw new ParserException("Length mismatch: " + sampleCount + ", " + out.length);
    }

    int totalSize = 0;
    if (defaultSampleInfoSize == 0) {
      boolean[] sampleHasSubsampleEncryptionTable = out.sampleHasSubsampleEncryptionTable;
      for (int i = 0; i < sampleCount; i++) {
        int sampleInfoSize = saiz.readUnsignedByte();
        totalSize += sampleInfoSize;
        sampleHasSubsampleEncryptionTable[i] = sampleInfoSize > vectorSize;
      }
    } else {
      boolean subsampleEncryption = defaultSampleInfoSize > vectorSize;
      totalSize += defaultSampleInfoSize * sampleCount;
      Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
    }
    out.initEncryptionData(totalSize);
  }
  private int appendSampleEncryptionData(ParsableByteArray sampleEncryptionData) {
    int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
    TrackEncryptionBox encryptionBox =
        track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex];
    int vectorSize = encryptionBox.initializationVectorSize;
    boolean subsampleEncryption = fragmentRun.sampleHasSubsampleEncryptionTable[sampleIndex];

    // Write the signal byte, containing the vector size and the subsample encryption flag.
    encryptionSignalByte.data[0] = (byte) (vectorSize | (subsampleEncryption ? 0x80 : 0));
    encryptionSignalByte.setPosition(0);
    trackOutput.sampleData(encryptionSignalByte, 1);
    // Write the vector.
    trackOutput.sampleData(sampleEncryptionData, vectorSize);
    // If we don't have subsample encryption data, we're done.
    if (!subsampleEncryption) {
      return 1 + vectorSize;
    }
    // Write the subsample encryption data.
    int subsampleCount = sampleEncryptionData.readUnsignedShort();
    sampleEncryptionData.skipBytes(-2);
    int subsampleDataLength = 2 + 6 * subsampleCount;
    trackOutput.sampleData(sampleEncryptionData, subsampleDataLength);
    return 1 + vectorSize + subsampleDataLength;
  }
  @Override
  public int read(DataSource dataSource) throws IOException {
    int bytesRead =
        dataSource.read(tsPacketBuffer.data, tsPacketBytesRead, TS_PACKET_SIZE - tsPacketBytesRead);
    if (bytesRead == -1) {
      return -1;
    }

    tsPacketBytesRead += bytesRead;
    if (tsPacketBytesRead < TS_PACKET_SIZE) {
      // We haven't read the whole packet yet.
      return bytesRead;
    }

    // Reset before reading the packet.
    tsPacketBytesRead = 0;
    tsPacketBuffer.setPosition(0);
    tsPacketBuffer.setLimit(TS_PACKET_SIZE);

    int syncByte = tsPacketBuffer.readUnsignedByte();
    if (syncByte != TS_SYNC_BYTE) {
      return bytesRead;
    }

    tsPacketBuffer.readBytes(tsScratch, 3);
    tsScratch.skipBits(1); // transport_error_indicator
    boolean payloadUnitStartIndicator = tsScratch.readBit();
    tsScratch.skipBits(1); // transport_priority
    int pid = tsScratch.readBits(13);
    tsScratch.skipBits(2); // transport_scrambling_control
    boolean adaptationFieldExists = tsScratch.readBit();
    boolean payloadExists = tsScratch.readBit();
    // Last 4 bits of scratch are skipped: continuity_counter

    // Skip the adaptation field.
    if (adaptationFieldExists) {
      int adaptationFieldLength = tsPacketBuffer.readUnsignedByte();
      tsPacketBuffer.skip(adaptationFieldLength);
    }

    // Read the payload.
    if (payloadExists) {
      TsPayloadReader payloadReader = tsPayloadReaders.get(pid);
      if (payloadReader != null) {
        payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
      }
    }

    if (!prepared) {
      prepared = checkPrepared();
    }

    return bytesRead;
  }
  /**
   * Parses a tfhd atom (defined in 14496-12).
   *
   * @param extendsDefaults Default sample values from the trex atom.
   * @param tfhd The tfhd atom to parse.
   * @param out The track fragment to populate with data from the tfhd atom.
   */
  private static void parseTfhd(
      DefaultSampleValues extendsDefaults, ParsableByteArray tfhd, TrackFragment out) {
    tfhd.setPosition(Atom.HEADER_SIZE);
    int fullAtom = tfhd.readInt();
    int flags = Atom.parseFullAtomFlags(fullAtom);

    tfhd.skipBytes(4); // trackId
    if ((flags & 0x01 /* base_data_offset_present */) != 0) {
      long baseDataPosition = tfhd.readUnsignedLongToLong();
      out.dataPosition = baseDataPosition;
      out.auxiliaryDataPosition = baseDataPosition;
    }

    int defaultSampleDescriptionIndex =
        ((flags & 0x02 /* default_sample_description_index_present */) != 0)
            ? tfhd.readUnsignedIntToInt() - 1
            : extendsDefaults.sampleDescriptionIndex;
    int defaultSampleDuration =
        ((flags & 0x08 /* default_sample_duration_present */) != 0)
            ? tfhd.readUnsignedIntToInt()
            : extendsDefaults.duration;
    int defaultSampleSize =
        ((flags & 0x10 /* default_sample_size_present */) != 0)
            ? tfhd.readUnsignedIntToInt()
            : extendsDefaults.size;
    int defaultSampleFlags =
        ((flags & 0x20 /* default_sample_flags_present */) != 0)
            ? tfhd.readUnsignedIntToInt()
            : extendsDefaults.flags;
    out.header =
        new DefaultSampleValues(
            defaultSampleDescriptionIndex,
            defaultSampleDuration,
            defaultSampleSize,
            defaultSampleFlags);
  }
  /**
   * Parses a saio atom (defined in 14496-12).
   *
   * @param saio The saio atom to parse.
   * @param out The track fragment to populate with data from the saio atom.
   */
  private static void parseSaio(ParsableByteArray saio, TrackFragment out) throws ParserException {
    saio.setPosition(Atom.HEADER_SIZE);
    int fullAtom = saio.readInt();
    int flags = Atom.parseFullAtomFlags(fullAtom);
    if ((flags & 0x01) == 1) {
      saio.skipBytes(8);
    }

    int entryCount = saio.readUnsignedIntToInt();
    if (entryCount != 1) {
      // We only support one trun element currently, so always expect one entry.
      throw new ParserException("Unexpected saio entry count: " + entryCount);
    }

    int version = Atom.parseFullAtomVersion(fullAtom);
    out.auxiliaryDataPosition +=
        version == 0 ? saio.readUnsignedInt() : saio.readUnsignedLongToLong();
  }
    @Override
    public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
      // Skip pointer.
      if (payloadUnitStartIndicator) {
        int pointerField = data.readUnsignedByte();
        data.skip(pointerField);
      }

      data.readBytes(pmtScratch, 3);
      pmtScratch.skipBits(12); // table_id (8), section_syntax_indicator (1), '0' (1), reserved (2)
      int sectionLength = pmtScratch.readBits(12);

      // program_number (16), reserved (2), version_number (5), current_next_indicator (1),
      // section_number (8), last_section_number (8), reserved (3), PCR_PID (13)
      // Skip the rest of the PMT header.
      data.skip(7);

      data.readBytes(pmtScratch, 2);
      pmtScratch.skipBits(4);
      int programInfoLength = pmtScratch.readBits(12);

      // Skip the descriptors.
      data.skip(programInfoLength);

      int entriesSize =
          sectionLength
              - 9 /* Size of the rest of the fields before descriptors */
              - programInfoLength
              - 4 /* CRC size */;
      while (entriesSize > 0) {
        data.readBytes(pmtScratch, 5);
        int streamType = pmtScratch.readBits(8);
        pmtScratch.skipBits(3); // reserved
        int elementaryPid = pmtScratch.readBits(13);
        pmtScratch.skipBits(4); // reserved
        int esInfoLength = pmtScratch.readBits(12);

        // Skip the descriptors.
        data.skip(esInfoLength);
        entriesSize -= esInfoLength + 5;

        if (sampleQueues.get(streamType) != null) {
          continue;
        }

        ElementaryStreamReader pesPayloadReader = null;
        switch (streamType) {
          case TS_STREAM_TYPE_AAC:
            pesPayloadReader = new AdtsReader(bufferPool);
            break;
          case TS_STREAM_TYPE_H264:
            SeiReader seiReader = new SeiReader(bufferPool);
            sampleQueues.put(TS_STREAM_TYPE_EIA608, seiReader);
            pesPayloadReader = new H264Reader(bufferPool, seiReader);
            break;
          case TS_STREAM_TYPE_ID3:
            pesPayloadReader = new Id3Reader(bufferPool);
            break;
        }

        if (pesPayloadReader != null) {
          sampleQueues.put(streamType, pesPayloadReader);
          tsPayloadReaders.put(elementaryPid, new PesReader(pesPayloadReader));
        }
      }

      // Skip CRC_32.
    }
  /**
   * Attempts to extract the next sample in the current mdat atom.
   *
   * <p>If there are no more samples in the current mdat atom then the parser state is transitioned
   * to {@link #STATE_READING_ATOM_HEADER} and {@code false} is returned.
   *
   * <p>It is possible for a sample to be extracted in part in the case that an exception is thrown.
   * In this case the method can be called again to extract the remainder of the sample.
   *
   * @param input The {@link ExtractorInput} from which to read data.
   * @return True if a sample was extracted. False otherwise.
   * @throws IOException If an error occurs reading from the input.
   * @throws InterruptedException If the thread is interrupted.
   */
  private boolean readSample(ExtractorInput input) throws IOException, InterruptedException {
    if (sampleIndex == 0) {
      int bytesToSkip = (int) (fragmentRun.dataPosition - input.getPosition());
      checkState(bytesToSkip >= 0, "Offset to sample data was negative.");
      input.skipFully(bytesToSkip);
    }

    if (sampleIndex >= fragmentRun.length) {
      int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
      checkState(bytesToSkip >= 0, "Offset to end of mdat was negative.");
      input.skipFully(bytesToSkip);
      // We've run out of samples in the current mdat atom.
      enterReadingAtomHeaderState();
      return false;
    }

    if (parserState == STATE_READING_SAMPLE_START) {
      sampleSize = fragmentRun.sampleSizeTable[sampleIndex];
      if (fragmentRun.definesEncryptionData) {
        sampleBytesWritten = appendSampleEncryptionData(fragmentRun.sampleEncryptionData);
        sampleSize += sampleBytesWritten;
      } else {
        sampleBytesWritten = 0;
      }
      sampleCurrentNalBytesRemaining = 0;
      parserState = STATE_READING_SAMPLE_CONTINUE;
    }

    if (track.nalUnitLengthFieldLength != -1) {
      // Zero the top three bytes of the array that we'll use to parse nal unit lengths, in case
      // they're only 1 or 2 bytes long.
      byte[] nalLengthData = nalLength.data;
      nalLengthData[0] = 0;
      nalLengthData[1] = 0;
      nalLengthData[2] = 0;
      int nalUnitLengthFieldLength = track.nalUnitLengthFieldLength;
      int nalUnitLengthFieldLengthDiff = 4 - track.nalUnitLengthFieldLength;
      // NAL units are length delimited, but the decoder requires start code delimited units.
      // Loop until we've written the sample to the track output, replacing length delimiters with
      // start codes as we encounter them.
      while (sampleBytesWritten < sampleSize) {
        if (sampleCurrentNalBytesRemaining == 0) {
          // Read the NAL length so that we know where we find the next one.
          input.readFully(nalLength.data, nalUnitLengthFieldLengthDiff, nalUnitLengthFieldLength);
          nalLength.setPosition(0);
          sampleCurrentNalBytesRemaining = nalLength.readUnsignedIntToInt();
          // Write a start code for the current NAL unit.
          nalStartCode.setPosition(0);
          trackOutput.sampleData(nalStartCode, 4);
          sampleBytesWritten += 4;
          sampleSize += nalUnitLengthFieldLengthDiff;
        } else {
          // Write the payload of the NAL unit.
          int writtenBytes = trackOutput.sampleData(input, sampleCurrentNalBytesRemaining, false);
          sampleBytesWritten += writtenBytes;
          sampleCurrentNalBytesRemaining -= writtenBytes;
        }
      }
    } else {
      while (sampleBytesWritten < sampleSize) {
        int writtenBytes = trackOutput.sampleData(input, sampleSize - sampleBytesWritten, false);
        sampleBytesWritten += writtenBytes;
      }
    }

    long sampleTimeUs = fragmentRun.getSamplePresentationTime(sampleIndex) * 1000L;
    int sampleFlags =
        (fragmentRun.definesEncryptionData ? C.SAMPLE_FLAG_ENCRYPTED : 0)
            | (fragmentRun.sampleIsSyncFrameTable[sampleIndex] ? C.SAMPLE_FLAG_SYNC : 0);
    int sampleDescriptionIndex = fragmentRun.header.sampleDescriptionIndex;
    byte[] encryptionKey =
        fragmentRun.definesEncryptionData
            ? track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId
            : null;
    trackOutput.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);

    sampleIndex++;
    parserState = STATE_READING_SAMPLE_START;
    return true;
  }
  /** Parses a sidx atom (defined in 14496-12). */
  private static ChunkIndex parseSidx(ParsableByteArray atom, long inputPosition)
      throws ParserException {
    atom.setPosition(Atom.HEADER_SIZE);
    int fullAtom = atom.readInt();
    int version = Atom.parseFullAtomVersion(fullAtom);

    atom.skipBytes(4);
    long timescale = atom.readUnsignedInt();
    long earliestPresentationTime;
    long offset = inputPosition;
    if (version == 0) {
      earliestPresentationTime = atom.readUnsignedInt();
      offset += atom.readUnsignedInt();
    } else {
      earliestPresentationTime = atom.readUnsignedLongToLong();
      offset += atom.readUnsignedLongToLong();
    }

    atom.skipBytes(2);

    int referenceCount = atom.readUnsignedShort();
    int[] sizes = new int[referenceCount];
    long[] offsets = new long[referenceCount];
    long[] durationsUs = new long[referenceCount];
    long[] timesUs = new long[referenceCount];

    long time = earliestPresentationTime;
    long timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale);
    for (int i = 0; i < referenceCount; i++) {
      int firstInt = atom.readInt();

      int type = 0x80000000 & firstInt;
      if (type != 0) {
        throw new ParserException("Unhandled indirect reference");
      }
      long referenceDuration = atom.readUnsignedInt();

      sizes[i] = 0x7fffffff & firstInt;
      offsets[i] = offset;

      // Calculate time and duration values such that any rounding errors are consistent. i.e. That
      // timesUs[i] + durationsUs[i] == timesUs[i + 1].
      timesUs[i] = timeUs;
      time += referenceDuration;
      timeUs = Util.scaleLargeTimestamp(time, C.MICROS_PER_SECOND, timescale);
      durationsUs[i] = timeUs - timesUs[i];

      atom.skipBytes(4);
      offset += sizes[i];
    }

    return new ChunkIndex(sizes, offsets, durationsUs, timesUs);
  }
 /**
  * Parses a tfdt atom (defined in 14496-12).
  *
  * @return baseMediaDecodeTime The sum of the decode durations of all earlier samples in the
  *     media, expressed in the media's timescale.
  */
 private static long parseTfdt(ParsableByteArray tfdt) {
   tfdt.setPosition(Atom.HEADER_SIZE);
   int fullAtom = tfdt.readInt();
   int version = Atom.parseFullAtomVersion(fullAtom);
   return version == 1 ? tfdt.readUnsignedLongToLong() : tfdt.readUnsignedInt();
 }
    @Override
    public void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
      if (payloadUnitStartIndicator) {
        switch (state) {
          case STATE_FINDING_HEADER:
          case STATE_READING_HEADER:
            // Expected.
            break;
          case STATE_READING_HEADER_EXTENSION:
            Log.w(TAG, "Unexpected start indicator reading extended header");
            break;
          case STATE_READING_BODY:
            // If payloadSize == -1 then the length of the previous packet was unspecified, and so
            // we only know that it's finished now that we've seen the start of the next one. This
            // is expected. If payloadSize != -1, then the length of the previous packet was known,
            // but we didn't receive that amount of data. This is not expected.
            if (payloadSize != -1) {
              Log.w(TAG, "Unexpected start indicator: expected " + payloadSize + " more bytes");
            }
            // Either way, if the body was started, notify the reader that it has now finished.
            if (bodyStarted) {
              pesPayloadReader.packetFinished();
            }
            break;
        }
        setState(STATE_READING_HEADER);
      }

      while (data.bytesLeft() > 0) {
        switch (state) {
          case STATE_FINDING_HEADER:
            data.skip(data.bytesLeft());
            break;
          case STATE_READING_HEADER:
            if (continueRead(data, pesScratch.getData(), HEADER_SIZE)) {
              setState(parseHeader() ? STATE_READING_HEADER_EXTENSION : STATE_FINDING_HEADER);
            }
            break;
          case STATE_READING_HEADER_EXTENSION:
            int readLength = Math.min(MAX_HEADER_EXTENSION_SIZE, extendedHeaderLength);
            // Read as much of the extended header as we're interested in, and skip the rest.
            if (continueRead(data, pesScratch.getData(), readLength)
                && continueRead(data, null, extendedHeaderLength)) {
              parseHeaderExtension();
              bodyStarted = false;
              setState(STATE_READING_BODY);
            }
            break;
          case STATE_READING_BODY:
            readLength = data.bytesLeft();
            int padding = payloadSize == -1 ? 0 : readLength - payloadSize;
            if (padding > 0) {
              readLength -= padding;
              data.setLimit(data.getPosition() + readLength);
            }
            pesPayloadReader.consume(data, timeUs, !bodyStarted);
            bodyStarted = true;
            if (payloadSize != -1) {
              payloadSize -= readLength;
              if (payloadSize == 0) {
                pesPayloadReader.packetFinished();
                setState(STATE_READING_HEADER);
              }
            }
            break;
        }
      }
    }
  /**
   * Parses a trun atom (defined in 14496-12).
   *
   * @param track The corresponding track.
   * @param defaultSampleValues Default sample values.
   * @param decodeTime The decode time.
   * @param trun The trun atom to parse.
   * @param out The {@TrackFragment} into which parsed data should be placed.
   */
  private static void parseTrun(
      Track track,
      DefaultSampleValues defaultSampleValues,
      long decodeTime,
      int workaroundFlags,
      ParsableByteArray trun,
      TrackFragment out) {
    trun.setPosition(Atom.HEADER_SIZE);
    int fullAtom = trun.readInt();
    int flags = Atom.parseFullAtomFlags(fullAtom);

    int sampleCount = trun.readUnsignedIntToInt();
    if ((flags & 0x01 /* data_offset_present */) != 0) {
      out.dataPosition += trun.readInt();
    }

    boolean firstSampleFlagsPresent = (flags & 0x04 /* first_sample_flags_present */) != 0;
    int firstSampleFlags = defaultSampleValues.flags;
    if (firstSampleFlagsPresent) {
      firstSampleFlags = trun.readUnsignedIntToInt();
    }

    boolean sampleDurationsPresent = (flags & 0x100 /* sample_duration_present */) != 0;
    boolean sampleSizesPresent = (flags & 0x200 /* sample_size_present */) != 0;
    boolean sampleFlagsPresent = (flags & 0x400 /* sample_flags_present */) != 0;
    boolean sampleCompositionTimeOffsetsPresent =
        (flags & 0x800 /* sample_composition_time_offsets_present */) != 0;

    out.initTables(sampleCount);
    int[] sampleSizeTable = out.sampleSizeTable;
    int[] sampleCompositionTimeOffsetTable = out.sampleCompositionTimeOffsetTable;
    long[] sampleDecodingTimeTable = out.sampleDecodingTimeTable;
    boolean[] sampleIsSyncFrameTable = out.sampleIsSyncFrameTable;

    long timescale = track.timescale;
    long cumulativeTime = decodeTime;
    boolean workaroundEveryVideoFrameIsSyncFrame =
        track.type == Track.TYPE_vide
            && (workaroundFlags & WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME) != 0;
    for (int i = 0; i < sampleCount; i++) {
      // Use trun values if present, otherwise tfhd, otherwise trex.
      int sampleDuration =
          sampleDurationsPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.duration;
      int sampleSize = sampleSizesPresent ? trun.readUnsignedIntToInt() : defaultSampleValues.size;
      int sampleFlags =
          (i == 0 && firstSampleFlagsPresent)
              ? firstSampleFlags
              : sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags;
      if (sampleCompositionTimeOffsetsPresent) {
        // The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
        // version 0 trun boxes, however a significant number of streams violate the spec and use
        // signed integers instead. It's safe to always parse sample offsets as signed integers
        // here, because unsigned integers will still be parsed correctly (unless their top bit is
        // set, which is never true in practice because sample offsets are always small).
        int sampleOffset = trun.readInt();
        sampleCompositionTimeOffsetTable[i] = (int) ((sampleOffset * 1000) / timescale);
      } else {
        sampleCompositionTimeOffsetTable[i] = 0;
      }
      sampleDecodingTimeTable[i] = Util.scaleLargeTimestamp(cumulativeTime, 1000, timescale);
      sampleSizeTable[i] = sampleSize;
      sampleIsSyncFrameTable[i] =
          ((sampleFlags >> 16) & 0x1) == 0 && (!workaroundEveryVideoFrameIsSyncFrame || i == 0);
      cumulativeTime += sampleDuration;
    }
  }
  private boolean readAtomHeader(ExtractorInput input) throws IOException, InterruptedException {
    if (atomHeaderBytesRead == 0) {
      // Read the standard length atom header.
      if (!input.readFully(atomHeader.data, 0, Atom.HEADER_SIZE, true)) {
        return false;
      }
      atomHeaderBytesRead = Atom.HEADER_SIZE;
      atomHeader.setPosition(0);
      atomSize = atomHeader.readUnsignedInt();
      atomType = atomHeader.readInt();
    }

    if (atomSize == Atom.LONG_SIZE_PREFIX) {
      // Read the extended atom size.
      int headerBytesRemaining = Atom.LONG_HEADER_SIZE - Atom.HEADER_SIZE;
      input.readFully(atomHeader.data, Atom.HEADER_SIZE, headerBytesRemaining);
      atomHeaderBytesRead += headerBytesRemaining;
      atomSize = atomHeader.readUnsignedLongToLong();
    }

    long atomPosition = input.getPosition() - atomHeaderBytesRead;
    if (atomType == Atom.TYPE_moof) {
      // The data positions may be updated when parsing the tfhd/trun.
      fragmentRun.auxiliaryDataPosition = atomPosition;
      fragmentRun.dataPosition = atomPosition;
    }

    if (atomType == Atom.TYPE_mdat) {
      endOfMdatPosition = atomPosition + atomSize;
      if (!haveOutputSeekMap) {
        extractorOutput.seekMap(SeekMap.UNSEEKABLE);
        haveOutputSeekMap = true;
      }
      if (fragmentRun.sampleEncryptionDataNeedsFill) {
        parserState = STATE_READING_ENCRYPTION_DATA;
      } else {
        parserState = STATE_READING_SAMPLE_START;
      }
      return true;
    }

    if (shouldParseContainerAtom(atomType)) {
      long endPosition = input.getPosition() + atomSize - Atom.HEADER_SIZE;
      containerAtoms.add(new ContainerAtom(atomType, endPosition));
      enterReadingAtomHeaderState();
    } else if (shouldParseLeafAtom(atomType)) {
      // We don't support parsing of leaf atoms that define extended atom sizes, or that have
      // lengths greater than Integer.MAX_VALUE.
      checkState(atomHeaderBytesRead == Atom.HEADER_SIZE);
      checkState(atomSize <= Integer.MAX_VALUE);
      atomData = new ParsableByteArray((int) atomSize);
      System.arraycopy(atomHeader.data, 0, atomData.data, 0, Atom.HEADER_SIZE);
      parserState = STATE_READING_ATOM_PAYLOAD;
    } else {
      // We don't support skipping of atoms that have lengths greater than Integer.MAX_VALUE.
      checkState(atomSize <= Integer.MAX_VALUE);
      atomData = null;
      parserState = STATE_READING_ATOM_PAYLOAD;
    }

    return true;
  }