All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.googlecode.mp4parser.authoring.builder.FragmentedMp4Builder Maven / Gradle / Ivy

Go to download

A generic parser and writer for all ISO 14496 based files (MP4, Quicktime, DCF, PDCF, ...)

The newest version!
/*
 * Copyright 2012 Sebastian Annies, Hamburg
 *
 * Licensed under the Apache License, Version 2.0 (the License);
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an AS IS BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.mp4parser.authoring.builder;

import com.coremedia.iso.BoxParser;
import com.coremedia.iso.IsoFile;
import com.coremedia.iso.IsoTypeWriter;
import com.coremedia.iso.boxes.*;
import com.coremedia.iso.boxes.fragment.*;
import com.googlecode.mp4parser.BasicContainer;
import com.googlecode.mp4parser.DataSource;
import com.googlecode.mp4parser.authoring.Edit;
import com.googlecode.mp4parser.authoring.Movie;
import com.googlecode.mp4parser.authoring.Sample;
import com.googlecode.mp4parser.authoring.Track;
import com.googlecode.mp4parser.authoring.tracks.CencEncryptedTrack;
import com.googlecode.mp4parser.boxes.dece.SampleEncryptionBox;
import com.googlecode.mp4parser.boxes.mp4.samplegrouping.GroupEntry;
import com.googlecode.mp4parser.boxes.mp4.samplegrouping.SampleGroupDescriptionBox;
import com.googlecode.mp4parser.boxes.mp4.samplegrouping.SampleToGroupBox;
import com.googlecode.mp4parser.util.Path;
import com.mp4parser.iso14496.part12.SampleAuxiliaryInformationOffsetsBox;
import com.mp4parser.iso14496.part12.SampleAuxiliaryInformationSizesBox;
import com.mp4parser.iso23001.part7.CencSampleAuxiliaryDataFormat;
import com.mp4parser.iso23001.part7.TrackEncryptionBox;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.util.*;
import java.util.logging.Logger;

import static com.googlecode.mp4parser.util.CastUtils.l2i;

/**
 * Creates a fragmented MP4 file.
 */
public class FragmentedMp4Builder implements Mp4Builder {
    private static final Logger LOG = Logger.getLogger(FragmentedMp4Builder.class.getName());

    protected Fragmenter fragmenter;

    public FragmentedMp4Builder() {
    }

    public Date getDate() {
        return new Date();
    }

    public Box createFtyp(Movie movie) {
        List minorBrands = new LinkedList();
        minorBrands.add("mp42");
        minorBrands.add("iso6");
        minorBrands.add("avc1");
        minorBrands.add("isom");
        return new FileTypeBox("iso6", 1, minorBrands);
    }

    /**
     * Sorts fragments by start time.
     *
     * @param tracks          the list of tracks to returned sorted
     * @param cycle           current fragment (sorting may vary between the fragments)
     * @param intersectionMap a map from tracks to their fragments' first samples.
     * @return the list of tracks in order of appearance in the fragment
     */
    protected List sortTracksInSequence(List tracks, final int cycle, final Map intersectionMap) {
        tracks = new LinkedList(tracks);
        Collections.sort(tracks, new Comparator() {
            public int compare(Track o1, Track o2) {
                long startSample1 = intersectionMap.get(o1)[cycle];
                // one based sample numbers - the first sample is 1
                long startSample2 = intersectionMap.get(o2)[cycle];
                // one based sample numbers - the first sample is 1

                // now let's get the start times
                long[] decTimes1 = o1.getSampleDurations();
                long[] decTimes2 = o2.getSampleDurations();
                long startTime1 = 0;
                long startTime2 = 0;

                for (int i = 1; i < startSample1; i++) {
                    startTime1 += decTimes1[i - 1];
                }
                for (int i = 1; i < startSample2; i++) {
                    startTime2 += decTimes2[i - 1];
                }

                // and compare
                return (int) (((double) startTime1 / o1.getTrackMetaData().getTimescale() - (double) startTime2 / o2.getTrackMetaData().getTimescale()) * 100);
            }
        });
        return tracks;
    }


