org.jcodec.containers.mps.index.BaseIndexer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jcodec Show documentation
Show all versions of jcodec Show documentation
Pure Java implementation of video/audio codecs and formats
package org.jcodec.containers.mps.index;
import org.jcodec.codecs.mpeg12.MPEGConst;
import org.jcodec.common.ArrayUtil;
import org.jcodec.common.IntArrayList;
import org.jcodec.common.LongArrayList;
import org.jcodec.common.RunLength;
import org.jcodec.common.logging.Logger;
import org.jcodec.common.tools.MathUtil;
import org.jcodec.containers.mps.MPSUtils;
import org.jcodec.containers.mps.PESPacket;
import org.jcodec.containers.mps.index.MPSIndex.MPSStreamIndex;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
* 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 analyzers;
private LongArrayList tokens;
private RunLength.Integer streams;
public BaseIndexer() {
this.analyzers = new HashMap();
this.tokens = LongArrayList.createLongArrayList();
this.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;
}
protected static abstract class BaseAnalyser {
protected IntArrayList pts;
protected IntArrayList dur;
public BaseAnalyser() {
this.pts = new IntArrayList(250000);
this.dur = new IntArrayList(250000);
}
public abstract void pkt(ByteBuffer pkt, PESPacket pesHeader);
public abstract void finishAnalyse();
public int estimateSize() {
return (pts.size() << 2) + 4;
}
public abstract MPSStreamIndex serialize(int streamId);
}
// TODO: check how ES are packetized in the following audio formats:
// mp1, mp2, s302m, aac, pcm_s16le, pcm_s16be, pcm_dvd, mp3, ac3, dts,
private static class GenericAnalyser extends BaseAnalyser {
private IntArrayList sizes;
private int knownDuration;
private long lastPts;
public GenericAnalyser() {
super();
this.sizes = new IntArrayList(250000);
}
public void pkt(ByteBuffer pkt, PESPacket pesHeader) {
sizes.add(pkt.remaining());
if (pesHeader.pts == -1) {
pesHeader.pts = lastPts + knownDuration;
} else {
knownDuration = (int) (pesHeader.pts - lastPts);
lastPts = pesHeader.pts;
}
pts.add((int) pesHeader.pts);
dur.add(knownDuration);
}
public MPSStreamIndex serialize(int streamId) {
return new MPSStreamIndex(streamId, sizes.toArray(), pts.toArray(), dur.toArray(), new int[0]);
}
@Override
public int estimateSize() {
return super.estimateSize() + (sizes.size() << 2) + 32;
}
@Override
public void finishAnalyse() {
}
}
private static class MPEGVideoAnalyser extends BaseAnalyser {
private int marker = -1;
private long position;
private IntArrayList sizes;
private IntArrayList keyFrames;
private int frameNo;
private boolean inFrameData;
private Frame lastFrame;
private List curGop;
private long phPos = -1;
private Frame lastFrameOfLastGop;
public MPEGVideoAnalyser() {
super();
this.sizes = new IntArrayList(250000);
this.keyFrames = new IntArrayList(20000);
this.curGop = new ArrayList();
}
private static class Frame {
long offset;
int size;
int pts;
int tempRef;
}
public void pkt(ByteBuffer pkt, PESPacket pesHeader) {
while (pkt.hasRemaining()) {
int b = pkt.get() & 0xff;
++position;
marker = (marker << 8) | b;
if (phPos != -1) {
long phOffset = position - phPos;
if (phOffset == 5)
lastFrame.tempRef = b << 2;
else if (phOffset == 6) {
int picCodingType = (b >> 3) & 0x7;
lastFrame.tempRef |= b >> 6;
if (picCodingType == MPEGConst.IntraCoded) {
keyFrames.add(frameNo - 1);
if (curGop.size() > 0)
outGop();
}
}
}
if ((marker & 0xffffff00) != 0x100)
continue;
if (inFrameData && (marker == 0x100 || marker > 0x1af)) {
// End of frame
lastFrame.size = (int) (position - 4 - lastFrame.offset);
curGop.add(lastFrame);
lastFrame = null;
inFrameData = false;
} else if (!inFrameData && (marker > 0x100 && marker <= 0x1af)) {
inFrameData = true;
}
if (lastFrame == null && (marker == 0x1b3 || marker == 0x1b8 || marker == 0x100)) {
Frame frame = new Frame();
frame.pts = (int) pesHeader.pts;
frame.offset = position - 4;
Logger.info(String.format("FRAME[%d]: %012x, %d", frameNo, (pesHeader.pos + pkt.position()
- 4), pesHeader.pts));
frameNo++;
lastFrame = frame;
}
if (lastFrame != null && lastFrame.pts == -1 && marker == 0x100) {
lastFrame.pts = (int) pesHeader.pts;
}
phPos = marker == 0x100 ? position - 4 : -1;
}
}
private void outGop() {
fixPts(curGop);
for (Frame frame : curGop) {
sizes.add(frame.size);
pts.add(frame.pts);
}
curGop.clear();
}
private void fixPts(List curGop) {
Frame[] frames = curGop.toArray(new Frame[0]);
Arrays.sort(frames, new Comparator() {
public int compare(Frame o1, Frame o2) {
return o1.tempRef > o2.tempRef ? 1 : (o1.tempRef == o2.tempRef ? 0 : -1);
}
});
for (int dir = 0; dir < 3; dir++) {
for (int i = 0, lastPts = -1, secondLastPts = -1, lastTref = -1, secondLastTref = -1; i < frames.length; i++) {
if (frames[i].pts == -1 && lastPts != -1 && secondLastPts != -1)
frames[i].pts = lastPts + (lastPts - secondLastPts) / MathUtil.abs(lastTref - secondLastTref);
if (frames[i].pts != -1) {
secondLastPts = lastPts;
secondLastTref = lastTref;
lastPts = frames[i].pts;
lastTref = frames[i].tempRef;
}
}
ArrayUtil.reverse(frames);
}
if (lastFrameOfLastGop != null) {
dur.add(frames[0].pts - lastFrameOfLastGop.pts);
}
for (int i = 1; i < frames.length; i++) {
dur.add(frames[i].pts - frames[i - 1].pts);
}
lastFrameOfLastGop = frames[frames.length - 1];
}
@Override
public void finishAnalyse() {
if (lastFrame == null)
return;
lastFrame.size = (int) (position - lastFrame.offset);
curGop.add(lastFrame);
outGop();
}
public MPSStreamIndex serialize(int streamId) {
return new MPSStreamIndex(streamId, sizes.toArray(), pts.toArray(), dur.toArray(), keyFrames.toArray());
}
}
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);
}
public MPSIndex serialize() {
List streamsIndices = new ArrayList();
Set> entrySet = analyzers.entrySet();
for (Entry entry : entrySet) {
streamsIndices.add(entry.getValue().serialize(entry.getKey()));
}
return new MPSIndex(tokens.toArray(), streams, streamsIndices.toArray(new MPSStreamIndex[0]));
}
protected void savePESMeta(int stream, long token) {
tokens.add(token);
streams.add(stream);
}
void finishAnalyse() {
super.finishRead();
for (BaseAnalyser baseAnalyser : analyzers.values()) {
baseAnalyser.finishAnalyse();
}
}
}