All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jcodec.containers.mps.index.BaseIndexer Maven / Gradle / Ivy

There is a newer version: 0.2.5
Show newest version
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();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy