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);
  }
Example #2
0
  public SampleList(TrackBox trackBox) {
    this.isoFile = trackBox.getIsoFile(); // where are we?
    offsets2Sizes = new HashMap<Long, Long>();

    // find all mdats first to be able to use them later with explicitly looking them up
    long currentOffset = 0;
    for (Box b : isoFile.getBoxes()) {
      long currentSize = b.getSize();
      if ("mdat".equals(b.getType())) {
        if (b instanceof MediaDataBox) {
          long contentOffset = currentOffset + ((MediaDataBox) b).getHeader().limit();
          mdatStartCache.put((MediaDataBox) b, contentOffset);
          mdatEndCache.put((MediaDataBox) b, contentOffset + currentSize);
          mdats.add((MediaDataBox) b);
        } else {
          throw new RuntimeException(
              "Sample need to be in mdats and mdats need to be instanceof MediaDataBox");
        }
      }
      currentOffset += currentSize;
    }

    // first we get all sample from the 'normal' MP4 part.
    // if there are none - no problem.

    SampleSizeBox sampleSizeBox = trackBox.getSampleTableBox().getSampleSizeBox();
    ChunkOffsetBox chunkOffsetBox = trackBox.getSampleTableBox().getChunkOffsetBox();
    SampleToChunkBox sampleToChunkBox = trackBox.getSampleTableBox().getSampleToChunkBox();

    if (sampleToChunkBox != null
        && sampleToChunkBox.getEntries().size() > 0
        && chunkOffsetBox != null
        && chunkOffsetBox.getChunkOffsets().length > 0
        && sampleSizeBox != null
        && sampleSizeBox.getSampleCount() > 0) {
      long[] numberOfSamplesInChunk =
          sampleToChunkBox.blowup(chunkOffsetBox.getChunkOffsets().length);
      if (sampleSizeBox.getSampleSize() > 0) {
        // Every sample has the same size!
        // no need to store each size separately
        // this happens when people use raw audio formats in MP4 (are you stupid guys???)
        offsets2Sizes = new DummyMap<Long, Long>(sampleSizeBox.getSampleSize());
        long sampleSize = sampleSizeBox.getSampleSize();
        for (int i = 0; i < numberOfSamplesInChunk.length; i++) {
          long thisChunksNumberOfSamples = numberOfSamplesInChunk[i];
          long sampleOffset = chunkOffsetBox.getChunkOffsets()[i];
          for (int j = 0; j < thisChunksNumberOfSamples; j++) {
            offsets2Sizes.put(sampleOffset, sampleSize);
            sampleOffset += sampleSize;
          }
        }
      } else {
        // the normal case where all samples have different sizes
        int sampleIndex = 0;
        long sampleSizes[] = sampleSizeBox.getSampleSizes();
        for (int i = 0; i < numberOfSamplesInChunk.length; i++) {
          long thisChunksNumberOfSamples = numberOfSamplesInChunk[i];
          long sampleOffset = chunkOffsetBox.getChunkOffsets()[i];
          for (int j = 0; j < thisChunksNumberOfSamples; j++) {
            long sampleSize = sampleSizes[sampleIndex];
            offsets2Sizes.put(sampleOffset, sampleSize);
            sampleOffset += sampleSize;
            sampleIndex++;
          }
        }
      }
    }

    // Next we add all samples from the fragments
    // in most cases - I've never seen it different it's either normal or fragmented.

    List<MovieExtendsBox> movieExtendsBoxes = trackBox.getParent().getBoxes(MovieExtendsBox.class);

    if (movieExtendsBoxes.size() > 0) {
      List<TrackExtendsBox> trackExtendsBoxes =
          movieExtendsBoxes.get(0).getBoxes(TrackExtendsBox.class);
      for (TrackExtendsBox trackExtendsBox : trackExtendsBoxes) {
        if (trackExtendsBox.getTrackId() == trackBox.getTrackHeaderBox().getTrackId()) {
          for (MovieFragmentBox movieFragmentBox :
              trackBox.getIsoFile().getBoxes(MovieFragmentBox.class)) {
            offsets2Sizes.putAll(
                getOffsets(movieFragmentBox, trackBox.getTrackHeaderBox().getTrackId()));
          }
        }
      }
    }

    // We have now a map from all sample offsets to their sizes
  }
  private TrackBox createTrackBox(Track track, Movie movie) {

    LOG.info("Creating Mp4TrackImpl " + track);
    TrackBox trackBox = new TrackBox();
    TrackHeaderBox tkhd = new TrackHeaderBox();
    tkhd.setVersion(1);
    int flags = 0;
    if (track.isEnabled()) {
      flags += 1;
    }

    if (track.isInMovie()) {
      flags += 2;
    }

    if (track.isInPreview()) {
      flags += 4;
    }

    if (track.isInPoster()) {
      flags += 8;
    }
    tkhd.setFlags(flags);

    tkhd.setAlternateGroup(track.getTrackMetaData().getGroup());
    tkhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
    // We need to take edit list box into account in trackheader duration
    // but as long as I don't support edit list boxes it is sufficient to
    // just translate media duration to movie timescale
    tkhd.setDuration(
        getDuration(track) * getTimescale(movie) / track.getTrackMetaData().getTimescale());
    tkhd.setHeight(track.getTrackMetaData().getHeight());
    tkhd.setWidth(track.getTrackMetaData().getWidth());
    tkhd.setLayer(track.getTrackMetaData().getLayer());
    tkhd.setModificationTime(DateHelper.convert(new Date()));
    tkhd.setTrackId(track.getTrackMetaData().getTrackId());
    tkhd.setVolume(track.getTrackMetaData().getVolume());
    trackBox.addBox(tkhd);

    /*
            EditBox edit = new EditBox();
            EditListBox editListBox = new EditListBox();
            editListBox.setEntries(Collections.singletonList(
                    new EditListBox.Entry(editListBox, (long) (track.getTrackMetaData().getStartTime() * getTimescale(movie)), -1, 1)));
            edit.addBox(editListBox);
            trackBox.addBox(edit);
    */

    MediaBox mdia = new MediaBox();
    trackBox.addBox(mdia);
    MediaHeaderBox mdhd = new MediaHeaderBox();
    mdhd.setCreationTime(DateHelper.convert(track.getTrackMetaData().getCreationTime()));
    mdhd.setDuration(getDuration(track));
    mdhd.setTimescale(track.getTrackMetaData().getTimescale());
    mdhd.setLanguage(track.getTrackMetaData().getLanguage());
    mdia.addBox(mdhd);
    HandlerBox hdlr = new HandlerBox();
    mdia.addBox(hdlr);

    hdlr.setHandlerType(track.getHandler());

    MediaInformationBox minf = new MediaInformationBox();
    minf.addBox(track.getMediaHeaderBox());

    // dinf: all these three boxes tell us is that the actual
    // data is in the current file and not somewhere external
    DataInformationBox dinf = new DataInformationBox();
    DataReferenceBox dref = new DataReferenceBox();
    dinf.addBox(dref);
    DataEntryUrlBox url = new DataEntryUrlBox();
    url.setFlags(1);
    dref.addBox(url);
    minf.addBox(dinf);
    //

    SampleTableBox stbl = new SampleTableBox();

    stbl.addBox(track.getSampleDescriptionBox());

    List<TimeToSampleBox.Entry> decodingTimeToSampleEntries = track.getDecodingTimeEntries();
    if (decodingTimeToSampleEntries != null && !track.getDecodingTimeEntries().isEmpty()) {
      TimeToSampleBox stts = new TimeToSampleBox();
      stts.setEntries(track.getDecodingTimeEntries());
      stbl.addBox(stts);
    }

    List<CompositionTimeToSample.Entry> compositionTimeToSampleEntries =
        track.getCompositionTimeEntries();
    if (compositionTimeToSampleEntries != null && !compositionTimeToSampleEntries.isEmpty()) {
      CompositionTimeToSample ctts = new CompositionTimeToSample();
      ctts.setEntries(compositionTimeToSampleEntries);
      stbl.addBox(ctts);
    }

    long[] syncSamples = track.getSyncSamples();
    if (syncSamples != null && syncSamples.length > 0) {
      SyncSampleBox stss = new SyncSampleBox();
      stss.setSampleNumber(syncSamples);
      stbl.addBox(stss);
    }

    if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) {
      SampleDependencyTypeBox sdtp = new SampleDependencyTypeBox();
      sdtp.setEntries(track.getSampleDependencies());
      stbl.addBox(sdtp);
    }
    HashMap<Track, int[]> track2ChunkSizes = new HashMap<Track, int[]>();
    for (Track current : movie.getTracks()) {
      track2ChunkSizes.put(current, getChunkSizes(current, movie));
    }
    int[] tracksChunkSizes = track2ChunkSizes.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);

    SampleSizeBox stsz = new SampleSizeBox();
    stsz.setSampleSizes(track2SampleSizes.get(track));

    stbl.addBox(stsz);
    // 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.
    StaticChunkOffsetBox stco = new StaticChunkOffsetBox();
    this.chunkOffsetBoxes.add(stco);
    long offset = 0;
    long[] chunkOffset = new long[tracksChunkSizes.length];
    // all tracks have the same number of chunks
    if (LOG.isLoggable(Level.FINE)) {
      LOG.fine("Calculating chunk offsets for track_" + track.getTrackMetaData().getTrackId());
    }

    for (int i = 0; i < tracksChunkSizes.length; i++) {
      // The filelayout will be:
      // chunk_1_track_1,... ,chunk_1_track_n, chunk_2_track_1,... ,chunk_2_track_n, ... ,
      // chunk_m_track_1,... ,chunk_m_track_n
      // calculating the offsets
      if (LOG.isLoggable(Level.FINER)) {
        LOG.finer(
            "Calculating chunk offsets for track_"
                + track.getTrackMetaData().getTrackId()
                + " chunk "
                + i);
      }
      for (Track current : movie.getTracks()) {
        if (LOG.isLoggable(Level.FINEST)) {
          LOG.finest("Adding offsets of track_" + current.getTrackMetaData().getTrackId());
        }
        int[] chunkSizes = track2ChunkSizes.get(current);
        long firstSampleOfChunk = 0;
        for (int j = 0; j < i; j++) {
          firstSampleOfChunk += chunkSizes[j];
        }
        if (current == track) {
          chunkOffset[i] = offset;
        }
        for (int j = l2i(firstSampleOfChunk); j < firstSampleOfChunk + chunkSizes[i]; j++) {
          offset += track2SampleSizes.get(current)[j];
        }
      }
    }
    stco.setChunkOffsets(chunkOffset);
    stbl.addBox(stco);
    minf.addBox(stbl);
    mdia.addBox(minf);

    return trackBox;
  }