package org.jcodec.containers.mp4.demuxer;
import static org.jcodec.containers.mp4.boxes.Box.findFirst;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.model.RationalLarge;
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.ChunkOffsets64Box;
import org.jcodec.containers.mp4.boxes.ChunkOffsetsBox;
import org.jcodec.containers.mp4.boxes.Edit;
import org.jcodec.containers.mp4.boxes.EditListBox;
import org.jcodec.containers.mp4.boxes.NameBox;
import org.jcodec.containers.mp4.boxes.NodeBox;
import org.jcodec.containers.mp4.boxes.SampleEntry;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox;
import org.jcodec.containers.mp4.boxes.SampleToChunkBox.SampleToChunkEntry;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox;
import org.jcodec.containers.mp4.boxes.TimeToSampleBox.TimeToSampleEntry;
import org.jcodec.containers.mp4.boxes.TrakBox;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Shared routines between PCM and Frames tracks
*
* @author The JCodec project
*
*/
public abstract class AbstractMP4DemuxerTrack implements SeekableDemuxerTrack {
protected TrakBox box;
private TrackType type;
private int no;
protected SampleEntry[] sampleEntries;
protected TimeToSampleEntry[] timeToSamples;
protected SampleToChunkEntry[] sampleToChunks;
protected long[] chunkOffsets;
protected long duration;
protected int sttsInd;
protected int sttsSubInd;
protected int stcoInd;
protected int stscInd;
protected long pts;
protected long curFrame;
protected int timescale;
public AbstractMP4DemuxerTrack(TrakBox trak) {
no = trak.getTrackHeader().getNo();
type = MP4Demuxer.getTrackType(trak);
sampleEntries = Box.findAll(trak, SampleEntry.class, "mdia", "minf", "stbl", "stsd", null);
NodeBox stbl = trak.getMdia().getMinf().getStbl();
TimeToSampleBox stts = findFirst(stbl, TimeToSampleBox.class, "stts");
SampleToChunkBox stsc = findFirst(stbl, SampleToChunkBox.class, "stsc");
ChunkOffsetsBox stco = findFirst(stbl, ChunkOffsetsBox.class, "stco");
ChunkOffsets64Box co64 = findFirst(stbl, ChunkOffsets64Box.class, "co64");
timeToSamples = stts.getEntries();
sampleToChunks = stsc.getSampleToChunk();
chunkOffsets = stco != null ? stco.getChunkOffsets() : co64.getChunkOffsets();
for (TimeToSampleEntry ttse : timeToSamples) {
duration += ttse.getSampleCount() * ttse.getSampleDuration();
}
box = trak;
timescale = trak.getTimescale();
}
public int pts2Sample(long _tv, int _timescale) {
long tv = _tv * timescale / _timescale;
int ttsInd, sample = 0;
for (ttsInd = 0; ttsInd < timeToSamples.length - 1; ttsInd++) {
int a = timeToSamples[ttsInd].getSampleCount() * timeToSamples[ttsInd].getSampleDuration();
if (tv < a)
break;
tv -= a;
sample += timeToSamples[ttsInd].getSampleCount();
}
return sample + (int) (tv / timeToSamples[ttsInd].getSampleDuration());
}
public TrackType getType() {
return type;
}
public int getNo() {
return no;
}
public SampleEntry[] getSampleEntries() {
return sampleEntries;
}
public TrakBox getBox() {
return box;
}
public long getTimescale() {
return timescale;
}
protected abstract void seekPointer(long frameNo);
public boolean canSeek(long pts) {
return pts >= 0 && pts < duration;
}
public synchronized boolean seek(long pts) {
if (pts < 0)
throw new IllegalArgumentException("Seeking to negative pts");
if (pts >= duration)
return false;
long prevDur = 0;
int frameNo = 0;
for (sttsInd = 0; pts > prevDur + timeToSamples[sttsInd].getSampleCount()
* timeToSamples[sttsInd].getSampleDuration()
&& sttsInd < timeToSamples.length - 1; sttsInd++) {
prevDur += timeToSamples[sttsInd].getSampleCount() * timeToSamples[sttsInd].getSampleDuration();
frameNo += timeToSamples[sttsInd].getSampleCount();
}
sttsSubInd = (int) ((pts - prevDur) / timeToSamples[sttsInd].getSampleDuration());
frameNo += sttsSubInd;
this.pts = prevDur + timeToSamples[sttsInd].getSampleDuration() * sttsSubInd;
seekPointer(frameNo);
return true;
}
protected void shiftPts(long frames) {
pts -= sttsSubInd * timeToSamples[sttsInd].getSampleDuration();
sttsSubInd += frames;
while (sttsInd < timeToSamples.length - 1 && sttsSubInd >= timeToSamples[sttsInd].getSampleCount()) {
pts += timeToSamples[sttsInd].getSegmentDuration();
sttsSubInd -= timeToSamples[sttsInd].getSampleCount();
sttsInd++;
}
pts += sttsSubInd * timeToSamples[sttsInd].getSampleDuration();
}
protected void nextChunk() {
if (stcoInd >= chunkOffsets.length)
return;
stcoInd++;
if ((stscInd + 1 < sampleToChunks.length) && stcoInd + 1 == sampleToChunks[stscInd + 1].getFirst()) {
stscInd++;
}
}
public synchronized boolean gotoFrame(long frameNo) {
if (frameNo < 0)
throw new IllegalArgumentException("negative frame number");
if (frameNo >= getFrameCount())
return false;
if (frameNo == curFrame)
return true;
seekPointer(frameNo);
seekPts(frameNo);
return true;
}
@Override
public void seek(double second) {
seek((long) (second * timescale));
}
private void seekPts(long frameNo) {
pts = sttsInd = sttsSubInd = 0;
shiftPts(frameNo);
}
public RationalLarge getDuration() {
return new RationalLarge(box.getMediaDuration(), box.getTimescale());
}
public abstract long getFrameCount();
public long getCurFrame() {
return curFrame;
}
public List<Edit> getEdits() {
EditListBox editListBox = Box.findFirst(box, EditListBox.class, "edts", "elst");
if (editListBox != null)
return editListBox.getEdits();
return null;
}
public String getName() {
NameBox nameBox = Box.findFirst(box, NameBox.class, "udta", "name");
return nameBox != null ? nameBox.getName() : null;
}
public String getFourcc() {
return getSampleEntries()[0].getFourcc();
}
protected ByteBuffer readPacketData(SeekableByteChannel input, ByteBuffer buffer, long offset, int size)
throws IOException {
ByteBuffer result = buffer.duplicate();
synchronized (input) {
input.position(offset);
NIOUtils.read(input, result, size);
}
result.flip();
return result;
}
public abstract MP4Packet nextFrame(ByteBuffer storage) throws IOException;
}