package org.jcodec.containers.mps;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jcodec.common.DemuxerTrackMeta;
import org.jcodec.common.NIOUtils;
import org.jcodec.common.SeekableByteChannel;
import org.jcodec.common.SeekableDemuxerTrack;
import org.jcodec.common.model.Packet;
/**
* 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 SeekableByteChannel ch;
private List<Stream> streams;
private int[] pesTokens;
private short[] pesRLE;
public MPSRandomAccessDemuxer(SeekableByteChannel ch, ByteBuffer index) throws IOException {
this.ch = ch;
streams = parseIndex(index);
}
private List<Stream> parseIndex(ByteBuffer index) throws IOException {
Map<Integer, Stream> streams = new HashMap<Integer, Stream>();
int pesCnt = index.getInt();
pesTokens = new int[pesCnt];
pesRLE = new short[pesCnt];
for (int i = 0; i < pesCnt; i++) {
byte b0 = index.get();
pesRLE[i] = (short) ((b0 & 0x80) != 0 ? (((b0 & 0x7f) << 8) | (index.get() & 0xff)) + 1 : (b0 & 0xff) + 1);
pesTokens[i] = index.getInt();
}
while (index.hasRemaining()) {
int stream = index.get() & 0xff;
getStream(streams, stream).parseIndex(index);
}
return new ArrayList<Stream>(streams.values());
}
public List<Stream> getStreams() {
return streams;
}
private Stream getStream(Map<Integer, Stream> streams, int streamId) {
Stream stream = streams.get(streamId);
if (stream == null) {
stream = new Stream(streamId, ch);
streams.put(streamId, stream);
}
return stream;
}
public class Stream implements SeekableDemuxerTrack {
private int[] fsizes;
private long[] foffs;
private int curPesIdx;
private int curFrame;
private int siLen;
private ByteBuffer si;
private ByteBuffer pesBuf;
private long curPesSubIdx;
private int streamId;
private long[] fpts;
private int seekToFrame = -1;
private SeekableByteChannel source;
private long duration;
private int[] sync;
public Stream(int streamId, SeekableByteChannel source) {
this.streamId = streamId;
this.source = source;
}
public void parseIndex(ByteBuffer index) throws IOException {
siLen = index.getInt();
int fCnt = index.getInt();
fsizes = new int[fCnt];
foffs = new long[fCnt];
fpts = new long[fCnt];
long foff = siLen;
for (int i = 0; i < fCnt; i++) {
int size = index.getInt();
fsizes[i] = size;
foffs[i] = foff;
foff += size;
}
int syncCount = index.getInt();
sync = new int[syncCount];
for(int i = 0; i < syncCount; i++)
sync[i] = index.getInt();
for (int i = 0; i < fCnt; i++) {
fpts[i] = index.getInt() & 0xffffffffL;
}
long[] seg = Arrays.copyOf(fpts, 100);
Arrays.sort(seg);
duration = (seg[99] - seg[0]) / 100;
seekToFrame = 0;
seekToFrame();
si = pesBuf.duplicate();
si.limit(si.position());
si.position(si.limit() - siLen);
}
@Override
public Packet nextFrame() throws IOException {
int fs = fsizes[curFrame];
ByteBuffer result = ByteBuffer.allocate(fs + si.remaining());
return nextFrame(result);
}
public 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 + si.remaining());
result.put(si.duplicate());
while (result.hasRemaining()) {
if (pesBuf.hasRemaining()) {
result.put(NIOUtils.read(pesBuf, Math.min(pesBuf.remaining(), result.remaining())));
} else {
++curPesSubIdx;
long posShift = 0;
if (curPesSubIdx == pesRLE[curPesIdx] && curPesIdx < pesTokens.length - 1) {
curPesIdx++;
curPesSubIdx = 0;
for (; streamId(pesTokens[curPesIdx]) != streamId && curPesIdx < pesTokens.length; curPesIdx++)
posShift += (payLoadSize(pesTokens[curPesIdx]) + leadingSize(pesTokens[curPesIdx]))
* pesRLE[curPesIdx];
}
source.position(source.position() + posShift + leadingSize(pesTokens[curPesIdx]));
pesBuf = NIOUtils.fetchFrom(source, payLoadSize(pesTokens[curPesIdx]));
}
}
result.flip();
Packet pkt = new Packet(result, fpts[curFrame], 90000, duration, curFrame, sync.length == 0 || Arrays.binarySearch(sync, curFrame) >= 0, null);
curFrame++;
return pkt;
}
@Override
public DemuxerTrackMeta getMeta() {
return null;
}
private int payLoadSize(int token) {
return token & 0xffff;
}
private int streamId(int token) {
return token >>> 24;
}
private int leadingSize(int token) {
return (token >>> 16) & 0xff;
}
@Override
public boolean gotoFrame(long i) {
seekToFrame = (int) i;
return true;
}
private void seekToFrame() throws IOException {
if (seekToFrame == -1)
return;
curFrame = seekToFrame;
long payloadOff = foffs[curFrame];
long fileOff = 0;
for (curPesIdx = 0;; curPesIdx++) {
int payloadSize = payLoadSize(pesTokens[curPesIdx]) * pesRLE[curPesIdx];
int leadingSize = leadingSize(pesTokens[curPesIdx]) * pesRLE[curPesIdx];
if (streamId(pesTokens[curPesIdx]) == streamId) {
if (payloadOff < payloadSize)
break;
payloadOff -= payloadSize;
}
fileOff += payloadSize + leadingSize;
}
curPesSubIdx = payloadOff / payLoadSize(pesTokens[curPesIdx]);
payloadOff = payloadOff % payLoadSize(pesTokens[curPesIdx]);
fileOff += curPesSubIdx * (payLoadSize(pesTokens[curPesIdx]) + leadingSize(pesTokens[curPesIdx]));
fileOff += leadingSize(pesTokens[curPesIdx]);
source.position(fileOff);
pesBuf = NIOUtils.fetchFrom(source, payLoadSize(pesTokens[curPesIdx]));
NIOUtils.skip(pesBuf, (int) payloadOff);
seekToFrame = -1;
}
@Override
public long getCurFrame() {
return curFrame;
}
@Override
public void seek(double second) {
throw new UnsupportedOperationException();
}
}
}