package org.jcodec.containers.mps; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import org.jcodec.codecs.mpeg12.bitstream.PictureHeader; import org.jcodec.common.IntArrayList; import org.jcodec.common.LongArrayList; import org.jcodec.common.RunLength; import org.jcodec.containers.mps.MPSDemuxer.PESPacket; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Indexes MPEG PS/TS file for the purpose of quick random access in the future * * @author The JCodec project * */ public abstract class BaseIndexer extends MPSUtils.PESReader { private Map<Integer, BaseAnalyser> analyzers = new HashMap<Integer, BaseAnalyser>(); private LongArrayList tokens = new LongArrayList(); private RunLength.Integer streams = new RunLength.Integer(); public int estimateSize() { int sizeEstimate = (tokens.size() << 3) + streams.estimateSize() + 128; for (Integer stream : analyzers.keySet()) { sizeEstimate += analyzers.get(stream).estimateSize(); } return sizeEstimate; } public void serializeTo(ByteBuffer index) { index.putInt(tokens.size()); for (int i = 0; i < tokens.size(); i++) { index.putLong(tokens.get(i)); } streams.serialize(index); for (Integer stream : analyzers.keySet()) { index.put((byte) stream.intValue()); analyzers.get(stream).serialize(index); } } protected abstract class BaseAnalyser { private IntArrayList pts = new IntArrayList(250000); public abstract void pkt(ByteBuffer pkt, PESPacket pesHeader); public int estimateSize() { return (pts.size() << 2) + 4; } public abstract void serialize(ByteBuffer bb); public void framePts(PESPacket pesHeader) { if (pesHeader.pts == -1) pts.add(pts.get(pts.size() - 1)); else pts.add((int) pesHeader.pts); } public void serializePts(ByteBuffer bb) { for (int i = 0; i < pts.size(); i++) bb.putInt(pts.get(i)); } } private class GenericAnalyser extends BaseAnalyser { private IntArrayList sizes = new IntArrayList(250000); public void pkt(ByteBuffer pkt, PESPacket pesHeader) { sizes.add(pkt.remaining()); super.framePts(pesHeader); } public void serialize(ByteBuffer bb) { bb.putInt(0); int[] array = sizes.toArray(); bb.putInt(array.length); for (int i = 0; i < array.length; i++) bb.putInt(array[i]); bb.putInt(0); // key frames table super.serializePts(bb); } @Override public int estimateSize() { return super.estimateSize() + (sizes.size() << 2) + 32; } } private class MPEGVideoAnalyser extends BaseAnalyser { private int marker = -1; private long position; private long prevFrame = -1; private IntArrayList sizes = new IntArrayList(250000); private IntArrayList keyFrames = new IntArrayList(20000); private int siSize; private int frameNo; public void pkt(ByteBuffer pkt, PESPacket pesHeader) { while (pkt.hasRemaining()) { int b = pkt.get() & 0xff; ++position; marker = (marker << 8) | b; if (marker == 0x100) { long frameStart = position - 4; if (prevFrame != -1) { sizes.add((int) (frameStart - prevFrame)); } else siSize = (int) frameStart; super.framePts(pesHeader); prevFrame = frameStart; System.out.println(String.format("FRAME[%d]: %012x, %d", frameNo, (pesHeader.pos + pkt.position() - 4), pesHeader.pts)); frameNo++; } if (position - prevFrame == 6) { int picCodingType = (b >> 3) & 0x7; if (picCodingType == PictureHeader.IntraCoded) keyFrames.add(frameNo - 1); } } } public void serialize(ByteBuffer bb) { bb.putInt(siSize); int[] array = sizes.toArray(); bb.putInt(array.length + 1); for (int i = 0; i < array.length; i++) bb.putInt(array[i]); bb.putInt((int) (position - prevFrame)); bb.putInt(keyFrames.size()); for (int i = 0; i < keyFrames.size(); i++) bb.putInt(keyFrames.get(i)); super.serializePts(bb); } @Override public int estimateSize() { return super.estimateSize() + ((sizes.size() + keyFrames.size()) << 2) + 64; } } protected BaseAnalyser getAnalyser(int stream) { BaseAnalyser analizer = analyzers.get(stream); if (analizer == null) { analizer = stream >= 0xe0 && stream <= 0xef ? new MPEGVideoAnalyser() : new GenericAnalyser(); analyzers.put(stream, analizer); } return analyzers.get(stream); } protected void savePesMeta(int stream, long leading, long pesLen, long payloadLen) { long token = (leading << 48) | (pesLen << 24) | payloadLen; tokens.add(token); streams.add(stream); } }