    protected List createMoofMdat(final Movie movie) {
        List moofsMdats = new LinkedList();
        HashMap intersectionMap = new HashMap();
        HashMap track2currentTime = new HashMap();

        for (Track track : movie.getTracks()) {
            long[] intersects = fragmenter.sampleNumbers(track);
            intersectionMap.put(track, intersects);
            track2currentTime.put(track, 0.0);

        }

        int sequence = 1;
        while (!intersectionMap.isEmpty()) {
            Track earliestTrack = null;
            double earliestTime = Double.MAX_VALUE;
            for (Map.Entry trackEntry : track2currentTime.entrySet()) {
                if (trackEntry.getValue() < earliestTime) {
                    earliestTime = trackEntry.getValue();
                    earliestTrack = trackEntry.getKey();
                }
            }
            assert earliestTrack != null;

            long[] startSamples = intersectionMap.get(earliestTrack);
            long startSample = startSamples[0];
            long endSample = startSamples.length > 1 ? startSamples[1] : earliestTrack.getSamples().size() + 1;

            long[] times = earliestTrack.getSampleDurations();
            long timscale = earliestTrack.getTrackMetaData().getTimescale();
            for (long i = startSample; i < endSample; i++) {
                earliestTime += (double) times[l2i(i-1)] / timscale;
            }
            createFragment(moofsMdats, earliestTrack, startSample, endSample, sequence);

            if (startSamples.length == 1) {
                intersectionMap.remove(earliestTrack);
                track2currentTime.remove(earliestTrack);
                // all sample written.
            } else {
                long[] nuStartSamples = new long[startSamples.length - 1];
                System.arraycopy(startSamples, 1, nuStartSamples, 0, nuStartSamples.length);
                intersectionMap.put(earliestTrack, nuStartSamples);
                track2currentTime.put(earliestTrack, earliestTime);
            }


            sequence++;

        }

      /* sequence = 1;
        // this loop has two indices:

        for (int cycle = 0; cycle < maxNumberOfFragments; cycle++) {

            final List sortedTracks = sortTracksInSequence(movie.getTracks(), cycle, intersectionMap);

            for (Track track : sortedTracks) {
                long[] startSamples = intersectionMap.get(track);
                long startSample = startSamples[cycle];
                // one based sample numbers - the first sample is 1
                long endSample = cycle + 1 < startSamples.length ? startSamples[cycle + 1] : track.getSamples().size() + 1;
                createFragment(moofsMdats, track, startSample, endSample, sequence);
                sequence++;
            }
        }*/
        return moofsMdats;
    }

    protected int createFragment(List moofsMdats, Track track, long startSample, long endSample, int sequence) {


        // if startSample == endSample the cycle is empty!
        if (startSample != endSample) {
            moofsMdats.add(createMoof(startSample, endSample, track, sequence));
            moofsMdats.add(createMdat(startSample, endSample, track, sequence));
        }
        return sequence;
    }

    /**
     * {@inheritDoc}
     */
    public Container build(Movie movie) {
        LOG.fine("Creating movie " + movie);
        if (fragmenter == null) {
            fragmenter = new BetterFragmenter(2);
        }
        BasicContainer isoFile = new BasicContainer();


        isoFile.addBox(createFtyp(movie));
        //isoFile.addBox(createPdin(movie));
        isoFile.addBox(createMoov(movie));

        for (Box box : createMoofMdat(movie)) {
            isoFile.addBox(box);
        }
        isoFile.addBox(createMfra(movie, isoFile));

        return isoFile;
    }

