package org.jcodec.containers.mps.index;
import org.jcodec.api.NotSupportedException;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.io.NIOUtils;
import org.jcodec.common.io.SeekableByteChannel;
import org.jcodec.common.model.Packet;
import org.jcodec.common.model.Packet.FrameType;
import org.jcodec.containers.mps.MPSUtils;
import org.jcodec.containers.mps.index.MPSIndex.MPSStreamIndex;
import org.jcodec.platform.Platform;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* This class is part of JCodec ( www.jcodec.org ) This software is distributed
* under FreeBSD License
*
* Demuxer for MPEG Program Stream format with random access
*
* Uses index to assist random access, see MPSIndexer
*
* @author The JCodec project
*
*/
public class MPSRandomAccessDemuxer {
private Stream[] streams;
private long[] pesTokens;
private int[] pesStreamIds;
public MPSRandomAccessDemuxer(SeekableByteChannel ch, MPSIndex mpsIndex) throws IOException {
pesTokens = mpsIndex.getPesTokens();
pesStreamIds = mpsIndex.getPesStreamIds().flattern();
MPSStreamIndex[] streamIndices = mpsIndex.getStreams();
streams = new Stream[streamIndices.length];
for (int i = 0; i < streamIndices.length; i++) {
streams[i] = newStream(ch, streamIndices[i]);
}
}
protected Stream newStream(SeekableByteChannel ch, MPSStreamIndex streamIndex) throws IOException {
return new Stream(this, streamIndex, ch);
}
public Stream[] getStreams() {
return streams;
}
public static class Stream extends MPSStreamIndex implements SeekableDemuxerTrack {
private static final int MPEG_TIMESCALE = 90000;
private int curPesIdx;
private int curFrame;
private ByteBuffer pesBuf;
private int _seekToFrame = -1;
protected SeekableByteChannel source;
private long[] foffs;
private MPSRandomAccessDemuxer demuxer;
public Stream(MPSRandomAccessDemuxer demuxer, MPSStreamIndex streamIndex, SeekableByteChannel source) throws IOException {
super(streamIndex.streamId, streamIndex.fsizes, streamIndex.fpts, streamIndex.fdur, streamIndex.sync);
this.demuxer = demuxer;
this.source = source;
foffs = new long[fsizes.length];
long curOff = 0;
for (int i = 0; i < fsizes.length; i++) {
foffs[i] = curOff;
curOff += fsizes[i];
}
int[] seg = Platform.copyOfInt(streamIndex.getFpts(), 100);
Arrays.sort(seg);
_seekToFrame = 0;
seekToFrame();
}
@Override
public Packet nextFrame() throws IOException {
seekToFrame();
if(curFrame >= fsizes.length)
return null;
int fs = fsizes[curFrame];
ByteBuffer result = ByteBuffer.allocate(fs);
return _nextFrame(result);
}
private Packet _nextFrame(ByteBuffer buf) throws IOException {
seekToFrame();
if (curFrame >= fsizes.length)
return null;
int fs = fsizes[curFrame];
ByteBuffer result = buf.duplicate();
result.limit(result.position() + fs);
while (result.hasRemaining()) {
if (pesBuf.hasRemaining()) {
result.put(NIOUtils.read(pesBuf, Math.min(pesBuf.remaining(), result.remaining())));
} else {
++curPesIdx;
long posShift = 0;
while (demuxer.pesStreamIds[curPesIdx] != streamId) {
posShift += MPSIndex.pesLen(demuxer.pesTokens[curPesIdx]) + MPSIndex.leadingSize(demuxer.pesTokens[curPesIdx]);
++curPesIdx;
}
skip(posShift + MPSIndex.leadingSize(demuxer.pesTokens[curPesIdx]));
int pesLen = MPSIndex.pesLen(demuxer.pesTokens[curPesIdx]);
pesBuf = fetch(pesLen);
MPSUtils.readPESHeader(pesBuf, 0);
}
}
result.flip();
Packet pkt = Packet.createPacket(result, fpts[curFrame], MPEG_TIMESCALE, fdur[curFrame], curFrame, sync.length == 0
|| Arrays.binarySearch(sync, curFrame) >= 0 ? FrameType.KEY : FrameType.INTER, null);
curFrame++;
return pkt;
}
protected ByteBuffer fetch(int pesLen) throws IOException {
return NIOUtils.fetchFromChannel(source, pesLen);
}
protected void skip(long leadingSize) throws IOException {
source.setPosition(source.position() + leadingSize);
}
protected void reset() throws IOException {
source.setPosition(0);
}
@Override
public DemuxerTrackMeta getMeta() {
return null;
}
@Override
public boolean gotoFrame(long frameNo) {
_seekToFrame = (int) frameNo;
return true;
}
@Override
public boolean gotoSyncFrame(long frameNo) {
for (int i = 0; i < sync.length; i++) {
if (sync[i] > frameNo) {
_seekToFrame = sync[i - 1];
return true;
}
}
_seekToFrame = sync[sync.length - 1];
return true;
}
private void seekToFrame() throws IOException {
if (_seekToFrame == -1)
return;
curFrame = _seekToFrame;
long payloadOff = foffs[curFrame];
long posShift = 0;
reset();
for (curPesIdx = 0;; curPesIdx++) {
if (demuxer.pesStreamIds[curPesIdx] == streamId) {
int payloadSize = MPSIndex.payLoadSize(demuxer.pesTokens[curPesIdx]);
if (payloadOff < payloadSize)
break;
payloadOff -= payloadSize;
}
posShift += MPSIndex.pesLen(demuxer.pesTokens[curPesIdx]) + MPSIndex.leadingSize(demuxer.pesTokens[curPesIdx]);
}
skip(posShift + MPSIndex.leadingSize(demuxer.pesTokens[curPesIdx]));
pesBuf = fetch(MPSIndex.pesLen(demuxer.pesTokens[curPesIdx]));
MPSUtils.readPESHeader(pesBuf, 0);
NIOUtils.skip(pesBuf, (int) payloadOff);
_seekToFrame = -1;
}
@Override
public long getCurFrame() {
return curFrame;
}
@Override
public void seek(double second) {
throw new NotSupportedException();
}
}
}