protected Box createStbl(Track track, Movie movie, Map<Track, int[]> chunks) {
    SampleTableBox stbl = new SampleTableBox();

    createStsd(track, stbl);
    createStts(track, stbl);
    createCtts(track, stbl);
    createStss(track, stbl);
    createSdtp(track, stbl);
    createStsc(track, chunks, stbl);
    createStsz(track, stbl);
    createStco(track, movie, chunks, stbl);

    Map<String, List<GroupEntry>> groupEntryFamilies = new HashMap<String, List<GroupEntry>>();
    for (Map.Entry<GroupEntry, long[]> sg : track.getSampleGroups().entrySet()) {
      String type = sg.getKey().getType();
      List<GroupEntry> groupEntries = groupEntryFamilies.get(type);
      if (groupEntries == null) {
        groupEntries = new ArrayList<GroupEntry>();
        groupEntryFamilies.put(type, groupEntries);
      }
      groupEntries.add(sg.getKey());
    }
    for (Map.Entry<String, List<GroupEntry>> sg : groupEntryFamilies.entrySet()) {
      SampleGroupDescriptionBox sgdb = new SampleGroupDescriptionBox();
      String type = sg.getKey();
      sgdb.setGroupEntries(sg.getValue());
      SampleToGroupBox sbgp = new SampleToGroupBox();
      sbgp.setGroupingType(type);
      SampleToGroupBox.Entry last = null;
      for (int i = 0; i < track.getSamples().size(); i++) {
        int index = 0;
        for (int j = 0; j < sg.getValue().size(); j++) {
          GroupEntry groupEntry = sg.getValue().get(j);
          long[] sampleNums = track.getSampleGroups().get(groupEntry);
          if (Arrays.binarySearch(sampleNums, i) >= 0) {
            index = j + 1;
          }
        }
        if (last == null || last.getGroupDescriptionIndex() != index) {
          last = new SampleToGroupBox.Entry(1, index);
          sbgp.getEntries().add(last);
        } else {
          last.setSampleCount(last.getSampleCount() + 1);
        }
      }
      stbl.addBox(sgdb);
      stbl.addBox(sbgp);
    }

    if (track instanceof CencEncryptedTrack) {
      createCencBoxes((CencEncryptedTrack) track, stbl, chunks.get(track));
    }
    createSubs(track, stbl);

    return stbl;
  }
 protected void createSdtp(Track track, SampleTableBox stbl) {
   if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) {
     SampleDependencyTypeBox sdtp = new SampleDependencyTypeBox();
     sdtp.setEntries(track.getSampleDependencies());
     stbl.addBox(sdtp);
   }
 }
 protected void createStss(Track track, SampleTableBox stbl) {
   long[] syncSamples = track.getSyncSamples();
   if (syncSamples != null && syncSamples.length > 0) {
     SyncSampleBox stss = new SyncSampleBox();
     stss.setSampleNumber(syncSamples);
     stbl.addBox(stss);
   }
 }
 protected void createCtts(Track track, SampleTableBox stbl) {
   List<CompositionTimeToSample.Entry> compositionTimeToSampleEntries =
       track.getCompositionTimeEntries();
   if (compositionTimeToSampleEntries != null && !compositionTimeToSampleEntries.isEmpty()) {
     CompositionTimeToSample ctts = new CompositionTimeToSample();
     ctts.setEntries(compositionTimeToSampleEntries);
     stbl.addBox(ctts);
   }
 }
  protected void createCencBoxes(CencEncryptedTrack track, SampleTableBox stbl, int[] chunkSizes) {

    SampleAuxiliaryInformationSizesBox saiz = new SampleAuxiliaryInformationSizesBox();

    saiz.setAuxInfoType("cenc");
    saiz.setFlags(1);
    List<CencSampleAuxiliaryDataFormat> sampleEncryptionEntries =
        track.getSampleEncryptionEntries();
    if (track.hasSubSampleEncryption()) {
      short[] sizes = new short[sampleEncryptionEntries.size()];
      for (int i = 0; i < sizes.length; i++) {
        sizes[i] = (short) sampleEncryptionEntries.get(i).getSize();
      }
      saiz.setSampleInfoSizes(sizes);
    } else {
      saiz.setDefaultSampleInfoSize(8); // 8 bytes iv
      saiz.setSampleCount(track.getSamples().size());
    }

    SampleAuxiliaryInformationOffsetsBox saio = new SampleAuxiliaryInformationOffsetsBox();
    SampleEncryptionBox senc = new SampleEncryptionBox();
    senc.setSubSampleEncryption(track.hasSubSampleEncryption());
    senc.setEntries(sampleEncryptionEntries);

    long offset = senc.getOffsetToFirstIV();
    int index = 0;
    long[] offsets = new long[chunkSizes.length];

    for (int i = 0; i < chunkSizes.length; i++) {
      offsets[i] = offset;
      for (int j = 0; j < chunkSizes[i]; j++) {
        offset += sampleEncryptionEntries.get(index++).getSize();
      }
    }
    saio.setOffsets(offsets);

    stbl.addBox(saiz);
    stbl.addBox(saio);
    stbl.addBox(senc);
    sampleAuxiliaryInformationOffsetsBoxes.add(saio);
  }
  protected void createStts(Track track, SampleTableBox stbl) {
    TimeToSampleBox.Entry lastEntry = null;
    List<TimeToSampleBox.Entry> entries = new ArrayList<TimeToSampleBox.Entry>();

    for (long delta : track.getSampleDurations()) {
      if (lastEntry != null && lastEntry.getDelta() == delta) {
        lastEntry.setCount(lastEntry.getCount() + 1);
      } else {
        lastEntry = new TimeToSampleBox.Entry(1, delta);
        entries.add(lastEntry);
      }
    }
    TimeToSampleBox stts = new TimeToSampleBox();
    stts.setEntries(entries);
    stbl.addBox(stts);
  }
  protected void createStsc(Track track, Map<Track, int[]> chunks, SampleTableBox stbl) {
    int[] tracksChunkSizes = chunks.get(track);

    SampleToChunkBox stsc = new SampleToChunkBox();
    stsc.setEntries(new LinkedList<SampleToChunkBox.Entry>());
    long lastChunkSize = Integer.MIN_VALUE; // to be sure the first chunks hasn't got the same size
    for (int i = 0; i < tracksChunkSizes.length; i++) {
      // The sample description index references the sample description box
      // that describes the samples of this chunk. My Tracks cannot have more
      // than one sample description box. Therefore 1 is always right
      // the first chunk has the number '1'
      if (lastChunkSize != tracksChunkSizes[i]) {
        stsc.getEntries().add(new SampleToChunkBox.Entry(i + 1, tracksChunkSizes[i], 1));
        lastChunkSize = tracksChunkSizes[i];
      }
    }
    stbl.addBox(stsc);
  }
  protected void createStsz(Track track, SampleTableBox stbl) {
    SampleSizeBox stsz = new SampleSizeBox();
    stsz.setSampleSizes(track2SampleSizes.get(track));

    stbl.addBox(stsz);
  }
  protected void createStco(
      Track targetTrack, Movie movie, Map<Track, int[]> chunks, SampleTableBox stbl) {
    if (chunkOffsetBoxes.get(targetTrack) == null) {
      // The ChunkOffsetBox we create here is just a stub
      // since we haven't created the whole structure we can't tell where the
      // first chunk starts (mdat box). So I just let the chunk offset
      // start at zero and I will add the mdat offset later.

      long offset = 0;
      // all tracks have the same number of chunks
      if (LOG.isLoggable(Level.FINE)) {
        LOG.fine(
            "Calculating chunk offsets for track_" + targetTrack.getTrackMetaData().getTrackId());
      }

      List<Track> tracks = new ArrayList<Track>(chunks.keySet());
      Collections.sort(
          tracks,
          new Comparator<Track>() {
            public int compare(Track o1, Track o2) {
              return l2i(o1.getTrackMetaData().getTrackId() - o2.getTrackMetaData().getTrackId());
            }
          });
      Map<Track, Integer> trackToChunk = new HashMap<Track, Integer>();
      Map<Track, Integer> trackToSample = new HashMap<Track, Integer>();
      Map<Track, Double> trackToTime = new HashMap<Track, Double>();
      for (Track track : tracks) {
        trackToChunk.put(track, 0);
        trackToSample.put(track, 0);
        trackToTime.put(track, 0.0);
        chunkOffsetBoxes.put(track, new StaticChunkOffsetBox());
      }

      while (true) {
        Track nextChunksTrack = null;
        for (Track track : tracks) {
          // This always chooses the least progressed track
          if ((nextChunksTrack == null || trackToTime.get(track) < trackToTime.get(nextChunksTrack))
              &&
              // either first OR track's next chunk's starttime is smaller than nextTrack's next
              // chunks starttime
              // AND their need to be chunks left!
              (trackToChunk.get(track) < chunks.get(track).length)) {
            nextChunksTrack = track;
          }
        }
        if (nextChunksTrack == null) {
          break; // no next
        }
        // found the next one
        ChunkOffsetBox chunkOffsetBox = chunkOffsetBoxes.get(nextChunksTrack);
        chunkOffsetBox.setChunkOffsets(
            Mp4Arrays.copyOfAndAppend(chunkOffsetBox.getChunkOffsets(), offset));

        int nextChunksIndex = trackToChunk.get(nextChunksTrack);

        int numberOfSampleInNextChunk = chunks.get(nextChunksTrack)[nextChunksIndex];
        int startSample = trackToSample.get(nextChunksTrack);
        double time = trackToTime.get(nextChunksTrack);

        for (int j = startSample; j < startSample + numberOfSampleInNextChunk; j++) {
          offset += track2SampleSizes.get(nextChunksTrack)[j];
          time +=
              (double) nextChunksTrack.getSampleDurations()[j]
                  / nextChunksTrack.getTrackMetaData().getTimescale();
        }
        trackToChunk.put(nextChunksTrack, nextChunksIndex + 1);
        trackToSample.put(nextChunksTrack, startSample + numberOfSampleInNextChunk);
        trackToTime.put(nextChunksTrack, time);
      }
    }

    stbl.addBox(chunkOffsetBoxes.get(targetTrack));
  }
 protected void createStsd(Track track, SampleTableBox stbl) {
   stbl.addBox(track.getSampleDescriptionBox());
 }
 protected void createSubs(Track track, SampleTableBox stbl) {
   if (track.getSubsampleInformationBox() != null) {
     stbl.addBox(track.getSubsampleInformationBox());
   }
 }