package org.jcodec.containers.mp4.demuxer;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import org.jcodec.codecs.aac.AACUtils;
import org.jcodec.codecs.h264.H264Utils;
import org.jcodec.codecs.h264.mp4.AvcCBox;
import org.jcodec.common.Codec;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.RationalLarge;
import org.jcodec.containers.mp4.MP4Packet;
import org.jcodec.containers.mp4.MP4TrackType;
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;
import org.jcodec.containers.mp4.boxes.VideoSampleEntry;
/**
* 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 MP4TrackType 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 = TrakBox.getTrackType(trak);
sampleEntries = NodeBox.findAllPath(trak, SampleEntry.class, new String[]{"mdia", "minf", "stbl", "stsd", null});
NodeBox stbl = trak.getMdia().getMinf().getStbl();
TimeToSampleBox stts = NodeBox.findFirst(stbl, TimeToSampleBox.class, "stts");
SampleToChunkBox stsc = NodeBox.findFirst(stbl, SampleToChunkBox.class, "stsc");
ChunkOffsetsBox stco = NodeBox.findFirst(stbl, ChunkOffsetsBox.class, "stco");
ChunkOffsets64Box co64 = NodeBox.findFirst(stbl, ChunkOffsets64Box.class, "co64");
timeToSamples = stts.getEntries();
sampleToChunks = stsc.getSampleToChunk();
chunkOffsets = stco != null ? stco.getChunkOffsets() : co64.getChunkOffsets();
for (int i = 0; i < timeToSamples.length; i++) {
TimeToSampleEntry ttse = timeToSamples[i];
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 MP4TrackType 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 seekPts(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);
seekFrame(frameNo);
return true;
}
@Override
public void seek(double second) {
seekPts((long) (second * timescale));
}
private void seekFrame(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 = NodeBox.findFirstPath(box, EditListBox.class, Box.path("edts.elst"));
if (editListBox != null)
return editListBox.getEdits();
return null;
}
public String getName() {
NameBox nameBox = NodeBox.findFirstPath(box, NameBox.class, Box.path("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.setPosition(offset);
NIOUtils.readL(input, result, size);
}
result.flip();
return result;
}
public abstract MP4Packet getNextFrame(ByteBuffer storage) throws IOException;
public Codec getCodec() {
SampleEntry se = getSampleEntries()[0];
String fourcc = se.getHeader().getFourcc();
if (fourcc.equals("avc1")) {
return Codec.H264;
} else if (fourcc.equals("m1v1") || fourcc.equals("m2v1")) {
return Codec.MPEG2;
} else if (fourcc.equals("apco") || fourcc.equals("apcs") || fourcc.equals("apcn") || fourcc.equals("apch")
|| fourcc.equals("ap4h")) {
return Codec.PRORES;
} else if (fourcc.equals("mp4a")) {
return Codec.AAC;
} else if (fourcc.equals("jpeg")) {
return Codec.JPEG;
}
return null;
}
public ByteBuffer convertPacket(ByteBuffer _in) {
return _in;
}
}