package org.jcodec.containers.mps; import static org.jcodec.common.DemuxerTrackMeta.Type.AUDIO; import static org.jcodec.common.DemuxerTrackMeta.Type.OTHER; import static org.jcodec.common.DemuxerTrackMeta.Type.VIDEO; import static org.jcodec.containers.mps.MPSUtils.audioStream; import static org.jcodec.containers.mps.MPSUtils.psMarker; import static org.jcodec.containers.mps.MPSUtils.readPESHeader; import static org.jcodec.containers.mps.MPSUtils.videoStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jcodec.codecs.mpeg12.MPEGES; import org.jcodec.codecs.mpeg12.SegmentReader; import org.jcodec.common.DemuxerTrackMeta; import org.jcodec.common.NIOUtils; import org.jcodec.common.SeekableByteChannel; import org.jcodec.common.model.Packet; import org.jcodec.common.model.TapeTimecode; /** * This class is part of JCodec ( www.jcodec.org ) This software is distributed * under FreeBSD License * * Demuxer for MPEG Program Stream format * * @author The JCodec project * */ public class MPSDemuxer extends SegmentReader implements MPEGDemuxer { private static final int BUFFER_SIZE = 0x100000; private Map<Integer, BaseTrack> streams = new HashMap<Integer, BaseTrack>(); private SeekableByteChannel channel; public MPSDemuxer(SeekableByteChannel channel) throws IOException { super(channel); this.channel = channel; findStreams(); } protected void findStreams() throws IOException { for (int i = 0; i == 0 || i < 5 * streams.size() && streams.size() < 2; i++) { PESPacket nextPacket = nextPacket(getBuffer()); if (nextPacket == null) break; addToStream(nextPacket); } } public static class PESPacket { public ByteBuffer data; public long pts; public int streamId; public int length; public long pos; public PESPacket(ByteBuffer data, long pts, int streamId, int length, long pos) { this.data = data; this.pts = pts; this.streamId = streamId; this.length = length; this.pos = pos; } } private List<ByteBuffer> bufPool = new ArrayList<ByteBuffer>(); public ByteBuffer getBuffer() { synchronized (bufPool) { if (bufPool.size() > 0) { return bufPool.remove(0); } } System.out.println("creating buffer"); return ByteBuffer.allocate(BUFFER_SIZE); } public void putBack(ByteBuffer buffer) { buffer.clear(); synchronized (bufPool) { bufPool.add(buffer); } } public abstract class BaseTrack implements MPEGDemuxer.MPEGDemuxerTrack { protected int streamId; protected List<PESPacket> pending = new ArrayList<PESPacket>(); public BaseTrack(int streamId, PESPacket pkt) throws IOException { this.streamId = streamId; this.pending.add(pkt); } public int getSid() { return streamId; } public void pending(PESPacket pkt) { if (pending != null) pending.add(pkt); else putBack(pkt.data); } public List<PESPacket> getPending() { return pending; } @Override public void ignore() { if (pending == null) return; for (PESPacket pesPacket : pending) { putBack(pesPacket.data); } pending = null; } } public class MPEGTrack extends BaseTrack implements ReadableByteChannel { private MPEGES es; public MPEGTrack(int streamId, PESPacket pkt) throws IOException { super(streamId, pkt); this.es = new MPEGES(this); } public boolean isOpen() { return true; } public MPEGES getES() { return es; } public void close() throws IOException { } public int read(ByteBuffer arg0) throws IOException { PESPacket pes = pending.size() > 0 ? pending.remove(0) : getPacket(); if (pes == null || !pes.data.hasRemaining()) return -1; int toRead = Math.min(arg0.remaining(), pes.data.remaining()); arg0.put(NIOUtils.read(pes.data, toRead)); if (pes.data.hasRemaining()) pending.add(0, pes); else putBack(pes.data); return toRead; } private PESPacket getPacket() throws IOException { if (pending.size() > 0) return pending.remove(0); PESPacket pkt; while ((pkt = nextPacket(getBuffer())) != null) { if (pkt.streamId == streamId) { if (pkt.pts != -1) { es.curPts = pkt.pts; } return pkt; } else addToStream(pkt); } return null; } @Override public Packet nextFrame(ByteBuffer buf) throws IOException { return es.getFrame(buf); } public DemuxerTrackMeta getMeta() { return new DemuxerTrackMeta(videoStream(streamId) ? VIDEO : (audioStream(streamId) ? AUDIO : OTHER), null, 0, 0, null); } } public class PlainTrack extends BaseTrack { private int frameNo; public PlainTrack(int streamId, PESPacket pkt) throws IOException { super(streamId, pkt); } public boolean isOpen() { return true; } public void close() throws IOException { } public Packet nextFrame(ByteBuffer buf) throws IOException { PESPacket pkt; if (pending.size() > 0) { pkt = pending.remove(0); } else { while ((pkt = nextPacket(getBuffer())) != null && pkt.streamId != streamId) addToStream(pkt); } return pkt == null ? null : new Packet(pkt.data, pkt.pts, 90000, 0, frameNo++, true, null); } public DemuxerTrackMeta getMeta() { return new DemuxerTrackMeta(videoStream(streamId) ? VIDEO : (audioStream(streamId) ? AUDIO : OTHER), null, 0, 0, null); } } public void seekByte(long offset) throws IOException { channel.position(offset); reset(); } public void reset() { for (BaseTrack track : streams.values()) { track.pending.clear(); } } private void addToStream(PESPacket pkt) throws IOException { BaseTrack pes = streams.get(pkt.streamId); if (pes == null) { if (isMPEG(pkt.data)) pes = new MPEGTrack(pkt.streamId, pkt); else pes = new PlainTrack(pkt.streamId, pkt); streams.put(pkt.streamId, pes); } else { pes.pending(pkt); } } public PESPacket nextPacket(ByteBuffer out) throws IOException { ByteBuffer dup = out.duplicate(); while (!psMarker(curMarker)) { if (!skipToMarker()) return null; } ByteBuffer fork = dup.duplicate(); readToNextMarker(dup); PESPacket pkt = readPESHeader(fork, curPos()); if (pkt.length == 0) { while (!psMarker(curMarker) && readToNextMarker(dup)) ; } else { read(dup, pkt.length - dup.position() + 6); } fork.limit(dup.position()); pkt.data = fork; return pkt; } public List<MPEGDemuxerTrack> getTracks() { return new ArrayList<MPEGDemuxerTrack>(streams.values()); } public List<MPEGDemuxerTrack> getVideoTracks() { List<MPEGDemuxerTrack> result = new ArrayList<MPEGDemuxerTrack>(); for (BaseTrack p : streams.values()) { if (videoStream(p.streamId)) result.add(p); } return result; } public List<MPEGDemuxerTrack> getAudioTracks() { List<MPEGDemuxerTrack> result = new ArrayList<MPEGDemuxerTrack>(); for (BaseTrack p : streams.values()) { if (audioStream(p.streamId)) result.add(p); } return result; } private boolean isMPEG(ByteBuffer _data) { ByteBuffer b = _data.duplicate(); int marker = 0xffffffff; int score = 0; boolean hasHeader = false, slicesStarted = false; while (b.hasRemaining()) { int code = b.get() & 0xff; marker = (marker << 8) | code; if (marker < 0x100 || marker > 0x1b8) continue; if (marker >= 0x1B0 && marker <= 0x1B8) { if ((hasHeader && marker != 0x1B5 && marker != 0x1B2) || slicesStarted) break; score += 5; } else if (marker == 0x100) { if (slicesStarted) break; hasHeader = true; } else if (marker > 0x100 && marker < 0x1B0) { if (!hasHeader) break; if (!slicesStarted) { score += 50; slicesStarted = true; } score += 1; } } return score > 50; } public static int probe(ByteBuffer b) { int marker = 0xffffffff; int score = 0; boolean inVideoPes = false, hasHeader = false, slicesStarted = false; while (b.hasRemaining()) { int code = b.get() & 0xff; marker = (marker << 8) | code; if (marker < 0x100 || marker > 0x1ff) continue; if (MPSUtils.videoMarker(marker)) { if (inVideoPes) break; else inVideoPes = true; } else if (marker >= 0x1B0 && marker <= 0x1B8 && inVideoPes) { if ((hasHeader && marker != 0x1B5 && marker != 0x1B2) || slicesStarted) break; score += 5; } else if (marker == 0x100 && inVideoPes) { if (slicesStarted) break; hasHeader = true; } else if (marker > 0x100 && marker < 0x1B0) { if (!hasHeader) break; if (!slicesStarted) { score += 50; slicesStarted = true; } score += 1; } } return score; } public static class MPEGPacket extends Packet { private long offset; private ByteBuffer seq; private int gop; private int timecode; public MPEGPacket(ByteBuffer data, long pts, long timescale, long duration, long frameNo, boolean keyFrame, TapeTimecode tapeTimecode) { super(data, pts, timescale, duration, frameNo, keyFrame, tapeTimecode); } public long getOffset() { return offset; } public ByteBuffer getSeq() { return seq; } public int getGOP() { return gop; } public int getTimecode() { return timecode; } } }