package org.jcodec.containers.mp4.muxer;
import static org.jcodec.containers.mp4.MP4TrackType.SOUND;
import static org.jcodec.containers.mp4.MP4TrackType.TIMECODE;
import static org.jcodec.containers.mp4.MP4TrackType.VIDEO;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import org.jcodec.api.UnhandledStateException;
import org.jcodec.common.MuxerTrack;
import org.jcodec.common.model.Rational;
import org.jcodec.common.model.Size;
import org.jcodec.common.model.Unit;
import org.jcodec.containers.mp4.MP4TrackType;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.Box.LeafBox;
import org.jcodec.containers.mp4.boxes.ClearApertureBox;
import org.jcodec.containers.mp4.boxes.DataInfoBox;
import org.jcodec.containers.mp4.boxes.DataRefBox;
import org.jcodec.containers.mp4.boxes.Edit;
import org.jcodec.containers.mp4.boxes.EditListBox;
import org.jcodec.containers.mp4.boxes.EncodedPixelBox;
import org.jcodec.containers.mp4.boxes.GenericMediaInfoBox;
import org.jcodec.containers.mp4.boxes.Header;
import org.jcodec.containers.mp4.boxes.MediaInfoBox;
import org.jcodec.containers.mp4.boxes.MovieHeaderBox;
import org.jcodec.containers.mp4.boxes.NameBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.PixelAspectExt;
import org.jcodec.containers.mp4.boxes.ProductionApertureBox;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
import org.jcodec.containers.mp4.boxes.SoundMediaHeaderBox;
import org.jcodec.containers.mp4.boxes.TimecodeMediaInfoBox;
import org.jcodec.containers.mp4.boxes.TrakBox;
import org.jcodec.containers.mp4.boxes.VideoMediaHeaderBox;
import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* @author The JCodec project
*
*/
public abstract class AbstractMP4MuxerTrack implements MuxerTrack {
protected static final int NO_TIMESCALE_SET = -1;
protected int trackId;
protected MP4TrackType type;
protected int _timescale = NO_TIMESCALE_SET;
protected Rational tgtChunkDuration;
protected Unit tgtChunkDurationUnit;
protected long chunkDuration;
protected List<ByteBuffer> curChunk;
protected List<SampleToChunkEntry> samplesInChunks;
protected int samplesInLastChunk = -1;
protected int chunkNo = 0;
protected boolean finished;
protected List<SampleEntry> sampleEntries;
protected List<Edit> edits;
private String name;
public AbstractMP4MuxerTrack(int trackId, MP4TrackType type) {
this.curChunk = new ArrayList<ByteBuffer>();
this.samplesInChunks = new ArrayList<SampleToChunkEntry>();
this.sampleEntries = new ArrayList<SampleEntry>();
this.trackId = trackId;
this.type = type;
}
public void setTgtChunkDuration(Rational duration, Unit unit) {
this.tgtChunkDuration = duration;
this.tgtChunkDurationUnit = unit;
}
public abstract long getTrackTotalDuration();
protected abstract Box finish(MovieHeaderBox mvhd) throws IOException;
public boolean isVideo() {
return type == VIDEO;
}
public boolean isTimecode() {
return type == TIMECODE;
}
public boolean isAudio() {
return type == SOUND;
}
public Size getDisplayDimensions() {
int width = 0, height = 0;
if (sampleEntries.get(0) instanceof VideoSampleEntry) {
VideoSampleEntry vse = (VideoSampleEntry) sampleEntries.get(0);
PixelAspectExt paspBox = NodeBox.findFirst(vse, PixelAspectExt.class, PixelAspectExt.fourcc());
Rational pasp = paspBox != null ? paspBox.getRational() : new Rational(1, 1);
width = (int) (pasp.getNum() * vse.getWidth()) / pasp.getDen();
height = (int) vse.getHeight();
}
return new Size(width, height);
}
public void tapt(TrakBox trak) {
Size dd = getDisplayDimensions();
if (type == VIDEO) {
NodeBox tapt = new NodeBox(new Header("tapt"));
tapt.add(ClearApertureBox.createClearApertureBox(dd.getWidth(), dd.getHeight()));
tapt.add(ProductionApertureBox.createProductionApertureBox(dd.getWidth(), dd.getHeight()));
tapt.add(EncodedPixelBox.createEncodedPixelBox(dd.getWidth(), dd.getHeight()));
trak.add(tapt);
}
}
protected void addSampleEntry(SampleEntry se) {
if (finished)
throw new IllegalStateException("The muxer track has finished muxing");
sampleEntries.add(se);
}
public List<SampleEntry> getEntries() {
return sampleEntries;
}
public void setEdits(List<Edit> edits) {
this.edits = edits;
}
protected void putEdits(TrakBox trak) {
if (edits != null) {
NodeBox edts = new NodeBox(new Header("edts"));
edts.add(EditListBox.createEditListBox(edits));
trak.add(edts);
}
}
public void setName(String name) {
this.name = name;
}
protected void putName(TrakBox trak) {
if (name != null) {
NodeBox udta = new NodeBox(new Header("udta"));
udta.add(NameBox.createNameBox(name));
trak.add(udta);
}
}
protected void mediaHeader(MediaInfoBox minf, MP4TrackType type) {
if (VIDEO == type) {
VideoMediaHeaderBox vmhd = VideoMediaHeaderBox.createVideoMediaHeaderBox(0, 0, 0, 0);
vmhd.setFlags(1);
minf.add(vmhd);
} else if(SOUND == type) {
SoundMediaHeaderBox smhd = SoundMediaHeaderBox.createSoundMediaHeaderBox();
smhd.setFlags(1);
minf.add(smhd);
} else if(TIMECODE == type) {
NodeBox gmhd = new NodeBox(new Header("gmhd"));
gmhd.add(GenericMediaInfoBox.createGenericMediaInfoBox());
NodeBox tmcd = new NodeBox(new Header("tmcd"));
gmhd.add(tmcd);
tmcd.add(TimecodeMediaInfoBox
.createTimecodeMediaInfoBox((short) 0, (short) 0, (short) 12, new short[] { 0, 0, 0 }, new short[] {
0xff, 0xff, 0xff }, "Lucida Grande"));
minf.add(gmhd);
} else {
throw new UnhandledStateException("Handler " + type.getHandler() + " not supported");
}
}
protected void addDref(NodeBox minf) {
DataInfoBox dinf = DataInfoBox.createDataInfoBox();
minf.add(dinf);
DataRefBox dref = DataRefBox.createDataRefBox();
dinf.add(dref);
dref.add(LeafBox.createLeafBox(Header.createHeader("alis", 0), ByteBuffer.wrap(new byte[] { 0, 0, 0, 1 })));
}
protected int getTimescale() {
return _timescale;
}
}