package org.jcodec.containers.mp4.demuxer;
import static org.jcodec.containers.mp4.QTTimeUtil.mediaToEdited;
import static org.jcodec.containers.mp4.boxes.Box.findFirst;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.TrackType;
import org.jcodec.containers.mp4.boxes.Box;
import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox;
import org.jcodec.containers.mp4.boxes.CompositionOffsetsBox.Entry;
import org.jcodec.containers.mp4.boxes.MovieBox;
import org.jcodec.containers.mp4.boxes.SampleSizesBox;
import org.jcodec.containers.mp4.boxes.SyncSamplesBox;
import org.jcodec.containers.mp4.boxes.TrakBox;
import static org.jcodec.common.DemuxerTrackMeta.Type.*;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Regular MP4 track containing frames
*
* @author The JCodec project
*
*/
public class FramesMP4DemuxerTrack extends AbstractMP4DemuxerTrack {
private int[] sizes;
private long offInChunk;
private int noInChunk;
private int[] syncSamples;
private int[] partialSync;
private int ssOff;
private int psOff;
private Entry[] compOffsets;
private int cttsInd;
private int cttsSubInd;
private SeekableByteChannel input;
private MovieBox movie;
public FramesMP4DemuxerTrack(MovieBox mov, TrakBox trak, SeekableByteChannel input) {
super(trak);
this.input = input;
this.movie = mov;
SampleSizesBox stsz = findFirst(trak, SampleSizesBox.class, "mdia", "minf", "stbl", "stsz");
SyncSamplesBox stss = Box.findFirst(trak, SyncSamplesBox.class, "mdia", "minf", "stbl", "stss");
SyncSamplesBox stps = Box.findFirst(trak, SyncSamplesBox.class, "mdia", "minf", "stbl", "stps");
CompositionOffsetsBox ctts = Box.findFirst(trak, CompositionOffsetsBox.class, "mdia", "minf", "stbl", "ctts");
compOffsets = ctts == null ? null : ctts.getEntries();
if (stss != null) {
syncSamples = stss.getSyncSamples();
}
if(stps != null) {
partialSync = stps.getSyncSamples();
}
sizes = stsz.getSizes();
}
public synchronized MP4Packet nextFrame() throws IOException {
if (curFrame >= sizes.length)
return null;
int size = sizes[(int) curFrame];
return nextFrame(ByteBuffer.allocate(size));
}
public synchronized MP4Packet nextFrame(ByteBuffer storage) throws IOException {
if (curFrame >= sizes.length)
return null;
int size = sizes[(int) curFrame];
if (storage != null && storage.remaining() < size) {
throw new IllegalArgumentException("Buffer size is not enough to fit a packet");
}
long pktPos = chunkOffsets[stcoInd] + offInChunk;
ByteBuffer result = readPacketData(input, storage, pktPos, size);
if (result != null && result.remaining() < size)
return null;
int duration = timeToSamples[sttsInd].getSampleDuration();
boolean sync = syncSamples == null;
if (syncSamples != null && ssOff < syncSamples.length && (curFrame + 1) == syncSamples[ssOff]) {
sync = true;
ssOff++;
}
boolean psync = false;
if (partialSync != null && psOff < partialSync.length && (curFrame + 1) == partialSync[psOff]) {
psync = true;
psOff++;
}
long realPts = pts;
if (compOffsets != null) {
realPts = pts + compOffsets[cttsInd].getOffset();
cttsSubInd++;
if (cttsInd < compOffsets.length - 1 && cttsSubInd == compOffsets[cttsInd].getCount()) {
cttsInd++;
cttsSubInd = 0;
}
}
MP4Packet pkt = new MP4Packet(result, mediaToEdited(box, realPts, movie.getTimescale()), timescale, duration,
curFrame, sync, null, realPts, sampleToChunks[stscInd].getEntry() - 1, pktPos, size, psync);
offInChunk += size;
curFrame++;
noInChunk++;
if (noInChunk >= sampleToChunks[stscInd].getCount()) {
noInChunk = 0;
offInChunk = 0;
nextChunk();
}
shiftPts(1);
return pkt;
}
protected void seekPointer(long frameNo) {
if (compOffsets != null) {
cttsSubInd = (int) frameNo;
cttsInd = 0;
while (cttsSubInd >= compOffsets[cttsInd].getCount()) {
cttsSubInd -= compOffsets[cttsInd].getCount();
cttsInd++;
}
}
curFrame = (int) frameNo;
stcoInd = 0;
stscInd = 0;
noInChunk = (int) frameNo;
offInChunk = 0;
while (noInChunk >= sampleToChunks[stscInd].getCount()) {
noInChunk -= sampleToChunks[stscInd].getCount();
nextChunk();
}
for (int i = 0; i < noInChunk; i++) {
offInChunk += sizes[(int) frameNo - noInChunk + i];
}
if (syncSamples != null)
for (ssOff = 0; ssOff < syncSamples.length && syncSamples[ssOff] < curFrame + 1; ssOff++)
;
if (partialSync != null)
for (psOff = 0; psOff < partialSync.length && partialSync[psOff] < curFrame + 1; psOff++)
;
}
public long getFrameCount() {
return sizes.length;
}
@Override
public DemuxerTrackMeta getMeta() {
int[] copyOf = Arrays.copyOf(syncSamples, syncSamples.length);
for (int i = 0; i < copyOf.length; i++)
copyOf[i]--;
TrackType type = getType();
return new DemuxerTrackMeta(type == TrackType.VIDEO ? VIDEO : (type == TrackType.SOUND ? AUDIO : OTHER),
copyOf, sizes.length, (double) duration / timescale, box.getCodedSize());
}
}