package org.jcodec.containers.mp4.muxer;
import static org.jcodec.containers.mp4.TrackType.SOUND;
import static org.jcodec.containers.mp4.TrackType.TIMECODE;
import static org.jcodec.containers.mp4.TrackType.VIDEO;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
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.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.LeafBox;
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 {
protected int trackId;
protected TrackType type;
protected int timescale;
protected Rational tgtChunkDuration;
protected Unit tgtChunkDurationUnit;
protected long chunkDuration;
protected List<ByteBuffer> curChunk = new ArrayList<ByteBuffer>();
protected List<SampleToChunkEntry> samplesInChunks = new ArrayList<SampleToChunkEntry>();
protected int samplesInLastChunk = -1;
protected int chunkNo = 0;
protected boolean finished;
protected List<SampleEntry> sampleEntries = new ArrayList<SampleEntry>();
protected List<Edit> edits;
private String name;
public AbstractMP4MuxerTrack(int trackId, TrackType type, int timescale) {
this.trackId = trackId;
this.type = type;
this.timescale = timescale;
}
public void setTgtChunkDuration(Rational duration, Unit unit) {
this.tgtChunkDuration = duration;
this.tgtChunkDurationUnit = unit;
}
public abstract long getTrackTotalDuration();
public int getTimescale() {
return timescale;
}
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 = Box.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(new ClearApertureBox(dd.getWidth(), dd.getHeight()));
tapt.add(new ProductionApertureBox(dd.getWidth(), dd.getHeight()));
tapt.add(new EncodedPixelBox(dd.getWidth(), dd.getHeight()));
trak.add(tapt);
}
}
public 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(new EditListBox(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(new NameBox(name));
trak.add(udta);
}
}
protected void mediaHeader(MediaInfoBox minf, TrackType type) {
switch (type) {
case VIDEO:
VideoMediaHeaderBox vmhd = new VideoMediaHeaderBox(0, 0, 0, 0);
vmhd.setFlags(1);
minf.add(vmhd);
break;
case SOUND:
SoundMediaHeaderBox smhd = new SoundMediaHeaderBox();
smhd.setFlags(1);
minf.add(smhd);
break;
case TIMECODE:
NodeBox gmhd = new NodeBox(new Header("gmhd"));
gmhd.add(new GenericMediaInfoBox());
NodeBox tmcd = new NodeBox(new Header("tmcd"));
gmhd.add(tmcd);
tmcd.add(new TimecodeMediaInfoBox((short) 0, (short) 0, (short) 12, new short[] { 0, 0, 0 }, new short[] {
0xff, 0xff, 0xff }, "Lucida Grande"));
minf.add(gmhd);
break;
default:
throw new IllegalStateException("Handler " + type.getHandler() + " not supported");
}
}
protected void addDref(NodeBox minf) {
DataInfoBox dinf = new DataInfoBox();
minf.add(dinf);
DataRefBox dref = new DataRefBox();
dinf.add(dref);
dref.add(new LeafBox(new Header("alis", 0), ByteBuffer.wrap(new byte[] { 0, 0, 0, 1 })));
}
}