diff options
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-base | 576 |
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; - } -} |