    protected Box createMdat(final long startSample, final long endSample, final Track track, final int i) {

        class Mdat implements Box {
            Container parent;
            long size_ = -1;

            public Container getParent() {
                return parent;
            }

            public void setParent(Container parent) {
                this.parent = parent;
            }

            public long getOffset() {
                throw new RuntimeException("Doesn't have any meaning for programmatically created boxes");
            }

            public long getSize() {
                if (size_ != -1) return size_;
                long size = 8; // I don't expect 2gig fragments
                for (Sample sample : getSamples(startSample, endSample, track)) {
                    size += sample.getSize();
                }
                size_ = size;
                return size;
            }

            public String getType() {
                return "mdat";
            }

            public void getBox(WritableByteChannel writableByteChannel) throws IOException {
                ByteBuffer header = ByteBuffer.allocate(8);
                IsoTypeWriter.writeUInt32(header, l2i(getSize()));
                header.put(IsoFile.fourCCtoBytes(getType()));
                header.rewind();
                writableByteChannel.write(header);

                List samples = getSamples(startSample, endSample, track);
                for (Sample sample : samples) {
                    sample.writeTo(writableByteChannel);
                }


            }

            public void parse(DataSource fileChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {

            }
        }

        return new Mdat();
    }

    protected void createTfhd(long startSample, long endSample, Track track, int sequenceNumber, TrackFragmentBox parent) {
        TrackFragmentHeaderBox tfhd = new TrackFragmentHeaderBox();
        SampleFlags sf = new SampleFlags();

        tfhd.setDefaultSampleFlags(sf);
        tfhd.setBaseDataOffset(-1);
        tfhd.setTrackId(track.getTrackMetaData().getTrackId());
        tfhd.setDefaultBaseIsMoof(true);
        parent.addBox(tfhd);
    }

    protected void createMfhd(long startSample, long endSample, Track track, int sequenceNumber, MovieFragmentBox parent) {
        MovieFragmentHeaderBox mfhd = new MovieFragmentHeaderBox();
        mfhd.setSequenceNumber(sequenceNumber);
        parent.addBox(mfhd);
    }

    protected void createTraf(long startSample, long endSample, Track track, int sequenceNumber, MovieFragmentBox parent) {
        TrackFragmentBox traf = new TrackFragmentBox();
        parent.addBox(traf);
        createTfhd(startSample, endSample, track, sequenceNumber, traf);
        createTfdt(startSample, track, traf);
        createTrun(startSample, endSample, track, sequenceNumber, traf);

        if (track instanceof CencEncryptedTrack) {
            createSaiz(startSample, endSample, (CencEncryptedTrack) track, sequenceNumber, traf);
            createSenc(startSample, endSample, (CencEncryptedTrack) track, sequenceNumber, traf);
            createSaio(startSample, endSample, (CencEncryptedTrack) track, sequenceNumber, traf);
        }


        Map> groupEntryFamilies = new HashMap>();
        for (Map.Entry sg : track.getSampleGroups().entrySet()) {
            String type = sg.getKey().getType();
            List groupEntries = groupEntryFamilies.get(type);
            if (groupEntries == null) {
                groupEntries = new ArrayList();
                groupEntryFamilies.put(type, groupEntries);
            }
            groupEntries.add(sg.getKey());
        }


        for (Map.Entry> sg : groupEntryFamilies.entrySet()) {
            SampleGroupDescriptionBox sgpd = new SampleGroupDescriptionBox();
            String type = sg.getKey();
            sgpd.setGroupEntries(sg.getValue());
            sgpd.setGroupingType(type);
            SampleToGroupBox sbgp = new SampleToGroupBox();
            sbgp.setGroupingType(type);
            SampleToGroupBox.Entry last = null;
            for (int i = l2i(startSample - 1); i < l2i(endSample - 1); 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 + 0x10001;
                    }
                }
                if (last == null || last.getGroupDescriptionIndex() != index) {
                    last = new SampleToGroupBox.Entry(1, index);
                    sbgp.getEntries().add(last);
                } else {
                    last.setSampleCount(last.getSampleCount() + 1);
                }
            }
            traf.addBox(sgpd);
            traf.addBox(sbgp);
        }


    }

    protected void createSenc(long startSample, long endSample, CencEncryptedTrack track, int sequenceNumber, TrackFragmentBox parent) {
        SampleEncryptionBox senc = new SampleEncryptionBox();
        senc.setSubSampleEncryption(track.hasSubSampleEncryption());
        senc.setEntries(track.getSampleEncryptionEntries().subList(l2i(startSample - 1), l2i(endSample - 1)));
        parent.addBox(senc);
    }

    protected void createSaio(long startSample, long endSample, CencEncryptedTrack track, int sequenceNumber, TrackFragmentBox parent) {
        SchemeTypeBox schm = Path.getPath(track.getSampleDescriptionBox(), "enc.[0]/sinf[0]/schm[0]");

        SampleAuxiliaryInformationOffsetsBox saio = new SampleAuxiliaryInformationOffsetsBox();
        parent.addBox(saio);
        assert parent.getBoxes(TrackRunBox.class).size() == 1 : "Don't know how to deal with multiple Track Run Boxes when encrypting";
        saio.setAuxInfoType("cenc");
        saio.setFlags(1);
        long offset = 0;
        offset += 8; // traf header till 1st child box
        for (Box box : parent.getBoxes()) {
            if (box instanceof SampleEncryptionBox) {
                offset += ((SampleEncryptionBox) box).getOffsetToFirstIV();
                break;
            } else {
                offset += box.getSize();
            }
        }
        MovieFragmentBox moof = (MovieFragmentBox) parent.getParent();
        offset += 16; // traf header till 1st child box
        for (Box box : moof.getBoxes()) {
            if (box == parent) {
                break;
            } else {
                offset += box.getSize();
            }

        }

        saio.setOffsets(new long[]{offset});

    }

    protected void createSaiz(long startSample, long endSample, CencEncryptedTrack track, int sequenceNumber, TrackFragmentBox parent) {
        SampleDescriptionBox sampleDescriptionBox = track.getSampleDescriptionBox();
        SchemeTypeBox schm = Path.getPath(sampleDescriptionBox, "enc.[0]/sinf[0]/schm[0]");
        TrackEncryptionBox tenc = Path.getPath(sampleDescriptionBox, "enc.[0]/sinf[0]/schi[0]/tenc[0]");

        SampleAuxiliaryInformationSizesBox saiz = new SampleAuxiliaryInformationSizesBox();
        saiz.setAuxInfoType("cenc");
        saiz.setFlags(1);
        if (track.hasSubSampleEncryption()) {
            short[] sizes = new short[l2i(endSample - startSample)];
            List auxs =
                    track.getSampleEncryptionEntries().subList(l2i(startSample - 1), l2i(endSample - 1));
            for (int i = 0; i < sizes.length; i++) {
                sizes[i] = (short) auxs.get(i).getSize();
            }
            saiz.setSampleInfoSizes(sizes);
        } else {
            saiz.setDefaultSampleInfoSize(tenc.getDefaultIvSize());
            saiz.setSampleCount(l2i(endSample - startSample));
        }
        parent.addBox(saiz);
    }


    /**
     * Gets all samples starting with startSample (one based -> one is the first) and
     * ending with endSample (exclusive).
     *
     * @param startSample low endpoint (inclusive) of the sample sequence
     * @param endSample   high endpoint (exclusive) of the sample sequence
     * @param track       source of the samples
     * @return a List<Sample> of raw samples
     */
    protected List getSamples(long startSample, long endSample, Track track) {
        // since startSample and endSample are one-based substract 1 before addressing list elements
        return track.getSamples().subList(l2i(startSample) - 1, l2i(endSample) - 1);
    }

    /**
     * Gets the sizes of a sequence of samples.
     *
     * @param startSample    low endpoint (inclusive) of the sample sequence
     * @param endSample      high endpoint (exclusive) of the sample sequence
     * @param track          source of the samples
     * @param sequenceNumber the fragment index of the requested list of samples
     * @return the sample sizes in the given interval
     */
    protected long[] getSampleSizes(long startSample, long endSample, Track track, int sequenceNumber) {
        List samples = getSamples(startSample, endSample, track);

        long[] sampleSizes = new long[samples.size()];
        for (int i = 0; i < sampleSizes.length; i++) {
            sampleSizes[i] = samples.get(i).getSize();
        }
        return sampleSizes;
    }

    protected void createTfdt(long startSample, Track track, TrackFragmentBox parent) {
        TrackFragmentBaseMediaDecodeTimeBox tfdt = new TrackFragmentBaseMediaDecodeTimeBox();
        tfdt.setVersion(1);
        long startTime = 0;
        long[] times = track.getSampleDurations();
        for (int i = 1; i < startSample; i++) {
            startTime += times[i - 1];
        }
        tfdt.setBaseMediaDecodeTime(startTime);
        parent.addBox(tfdt);
    }

    /**
     * Creates one or more track run boxes for a given sequence.
     *
     * @param startSample    low endpoint (inclusive) of the sample sequence
     * @param endSample      high endpoint (exclusive) of the sample sequence
     * @param track          source of the samples
     * @param sequenceNumber the fragment index of the requested list of samples
     * @param parent         the created box must be added to this box
     */
    protected void createTrun(long startSample, long endSample, Track track, int sequenceNumber, TrackFragmentBox parent) {
        TrackRunBox trun = new TrackRunBox();
        trun.setVersion(1);
        long[] sampleSizes = getSampleSizes(startSample, endSample, track, sequenceNumber);

        trun.setSampleDurationPresent(true);
        trun.setSampleSizePresent(true);
        List entries = new ArrayList(l2i(endSample - startSample));


        List compositionTimeEntries = track.getCompositionTimeEntries();
        int compositionTimeQueueIndex = 0;
        CompositionTimeToSample.Entry[] compositionTimeQueue =
                compositionTimeEntries != null && compositionTimeEntries.size() > 0 ?
                        compositionTimeEntries.toArray(new CompositionTimeToSample.Entry[compositionTimeEntries.size()]) : null;
        long compositionTimeEntriesLeft = compositionTimeQueue != null ? compositionTimeQueue[compositionTimeQueueIndex].getCount() : -1;


        trun.setSampleCompositionTimeOffsetPresent(compositionTimeEntriesLeft > 0);

        // fast forward composition stuff
        for (long i = 1; i < startSample; i++) {
            if (compositionTimeQueue != null) {
                //trun.setSampleCompositionTimeOffsetPresent(true);
                if (--compositionTimeEntriesLeft == 0 && (compositionTimeQueue.length - compositionTimeQueueIndex) > 1) {
                    compositionTimeQueueIndex++;
                    compositionTimeEntriesLeft = compositionTimeQueue[compositionTimeQueueIndex].getCount();
                }
            }
        }

        boolean sampleFlagsRequired = (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty() ||
                track.getSyncSamples() != null && track.getSyncSamples().length != 0);

        trun.setSampleFlagsPresent(sampleFlagsRequired);

        for (int i = 0; i < sampleSizes.length; i++) {
            TrackRunBox.Entry entry = new TrackRunBox.Entry();
            entry.setSampleSize(sampleSizes[i]);
            if (sampleFlagsRequired) {
                //if (false) {
                SampleFlags sflags = new SampleFlags();

                if (track.getSampleDependencies() != null && !track.getSampleDependencies().isEmpty()) {
                    SampleDependencyTypeBox.Entry e = track.getSampleDependencies().get(i);
                    sflags.setSampleDependsOn(e.getSampleDependsOn());
                    sflags.setSampleIsDependedOn(e.getSampleIsDependentOn());
                    sflags.setSampleHasRedundancy(e.getSampleHasRedundancy());
                }
                if (track.getSyncSamples() != null && track.getSyncSamples().length > 0) {
                    // we have to mark non-sync samples!
                    if (Arrays.binarySearch(track.getSyncSamples(), startSample + i) >= 0) {
                        sflags.setSampleIsDifferenceSample(false);
                        sflags.setSampleDependsOn(2);
                    } else {
                        sflags.setSampleIsDifferenceSample(true);
                        sflags.setSampleDependsOn(1);
                    }
                }
                // i don't have sample degradation
                entry.setSampleFlags(sflags);

            }

            entry.setSampleDuration(track.getSampleDurations()[l2i(startSample + i - 1)]);

            if (compositionTimeQueue != null) {
                entry.setSampleCompositionTimeOffset(compositionTimeQueue[compositionTimeQueueIndex].getOffset());
                if (--compositionTimeEntriesLeft == 0 && (compositionTimeQueue.length - compositionTimeQueueIndex) > 1) {
                    compositionTimeQueueIndex++;
                    compositionTimeEntriesLeft = compositionTimeQueue[compositionTimeQueueIndex].getCount();
                }
            }
            entries.add(entry);
        }

        trun.setEntries(entries);

        parent.addBox(trun);
    }

    /**
     * Creates a 'moof' box for a given sequence of samples.
     *
     * @param startSample    low endpoint (inclusive) of the sample sequence
     * @param endSample      high endpoint (exclusive) of the sample sequence
     * @param track          source of the samples
     * @param sequenceNumber the fragment index of the requested list of samples
     * @return the list of TrackRun boxes.
     */
    protected Box createMoof(long startSample, long endSample, Track track, int sequenceNumber) {
        MovieFragmentBox moof = new MovieFragmentBox();
        createMfhd(startSample, endSample, track, sequenceNumber, moof);
        createTraf(startSample, endSample, track, sequenceNumber, moof);

        TrackRunBox firstTrun = moof.getTrackRunBoxes().get(0);
        firstTrun.setDataOffset(1); // dummy to make size correct
        firstTrun.setDataOffset((int) (8 + moof.getSize())); // mdat header + moof size

        return moof;
    }

    /**
     * Creates a single 'mvhd' movie header box for a given movie.
     *
     * @param movie the concerned movie
     * @return an 'mvhd' box
     */
    protected Box createMvhd(Movie movie) {
        MovieHeaderBox mvhd = new MovieHeaderBox();
        mvhd.setVersion(1);
        mvhd.setCreationTime(getDate());
        mvhd.setModificationTime(getDate());
        mvhd.setDuration(0);//no duration in moov for fragmented movies
        long movieTimeScale = movie.getTimescale();
        mvhd.setTimescale(movieTimeScale);
        // find the next available trackId
        long nextTrackId = 0;
        for (Track track : movie.getTracks()) {
            nextTrackId = nextTrackId < track.getTrackMetaData().getTrackId() ? track.getTrackMetaData().getTrackId() : nextTrackId;
        }
        mvhd.setNextTrackId(++nextTrackId);
        return mvhd;
    }

    /**
     * Creates a fully populated 'moov' box with all child boxes. Child boxes are:
     * 
    *
  • {@link #createMvhd(com.googlecode.mp4parser.authoring.Movie) mvhd}
  • *
  • {@link #createMvex(com.googlecode.mp4parser.authoring.Movie) mvex}
  • *
  • a {@link #createTrak(com.googlecode.mp4parser.authoring.Track, com.googlecode.mp4parser.authoring.Movie) trak} for every track
  • *
* * @param movie the concerned movie * @return fully populated 'moov' */ protected Box createMoov(Movie movie) { MovieBox movieBox = new MovieBox(); movieBox.addBox(createMvhd(movie)); for (Track track : movie.getTracks()) { movieBox.addBox(createTrak(track, movie)); } movieBox.addBox(createMvex(movie)); // metadata here return movieBox; } /** * Creates a 'tfra' - track fragment random access box for the given track with the isoFile. * The tfra contains a map of random access points with time as key and offset within the isofile * as value. * * @param track the concerned track * @param isoFile the track is contained in * @return a track fragment random access box. */ protected Box createTfra(Track track, Container isoFile) { TrackFragmentRandomAccessBox tfra = new TrackFragmentRandomAccessBox(); tfra.setVersion(1); // use long offsets and times List offset2timeEntries = new LinkedList(); TrackExtendsBox trex = null; List trexs = Path.getPaths(isoFile, "moov/mvex/trex"); for (TrackExtendsBox innerTrex : trexs) { if (innerTrex.getTrackId() == track.getTrackMetaData().getTrackId()) { trex = innerTrex; } } long offset = 0; long duration = 0; for (Box box : isoFile.getBoxes()) { if (box instanceof MovieFragmentBox) { List trafs = ((MovieFragmentBox) box).getBoxes(TrackFragmentBox.class); for (int i = 0; i < trafs.size(); i++) { TrackFragmentBox traf = trafs.get(i); if (traf.getTrackFragmentHeaderBox().getTrackId() == track.getTrackMetaData().getTrackId()) { // here we are at the offset required for the current entry. List truns = traf.getBoxes(TrackRunBox.class); for (int j = 0; j < truns.size(); j++) { List offset2timeEntriesThisTrun = new LinkedList(); TrackRunBox trun = truns.get(j); for (int k = 0; k < trun.getEntries().size(); k++) { TrackRunBox.Entry trunEntry = trun.getEntries().get(k); SampleFlags sf; if (k == 0 && trun.isFirstSampleFlagsPresent()) { sf = trun.getFirstSampleFlags(); } else if (trun.isSampleFlagsPresent()) { sf = trunEntry.getSampleFlags(); } else { sf = trex.getDefaultSampleFlags(); } if (sf == null && track.getHandler().equals("vide")) { throw new RuntimeException("Cannot find SampleFlags for video track but it's required to build tfra"); } if (sf == null || sf.getSampleDependsOn() == 2) { offset2timeEntriesThisTrun.add(new TrackFragmentRandomAccessBox.Entry( duration, offset, i + 1, j + 1, k + 1)); } duration += trunEntry.getSampleDuration(); } if (offset2timeEntriesThisTrun.size() == trun.getEntries().size() && trun.getEntries().size() > 0) { // Oooops every sample seems to be random access sample // is this an audio track? I don't care. // I just use the first for trun sample for tfra random access offset2timeEntries.add(offset2timeEntriesThisTrun.get(0)); } else { offset2timeEntries.addAll(offset2timeEntriesThisTrun); } } } } } offset += box.getSize(); } tfra.setEntries(offset2timeEntries); tfra.setTrackId(track.getTrackMetaData().getTrackId()); return tfra; } /** * Creates a 'mfra' - movie fragment random access box for the given movie in the given * isofile. Uses {@link #createTfra(com.googlecode.mp4parser.authoring.Track, Container)} * to generate the child boxes. * * @param movie concerned movie * @param isoFile concerned isofile * @return a complete 'mfra' box */ protected Box createMfra(Movie movie, Container isoFile) { MovieFragmentRandomAccessBox mfra = new MovieFragmentRandomAccessBox(); for (Track track : movie.getTracks()) { mfra.addBox(createTfra(track, isoFile)); } MovieFragmentRandomAccessOffsetBox mfro = new MovieFragmentRandomAccessOffsetBox(); mfra.addBox(mfro); mfro.setMfraSize(mfra.getSize()); return mfra; } protected Box createTrex(Movie movie, Track track) { TrackExtendsBox trex = new TrackExtendsBox(); trex.setTrackId(track.getTrackMetaData().getTrackId()); trex.setDefaultSampleDescriptionIndex(1); trex.setDefaultSampleDuration(0); trex.setDefaultSampleSize(0); SampleFlags sf = new SampleFlags(); if ("soun".equals(track.getHandler()) || "subt".equals(track.getHandler())) { // as far as I know there is no audio encoding // where the sample are not self contained. // same seems to be true for subtitle tracks sf.setSampleDependsOn(2); sf.setSampleIsDependedOn(2); } trex.setDefaultSampleFlags(sf); return trex; } /** * Creates a 'mvex' - movie extends box and populates it with 'trex' boxes * by calling {@link #createTrex(com.googlecode.mp4parser.authoring.Movie, com.googlecode.mp4parser.authoring.Track)} * for each track to generate them * * @param movie the source movie * @return a complete 'mvex' */ protected Box createMvex(Movie movie) { MovieExtendsBox mvex = new MovieExtendsBox(); final MovieExtendsHeaderBox mved = new MovieExtendsHeaderBox(); mved.setVersion(1); for (Track track : movie.getTracks()) { final long trackDuration = getTrackDuration(movie, track); if (mved.getFragmentDuration() < trackDuration) { mved.setFragmentDuration(trackDuration); } } mvex.addBox(mved); for (Track track : movie.getTracks()) { mvex.addBox(createTrex(movie, track)); } return mvex; } protected Box createTkhd(Movie movie, Track track) { TrackHeaderBox tkhd = new TrackHeaderBox(); tkhd.setVersion(1); tkhd.setFlags(7); // enabled, in movie, in previe, in poster tkhd.setAlternateGroup(track.getTrackMetaData().getGroup()); tkhd.setCreationTime(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(0);//no duration in moov for fragmented movies tkhd.setHeight(track.getTrackMetaData().getHeight()); tkhd.setWidth(track.getTrackMetaData().getWidth()); tkhd.setLayer(track.getTrackMetaData().getLayer()); tkhd.setModificationTime(getDate()); tkhd.setTrackId(track.getTrackMetaData().getTrackId()); tkhd.setVolume(track.getTrackMetaData().getVolume()); return tkhd; } private long getTrackDuration(Movie movie, Track track) { return (track.getDuration() * movie.getTimescale()) / track.getTrackMetaData().getTimescale(); } protected Box createMdhd(Movie movie, Track track) { MediaHeaderBox mdhd = new MediaHeaderBox(); mdhd.setCreationTime(track.getTrackMetaData().getCreationTime()); mdhd.setModificationTime(getDate()); mdhd.setDuration(0);//no duration in moov for fragmented movies mdhd.setTimescale(track.getTrackMetaData().getTimescale()); mdhd.setLanguage(track.getTrackMetaData().getLanguage()); return mdhd; } protected Box createStbl(Movie movie, Track track) { SampleTableBox stbl = new SampleTableBox(); createStsd(track, stbl); stbl.addBox(new TimeToSampleBox()); stbl.addBox(new SampleToChunkBox()); stbl.addBox(new SampleSizeBox()); stbl.addBox(new StaticChunkOffsetBox()); return stbl; } protected void createStsd(Track track, SampleTableBox stbl) { stbl.addBox(track.getSampleDescriptionBox()); } protected Box createMinf(Track track, Movie movie) { MediaInformationBox minf = new MediaInformationBox(); if (track.getHandler().equals("vide")) { minf.addBox(new VideoMediaHeaderBox()); } else if (track.getHandler().equals("soun")) { minf.addBox(new SoundMediaHeaderBox()); } else if (track.getHandler().equals("text")) { minf.addBox(new NullMediaHeaderBox()); } else if (track.getHandler().equals("subt")) { minf.addBox(new SubtitleMediaHeaderBox()); } else if (track.getHandler().equals("hint")) { minf.addBox(new HintMediaHeaderBox()); } else if (track.getHandler().equals("sbtl")) { minf.addBox(new NullMediaHeaderBox()); } minf.addBox(createDinf(movie, track)); minf.addBox(createStbl(movie, track)); return minf; } protected Box createMdiaHdlr(Track track, Movie movie) { HandlerBox hdlr = new HandlerBox(); hdlr.setHandlerType(track.getHandler()); return hdlr; } protected Box createMdia(Track track, Movie movie) { MediaBox mdia = new MediaBox(); mdia.addBox(createMdhd(movie, track)); mdia.addBox(createMdiaHdlr(track, movie)); mdia.addBox(createMinf(track, movie)); return mdia; } protected Box createTrak(Track track, Movie movie) { LOG.fine("Creating Track " + track); TrackBox trackBox = new TrackBox(); trackBox.addBox(createTkhd(movie, track)); Box edts = createEdts(track, movie); if (edts != null) { trackBox.addBox(edts); } trackBox.addBox(createMdia(track, movie)); return trackBox; } protected Box createEdts(Track track, Movie movie) { if (track.getEdits() != null && track.getEdits().size() > 0) { EditListBox elst = new EditListBox(); elst.setVersion(1); List entries = new ArrayList(); for (Edit edit : track.getEdits()) { entries.add(new EditListBox.Entry(elst, Math.round(edit.getSegmentDuration() * movie.getTimescale()), edit.getMediaTime() * track.getTrackMetaData().getTimescale() / edit.getTimeScale(), edit.getMediaRate())); } elst.setEntries(entries); EditBox edts = new EditBox(); edts.addBox(elst); return edts; } else { return null; } } protected DataInformationBox createDinf(Movie movie, Track track) { DataInformationBox dinf = new DataInformationBox(); DataReferenceBox dref = new DataReferenceBox(); dinf.addBox(dref); DataEntryUrlBox url = new DataEntryUrlBox(); url.setFlags(1); dref.addBox(url); return dinf; } public Fragmenter getFragmenter() { return fragmenter; } public void setFragmenter(Fragmenter fragmenter) { this.fragmenter = fragmenter; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy