Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
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, ...)
/*
* 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;
}
}