summaryrefslogtreecommitdiff
path: root/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base
diff options
context:
space:
mode:
Diffstat (limited to 'isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base')
-rw-r--r--isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base576
1 files changed, 0 insertions, 576 deletions
diff --git a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base b/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base
deleted file mode 100644
index 9bd1ca6..0000000
--- a/isoparser/src/main/java/com/googlecode/mp4parser/authoring/builder/.svn/text-base/DefaultMp4Builder.java.svn-base
+++ /dev/null
@@ -1,576 +0,0 @@
-/*
- * 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.Box;
-import com.coremedia.iso.boxes.CompositionTimeToSample;
-import com.coremedia.iso.boxes.ContainerBox;
-import com.coremedia.iso.boxes.DataEntryUrlBox;
-import com.coremedia.iso.boxes.DataInformationBox;
-import com.coremedia.iso.boxes.DataReferenceBox;
-import com.coremedia.iso.boxes.FileTypeBox;
-import com.coremedia.iso.boxes.HandlerBox;
-import com.coremedia.iso.boxes.MediaBox;
-import com.coremedia.iso.boxes.MediaHeaderBox;
-import com.coremedia.iso.boxes.MediaInformationBox;
-import com.coremedia.iso.boxes.MovieBox;
-import com.coremedia.iso.boxes.MovieHeaderBox;
-import com.coremedia.iso.boxes.SampleDependencyTypeBox;
-import com.coremedia.iso.boxes.SampleSizeBox;
-import com.coremedia.iso.boxes.SampleTableBox;
-import com.coremedia.iso.boxes.SampleToChunkBox;
-import com.coremedia.iso.boxes.StaticChunkOffsetBox;
-import com.coremedia.iso.boxes.SyncSampleBox;
-import com.coremedia.iso.boxes.TimeToSampleBox;
-import com.coremedia.iso.boxes.TrackBox;
-import com.coremedia.iso.boxes.TrackHeaderBox;
-import com.googlecode.mp4parser.authoring.DateHelper;
-import com.googlecode.mp4parser.authoring.Movie;
-import com.googlecode.mp4parser.authoring.Track;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.GatheringByteChannel;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import static com.googlecode.mp4parser.util.CastUtils.l2i;
-
-/**
- * Creates a plain MP4 file from a video. Plain as plain can be.
- */
-public class DefaultMp4Builder implements Mp4Builder {
-
- public int STEPSIZE = 64;
- Set<StaticChunkOffsetBox> chunkOffsetBoxes = new HashSet<StaticChunkOffsetBox>();
- private static Logger LOG = Logger.getLogger(DefaultMp4Builder.class.getName());
-
- HashMap<Track, List<ByteBuffer>> track2Sample = new HashMap<Track, List<ByteBuffer>>();
- HashMap<Track, long[]> track2SampleSizes = new HashMap<Track, long[]>();
- private FragmentIntersectionFinder intersectionFinder = new TwoSecondIntersectionFinder();
-
- public void setIntersectionFinder(FragmentIntersectionFinder intersectionFinder) {
- this.intersectionFinder = intersectionFinder;
- }
-
- /**
- * {@inheritDoc}
- */
- public IsoFile build(Movie movie) {
- LOG.fine("Creating movie " + movie);
- for (Track track : movie.getTracks()) {
- // getting the samples may be a time consuming activity
- List<ByteBuffer> samples = track.getSamples();
- putSamples(track, samples);
- long[] sizes = new long[samples.size()];
- for (int i = 0; i < sizes.length; i++) {
- sizes[i] = samples.get(i).limit();
- }
- putSampleSizes(track, sizes);
- }
-
- IsoFile isoFile = new IsoFile();
- // ouch that is ugly but I don't know how to do it else
- List<String> minorBrands = new LinkedList<String>();
- minorBrands.add("isom");
- minorBrands.add("iso2");
- minorBrands.add("avc1");
-
- isoFile.addBox(new FileTypeBox("isom", 0, minorBrands));
- isoFile.addBox(createMovieBox(movie));
- InterleaveChunkMdat mdat = new InterleaveChunkMdat(movie);
- isoFile.addBox(mdat);
-
- /*
- dataOffset is where the first sample starts. In this special mdat the samples always start
- at offset 16 so that we can use the same offset for large boxes and small boxes
- */
- long dataOffset = mdat.getDataOffset();
- for (StaticChunkOffsetBox chunkOffsetBox : chunkOffsetBoxes) {
- long[] offsets = chunkOffsetBox.getChunkOffsets();
- for (int i = 0; i < offsets.length; i++) {
- offsets[i] += dataOffset;
- }
- }
-
-
- return isoFile;
- }
-
- public FragmentIntersectionFinder getFragmentIntersectionFinder() {
- throw new UnsupportedOperationException("No fragment intersection finder in default MP4 builder!");
- }
-
- protected long[] putSampleSizes(Track track, long[] sizes) {
- return track2SampleSizes.put(track, sizes);
- }
-
- protected List<ByteBuffer> putSamples(Track track, List<ByteBuffer> samples) {
- return track2Sample.put(track, samples);
- }
-
- private MovieBox createMovieBox(Movie movie) {
- MovieBox movieBox = new MovieBox();
- MovieHeaderBox mvhd = new MovieHeaderBox();
-
- mvhd.setCreationTime(DateHelper.convert(new Date()));
- mvhd.setModificationTime(DateHelper.convert(new Date()));
-
- long movieTimeScale = getTimescale(movie);
- long duration = 0;
-
- for (Track track : movie.getTracks()) {
- long tracksDuration = getDuration(track) * movieTimeScale / track.getTrackMetaData().getTimescale();
- if (tracksDuration > duration) {
- duration = tracksDuration;
- }
-
-
- }
-
- mvhd.setDuration(duration);
- 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);
- if (mvhd.getCreationTime() >= 1l << 32 ||
- mvhd.getModificationTime() >= 1l << 32 ||
- mvhd.getDuration() >= 1l << 32) {
- mvhd.setVersion(1);
- }
-
- movieBox.addBox(mvhd);
- for (Track track : movie.getTracks()) {
- movieBox.addBox(createTrackBox(track, movie));
- }
- // metadata here
- Box udta = createUdta(movie);
- if (udta != null) {
- movieBox.addBox(udta);
- }
- return movieBox;
-
- }
-
- /**
- * Override to create a user data box that may contain metadata.
- *
- * @return a 'udta' box or <code>null</code> if none provided
- */
- protected Box createUdta(Movie movie) {
- return null;
- }
-
- private TrackBox createTrackBox(Track track, Movie movie) {
-
- LOG.info("Creating Mp4TrackImpl " + track);
- TrackBox trackBox = new TrackBox();
- TrackHeaderBox tkhd = new TrackHeaderBox();
- 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());
- if (tkhd.getCreationTime() >= 1l << 32 ||
- tkhd.getModificationTime() >= 1l << 32 ||
- tkhd.getDuration() >= 1l << 32) {
- tkhd.setVersion(1);
- }
-
- 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;
- }
-
- private class InterleaveChunkMdat implements Box {
- List<Track> tracks;
- List<ByteBuffer> samples = new ArrayList<ByteBuffer>();
- ContainerBox parent;
-
- long contentSize = 0;
-
- public ContainerBox getParent() {
- return parent;
- }
-
- public void setParent(ContainerBox parent) {
- this.parent = parent;
- }
-
- public void parse(ReadableByteChannel readableByteChannel, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException {
- }
-
- private InterleaveChunkMdat(Movie movie) {
-
- tracks = movie.getTracks();
- Map<Track, int[]> chunks = new HashMap<Track, int[]>();
- for (Track track : movie.getTracks()) {
- chunks.put(track, getChunkSizes(track, movie));
- }
-
- for (int i = 0; i < chunks.values().iterator().next().length; i++) {
- for (Track track : tracks) {
-
- int[] chunkSizes = chunks.get(track);
- long firstSampleOfChunk = 0;
- for (int j = 0; j < i; j++) {
- firstSampleOfChunk += chunkSizes[j];
- }
-
- for (int j = l2i(firstSampleOfChunk); j < firstSampleOfChunk + chunkSizes[i]; j++) {
-
- ByteBuffer s = DefaultMp4Builder.this.track2Sample.get(track).get(j);
- contentSize += s.limit();
- samples.add((ByteBuffer) s.rewind());
- }
-
- }
-
- }
-
- }
-
- public long getDataOffset() {
- Box b = this;
- long offset = 16;
- while (b.getParent() != null) {
- for (Box box : b.getParent().getBoxes()) {
- if (b == box) {
- break;
- }
- offset += box.getSize();
- }
- b = b.getParent();
- }
- return offset;
- }
-
-
- public String getType() {
- return "mdat";
- }
-
- public long getSize() {
- return 16 + contentSize;
- }
-
- private boolean isSmallBox(long contentSize) {
- return (contentSize + 8) < 4294967296L;
- }
-
-
- public void getBox(WritableByteChannel writableByteChannel) throws IOException {
- ByteBuffer bb = ByteBuffer.allocate(16);
- long size = getSize();
- if (isSmallBox(size)) {
- IsoTypeWriter.writeUInt32(bb, size);
- } else {
- IsoTypeWriter.writeUInt32(bb, 1);
- }
- bb.put(IsoFile.fourCCtoBytes("mdat"));
- if (isSmallBox(size)) {
- bb.put(new byte[8]);
- } else {
- IsoTypeWriter.writeUInt64(bb, size);
- }
- bb.rewind();
- writableByteChannel.write(bb);
- if (writableByteChannel instanceof GatheringByteChannel) {
- List<ByteBuffer> nuSamples = unifyAdjacentBuffers(samples);
-
-
- for (int i = 0; i < Math.ceil((double) nuSamples.size() / STEPSIZE); i++) {
- List<ByteBuffer> sublist = nuSamples.subList(
- i * STEPSIZE, // start
- (i + 1) * STEPSIZE < nuSamples.size() ? (i + 1) * STEPSIZE : nuSamples.size()); // end
- ByteBuffer sampleArray[] = sublist.toArray(new ByteBuffer[sublist.size()]);
- do {
- ((GatheringByteChannel) writableByteChannel).write(sampleArray);
- } while (sampleArray[sampleArray.length - 1].remaining() > 0);
- }
- //System.err.println(bytesWritten);
- } else {
- for (ByteBuffer sample : samples) {
- sample.rewind();
- writableByteChannel.write(sample);
- }
- }
- }
-
- }
-
- /**
- * Gets the chunk sizes for the given track.
- *
- * @param track
- * @param movie
- * @return
- */
- int[] getChunkSizes(Track track, Movie movie) {
-
- long[] referenceChunkStarts = intersectionFinder.sampleNumbers(track, movie);
- int[] chunkSizes = new int[referenceChunkStarts.length];
-
-
- for (int i = 0; i < referenceChunkStarts.length; i++) {
- long start = referenceChunkStarts[i] - 1;
- long end;
- if (referenceChunkStarts.length == i + 1) {
- end = track.getSamples().size();
- } else {
- end = referenceChunkStarts[i + 1] - 1;
- }
-
- chunkSizes[i] = l2i(end - start);
- // The Stretch makes sure that there are as much audio and video chunks!
- }
- assert DefaultMp4Builder.this.track2Sample.get(track).size() == sum(chunkSizes) : "The number of samples and the sum of all chunk lengths must be equal";
- return chunkSizes;
-
-
- }
-
-
- private static long sum(int[] ls) {
- long rc = 0;
- for (long l : ls) {
- rc += l;
- }
- return rc;
- }
-
- protected static long getDuration(Track track) {
- long duration = 0;
- for (TimeToSampleBox.Entry entry : track.getDecodingTimeEntries()) {
- duration += entry.getCount() * entry.getDelta();
- }
- return duration;
- }
-
- public long getTimescale(Movie movie) {
- long timescale = movie.getTracks().iterator().next().getTrackMetaData().getTimescale();
- for (Track track : movie.getTracks()) {
- timescale = gcd(track.getTrackMetaData().getTimescale(), timescale);
- }
- return timescale;
- }
-
- public static long gcd(long a, long b) {
- if (b == 0) {
- return a;
- }
- return gcd(b, a % b);
- }
-
- public List<ByteBuffer> unifyAdjacentBuffers(List<ByteBuffer> samples) {
- ArrayList<ByteBuffer> nuSamples = new ArrayList<ByteBuffer>(samples.size());
- for (ByteBuffer buffer : samples) {
- int lastIndex = nuSamples.size() - 1;
- if (lastIndex >= 0 && buffer.hasArray() && nuSamples.get(lastIndex).hasArray() && buffer.array() == nuSamples.get(lastIndex).array() &&
- nuSamples.get(lastIndex).arrayOffset() + nuSamples.get(lastIndex).limit() == buffer.arrayOffset()) {
- ByteBuffer oldBuffer = nuSamples.remove(lastIndex);
- ByteBuffer nu = ByteBuffer.wrap(buffer.array(), oldBuffer.arrayOffset(), oldBuffer.limit() + buffer.limit()).slice();
- // We need to slice here since wrap([], offset, length) just sets position and not the arrayOffset.
- nuSamples.add(nu);
- } else if (lastIndex >= 0 &&
- buffer instanceof MappedByteBuffer && nuSamples.get(lastIndex) instanceof MappedByteBuffer &&
- nuSamples.get(lastIndex).limit() == nuSamples.get(lastIndex).capacity() - buffer.capacity()) {
- // This can go wrong - but will it?
- ByteBuffer oldBuffer = nuSamples.get(lastIndex);
- oldBuffer.limit(buffer.limit() + oldBuffer.limit());
- } else {
- nuSamples.add(buffer);
- }
- }
- return nuSamples;
- }
-}