/** 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);
  }
  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);
  }
  /**
   * 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();
  }
  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;
  }
  /**
   * 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);
  }