package org.jcodec.containers.mp4.muxer; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Date; import org.jcodec.common.Assert; import org.jcodec.common.LongArrayList; import org.jcodec.common.SeekableByteChannel; import org.jcodec.common.model.Rational; import org.jcodec.common.model.Size; import org.jcodec.common.model.Unit; import org.jcodec.containers.mp4.TrackType; import org.jcodec.containers.mp4.boxes.Box; import org.jcodec.containers.mp4.boxes.ChunkOffsets64Box; import org.jcodec.containers.mp4.boxes.HandlerBox; import org.jcodec.containers.mp4.boxes.Header; import org.jcodec.containers.mp4.boxes.MediaBox; import org.jcodec.containers.mp4.boxes.MediaHeaderBox; import org.jcodec.containers.mp4.boxes.MediaInfoBox; import org.jcodec.containers.mp4.boxes.MovieHeaderBox; import org.jcodec.containers.mp4.boxes.NodeBox; import org.jcodec.containers.mp4.boxes.SampleDescriptionBox; import org.jcodec.containers.mp4.boxes.SampleEntry; import org.jcodec.containers.mp4.boxes.SampleSizesBox; import org.jcodec.containers.mp4.boxes.SampleToChunkBox; import org.jcodec.containers.mp4.boxes.TimeToSampleBox; import org.jcodec.containers.mp4.boxes.TrackHeaderBox; import org.jcodec.containers.mp4.boxes.TrakBox; import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry; import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * @author The JCodec project * */ public class PCMMP4MuxerTrack extends AbstractMP4MuxerTrack { private int frameDuration; private int frameSize; private int framesInCurChunk; private LongArrayList chunkOffsets = new LongArrayList(); private int totalFrames; private SeekableByteChannel out; public PCMMP4MuxerTrack(SeekableByteChannel out, int trackId, TrackType type, int timescale, int frameDuration, int frameSize, SampleEntry se) { super(trackId, type, timescale); this.out = out; this.frameDuration = frameDuration; this.frameSize = frameSize; addSampleEntry(se); setTgtChunkDuration(new Rational(1, 2), Unit.SEC); } public void addSamples(ByteBuffer buffer) throws IOException { curChunk.add(buffer); int frames = buffer.remaining() / frameSize; totalFrames += frames; framesInCurChunk += frames; chunkDuration += frames * frameDuration; outChunkIfNeeded(); } private void outChunkIfNeeded() throws IOException { Assert.assertTrue(tgtChunkDurationUnit == Unit.FRAME || tgtChunkDurationUnit == Unit.SEC); if (tgtChunkDurationUnit == Unit.FRAME && framesInCurChunk * tgtChunkDuration.getDen() == tgtChunkDuration.getNum()) { outChunk(); } else if (tgtChunkDurationUnit == Unit.SEC && chunkDuration > 0 && chunkDuration * tgtChunkDuration.getDen() >= tgtChunkDuration.getNum() * timescale) { outChunk(); } } private void outChunk() throws IOException { if (framesInCurChunk == 0) return; chunkOffsets.add(out.position()); for (ByteBuffer b : curChunk) { out.write(b); } curChunk.clear(); if (samplesInLastChunk == -1 || framesInCurChunk != samplesInLastChunk) { samplesInChunks.add(new SampleToChunkEntry(chunkNo + 1, framesInCurChunk, 1)); } samplesInLastChunk = framesInCurChunk; chunkNo++; framesInCurChunk = 0; chunkDuration = 0; } protected Box finish(MovieHeaderBox mvhd) throws IOException { if (finished) throw new IllegalStateException("The muxer track has finished muxing"); outChunk(); finished = true; TrakBox trak = new TrakBox(); Size dd = getDisplayDimensions(); TrackHeaderBox tkhd = new TrackHeaderBox(trackId, ((long) mvhd.getTimescale() * totalFrames * frameDuration) / timescale, dd.getWidth(), dd.getHeight(), new Date().getTime(), new Date().getTime(), 1.0f, (short) 0, 0, new int[] { 0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x40000000 }); tkhd.setFlags(0xf); trak.add(tkhd); tapt(trak); MediaBox media = new MediaBox(); trak.add(media); media.add(new MediaHeaderBox(timescale, totalFrames * frameDuration, 0, new Date().getTime(), new Date() .getTime(), 0)); HandlerBox hdlr = new HandlerBox("mhlr", type.getHandler(), "appl", 0, 0); media.add(hdlr); MediaInfoBox minf = new MediaInfoBox(); media.add(minf); mediaHeader(minf, type); minf.add(new HandlerBox("dhlr", "url ", "appl", 0, 0)); addDref(minf); NodeBox stbl = new NodeBox(new Header("stbl")); minf.add(stbl); putEdits(trak); putName(trak); stbl.add(new SampleDescriptionBox(sampleEntries.toArray(new SampleEntry[0]))); stbl.add(new SampleToChunkBox(samplesInChunks.toArray(new SampleToChunkEntry[0]))); stbl.add(new SampleSizesBox(frameSize, totalFrames)); stbl.add(new TimeToSampleBox(new TimeToSampleEntry[] { new TimeToSampleEntry(totalFrames, frameDuration) })); stbl.add(new ChunkOffsets64Box(chunkOffsets.toArray())); return trak; } public long getTrackTotalDuration() { return totalFrames * frameDuration; } }