diff --git a/isoparser/src/main/java/org/mp4parser/AbstractBoxParser.java b/isoparser/src/main/java/org/mp4parser/AbstractBoxParser.java index c5c0e7cb3..0f1731ee4 100644 --- a/isoparser/src/main/java/org/mp4parser/AbstractBoxParser.java +++ b/isoparser/src/main/java/org/mp4parser/AbstractBoxParser.java @@ -17,6 +17,7 @@ import org.mp4parser.boxes.UserBox; import org.mp4parser.tools.IsoTypeReader; +import org.mp4parser.support.AbstractBox; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,6 +115,12 @@ public ParsableBox parseBox(ReadableByteChannel byteChannel, String parentType) ((Buffer)header.get()).rewind(); parsableBox.parse(byteChannel, header.get(), contentSize, this); + + if (parsableBox instanceof AbstractBox) + { + ((AbstractBox)parsableBox).parseDetails(); + } + return parsableBox; } diff --git a/muxer/src/main/java/org/mp4parser/muxer/tracks/AbstractH26XTrack.java b/muxer/src/main/java/org/mp4parser/muxer/tracks/AbstractH26XTrack.java index 9cf2652ad..21a19ba9e 100644 --- a/muxer/src/main/java/org/mp4parser/muxer/tracks/AbstractH26XTrack.java +++ b/muxer/src/main/java/org/mp4parser/muxer/tracks/AbstractH26XTrack.java @@ -132,7 +132,15 @@ public LookAhead(DataSource dataSource) throws IOException { fillBuffer(); } + long lastBufferStartPos = -1; + public void fillBuffer() throws IOException { + if(lastBufferStartPos == bufferStartPos) + { + BUFFER = BUFFER * 2; + } + + lastBufferStartPos = bufferStartPos; buffer = dataSource.map(bufferStartPos, Math.min(dataSource.size() - bufferStartPos, BUFFER)); } diff --git a/muxer/src/main/java/org/mp4parser/muxer/tracks/h265/H265TrackImpl.java b/muxer/src/main/java/org/mp4parser/muxer/tracks/h265/H265TrackImpl.java index 0546351e2..28ee78070 100644 --- a/muxer/src/main/java/org/mp4parser/muxer/tracks/h265/H265TrackImpl.java +++ b/muxer/src/main/java/org/mp4parser/muxer/tracks/h265/H265TrackImpl.java @@ -18,9 +18,7 @@ import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.channels.Channels; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; /** * Takes a raw H265 stream and muxes into an MP4. @@ -42,6 +40,15 @@ public H265TrackImpl(DataSource dataSource) throws IOException { boolean[] vclNalUnitSeenInAU = new boolean[]{false}; boolean[] isIdr = new boolean[]{true}; + visualSampleEntry = new VisualSampleEntry("hvc1"); + visualSampleEntry.setDataReferenceIndex(1); + visualSampleEntry.setDepth(24); + visualSampleEntry.setFrameCount(1); + visualSampleEntry.setHorizresolution(72); + visualSampleEntry.setVertresolution(72); + visualSampleEntry.setWidth(640); + visualSampleEntry.setHeight(480); + visualSampleEntry.setCompressorname("HEVC Coding"); while ((nal = findNextNal(la)) != null) { @@ -83,32 +90,37 @@ public H265TrackImpl(DataSource dataSource) throws IOException { // collect sps/vps/pps switch (unitHeader.nalUnitType) { case NAL_TYPE_PPS_NUT: - ((Buffer)nal).position(2); + ((Buffer)nal).position(0); pps.add(nal.slice()); + // TODO: skip 2 bytes, remove emulation prevention bytes, add PPS parser, parse PPS System.err.println("Stored PPS"); break; case NAL_TYPE_VPS_NUT: - ((Buffer)nal).position(2); + ((Buffer)nal).position(0); vps.add(nal.slice()); + // TODO: skip 2 bytes, remove emulation prevention bytes, parse VPS + // parsedVPS = new VideoParemeterSet(Channels.newInputStream(new ByteBufferByteChannel(nal.slice()))); System.err.println("Stored VPS"); break; case NAL_TYPE_SPS_NUT: - ((Buffer)nal).position(2); + ((Buffer)nal).position(0); sps.add(nal.slice()); - ((Buffer)nal).position(1); - new SequenceParameterSetRbsp(Channels.newInputStream(new ByteBufferByteChannel(nal.slice()))); + // TODO: skip 2 bytes, remove emulation prevention bytes, parse SPS + // parsedSPS = new SequenceParameterSetRbsp(Channels.newInputStream(new ByteBufferByteChannel(nal.slice()))); System.err.println("Stored SPS"); break; case NAL_TYPE_PREFIX_SEI_NUT: + ((Buffer)nal).position(2); new SEIMessage(new BitReaderBuffer(nal.slice())); break; } switch (unitHeader.nalUnitType) { - case NAL_TYPE_SPS_NUT: - case NAL_TYPE_VPS_NUT: - case NAL_TYPE_PPS_NUT: + // for hvc1 these must be in mdat!!! Otherwise the video is not playable. + // case NAL_TYPE_SPS_NUT: + // case NAL_TYPE_VPS_NUT: + // case NAL_TYPE_PPS_NUT: case NAL_TYPE_EOB_NUT: case NAL_TYPE_EOS_NUT: case NAL_TYPE_AUD_NUT: @@ -117,6 +129,7 @@ public H265TrackImpl(DataSource dataSource) throws IOException { break; default: System.err.println("Adding " + unitHeader.nalUnitType); + nal.position(0); nals.add(nal); } if (isVcl(unitHeader)) { @@ -133,7 +146,7 @@ public H265TrackImpl(DataSource dataSource) throws IOException { vclNalUnitSeenInAU[0] |= isVcl(unitHeader); } - visualSampleEntry = createSampleEntry(); + visualSampleEntry = fillSampleEntry(); decodingTimes = new long[samples.size()]; getTrackMetaData().setTimescale(25); Arrays.fill(decodingTimes, 1); @@ -168,21 +181,13 @@ public static void main(String[] args) throws IOException { } - private VisualSampleEntry createSampleEntry() { - - - VisualSampleEntry visualSampleEntry = new VisualSampleEntry("hvc1"); - visualSampleEntry.setDataReferenceIndex(1); - visualSampleEntry.setDepth(24); - visualSampleEntry.setFrameCount(1); - visualSampleEntry.setHorizresolution(72); - visualSampleEntry.setVertresolution(72); - visualSampleEntry.setWidth(640); - visualSampleEntry.setHeight(480); - visualSampleEntry.setCompressorname("HEVC Coding"); + private VisualSampleEntry fillSampleEntry() { HevcConfigurationBox hevcConfigurationBox = new HevcConfigurationBox(); - + hevcConfigurationBox.getHevcDecoderConfigurationRecord().setConfigurationVersion(1); + hevcConfigurationBox.getHevcDecoderConfigurationRecord().setLengthSizeMinusOne(3); // 4 bytes size block inserted in between NAL units + // TODO: fill in other metadata from parsed VPS/SPS + HevcDecoderConfigurationRecord.Array spsArray = new HevcDecoderConfigurationRecord.Array(); spsArray.array_completeness = true; spsArray.nal_unit_type = NAL_TYPE_SPS_NUT; @@ -201,15 +206,23 @@ private VisualSampleEntry createSampleEntry() { HevcDecoderConfigurationRecord.Array vpsArray = new HevcDecoderConfigurationRecord.Array(); vpsArray.array_completeness = true; - vpsArray.nal_unit_type = NAL_TYPE_PPS_NUT; + vpsArray.nal_unit_type = NAL_TYPE_VPS_NUT; vpsArray.nalUnits = new ArrayList(); for (ByteBuffer vp : vps) { vpsArray.nalUnits.add(toArray(vp)); } - hevcConfigurationBox.getArrays().addAll(Arrays.asList(spsArray, vpsArray, ppsArray)); + // correct order is VPS, SPS, PPS. Other order produced ffmpeg errors such as "VPS 0 does not exist" and "SPS 0 does not exist." + hevcConfigurationBox.getArrays().addAll(Arrays.asList(vpsArray, spsArray, ppsArray)); visualSampleEntry.addBox(hevcConfigurationBox); + + trackMetaData.setCreationTime(new Date()); + trackMetaData.setModificationTime(new Date()); + trackMetaData.setLanguage("enu"); + trackMetaData.setTimescale(25); + // TODO: fill in other metadata from parsed VPS/SPS + return visualSampleEntry; } @@ -228,7 +241,7 @@ public void wrapUp(List nals, boolean[] vclNalUnitSeenInAU, boolean[ } public List getSampleEntries() { - return null; + return Collections.singletonList(visualSampleEntry); } public String getHandler() { diff --git a/muxer/src/test/java/org/mp4parser/muxer/tracks/H265TrackImplTest.java b/muxer/src/test/java/org/mp4parser/muxer/tracks/H265TrackImplTest.java new file mode 100644 index 000000000..d30276eb8 --- /dev/null +++ b/muxer/src/test/java/org/mp4parser/muxer/tracks/H265TrackImplTest.java @@ -0,0 +1,54 @@ +/* + * 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 org.mp4parser.muxer.tracks; + +import org.junit.Test; +import org.mp4parser.Container; +import org.mp4parser.IsoFile; +import org.mp4parser.muxer.DataSource; +import org.mp4parser.muxer.FileDataSourceImpl; +import org.mp4parser.muxer.Movie; +import org.mp4parser.muxer.Track; +import org.mp4parser.muxer.builder.DefaultMp4Builder; +import org.mp4parser.muxer.tracks.h265.H265TrackImpl; +import org.mp4parser.support.BoxComparator; + +import java.io.FileOutputStream; +import java.io.IOException; + +/** + * Simple test to make sure nothing breaks. + */ +public class H265TrackImplTest { + + @Test + public void freeze() throws IOException { + DataSource fc = new FileDataSourceImpl(getClass().getProtectionDomain().getCodeSource().getLocation().getFile() + "/org/mp4parser/muxer/tracks/hevc.h265"); + H265TrackImpl.BUFFER = 65535; // make sure we are not just in one buffer + Track t = new H265TrackImpl(fc); + Movie m = new Movie(); + m.addTrack(t); + + DefaultMp4Builder mp4Builder = new DefaultMp4Builder(); + Container c = mp4Builder.build(m); + + c.writeContainer(new FileOutputStream("C:\\Temp\\h265-sample-java.mp4").getChannel()); + + + IsoFile isoFileReference = new IsoFile(getClass().getProtectionDomain().getCodeSource().getLocation().getFile() + "org/mp4parser/muxer/tracks/h265-sample.mp4"); + BoxComparator.check(c, isoFileReference, "moov[0]/mvhd[0]", "moov[0]/trak[0]/tkhd[0]", "moov[0]/trak[0]/mdia[0]/mdhd[0]", "moov[0]/trak[0]/mdia[0]/minf[0]/stbl[0]/stco[0]"); + } +} diff --git a/muxer/src/test/resources/org/mp4parser/muxer/tracks/h265-sample.mp4 b/muxer/src/test/resources/org/mp4parser/muxer/tracks/h265-sample.mp4 new file mode 100644 index 000000000..af52de059 Binary files /dev/null and b/muxer/src/test/resources/org/mp4parser/muxer/tracks/h265-sample.mp4 differ diff --git a/muxer/src/test/resources/org/mp4parser/muxer/tracks/hevc.h265 b/muxer/src/test/resources/org/mp4parser/muxer/tracks/hevc.h265 new file mode 100644 index 000000000..446769cc2 Binary files /dev/null and b/muxer/src/test/resources/org/mp4parser/muxer/tracks/hevc.h265 differ diff --git a/streaming/src/main/java/org/mp4parser/streaming/output/mp4/FragmentedMp4Writer.java b/streaming/src/main/java/org/mp4parser/streaming/output/mp4/FragmentedMp4Writer.java index 89c00fb08..5b00bd629 100644 --- a/streaming/src/main/java/org/mp4parser/streaming/output/mp4/FragmentedMp4Writer.java +++ b/streaming/src/main/java/org/mp4parser/streaming/output/mp4/FragmentedMp4Writer.java @@ -444,7 +444,7 @@ protected void createTrun(StreamingTrack streamingTrack, TrackFragmentBox parent for (StreamingSample streamingSample : samples) { TrackRunBox.Entry entry = new TrackRunBox.Entry(); - entry.setSampleSize(streamingSample.getContent().remaining()); + entry.setSampleSize(streamingSample.getContent().capacity()); if (defaultSampleFlagsTrackExtension == null) { SampleFlagsSampleExtension sampleFlagsSampleExtension = streamingSample.getSampleExtension(SampleFlagsSampleExtension.class); assert sampleFlagsSampleExtension != null : "SampleDependencySampleExtension missing even though SampleDependencyTrackExtension was present"; diff --git a/streaming/src/test/java/org/mp4parser/streaming/output/mp4/FragmentedMp4WriterTest.java b/streaming/src/test/java/org/mp4parser/streaming/output/mp4/FragmentedMp4WriterTest.java new file mode 100644 index 000000000..786546a52 --- /dev/null +++ b/streaming/src/test/java/org/mp4parser/streaming/output/mp4/FragmentedMp4WriterTest.java @@ -0,0 +1,32 @@ +package org.mp4parser.streaming.output.mp4; + +import org.junit.Test; +import org.mp4parser.streaming.StreamingTrack; +import org.mp4parser.streaming.input.h264.H264AnnexBTrack; + +import java.io.FileOutputStream; +import java.io.OutputStream; +import java.nio.channels.Channels; +import java.util.Collections; + + +public class FragmentedMp4WriterTest { + + @Test + public void testMuxing() throws Exception { + H264AnnexBTrack b = new H264AnnexBTrack(FragmentedMp4WriterTest.class.getResourceAsStream("/org/mp4parser/streaming/input/h264/tos.h264")); + OutputStream baos = new FileOutputStream("output_fragmented.mp4"); + //StandardMp4Writer writer = new StandardMp4Writer(Collections.singletonList(b), Channels.newChannel(baos)); + FragmentedMp4Writer writer = new FragmentedMp4Writer(Collections.singletonList(b), Channels.newChannel(baos)); + b.call(); + writer.close(); + + //Walk.through(isoFile); + //List s = new Mp4SampleList(1, isoFile, new InMemRandomAccessSourceImpl(baos.toByteArray())); + //for (Sample sample : s) { +// System.err.println("s: " + sample.getSize()); + // sample.asByteBuffer(); + // } + } + +} \ No newline at end of file