diff --git a/.gitignore b/.gitignore index 1f78fda39..d8a4ec398 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,10 @@ *.jar *.war *.ear +.idea *.iml + +examples/target +isoparser/target +isoparser/aac-sample.mp4 +isoparser/ac3-sample.mp4 diff --git a/examples/src/main/java/com/googlecode/mp4parser/AppendExample.java b/examples/src/main/java/com/googlecode/mp4parser/AppendExample.java index 0a0620356..5797c4691 100644 --- a/examples/src/main/java/com/googlecode/mp4parser/AppendExample.java +++ b/examples/src/main/java/com/googlecode/mp4parser/AppendExample.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -20,14 +21,100 @@ public class AppendExample { public static void main(String[] args) throws IOException { - String f1 = "C:\\Users\\sannies\\Downloads\\merge_73677.mp4"; - String f2 = "C:\\Users\\sannies\\Downloads\\rsmedia-test(1).mp4"; - //String f2 = AppendExample.class.getProtectionDomain().getCodeSource().getLocation().getFile() + "/1365070285923.mp4"; - //String f3 = AppendExample.class.getProtectionDomain().getCodeSource().getLocation().getFile() + "/1365070453555.mp4"; - - Movie[] inMovies = new Movie[]{ - MovieCreator.build(f1), - MovieCreator.build(f2)}; + String[] videoUris = new String[]{ + + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20150930_161515.mp4", + "c:\\content\\20150930_161525.mp4", + "c:\\content\\20150930_161529.mp4", + "c:\\content\\20150930_161534.mp4", + "c:\\content\\20150930_161543.mp4", + "c:\\content\\20151001_135436.mp4", + "c:\\content\\20151001_135446.mp4", + "c:\\content\\20151001_135540.mp4" + + }; + + List inMovies = new ArrayList(); + for (String videoUri : videoUris) { + inMovies.add(MovieCreator.build(videoUri)); + } List videoTracks = new LinkedList(); List audioTracks = new LinkedList(); diff --git a/muxer/src/main/java/org/mp4parser/muxer/FileDataSourceImpl.java b/muxer/src/main/java/org/mp4parser/muxer/FileDataSourceImpl.java index 790058b64..20f96c99e 100644 --- a/muxer/src/main/java/org/mp4parser/muxer/FileDataSourceImpl.java +++ b/muxer/src/main/java/org/mp4parser/muxer/FileDataSourceImpl.java @@ -1,5 +1,7 @@ package org.mp4parser.muxer; +import com.googlecode.mp4parser.util.Logger; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -9,6 +11,7 @@ import java.nio.channels.WritableByteChannel; public class FileDataSourceImpl implements DataSource { + private static Logger LOG = Logger.getLogger(FileDataSourceImpl.class); FileChannel fc; String filename; @@ -56,6 +59,7 @@ public synchronized long transferTo(long startPosition, long count, WritableByte } public synchronized ByteBuffer map(long startPosition, long size) throws IOException { + LOG.logDebug(startPosition + " " + size); return fc.map(FileChannel.MapMode.READ_ONLY, startPosition, size); } diff --git a/muxer/src/main/java/org/mp4parser/muxer/FileDataSourceViaHeapImpl.java b/muxer/src/main/java/org/mp4parser/muxer/FileDataSourceViaHeapImpl.java new file mode 100644 index 000000000..eb11b36ac --- /dev/null +++ b/muxer/src/main/java/org/mp4parser/muxer/FileDataSourceViaHeapImpl.java @@ -0,0 +1,79 @@ +package org.mp4parser.muxer; + +import com.googlecode.mp4parser.util.Logger; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.WritableByteChannel; + +import static com.googlecode.mp4parser.util.CastUtils.l2i; + + +public class FileDataSourceViaHeapImpl implements DataSource { + private static Logger LOG = Logger.getLogger(FileDataSourceViaHeapImpl.class); + FileChannel fc; + String filename; + + + public FileDataSourceViaHeapImpl(File f) throws FileNotFoundException { + this.fc = new FileInputStream(f).getChannel(); + this.filename = f.getName(); + } + + public FileDataSourceViaHeapImpl(String f) throws FileNotFoundException { + File file = new File(f); + this.fc = new FileInputStream(file).getChannel(); + this.filename = file.getName(); + } + + + public FileDataSourceViaHeapImpl(FileChannel fc) { + this.fc = fc; + this.filename = "unknown"; + } + + public FileDataSourceViaHeapImpl(FileChannel fc, String filename) { + this.fc = fc; + this.filename = filename; + } + + public synchronized int read(ByteBuffer byteBuffer) throws IOException { + return fc.read(byteBuffer); + } + + public synchronized long size() throws IOException { + return fc.size(); + } + + public synchronized long position() throws IOException { + return fc.position(); + } + + public synchronized void position(long nuPos) throws IOException { + fc.position(nuPos); + } + + public synchronized long transferTo(long startPosition, long count, WritableByteChannel sink) throws IOException { + return fc.transferTo(startPosition, count, sink); + } + + public synchronized ByteBuffer map(long startPosition, long size) throws IOException { + ByteBuffer bb = ByteBuffer.allocate(l2i(size)); + fc.read(bb, startPosition); + return (ByteBuffer) bb.rewind(); + } + + public void close() throws IOException { + fc.close(); + } + + @Override + public String toString() { + return filename; + } + +} diff --git a/muxer/src/main/java/org/mp4parser/muxer/builder/DefaultMp4Builder.java b/muxer/src/main/java/org/mp4parser/muxer/builder/DefaultMp4Builder.java index 778eede6a..bbf3cc654 100644 --- a/muxer/src/main/java/org/mp4parser/muxer/builder/DefaultMp4Builder.java +++ b/muxer/src/main/java/org/mp4parser/muxer/builder/DefaultMp4Builder.java @@ -27,17 +27,17 @@ import org.mp4parser.muxer.Sample; import org.mp4parser.muxer.Track; import org.mp4parser.muxer.tracks.CencEncryptedTrack; +import org.mp4parser.support.Logger; import org.mp4parser.tools.IsoTypeWriter; import org.mp4parser.tools.Mp4Arrays; import org.mp4parser.tools.Offsets; import org.mp4parser.tools.Path; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; import static org.mp4parser.tools.CastUtils.l2i; import static org.mp4parser.tools.Mp4Math.lcm; @@ -47,7 +47,7 @@ */ public class DefaultMp4Builder implements Mp4Builder { - private static Logger LOG = Logger.getLogger(DefaultMp4Builder.class.getName()); + private static Logger LOG = Logger.getLogger(DefaultMp4Builder.class); Map chunkOffsetBoxes = new HashMap(); Set sampleAuxiliaryInformationOffsetsBoxes = new HashSet(); HashMap> track2Sample = new HashMap>(); @@ -83,7 +83,7 @@ public Container build(Movie movie) { if (fragmenter == null) { fragmenter = new TimeBasedFragmenter(2); } - LOG.fine("Creating movie " + movie); + LOG.logDebug("Creating movie " + movie); for (Track track : movie.getTracks()) { // getting the samples may be a time consuming activity List samples = track.getSamples(); @@ -114,7 +114,7 @@ public Container build(Movie movie) { contentSize += sum(stsz.getSampleSizes()); } - + LOG.logDebug("About to create mdat"); InterleaveChunkMdat mdat = new InterleaveChunkMdat(movie, chunks, contentSize); long dataOffset = 16; @@ -122,6 +122,7 @@ public Container build(Movie movie) { dataOffset += lightBox.getSize(); } isoFile.addBox(mdat); + LOG.logDebug("mdat crated"); /* dataOffset is where the first sample starts. In this special mdat the samples always start @@ -306,7 +307,7 @@ protected TrackBox createTrackBox(Track track, Movie movie, Map ch ParsableBox stbl = createStbl(track, movie, chunks); minf.addBox(stbl); mdia.addBox(minf); - + LOG.logDebug("done with trak for track_" + track.getTrackMetaData().getTrackId()); return trackBox; } @@ -344,6 +345,7 @@ protected ParsableBox createStbl(Track track, Movie movie, Map chu createStsz(track, stbl); createStco(track, movie, chunks, stbl); + Map> groupEntryFamilies = new HashMap>(); for (Map.Entry sg : track.getSampleGroups().entrySet()) { String type = sg.getKey().getType(); @@ -385,7 +387,7 @@ protected ParsableBox createStbl(Track track, Movie movie, Map chu createCencBoxes((CencEncryptedTrack) track, stbl, chunks.get(track)); } createSubs(track, stbl); - + LOG.logDebug("done with stbl for track_" + track.getTrackMetaData().getTrackId()); return stbl; } @@ -452,9 +454,7 @@ protected void createStco(Track targetTrack, Movie movie, Map chun long offset = 0; // all tracks have the same number of chunks - if (LOG.isLoggable(Level.FINE)) { - LOG.fine("Calculating chunk offsets for track_" + targetTrack.getTrackMetaData().getTrackId()); - } + LOG.logDebug("Calculating chunk offsets for track_" + targetTrack.getTrackMetaData().getTrackId()); List tracks = new ArrayList(chunks.keySet()); Collections.sort(tracks, new Comparator() { @@ -496,9 +496,10 @@ public int compare(Track o1, Track o2) { int startSample = trackToSample.get(nextChunksTrack); double time = trackToTime.get(nextChunksTrack); + long[] durs = nextChunksTrack.getSampleDurations(); for (int j = startSample; j < startSample + numberOfSampleInNextChunk; j++) { offset += track2SampleSizes.get(nextChunksTrack)[j]; - time += (double) nextChunksTrack.getSampleDurations()[j] / nextChunksTrack.getTrackMetaData().getTimescale(); + time += (double) durs[j] / nextChunksTrack.getTrackMetaData().getTimescale(); } trackToChunk.put(nextChunksTrack, nextChunksIndex + 1); trackToSample.put(nextChunksTrack, startSample + numberOfSampleInNextChunk); @@ -705,9 +706,19 @@ public void getBox(WritableByteChannel writableByteChannel) throws IOException { } bb.rewind(); writableByteChannel.write(bb); + long writtenBytes = 0; + long writtenMegaBytes = 0; + + LOG.logDebug("About to write " + contentSize); for (List samples : chunkList) { for (Sample sample : samples) { sample.writeTo(writableByteChannel); + writtenBytes += sample.getSize(); + if (writtenBytes > 1024 * 1024) { + writtenBytes -= 1024 * 1024; + writtenMegaBytes++; + LOG.logDebug("Written " + writtenMegaBytes + "MB"); + } } } diff --git a/muxer/src/main/java/org/mp4parser/muxer/samples/DefaultMp4SampleList.java b/muxer/src/main/java/org/mp4parser/muxer/samples/DefaultMp4SampleList.java index 7b0f1120e..82024129c 100644 --- a/muxer/src/main/java/org/mp4parser/muxer/samples/DefaultMp4SampleList.java +++ b/muxer/src/main/java/org/mp4parser/muxer/samples/DefaultMp4SampleList.java @@ -12,9 +12,12 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; +import java.lang.ref.SoftReference; +import java.lang.reflect.Array; import java.nio.ByteBuffer; import java.nio.channels.WritableByteChannel; import java.util.AbstractList; +import java.util.Arrays; import java.util.List; import static org.mp4parser.tools.CastUtils.l2i; @@ -25,7 +28,7 @@ public class DefaultMp4SampleList extends AbstractList { Container topLevel; TrackBox trackBox = null; - ByteBuffer[] cache = null; + SoftReference[] cache = null; int[] chunkNumsStartSampleNum; long[] chunkOffsets; long[] chunkSizes; @@ -52,7 +55,9 @@ public DefaultMp4SampleList(long track, Container topLevel, RandomAccessSource r chunkOffsets = trackBox.getSampleTableBox().getChunkOffsetBox().getChunkOffsets(); chunkSizes = new long[chunkOffsets.length]; - cache = new ByteBuffer[chunkOffsets.length]; + cache = new SoftReference[chunkOffsets.length]; + Arrays.fill(cache, new SoftReference(null)); + sampleOffsetsWithinChunks = new long[chunkOffsets.length][]; ssb = trackBox.getSampleTableBox().getSampleSizeBox(); List s2chunkEntries = trackBox.getSampleTableBox().getSampleToChunkBox().getEntries(); @@ -160,61 +165,71 @@ synchronized int getChunkForSample(int index) { } @Override - public Sample get(int index) { + public Sample get(final int index) { if (index >= ssb.getSampleCount()) { throw new IndexOutOfBoundsException(); } + return new SampleImpl(index); + } - int chunkNumber = getChunkForSample(index); - int chunkStartSample = chunkNumsStartSampleNum[chunkNumber] - 1; - final long chunkOffset = chunkOffsets[l2i(chunkNumber)]; - int sampleInChunk = index - chunkStartSample; - long[] sampleOffsetsWithinChunk = sampleOffsetsWithinChunks[l2i(chunkNumber)]; - final long offsetWithInChunk = sampleOffsetsWithinChunk[sampleInChunk]; - - - ByteBuffer chunkBuffer = cache[chunkNumber]; - if (chunkBuffer == null) { - - try { - cache[chunkNumber] = chunkBuffer = randomAccess.get( - chunkOffset, - sampleOffsetsWithinChunk[sampleOffsetsWithinChunk.length - 1] + ssb.getSampleSizeAtIndex(chunkStartSample + sampleOffsetsWithinChunk.length - 1)); - } catch (IOException e) { - StringWriter sw = new StringWriter(); - e.printStackTrace(new PrintWriter(sw)); - LOG.logError(sw.toString()); - throw new IndexOutOfBoundsException(e.getMessage()); - } + @Override + public int size() { + return l2i(trackBox.getSampleTableBox().getSampleSizeBox().getSampleCount()); + } + + class SampleImpl implements Sample { + + private int index; + + public SampleImpl(int index) { + this.index = index; } + public void writeTo(WritableByteChannel channel) throws IOException { + channel.write(asByteBuffer()); + } - final long sampleSize = ssb.getSampleSizeAtIndex(index); - final ByteBuffer finalChunkBuffer = chunkBuffer.duplicate(); // create duplicate so that we don't run into - return new Sample() { + public long getSize() { + return ssb.getSampleSizeAtIndex(index); + } - public void writeTo(WritableByteChannel channel) throws IOException { - channel.write(asByteBuffer()); - } + public synchronized ByteBuffer asByteBuffer() { + ByteBuffer b; - public long getSize() { - return sampleSize; - } + final int chunkNumber = getChunkForSample(index); + SoftReference chunkBufferSr = cache[chunkNumber]; - public ByteBuffer asByteBuffer() { - return (ByteBuffer) ((ByteBuffer) finalChunkBuffer.position(l2i(offsetWithInChunk))).slice().limit(l2i(sampleSize)); - } + final int chunkStartSample = chunkNumsStartSampleNum[chunkNumber] - 1; + + int sampleInChunk = index - chunkStartSample; + long[] sampleOffsetsWithinChunk = sampleOffsetsWithinChunks[l2i(chunkNumber)]; + long offsetWithInChunk = sampleOffsetsWithinChunk[sampleInChunk]; - @Override - public String toString() { - return "Sample(offset: " + (chunkOffset + offsetWithInChunk) + " size: " + sampleSize + ")"; + ByteBuffer chunkBuffer; + if (chunkBufferSr == null || (chunkBuffer = chunkBufferSr.get()) == null) { + try { + + chunkBuffer = randomAccess.get( + chunkOffsets[l2i(chunkNumber)], + sampleOffsetsWithinChunk[sampleOffsetsWithinChunk.length - 1] + ssb.getSampleSizeAtIndex(chunkStartSample + sampleOffsetsWithinChunk.length - 1)); + cache[chunkNumber] = new SoftReference(chunkBuffer); + } catch (IOException e) { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + LOG.logError(sw.toString()); + throw new IndexOutOfBoundsException(e.getMessage()); + } } - }; - } + b = (ByteBuffer) ((ByteBuffer) chunkBuffer.duplicate().position(l2i(offsetWithInChunk))).slice().limit(l2i(ssb.getSampleSizeAtIndex(index))); + return b; + } - @Override - public int size() { - return l2i(trackBox.getSampleTableBox().getSampleSizeBox().getSampleCount()); + @Override + public String toString() { + return "Sample(index: " + index + " size: " + ssb.getSampleSizeAtIndex(index) + ")"; + } } + ; + } diff --git a/muxer/src/main/java/org/mp4parser/muxer/samples/FragmentedMp4SampleList.java b/muxer/src/main/java/org/mp4parser/muxer/samples/FragmentedMp4SampleList.java index 6207b8160..80a118280 100644 --- a/muxer/src/main/java/org/mp4parser/muxer/samples/FragmentedMp4SampleList.java +++ b/muxer/src/main/java/org/mp4parser/muxer/samples/FragmentedMp4SampleList.java @@ -116,7 +116,7 @@ public Sample get(int index) { TrackRunBox trun = (TrackRunBox) box; - if (trun.getEntries().size() < (sampleIndexWithInTraf - previousTrunsSize)) { + if (trun.getEntries().size() <= (sampleIndexWithInTraf - previousTrunsSize)) { previousTrunsSize += trun.getEntries().size(); } else { // we are in correct trun box @@ -221,7 +221,10 @@ public int size() { for (MovieFragmentBox moof : isofile.getBoxes(MovieFragmentBox.class)) { for (TrackFragmentBox trackFragmentBox : moof.getBoxes(TrackFragmentBox.class)) { if (trackFragmentBox.getTrackFragmentHeaderBox().getTrackId() == trackBox.getTrackHeaderBox().getTrackId()) { - i += trackFragmentBox.getBoxes(TrackRunBox.class).get(0).getSampleCount(); + for (TrackRunBox trackRunBox : trackFragmentBox.getBoxes(TrackRunBox.class)) { + i += trackRunBox.getSampleCount(); + } + } } } diff --git a/muxer/src/main/java/org/mp4parser/muxer/tracks/AppendTrack.java b/muxer/src/main/java/org/mp4parser/muxer/tracks/AppendTrack.java index 94e425764..cd8dbfc7f 100644 --- a/muxer/src/main/java/org/mp4parser/muxer/tracks/AppendTrack.java +++ b/muxer/src/main/java/org/mp4parser/muxer/tracks/AppendTrack.java @@ -49,6 +49,7 @@ public class AppendTrack extends AbstractTrack { Track[] tracks; SampleDescriptionBox stsd; List lists; + long[] decodingTimes; public AppendTrack(Track... tracks) throws IOException { super(appendTracknames(tracks)); @@ -67,9 +68,22 @@ public AppendTrack(Track... tracks) throws IOException { lists = new ArrayList(); for (Track track : tracks) { - System.err.println("Track " + track + " is about to be appended"); + //System.err.println("Track " + track + " is about to be appended"); lists.addAll(track.getSamples()); } + + int numSamples = 0; + for (Track track : tracks) { + numSamples += track.getSampleDurations().length; + } + decodingTimes = new long[numSamples]; + int index = 0; + // should use system arraycopy but this works too (yes it's slow ...) + for (Track track : tracks) { + long[] durs = track.getSampleDurations(); + System.arraycopy(durs, 0, decodingTimes, index, durs.length); + index += durs.length; + } } public static String appendTracknames(Track... tracks) { @@ -388,18 +402,6 @@ public SampleDescriptionBox getSampleDescriptionBox() { } public synchronized long[] getSampleDurations() { - int numSamples = 0; - for (Track track : tracks) { - numSamples += track.getSampleDurations().length; - } - long[] decodingTimes = new long[numSamples]; - int index = 0; - // should use system arraycopy but this works too (yes it's slow ...) - for (Track track : tracks) { - for (long l : track.getSampleDurations()) { - decodingTimes[index++] = l; - } - } return decodingTimes; }